claude-teammate 0.1.147 → 0.1.149

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.147",
3
+ "version": "0.1.149",
4
4
  "description": "CLI bootstrapper for Claude Teammate.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -269,7 +269,7 @@ export async function runWorkerCommand({ projectRoot }) {
269
269
  });
270
270
  processedIssues.push(processed);
271
271
  }
272
- const activeIssues = processedIssues.filter((issue) => issue?.workflowState !== "done");
272
+ const activeIssues = processedIssues.filter((issue) => shouldDisplayIssueInState(issue));
273
273
  return {
274
274
  stateUpdates: { issueCount: result.total, issues: activeIssues },
275
275
  logInfo: { issues: result.issues.length, assigned: result.total }
@@ -2481,6 +2481,29 @@ export function hasNewHumanReplyWhileWaiting(detail, botUser, jira) {
2481
2481
  return getCommentTimestamp(latestHumanComment) > getCommentTimestamp(latestBotComment);
2482
2482
  }
2483
2483
 
2484
+ export function shouldDisplayIssueInState(issue) {
2485
+ if (!issue || issue.workflowState === "done") {
2486
+ return false;
2487
+ }
2488
+
2489
+ if (issue.workflowState !== "in_pr_review") {
2490
+ return true;
2491
+ }
2492
+
2493
+ if (String(issue.nextAction || "").trim() || String(issue.blocker || "").trim()) {
2494
+ return true;
2495
+ }
2496
+
2497
+ const sourceOfTruth = issue.sourceOfTruth || {};
2498
+ const githubIssues = Array.isArray(sourceOfTruth.githubIssues) ? sourceOfTruth.githubIssues : [];
2499
+ const pullRequests = Array.isArray(sourceOfTruth.pullRequests) ? sourceOfTruth.pullRequests : [];
2500
+
2501
+ return [...githubIssues, ...pullRequests].some((entry) => {
2502
+ const phase = String(entry?.phase || "").trim();
2503
+ return phase && phase !== "discovered" && phase !== "done";
2504
+ });
2505
+ }
2506
+
2484
2507
  export function shouldReuseNoCodeDecision({ issueMemory, latestInputId }) {
2485
2508
  return (
2486
2509
  issueMemory?.code_change_verdict === "no_code" &&
@@ -2585,13 +2608,16 @@ export function selectIssueCreationRepo({ detail, issueMemory, repos }) {
2585
2608
  }
2586
2609
 
2587
2610
  const repoByUrl = new Map(repos.map((repo) => [String(repo.url || "").trim(), repo]));
2588
- const explicitRepoUrls = [
2589
- ...extractRepoUrls(detail?.descriptionText || ""),
2590
- ...findRepoUrlsInComments(Array.isArray(detail?.comments) ? detail.comments : [])
2591
- ].filter((repoUrl) => repoByUrl.has(repoUrl));
2592
- const uniqueExplicitRepos = [...new Set(explicitRepoUrls)];
2593
- if (uniqueExplicitRepos.length === 1) {
2594
- return repoByUrl.get(uniqueExplicitRepos[0]) || null;
2611
+ const latestExplicitRepo = getLatestExplicitIssueCreationRepo(detail, repoByUrl);
2612
+ if (latestExplicitRepo) {
2613
+ return latestExplicitRepo;
2614
+ }
2615
+
2616
+ const descriptionRepoUrls = [...new Set(
2617
+ extractRepoUrls(detail?.descriptionText || "").filter((repoUrl) => repoByUrl.has(repoUrl))
2618
+ )];
2619
+ if (descriptionRepoUrls.length === 1) {
2620
+ return repoByUrl.get(descriptionRepoUrls[0]) || null;
2595
2621
  }
2596
2622
 
2597
2623
  const rememberedRepoUrls = [...new Set(
@@ -2606,6 +2632,22 @@ export function selectIssueCreationRepo({ detail, issueMemory, repos }) {
2606
2632
  return null;
2607
2633
  }
2608
2634
 
2635
+ function getLatestExplicitIssueCreationRepo(detail, repoByUrl) {
2636
+ const comments = Array.isArray(detail?.comments) ? [...detail.comments] : [];
2637
+ comments.sort(compareCommentsNewestFirst);
2638
+
2639
+ for (const comment of comments) {
2640
+ const commentRepoUrls = [...new Set(
2641
+ extractRepoUrls(comment?.bodyText || "").filter((repoUrl) => repoByUrl.has(repoUrl))
2642
+ )];
2643
+ if (commentRepoUrls.length === 1) {
2644
+ return repoByUrl.get(commentRepoUrls[0]) || null;
2645
+ }
2646
+ }
2647
+
2648
+ return null;
2649
+ }
2650
+
2609
2651
  function buildRepoSelectionComment(repos) {
2610
2652
  const choices = repos.map((repo) => `- ${repo.url}`).join("\n");
2611
2653
  return `I found multiple candidate repositories for this task. Please reply with the exact repo URL I should use:\n${choices}`;
@@ -75,11 +75,17 @@ export function createGitHubClient(config) {
75
75
 
76
76
  async listIssues(repoUrl, options = {}) {
77
77
  const repo = parseGitHubRepoUrl(repoUrl);
78
- const url = new URL(`https://api.github.com/repos/${repo.owner}/${repo.name}/issues`);
79
- url.searchParams.set("state", "open");
80
- url.searchParams.set("per_page", "100");
81
-
82
- const payload = await requestGitHub(url, config, { method: "GET" }, repo);
78
+ const payload = await requestGitHubPaginated(
79
+ `https://api.github.com/repos/${repo.owner}/${repo.name}/issues`,
80
+ config,
81
+ {
82
+ searchParams: {
83
+ state: "open",
84
+ per_page: "100"
85
+ }
86
+ },
87
+ repo
88
+ );
83
89
  const issues = Array.isArray(payload)
84
90
  ? payload
85
91
  .filter((issue) => !issue.pull_request)
@@ -91,11 +97,17 @@ export function createGitHubClient(config) {
91
97
 
92
98
  async listPullRequests(repoUrl, options = {}) {
93
99
  const repo = parseGitHubRepoUrl(repoUrl);
94
- const url = new URL(`https://api.github.com/repos/${repo.owner}/${repo.name}/pulls`);
95
- url.searchParams.set("state", "open");
96
- url.searchParams.set("per_page", "100");
97
-
98
- const payload = await requestGitHub(url, config, { method: "GET" }, repo);
100
+ const payload = await requestGitHubPaginated(
101
+ `https://api.github.com/repos/${repo.owner}/${repo.name}/pulls`,
102
+ config,
103
+ {
104
+ searchParams: {
105
+ state: "open",
106
+ per_page: "100"
107
+ }
108
+ },
109
+ repo
110
+ );
99
111
  const pullRequests = Array.isArray(payload)
100
112
  ? payload.map((pullRequest) => mapGitHubPullRequestSummary(pullRequest))
101
113
  : [];
@@ -299,11 +311,18 @@ export function createGitHubClient(config) {
299
311
 
300
312
  async listPrsNeedingReview() {
301
313
  const query = "is:pr is:open user-review-requested:@me -label:AI-reviewed";
302
- const url = new URL("https://api.github.com/search/issues");
303
- url.searchParams.set("q", query);
304
- url.searchParams.set("per_page", "10");
305
-
306
- const payload = await requestGitHub(url, config, { method: "GET" });
314
+ const payload = await requestGitHubPaginated(
315
+ "https://api.github.com/search/issues",
316
+ config,
317
+ {
318
+ searchParams: {
319
+ q: query,
320
+ per_page: "100"
321
+ }
322
+ },
323
+ null,
324
+ "items"
325
+ );
307
326
  return Array.isArray(payload.items)
308
327
  ? payload.items.map((item) => ({
309
328
  repoUrl: mapRepositoryApiUrlToHtmlUrl(item.repository_url),
@@ -600,11 +619,18 @@ function mapGitHubLabels(labels) {
600
619
 
601
620
  async function searchGitHubTrackedItems({ config, type, botLogin }) {
602
621
  const query = buildTrackedSearchQuery(type, botLogin);
603
- const url = new URL("https://api.github.com/search/issues");
604
- url.searchParams.set("q", query);
605
- url.searchParams.set("per_page", "100");
606
-
607
- const payload = await requestGitHub(url, config, { method: "GET" });
622
+ const payload = await requestGitHubPaginated(
623
+ "https://api.github.com/search/issues",
624
+ config,
625
+ {
626
+ searchParams: {
627
+ q: query,
628
+ per_page: "100"
629
+ }
630
+ },
631
+ null,
632
+ "items"
633
+ );
608
634
  const items = Array.isArray(payload.items) ? payload.items : [];
609
635
 
610
636
  return items
@@ -701,9 +727,9 @@ function buildReviewComments(suggestions) {
701
727
  }
702
728
 
703
729
  async function requestGitHub(url, config, init = {}, repo = null) {
704
- const targetUrl = typeof url === "string" ? new URL(url) : url;
730
+ const targetUrl = buildGitHubUrl(url, init.searchParams);
705
731
  const method = String(init.method || "GET").toUpperCase();
706
- const response = await fetchGitHubRaw(url, config, init);
732
+ const response = await fetchGitHubRaw(targetUrl, config, init);
707
733
 
708
734
  if (!response.ok) {
709
735
  const body = await response.text();
@@ -720,6 +746,49 @@ async function requestGitHub(url, config, init = {}, repo = null) {
720
746
  return response.json();
721
747
  }
722
748
 
749
+ async function requestGitHubPaginated(url, config, init = {}, repo = null, itemsKey = null) {
750
+ const results = [];
751
+ let page = 1;
752
+
753
+ while (true) {
754
+ const targetUrl = buildGitHubUrl(url, {
755
+ ...(init.searchParams || {}),
756
+ per_page: init.searchParams?.per_page || "100",
757
+ page: String(page)
758
+ });
759
+ const payload = await requestGitHub(targetUrl, config, init, repo);
760
+ const items = itemsKey ? payload?.[itemsKey] : payload;
761
+ const batch = Array.isArray(items) ? items : [];
762
+ results.push(...batch);
763
+
764
+ if (batch.length < 100) {
765
+ if (!itemsKey) {
766
+ return results;
767
+ }
768
+ return {
769
+ ...(payload && typeof payload === "object" ? payload : {}),
770
+ [itemsKey]: results
771
+ };
772
+ }
773
+
774
+ page += 1;
775
+ }
776
+ }
777
+
778
+ function buildGitHubUrl(url, searchParams = null) {
779
+ const targetUrl = typeof url === "string" ? new URL(url) : new URL(String(url));
780
+
781
+ if (searchParams) {
782
+ for (const [key, value] of Object.entries(searchParams)) {
783
+ if (value !== undefined && value !== null && value !== "") {
784
+ targetUrl.searchParams.set(key, value);
785
+ }
786
+ }
787
+ }
788
+
789
+ return targetUrl;
790
+ }
791
+
723
792
  async function requestGitHubGraphql(config, query, variables = {}) {
724
793
  const response = await fetch("https://api.github.com/graphql", {
725
794
  method: "POST",
@@ -68,7 +68,8 @@ export function createGitLabClient(config) {
68
68
 
69
69
  async listIssues(repoUrl, options = {}) {
70
70
  const repo = parseGitLabRepoUrl(repoUrl);
71
- const payload = await requestGitLabProject(config, repo, "/issues", {
71
+ const payload = await requestGitLabPaginated(config, projectPath(repo, "/issues"), {
72
+ baseUrl: `${repo.origin}/`,
72
73
  searchParams: {
73
74
  state: "opened",
74
75
  per_page: "100"
@@ -82,7 +83,8 @@ export function createGitLabClient(config) {
82
83
 
83
84
  async listPullRequests(repoUrl, options = {}) {
84
85
  const repo = parseGitLabRepoUrl(repoUrl);
85
- const payload = await requestGitLabProject(config, repo, "/merge_requests", {
86
+ const payload = await requestGitLabPaginated(config, projectPath(repo, "/merge_requests"), {
87
+ baseUrl: `${repo.origin}/`,
86
88
  searchParams: {
87
89
  state: "opened",
88
90
  per_page: "100"
@@ -292,7 +294,7 @@ export function createGitLabClient(config) {
292
294
  if (!currentUser.login) {
293
295
  continue;
294
296
  }
295
- const payload = await requestGitLab(config, "/api/v4/merge_requests", {
297
+ const payload = await requestGitLabPaginated(config, "/api/v4/merge_requests", {
296
298
  baseUrl,
297
299
  searchParams: {
298
300
  state: "opened",
@@ -787,15 +789,19 @@ async function listGitLabTrackedItems({ config, repoUrls = [], type, authorLogin
787
789
  const seen = new Set();
788
790
 
789
791
  for (const baseUrl of baseUrls) {
790
- const payload = await requestGitLab(config, type === "pr" ? "/api/v4/merge_requests" : "/api/v4/issues", {
791
- baseUrl,
792
- searchParams: {
793
- state: "opened",
794
- per_page: "100",
795
- scope: "all",
796
- author_username: authorLogin
792
+ const payload = await requestGitLabPaginated(
793
+ config,
794
+ type === "pr" ? "/api/v4/merge_requests" : "/api/v4/issues",
795
+ {
796
+ baseUrl,
797
+ searchParams: {
798
+ state: "opened",
799
+ per_page: "100",
800
+ scope: "all",
801
+ author_username: authorLogin
802
+ }
797
803
  }
798
- });
804
+ );
799
805
  const items = Array.isArray(payload) ? payload : [];
800
806
  for (const item of items) {
801
807
  const key = `${item.web_url || ""}|${item.iid || ""}`;
@@ -891,6 +897,30 @@ async function requestGitLab(config, pathOrUrl, init = {}) {
891
897
  return response.json();
892
898
  }
893
899
 
900
+ async function requestGitLabPaginated(config, pathOrUrl, init = {}) {
901
+ const results = [];
902
+ let page = 1;
903
+
904
+ while (true) {
905
+ const payload = await requestGitLab(config, pathOrUrl, {
906
+ ...init,
907
+ searchParams: {
908
+ ...(init.searchParams || {}),
909
+ per_page: init.searchParams?.per_page || "100",
910
+ page: String(page)
911
+ }
912
+ });
913
+ const batch = Array.isArray(payload) ? payload : [];
914
+ results.push(...batch);
915
+
916
+ if (batch.length < 100) {
917
+ return results;
918
+ }
919
+
920
+ page += 1;
921
+ }
922
+ }
923
+
894
924
  function determineGitLabBaseUrl(config) {
895
925
  const raw = String(config.GITLAB_BASE_URL || "").trim();
896
926
  if (raw) {
package/src/jira.js CHANGED
@@ -14,30 +14,44 @@ export function createJiraClient(config) {
14
14
 
15
15
  return {
16
16
  async fetchAssignedIssues() {
17
- const url = new URL(`${baseUrl}${SEARCH_PATH}`);
18
17
  const jql = buildAssignedIssuesJql(config.JIRA_BOT_EMAIL);
19
- url.searchParams.set("jql", jql);
20
- url.searchParams.set(
21
- "fields",
22
- [
23
- "summary",
24
- "status",
25
- "updated",
26
- "created",
27
- "project",
28
- "issuetype",
29
- "assignee",
30
- "labels",
31
- "parent"
32
- ].join(",")
33
- );
34
- url.searchParams.set("maxResults", "50");
35
-
36
- const payload = await requestJson(url, auth);
37
- const issues = Array.isArray(payload.issues) ? payload.issues.map((issue) => mapIssue(issue, baseUrl)) : [];
18
+ const issues = [];
19
+ let startAt = 0;
20
+ let total = 0;
21
+
22
+ do {
23
+ const url = new URL(`${baseUrl}${SEARCH_PATH}`);
24
+ url.searchParams.set("jql", jql);
25
+ url.searchParams.set(
26
+ "fields",
27
+ [
28
+ "summary",
29
+ "status",
30
+ "updated",
31
+ "created",
32
+ "project",
33
+ "issuetype",
34
+ "assignee",
35
+ "labels",
36
+ "parent"
37
+ ].join(",")
38
+ );
39
+ url.searchParams.set("maxResults", "100");
40
+ url.searchParams.set("startAt", String(startAt));
41
+
42
+ const payload = await requestJson(url, auth);
43
+ const batch = Array.isArray(payload.issues) ? payload.issues.map((issue) => mapIssue(issue, baseUrl)) : [];
44
+ total = Number.isInteger(payload.total) && payload.total > 0 ? payload.total : batch.length;
45
+ issues.push(...batch);
46
+ startAt += batch.length;
47
+
48
+ if (batch.length === 0) {
49
+ break;
50
+ }
51
+ } while (startAt < total);
38
52
 
39
53
  return {
40
- total: Number.isInteger(payload.total) && payload.total > 0 ? payload.total : issues.length,
54
+ total: total > 0 ? total : issues.length,
41
55
  issues,
42
56
  jql
43
57
  };
@@ -191,7 +205,7 @@ export function createJiraClient(config) {
191
205
 
192
206
  export function buildAssignedIssuesJql(botEmail) {
193
207
  const escapedEmail = botEmail.replace(/\\/gu, "\\\\").replace(/"/gu, '\\"');
194
- return `created >= -30d AND assignee = "${escapedEmail}" ORDER BY created ASC`;
208
+ return `created >= -30d AND assignee = "${escapedEmail}" AND statusCategory != Done ORDER BY created ASC`;
195
209
  }
196
210
 
197
211
  export function isBotAuthor(author, botUser, configuredEmail) {