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 +1 -1
- package/src/commands/worker.js +50 -8
- package/src/forge/github.js +91 -22
- package/src/forge/gitlab.js +41 -11
- package/src/jira.js +36 -22
package/package.json
CHANGED
package/src/commands/worker.js
CHANGED
|
@@ -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
|
|
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
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
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}`;
|
package/src/forge/github.js
CHANGED
|
@@ -75,11 +75,17 @@ export function createGitHubClient(config) {
|
|
|
75
75
|
|
|
76
76
|
async listIssues(repoUrl, options = {}) {
|
|
77
77
|
const repo = parseGitHubRepoUrl(repoUrl);
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
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 =
|
|
730
|
+
const targetUrl = buildGitHubUrl(url, init.searchParams);
|
|
705
731
|
const method = String(init.method || "GET").toUpperCase();
|
|
706
|
-
const response = await fetchGitHubRaw(
|
|
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",
|
package/src/forge/gitlab.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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:
|
|
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) {
|