claude-task-worker 0.19.0 → 0.20.0
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/dist/index.js +160 -13
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -572,11 +572,14 @@ async function listIssuesByNumbers(assignee, labels, excludeLabels, numbers) {
|
|
|
572
572
|
return results;
|
|
573
573
|
}
|
|
574
574
|
async function findOpenPrNumberByHeadRef(headRefName) {
|
|
575
|
+
return findPrNumberByHeadRef(headRefName, "open");
|
|
576
|
+
}
|
|
577
|
+
async function findPrNumberByHeadRef(headRefName, state = "open") {
|
|
575
578
|
const output = await execGh([
|
|
576
579
|
"pr",
|
|
577
580
|
"list",
|
|
578
581
|
"--state",
|
|
579
|
-
|
|
582
|
+
state,
|
|
580
583
|
"--head",
|
|
581
584
|
headRefName,
|
|
582
585
|
"--json",
|
|
@@ -587,6 +590,37 @@ async function findOpenPrNumberByHeadRef(headRefName) {
|
|
|
587
590
|
const prs = JSON.parse(output);
|
|
588
591
|
return prs.length > 0 ? prs[0].number : null;
|
|
589
592
|
}
|
|
593
|
+
async function getIssueState(issueNumber) {
|
|
594
|
+
const output = await execGh(["issue", "view", String(issueNumber), "--json", "state"]);
|
|
595
|
+
const parsed = JSON.parse(output);
|
|
596
|
+
return parsed.state;
|
|
597
|
+
}
|
|
598
|
+
async function findPrNumberClosingIssue(issueNumber) {
|
|
599
|
+
const { owner, name } = await getRepoInfo();
|
|
600
|
+
const query = `query($owner: String!, $name: String!, $number: Int!) {
|
|
601
|
+
repository(owner: $owner, name: $name) {
|
|
602
|
+
issue(number: $number) {
|
|
603
|
+
closedByPullRequestsReferences(first: 10, includeClosedPrs: true) { nodes { number state } }
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}`;
|
|
607
|
+
const output = await execGh([
|
|
608
|
+
"api",
|
|
609
|
+
"graphql",
|
|
610
|
+
"-f",
|
|
611
|
+
`query=${query}`,
|
|
612
|
+
"-F",
|
|
613
|
+
`owner=${owner}`,
|
|
614
|
+
"-F",
|
|
615
|
+
`name=${name}`,
|
|
616
|
+
"-F",
|
|
617
|
+
`number=${issueNumber}`
|
|
618
|
+
]);
|
|
619
|
+
const parsed = JSON.parse(output);
|
|
620
|
+
const nodes = parsed?.data?.repository?.issue?.closedByPullRequestsReferences?.nodes ?? [];
|
|
621
|
+
const validPr = nodes.find((node) => node.state === "MERGED" || node.state === "OPEN");
|
|
622
|
+
return validPr ? validPr.number : null;
|
|
623
|
+
}
|
|
590
624
|
async function getIssueSubIssuesSummary(issueNumber) {
|
|
591
625
|
const output = await execGh(["issue", "view", String(issueNumber), "--json", "subIssuesSummary"]);
|
|
592
626
|
const parsed = JSON.parse(output);
|
|
@@ -694,6 +728,9 @@ async function removeLabel(type, number, label) {
|
|
|
694
728
|
async function commentOnPR(prNumber, body) {
|
|
695
729
|
await execGh(["pr", "comment", String(prNumber), "--body", body]);
|
|
696
730
|
}
|
|
731
|
+
async function commentOnIssue(issueNumber, body) {
|
|
732
|
+
await execGh(["issue", "comment", String(issueNumber), "--body", body]);
|
|
733
|
+
}
|
|
697
734
|
async function createLabel(name, color, force) {
|
|
698
735
|
try {
|
|
699
736
|
const args = ["label", "create", name];
|
|
@@ -712,6 +749,11 @@ var DISALLOWED_TOOLS = [
|
|
|
712
749
|
// 呼ぶと処理未完のままプロセスが終了する。
|
|
713
750
|
"Monitor",
|
|
714
751
|
"ScheduleWakeup",
|
|
752
|
+
// SendMessage は過去に起動したサブエージェントをバックグラウンドでしか再開できず、
|
|
753
|
+
// 「完了通知を待ちます」でターンを終える誘因になる(通知が届く前にプロセスが正常終了し、
|
|
754
|
+
// PR未作成のまま完了扱いになる事故の実例あり)。未完のサブエージェントへの追加指示は
|
|
755
|
+
// フォアグラウンドの新規 Agent 起動で代替させる。
|
|
756
|
+
"SendMessage",
|
|
715
757
|
// 対話 / 承認: 自律実行セッションには回答・承認するユーザーが存在しない。
|
|
716
758
|
"AskUserQuestion",
|
|
717
759
|
"EnterPlanMode",
|
|
@@ -972,8 +1014,32 @@ async function assertRemoteTrackingExists(epicBranch) {
|
|
|
972
1014
|
// src/process-manager.ts
|
|
973
1015
|
import { spawn } from "node:child_process";
|
|
974
1016
|
init_table();
|
|
975
|
-
|
|
1017
|
+
|
|
1018
|
+
// src/task-result.ts
|
|
976
1019
|
var TASK_TIMEOUT_MS = 90 * 60 * 1e3;
|
|
1020
|
+
var STDERR_TAIL_LIMIT = 8 * 1024;
|
|
1021
|
+
function buildTaskResult(code, timedOut, stdout, stderrTail) {
|
|
1022
|
+
const emptyOutput = stdout.trim() === "";
|
|
1023
|
+
const completed = !timedOut && code === 0 && !emptyOutput;
|
|
1024
|
+
let output = stdout;
|
|
1025
|
+
if (timedOut) {
|
|
1026
|
+
output += `
|
|
1027
|
+
[worker] task timed out after ${TASK_TIMEOUT_MS / 1e3}s`;
|
|
1028
|
+
} else if (code === 0 && emptyOutput) {
|
|
1029
|
+
output += "[worker] claude exited with code 0 but produced no output (session aborted before the model ran; e.g. a skill preamble command failed)";
|
|
1030
|
+
} else if (!completed) {
|
|
1031
|
+
output += `
|
|
1032
|
+
[worker] claude exited with code ${code}`;
|
|
1033
|
+
}
|
|
1034
|
+
if (!completed && stderrTail.trim() !== "") {
|
|
1035
|
+
output += `
|
|
1036
|
+
[stderr] ${stderrTail.trim()}`;
|
|
1037
|
+
}
|
|
1038
|
+
return { status: completed ? "completed" : "failed", output };
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
// src/process-manager.ts
|
|
1042
|
+
var childProcesses = /* @__PURE__ */ new Map();
|
|
977
1043
|
var tasks = /* @__PURE__ */ new Map();
|
|
978
1044
|
var shuttingDown = false;
|
|
979
1045
|
function setShuttingDown() {
|
|
@@ -1105,11 +1171,20 @@ function run(command, args, id, title, workerName, path, onComplete, cwd) {
|
|
|
1105
1171
|
...cwd ? { cwd } : {}
|
|
1106
1172
|
});
|
|
1107
1173
|
childProcesses.set(id, child);
|
|
1108
|
-
child.stderr?.resume();
|
|
1109
1174
|
const outputChunks = [];
|
|
1110
1175
|
child.stdout?.on("data", (chunk) => {
|
|
1111
1176
|
outputChunks.push(chunk);
|
|
1112
1177
|
});
|
|
1178
|
+
const stderrChunks = [];
|
|
1179
|
+
let stderrLen = 0;
|
|
1180
|
+
child.stderr?.on("data", (chunk) => {
|
|
1181
|
+
stderrChunks.push(chunk);
|
|
1182
|
+
stderrLen += chunk.length;
|
|
1183
|
+
while (stderrChunks.length > 1 && stderrLen - stderrChunks[0].length >= STDERR_TAIL_LIMIT) {
|
|
1184
|
+
stderrLen -= stderrChunks[0].length;
|
|
1185
|
+
stderrChunks.shift();
|
|
1186
|
+
}
|
|
1187
|
+
});
|
|
1113
1188
|
let timedOut = false;
|
|
1114
1189
|
const timeoutHandle = setTimeout(() => {
|
|
1115
1190
|
timedOut = true;
|
|
@@ -1128,9 +1203,12 @@ function run(command, args, id, title, workerName, path, onComplete, cwd) {
|
|
|
1128
1203
|
timeoutHandle.unref();
|
|
1129
1204
|
child.on("close", async (code) => {
|
|
1130
1205
|
clearTimeout(timeoutHandle);
|
|
1131
|
-
const
|
|
1132
|
-
|
|
1133
|
-
|
|
1206
|
+
const { status: finalStatus, output } = buildTaskResult(
|
|
1207
|
+
code,
|
|
1208
|
+
timedOut,
|
|
1209
|
+
Buffer.concat(outputChunks).toString("utf-8"),
|
|
1210
|
+
Buffer.concat(stderrChunks).toString("utf-8").slice(-STDERR_TAIL_LIMIT)
|
|
1211
|
+
);
|
|
1134
1212
|
try {
|
|
1135
1213
|
await Promise.race([
|
|
1136
1214
|
onComplete?.(finalStatus, output) ?? Promise.resolve(),
|
|
@@ -1651,6 +1729,14 @@ async function deleteLocalBranch(branchName) {
|
|
|
1651
1729
|
console.error(`[worktree] Failed to delete local branch ${branchName}: ${stderr.trim()}`);
|
|
1652
1730
|
}
|
|
1653
1731
|
}
|
|
1732
|
+
async function localBranchExists(branchName) {
|
|
1733
|
+
try {
|
|
1734
|
+
await execFileAsync2("git", ["rev-parse", "--verify", "--quiet", `refs/heads/${branchName}`]);
|
|
1735
|
+
return true;
|
|
1736
|
+
} catch {
|
|
1737
|
+
return false;
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1654
1740
|
function getWorktreePath(worktreeId) {
|
|
1655
1741
|
return `${WORKTREES_DIR}/${worktreeId}`;
|
|
1656
1742
|
}
|
|
@@ -1814,13 +1900,20 @@ function createIssuePollingWorker(config) {
|
|
|
1814
1900
|
}
|
|
1815
1901
|
try {
|
|
1816
1902
|
if (status === "completed") {
|
|
1817
|
-
await config.onCompleted?.(issue.number);
|
|
1818
|
-
|
|
1903
|
+
const verified = await config.onCompleted?.(issue.number, worktreeId) ?? true;
|
|
1904
|
+
if (verified === false) {
|
|
1905
|
+
await notifyTaskFailed(config.name, name, issue.number, issue.title, issueUrl, output);
|
|
1906
|
+
} else {
|
|
1907
|
+
await notifyTaskCompleted(config.name, name, issue.number, issue.title, issueUrl, output);
|
|
1908
|
+
}
|
|
1819
1909
|
} else {
|
|
1820
1910
|
await notifyTaskFailed(config.name, name, issue.number, issue.title, issueUrl, output);
|
|
1821
1911
|
}
|
|
1822
1912
|
} catch (err) {
|
|
1823
1913
|
console.error(`[${config.name}] post-task error for #${issue.number}: ${err}`);
|
|
1914
|
+
await notifyTaskFailed(config.name, name, issue.number, issue.title, issueUrl, output).catch(
|
|
1915
|
+
(notifyErr) => console.error(`[${config.name}] notifyTaskFailed failed for #${issue.number}: ${notifyErr}`)
|
|
1916
|
+
);
|
|
1824
1917
|
} finally {
|
|
1825
1918
|
await removeLabel("issue", issue.number, "cc-in-progress").catch(
|
|
1826
1919
|
(err) => console.error(`[${config.name}] removeLabel cc-in-progress failed for #${issue.number}: ${err}`)
|
|
@@ -1852,18 +1945,47 @@ function createIssuePollingWorker(config) {
|
|
|
1852
1945
|
}
|
|
1853
1946
|
|
|
1854
1947
|
// src/workers/exec-issue.ts
|
|
1948
|
+
function prMissingComment(worktreeId) {
|
|
1949
|
+
return [
|
|
1950
|
+
"## PR\u672A\u4F5C\u6210\u306E\u307E\u307E\u81EA\u52D5\u5B9F\u884C\u304C\u7D42\u4E86\u3057\u307E\u3057\u305F\uFF08\u8981\u4EBA\u624B\u78BA\u8A8D\uFF09",
|
|
1951
|
+
`exec-issue \u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306F\u6B63\u5E38\u7D42\u4E86\uFF08exit 0\uFF09\u3057\u307E\u3057\u305F\u304C\u3001\u3053\u306E\u5B9F\u884C\u306E\u4F5C\u696D\u30D6\u30E9\u30F3\u30C1\uFF08\`${worktreeId}\`\uFF09\u3092 head \u3068\u3059\u308BPR\u3082\u3001\u672CIssue\u3092 closing \u53C2\u7167\u3059\u308BPR\u3082\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F\u3002PR\u4F5C\u6210\u524D\u306B\u30BB\u30C3\u30B7\u30E7\u30F3\u304C\u7D42\u4E86\u3057\u305F\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002`,
|
|
1952
|
+
"",
|
|
1953
|
+
"## \u72B6\u614B\u306E\u78BA\u8A8D",
|
|
1954
|
+
`- \u5909\u66F4\u304C push \u6E08\u307F\u306E\u5834\u5408\u306F\u30EA\u30E2\u30FC\u30C8\u30D6\u30E9\u30F3\u30C1 \`${worktreeId}\` \u304C\u6B8B\u3063\u3066\u3044\u307E\u3059\u3002\u5185\u5BB9\u3092\u78BA\u8A8D\u3057\u3001\u5FC5\u8981\u306A\u3089\u624B\u52D5\u3067PR\u3092\u4F5C\u6210\u3057\u3066\u304F\u3060\u3055\u3044`,
|
|
1955
|
+
"",
|
|
1956
|
+
"## \u5BFE\u5FDC\u5F8C\u306E\u9032\u3081\u65B9",
|
|
1957
|
+
"- \u81EA\u52D5\u5B9F\u884C\u3092\u3084\u308A\u76F4\u3059\u5834\u5408: `cc-need-human-check` \u30E9\u30D9\u30EB\u3092\u5916\u3057\u3001`cc-exec-issue` \u30E9\u30D9\u30EB\u3092\u4ED8\u3051\u76F4\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
1958
|
+
"- \u624B\u52D5\u3067PR\u3092\u4F5C\u6210\u3057\u305F\u5834\u5408\u306A\u3069\u5BFE\u5FDC\u6E08\u307F\u306E\u5834\u5408: `cc-need-human-check` \u30E9\u30D9\u30EB\u3092\u5916\u3057\u3066\u304F\u3060\u3055\u3044"
|
|
1959
|
+
].join("\n");
|
|
1960
|
+
}
|
|
1855
1961
|
var execIssueWorker = (opts = {}) => createIssuePollingWorker({
|
|
1856
1962
|
name: "exec-issue",
|
|
1857
1963
|
command: "/claude-task-worker:exec-issue",
|
|
1858
1964
|
triggerLabels: ["cc-exec-issue"],
|
|
1859
1965
|
epicFilters: opts.epicFilters,
|
|
1860
1966
|
labelFilters: opts.labelFilters,
|
|
1861
|
-
onCompleted: async (issueNumber) => {
|
|
1967
|
+
onCompleted: async (issueNumber, worktreeId) => {
|
|
1862
1968
|
if (await hasLabel("issue", issueNumber, "cc-need-human-check")) {
|
|
1863
1969
|
console.log(`[exec-issue] #${issueNumber}: cc-need-human-check present, skip cc-pr-created`);
|
|
1970
|
+
return false;
|
|
1971
|
+
}
|
|
1972
|
+
if (await getIssueState(issueNumber) === "CLOSED") {
|
|
1973
|
+
console.log(`[exec-issue] #${issueNumber}: issue closed by skill (no-change path), skip cc-pr-created`);
|
|
1864
1974
|
return;
|
|
1865
1975
|
}
|
|
1866
|
-
await
|
|
1976
|
+
const prNumber = await findPrNumberByHeadRef(worktreeId, "all") ?? await findPrNumberClosingIssue(issueNumber);
|
|
1977
|
+
if (prNumber !== null) {
|
|
1978
|
+
await addLabel("issue", issueNumber, "cc-pr-created");
|
|
1979
|
+
return;
|
|
1980
|
+
}
|
|
1981
|
+
console.error(
|
|
1982
|
+
`[exec-issue] #${issueNumber}: session exited without a PR (branch: ${worktreeId}); marking cc-need-human-check`
|
|
1983
|
+
);
|
|
1984
|
+
await addLabel("issue", issueNumber, "cc-need-human-check");
|
|
1985
|
+
await commentOnIssue(issueNumber, prMissingComment(worktreeId)).catch(
|
|
1986
|
+
(err) => console.error(`[exec-issue] commentOnIssue failed for #${issueNumber}: ${err}`)
|
|
1987
|
+
);
|
|
1988
|
+
return false;
|
|
1867
1989
|
}
|
|
1868
1990
|
})();
|
|
1869
1991
|
|
|
@@ -1897,6 +2019,14 @@ function createPrPollingWorker(config) {
|
|
|
1897
2019
|
try {
|
|
1898
2020
|
await removeWorktreeByBranch(pr.headRefName);
|
|
1899
2021
|
await deleteLocalBranch(pr.headRefName);
|
|
2022
|
+
if (await localBranchExists(pr.headRefName)) {
|
|
2023
|
+
console.error(
|
|
2024
|
+
`[${config.name}] PR #${pr.number}: branch ${pr.headRefName} is still checked out by another worktree; skipping this tick`
|
|
2025
|
+
);
|
|
2026
|
+
await removeLabel("pr", pr.number, LABEL_IN_PROGRESS).catch(() => {
|
|
2027
|
+
});
|
|
2028
|
+
continue;
|
|
2029
|
+
}
|
|
1900
2030
|
syncDefaultBranch(defaultBranch);
|
|
1901
2031
|
await createWorktreeFromBranch(worktreeId, defaultBranch);
|
|
1902
2032
|
const cwd = getWorktreePath(worktreeId);
|
|
@@ -2074,6 +2204,19 @@ var checkDependabotWorker = createPrPollingWorker({
|
|
|
2074
2204
|
});
|
|
2075
2205
|
|
|
2076
2206
|
// src/workers/epic-issue.ts
|
|
2207
|
+
function epicPrMissingComment(issueNumber) {
|
|
2208
|
+
return [
|
|
2209
|
+
"## Epic PR\u672A\u4F5C\u6210\u306E\u307E\u307E\u81EA\u52D5\u5B9F\u884C\u304C\u7D42\u4E86\u3057\u307E\u3057\u305F\uFF08\u8981\u4EBA\u624B\u78BA\u8A8D\uFF09",
|
|
2210
|
+
`create-epic-pr \u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306F\u6B63\u5E38\u7D42\u4E86\uFF08exit 0\uFF09\u3057\u307E\u3057\u305F\u304C\u3001Epic \u30D6\u30E9\u30F3\u30C1 \`cc-epic-${issueNumber}\` \u3092 head \u3068\u3059\u308B\u30AA\u30FC\u30D7\u30F3\u306APR\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F\u3002PR\u4F5C\u6210\u524D\u306B\u30BB\u30C3\u30B7\u30E7\u30F3\u304C\u7D42\u4E86\u3057\u305F\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002`,
|
|
2211
|
+
"",
|
|
2212
|
+
"## \u72B6\u614B\u306E\u78BA\u8A8D",
|
|
2213
|
+
`- \u5909\u66F4\u304C push \u6E08\u307F\u306E\u5834\u5408\u306F\u30EA\u30E2\u30FC\u30C8\u30D6\u30E9\u30F3\u30C1 \`cc-epic-${issueNumber}\` \u304C\u6B8B\u3063\u3066\u3044\u307E\u3059\u3002\u5185\u5BB9\u3092\u78BA\u8A8D\u3057\u3001\u5FC5\u8981\u306A\u3089\u624B\u52D5\u3067PR\u3092\u4F5C\u6210\u3057\u3066\u304F\u3060\u3055\u3044`,
|
|
2214
|
+
"",
|
|
2215
|
+
"## \u5BFE\u5FDC\u5F8C\u306E\u9032\u3081\u65B9",
|
|
2216
|
+
"- \u81EA\u52D5\u5B9F\u884C\u3092\u3084\u308A\u76F4\u3059\u5834\u5408: `cc-need-human-check` \u30E9\u30D9\u30EB\u3092\u5916\u3057\u3001`cc-epic-issue` \u30E9\u30D9\u30EB\u3092\u4ED8\u3051\u76F4\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
2217
|
+
"- \u624B\u52D5\u3067PR\u3092\u4F5C\u6210\u3057\u305F\u5834\u5408\u306A\u3069\u5BFE\u5FDC\u6E08\u307F\u306E\u5834\u5408: `cc-need-human-check` \u30E9\u30D9\u30EB\u3092\u5916\u3057\u3066\u304F\u3060\u3055\u3044"
|
|
2218
|
+
].join("\n");
|
|
2219
|
+
}
|
|
2077
2220
|
var epicIssueWorker = (opts = {}) => createIssuePollingWorker({
|
|
2078
2221
|
name: "epic-issue",
|
|
2079
2222
|
command: "/claude-task-worker:create-epic-pr",
|
|
@@ -2088,12 +2231,16 @@ var epicIssueWorker = (opts = {}) => createIssuePollingWorker({
|
|
|
2088
2231
|
return "proceed";
|
|
2089
2232
|
},
|
|
2090
2233
|
onCompleted: async (issueNumber) => {
|
|
2091
|
-
await addLabel("issue", issueNumber, "cc-pr-created");
|
|
2092
2234
|
const prNumber = await findOpenPrNumberByHeadRef(`cc-epic-${issueNumber}`);
|
|
2093
2235
|
if (prNumber === null) {
|
|
2094
|
-
console.error(`[epic-issue] Epic PR for branch cc-epic-${issueNumber} not found; skip
|
|
2095
|
-
|
|
2236
|
+
console.error(`[epic-issue] Epic PR for branch cc-epic-${issueNumber} not found; skip cc-pr-created`);
|
|
2237
|
+
await addLabel("issue", issueNumber, "cc-need-human-check");
|
|
2238
|
+
await commentOnIssue(issueNumber, epicPrMissingComment(issueNumber)).catch(
|
|
2239
|
+
(err) => console.error(`[epic-issue] commentOnIssue failed for #${issueNumber}: ${err}`)
|
|
2240
|
+
);
|
|
2241
|
+
return false;
|
|
2096
2242
|
}
|
|
2243
|
+
await addLabel("issue", issueNumber, "cc-pr-created");
|
|
2097
2244
|
await addLabel("pr", prNumber, "cc-epic-issue");
|
|
2098
2245
|
await addLabel("pr", prNumber, "cc-triage-scope");
|
|
2099
2246
|
}
|