claude-teammate 0.1.149 → 0.1.151
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/status.js +13 -2
- package/src/commands/worker.js +145 -10
- package/src/dashboard/ui.html +19 -3
package/package.json
CHANGED
package/src/commands/status.js
CHANGED
|
@@ -66,7 +66,7 @@ export async function runStatusCommand({ projectRoot, args = [] }) {
|
|
|
66
66
|
process.stdout.write("GitHub issues:\n");
|
|
67
67
|
for (const issue of state.githubIssues) {
|
|
68
68
|
process.stdout.write(
|
|
69
|
-
`- ${issue.repoUrl}
|
|
69
|
+
`- ${formatForgeQueueLabel(issue.repoUrl, issue.issueNumber)}${issue.title ? ` | ${issue.title}` : ""}${issue.workflowState ? ` | ${issue.workflowState}` : ""}${issue.prUrl ? ` | PR: ${issue.prUrl}` : ""}${issue.branchName ? ` | branch: ${issue.branchName}` : ""}${issue.action ? ` | action: ${issue.action}` : ""}\n`
|
|
70
70
|
);
|
|
71
71
|
}
|
|
72
72
|
}
|
|
@@ -75,9 +75,20 @@ export async function runStatusCommand({ projectRoot, args = [] }) {
|
|
|
75
75
|
process.stdout.write("Draft PRs:\n");
|
|
76
76
|
for (const pullRequest of state.draftPrs) {
|
|
77
77
|
process.stdout.write(
|
|
78
|
-
`- ${pullRequest.
|
|
78
|
+
`- ${formatForgeQueueLabel(pullRequest.repoUrl, pullRequest.pullRequestNumber)}${pullRequest.title ? ` | ${pullRequest.title}` : ""}${pullRequest.branchName ? ` | branch: ${pullRequest.branchName}` : ""}${pullRequest.status ? ` | status: ${pullRequest.status}` : ""}${pullRequest.action ? ` | action: ${pullRequest.action}` : ""}\n`
|
|
79
79
|
);
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
|
+
|
|
85
|
+
function formatForgeQueueLabel(repoUrl, itemNumber) {
|
|
86
|
+
try {
|
|
87
|
+
const url = new URL(String(repoUrl || "").trim());
|
|
88
|
+
const parts = url.pathname.split("/").filter(Boolean);
|
|
89
|
+
const repoName = parts[parts.length - 1] || "repo";
|
|
90
|
+
return `${repoName} #${itemNumber}`;
|
|
91
|
+
} catch {
|
|
92
|
+
return `repo #${itemNumber}`;
|
|
93
|
+
}
|
|
94
|
+
}
|
package/src/commands/worker.js
CHANGED
|
@@ -711,7 +711,7 @@ async function processJiraIssue({ issue, jira, forgeRegistry, botUser, config, p
|
|
|
711
711
|
let epicMemory = epicMemoryRecord.data;
|
|
712
712
|
let issueMemory = issueMemoryRecord.data;
|
|
713
713
|
let liveRepos = deriveTaskRepos(detail, epicMemory, forgeRegistry);
|
|
714
|
-
|
|
714
|
+
let labels = normalizeLabels(detail.labels);
|
|
715
715
|
|
|
716
716
|
if (isDoneStatus(detail.status)) {
|
|
717
717
|
return buildIssueState(detail, { repos: liveRepos }, issueMemory, null);
|
|
@@ -845,30 +845,44 @@ async function processJiraIssue({ issue, jira, forgeRegistry, botUser, config, p
|
|
|
845
845
|
await saveProgress("Repository ready. Analyzing requirements...");
|
|
846
846
|
}
|
|
847
847
|
|
|
848
|
+
const linkedSource = await fetchLinkedForgeState({
|
|
849
|
+
issueMemory,
|
|
850
|
+
forgeRegistry,
|
|
851
|
+
logger
|
|
852
|
+
});
|
|
853
|
+
await reconcileJiraWithLinkedForgeState({
|
|
854
|
+
detail,
|
|
855
|
+
jira,
|
|
856
|
+
linkedSource,
|
|
857
|
+
logger
|
|
858
|
+
});
|
|
859
|
+
labels = normalizeLabels(detail.labels);
|
|
860
|
+
|
|
848
861
|
if (!isInProgressStatus(detail.status)) {
|
|
849
|
-
return buildIssueState(detail, { repos: liveRepos }, issueMemory, null);
|
|
862
|
+
return buildIssueState(detail, { repos: liveRepos }, issueMemory, null, null, linkedSource);
|
|
850
863
|
}
|
|
851
864
|
|
|
852
865
|
if (labels.includes(OPERATIONAL_LABELS.waitingHuman)) {
|
|
853
866
|
if (!hasNewHumanReplyWhileWaiting(detail, botUser, jira)) {
|
|
854
|
-
return buildIssueState(detail, { repos: liveRepos }, issueMemory, null);
|
|
867
|
+
return buildIssueState(detail, { repos: liveRepos }, issueMemory, null, null, linkedSource);
|
|
855
868
|
}
|
|
856
869
|
|
|
857
870
|
await replaceJiraOperationalLabels(jira, detail, []);
|
|
871
|
+
labels = normalizeLabels(detail.labels);
|
|
858
872
|
await logger.info("Human reply received for waiting task; resuming processing", {
|
|
859
873
|
issue: detail.key
|
|
860
874
|
});
|
|
861
875
|
}
|
|
862
876
|
|
|
863
877
|
if (labels.includes(OPERATIONAL_LABELS.waitingApproval)) {
|
|
864
|
-
return buildIssueState(detail, { repos: liveRepos }, issueMemory, null);
|
|
878
|
+
return buildIssueState(detail, { repos: liveRepos }, issueMemory, null, null, linkedSource);
|
|
865
879
|
}
|
|
866
880
|
|
|
867
881
|
let claudeResult = null;
|
|
868
882
|
const latestInput = getLatestClarificationInput(detail, botUser, jira);
|
|
869
883
|
if (!latestInput) {
|
|
870
884
|
await saveProgress("Task restarted. Please comment with any updated instructions or reply to continue.");
|
|
871
|
-
return buildIssueState(detail, { repos: liveRepos }, issueMemory, null);
|
|
885
|
+
return buildIssueState(detail, { repos: liveRepos }, issueMemory, null, null, linkedSource);
|
|
872
886
|
}
|
|
873
887
|
|
|
874
888
|
const reopenContext = buildReopenContext({
|
|
@@ -881,7 +895,7 @@ async function processJiraIssue({ issue, jira, forgeRegistry, botUser, config, p
|
|
|
881
895
|
if (isClaudeUsageLimited()) {
|
|
882
896
|
await logger.info("Skipping Claude: usage limit active", { issue: detail.key, resumesAt: new Date(claudeUsageLimitUntil).toISOString() });
|
|
883
897
|
await saveProgress(`Claude usage limit active. Will retry automatically at ${new Date(claudeUsageLimitUntil).toISOString().replace("T", " ").slice(0, 16)} UTC.`);
|
|
884
|
-
return buildIssueState(detail, { repos: liveRepos }, issueMemory, null);
|
|
898
|
+
return buildIssueState(detail, { repos: liveRepos }, issueMemory, null, null, linkedSource);
|
|
885
899
|
}
|
|
886
900
|
|
|
887
901
|
await saveProgress("Analyzing requirements...");
|
|
@@ -2343,10 +2357,10 @@ async function continueGitHubIssueCreation({
|
|
|
2343
2357
|
issueMemory.github_issues = dedupeGitHubIssues(issueMemory.github_issues);
|
|
2344
2358
|
issueMemory = await saveIssueMemory(issueMemoryRecord.filePath, detail, issueMemory);
|
|
2345
2359
|
|
|
2346
|
-
const successComment =
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2360
|
+
const successComment = existingGitHubIssue
|
|
2361
|
+
? `Using existing repository issue: [#${githubIssue.number}](${githubIssue.url})`
|
|
2362
|
+
: `Created repository issue: [#${githubIssue.number}](${githubIssue.url})`;
|
|
2363
|
+
await ensureJiraComment(detail, jira, botUser, successComment);
|
|
2350
2364
|
await replaceJiraOperationalLabels(jira, detail, [OPERATIONAL_LABELS.waitingApproval]);
|
|
2351
2365
|
issueMemory.last_error = "";
|
|
2352
2366
|
issueMemory = await saveIssueMemory(issueMemoryRecord.filePath, detail, issueMemory);
|
|
@@ -3219,6 +3233,7 @@ function buildGitHubIssueState(issue, githubIssueMemory, action) {
|
|
|
3219
3233
|
repoUrl: githubIssueMemory.repo_url,
|
|
3220
3234
|
issueNumber: String(issue.number),
|
|
3221
3235
|
issueUrl: issue.url,
|
|
3236
|
+
title: issue.title || "",
|
|
3222
3237
|
state: issue.state || "",
|
|
3223
3238
|
labels: normalizeLabels(issue.labels),
|
|
3224
3239
|
workflowState: live.phase,
|
|
@@ -3236,6 +3251,7 @@ function buildDraftPrState(pullRequest, status, action) {
|
|
|
3236
3251
|
repoUrl,
|
|
3237
3252
|
pullRequestNumber: String(pullRequest.number),
|
|
3238
3253
|
pullRequestUrl: pullRequest.url,
|
|
3254
|
+
title: pullRequest.title || "",
|
|
3239
3255
|
branchName: pullRequest.headRef || "",
|
|
3240
3256
|
status,
|
|
3241
3257
|
labels: normalizeLabels(pullRequest.labels),
|
|
@@ -3258,6 +3274,125 @@ function buildDefaultIssueMemory(workflowState) {
|
|
|
3258
3274
|
};
|
|
3259
3275
|
}
|
|
3260
3276
|
|
|
3277
|
+
async function fetchLinkedForgeState({ issueMemory, forgeRegistry, logger }) {
|
|
3278
|
+
const linkedSource = {
|
|
3279
|
+
githubIssues: [],
|
|
3280
|
+
pullRequests: []
|
|
3281
|
+
};
|
|
3282
|
+
|
|
3283
|
+
const entries = Array.isArray(issueMemory?.github_issues) ? issueMemory.github_issues : [];
|
|
3284
|
+
const seenIssues = new Set();
|
|
3285
|
+
const seenPullRequests = new Set();
|
|
3286
|
+
|
|
3287
|
+
for (const entry of entries) {
|
|
3288
|
+
const repoUrl = String(entry?.repo_url || "").trim();
|
|
3289
|
+
if (!repoUrl) {
|
|
3290
|
+
continue;
|
|
3291
|
+
}
|
|
3292
|
+
|
|
3293
|
+
let provider;
|
|
3294
|
+
try {
|
|
3295
|
+
provider = forgeRegistry.getClientForRepo(repoUrl);
|
|
3296
|
+
} catch (error) {
|
|
3297
|
+
await logger.error("Failed to resolve linked forge client", { repo: repoUrl, error });
|
|
3298
|
+
continue;
|
|
3299
|
+
}
|
|
3300
|
+
|
|
3301
|
+
const issueNumber = String(entry?.number || "").trim();
|
|
3302
|
+
if (issueNumber) {
|
|
3303
|
+
const issueKey = `${repoUrl}#${issueNumber}`;
|
|
3304
|
+
if (!seenIssues.has(issueKey)) {
|
|
3305
|
+
seenIssues.add(issueKey);
|
|
3306
|
+
try {
|
|
3307
|
+
const issue = await provider.fetchIssue(repoUrl, issueNumber);
|
|
3308
|
+
linkedSource.githubIssues.push({
|
|
3309
|
+
repoUrl,
|
|
3310
|
+
issueNumber: String(issue.number || issueNumber),
|
|
3311
|
+
issueUrl: issue.url || entry.url || "",
|
|
3312
|
+
title: issue.title || "",
|
|
3313
|
+
state: issue.state || "",
|
|
3314
|
+
labels: normalizeLabels(issue.labels)
|
|
3315
|
+
});
|
|
3316
|
+
} catch (error) {
|
|
3317
|
+
await logger.error("Failed to fetch linked forge issue", {
|
|
3318
|
+
repo: repoUrl,
|
|
3319
|
+
issue: issueNumber,
|
|
3320
|
+
error
|
|
3321
|
+
});
|
|
3322
|
+
}
|
|
3323
|
+
}
|
|
3324
|
+
}
|
|
3325
|
+
|
|
3326
|
+
const pullRequestNumber = String(entry?.pr_number || "").trim();
|
|
3327
|
+
if (pullRequestNumber) {
|
|
3328
|
+
const pullKey = `${repoUrl}!${pullRequestNumber}`;
|
|
3329
|
+
if (!seenPullRequests.has(pullKey)) {
|
|
3330
|
+
seenPullRequests.add(pullKey);
|
|
3331
|
+
try {
|
|
3332
|
+
const pullRequest = await provider.fetchPullRequest(repoUrl, pullRequestNumber);
|
|
3333
|
+
linkedSource.pullRequests.push({
|
|
3334
|
+
repoUrl,
|
|
3335
|
+
pullRequestNumber: String(pullRequest.number || pullRequestNumber),
|
|
3336
|
+
pullRequestUrl: pullRequest.url || entry.pr_url || "",
|
|
3337
|
+
title: pullRequest.title || "",
|
|
3338
|
+
state: pullRequest.state || "",
|
|
3339
|
+
draft: pullRequest.draft === true,
|
|
3340
|
+
labels: normalizeLabels(pullRequest.labels),
|
|
3341
|
+
status: getPullRequestStatus(pullRequest.body || "")
|
|
3342
|
+
});
|
|
3343
|
+
} catch (error) {
|
|
3344
|
+
await logger.error("Failed to fetch linked pull request", {
|
|
3345
|
+
repo: repoUrl,
|
|
3346
|
+
pr: pullRequestNumber,
|
|
3347
|
+
error
|
|
3348
|
+
});
|
|
3349
|
+
}
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
}
|
|
3353
|
+
|
|
3354
|
+
return linkedSource;
|
|
3355
|
+
}
|
|
3356
|
+
|
|
3357
|
+
async function reconcileJiraWithLinkedForgeState({ detail, jira, linkedSource, logger }) {
|
|
3358
|
+
const pullRequests = Array.isArray(linkedSource?.pullRequests) ? linkedSource.pullRequests : [];
|
|
3359
|
+
const pullRequestStates = pullRequests.map((pullRequest) => derivePullRequestLiveState(pullRequest));
|
|
3360
|
+
|
|
3361
|
+
if (pullRequestStates.some((pullRequest) => pullRequest.phase === "done")) {
|
|
3362
|
+
if (!isDoneStatus(detail.status)) {
|
|
3363
|
+
const transition = await jira.transitionIssueToStatus(detail.key, "Done");
|
|
3364
|
+
if (transition.transitioned) {
|
|
3365
|
+
detail.status = "Done";
|
|
3366
|
+
}
|
|
3367
|
+
await logger.info("Linked pull request is complete; reconciling Jira to Done", {
|
|
3368
|
+
issue: detail.key,
|
|
3369
|
+
moved: transition.transitioned ? "yes" : "no"
|
|
3370
|
+
});
|
|
3371
|
+
}
|
|
3372
|
+
await replaceJiraOperationalLabels(jira, detail, []);
|
|
3373
|
+
return;
|
|
3374
|
+
}
|
|
3375
|
+
|
|
3376
|
+
if (pullRequestStates.length > 0 && normalizeLabels(detail.labels).includes(OPERATIONAL_LABELS.waitingApproval)) {
|
|
3377
|
+
await replaceJiraOperationalLabels(jira, detail, []);
|
|
3378
|
+
}
|
|
3379
|
+
|
|
3380
|
+
if (
|
|
3381
|
+
pullRequestStates.some((pullRequest) => pullRequest.phase === "in_pr_review") &&
|
|
3382
|
+
!isInReviewStatus(detail.status) &&
|
|
3383
|
+
!isDoneStatus(detail.status)
|
|
3384
|
+
) {
|
|
3385
|
+
const transition = await jira.transitionIssueToStatus(detail.key, "In Review");
|
|
3386
|
+
if (transition.transitioned) {
|
|
3387
|
+
detail.status = "In Review";
|
|
3388
|
+
}
|
|
3389
|
+
await logger.info("Linked pull request entered review; reconciling Jira to In Review", {
|
|
3390
|
+
issue: detail.key,
|
|
3391
|
+
moved: transition.transitioned ? "yes" : "no"
|
|
3392
|
+
});
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
3395
|
+
|
|
3261
3396
|
function buildPrSubtaskState(previous = {}) {
|
|
3262
3397
|
return {
|
|
3263
3398
|
phase: String(previous?.phase || "").trim(),
|
package/src/dashboard/ui.html
CHANGED
|
@@ -1947,10 +1947,19 @@ function renderHeartbeatQueues(data) {
|
|
|
1947
1947
|
const wrap = document.getElementById("heartbeat-queues-wrap");
|
|
1948
1948
|
const s = data.state || {};
|
|
1949
1949
|
const busy = s.pollerBusy || {};
|
|
1950
|
+
const forgeRepoName = (url) => {
|
|
1951
|
+
try {
|
|
1952
|
+
const u = new URL(url || "");
|
|
1953
|
+
const parts = u.pathname.split("/").filter(Boolean);
|
|
1954
|
+
return parts[parts.length - 1] || "repo";
|
|
1955
|
+
} catch {
|
|
1956
|
+
return "repo";
|
|
1957
|
+
}
|
|
1958
|
+
};
|
|
1950
1959
|
|
|
1951
1960
|
const rows = [
|
|
1952
1961
|
{ label: "Jira", items: s.issues || [], busyKey: "jira", type: "jira" },
|
|
1953
|
-
{ label: "GitHub", items: s.githubIssues || [], busyKey: "github", type: "
|
|
1962
|
+
{ label: "GitHub", items: s.githubIssues || [], busyKey: "github", type: "github" },
|
|
1954
1963
|
{ label: "Draft PRs", items: s.draftPrs || [], busyKey: "draftPr", type: "pr" },
|
|
1955
1964
|
{ label: "Review PRs", items: s.reviewPrs || [], busyKey: "reviewPr", type: "pr" }
|
|
1956
1965
|
];
|
|
@@ -1964,9 +1973,16 @@ function renderHeartbeatQueues(data) {
|
|
|
1964
1973
|
: "";
|
|
1965
1974
|
return `<div class="hbq-card${activeClass}" ${onclick}>${key}</div>`;
|
|
1966
1975
|
}
|
|
1967
|
-
|
|
1976
|
+
if (type === "github") {
|
|
1977
|
+
const label = esc(`${forgeRepoName(item.issueUrl || item.repoUrl)} ${item.issueNumber ? `#${item.issueNumber}` : "issue"}`);
|
|
1978
|
+
const href = item.issueUrl ? esc(item.issueUrl) : "#";
|
|
1979
|
+
const title = esc(item.title || label);
|
|
1980
|
+
return `<a class="hbq-card${activeClass}" href="${href}" target="_blank" style="text-decoration:none" title="${title}">${label}</a>`;
|
|
1981
|
+
}
|
|
1982
|
+
const label = esc(`${forgeRepoName(item.pullRequestUrl || item.repoUrl)} ${item.pullRequestNumber ? `#${item.pullRequestNumber}` : "PR"}`);
|
|
1968
1983
|
const href = item.pullRequestUrl ? esc(item.pullRequestUrl) : "#";
|
|
1969
|
-
|
|
1984
|
+
const title = esc(item.title || label);
|
|
1985
|
+
return `<a class="hbq-card${activeClass}" href="${href}" target="_blank" style="text-decoration:none" title="${title}">${label}</a>`;
|
|
1970
1986
|
};
|
|
1971
1987
|
|
|
1972
1988
|
wrap.innerHTML = `
|