claude-task-worker 0.115.0 → 0.117.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.
Files changed (64) hide show
  1. package/README.md +53 -7
  2. package/dist/chunk-3RY43CYR.js +41 -0
  3. package/dist/chunk-FAD5CNP4.js +3396 -0
  4. package/dist/chunk-N42YU3GJ.js +184 -0
  5. package/dist/claude-args.d.ts +62 -0
  6. package/dist/commands/cloud-setup.d.ts +14 -0
  7. package/dist/commands/codegraph.d.ts +39 -0
  8. package/dist/commands/design-md.d.ts +11 -0
  9. package/dist/commands/init.d.ts +17 -0
  10. package/dist/commands/install.d.ts +14 -0
  11. package/dist/commands/pen.d.ts +15 -0
  12. package/dist/commands/playwright.d.ts +27 -0
  13. package/dist/commands/run-command.d.ts +10 -0
  14. package/dist/commands/update.d.ts +1 -0
  15. package/dist/commands/version.d.ts +4 -0
  16. package/dist/config.d.ts +77 -0
  17. package/dist/custom-workers.d.ts +16 -0
  18. package/dist/dispatch-args.d.ts +13 -0
  19. package/dist/dispatcher-JKUMXTJ2.js +410 -0
  20. package/dist/dispatcher.d.ts +77 -0
  21. package/dist/gh.d.ts +112 -0
  22. package/dist/git.d.ts +7 -0
  23. package/dist/herdr-NDLUM6JF.js +309 -0
  24. package/dist/herdr-runner-33D6C2KI.js +307 -0
  25. package/dist/herdr-runner.d.ts +136 -0
  26. package/dist/herdr.d.ts +85 -0
  27. package/dist/index.d.ts +2 -0
  28. package/dist/index.js +855 -4984
  29. package/dist/last-run-pr.d.ts +16 -0
  30. package/dist/lib.d.ts +20 -0
  31. package/dist/lib.js +123 -0
  32. package/dist/process-manager.d.ts +38 -0
  33. package/dist/random-name.d.ts +2 -0
  34. package/dist/run-command-O2TMRPZK.js +8 -0
  35. package/dist/runcat.d.ts +23 -0
  36. package/dist/slack.d.ts +46 -0
  37. package/dist/table-DWVMNDS3.js +32 -0
  38. package/dist/table.d.ts +55 -0
  39. package/dist/task-result.d.ts +25 -0
  40. package/dist/transcript.d.ts +27 -0
  41. package/dist/user-config.d.ts +30 -0
  42. package/dist/workers/answer-issue-questions.d.ts +1 -0
  43. package/dist/workers/apply-ui-design.d.ts +1 -0
  44. package/dist/workers/check-dependabot.d.ts +1 -0
  45. package/dist/workers/create-issue.d.ts +1 -0
  46. package/dist/workers/create-ui-design.d.ts +2 -0
  47. package/dist/workers/epic-issue.d.ts +2 -0
  48. package/dist/workers/exec-issue.d.ts +15 -0
  49. package/dist/workers/fix-review-point.d.ts +1 -0
  50. package/dist/workers/issue-worker.d.ts +18 -0
  51. package/dist/workers/pr-worker.d.ts +18 -0
  52. package/dist/workers/registry.d.ts +8 -0
  53. package/dist/workers/resolve-conflict.d.ts +2 -0
  54. package/dist/workers/scheduled-worker.d.ts +8 -0
  55. package/dist/workers/triage-created-issue.d.ts +1 -0
  56. package/dist/workers/triage-pr.d.ts +5 -0
  57. package/dist/workers/ui-design.d.ts +14 -0
  58. package/dist/workers/update-coding-guidelines.d.ts +1 -0
  59. package/dist/workers/update-design-md.d.ts +1 -0
  60. package/dist/workers/update-issue.d.ts +1 -0
  61. package/dist/workers/update-requirement-rules.d.ts +1 -0
  62. package/dist/workers/worker-definition.d.ts +18 -0
  63. package/dist/worktree.d.ts +21 -0
  64. package/package.json +14 -3
@@ -0,0 +1,3396 @@
1
+ import {
2
+ CLOUD_WORKTREE_LABEL,
3
+ TASK_DISPLAY_LIMIT,
4
+ buildLogTableLines,
5
+ buildTaskTableLines,
6
+ logLines,
7
+ pushLogLine,
8
+ selectRecentTasks,
9
+ writeScreen
10
+ } from "./chunk-N42YU3GJ.js";
11
+
12
+ // src/gh.ts
13
+ import { createRequire } from "node:module";
14
+ var childProcess = createRequire(import.meta.url)("node:child_process");
15
+ function execGh(args) {
16
+ return new Promise((resolve5, reject) => {
17
+ childProcess.execFile("gh", args, (error, stdout, stderr) => {
18
+ if (error) {
19
+ reject(new Error(`gh ${args.join(" ")} failed: ${stderr || error.message}`));
20
+ return;
21
+ }
22
+ resolve5(stdout.trim());
23
+ });
24
+ });
25
+ }
26
+ function execGhAllowExit(args, allowedCodes) {
27
+ return new Promise((resolve5, reject) => {
28
+ childProcess.execFile("gh", args, (error, stdout, stderr) => {
29
+ if (error) {
30
+ const code = error.code;
31
+ if (typeof code === "number" && allowedCodes.includes(code)) {
32
+ resolve5(stdout.trim());
33
+ return;
34
+ }
35
+ reject(new Error(`gh ${args.join(" ")} failed: ${stderr || error.message}`));
36
+ return;
37
+ }
38
+ resolve5(stdout.trim());
39
+ });
40
+ });
41
+ }
42
+ async function getCurrentUser() {
43
+ return execGh(["api", "user", "--jq", ".login"]);
44
+ }
45
+ async function getRepoInfo() {
46
+ const output = await execGh(["repo", "view", "--json", "owner,name,defaultBranchRef"]);
47
+ const parsed = JSON.parse(output);
48
+ return { owner: parsed.owner.login, name: parsed.name, defaultBranch: parsed.defaultBranchRef.name };
49
+ }
50
+ async function listIssuesByLabel(assignee, labels, excludeLabels = [], epicFilter, limit = 10) {
51
+ const labelArgs = labels.flatMap((label) => ["--label", label]);
52
+ const searchTerms = ["sort:created-asc", "-is:blocked", ...excludeLabels.map((label) => `-label:"${label}"`)];
53
+ if (epicFilter && epicFilter.numbers.length > 0) {
54
+ for (const number of epicFilter.numbers) {
55
+ searchTerms.push(`parent-issue:${epicFilter.owner}/${epicFilter.repo}#${number}`);
56
+ }
57
+ }
58
+ const search = searchTerms.join(" ");
59
+ const output = await execGh([
60
+ "issue",
61
+ "list",
62
+ "--assignee",
63
+ assignee,
64
+ ...labelArgs,
65
+ "--json",
66
+ "number,title,labels,parent",
67
+ "--search",
68
+ search,
69
+ "--limit",
70
+ String(limit)
71
+ ]);
72
+ return JSON.parse(output);
73
+ }
74
+ async function listIssuesByNumbers(assignee, labels, excludeLabels, numbers) {
75
+ const results = [];
76
+ for (const number of numbers) {
77
+ try {
78
+ const output = await execGh([
79
+ "issue",
80
+ "view",
81
+ String(number),
82
+ "--json",
83
+ "number,title,labels,parent,assignees,state"
84
+ ]);
85
+ const parsed = JSON.parse(output);
86
+ if (parsed.state !== "OPEN") continue;
87
+ if (!parsed.assignees.some((a) => a.login === assignee)) continue;
88
+ const labelNames = new Set(parsed.labels.map((l) => l.name));
89
+ if (!labels.every((label) => labelNames.has(label))) continue;
90
+ if (excludeLabels.some((label) => labelNames.has(label))) continue;
91
+ results.push({
92
+ number: parsed.number,
93
+ title: parsed.title,
94
+ labels: parsed.labels,
95
+ parent: parsed.parent
96
+ });
97
+ } catch (err) {
98
+ console.error(`[gh] listIssuesByNumbers failed for #${number}: ${err}`);
99
+ }
100
+ }
101
+ return results;
102
+ }
103
+ async function findOpenPrNumberByHeadRef(headRefName) {
104
+ return findPrNumberByHeadRef(headRefName, "open");
105
+ }
106
+ async function findPrNumberByHeadRef(headRefName, state = "open") {
107
+ const output = await execGh([
108
+ "pr",
109
+ "list",
110
+ "--state",
111
+ state,
112
+ "--head",
113
+ headRefName,
114
+ "--json",
115
+ "number",
116
+ "--limit",
117
+ "1"
118
+ ]);
119
+ const prs = JSON.parse(output);
120
+ return prs.length > 0 ? prs[0].number : null;
121
+ }
122
+ async function findPrStateByHeadRef(headRefName) {
123
+ const output = await execGh([
124
+ "pr",
125
+ "list",
126
+ "--state",
127
+ "all",
128
+ "--head",
129
+ headRefName,
130
+ "--json",
131
+ "number,state,mergedAt",
132
+ "--limit",
133
+ "1"
134
+ ]);
135
+ const prs = JSON.parse(output);
136
+ if (prs.length === 0) return null;
137
+ return { number: prs[0].number, state: prs[0].state, mergedAt: prs[0].mergedAt ?? null };
138
+ }
139
+ async function getPrMergeable(prNumber) {
140
+ const output = await execGh(["pr", "view", String(prNumber), "--json", "mergeable"]);
141
+ const parsed = JSON.parse(output);
142
+ return typeof parsed?.mergeable === "string" ? parsed.mergeable : "UNKNOWN";
143
+ }
144
+ async function getIssueBody(issueNumber) {
145
+ const output = await execGh(["issue", "view", String(issueNumber), "--json", "body"]);
146
+ const parsed = JSON.parse(output);
147
+ return typeof parsed?.body === "string" ? parsed.body : "";
148
+ }
149
+ async function getIssueState(issueNumber) {
150
+ const output = await execGh(["issue", "view", String(issueNumber), "--json", "state"]);
151
+ const parsed = JSON.parse(output);
152
+ return parsed.state;
153
+ }
154
+ async function listPrsClosingIssue(issueNumber) {
155
+ const { owner, name } = await getRepoInfo();
156
+ const query = `query($owner: String!, $name: String!, $number: Int!) {
157
+ repository(owner: $owner, name: $name) {
158
+ issue(number: $number) {
159
+ closedByPullRequestsReferences(first: 10, includeClosedPrs: true) { nodes { number state headRefName baseRefName createdAt } }
160
+ }
161
+ }
162
+ }`;
163
+ const output = await execGh([
164
+ "api",
165
+ "graphql",
166
+ "-f",
167
+ `query=${query}`,
168
+ "-F",
169
+ `owner=${owner}`,
170
+ "-F",
171
+ `name=${name}`,
172
+ "-F",
173
+ `number=${issueNumber}`
174
+ ]);
175
+ const parsed = JSON.parse(output);
176
+ const nodes = parsed?.data?.repository?.issue?.closedByPullRequestsReferences?.nodes ?? [];
177
+ return nodes.map((node) => ({
178
+ number: node.number,
179
+ state: node.state ?? "",
180
+ headRefName: node.headRefName ?? "",
181
+ baseRefName: node.baseRefName ?? "",
182
+ createdAt: node.createdAt ?? ""
183
+ }));
184
+ }
185
+ async function fetchPrRef(owner, name, prNumber) {
186
+ try {
187
+ const parsed = JSON.parse(await execGh(["api", `repos/${owner}/${name}/pulls/${prNumber}`]));
188
+ return {
189
+ number: prNumber,
190
+ state: parsed?.merged_at ? "MERGED" : String(parsed?.state ?? "").toUpperCase(),
191
+ headRefName: parsed?.head?.ref ?? "",
192
+ baseRefName: parsed?.base?.ref ?? "",
193
+ createdAt: parsed?.created_at ?? "",
194
+ body: typeof parsed?.body === "string" ? parsed.body : ""
195
+ };
196
+ } catch (err) {
197
+ console.error(`[gh] failed to read PR #${prNumber}: ${err}`);
198
+ return null;
199
+ }
200
+ }
201
+ async function getPrDetail(prNumber) {
202
+ const { owner, name } = await getRepoInfo();
203
+ return fetchPrRef(owner, name, prNumber);
204
+ }
205
+ async function closeIssue(issueNumber, reason = "completed") {
206
+ const { owner, name } = await getRepoInfo();
207
+ await execGh([
208
+ "api",
209
+ "-X",
210
+ "PATCH",
211
+ `repos/${owner}/${name}/issues/${issueNumber}`,
212
+ "-f",
213
+ "state=closed",
214
+ "-f",
215
+ `state_reason=${reason}`
216
+ ]);
217
+ }
218
+ function parseClosingIssueNumbers(body) {
219
+ const pattern = /\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s*:?\s*#(\d+)\b(?!\d)/gi;
220
+ return [...new Set([...body.matchAll(pattern)].map((m) => Number(m[1])))];
221
+ }
222
+ function bodyClosesIssue(body, issueNumber) {
223
+ const pattern = new RegExp(`\\b(close[sd]?|fix(?:e[sd])?|resolve[sd]?)\\s*:?\\s*#${issueNumber}\\b(?!\\d)`, "i");
224
+ return pattern.test(body);
225
+ }
226
+ async function listPrsCrossReferencingIssue(issueNumber) {
227
+ const { owner, name } = await getRepoInfo();
228
+ const output = await execGh(["api", `repos/${owner}/${name}/issues/${issueNumber}/timeline`, "--paginate"]);
229
+ const events = JSON.parse(output);
230
+ const numbers = [
231
+ ...new Set(
232
+ events.filter(
233
+ (e) => e.event === "cross-referenced" && e.source?.issue?.pull_request != null && e.source.issue.repository?.full_name === `${owner}/${name}` && typeof e.source.issue.number === "number"
234
+ ).map((e) => e.source?.issue?.number)
235
+ )
236
+ ];
237
+ const refs = await Promise.all(numbers.map((number) => fetchPrRef(owner, name, number)));
238
+ return refs.filter((ref) => ref !== null && bodyClosesIssue(ref.body, issueNumber)).map(({ body: _body, ...ref }) => ref);
239
+ }
240
+ var ADD_CLOSE_ISSUE_REFERENCES = `mutation($issueId: ID!, $prId: ID!) {
241
+ addCloseIssueReferences(input: { issueId: $issueId, pullRequestIds: [$prId] }) { issue { number } }
242
+ }`;
243
+ async function linkClosingPr(issueNumber, prNumber) {
244
+ const { owner, name } = await getRepoInfo();
245
+ const issueId = JSON.parse(await execGh(["api", `repos/${owner}/${name}/issues/${issueNumber}`]))?.node_id;
246
+ const prId = JSON.parse(await execGh(["api", `repos/${owner}/${name}/pulls/${prNumber}`]))?.node_id;
247
+ if (typeof issueId !== "string" || typeof prId !== "string") {
248
+ throw new Error(`node_id not found (issue #${issueNumber} / PR #${prNumber})`);
249
+ }
250
+ await execGh([
251
+ "api",
252
+ "graphql",
253
+ "-f",
254
+ `query=${ADD_CLOSE_ISSUE_REFERENCES}`,
255
+ "-F",
256
+ `issueId=${issueId}`,
257
+ "-F",
258
+ `prId=${prId}`
259
+ ]);
260
+ }
261
+ async function getIssueSubIssuesSummary(issueNumber) {
262
+ const output = await execGh(["issue", "view", String(issueNumber), "--json", "subIssuesSummary"]);
263
+ const parsed = JSON.parse(output);
264
+ const summary = parsed?.subIssuesSummary;
265
+ if (!summary || typeof summary.total !== "number" || typeof summary.completed !== "number" || typeof summary.percentCompleted !== "number") {
266
+ return { total: 0, completed: 0, percentCompleted: 0 };
267
+ }
268
+ return {
269
+ total: summary.total,
270
+ completed: summary.completed,
271
+ percentCompleted: summary.percentCompleted
272
+ };
273
+ }
274
+ var COMPLETED_CHECK_STATES = /* @__PURE__ */ new Set([
275
+ "SUCCESS",
276
+ "FAILURE",
277
+ "ERROR",
278
+ "NEUTRAL",
279
+ "CANCELLED",
280
+ "SKIPPED",
281
+ "TIMED_OUT",
282
+ "ACTION_REQUIRED",
283
+ "STARTUP_FAILURE",
284
+ "STALE"
285
+ ]);
286
+ function isCICompleted(checks) {
287
+ if (checks.length === 0) return true;
288
+ if (checks.some((check) => check.state === "FAILURE" || check.state === "ERROR")) return true;
289
+ return checks.every((check) => COMPLETED_CHECK_STATES.has(check.state));
290
+ }
291
+ async function fetchPRChecks(prNumber) {
292
+ const output = await execGhAllowExit(["pr", "checks", String(prNumber), "--json", "state"], [0, 1, 8]);
293
+ if (!output) return [];
294
+ return JSON.parse(output);
295
+ }
296
+ async function listPullRequestsWithChecks(assignee, label, excludeLabels = [], limit = 5) {
297
+ const search = ["sort:created-asc", ...excludeLabels.map((l) => `-label:"${l}"`)].join(" ");
298
+ const args = [
299
+ "pr",
300
+ "list",
301
+ "--state",
302
+ "open",
303
+ "--json",
304
+ "number,title,labels,headRefName",
305
+ "--search",
306
+ search,
307
+ "--limit",
308
+ String(limit)
309
+ ];
310
+ if (assignee) {
311
+ args.push("--assignee", assignee);
312
+ }
313
+ if (label) {
314
+ args.push("--label", label);
315
+ }
316
+ const output = await execGh(args);
317
+ const prs = JSON.parse(output);
318
+ const withChecks = await Promise.all(prs.map(async (pr) => ({ ...pr, checks: await fetchPRChecks(pr.number) })));
319
+ return withChecks.filter((pr) => isCICompleted(pr.checks));
320
+ }
321
+ async function withRetry(fn, maxRetries = 5, baseDelayMs = 1e3) {
322
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
323
+ try {
324
+ return await fn();
325
+ } catch (err) {
326
+ if (attempt === maxRetries) throw err;
327
+ const delayMs = baseDelayMs * Math.pow(2, attempt - 1);
328
+ console.error(`[gh] Attempt ${attempt}/${maxRetries} failed, retrying in ${delayMs}ms...`);
329
+ await new Promise((resolve5) => setTimeout(resolve5, delayMs));
330
+ }
331
+ }
332
+ throw new Error("unreachable");
333
+ }
334
+ async function addLabel(type, number, label) {
335
+ await withRetry(async () => {
336
+ try {
337
+ await execGh([type, "edit", String(number), "--add-label", label]);
338
+ } catch (err) {
339
+ const message = err instanceof Error ? err.message : String(err);
340
+ if (message.includes("already exists") || message.includes("already has")) return;
341
+ throw err;
342
+ }
343
+ });
344
+ }
345
+ async function addAssignee(type, number, login) {
346
+ await withRetry(async () => {
347
+ await execGh([type, "edit", String(number), "--add-assignee", login]);
348
+ });
349
+ }
350
+ async function hasLabel(type, number, label) {
351
+ return withRetry(async () => {
352
+ const output = await execGh([type, "view", String(number), "--json", "labels"]);
353
+ const parsed = JSON.parse(output);
354
+ const labels = parsed?.labels ?? [];
355
+ return labels.some((l) => l.name === label);
356
+ });
357
+ }
358
+ async function findCommentSince(number, since, heading) {
359
+ const output = await execGh(["api", `repos/{owner}/{repo}/issues/${number}/comments?since=${since.toISOString()}`]);
360
+ const comments = JSON.parse(output);
361
+ const matched = comments.filter((comment) => comment.body.split("\n").some((line) => line.trim() === heading));
362
+ if (matched.length === 0) {
363
+ return null;
364
+ }
365
+ return matched[matched.length - 1].body;
366
+ }
367
+ async function listNumbersWithLabel(type, label, limit = 50) {
368
+ const output = await execGh([
369
+ type,
370
+ "list",
371
+ "--label",
372
+ label,
373
+ "--state",
374
+ "all",
375
+ "--json",
376
+ "number",
377
+ "--limit",
378
+ String(limit)
379
+ ]);
380
+ const parsed = JSON.parse(output);
381
+ return parsed.map((entry) => entry.number);
382
+ }
383
+ async function hasOpenBlockers(issueNumber) {
384
+ return withRetry(async () => {
385
+ const output = await execGh(["issue", "view", String(issueNumber), "--json", "blockedBy"]);
386
+ const parsed = JSON.parse(output);
387
+ const nodes = parsed?.blockedBy?.nodes ?? [];
388
+ return nodes.some((node) => node.state === "OPEN");
389
+ });
390
+ }
391
+ async function removeLabel(type, number, label) {
392
+ await withRetry(async () => {
393
+ try {
394
+ await execGh([type, "edit", String(number), "--remove-label", label]);
395
+ } catch (err) {
396
+ const message = err instanceof Error ? err.message : String(err);
397
+ if (message.includes("not found") || message.includes("does not exist") || message.includes("could not remove"))
398
+ return;
399
+ throw err;
400
+ }
401
+ });
402
+ }
403
+ async function commentOnPR(prNumber, body) {
404
+ await execGh(["pr", "comment", String(prNumber), "--body", body]);
405
+ }
406
+ async function commentOnIssue(issueNumber, body) {
407
+ await execGh(["issue", "comment", String(issueNumber), "--body", body]);
408
+ }
409
+ async function createPullRequest(base, head, title, body) {
410
+ const args = ["pr", "create", "--base", base, "--head", head, "--title", title, "--body", body];
411
+ const url = await execGh(args);
412
+ const matched = url.match(/\/pull\/(\d+)/);
413
+ if (!matched) throw new Error(`gh pr create returned an unexpected output: ${url}`);
414
+ return Number(matched[1]);
415
+ }
416
+ async function createLabel(name, color, force) {
417
+ try {
418
+ const args = ["label", "create", name];
419
+ if (color) args.push("--color", color);
420
+ if (force) args.push("--force");
421
+ await execGh(args);
422
+ return true;
423
+ } catch {
424
+ return false;
425
+ }
426
+ }
427
+
428
+ // src/dispatch-args.ts
429
+ import { resolve } from "node:path";
430
+ var FLAG_INCOMPATIBLE_COMMANDS = [
431
+ "init",
432
+ "apply-labels",
433
+ "install",
434
+ "update",
435
+ "cloud-setup",
436
+ "usage",
437
+ "version",
438
+ "list-workers"
439
+ ];
440
+ function collectFlagValues(argv, flag) {
441
+ const values = [];
442
+ for (let i = 0; i < argv.length; i++) {
443
+ if (argv[i] !== flag) continue;
444
+ const raw = argv[i + 1];
445
+ if (!raw || raw.startsWith("--")) {
446
+ console.error(`[dispatcher] ${flag} requires a value`);
447
+ process.exit(1);
448
+ }
449
+ values.push(raw);
450
+ }
451
+ return values;
452
+ }
453
+ function parseProjectFilters() {
454
+ return collectFlagValues(process.argv, "--project");
455
+ }
456
+ function hasProjectFilter() {
457
+ return process.argv.includes("--project");
458
+ }
459
+ function assertProjectCompatibleCommand(command) {
460
+ if (FLAG_INCOMPATIBLE_COMMANDS.includes(command)) {
461
+ console.error(`[dispatcher] --project cannot be used with the "${command}" command`);
462
+ process.exit(1);
463
+ }
464
+ }
465
+ var cachedCloudFlag;
466
+ function hasCloudFlag() {
467
+ if (cachedCloudFlag === void 0) {
468
+ cachedCloudFlag = process.argv.includes("--cloud");
469
+ }
470
+ return cachedCloudFlag;
471
+ }
472
+ var cachedDebugFlag;
473
+ function hasDebugFlag() {
474
+ if (cachedDebugFlag === void 0) {
475
+ cachedDebugFlag = process.argv.includes("--debug");
476
+ }
477
+ return cachedDebugFlag;
478
+ }
479
+ var INHERIT_CONFIG_FLAG = "--inherit-config";
480
+ var cachedInheritConfigPath;
481
+ function getInheritConfigPath() {
482
+ if (cachedInheritConfigPath === void 0) {
483
+ const values = collectFlagValues(process.argv, INHERIT_CONFIG_FLAG);
484
+ cachedInheritConfigPath = values.length > 0 ? resolve(values[values.length - 1]) : null;
485
+ }
486
+ return cachedInheritConfigPath;
487
+ }
488
+ function assertCloudCompatibleCommand(command) {
489
+ if (FLAG_INCOMPATIBLE_COMMANDS.includes(command)) {
490
+ console.error(`[worker] --cloud cannot be used with the "${command}" command`);
491
+ process.exit(1);
492
+ }
493
+ }
494
+ function shellQuote(value) {
495
+ if (value === "") return "''";
496
+ return `'${value.replace(/'/g, "'\\''")}'`;
497
+ }
498
+ function buildForwardedCommand(argv) {
499
+ const tokens = [];
500
+ for (let i = 0; i < argv.length; i++) {
501
+ if (argv[i] === "--project") {
502
+ i++;
503
+ continue;
504
+ }
505
+ if (argv[i] === INHERIT_CONFIG_FLAG && i + 1 < argv.length) {
506
+ tokens.push(argv[i], resolve(argv[i + 1]));
507
+ i++;
508
+ continue;
509
+ }
510
+ tokens.push(argv[i]);
511
+ }
512
+ return ["claude-task-worker", ...tokens.map(shellQuote)].join(" ");
513
+ }
514
+
515
+ // src/config.ts
516
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
517
+ import { dirname, isAbsolute, join, normalize, relative, resolve as resolve2, sep as SEP } from "node:path";
518
+ var DEFAULT_UI_DESIGN_CONFIG = {
519
+ enabled: false,
520
+ designDir: "designs",
521
+ yolo: false
522
+ };
523
+ var DEFAULT_WORKER_CONFIG = {
524
+ skill: "",
525
+ model: "opus",
526
+ advisorModel: "",
527
+ effort: "medium",
528
+ pollingIntervalSeconds: 60,
529
+ cooldownSeconds: 0,
530
+ maxConcurrentTasks: 1,
531
+ enabled: true
532
+ };
533
+ var WORKER_DEFAULTS = {
534
+ "answer-issue-questions": {
535
+ skill: "/claude-task-worker:answer-issue-questions",
536
+ model: "opus",
537
+ advisorModel: "",
538
+ effort: "medium",
539
+ pollingIntervalSeconds: 60,
540
+ cooldownSeconds: 0,
541
+ maxConcurrentTasks: 1,
542
+ enabled: true
543
+ },
544
+ "create-issue": {
545
+ skill: "/claude-task-worker:create-issue-from-issue-number",
546
+ model: "opus",
547
+ advisorModel: "",
548
+ effort: "medium",
549
+ pollingIntervalSeconds: 60,
550
+ cooldownSeconds: 0,
551
+ maxConcurrentTasks: 1,
552
+ enabled: true
553
+ },
554
+ "update-issue": {
555
+ skill: "/claude-task-worker:update-issue",
556
+ model: "sonnet",
557
+ advisorModel: "",
558
+ effort: "medium",
559
+ pollingIntervalSeconds: 60,
560
+ cooldownSeconds: 0,
561
+ maxConcurrentTasks: 1,
562
+ enabled: true
563
+ },
564
+ "exec-issue": {
565
+ skill: "/claude-task-worker:exec-issue",
566
+ model: "opus",
567
+ advisorModel: "",
568
+ effort: "medium",
569
+ pollingIntervalSeconds: 60,
570
+ cooldownSeconds: 0,
571
+ maxConcurrentTasks: 1,
572
+ enabled: true
573
+ },
574
+ "fix-review-point": {
575
+ skill: "/claude-task-worker:fix-review-point",
576
+ model: "opus",
577
+ advisorModel: "",
578
+ effort: "medium",
579
+ pollingIntervalSeconds: 60,
580
+ cooldownSeconds: 0,
581
+ maxConcurrentTasks: 1,
582
+ enabled: true
583
+ },
584
+ "triage-created-issue": {
585
+ skill: "/claude-task-worker:triage-created-issue",
586
+ model: "sonnet",
587
+ advisorModel: "",
588
+ effort: "medium",
589
+ pollingIntervalSeconds: 60,
590
+ cooldownSeconds: 0,
591
+ maxConcurrentTasks: 1,
592
+ enabled: true
593
+ },
594
+ "triage-pr": {
595
+ skill: "/claude-task-worker:triage-pr",
596
+ model: "opus",
597
+ advisorModel: "",
598
+ effort: "medium",
599
+ pollingIntervalSeconds: 60,
600
+ cooldownSeconds: 0,
601
+ maxConcurrentTasks: 1,
602
+ enabled: true
603
+ },
604
+ "resolve-conflict": {
605
+ skill: "/claude-task-worker:resolve-pr-conflict",
606
+ model: "sonnet",
607
+ advisorModel: "",
608
+ effort: "high",
609
+ pollingIntervalSeconds: 60,
610
+ cooldownSeconds: 0,
611
+ maxConcurrentTasks: 1,
612
+ enabled: true
613
+ },
614
+ "check-dependabot": {
615
+ skill: "/claude-task-worker:check-dependabot",
616
+ model: "sonnet",
617
+ advisorModel: "",
618
+ effort: "high",
619
+ pollingIntervalSeconds: 3600,
620
+ cooldownSeconds: 0,
621
+ maxConcurrentTasks: 1,
622
+ enabled: true
623
+ },
624
+ "epic-issue": {
625
+ skill: "/claude-task-worker:create-epic-pr",
626
+ model: "sonnet",
627
+ advisorModel: "",
628
+ effort: "medium",
629
+ pollingIntervalSeconds: 300,
630
+ cooldownSeconds: 0,
631
+ maxConcurrentTasks: 1,
632
+ enabled: true
633
+ },
634
+ "create-ui-design": {
635
+ skill: "/claude-task-worker:create-ui-design",
636
+ model: "opus",
637
+ advisorModel: "",
638
+ effort: "medium",
639
+ pollingIntervalSeconds: 60,
640
+ cooldownSeconds: 0,
641
+ maxConcurrentTasks: 1,
642
+ enabled: true
643
+ },
644
+ "apply-ui-design": {
645
+ skill: "/claude-task-worker:apply-ui-design",
646
+ model: "sonnet",
647
+ advisorModel: "",
648
+ effort: "medium",
649
+ pollingIntervalSeconds: 300,
650
+ cooldownSeconds: 0,
651
+ maxConcurrentTasks: 1,
652
+ enabled: true
653
+ },
654
+ // 以下3つは定期ワーカー(createScheduledWorker)。実行間隔そのものは
655
+ // SCHEDULE_INTERVAL_HOURS(24時間)と実行ログで決まり、pollingIntervalSeconds は
656
+ // 「24時間経過したかを確認する頻度」でしかない。
657
+ // model が opus なのは、成果物(CODING_GUIDELINES.md / .claude/requirements/ / DESIGN.md)が
658
+ // 後続の全 Issue・全デザインの前提として読まれ、誤った一般化がそのまま下流の手戻りになるため。
659
+ "update-coding-guidelines": {
660
+ skill: "/claude-task-worker:update-coding-guidelines",
661
+ model: "opus",
662
+ advisorModel: "",
663
+ effort: "medium",
664
+ pollingIntervalSeconds: 3600,
665
+ cooldownSeconds: 0,
666
+ maxConcurrentTasks: 1,
667
+ enabled: true
668
+ },
669
+ "update-requirement-rules": {
670
+ skill: "/claude-task-worker:update-requirement-rules",
671
+ model: "opus",
672
+ advisorModel: "",
673
+ effort: "medium",
674
+ pollingIntervalSeconds: 3600,
675
+ cooldownSeconds: 0,
676
+ maxConcurrentTasks: 1,
677
+ enabled: true
678
+ },
679
+ "update-design-md": {
680
+ skill: "/claude-task-worker:update-design-md",
681
+ model: "opus",
682
+ advisorModel: "",
683
+ effort: "medium",
684
+ pollingIntervalSeconds: 3600,
685
+ cooldownSeconds: 0,
686
+ maxConcurrentTasks: 1,
687
+ enabled: true
688
+ }
689
+ };
690
+ var SCHEDULED_WORKER_NAMES = [
691
+ "update-coding-guidelines",
692
+ "update-requirement-rules",
693
+ "update-design-md"
694
+ ];
695
+ var CLOUD_DONE_LABEL = "cc-cloud-done";
696
+ function isCloudWorker(_name) {
697
+ return hasCloudFlag();
698
+ }
699
+ function checkCloudAuth(input) {
700
+ if (input.status.kind === "unknown") return [];
701
+ const { loggedIn, authMethod, apiProvider, apiKeySource } = input.status;
702
+ const baseUrlSet = !!input.baseUrl;
703
+ if (loggedIn && apiProvider === "firstParty" && authMethod === "claude.ai" && !apiKeySource && !baseUrlSet) {
704
+ return [];
705
+ }
706
+ const prefix = `\u30AF\u30E9\u30A6\u30C9\u5B9F\u884C\uFF08--cloud \u30D5\u30E9\u30B0\uFF09\u306B\u306F claude.ai \u30A2\u30AB\u30A6\u30F3\u30C8\u3067\u306E\u30B5\u30A4\u30F3\u30A4\u30F3\u304C\u5FC5\u8981\u3067\u3059\u3002\u73FE\u5728\u306E\u8A8D\u8A3C\u69CB\u6210: ${authMethod} / ${apiProvider}\u3002`;
707
+ if (apiProvider === "bedrock" || apiProvider === "vertex") {
708
+ return [
709
+ `${prefix} \u7B2C\u4E09\u8005\u30D7\u30ED\u30D0\u30A4\u30C0\uFF08Bedrock / Vertex\uFF09\u3092\u4F7F\u3063\u3066\u3044\u308B\u5834\u5408: \u30AF\u30E9\u30A6\u30C9\u30BB\u30C3\u30B7\u30E7\u30F3\u306F Anthropic \u306E\u30A4\u30F3\u30D5\u30E9\u4E0A\u3067\u52D5\u304F\u305F\u3081\u5229\u7528\u3067\u304D\u307E\u305B\u3093\u3002CLAUDE_CODE_USE_BEDROCK / CLAUDE_CODE_USE_VERTEX \u3092\u89E3\u9664\u3059\u308B\u304B\u3001--cloud \u30D5\u30E9\u30B0\u3092\u5916\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
710
+ ];
711
+ }
712
+ if (apiKeySource || authMethod === "oauth_token") {
713
+ return [
714
+ `${prefix} API \u30AD\u30FC\u8A8D\u8A3C\uFF08ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN\uFF09\u306E\u5834\u5408: API \u30AD\u30FC\u3067\u306F\u30AF\u30E9\u30A6\u30C9\u30BB\u30C3\u30B7\u30E7\u30F3\u3092\u4F5C\u6210\u3067\u304D\u307E\u305B\u3093\u3002\u74B0\u5883\u5909\u6570\u3092\u89E3\u9664\u3057\u3066 claude auth login \u3067\u30B5\u30A4\u30F3\u30A4\u30F3\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
715
+ ];
716
+ }
717
+ if (!loggedIn) {
718
+ return [`${prefix} \u672A\u30B5\u30A4\u30F3\u30A4\u30F3\u306E\u5834\u5408: claude auth login \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002`];
719
+ }
720
+ if (baseUrlSet) {
721
+ return [
722
+ `${prefix} ANTHROPIC_BASE_URL \u3092\u8A2D\u5B9A\u3057\u3066\u3044\u308B\u5834\u5408: \u30AB\u30B9\u30BF\u30E0\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u69CB\u6210\u3067\u306F\u30AF\u30E9\u30A6\u30C9\u30BB\u30C3\u30B7\u30E7\u30F3\u3092\u5229\u7528\u3067\u304D\u307E\u305B\u3093\u3002\u89E3\u9664\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
723
+ ];
724
+ }
725
+ return [`${prefix} claude auth status --json \u306E\u51FA\u529B\u304B\u3089\u30AF\u30E9\u30A6\u30C9\u5B9F\u884C\u306E\u524D\u63D0\u6761\u4EF6\u3092\u5224\u5B9A\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002`];
726
+ }
727
+ function checkCloudConfig(input) {
728
+ if (!input.cloud) return [];
729
+ const errors = [];
730
+ if (input.scriptAvailable === false) {
731
+ errors.push(
732
+ `--cloud requires a pty, provided via the "script" command (creating a new cloud session requires a TTY, which the worker's spawn does not have on its own). "script" is unavailable \u2014 either the platform is not darwin/linux, or "script" is not on PATH. Drop the --cloud flag, or run on darwin/linux where "script" is available.`
733
+ );
734
+ }
735
+ if (input.auth !== void 0) errors.push(...checkCloudAuth(input.auth));
736
+ return errors;
737
+ }
738
+ var DEFAULT_CONFIG = {
739
+ fixReviewPointCallbackCommentMessage: "",
740
+ remoteEnvId: null,
741
+ labels: [],
742
+ workerFiles: [],
743
+ uiDesign: { ...DEFAULT_UI_DESIGN_CONFIG },
744
+ lastRun: {},
745
+ workers: {}
746
+ };
747
+ var CONFIG_PATH = join(process.cwd(), "claude-task-worker.json");
748
+ var LOCAL_CONFIG_PATH = join(process.cwd(), "claude-task-worker.local.json");
749
+ function defaultsFor(name) {
750
+ return WORKER_DEFAULTS[name] ?? DEFAULT_WORKER_CONFIG;
751
+ }
752
+ function parseWorkerEntry(name, val) {
753
+ const base = defaultsFor(name);
754
+ if (typeof val !== "object" || val === null || Array.isArray(val)) {
755
+ console.warn(`[config] invalid workers.${name}: expected object, using defaults`);
756
+ return null;
757
+ }
758
+ const entry = val;
759
+ const result = { ...base };
760
+ if ("skill" in entry) {
761
+ if (typeof entry.skill === "string" && entry.skill.length > 0) {
762
+ result.skill = entry.skill;
763
+ } else {
764
+ console.warn(`[config] invalid workers.${name}.skill: ${String(entry.skill)}, using default ${base.skill}`);
765
+ }
766
+ }
767
+ if ("model" in entry) {
768
+ if (typeof entry.model === "string" && entry.model.length > 0) {
769
+ result.model = entry.model;
770
+ } else {
771
+ console.warn(`[config] invalid workers.${name}.model: ${String(entry.model)}, using default ${base.model}`);
772
+ }
773
+ }
774
+ if ("advisorModel" in entry) {
775
+ if (typeof entry.advisorModel === "string") {
776
+ result.advisorModel = entry.advisorModel;
777
+ } else {
778
+ console.warn(
779
+ `[config] invalid workers.${name}.advisorModel: ${String(entry.advisorModel)}, using default ${JSON.stringify(base.advisorModel)}`
780
+ );
781
+ }
782
+ }
783
+ if ("effort" in entry) {
784
+ if (typeof entry.effort === "string" && entry.effort.length > 0) {
785
+ result.effort = entry.effort;
786
+ } else {
787
+ console.warn(`[config] invalid workers.${name}.effort: ${String(entry.effort)}, using default ${base.effort}`);
788
+ }
789
+ }
790
+ if ("pollingIntervalSeconds" in entry) {
791
+ const val2 = entry.pollingIntervalSeconds;
792
+ if (typeof val2 === "number" && Number.isFinite(val2) && val2 > 0) {
793
+ result.pollingIntervalSeconds = val2;
794
+ } else {
795
+ console.warn(
796
+ `[config] invalid workers.${name}.pollingIntervalSeconds: ${String(val2)}, using default ${base.pollingIntervalSeconds}`
797
+ );
798
+ }
799
+ }
800
+ if ("cooldownSeconds" in entry) {
801
+ const val2 = entry.cooldownSeconds;
802
+ if (typeof val2 === "number" && Number.isFinite(val2) && val2 >= 0) {
803
+ result.cooldownSeconds = val2;
804
+ } else {
805
+ console.warn(
806
+ `[config] invalid workers.${name}.cooldownSeconds: ${String(val2)}, using default ${base.cooldownSeconds}`
807
+ );
808
+ }
809
+ }
810
+ if ("maxConcurrentTasks" in entry) {
811
+ const val2 = entry.maxConcurrentTasks;
812
+ if (typeof val2 === "number" && Number.isInteger(val2) && val2 > 0) {
813
+ result.maxConcurrentTasks = val2;
814
+ } else {
815
+ console.warn(
816
+ `[config] invalid workers.${name}.maxConcurrentTasks: ${String(val2)}, using default ${base.maxConcurrentTasks}`
817
+ );
818
+ }
819
+ }
820
+ if ("enabled" in entry) {
821
+ if (typeof entry.enabled === "boolean") {
822
+ result.enabled = entry.enabled;
823
+ } else {
824
+ console.warn(`[config] invalid workers.${name}.enabled: ${String(entry.enabled)}, using default ${base.enabled}`);
825
+ }
826
+ }
827
+ if ("cloud" in entry) {
828
+ console.warn(
829
+ `[config] workers.${name}.cloud is removed; cloud execution now opts in via the --cloud flag at runtime. This setting is ignored.`
830
+ );
831
+ }
832
+ return result;
833
+ }
834
+ function parseUiDesignEntry(val) {
835
+ const result = { ...DEFAULT_UI_DESIGN_CONFIG };
836
+ if (typeof val !== "object" || val === null || Array.isArray(val)) {
837
+ console.warn(`[config] invalid uiDesign: expected object, using defaults`);
838
+ return result;
839
+ }
840
+ const entry = val;
841
+ if ("enabled" in entry) {
842
+ if (typeof entry.enabled === "boolean") {
843
+ result.enabled = entry.enabled;
844
+ } else {
845
+ console.warn(
846
+ `[config] invalid uiDesign.enabled: ${String(entry.enabled)}, using default ${DEFAULT_UI_DESIGN_CONFIG.enabled}`
847
+ );
848
+ }
849
+ }
850
+ if ("yolo" in entry) {
851
+ if (typeof entry.yolo === "boolean") {
852
+ result.yolo = entry.yolo;
853
+ } else {
854
+ console.warn(
855
+ `[config] invalid uiDesign.yolo: ${String(entry.yolo)}, using default ${DEFAULT_UI_DESIGN_CONFIG.yolo}`
856
+ );
857
+ }
858
+ }
859
+ if ("designDir" in entry) {
860
+ const normalized = typeof entry.designDir === "string" && entry.designDir.length > 0 ? normalize(entry.designDir) : null;
861
+ const isContained = normalized !== null && !isAbsolute(normalized) && normalized !== ".." && !normalized.startsWith(`..${SEP}`);
862
+ if (isContained) {
863
+ result.designDir = normalized;
864
+ } else {
865
+ console.warn(
866
+ `[config] invalid uiDesign.designDir: ${String(entry.designDir)}, using default ${DEFAULT_UI_DESIGN_CONFIG.designDir}`
867
+ );
868
+ }
869
+ }
870
+ return result;
871
+ }
872
+ function parseLastRunEntry(val) {
873
+ if (typeof val !== "object" || val === null || Array.isArray(val)) {
874
+ console.warn(`[config] invalid lastRun: expected object, ignoring`);
875
+ return {};
876
+ }
877
+ const result = {};
878
+ for (const [name, at] of Object.entries(val)) {
879
+ if (typeof at === "string" && !Number.isNaN(Date.parse(at))) {
880
+ result[name] = at;
881
+ } else {
882
+ console.warn(`[config] invalid lastRun.${name}: ${String(at)}, ignoring`);
883
+ }
884
+ }
885
+ return result;
886
+ }
887
+ function parseLabelsEntry(val) {
888
+ if (!Array.isArray(val)) {
889
+ console.warn(`[config] invalid labels: expected array of strings, ignoring`);
890
+ return [];
891
+ }
892
+ const result = [];
893
+ for (const item of val) {
894
+ if (typeof item === "string" && item.trim().length > 0) {
895
+ result.push(item.trim());
896
+ } else {
897
+ console.warn(`[config] invalid labels entry: ${String(item)}, ignoring`);
898
+ }
899
+ }
900
+ return result;
901
+ }
902
+ function parseWorkerFilesEntry(val) {
903
+ if (!Array.isArray(val)) {
904
+ console.warn(`[config] invalid workerFiles: expected array of strings, ignoring`);
905
+ return [];
906
+ }
907
+ const result = [];
908
+ for (const item of val) {
909
+ if (typeof item === "string" && item.trim().length > 0) {
910
+ result.push(item.trim());
911
+ } else {
912
+ console.warn(`[config] invalid workerFiles entry: ${String(item)}, ignoring`);
913
+ }
914
+ }
915
+ return result;
916
+ }
917
+ function resolveWorkerFilePath(entry, configDir, home) {
918
+ if (entry === "~") return home;
919
+ if (entry.startsWith("~/")) return resolve2(home, entry.slice(2));
920
+ return resolve2(configDir, entry);
921
+ }
922
+ function isPlainObject(val) {
923
+ return typeof val === "object" && val !== null && !Array.isArray(val);
924
+ }
925
+ function mergeConfigRaw(base, local) {
926
+ const result = { ...base };
927
+ for (const [key, val] of Object.entries(local)) {
928
+ const current = result[key];
929
+ result[key] = isPlainObject(current) && isPlainObject(val) ? mergeConfigRaw(current, val) : val;
930
+ }
931
+ return result;
932
+ }
933
+ function readRawConfig(path2) {
934
+ let parsed;
935
+ try {
936
+ parsed = JSON.parse(readFileSync(path2, "utf-8"));
937
+ } catch (err) {
938
+ if (err.code === "ENOENT") return {};
939
+ throw err;
940
+ }
941
+ if (!isPlainObject(parsed)) {
942
+ console.warn(`[config] invalid ${path2}: expected object, ignoring`);
943
+ return {};
944
+ }
945
+ return parsed;
946
+ }
947
+ function resolveInheritedRelativePaths(raw, configDir, repoRoot) {
948
+ const result = { ...raw };
949
+ const uiDesign = raw["uiDesign"];
950
+ if (isPlainObject(uiDesign) && typeof uiDesign.designDir === "string" && uiDesign.designDir.length > 0) {
951
+ result["uiDesign"] = {
952
+ ...uiDesign,
953
+ designDir: relative(repoRoot, resolve2(configDir, uiDesign.designDir)) || "."
954
+ };
955
+ }
956
+ const workerFiles = raw["workerFiles"];
957
+ if (Array.isArray(workerFiles)) {
958
+ result["workerFiles"] = workerFiles.map(
959
+ (entry) => typeof entry === "string" && entry.trim().length > 0 && !entry.trim().startsWith("~") ? resolve2(configDir, entry.trim()) : entry
960
+ );
961
+ }
962
+ return result;
963
+ }
964
+ function readInheritedRawConfig(path2, repoRoot) {
965
+ if (!existsSync(path2)) {
966
+ throw new Error(`${INHERIT_CONFIG_FLAG} ${path2} does not exist`);
967
+ }
968
+ return resolveInheritedRelativePaths(readRawConfig(path2), dirname(path2), repoRoot);
969
+ }
970
+ function readCwdRawConfig() {
971
+ return mergeConfigRaw(readRawConfig(CONFIG_PATH), readRawConfig(LOCAL_CONFIG_PATH));
972
+ }
973
+ function loadConfig() {
974
+ const inheritPath = getInheritConfigPath();
975
+ const cwdRaw = readCwdRawConfig();
976
+ const raw = inheritPath ? mergeConfigRaw(readInheritedRawConfig(inheritPath, process.cwd()), cwdRaw) : cwdRaw;
977
+ const result = {
978
+ ...DEFAULT_CONFIG,
979
+ labels: [],
980
+ workerFiles: [],
981
+ uiDesign: { ...DEFAULT_UI_DESIGN_CONFIG },
982
+ lastRun: {},
983
+ workers: {}
984
+ };
985
+ if ("remoteEnvId" in raw) {
986
+ const val = raw["remoteEnvId"];
987
+ if (val === null) {
988
+ result.remoteEnvId = null;
989
+ } else if (typeof val === "string" && val.trim().length > 0) {
990
+ result.remoteEnvId = val.trim();
991
+ } else {
992
+ console.warn(`[config] invalid remoteEnvId: ${String(val)}, using default null`);
993
+ }
994
+ }
995
+ if ("labels" in raw) {
996
+ result.labels = parseLabelsEntry(raw["labels"]);
997
+ }
998
+ if ("workerFiles" in raw) {
999
+ result.workerFiles = parseWorkerFilesEntry(raw["workerFiles"]);
1000
+ }
1001
+ if ("lastRun" in raw) {
1002
+ result.lastRun = parseLastRunEntry(raw["lastRun"]);
1003
+ }
1004
+ if ("fixReviewPointCallbackCommentMessage" in raw) {
1005
+ const val = raw["fixReviewPointCallbackCommentMessage"];
1006
+ if (typeof val === "string") {
1007
+ result.fixReviewPointCallbackCommentMessage = val;
1008
+ }
1009
+ }
1010
+ if ("uiDesign" in raw) {
1011
+ result.uiDesign = parseUiDesignEntry(raw["uiDesign"]);
1012
+ }
1013
+ if ("workers" in raw) {
1014
+ const workers = raw["workers"];
1015
+ if (typeof workers !== "object" || workers === null || Array.isArray(workers)) {
1016
+ console.warn(`[config] invalid workers: expected object, ignoring`);
1017
+ } else {
1018
+ for (const [name, val] of Object.entries(workers)) {
1019
+ const parsed = parseWorkerEntry(name, val);
1020
+ if (parsed) result.workers[name] = parsed;
1021
+ }
1022
+ }
1023
+ }
1024
+ return result;
1025
+ }
1026
+ function getWorkerConfig(workerName) {
1027
+ const config = loadConfig();
1028
+ return config.workers[workerName] ?? { ...defaultsFor(workerName) };
1029
+ }
1030
+ function isWorkerEnabled(workerName) {
1031
+ try {
1032
+ return getWorkerConfig(workerName).enabled;
1033
+ } catch (err) {
1034
+ console.warn(`[config] failed to load workers.${workerName}.enabled, treating it as enabled: ${err}`);
1035
+ return true;
1036
+ }
1037
+ }
1038
+ function partitionEnabledWorkers(names, isEnabled) {
1039
+ const enabled = [];
1040
+ const disabled = [];
1041
+ for (const name of names) (isEnabled(name) ? enabled : disabled).push(name);
1042
+ return { enabled, disabled };
1043
+ }
1044
+ function disabledWorkerMessage(workerName) {
1045
+ return `${workerName} is disabled by workers.${workerName}.enabled: false in claude-task-worker.json (or claude-task-worker.local.json). To run it, set workers.${workerName}.enabled to true or remove the key.`;
1046
+ }
1047
+ function getLastRunAt(workerName) {
1048
+ let at;
1049
+ try {
1050
+ const raw = readCwdRawConfig();
1051
+ at = "lastRun" in raw ? parseLastRunEntry(raw["lastRun"])[workerName] : void 0;
1052
+ } catch (err) {
1053
+ console.warn(`[config] failed to load lastRun, treating ${workerName} as never run: ${err}`);
1054
+ return void 0;
1055
+ }
1056
+ if (at === void 0) return void 0;
1057
+ const parsed = Date.parse(at);
1058
+ return Number.isNaN(parsed) ? void 0 : parsed;
1059
+ }
1060
+ function writeLastRun(repoRoot, workerName, at = /* @__PURE__ */ new Date()) {
1061
+ const path2 = join(repoRoot, "claude-task-worker.json");
1062
+ let raw = {};
1063
+ try {
1064
+ const parsed = JSON.parse(readFileSync(path2, "utf-8"));
1065
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
1066
+ raw = parsed;
1067
+ }
1068
+ } catch (err) {
1069
+ if (err.code !== "ENOENT") {
1070
+ console.warn(`[config] failed to read ${path2}, rewriting it with lastRun only: ${err}`);
1071
+ }
1072
+ }
1073
+ const current = typeof raw["lastRun"] === "object" && raw["lastRun"] !== null ? raw["lastRun"] : {};
1074
+ raw["lastRun"] = { ...current, [workerName]: at.toISOString() };
1075
+ writeFileSync(path2, `${JSON.stringify(raw, null, 2)}
1076
+ `, "utf-8");
1077
+ }
1078
+ function getRemoteEnvId() {
1079
+ try {
1080
+ return loadConfig().remoteEnvId;
1081
+ } catch (err) {
1082
+ console.warn(`[config] failed to load remoteEnvId, not passing --environment: ${err}`);
1083
+ return null;
1084
+ }
1085
+ }
1086
+ function getUiDesignConfig() {
1087
+ try {
1088
+ return loadConfig().uiDesign;
1089
+ } catch (err) {
1090
+ console.warn(`[config] failed to load uiDesign config, using defaults: ${err}`);
1091
+ return { ...DEFAULT_UI_DESIGN_CONFIG };
1092
+ }
1093
+ }
1094
+
1095
+ // src/user-config.ts
1096
+ import { readFileSync as readFileSync2, statSync } from "node:fs";
1097
+ import { homedir } from "node:os";
1098
+ import { isAbsolute as isAbsolute2, join as join2, resolve as resolve3 } from "node:path";
1099
+ var RESERVED_ALL = "all";
1100
+ var DEFAULT_RUN_MODE = "default";
1101
+ var DEFAULT_ADVISOR_ENABLED = false;
1102
+ var PERMISSION_MODES = [
1103
+ "manual",
1104
+ "auto",
1105
+ "acceptEdits",
1106
+ "dontAsk",
1107
+ "plan",
1108
+ "bypassPermissions"
1109
+ ];
1110
+ var DEFAULT_PERMISSION_MODE = "bypassPermissions";
1111
+ var UserConfigError = class extends Error {
1112
+ constructor(message) {
1113
+ super(message);
1114
+ this.name = "UserConfigError";
1115
+ }
1116
+ };
1117
+ function getConfigDir() {
1118
+ const xdg = process.env.XDG_CONFIG_HOME;
1119
+ const configHome = xdg && xdg.length > 0 ? xdg : join2(homedir(), ".config");
1120
+ return join2(configHome, "claude-task-worker");
1121
+ }
1122
+ function getUserConfigPath() {
1123
+ return join2(getConfigDir(), "config.json");
1124
+ }
1125
+ function isDirectory(path2) {
1126
+ try {
1127
+ return statSync(path2).isDirectory();
1128
+ } catch {
1129
+ return false;
1130
+ }
1131
+ }
1132
+ function parseConfigFile(path2) {
1133
+ let content;
1134
+ try {
1135
+ content = readFileSync2(path2, "utf-8");
1136
+ } catch (err) {
1137
+ if (err.code === "ENOENT") return void 0;
1138
+ throw err;
1139
+ }
1140
+ try {
1141
+ return JSON.parse(content);
1142
+ } catch (err) {
1143
+ if (err instanceof SyntaxError) {
1144
+ throw new UserConfigError(`config file contains invalid JSON: ${path2}: ${err.message}`);
1145
+ }
1146
+ throw err;
1147
+ }
1148
+ }
1149
+ function readRawConfig2() {
1150
+ return parseConfigFile(getUserConfigPath());
1151
+ }
1152
+ function parseMode(raw, path2) {
1153
+ if (!("mode" in raw)) return DEFAULT_RUN_MODE;
1154
+ const value = raw["mode"];
1155
+ if (value === "default" || value === "herdr") return value;
1156
+ console.warn(`[config] invalid mode: ${JSON.stringify(value)} in ${path2}, using "${DEFAULT_RUN_MODE}"`);
1157
+ return DEFAULT_RUN_MODE;
1158
+ }
1159
+ function parseAdvisor(raw, path2) {
1160
+ if (!("advisor" in raw)) return DEFAULT_ADVISOR_ENABLED;
1161
+ const value = raw["advisor"];
1162
+ if (typeof value === "boolean") return value;
1163
+ console.warn(`[config] invalid advisor: ${JSON.stringify(value)} in ${path2}, using ${DEFAULT_ADVISOR_ENABLED}`);
1164
+ return DEFAULT_ADVISOR_ENABLED;
1165
+ }
1166
+ function parsePermission(raw, path2) {
1167
+ if (!("permission" in raw)) return DEFAULT_PERMISSION_MODE;
1168
+ const value = raw["permission"];
1169
+ if (typeof value === "string" && PERMISSION_MODES.includes(value)) return value;
1170
+ console.warn(`[config] invalid permission: ${JSON.stringify(value)} in ${path2}, using "${DEFAULT_PERMISSION_MODE}"`);
1171
+ return DEFAULT_PERMISSION_MODE;
1172
+ }
1173
+ function loadUserConfig() {
1174
+ const path2 = getUserConfigPath();
1175
+ const raw = readRawConfig2();
1176
+ if (raw === void 0) {
1177
+ throw new UserConfigError(`config.json not found: ${path2}`);
1178
+ }
1179
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
1180
+ throw new UserConfigError(`config.json must contain a JSON object: ${path2}`);
1181
+ }
1182
+ if (!("projects" in raw) || typeof raw["projects"] !== "object" || raw["projects"] === null || Array.isArray(raw["projects"])) {
1183
+ throw new UserConfigError(`config.json must contain a "projects" section as an object: ${path2}`);
1184
+ }
1185
+ if ("projectGroups" in raw && (typeof raw["projectGroups"] !== "object" || raw["projectGroups"] === null || Array.isArray(raw["projectGroups"]))) {
1186
+ throw new UserConfigError(`config.json "projectGroups" must be an object: ${path2}`);
1187
+ }
1188
+ const mode = parseMode(raw, path2);
1189
+ const advisor = parseAdvisor(raw, path2);
1190
+ const permission = parsePermission(raw, path2);
1191
+ const rawProjects = raw["projects"];
1192
+ const rawProjectGroups = "projectGroups" in raw ? raw["projectGroups"] : {};
1193
+ const projectKeys = Object.keys(rawProjects);
1194
+ const groupKeys = Object.keys(rawProjectGroups);
1195
+ if (projectKeys.includes(RESERVED_ALL) || groupKeys.includes(RESERVED_ALL)) {
1196
+ throw new UserConfigError(
1197
+ `"${RESERVED_ALL}" is a reserved word and cannot be used as a key in "projects" or "projectGroups"`
1198
+ );
1199
+ }
1200
+ const groupKeySet = new Set(groupKeys);
1201
+ const duplicateKeys = projectKeys.filter((key) => groupKeySet.has(key));
1202
+ if (duplicateKeys.length > 0) {
1203
+ throw new UserConfigError(
1204
+ `"projects" and "projectGroups" share the same key namespace; duplicate keys are not allowed: ${duplicateKeys.join(", ")}`
1205
+ );
1206
+ }
1207
+ const projects = {};
1208
+ for (const [name, value] of Object.entries(rawProjects)) {
1209
+ if (name === "__proto__") {
1210
+ throw new UserConfigError(`"__proto__" cannot be used as a key in "projects": ${path2}`);
1211
+ }
1212
+ if (typeof value !== "string" || !isAbsolute2(value)) {
1213
+ console.warn(`[config] invalid projects.${name}: expected an absolute path, skipping`);
1214
+ continue;
1215
+ }
1216
+ if (!isDirectory(value)) {
1217
+ console.warn(`[config] projects.${name} does not exist as a directory: ${value}, skipping`);
1218
+ continue;
1219
+ }
1220
+ projects[name] = value;
1221
+ }
1222
+ const projectGroups = {};
1223
+ for (const [groupName, value] of Object.entries(rawProjectGroups)) {
1224
+ if (groupName === "__proto__") {
1225
+ throw new UserConfigError(`"__proto__" cannot be used as a key in "projectGroups": ${path2}`);
1226
+ }
1227
+ if (!Array.isArray(value)) {
1228
+ console.warn(`[config] invalid projectGroups.${groupName}: expected an array, skipping`);
1229
+ continue;
1230
+ }
1231
+ const members = [];
1232
+ for (const member of value) {
1233
+ if (typeof member !== "string" || !Object.prototype.hasOwnProperty.call(projects, member)) {
1234
+ console.warn(`[config] projectGroups.${groupName} references unknown project "${String(member)}", skipping`);
1235
+ continue;
1236
+ }
1237
+ members.push(member);
1238
+ }
1239
+ projectGroups[groupName] = members;
1240
+ }
1241
+ return { mode, advisor, permission, projects, projectGroups };
1242
+ }
1243
+ var cachedRunMode;
1244
+ function getRunMode() {
1245
+ if (cachedRunMode === void 0) {
1246
+ cachedRunMode = readRunMode();
1247
+ }
1248
+ return cachedRunMode;
1249
+ }
1250
+ var cachedAdvisorEnabled;
1251
+ function isAdvisorEnabled() {
1252
+ if (cachedAdvisorEnabled === void 0) {
1253
+ cachedAdvisorEnabled = readAdvisorEnabled();
1254
+ }
1255
+ return cachedAdvisorEnabled;
1256
+ }
1257
+ var cachedPermissionMode;
1258
+ function getPermissionMode() {
1259
+ if (cachedPermissionMode === void 0) {
1260
+ cachedPermissionMode = readTopLevel(parsePermission, DEFAULT_PERMISSION_MODE, "permission");
1261
+ }
1262
+ return cachedPermissionMode;
1263
+ }
1264
+ function readTopLevel(parse, fallback, label) {
1265
+ let raw;
1266
+ try {
1267
+ raw = readRawConfig2();
1268
+ } catch (err) {
1269
+ console.warn(`[config] failed to read config file, using default ${label}: ${err}`);
1270
+ return fallback;
1271
+ }
1272
+ if (raw === void 0) return fallback;
1273
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
1274
+ return fallback;
1275
+ }
1276
+ return parse(raw, getUserConfigPath());
1277
+ }
1278
+ function readAdvisorEnabled() {
1279
+ return readTopLevel(parseAdvisor, DEFAULT_ADVISOR_ENABLED, "advisor");
1280
+ }
1281
+ function readRunMode() {
1282
+ return readTopLevel(parseMode, DEFAULT_RUN_MODE, "mode");
1283
+ }
1284
+ function findProjectNameByPath(path2) {
1285
+ let config;
1286
+ try {
1287
+ config = loadUserConfig();
1288
+ } catch {
1289
+ return void 0;
1290
+ }
1291
+ const target = resolve3(path2);
1292
+ for (const [name, projectPath] of Object.entries(config.projects)) {
1293
+ if (resolve3(projectPath) === target) return name;
1294
+ }
1295
+ return void 0;
1296
+ }
1297
+ function resolveTargetProjects(requested, config) {
1298
+ const resolved = /* @__PURE__ */ new Map();
1299
+ for (const name of requested) {
1300
+ if (name === RESERVED_ALL) {
1301
+ for (const [projectName, projectPath] of Object.entries(config.projects)) {
1302
+ resolved.set(projectName, { name: projectName, path: projectPath });
1303
+ }
1304
+ continue;
1305
+ }
1306
+ if (Object.prototype.hasOwnProperty.call(config.projects, name)) {
1307
+ resolved.set(name, { name, path: config.projects[name] });
1308
+ continue;
1309
+ }
1310
+ if (Object.prototype.hasOwnProperty.call(config.projectGroups, name)) {
1311
+ for (const projectName of config.projectGroups[name]) {
1312
+ const projectPath = config.projects[projectName];
1313
+ if (projectPath === void 0) continue;
1314
+ resolved.set(projectName, { name: projectName, path: projectPath });
1315
+ }
1316
+ continue;
1317
+ }
1318
+ const availableProjects = Object.keys(config.projects).join(", ") || "(none)";
1319
+ const availableGroups = Object.keys(config.projectGroups).join(", ") || "(none)";
1320
+ throw new UserConfigError(
1321
+ `Unknown project or group: "${name}". Available projects: ${availableProjects}. Available groups: ${availableGroups}.`
1322
+ );
1323
+ }
1324
+ const resolvedProjects = Array.from(resolved.values());
1325
+ if (resolvedProjects.length === 0) {
1326
+ throw new UserConfigError(`No projects resolved from requested targets: ${requested.join(", ")}`);
1327
+ }
1328
+ return resolvedProjects;
1329
+ }
1330
+
1331
+ // src/claude-args.ts
1332
+ import { mkdirSync, renameSync, writeFileSync as writeFileSync2 } from "node:fs";
1333
+ import os from "node:os";
1334
+ import path from "node:path";
1335
+ var CLOUD_REPORT_HEADING = "## claude-task-worker \u5B9F\u884C\u7D50\u679C";
1336
+ var DISALLOWED_TOOLS = [
1337
+ // 遅延 / yield: 後続ウェイクアップ前提。print モードではウェイクアップが発火せず、
1338
+ // 呼ぶと処理未完のままプロセスが終了する。
1339
+ "Monitor",
1340
+ "ScheduleWakeup",
1341
+ // 対話 / 承認: 自律実行セッションには回答・承認するユーザーが存在しない。
1342
+ "AskUserQuestion",
1343
+ "EnterPlanMode",
1344
+ // スコープ外の副作用を伴う自動化: コード修正タスクに用途がなく、ユーザーの
1345
+ // クラウド routine / リモート環境へ副作用を及ぼしうる。
1346
+ "CronCreate",
1347
+ "CronDelete",
1348
+ "CronList",
1349
+ "RemoteTrigger",
1350
+ // 環境管理の競合: ワーカーは locked worktree の残骸問題のため claude 管理の worktree を
1351
+ // 意図的に避け、自前で worktree を生成して cwd として渡している。モデルが worktree を
1352
+ // 作成/切り替えると、この前提とクリーンアップが壊れる。
1353
+ "EnterWorktree"
1354
+ ];
1355
+ var DISALLOWED_TOOLS_ARG = DISALLOWED_TOOLS.join(",");
1356
+ var CLAUDE_SPAWN_ENV = {
1357
+ CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: "1",
1358
+ CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS: "0"
1359
+ };
1360
+ var SYSTEM_PROMPT_BASE = `\u3053\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306F \`claude-task-worker\` \u306E\u30EF\u30FC\u30AB\u30FC\u304B\u3089\u81EA\u52D5\u8D77\u52D5\u3055\u308C\u3066\u3044\u308B\uFF08\u5FDC\u7B54\u3067\u304D\u308B\u30E6\u30FC\u30B6\u30FC\u306F\u5E38\u99D0\u3057\u3066\u3044\u306A\u3044\uFF09\u3002\u4EE5\u4E0B\u306E\u81EA\u5F8B\u5B9F\u884C\u539F\u5247\u3092\u5FC5\u305A\u9075\u5B88\u3059\u308B\u3053\u3068\u3002
1361
+
1362
+ - \u30E6\u30FC\u30B6\u30FC\u3078\u306E\u78BA\u8A8D\u30FB\u8CEA\u554F\u306F\u884C\u308F\u305A\u3001\u8D77\u52D5\u3055\u308C\u305F\u30B9\u30AD\u30EB\u306E\u30EB\u30FC\u30EB\u306B\u5F93\u3063\u3066\u81EA\u5F8B\u7684\u306B\u5224\u65AD\u3059\u308B
1363
+ - \u66D6\u6627\u306A\u5834\u5408\u306F\u300C\u3088\u308A\u5B89\u5168\u306A\u5074\uFF08\u7834\u58CA\u7684\u3067\u306A\u3044\u5074\uFF09\u300D\u3092\u9078\u629E\u3057\u3001\u305D\u306E\u5224\u65AD\u3068\u6839\u62E0\u3092\u6700\u7D42\u5831\u544A\u306B\u660E\u8A18\u3059\u308B
1364
+ - \u5168\u30B9\u30C6\u30C3\u30D7\u3092\u5B8C\u9042\u3057\u3066\u304B\u3089\u7D42\u4E86\u3059\u308B\uFF08\u30B9\u30AD\u30EB\u306B\u5B9A\u7FA9\u3055\u308C\u305F\u4E2D\u65AD\u6761\u4EF6\u306B\u8A72\u5F53\u3057\u305F\u5834\u5408\u306E\u307F\u3001\u7406\u7531\u3092\u51FA\u529B\u3057\u3066\u7D42\u4E86\u3059\u308B\uFF09
1365
+ - \u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u3078\u4F5C\u696D\u3092\u59D4\u8B72\u3059\u308B\u5834\u5408\u306F\u3001\u4E0A\u8A18\u306E\u539F\u5247\u3092\u59D4\u8B72\u30D7\u30ED\u30F3\u30D7\u30C8\u306B\u3082\u660E\u8A18\u3057\u3066\u4F1D\u3048\u308B
1366
+ - \u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u306E\u5B8C\u4E86\u5831\u544A\u306F\u9D5C\u5451\u307F\u306B\u3057\u306A\u3044\u3002\`git diff\` \u7B49\u3067\u5B9F\u969B\u306E\u6210\u679C\u7269\u3092\u691C\u8A3C\u3057\u3066\u304B\u3089\u5B8C\u4E86\u6271\u3044\u306B\u3059\u308B
1367
+
1368
+ \u30B3\u30FC\u30C9\u306E\u63A2\u7D22\u30FB\u8ABF\u67FB\u3067\u306F\u4EE5\u4E0B\u306B\u5F93\u3046\u3053\u3068\u3002
1369
+
1370
+ - **CodeGraph \u304C\u4F7F\u3048\u308B\u5834\u5408\u306F \`Grep\`/\`Glob\` \u306B\u3088\u308B\u30C6\u30AD\u30B9\u30C8\u691C\u7D22\u3088\u308A\u512A\u5148\u3059\u308B**\u3002\u30B7\u30F3\u30DC\u30EB\u306E\u5B9A\u7FA9\u5143\u30FB\u53C2\u7167\u5143\u30FB\u547C\u3073\u51FA\u3057\u95A2\u4FC2\u3092\u69CB\u9020\u3068\u3057\u3066\u8FBF\u308C\u308B\u305F\u3081\u3001\u547D\u540D\u3086\u308C\u306B\u3088\u308B\u53D6\u308A\u3053\u307C\u3057\u304C\u8D77\u304D\u306B\u304F\u304F\u3001\u5FC5\u8981\u306A\u60C5\u5831\u306B\u5C11\u306A\u3044\u8A66\u884C\u3067\u5230\u9054\u3067\u304D\u308B
1371
+ - **\u5229\u7528\u53EF\u5426\u306F codegraph \u7CFB\u306E MCP \u30C4\u30FC\u30EB\uFF08\`codegraph_explore\` \u7B49\uFF09\u304C\u81EA\u5206\u306B\u4E0E\u3048\u3089\u308C\u3066\u3044\u308B\u304B\u3060\u3051\u3067\u5224\u65AD\u3059\u308B**\u3002\u7121\u3051\u308C\u3070\u300C\u5229\u7528\u4E0D\u53EF\u300D\u3068\u5373\u65AD\u3057\u3066\u30C6\u30AD\u30B9\u30C8\u691C\u7D22\u3078\u9032\u307F\u3001\u5224\u5B9A\u306B\u624B\u9593\u3092\u304B\u3051\u306A\u3044
1372
+ - \u672A\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u306E\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3067\u306F MCP \u30C4\u30FC\u30EB\u304C\u3042\u3063\u3066\u3082\u30A8\u30E9\u30FC\u3084\u7A7A\u306E\u7D50\u679C\u304C\u8FD4\u308B\u3002\u305D\u306E\u5834\u5408\u3082\u30C6\u30AD\u30B9\u30C8\u691C\u7D22\u3078\u5207\u308A\u66FF\u3048\u308B\u3060\u3051\u3067\u3088\u304F\u3001\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u3092\u7528\u610F\u3057\u3088\u3046\u3068\u3057\u306A\u3044\uFF08\u30BF\u30B9\u30AF\u306E\u8CAC\u52D9\u5916\uFF09
1373
+ - CodeGraph \u304C\u8FD4\u3057\u305F\u30BD\u30FC\u30B9\u306F\u300C\u8AAD\u307F\u7D42\u3048\u305F\u3082\u306E\u300D\u3068\u3057\u3066\u6271\u3044\u3001\u540C\u3058\u7B87\u6240\u3092 \`Grep\`/\`Read\` \u3067\u88CF\u53D6\u308A\u3057\u76F4\u3055\u306A\u3044\u3002\u305F\u3060\u3057\u51FA\u529B\u306B staleness\uFF08\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u304C\u53E4\u3044\u65E8\uFF09\u306E\u8B66\u544A\u304C\u51FA\u3066\u3044\u308B\u5834\u5408\u306F\u8A72\u5F53\u30D5\u30A1\u30A4\u30EB\u3092 \`Read\` \u3057\u3066\u73FE\u7269\u3092\u78BA\u8A8D\u3059\u308B
1374
+ - \u8A2D\u5B9A\u30D5\u30A1\u30A4\u30EB\u30FB\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8\u30FB\u30B3\u30E1\u30F3\u30C8/\u6587\u5B57\u5217\u30EA\u30C6\u30E9\u30EB\u30FB\u672A\u5BFE\u5FDC\u8A00\u8A9E\u306A\u3069 CodeGraph \u304C\u6271\u308F\u306A\u3044\u5BFE\u8C61\u306F\u3001\u5F93\u6765\u3069\u304A\u308A\u30C6\u30AD\u30B9\u30C8\u691C\u7D22\u3067\u88DC\u3046
1375
+ - \u63A2\u7D22\u3092\u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u3078\u59D4\u8B72\u3059\u308B\u5834\u5408\u306F\u3001\u3053\u306E\u65B9\u91DD\u3082\u59D4\u8B72\u30D7\u30ED\u30F3\u30D7\u30C8\u306B\u660E\u8A18\u3057\u3066\u4F1D\u3048\u308B`;
1376
+ var OPUS_SYSTEM_PROMPT_ADDENDUM = `\u6210\u679C\u7269\u306E\u5206\u91CF\u3068\u30B9\u30B3\u30FC\u30D7\u306B\u3064\u3044\u3066\u306F\u4EE5\u4E0B\u306B\u5F93\u3046\u3053\u3068\u3002
1377
+
1378
+ - \u4F9D\u983C\u3055\u308C\u305F\u30B9\u30B3\u30FC\u30D7\u3060\u3051\u3092\u6210\u679C\u7269\u306B\u3059\u308B\u3002\u5468\u8FBA\u306E\u30EA\u30D5\u30A1\u30AF\u30BF\u30FB\u547D\u540D\u6574\u7406\u30FB\u6C17\u3065\u3044\u305F\u5225\u306E\u6539\u5584\u3092\u52DD\u624B\u306B\u8DB3\u3055\u306A\u3044\u3002\u6C17\u3065\u3044\u305F\u70B9\u306F\u5B9F\u88C5\u305B\u305A\u3001\u6700\u7D42\u5831\u544A\u306B1\u884C\u3067\u6319\u3052\u308B\u3060\u3051\u306B\u3059\u308B
1379
+ - \u4F9D\u983C\u306E\u524D\u63D0\u304C\u8AA4\u3063\u3066\u3044\u308B\u3068\u8003\u3048\u308B\u5834\u5408\u3082\u3001\u6307\u6458\u30921-2\u884C\u6DFB\u3048\u305F\u3046\u3048\u3067**\u4F9D\u983C\u3069\u304A\u308A\u306E\u30B9\u30B3\u30FC\u30D7\u3067**\u5B8C\u9042\u3059\u308B\uFF08\u9ED9\u3063\u3066\u7E2E\u5C0F\u30FB\u62E1\u5927\u30FB\u5225\u7269\u3078\u306E\u7F6E\u304D\u63DB\u3048\u3092\u3057\u306A\u3044\uFF09
1380
+ - \u5909\u66F4\u306B\u5165\u308B\u524D\u306B\u3001\u4F9D\u983C\u304C\u660E\u793A\u3057\u3066\u3044\u306A\u304F\u3066\u3082\u7D50\u8AD6\u3092\u5DE6\u53F3\u3057\u3046\u308B\u60C5\u5831\u6E90\uFF08Issue/PR \u306E\u30B3\u30E1\u30F3\u30C8\u30FB\u95A2\u9023 Issue/PR\u30FB\`docs/\`\u30FB\`.claude/requirements/\`\u30FB\`CODING_GUIDELINES.md\`\u30FB\u30EA\u30F3\u30AF\u5148\u306E\u4ED5\u69D8\uFF09\u3092\u898B\u3066\u304B\u3089\u7740\u624B\u3059\u308B\u3002\u898B\u3064\u3051\u305F\u5185\u5BB9\u306F\u4F7F\u3046
1381
+ - Issue\u30B3\u30E1\u30F3\u30C8\u30FBPR\u306E\u672C\u6587\u30FBdescription\u30FB\u30EC\u30DD\u30FC\u30C8\u7B49\u306E\u66F8\u304D\u7269\u306F\u3001\u5FC5\u8981\u306A\u5B9F\u8CEA\u3060\u3051\u3092\u66F8\u304F\u3002\u540C\u3058\u5185\u5BB9\u306E\u8A00\u3044\u63DB\u3048\u30FB\u57CB\u3081\u8349\u30BB\u30AF\u30B7\u30E7\u30F3\u30FB\u300C\u8A72\u5F53\u306A\u3057\u300D\u3092\u4E26\u3079\u308B\u3060\u3051\u306E\u7BC0\u3092\u8DB3\u3055\u306A\u3044
1382
+ - \u6700\u7D42\u5831\u544A\u306F\u7D50\u8AD6\u304B\u3089\u66F8\u304F\u30021\u6587\u76EE\u3067\u300C\u4F55\u3092\u3057\u305F\u304B / \u3069\u3053\u3067\u6B62\u307E\u3063\u305F\u304B\u300D\u3092\u8FF0\u3079\u3001\u8A73\u7D30\u3092\u305D\u306E\u5F8C\u306B\u7F6E\u304F
1383
+
1384
+ \u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u3078\u306E\u59D4\u8B72\u306F\u4EE5\u4E0B\u306B\u5F93\u3046\u3053\u3068\u3002
1385
+
1386
+ - \u81EA\u5206\u3067\u6570\u56DE\u306E\u30C4\u30FC\u30EB\u547C\u3073\u51FA\u3057\u3067\u7D42\u308F\u308B\u4F5C\u696D\u306F\u59D4\u8B72\u3057\u306A\u3044\uFF08\u30D6\u30EA\u30FC\u30D5\u30A3\u30F3\u30B0\u4F5C\u6210\u3068\u59D4\u8B72\u5148\u306E\u518D\u63A2\u7D22\u3067\u30B3\u30B9\u30C8\u3068\u6642\u9593\u304C\u500D\u306B\u306A\u308B\uFF09
1387
+ - \u59D4\u8B72\u3059\u308B\u306E\u306F\u3001\u72EC\u7ACB\u3057\u3066\u4E26\u5217\u5B9F\u884C\u3067\u304D\u308B\u4F5C\u696D\u30FB\u63A2\u7D22\u7BC4\u56F2\u306E\u5E83\u3044\u8ABF\u67FB\u30FB\u5C02\u9580\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u306E\u524D\u63D0\u77E5\u8B58\u304C\u8981\u308B\u4F5C\u696D\u306B\u9650\u308B
1388
+ - 1\u30BF\u30B9\u30AF\u306B1\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u30021\u4F53\u3067\u5B8C\u7D50\u3059\u308B\u4F5C\u696D\u306B\u8907\u6570\u4F53\u3092\u91CD\u306D\u3066\u8D77\u52D5\u3057\u306A\u3044
1389
+ - **\u81EA\u5206\u306E\u4F5C\u696D\u306E\u78BA\u8A8D\u30FB\u518D\u30C1\u30A7\u30C3\u30AF\u3092\u76EE\u7684\u306B\u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u3092\u8D77\u52D5\u3057\u306A\u3044**\u3002\u6210\u679C\u7269\u306E\u691C\u8A3C\u306F \`git diff\` \u3084\u30C6\u30B9\u30C8\u30FBLint\u306E\u5B9F\u884C\u3067\u81EA\u5206\u3067\u884C\u3046
1390
+
1391
+ \u30BF\u30FC\u30F3\u306E\u7D42\u3048\u65B9\u306F\u4EE5\u4E0B\u306B\u5F93\u3046\u3053\u3068\u3002\u30C4\u30FC\u30EB\u547C\u3073\u51FA\u3057\u3092\u542B\u307E\u306A\u3044\u30E1\u30C3\u30BB\u30FC\u30B8\u3067\u30BF\u30FC\u30F3\u304C\u7D42\u308F\u308B\u3068\u3001\u3053\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u3067\u306F\u51E6\u7406\u304C\u305D\u3053\u3067\u6253\u3061\u5207\u3089\u308C\u3001\u7D9A\u304D\u3092\u6307\u793A\u3059\u308B\u4EBA\u306F\u3044\u306A\u3044\u3002
1392
+
1393
+ - \u6B21\u306E4\u7A2E\u985E\u306E\u7D42\u3048\u65B9\u3092\u3057\u306A\u3044: (1) \u5B9F\u65BD\u5185\u5BB9\u306E\u8981\u7D04\u3092\u66F8\u304D\u300C\u6B21\u306F\u301C\u3059\u308B\u300D\u3068\u544A\u3052\u308B\u3060\u3051\u3067\u6B21\u306B\u9032\u307E\u306A\u3044\u3001(2) \u300C\u301C\u3057\u3066\u3088\u3044\u304B\u300D\u300C\u4ED6\u306B\u5E0C\u671B\u304C\u306A\u3051\u308C\u3070\u7D9A\u3051\u308B\u300D\u3068\u78BA\u8A8D\u3057\u3066\u6B62\u307E\u308B\u3001(3) \u81EA\u5206\u3067\u6C7A\u3081\u3089\u308C\u308B\u9078\u629E\u80A2\u3092\u30E6\u30FC\u30B6\u30FC\u5411\u3051\u306B\u5217\u6319\u3057\u3066\u6B62\u307E\u308B\u3001(4) \u533A\u5207\u308A\u304C\u826F\u3044\u30FB\u9577\u304F\u4F5C\u696D\u3057\u305F\u3068\u3044\u3046\u7406\u7531\u3067\u4E2D\u9593\u5831\u544A\u3060\u3051\u3092\u51FA\u3057\u3066\u6B62\u307E\u308B
1394
+ - \u72B6\u6CC1\u30E1\u30E2\u3084\u63A8\u5968\u6848\u306F\u3001\u6B21\u306E\u30C4\u30FC\u30EB\u547C\u3073\u51FA\u3057\u3068\u540C\u3058\u30E1\u30C3\u30BB\u30FC\u30B8\u306B\u66F8\u3044\u3066\u4F5C\u696D\u3092\u7D9A\u3051\u308B\u3002\u30E6\u30FC\u30B6\u30FC\u306E\u56DE\u7B54\u306B\u4F9D\u5B58\u3057\u306A\u3044\u4F5C\u696D\u306F\u6B62\u3081\u305A\u306B\u9032\u3081\u308B
1395
+ - \u30BF\u30FC\u30F3\u3092\u7D42\u3048\u3066\u3088\u3044\u306E\u306F\u3001\u30B9\u30AD\u30EB\u306E\u5168\u30B9\u30C6\u30C3\u30D7\u3092\u5B8C\u9042\u3057\u305F\u3068\u304D\u3001\u307E\u305F\u306F\u30B9\u30AD\u30EB\u306B\u5B9A\u7FA9\u3055\u308C\u305F\u4E2D\u65AD\u6761\u4EF6\u306B\u8A72\u5F53\u3057\u305F\u3068\u304D\u3060\u3051\u3002\u7834\u58CA\u7684\u30FB\u4E0D\u53EF\u9006\u306A\u64CD\u4F5C\u3067\u5224\u65AD\u306B\u8FF7\u3046\u5834\u5408\u306F\u3001\u78BA\u8A8D\u3092\u6C42\u3081\u3066\u6B62\u307E\u308B\u306E\u3067\u306F\u306A\u304F\u5B9F\u884C\u3057\u306A\u3044\u5074\u3092\u9078\u3073\u3001\u305D\u306E\u65E8\u3092\u6700\u7D42\u5831\u544A\u306B\u66F8\u3044\u3066\u6B8B\u308A\u306E\u4F5C\u696D\u3092\u9032\u3081\u308B`;
1396
+ var SONNET_SYSTEM_PROMPT_ADDENDUM = `\u4F5C\u696D\u306E\u9032\u3081\u65B9\u306F\u4EE5\u4E0B\u306B\u5F93\u3046\u3053\u3068\u3002
1397
+
1398
+ - \u4F9D\u983C\u3055\u308C\u305F\u4F5C\u696D\u304C\u3059\u3079\u3066\u7D42\u308F\u308B\u307E\u3067\u7D9A\u3051\u308B\u3002\u8A08\u753B\u306E\u78BA\u8A8D\u30FB\u81EA\u5206\u3067\u7B54\u3048\u3089\u308C\u308B\u8CEA\u554F\u30FB\u591A\u6BB5\u30BF\u30B9\u30AF\u306E\u4E00\u90E8\u3092\u7D42\u3048\u305F\u6642\u70B9\u3067\u306E\u300C\u7D9A\u3051\u308B\u304B\u300D\u306E\u78BA\u8A8D\u306E\u305F\u3081\u306B\u6B62\u307E\u3089\u306A\u3044\u3002\u6B62\u307E\u3063\u3066\u3088\u3044\u306E\u306F\u3001\u30B9\u30AD\u30EB\u306B\u5B9A\u7FA9\u3055\u308C\u305F\u4E2D\u65AD\u6761\u4EF6\u306B\u8A72\u5F53\u3057\u305F\u3068\u304D\u3060\u3051
1399
+ - \u4F9D\u983C\u3055\u308C\u305F\u4F5C\u696D\u304C\u7D42\u308F\u308A\u691C\u8A3C\u3082\u6E08\u3093\u3060\u3089\u3001\u305D\u3053\u3067\u7D42\u3048\u3066\u5831\u544A\u3059\u308B\u3002\u4F9D\u983C\u3055\u308C\u3066\u3044\u306A\u3044\u30C6\u30B9\u30C8\u30FB\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8\u30FB\u88DC\u52A9\u30D5\u30A1\u30A4\u30EB\u30FB\u30EA\u30D5\u30A1\u30AF\u30BF\u3092\u8DB3\u3055\u306A\u3044\u3002\u3042\u3063\u305F\u65B9\u304C\u3088\u3044\u3068\u8003\u3048\u308B\u3082\u306E\u306F\u6700\u7D42\u5831\u544A\u306B1\u884C\u3067\u6319\u3052\u308B
1400
+ - \u5B9F\u884C\u30FB\u30D3\u30EB\u30C9\u30FB\u578B\u30C1\u30A7\u30C3\u30AF\u3067\u304D\u308B\u30B3\u30FC\u30C9\u3092\u5909\u66F4\u3057\u305F\u3089\u3001\u5B8C\u4E86\u3068\u5831\u544A\u3059\u308B\u524D\u306B**\u305D\u306E\u5909\u66F4\u3092\u5B9F\u969B\u306B\u901A\u3059\u691C\u8A3C**\uFF08\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u30C6\u30B9\u30C8\u30FB\u578B\u30C1\u30A7\u30C3\u30AF\u30FB\u30D3\u30EB\u30C9\u3001\u307E\u305F\u306F\u5909\u66F4\u3057\u305F\u30B3\u30DE\u30F3\u30C9\u81EA\u4F53\uFF09\u3092\u5B9F\u884C\u3059\u308B\u3002\u69CB\u6587\u30C1\u30A7\u30C3\u30AF\u3060\u3051\u3001\u307E\u305F\u306F\u8D77\u52D5\u306B\u5931\u6557\u3057\u305F\u30C1\u30A7\u30C3\u30AF\u30B3\u30DE\u30F3\u30C9\u306F\u691C\u8A3C\u306B\u6570\u3048\u306A\u3044\u3002\u8DB3\u308A\u306A\u3044\u306E\u304C\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u5BA3\u8A00\u6E08\u307F\u4F9D\u5B58\u3060\u3051\u306A\u3089\u3001\u305D\u306E\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u30D1\u30C3\u30B1\u30FC\u30B8\u30DE\u30CD\u30FC\u30B8\u30E3\u3068\u30ED\u30C3\u30AF\u30D5\u30A1\u30A4\u30EB\u3067\u5165\u308C\u308B\uFF08\`sudo\` \u3084\u30B7\u30B9\u30C6\u30E0\u306E\u30D1\u30C3\u30B1\u30FC\u30B8\u30DE\u30CD\u30FC\u30B8\u30E3\u306F\u4F7F\u308F\u306A\u3044\uFF09\u3002\u3069\u306E\u691C\u8A3C\u3082\u5B9F\u884C\u3067\u304D\u306A\u3044\u5834\u5408\u306B\u9650\u308A\u3001\u5B8C\u4E86\u3067\u306F\u306A\u304F\u300C\u3069\u306E\u691C\u8A3C\u3092\u5B9F\u884C\u3057\u306A\u304B\u3063\u305F\u304B\u30FB\u306A\u305C\u304B\u300D\u3092\u5831\u544A\u3059\u308B`;
1401
+ function systemPromptFor(model) {
1402
+ if (isOpusModel(model)) return `${SYSTEM_PROMPT_BASE}
1403
+
1404
+ ${OPUS_SYSTEM_PROMPT_ADDENDUM}`;
1405
+ if (isSonnetModel(model)) return `${SYSTEM_PROMPT_BASE}
1406
+
1407
+ ${SONNET_SYSTEM_PROMPT_ADDENDUM}`;
1408
+ return SYSTEM_PROMPT_BASE;
1409
+ }
1410
+ function systemPromptVariant(model) {
1411
+ if (isOpusModel(model)) return "opus";
1412
+ if (isSonnetModel(model)) return "sonnet";
1413
+ return "default";
1414
+ }
1415
+ function isOpusModel(model) {
1416
+ return model.toLowerCase().includes("opus");
1417
+ }
1418
+ function isSonnetModel(model) {
1419
+ return model.toLowerCase().includes("sonnet");
1420
+ }
1421
+ var CLAUDE_COMMAND = "claude";
1422
+ var cachedSystemPromptFilePaths = /* @__PURE__ */ new Map();
1423
+ function systemPromptFilePath(model) {
1424
+ const variant = systemPromptVariant(model);
1425
+ const cached = cachedSystemPromptFilePaths.get(variant);
1426
+ if (cached) return cached;
1427
+ const dir = path.join(os.tmpdir(), "claude-task-worker");
1428
+ mkdirSync(dir, { recursive: true });
1429
+ const target = path.join(dir, `append-system-prompt-${process.pid}-${variant}.txt`);
1430
+ const tmp = `${target}.tmp`;
1431
+ writeFileSync2(tmp, systemPromptFor(model), "utf8");
1432
+ renameSync(tmp, target);
1433
+ cachedSystemPromptFilePaths.set(variant, target);
1434
+ return target;
1435
+ }
1436
+ function buildClaudeArgs({
1437
+ mode,
1438
+ prompt,
1439
+ model,
1440
+ effort,
1441
+ advisorModel,
1442
+ permissionMode,
1443
+ cloud,
1444
+ remoteEnvId,
1445
+ baseRef,
1446
+ onBranch
1447
+ }) {
1448
+ const ref = baseRef?.trim() ?? "";
1449
+ const onBranchValue = onBranch?.trim() ?? "";
1450
+ if (cloud === true && ref !== "" && onBranchValue !== "") {
1451
+ throw new Error("--on-branch and --ref both set the cloud session's base branch; pass one or the other");
1452
+ }
1453
+ const advisor = advisorModel?.trim() ?? "";
1454
+ const remoteEnv = remoteEnvId?.trim() ?? "";
1455
+ const permission = permissionMode ?? DEFAULT_PERMISSION_MODE;
1456
+ return [
1457
+ // default モードはプロンプトを引数で渡す(print モード)。herdr モードでは渡さない:
1458
+ // 引数で渡すと claude が起動と同時に作業を始めてしまい、`herdr agent start` が
1459
+ // 「入力待ちになるまで」ブロックする仕様と噛み合わない(タスクが終わるまで返らず、
1460
+ // 2分を超えると timeout で落ちる)。プロンプトは起動後に `herdr agent prompt` で
1461
+ // 投入し、herdr にターンを追跡させる(herdr-runner.ts の startHerdrTask 参照)。
1462
+ // クラウド実行(`cloud: true`)も print モード非対応のため同様に省く
1463
+ // (実測 T2: `Error: --cloud cannot be combined with --print.`)。
1464
+ ...mode === "herdr" || cloud === true ? [] : ["-p", prompt],
1465
+ // クラウド実行では権限モードを渡さない。クラウドセッションは `--permission-mode` を
1466
+ // 受理するが VM 側へ反映せず(Issue #307。VM の権限モードは cloud-setup が書く
1467
+ // settings.json の `permissions.defaultMode` が決める)、一方でローカル側の作成
1468
+ // コマンドは script(1) の疑似pty越し=**対話起動**として扱われるため、
1469
+ // `bypassPermissions` を渡すと claude が「WARNING: ... Bypass Permissions mode /
1470
+ // No, exit / Yes, I accept」の承認ダイアログを出す(print モードでは出ない)。
1471
+ // 作成コマンドの stdin は ignore で誰も応答できず、セッションIDが出力されないまま
1472
+ // 必ず `timed out waiting for the cloud session id` で失敗する。
1473
+ ...cloud === true ? [] : ["--permission-mode", permission],
1474
+ "--disallowedTools",
1475
+ DISALLOWED_TOOLS_ARG,
1476
+ "--append-system-prompt-file",
1477
+ systemPromptFilePath(model),
1478
+ "--model",
1479
+ model,
1480
+ "--effort",
1481
+ effort,
1482
+ // advisor 未指定(空文字)ならフラグごと省く。値なしの `--advisor` を渡すと
1483
+ // 後続フラグを値として食われるため、必ずモデル名とセットでのみ付ける。
1484
+ ...advisor === "" ? [] : ["--advisor", advisor],
1485
+ // クラウド環境の指名。値なしの `--environment` は後続フラグを値として食うため、
1486
+ // `--advisor` と同じく ID とセットでのみ付ける。
1487
+ ...cloud === true && remoteEnv !== "" ? ["--environment", remoteEnv] : [],
1488
+ // クラウド実行時は「作成コマンドの共通フラグ」だけをここで返す。`--cloud` 自体は
1489
+ // 付けない(値として渡す description(=クラウドセッションの初期プロンプト。
1490
+ // appendCloudDoneInstruction() 適用後のタスクプロンプトそのもの)は
1491
+ // `src/process-manager.ts` 側でしか決まらないため。`buildCloudCreateArgs()` が
1492
+ // このフラグ列の先頭へ `--cloud <description>` を足して作成コマンドを完成させる)。
1493
+ // ベースブランチ指定は `--ref` / `--on-branch` のどちらか一方のみ(両方指定は上で例外)。
1494
+ ...cloud === true && ref !== "" ? ["--ref", ref] : [],
1495
+ ...cloud === true && onBranchValue !== "" ? ["--on-branch", onBranchValue] : []
1496
+ ];
1497
+ }
1498
+ function buildCloudCreateArgs(commonArgs, prompt) {
1499
+ return ["--cloud", prompt, ...commonArgs];
1500
+ }
1501
+ function appendCloudDoneInstruction(prompt, target, debug = false) {
1502
+ const targetLabel = target.type === "issue" ? `Issue #${target.number}` : `PR #${target.number}`;
1503
+ const checkoutInstruction = buildCloudCheckoutInstruction(target);
1504
+ const worktreeInstruction = buildCloudWorktreeInstruction(prompt);
1505
+ const reportInstruction = debug ? `\`${CLOUD_DONE_LABEL}\` \u30E9\u30D9\u30EB\u3092\u4ED8\u3051\u308B\u76F4\u524D\u306B\u3001${targetLabel} \u3078 \`${CLOUD_REPORT_HEADING}\` \u3092\u898B\u51FA\u3057\u3068\u3059\u308B\u30B3\u30E1\u30F3\u30C8\u30921\u4EF6\u6295\u7A3F\u3057\u3001\u672C\u6587\u306B\u6700\u7D42\u5831\u544A\uFF08\u5B8C\u4E86\u30FB\u4E2D\u65AD\u306B\u304B\u304B\u308F\u3089\u305A\uFF09\u3092\u66F8\u304F\u3053\u3068\u3002GitHub MCP\uFF08\`add_issue_comment\`\uFF09\u3092\u512A\u5148\u3057\u3001\u5931\u6557\u3057\u305F\u5834\u5408\u306E\u307F \`gh ${target.type} comment ${target.number} --body-file -\` \u3078\u30D5\u30A9\u30FC\u30EB\u30D0\u30C3\u30AF\u3059\u308B\u3053\u3068\uFF08\u30D5\u30A9\u30FC\u30EB\u30D0\u30C3\u30AF\u306F1\u56DE\u307E\u3067\uFF09\u3002\u30EF\u30FC\u30AB\u30FC\u306F\u3053\u306E\u30B3\u30E1\u30F3\u30C8\u3092\u6700\u7D42\u30EC\u30DD\u30FC\u30C8\u3068\u3057\u3066\u56DE\u53CE\u3057 Slack \u901A\u77E5\u306B\u8F09\u305B\u308B\u3002` : "";
1506
+ const labelInstruction = `${debug ? "\u4E0A\u8A18\u30B3\u30E1\u30F3\u30C8\u306E\u6295\u7A3F\u5F8C\u3001" : ""}\u3053\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u6700\u5F8C\u306E\u64CD\u4F5C\u3068\u3057\u3066 ${targetLabel} \u306B \`${CLOUD_DONE_LABEL}\` \u30E9\u30D9\u30EB\u3092\u4ED8\u4E0E\u3059\u308B\u3053\u3068\u3002\u4ED8\u4E0E\u306F \`bash \${CLAUDE_PLUGIN_ROOT}/scripts/gh-compat.sh add-label ${target.number} ${CLOUD_DONE_LABEL}\` \u3067\u884C\u3044\u3001\u5931\u6557\u3057\u305F\u5834\u5408\u306E\u307F \`gh api -X POST repos/{owner}/{repo}/issues/${target.number}/labels -f "labels[]=${CLOUD_DONE_LABEL}"\` \u3078\u30D5\u30A9\u30FC\u30EB\u30D0\u30C3\u30AF\u3059\u308B\u3053\u3068\uFF08\u30D5\u30A9\u30FC\u30EB\u30D0\u30C3\u30AF\u306F1\u56DE\u307E\u3067\uFF09\u3002**GitHub MCP \u306E \`issue_write\` / \`pull_request_write\`\uFF08method: \`update\`\uFF09\u306F\u4F7F\u308F\u306A\u3044\u3053\u3068** \u2014 \u540C\u30C4\u30FC\u30EB\u306E \`labels\` \u306F\u5168\u7F6E\u63DB\u3067\u3001\`${CLOUD_DONE_LABEL}\` \u3092\u8DB3\u3059\u3064\u3082\u308A\u306E\u547C\u3073\u51FA\u3057\u304C\u65E2\u5B58\u30E9\u30D9\u30EB\uFF08\`cc-triage-scope\` / \`cc-in-progress\` \u7B49\uFF09\u3092\u5DFB\u304D\u6DFB\u3048\u3067\u6D88\u3057\u3001\u305D\u306E Issue/PR \u304C\u30EF\u30FC\u30AB\u30FC\u306E\u30DD\u30FC\u30EA\u30F3\u30B0\u5BFE\u8C61\u304B\u3089\u5916\u308C\u308B\u3002\u30EF\u30FC\u30AB\u30FC\u306F\u3053\u306E\u30E9\u30D9\u30EB\u3067\u30BF\u30B9\u30AF\u306E\u7D42\u4E86\u3092\u691C\u77E5\u3057\u3066\u304A\u308A\u3001\u4ED8\u4E0E\u3055\u308C\u306A\u3044\u3068\u30BF\u30A4\u30E0\u30A2\u30A6\u30C8\u307E\u3067\u5B8C\u4E86\u6271\u3044\u306B\u306A\u3089\u306A\u3044\u3002`;
1507
+ return [prompt, checkoutInstruction, worktreeInstruction, reportInstruction, labelInstruction].filter((part) => part !== "").join("\n\n");
1508
+ }
1509
+ var CLOUD_WORKTREE_EXEMPT_SKILLS = ["exec-issue", "fix-review-point", "create-ui-design"];
1510
+ function buildCloudWorktreeInstruction(prompt) {
1511
+ const skill = prompt.trim().split(/\s+/)[0] ?? "";
1512
+ if (!CLOUD_WORKTREE_EXEMPT_SKILLS.some((name) => skill.endsWith(`:${name}`))) return "";
1513
+ return "\u3053\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306F\u30AF\u30E9\u30A6\u30C9\u5B9F\u884C\u306E\u305F\u3081 worktree \u3092\u6301\u305F\u305A\u3001cwd \u306F\u30EA\u30DD\u30B8\u30C8\u30EA\u30EB\u30FC\u30C8\u3067\u3042\u308B\uFF08\u30EF\u30FC\u30AB\u30FC\u306F\u30AF\u30E9\u30A6\u30C9\u5B9F\u884C\u6642\u306B worktree \u3092\u4F5C\u3089\u306A\u3044\uFF09\u3002\u30B9\u30AD\u30EB\u672C\u6587\u306E\u30D5\u30A7\u30FC\u30BA0\u306B\u3042\u308B\u300C`pwd` \u304C `.claude/worktrees/` \u914D\u4E0B\u3067\u306A\u3051\u308C\u3070\u4E2D\u65AD\u3059\u308B\u300D\u78BA\u8A8D\u306F\u3001\u3053\u306E\u6307\u793A\u304C\u3042\u308B\u5834\u5408\u306B\u9650\u308A\u9069\u7528\u3057\u306A\u3044\u3053\u3068\u3002\u305F\u3060\u3057\u30AC\u30FC\u30C9\u306E\u76EE\u7684\u3067\u3042\u308B\u300C\u30C7\u30D5\u30A9\u30EB\u30C8\u30D6\u30E9\u30F3\u30C1\u3067\u4F5C\u696D\u3057\u306A\u3044\u300D\u306F\u7DAD\u6301\u3059\u308B\u3053\u3068 \u2014 \u540C\u3058\u30D5\u30A7\u30FC\u30BA0\u306E\u300C\u30C7\u30D5\u30A9\u30EB\u30C8\u30D6\u30E9\u30F3\u30C1\u540D\u3092\u53D6\u5F97\u3057\u3001\u73FE\u5728\u30D6\u30E9\u30F3\u30C1\u3068\u4E00\u81F4\u3059\u308B\u5834\u5408\u306F\u4E2D\u65AD\u3059\u308B\uFF08\u53D6\u5F97\u5931\u6557\u3082\u4E2D\u65AD\uFF09\u300D\u306F\u5FC5\u305A\u5B9F\u884C\u3057\u3001\u4E00\u81F4\u3057\u305F\u5834\u5408\u306F\u5F93\u6765\u3069\u304A\u308A\u4E2D\u65AD\u3059\u308B\u3053\u3068\u3002";
1514
+ }
1515
+ function buildCloudCheckoutInstruction(target) {
1516
+ if (target.type !== "pr" || !target.onBranch) return "";
1517
+ return `\u3053\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306F PR #${target.number} \u306E head \u30D6\u30E9\u30F3\u30C1\u4E0A\u3067\u958B\u59CB\u3057\u3066\u3044\u308B\uFF08\u30EF\u30FC\u30AB\u30FC\u304C \`--on-branch\` \u3067\u6307\u5B9A\u6E08\u307F\uFF09\u3002\u30B9\u30AD\u30EB\u672C\u6587\u304C\u6307\u793A\u3059\u308B \`gh pr checkout\` \u306F\u5B9F\u884C\u3057\u306A\u3044\u3053\u3068\uFF08\u30AF\u30E9\u30A6\u30C9\u3067\u306F GraphQL \u30B2\u30FC\u30C8\u306B\u3088\u308A 403 \u3067\u5931\u6557\u3057\u3001\u304B\u3064\u65E2\u306B\u30D6\u30E9\u30F3\u30C1\u4E0A\u306B\u3044\u308B\u306E\u3067\u4E0D\u8981\uFF09\u3002\u30D6\u30E9\u30F3\u30C1\u306E\u78BA\u8A8D\u304C\u8981\u308B\u5834\u5408\u306F \`git rev-parse --abbrev-ref HEAD\` \u3092\u4F7F\u3046\u3053\u3068\u3002`;
1518
+ }
1519
+ function buildCloudGitHubAccessInstruction() {
1520
+ return "\u3053\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306F\u30AF\u30E9\u30A6\u30C9\u5B9F\u884C\u306E\u305F\u3081\u3001GitHub \u306E\u53C2\u7167/\u66F4\u65B0\u306F **GitHub MCP \u3092\u7B2C\u4E00\u624B\u6BB5**\u306B\u3059\u308B\u3053\u3068\uFF08\u30B9\u30AD\u30EB\u672C\u6587\u3068 `references/github-access.md` \u306E\u65E2\u5B9A\u3067\u3042\u308B `gh` \u512A\u5148\u3092\u3001\u3053\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306B\u9650\u308A\u9006\u8EE2\u3055\u305B\u308B\uFF09\u3002\u30AF\u30E9\u30A6\u30C9\u306E GitHub \u30D7\u30ED\u30AD\u30B7\u306F `gh issue view --json` / `gh pr view --json` \u306A\u3069\u306E GraphQL \u7D4C\u7531\u306E\u64CD\u4F5C\u3092\u30D5\u30A3\u30FC\u30EB\u30C9\u3092\u554F\u308F\u305A 403 \u306B\u3059\u308B\u305F\u3081\u3001`gh` \u3092\u7B2C\u4E00\u624B\u6BB5\u306B\u3059\u308B\u3068 Issue/PR \u672C\u6587\u3092\u53D6\u5F97\u3067\u304D\u306A\u3044\u3002MCP \u306B\u8A72\u5F53\u30C4\u30FC\u30EB\u304C\u7121\u3044\u64CD\u4F5C\u306F `gh api repos/...`\uFF08REST\uFF09\u307E\u305F\u306F `plugin/scripts/gh-compat.sh` \u3092\u4F7F\u3046\u3053\u3068\uFF08REST \u306F\u30AF\u30E9\u30A6\u30C9\u3067\u3082\u6210\u529F\u3059\u308B\uFF09\u3002\u30D5\u30A9\u30FC\u30EB\u30D0\u30C3\u30AF\u306F1\u64CD\u4F5C\u306B\u3064\u304D1\u56DE\u307E\u3067\u3002\u305F\u3060\u3057\u6B21\u306E3\u3064\u306F MCP \u3092\u4F7F\u308F\u306A\u3044\u3053\u3068: (1) Issue / PR \u306E\u4F5C\u6210\u306F `gh-compat.sh create-issue` / `create-pr`\uFF08MCP \u306E `create_pull_request` \u306F labels / assignees \u3092\u53D7\u3051\u4ED8\u3051\u305A\u3001`issue_write` \u306E create \u3082\u6E21\u3057\u5FD8\u308C\u3067\u6B20\u843D\u3057\u3001Assignee \u3068 `cc-triage-scope` \u306E\u7121\u3044 Issue/PR \u304C\u30EF\u30FC\u30AB\u30FC\u306B\u62FE\u308F\u308C\u306A\u304F\u306A\u308B\uFF09\u3001(2) \u30E9\u30D9\u30EB\u30FBAssignee \u306E\u8FFD\u52A0\u306F `gh-compat.sh add-label` / `add-assignee`\uFF08MCP \u306E update \u306F\u5168\u7F6E\u63DB\u3067\u65E2\u5B58\u306E\u3082\u306E\u3092\u6D88\u3059\uFF09\u3001(3) \u30B3\u30DF\u30C3\u30C8\u306F `git commit` \u3068 `git push` \u3060\u3051\u3067\u884C\u3044\u3001`push_files` / `create_or_update_file` / `delete_file` / `create_branch` \u306F\u4F7F\u308F\u306A\u3044\uFF08MCP \u7D4C\u7531\u306E\u30B3\u30DF\u30C3\u30C8\u3067\u306F CI \u304C\u8D77\u52D5\u3057\u306A\u3044\u5834\u9762\u304C\u3042\u308B\u3002`git push` \u304C\u5931\u6557\u3057\u3066\u3082 MCP \u3078\u30D5\u30A9\u30FC\u30EB\u30D0\u30C3\u30AF\u305B\u305A\u3001\u5931\u6557\u3068\u3057\u3066\u5831\u544A\u3059\u308B\uFF09\u3002";
1521
+ }
1522
+ function buildCloudToolRestriction() {
1523
+ return `\u30AF\u30E9\u30A6\u30C9\u5B9F\u884C\u3067\u306F \`--disallowedTools\` \u30D5\u30E9\u30B0\u306B\u3088\u308B\u30C4\u30FC\u30EB\u5236\u9650\u304C\u53CD\u6620\u3055\u308C\u306A\u3044\u305F\u3081\u3001\u4EE5\u4E0B\u306E\u30C4\u30FC\u30EB\u3092\u4F7F\u308F\u306A\u3044\u3053\u3068: ${DISALLOWED_TOOLS.join(", ")}`;
1524
+ }
1525
+ function buildCloudPrompt(prompt, model, target, debug = false) {
1526
+ const principles = `\u4EE5\u4E0B\u306F\u3053\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u5B9F\u884C\u539F\u5247\u3067\u3042\u308B\u3002\u30AF\u30E9\u30A6\u30C9\u5B9F\u884C\u3067\u306F\u30B7\u30B9\u30C6\u30E0\u30D7\u30ED\u30F3\u30D7\u30C8\u306B\u3088\u308B\u6CE8\u5165\u304C\u53CD\u6620\u3055\u308C\u306A\u3044\u305F\u3081\u3001\u30D7\u30ED\u30F3\u30D7\u30C8\u672C\u6587\u3068\u3057\u3066\u6E21\u3057\u3066\u3044\u308B\u3002
1527
+
1528
+ ${systemPromptFor(model)}
1529
+
1530
+ ${buildCloudToolRestriction()}
1531
+
1532
+ ${buildCloudGitHubAccessInstruction()}`;
1533
+ const withPrinciples = `${prompt}
1534
+
1535
+ ${principles}`;
1536
+ return target ? appendCloudDoneInstruction(withPrinciples, target, debug) : withPrinciples;
1537
+ }
1538
+ function shellQuote2(value) {
1539
+ return `'${value.replace(/'/g, "'\\''")}'`;
1540
+ }
1541
+ function buildScriptCommand(command, args, platform = process.platform) {
1542
+ if (platform === "darwin") {
1543
+ return { command: "script", args: ["-q", "/dev/null", command, ...args] };
1544
+ }
1545
+ if (platform === "linux") {
1546
+ return { command: "script", args: ["-qec", [command, ...args].map(shellQuote2).join(" "), "/dev/null"] };
1547
+ }
1548
+ throw new Error(`unsupported platform for script(1): ${platform}`);
1549
+ }
1550
+ function buildClaudeExecution(invocation) {
1551
+ return {
1552
+ command: CLAUDE_COMMAND,
1553
+ args: buildClaudeArgs(invocation),
1554
+ // クラウド実行では `-p` を付けない代わりに `--cloud` の値(初期プロンプト)として
1555
+ // 渡すため、mode に関わらずプロンプトを返す。
1556
+ ...invocation.mode === "herdr" || invocation.cloud === true ? { prompt: invocation.prompt } : {}
1557
+ };
1558
+ }
1559
+ function buildClaudeEnv(mode, cloud) {
1560
+ const base = mode === "herdr" || cloud ? { CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: CLAUDE_SPAWN_ENV.CLAUDE_CODE_DISABLE_BACKGROUND_TASKS } : { ...CLAUDE_SPAWN_ENV };
1561
+ if (!cloud) return base;
1562
+ return { ...base, CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1" };
1563
+ }
1564
+
1565
+ // src/git.ts
1566
+ import { execSync, execFile } from "node:child_process";
1567
+ import { promisify } from "node:util";
1568
+ var execFileAsync = promisify(execFile);
1569
+ var running = false;
1570
+ function syncDefaultBranch(branch) {
1571
+ if (running) return;
1572
+ running = true;
1573
+ try {
1574
+ execSync(
1575
+ `git worktree prune && git checkout ${branch} && git add -A && git reset --hard && git fetch origin ${branch} && git reset --hard origin/${branch}`,
1576
+ { stdio: "pipe" }
1577
+ );
1578
+ } finally {
1579
+ running = false;
1580
+ }
1581
+ }
1582
+ async function ensureEpicBranch(epicBranch, defaultBranch) {
1583
+ try {
1584
+ await fetchRemoteTracking(epicBranch);
1585
+ await assertRemoteTrackingExists(epicBranch);
1586
+ return;
1587
+ } catch (error) {
1588
+ if (!isMissingRemoteRefError(error)) {
1589
+ throw error;
1590
+ }
1591
+ }
1592
+ await fetchRemoteTracking(defaultBranch);
1593
+ await execFileAsync("git", ["push", "origin", `refs/remotes/origin/${defaultBranch}:refs/heads/${epicBranch}`]);
1594
+ await fetchRemoteTracking(epicBranch);
1595
+ await assertRemoteTrackingExists(epicBranch);
1596
+ }
1597
+ async function fetchRemoteTracking(branch) {
1598
+ await execFileAsync("git", ["fetch", "--no-prune", "origin", `+refs/heads/${branch}:refs/remotes/origin/${branch}`]);
1599
+ }
1600
+ function isMissingRemoteRefError(error) {
1601
+ const stderr = String(error?.stderr ?? "");
1602
+ return /couldn't find remote ref/i.test(stderr);
1603
+ }
1604
+ async function assertRemoteTrackingExists(epicBranch) {
1605
+ await execFileAsync("git", ["rev-parse", "--verify", "--quiet", `refs/remotes/origin/${epicBranch}`]);
1606
+ }
1607
+
1608
+ // src/process-manager.ts
1609
+ import { spawn } from "node:child_process";
1610
+ import { basename } from "node:path";
1611
+ import { StringDecoder } from "node:string_decoder";
1612
+
1613
+ // src/task-result.ts
1614
+ var STDERR_TAIL_LIMIT = 8 * 1024;
1615
+ var CLOUD_FAILURE_GUIDANCE = "[worker] \u30AF\u30E9\u30A6\u30C9\u5B9F\u884C\u306E\u524D\u63D0\u6761\u4EF6\uFF08GitHub \u9023\u643A / allow_remote_sessions \u7D44\u7E54\u30DD\u30EA\u30B7\u30FC\uFF09\u304C\u6E80\u305F\u3055\u308C\u3066\u3044\u306A\u3044\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002\u8A73\u7D30\u306F docs/cloud-prerequisite-checks.md \u3092\u53C2\u7167\u3057\u3066\u304F\u3060\u3055\u3044\u3002";
1616
+ function appendCloudFailureGuidance(result, cloud) {
1617
+ if (!cloud || result.status === "completed") return result;
1618
+ return { ...result, output: `${CLOUD_FAILURE_GUIDANCE}
1619
+ ${result.output}` };
1620
+ }
1621
+ function buildTaskResult(code, stdout, stderrTail) {
1622
+ const emptyOutput = stdout.trim() === "";
1623
+ const completed = code === 0 && !emptyOutput;
1624
+ let output = stdout;
1625
+ if (code === 0 && emptyOutput) {
1626
+ output += "[worker] claude exited with code 0 but produced no output (session aborted before the model ran; e.g. a skill preamble command failed)";
1627
+ } else if (!completed) {
1628
+ output += `
1629
+ [worker] claude exited with code ${code}`;
1630
+ }
1631
+ if (!completed && stderrTail.trim() !== "") {
1632
+ output += `
1633
+ [stderr] ${stderrTail.trim()}`;
1634
+ }
1635
+ return { status: completed ? "completed" : "failed", output };
1636
+ }
1637
+
1638
+ // src/process-manager.ts
1639
+ var childProcesses = /* @__PURE__ */ new Map();
1640
+ var herdrTasks = /* @__PURE__ */ new Map();
1641
+ var herdrAbortSignal = { aborted: false };
1642
+ var tasks = /* @__PURE__ */ new Map();
1643
+ function pushTaskLogLine(id, stream, text) {
1644
+ pushLogLine({ id, stream, text: text.replace(/\r$/, ""), time: /* @__PURE__ */ new Date() });
1645
+ }
1646
+ function makeLogFeeder(id, stream) {
1647
+ let partial = "";
1648
+ const decoder = new StringDecoder("utf-8");
1649
+ return {
1650
+ feed(chunk) {
1651
+ partial += decoder.write(chunk);
1652
+ const parts = partial.split("\n");
1653
+ partial = parts.pop() ?? "";
1654
+ for (const part of parts) pushTaskLogLine(id, stream, part);
1655
+ },
1656
+ flush() {
1657
+ partial += decoder.end();
1658
+ if (partial.length > 0) {
1659
+ pushTaskLogLine(id, stream, partial);
1660
+ partial = "";
1661
+ }
1662
+ }
1663
+ };
1664
+ }
1665
+ function pruneTaskHistory() {
1666
+ const finished = [...tasks.values()].filter((t) => t.status !== "running").sort((a, b) => (b.finishedAt ?? b.startedAt).getTime() - (a.finishedAt ?? a.startedAt).getTime());
1667
+ for (const t of finished.slice(TASK_DISPLAY_LIMIT)) tasks.delete(t.id);
1668
+ }
1669
+ var shuttingDown = false;
1670
+ function setShuttingDown() {
1671
+ shuttingDown = true;
1672
+ }
1673
+ function isShuttingDown() {
1674
+ return shuttingDown;
1675
+ }
1676
+ function isRunning(id) {
1677
+ const task = tasks.get(id);
1678
+ return task?.status === "running";
1679
+ }
1680
+ function isWorktreeInUse(worktreeId) {
1681
+ for (const task of tasks.values()) {
1682
+ if (task.status === "running" && task.path === worktreeId) {
1683
+ return true;
1684
+ }
1685
+ }
1686
+ return false;
1687
+ }
1688
+ function isWorkerAtCapacity(workerName) {
1689
+ let count = 0;
1690
+ for (const task of tasks.values()) {
1691
+ if (task.workerName === workerName && task.status === "running") {
1692
+ count++;
1693
+ }
1694
+ }
1695
+ return count >= getWorkerConfig(workerName).maxConcurrentTasks;
1696
+ }
1697
+ function renderTable() {
1698
+ const taskLines = buildTaskTableLines(selectRecentTasks([...tasks.values()]));
1699
+ const logTableLines = buildLogTableLines(logLines);
1700
+ if (taskLines.length === 0 && logTableLines.length === 0) return;
1701
+ const lines = [...taskLines];
1702
+ if (logTableLines.length > 0) {
1703
+ if (lines.length > 0) lines.push("");
1704
+ lines.push("Logs", ...logTableLines);
1705
+ }
1706
+ writeScreen(lines);
1707
+ }
1708
+ var renderInterval;
1709
+ function ensureRenderInterval() {
1710
+ if (renderInterval) return;
1711
+ renderInterval = setInterval(renderTable, 1e3);
1712
+ renderInterval.unref();
1713
+ }
1714
+ async function finishTask(id, result, onComplete) {
1715
+ try {
1716
+ await Promise.race([
1717
+ onComplete?.(result.status, result.output, result.cloudSessionId) ?? Promise.resolve(),
1718
+ new Promise(
1719
+ (_, reject) => setTimeout(() => reject(new Error("onComplete timed out after 120s")), 12e4).unref()
1720
+ )
1721
+ ]);
1722
+ } catch (err) {
1723
+ console.error(`[worker] onComplete error for #${id}: ${err}`);
1724
+ }
1725
+ const task = tasks.get(id);
1726
+ if (task) {
1727
+ task.status = result.status;
1728
+ task.finishedAt = /* @__PURE__ */ new Date();
1729
+ task.agentStatus = void 0;
1730
+ }
1731
+ pruneTaskHistory();
1732
+ renderTable();
1733
+ }
1734
+ function onCompleteWithDebugReport(onComplete, target) {
1735
+ if (!target || !hasDebugFlag()) return onComplete;
1736
+ return async (status, output, cloudSessionId) => {
1737
+ const body = output.trim();
1738
+ if (body !== "") {
1739
+ const comment = target.type === "issue" ? commentOnIssue : commentOnPR;
1740
+ await comment(target.number, `${CLOUD_REPORT_HEADING}
1741
+
1742
+ ${body}`).catch(
1743
+ (err) => console.error(`[worker] failed to post the debug report to ${target.type} #${target.number}: ${err}`)
1744
+ );
1745
+ }
1746
+ await onComplete?.(status, output, cloudSessionId);
1747
+ };
1748
+ }
1749
+ function resolveProjectName(cwd = process.cwd()) {
1750
+ const injected = process.env.CTW_PROJECT_NAME;
1751
+ if (injected && injected.length > 0) return injected;
1752
+ return findProjectNameByPath(cwd) ?? basename(cwd);
1753
+ }
1754
+ async function runViaHerdr(args, prompt, id, onComplete, cwd, env) {
1755
+ const { startHerdrTask, stopHerdrTask, taskTabLabel, waitForHerdrTask } = await import("./herdr-runner-33D6C2KI.js");
1756
+ const { getCurrentWorkspaceId } = await import("./herdr-NDLUM6JF.js");
1757
+ const label = taskTabLabel(resolveProjectName(), id);
1758
+ let task;
1759
+ let result;
1760
+ herdrTasks.set(id, { paneId: "", tabId: "" });
1761
+ try {
1762
+ task = await startHerdrTask({
1763
+ label,
1764
+ cwd: cwd ?? process.cwd(),
1765
+ args,
1766
+ prompt,
1767
+ env,
1768
+ workspaceId: getCurrentWorkspaceId()
1769
+ });
1770
+ herdrTasks.set(id, task);
1771
+ result = await waitForHerdrTask(task.paneId, {
1772
+ signal: herdrAbortSignal,
1773
+ onBlocked: () => console.warn(`[worker] #${id} is blocked and waiting for input in herdr tab "${label}"`),
1774
+ onStatus: (status) => {
1775
+ const task2 = tasks.get(id);
1776
+ if (task2 && task2.status === "running") task2.agentStatus = status;
1777
+ }
1778
+ });
1779
+ } catch (err) {
1780
+ console.error(`[worker] failed to run #${id} via herdr: ${err}`);
1781
+ result = { status: "failed", output: `[worker] failed to run the task via herdr: ${err}` };
1782
+ } finally {
1783
+ if (task) {
1784
+ await stopHerdrTask(task);
1785
+ }
1786
+ }
1787
+ await finishTask(id, result, onComplete);
1788
+ herdrTasks.delete(id);
1789
+ }
1790
+ var CLOUD_SESSION_TIMEOUT_MS = Number(process.env.CTW_CLOUD_SESSION_TIMEOUT_MS) || 120 * 1e3;
1791
+ var CLOUD_SESSION_POLL_INTERVAL_MS = 1e3;
1792
+ var CLOUD_POLL_INTERVAL_MS = 30 * 1e3;
1793
+ var CLOUD_TASK_TIMEOUT_MS = Number(process.env.CTW_CLOUD_TASK_TIMEOUT_MS) || 4 * 60 * 60 * 1e3;
1794
+ var cloudWaiters = /* @__PURE__ */ new Map();
1795
+ var cloudPollLoopRunning = false;
1796
+ function ensureCloudPollLoop() {
1797
+ if (cloudPollLoopRunning) return;
1798
+ cloudPollLoopRunning = true;
1799
+ void (async () => {
1800
+ try {
1801
+ await pollCloudWaiters();
1802
+ } finally {
1803
+ cloudPollLoopRunning = false;
1804
+ }
1805
+ })();
1806
+ }
1807
+ async function pollCloudWaiters() {
1808
+ while (cloudWaiters.size > 0) {
1809
+ if (herdrAbortSignal.aborted) {
1810
+ for (const waiter of cloudWaiters.values()) waiter.settle("aborted");
1811
+ cloudWaiters.clear();
1812
+ break;
1813
+ }
1814
+ const pendingTypes = /* @__PURE__ */ new Set();
1815
+ for (const waiter of cloudWaiters.values()) pendingTypes.add(waiter.type);
1816
+ const doneKeys = /* @__PURE__ */ new Map();
1817
+ for (const type of pendingTypes) {
1818
+ try {
1819
+ const numbers = await listNumbersWithLabel(type, CLOUD_DONE_LABEL);
1820
+ doneKeys.set(type, new Set(numbers));
1821
+ } catch (err) {
1822
+ console.error(`[worker] failed to poll ${CLOUD_DONE_LABEL} for ${type}: ${err}`);
1823
+ }
1824
+ }
1825
+ const now = Date.now();
1826
+ for (const [key, waiter] of [...cloudWaiters.entries()]) {
1827
+ const done = doneKeys.get(waiter.type);
1828
+ if (done?.has(waiter.number)) {
1829
+ waiter.settle("completed");
1830
+ cloudWaiters.delete(key);
1831
+ } else if (now >= waiter.deadline) {
1832
+ waiter.settle("timeout");
1833
+ cloudWaiters.delete(key);
1834
+ }
1835
+ }
1836
+ if (cloudWaiters.size === 0) break;
1837
+ for (let waited = 0; waited < CLOUD_POLL_INTERVAL_MS; waited += 1e3) {
1838
+ if (herdrAbortSignal.aborted) break;
1839
+ await new Promise((resolve5) => setTimeout(resolve5, 1e3));
1840
+ }
1841
+ }
1842
+ }
1843
+ function waitForCloudTask(id, target) {
1844
+ return new Promise((resolve5) => {
1845
+ cloudWaiters.set(`${target.type}:${target.number}:${id}`, {
1846
+ type: target.type,
1847
+ number: target.number,
1848
+ deadline: Date.now() + CLOUD_TASK_TIMEOUT_MS,
1849
+ settle: resolve5
1850
+ });
1851
+ ensureCloudPollLoop();
1852
+ });
1853
+ }
1854
+ async function flagOrphanedCloudSession(target, reason, cloudSessionId) {
1855
+ const { type, number: id } = target;
1856
+ await addLabel(type, id, "cc-need-human-check").catch((err) => {
1857
+ console.error(`[worker] failed to add cc-need-human-check to ${type} #${id}: ${err}`);
1858
+ });
1859
+ const causeLine = reason === "session-id" ? "\u30BB\u30C3\u30B7\u30E7\u30F3ID\u306E\u62BD\u51FA\u306B\u5931\u6557\u3057\u3066\u5F85\u6A5F\u3092\u6253\u3061\u5207\u308A\u307E\u3057\u305F\u3002\u30D7\u30ED\u30F3\u30D7\u30C8\u306F\u6295\u5165\u6E08\u307F\u3067\u3001\u30AF\u30E9\u30A6\u30C9\u30BB\u30C3\u30B7\u30E7\u30F3\u306F\u72EC\u7ACB\u3057\u3066\u7A3C\u50CD\u3057\u3066\u3044\u308B\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002" : "\u30EF\u30FC\u30AB\u30FC\u306E\u30B7\u30E3\u30C3\u30C8\u30C0\u30A6\u30F3\u3067\u5F85\u6A5F\u3092\u6253\u3061\u5207\u308A\u307E\u3057\u305F\u3002\u30AF\u30E9\u30A6\u30C9\u30BB\u30C3\u30B7\u30E7\u30F3\u306F\u7D99\u7D9A\u3057\u3066\u3044\u308B\u53EF\u80FD\u6027\u304C\u9AD8\u3044\u3067\u3059\u3002";
1860
+ const sessionLine = cloudSessionId ? `https://claude.ai/code/${cloudSessionId}` : "\u30BB\u30C3\u30B7\u30E7\u30F3URL\u4E0D\u660E\uFF08ID\u62BD\u51FA\u306B\u5931\u6557\uFF09";
1861
+ const body = `## \u5B64\u7ACB\u30AF\u30E9\u30A6\u30C9\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u53EF\u80FD\u6027
1862
+
1863
+ **\u539F\u56E0**: ${causeLine}
1864
+
1865
+ **\u30BB\u30C3\u30B7\u30E7\u30F3URL**: ${sessionLine}
1866
+
1867
+ **\u518D\u958B\u65B9\u6CD5**: \u5B64\u7ACB\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u6210\u679C\u7269\u304C\u5C4A\u3044\u3066\u3044\u306A\u3044\u304B\u78BA\u8A8D\u3057\u305F\u3046\u3048\u3067\u3001\`cc-need-human-check\` \u30E9\u30D9\u30EB\u3092\u5916\u3059\u3068\u30DD\u30FC\u30EA\u30F3\u30B0\u5BFE\u8C61\u306B\u623B\u308A\u307E\u3059\u3002`;
1868
+ const commentFn = type === "issue" ? commentOnIssue : commentOnPR;
1869
+ await commentFn(id, body).catch((err) => {
1870
+ console.error(`[worker] failed to comment on ${type} #${id} about the orphaned cloud session: ${err}`);
1871
+ });
1872
+ }
1873
+ async function createCloudSession(args, initialPrompt, cwd, env) {
1874
+ const { extractCloudSessionId, normalizePtyOutput } = await import("./herdr-runner-33D6C2KI.js");
1875
+ const spec = buildScriptCommand("claude", buildCloudCreateArgs(args, initialPrompt));
1876
+ const child = spawn(spec.command, spec.args, {
1877
+ stdio: ["ignore", "pipe", "pipe"],
1878
+ ...cwd ? { cwd } : {},
1879
+ ...env ? { env: { ...process.env, ...env } } : {}
1880
+ });
1881
+ let output = "";
1882
+ return await new Promise((resolve5, reject) => {
1883
+ let settled = false;
1884
+ const finish = (err, sessionId) => {
1885
+ if (settled) return;
1886
+ settled = true;
1887
+ clearTimeout(timer);
1888
+ clearInterval(abortCheck);
1889
+ if (err) {
1890
+ child.kill();
1891
+ reject(err);
1892
+ } else {
1893
+ resolve5(sessionId);
1894
+ }
1895
+ };
1896
+ const scan = () => {
1897
+ const sessionId = extractCloudSessionId(normalizePtyOutput(output));
1898
+ if (sessionId) finish(void 0, sessionId);
1899
+ };
1900
+ child.stdout?.on("data", (chunk) => {
1901
+ output += chunk.toString("utf-8");
1902
+ scan();
1903
+ });
1904
+ child.on("error", (err) => finish(new Error(`failed to spawn the cloud session command: ${err.message}`)));
1905
+ child.on("close", () => {
1906
+ scan();
1907
+ finish(new Error(`the cloud session command exited without a session id (output tail: ${output.slice(-1e3)})`));
1908
+ });
1909
+ const timer = setTimeout(
1910
+ () => finish(new Error(`timed out waiting for the cloud session id (output tail: ${output.slice(-1e3)})`)),
1911
+ CLOUD_SESSION_TIMEOUT_MS
1912
+ );
1913
+ const abortCheck = setInterval(() => {
1914
+ if (herdrAbortSignal.aborted) {
1915
+ finish(new Error("the worker is shutting down before the cloud session could be created"));
1916
+ }
1917
+ }, CLOUD_SESSION_POLL_INTERVAL_MS);
1918
+ });
1919
+ }
1920
+ async function runViaCloud(args, prompt, id, onComplete, cwd, env, cloudTarget, model) {
1921
+ herdrTasks.set(id, { paneId: "", tabId: "" });
1922
+ const debug = hasDebugFlag();
1923
+ const initialPrompt = buildCloudPrompt(prompt, model ?? "", cloudTarget, debug);
1924
+ let result;
1925
+ let cloudSessionId;
1926
+ try {
1927
+ cloudSessionId = await createCloudSession(args, initialPrompt, cwd, env);
1928
+ const createOutput = `[worker] created cloud session ${cloudSessionId} with the task's initial prompt`;
1929
+ if (!cloudTarget) {
1930
+ console.warn(`[worker] #${id} has no completion-detection target, treating session creation as completion`);
1931
+ result = { status: "completed", output: createOutput };
1932
+ } else {
1933
+ const outcome = await waitForCloudTask(id, cloudTarget);
1934
+ const { type: targetType, number: targetNumber } = cloudTarget;
1935
+ if (outcome === "completed") {
1936
+ await removeLabel(targetType, targetNumber, CLOUD_DONE_LABEL).catch((err) => {
1937
+ console.error(`[worker] failed to remove ${CLOUD_DONE_LABEL} from ${targetType} #${targetNumber}: ${err}`);
1938
+ });
1939
+ let reportBody = null;
1940
+ const startedAt = tasks.get(id)?.startedAt;
1941
+ if (debug && startedAt) {
1942
+ try {
1943
+ reportBody = await findCommentSince(targetNumber, startedAt, CLOUD_REPORT_HEADING);
1944
+ } catch (err) {
1945
+ console.error(
1946
+ `[worker] failed to fetch the cloud report comment for ${targetType} #${targetNumber}: ${err}`
1947
+ );
1948
+ }
1949
+ }
1950
+ result = {
1951
+ status: "completed",
1952
+ output: reportBody ?? `${createOutput}
1953
+ [worker] detected completion via the ${CLOUD_DONE_LABEL} label`
1954
+ };
1955
+ } else if (outcome === "timeout") {
1956
+ await addLabel(targetType, targetNumber, "cc-need-human-check").catch((err) => {
1957
+ console.error(`[worker] failed to add cc-need-human-check to ${targetType} #${targetNumber}: ${err}`);
1958
+ });
1959
+ result = {
1960
+ status: "failed",
1961
+ output: `${createOutput}
1962
+ [worker] timed out waiting for the ${CLOUD_DONE_LABEL} label after ${CLOUD_TASK_TIMEOUT_MS / 1e3 / 60} minutes. Possible causes: the session stopped on AskUserQuestion, the cloud VM crashed, the plugin was not installed, or label assignment itself failed.`
1963
+ };
1964
+ } else {
1965
+ await flagOrphanedCloudSession(cloudTarget, "shutdown", cloudSessionId);
1966
+ result = {
1967
+ status: "failed",
1968
+ output: `${createOutput}
1969
+ [worker] shutdown aborted the wait for the ${CLOUD_DONE_LABEL} label. The session may still be running; added cc-need-human-check.`
1970
+ };
1971
+ }
1972
+ }
1973
+ result.cloudSessionId = cloudSessionId;
1974
+ } catch (err) {
1975
+ console.error(`[worker] failed to run #${id} via cloud: ${err}`);
1976
+ let orphanNote = "[worker] note: with the 1-command launch, the cloud session may already have started working independently even though this task is being reported as failed locally (orphaned session).";
1977
+ if (cloudTarget) {
1978
+ const reason = herdrAbortSignal.aborted ? "shutdown" : "session-id";
1979
+ await flagOrphanedCloudSession(cloudTarget, reason, cloudSessionId);
1980
+ orphanNote += " added cc-need-human-check so a human can verify whether it completed on its own.";
1981
+ }
1982
+ result = {
1983
+ status: "failed",
1984
+ output: `[worker] failed to run the task via cloud: ${err}
1985
+ ${orphanNote}`,
1986
+ ...cloudSessionId ? { cloudSessionId } : {}
1987
+ };
1988
+ }
1989
+ result = appendCloudFailureGuidance(result, true);
1990
+ await finishTask(id, result, onComplete);
1991
+ herdrTasks.delete(id);
1992
+ }
1993
+ function run(command, args, id, title, workerName, path2, onComplete, cwd, env, prompt, cloud, cloudTarget, model) {
1994
+ tasks.delete(id);
1995
+ tasks.set(id, {
1996
+ id,
1997
+ title,
1998
+ status: "running",
1999
+ workerName,
2000
+ // クラウド実行は worktree を持たない(ワーカーが path に undefined を渡す)ため、
2001
+ // 空欄ではなく実行場所が分かる表示を入れる。
2002
+ path: cloud ? CLOUD_WORKTREE_LABEL : path2,
2003
+ startedAt: /* @__PURE__ */ new Date()
2004
+ });
2005
+ ensureRenderInterval();
2006
+ renderTable();
2007
+ if (cloud) {
2008
+ void runViaCloud(args, prompt ?? "", id, onComplete, cwd, env, cloudTarget, model);
2009
+ return;
2010
+ }
2011
+ const onCompleteWithReport = onCompleteWithDebugReport(onComplete, cloudTarget);
2012
+ if (getRunMode() === "herdr") {
2013
+ void runViaHerdr(args, prompt ?? "", id, onCompleteWithReport, cwd, env);
2014
+ return;
2015
+ }
2016
+ const child = spawn(command, args, {
2017
+ stdio: ["ignore", "pipe", "pipe"],
2018
+ detached: true,
2019
+ ...cwd ? { cwd } : {},
2020
+ ...env ? { env: { ...process.env, ...env } } : {}
2021
+ });
2022
+ childProcesses.set(id, child);
2023
+ const stdoutFeeder = makeLogFeeder(id, "stdout");
2024
+ const stderrFeeder = makeLogFeeder(id, "stderr");
2025
+ const outputChunks = [];
2026
+ child.stdout?.on("data", (chunk) => {
2027
+ outputChunks.push(chunk);
2028
+ stdoutFeeder.feed(chunk);
2029
+ });
2030
+ const stderrChunks = [];
2031
+ let stderrLen = 0;
2032
+ child.stderr?.on("data", (chunk) => {
2033
+ stderrChunks.push(chunk);
2034
+ stderrLen += chunk.length;
2035
+ while (stderrChunks.length > 1 && stderrLen - stderrChunks[0].length >= STDERR_TAIL_LIMIT) {
2036
+ stderrLen -= stderrChunks[0].length;
2037
+ stderrChunks.shift();
2038
+ }
2039
+ stderrFeeder.feed(chunk);
2040
+ });
2041
+ child.on("close", async (code) => {
2042
+ stdoutFeeder.flush();
2043
+ stderrFeeder.flush();
2044
+ const result = buildTaskResult(
2045
+ code,
2046
+ Buffer.concat(outputChunks).toString("utf-8"),
2047
+ Buffer.concat(stderrChunks).toString("utf-8").slice(-STDERR_TAIL_LIMIT)
2048
+ );
2049
+ await finishTask(id, result, onCompleteWithReport);
2050
+ childProcesses.delete(id);
2051
+ });
2052
+ child.on("error", async (err) => {
2053
+ console.error(`[worker] failed to spawn process for #${id}: ${err.message}`);
2054
+ await finishTask(id, { status: "failed", output: err.message }, onCompleteWithReport);
2055
+ childProcesses.delete(id);
2056
+ });
2057
+ }
2058
+ function waitForAllProcesses() {
2059
+ return new Promise((resolve5) => {
2060
+ const check = () => {
2061
+ if (childProcesses.size === 0 && herdrTasks.size === 0) {
2062
+ resolve5();
2063
+ } else {
2064
+ setTimeout(check, 500);
2065
+ }
2066
+ };
2067
+ check();
2068
+ });
2069
+ }
2070
+ function shutdown(signal = "SIGTERM") {
2071
+ for (const [, child] of childProcesses) {
2072
+ if (!child.pid) continue;
2073
+ try {
2074
+ process.kill(-child.pid, signal);
2075
+ } catch {
2076
+ try {
2077
+ child.kill(signal);
2078
+ } catch {
2079
+ }
2080
+ }
2081
+ }
2082
+ if (herdrTasks.size > 0) {
2083
+ herdrAbortSignal.aborted = true;
2084
+ }
2085
+ }
2086
+
2087
+ // src/random-name.ts
2088
+ import { randomInt } from "node:crypto";
2089
+ var ADJECTIVES = [
2090
+ "brave",
2091
+ "calm",
2092
+ "dark",
2093
+ "eager",
2094
+ "fair",
2095
+ "gentle",
2096
+ "happy",
2097
+ "keen",
2098
+ "lively",
2099
+ "noble",
2100
+ "proud",
2101
+ "quiet",
2102
+ "rapid",
2103
+ "sharp",
2104
+ "swift",
2105
+ "tender",
2106
+ "vivid",
2107
+ "warm",
2108
+ "bold",
2109
+ "bright",
2110
+ "clever",
2111
+ "daring",
2112
+ "fierce",
2113
+ "grand",
2114
+ "humble",
2115
+ "jovial",
2116
+ "kind",
2117
+ "lucky",
2118
+ "mighty",
2119
+ "neat",
2120
+ "agile",
2121
+ "amber",
2122
+ "breezy",
2123
+ "crisp",
2124
+ "deep",
2125
+ "dusty",
2126
+ "elfin",
2127
+ "frosty",
2128
+ "golden",
2129
+ "hardy",
2130
+ "icy",
2131
+ "joyful",
2132
+ "lean",
2133
+ "misty",
2134
+ "nimble",
2135
+ "open",
2136
+ "peppy",
2137
+ "radiant",
2138
+ "serene",
2139
+ "tidy",
2140
+ "upbeat",
2141
+ "valiant",
2142
+ "wiry",
2143
+ "zesty",
2144
+ "ancient",
2145
+ "brisk",
2146
+ "crispy",
2147
+ "deft",
2148
+ "earnest",
2149
+ "fleet",
2150
+ "airy",
2151
+ "ardent",
2152
+ "astute",
2153
+ "azure",
2154
+ "balmy",
2155
+ "blithe",
2156
+ "buoyant",
2157
+ "candid",
2158
+ "cheery",
2159
+ "coastal",
2160
+ "dashing",
2161
+ "dazzling",
2162
+ "devoted",
2163
+ "dynamic",
2164
+ "edgy",
2165
+ "elegant",
2166
+ "enduring",
2167
+ "exotic",
2168
+ "fabled",
2169
+ "faithful",
2170
+ "famous",
2171
+ "fearless",
2172
+ "flawless",
2173
+ "fluent",
2174
+ "focused",
2175
+ "forceful",
2176
+ "forged",
2177
+ "frank",
2178
+ "fresh",
2179
+ "frugal",
2180
+ "gallant",
2181
+ "gifted",
2182
+ "glowing",
2183
+ "graceful",
2184
+ "grounded",
2185
+ "honest",
2186
+ "hopeful",
2187
+ "ideal",
2188
+ "immense",
2189
+ "intact",
2190
+ "intrepid",
2191
+ "ironclad",
2192
+ "ivory",
2193
+ "keen",
2194
+ "knowing",
2195
+ "lavish",
2196
+ "level",
2197
+ "lithe",
2198
+ "lofty",
2199
+ "loyal",
2200
+ "luminous",
2201
+ "majestic",
2202
+ "mellow",
2203
+ "mindful",
2204
+ "modern"
2205
+ ];
2206
+ var NOUNS = [
2207
+ "falcon",
2208
+ "river",
2209
+ "cedar",
2210
+ "flame",
2211
+ "stone",
2212
+ "breeze",
2213
+ "coral",
2214
+ "delta",
2215
+ "frost",
2216
+ "grove",
2217
+ "harbor",
2218
+ "iris",
2219
+ "jade",
2220
+ "lark",
2221
+ "maple",
2222
+ "orbit",
2223
+ "pearl",
2224
+ "ridge",
2225
+ "spark",
2226
+ "tide",
2227
+ "vale",
2228
+ "wolf",
2229
+ "apex",
2230
+ "bloom",
2231
+ "crest",
2232
+ "dawn",
2233
+ "ember",
2234
+ "flint",
2235
+ "hawk",
2236
+ "sage",
2237
+ "arrow",
2238
+ "basin",
2239
+ "cliff",
2240
+ "dune",
2241
+ "echo",
2242
+ "fern",
2243
+ "gale",
2244
+ "helm",
2245
+ "isle",
2246
+ "jungle",
2247
+ "kite",
2248
+ "lance",
2249
+ "marsh",
2250
+ "nova",
2251
+ "oak",
2252
+ "pine",
2253
+ "quill",
2254
+ "reef",
2255
+ "shore",
2256
+ "thorn",
2257
+ "umber",
2258
+ "veil",
2259
+ "wave",
2260
+ "xenon",
2261
+ "yew",
2262
+ "zenith",
2263
+ "acorn",
2264
+ "birch",
2265
+ "comet",
2266
+ "dew",
2267
+ "abyss",
2268
+ "anchor",
2269
+ "anvil",
2270
+ "arc",
2271
+ "ash",
2272
+ "atlas",
2273
+ "aurora",
2274
+ "axe",
2275
+ "bay",
2276
+ "beacon",
2277
+ "blade",
2278
+ "bolt",
2279
+ "brook",
2280
+ "bud",
2281
+ "burl",
2282
+ "cairn",
2283
+ "canopy",
2284
+ "cape",
2285
+ "cavern",
2286
+ "chain",
2287
+ "chalk",
2288
+ "chasm",
2289
+ "chord",
2290
+ "chrome",
2291
+ "citrus",
2292
+ "claw",
2293
+ "cloud",
2294
+ "cobalt",
2295
+ "creek",
2296
+ "crown",
2297
+ "crystal",
2298
+ "current",
2299
+ "cypress",
2300
+ "depot",
2301
+ "depth",
2302
+ "drift",
2303
+ "dusk",
2304
+ "dust",
2305
+ "eddy",
2306
+ "elm",
2307
+ "epoch",
2308
+ "ether",
2309
+ "field",
2310
+ "fin",
2311
+ "fjord",
2312
+ "foam",
2313
+ "forge",
2314
+ "fossil",
2315
+ "geyser",
2316
+ "glacier",
2317
+ "glow",
2318
+ "granite",
2319
+ "heath",
2320
+ "heron",
2321
+ "hollow"
2322
+ ];
2323
+ function pick(list) {
2324
+ return list[randomInt(list.length)];
2325
+ }
2326
+ function generateWorktreeName() {
2327
+ const suffix = String(randomInt(1e4)).padStart(4, "0");
2328
+ return `${pick(ADJECTIVES)}-${pick(NOUNS)}-${suffix}`;
2329
+ }
2330
+ function isGeneratedWorktreeName(name) {
2331
+ return /^[a-z]+-[a-z]+-\d{4}$/.test(name);
2332
+ }
2333
+
2334
+ // src/slack.ts
2335
+ import { exec } from "node:child_process";
2336
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "node:fs";
2337
+ import { homedir as homedir3 } from "node:os";
2338
+ import { join as join4 } from "node:path";
2339
+ import { promisify as promisify2 } from "node:util";
2340
+
2341
+ // src/runcat.ts
2342
+ import { mkdirSync as mkdirSync2, renameSync as renameSync2, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
2343
+ import { homedir as homedir2 } from "node:os";
2344
+ import { dirname as dirname2, join as join3 } from "node:path";
2345
+ var RUNCAT_OUT_FILE = process.env.RUNCAT_OUT_FILE ?? join3(process.env.HOME ?? homedir2(), ".claude", "runcat-usage.json");
2346
+ var JST = "Asia/Tokyo";
2347
+ function formatG(value) {
2348
+ return String(Number(value.toPrecision(6)));
2349
+ }
2350
+ function jstFields(date) {
2351
+ const parts = new Intl.DateTimeFormat("en-US", {
2352
+ timeZone: JST,
2353
+ month: "2-digit",
2354
+ day: "2-digit",
2355
+ hour: "2-digit",
2356
+ minute: "2-digit",
2357
+ hourCycle: "h23"
2358
+ }).formatToParts(date);
2359
+ const pick2 = (type) => parts.find((p) => p.type === type)?.value ?? "";
2360
+ return { month: pick2("month"), day: pick2("day"), hour: pick2("hour"), minute: pick2("minute") };
2361
+ }
2362
+ var MINUTE_MS = 6e4;
2363
+ function ceilToMinute(date) {
2364
+ const remainder = date.getTime() % MINUTE_MS;
2365
+ return remainder === 0 ? date : new Date(date.getTime() + (MINUTE_MS - remainder));
2366
+ }
2367
+ function parseResetsAt(isoString) {
2368
+ const date = new Date(isoString);
2369
+ return Number.isNaN(date.getTime()) ? null : ceilToMinute(date);
2370
+ }
2371
+ function resetStamp(isoString, now = /* @__PURE__ */ new Date()) {
2372
+ const date = parseResetsAt(isoString);
2373
+ if (!date) return "";
2374
+ const reset = jstFields(date);
2375
+ const today = jstFields(now);
2376
+ const time = `${reset.hour}:${reset.minute}`;
2377
+ if (reset.month === today.month && reset.day === today.day) return time;
2378
+ return `${reset.month}/${reset.day} ${time}`;
2379
+ }
2380
+ function resetHour(isoString) {
2381
+ const date = parseResetsAt(isoString);
2382
+ return date ? jstFields(date).hour : "";
2383
+ }
2384
+ function metric(title, pct, resetsAt, now) {
2385
+ const stamp = resetStamp(resetsAt, now);
2386
+ return {
2387
+ title,
2388
+ formattedValue: `${formatG(pct)}%` + (stamp ? ` \u21BB${stamp}` : ""),
2389
+ normalizedValue: Math.round(pct / 100 * 1e4) / 1e4
2390
+ };
2391
+ }
2392
+ function buildRuncatSnapshot(usage, now = /* @__PURE__ */ new Date()) {
2393
+ const bar = `${formatG(usage.fiveHourUtilization)}/${formatG(usage.sevenDayUtilization)}`;
2394
+ const stamp = resetHour(usage.fiveHourResetsAt);
2395
+ return {
2396
+ title: "Claude Code",
2397
+ symbol: "staroflife",
2398
+ metrics: [
2399
+ metric("5h", usage.fiveHourUtilization, usage.fiveHourResetsAt, now),
2400
+ metric("7d", usage.sevenDayUtilization, usage.sevenDayResetsAt, now)
2401
+ ],
2402
+ metricsBarValue: stamp ? `${bar} \u21BB${stamp}` : bar,
2403
+ lastUpdatedDate: now.toISOString().replace(/\.\d{3}Z$/, "Z")
2404
+ };
2405
+ }
2406
+ function writeRuncatUsage(usage, outFile = RUNCAT_OUT_FILE) {
2407
+ const snapshot = buildRuncatSnapshot(usage);
2408
+ const dir = dirname2(outFile);
2409
+ const tmp = join3(dir, `.runcat-${process.pid}-${Date.now()}.json`);
2410
+ try {
2411
+ mkdirSync2(dir, { recursive: true });
2412
+ writeFileSync3(tmp, JSON.stringify(snapshot), "utf-8");
2413
+ renameSync2(tmp, outFile);
2414
+ } catch (err) {
2415
+ rmSync(tmp, { force: true });
2416
+ console.error(`[runcat] Failed to write ${outFile}: ${err}`);
2417
+ }
2418
+ }
2419
+
2420
+ // src/slack.ts
2421
+ var execAsync = promisify2(exec);
2422
+ var WEBHOOK_URL = process.env.CLAUDE_TASK_WORKER_SLACK_WEBHOOK_URL;
2423
+ var USAGE_CACHE_PATH = "/tmp/claude-usage-cache.json";
2424
+ var USAGE_CACHE_TTL_SECONDS = 360;
2425
+ async function send(payload) {
2426
+ if (!WEBHOOK_URL) return;
2427
+ try {
2428
+ await fetch(WEBHOOK_URL, {
2429
+ method: "POST",
2430
+ headers: { "Content-Type": "application/json" },
2431
+ body: JSON.stringify(payload)
2432
+ });
2433
+ } catch (err) {
2434
+ console.error(`[slack] Failed to send notification: ${err}`);
2435
+ }
2436
+ }
2437
+ function extractToken(credentials) {
2438
+ if (typeof credentials === "string") return credentials;
2439
+ return credentials.claudeAiOauth?.accessToken ?? credentials.oauth_token ?? credentials.access_token ?? "";
2440
+ }
2441
+ async function getOAuthToken() {
2442
+ try {
2443
+ const { stdout } = await execAsync('security find-generic-password -s "Claude Code-credentials" -w');
2444
+ return extractToken(JSON.parse(stdout.trim()));
2445
+ } catch {
2446
+ const raw = readFileSync3(join4(homedir3(), ".claude", ".credentials.json"), "utf-8");
2447
+ return extractToken(JSON.parse(raw));
2448
+ }
2449
+ }
2450
+ function readUsageCache() {
2451
+ try {
2452
+ const raw = readFileSync3(USAGE_CACHE_PATH, "utf-8");
2453
+ const cached = JSON.parse(raw);
2454
+ if (Date.now() - cached.timestamp < USAGE_CACHE_TTL_SECONDS * 1e3) {
2455
+ return cached.data;
2456
+ }
2457
+ } catch {
2458
+ }
2459
+ return null;
2460
+ }
2461
+ function writeUsageCache(data) {
2462
+ try {
2463
+ writeFileSync4(USAGE_CACHE_PATH, JSON.stringify({ timestamp: Date.now(), data }));
2464
+ } catch {
2465
+ }
2466
+ }
2467
+ async function fetchUsageInfo() {
2468
+ const cached = readUsageCache();
2469
+ if (cached) return cached;
2470
+ try {
2471
+ const token = await getOAuthToken();
2472
+ const res = await fetch("https://api.anthropic.com/api/oauth/usage", {
2473
+ headers: {
2474
+ Authorization: `Bearer ${token}`,
2475
+ "anthropic-beta": "oauth-2025-04-20"
2476
+ }
2477
+ });
2478
+ if (!res.ok) {
2479
+ console.error(`[slack] Usage API returned ${res.status}`);
2480
+ return null;
2481
+ }
2482
+ const body = await res.json();
2483
+ const data = {
2484
+ fiveHourUtilization: body.five_hour.utilization,
2485
+ fiveHourResetsAt: body.five_hour.resets_at,
2486
+ sevenDayUtilization: body.seven_day.utilization,
2487
+ sevenDayResetsAt: body.seven_day.resets_at
2488
+ };
2489
+ writeUsageCache(data);
2490
+ return data;
2491
+ } catch (err) {
2492
+ console.error(`[slack] Failed to fetch usage info: ${err}`);
2493
+ return null;
2494
+ }
2495
+ }
2496
+ function utilizationEmoji(value) {
2497
+ if (value < 50) return "\u{1F7E2}";
2498
+ if (value < 80) return "\u{1F7E1}";
2499
+ return "\u{1F534}";
2500
+ }
2501
+ function formatResetTimeJST(isoString) {
2502
+ const date = new Date(isoString);
2503
+ return date.toLocaleString("ja-JP", {
2504
+ timeZone: "Asia/Tokyo",
2505
+ month: "numeric",
2506
+ day: "numeric",
2507
+ hour: "numeric",
2508
+ minute: "2-digit"
2509
+ });
2510
+ }
2511
+ function formatTokenLimitText(usage) {
2512
+ const fiveH = usage.fiveHourUtilization.toFixed(1);
2513
+ const sevenD = usage.sevenDayUtilization.toFixed(1);
2514
+ const emoji = utilizationEmoji(Math.max(usage.fiveHourUtilization, usage.sevenDayUtilization));
2515
+ const fiveHReset = formatResetTimeJST(usage.fiveHourResetsAt);
2516
+ const sevenDReset = formatResetTimeJST(usage.sevenDayResetsAt);
2517
+ return ` | ${emoji} 5h: ${fiveH}% (reset: ${fiveHReset}) / 7d: ${sevenD}% (reset: ${sevenDReset})`;
2518
+ }
2519
+ async function buildTokenLimitText() {
2520
+ const usage = await fetchUsageInfo();
2521
+ if (!usage) return "";
2522
+ writeRuncatUsage(usage);
2523
+ return formatTokenLimitText(usage);
2524
+ }
2525
+ function truncateOutputForNotification(output) {
2526
+ if (output.length <= 1e3) return output;
2527
+ if (output.startsWith(CLOUD_FAILURE_GUIDANCE)) {
2528
+ const rest = output.slice(CLOUD_FAILURE_GUIDANCE.length);
2529
+ return `${CLOUD_FAILURE_GUIDANCE}\u2026${rest.slice(-1e3)}`;
2530
+ }
2531
+ return `\u2026${output.slice(-1e3)}`;
2532
+ }
2533
+ function buildTaskNotificationText(params) {
2534
+ const { status, workerName, repoName, id, title, url, tokenText, output, cloud } = params;
2535
+ const emoji = status === "completed" ? "\u2705" : "\u274C";
2536
+ const label = status === "completed" ? "completed" : "failed";
2537
+ const truncatedOutput = output ? truncateOutputForNotification(output) : output;
2538
+ const outputBlock = truncatedOutput ? `
2539
+ \`\`\`${truncatedOutput}\`\`\`` : "";
2540
+ const body = `${emoji} [${workerName}] ${repoName} | Task ${label}: <${url}|#${id} ${title}>${tokenText}${outputBlock}`;
2541
+ let sessionUrlPrefix = "";
2542
+ if (cloud) {
2543
+ sessionUrlPrefix = cloud.sessionId?.trim() ? `https://claude.ai/code/${cloud.sessionId.trim()}
2544
+ ` : "\u30BB\u30C3\u30B7\u30E7\u30F3URL\u4E0D\u660E\uFF08ID\u62BD\u51FA\u306B\u5931\u6557\uFF09\n";
2545
+ }
2546
+ return `${sessionUrlPrefix}${body}`;
2547
+ }
2548
+ async function notifyTaskCompleted(workerName, repoName, id, title, url, output, cloud) {
2549
+ const tokenText = await buildTokenLimitText();
2550
+ await send({
2551
+ text: buildTaskNotificationText({
2552
+ status: "completed",
2553
+ workerName,
2554
+ repoName,
2555
+ id,
2556
+ title,
2557
+ url,
2558
+ tokenText,
2559
+ output,
2560
+ cloud
2561
+ })
2562
+ });
2563
+ }
2564
+ async function notifyTaskFailed(workerName, repoName, id, title, url, output, cloud) {
2565
+ const tokenText = await buildTokenLimitText();
2566
+ await send({
2567
+ text: buildTaskNotificationText({
2568
+ status: "failed",
2569
+ workerName,
2570
+ repoName,
2571
+ id,
2572
+ title,
2573
+ url,
2574
+ tokenText,
2575
+ output,
2576
+ cloud
2577
+ })
2578
+ });
2579
+ }
2580
+ async function notifyError(workerName, repoName, error) {
2581
+ const message = error instanceof Error ? error.message : String(error);
2582
+ await send({
2583
+ text: `\u{1F6A8} [${workerName}] ${repoName} | Error: ${message}`
2584
+ });
2585
+ }
2586
+
2587
+ // src/worktree.ts
2588
+ import { createRequire as createRequire2 } from "node:module";
2589
+ import { promisify as promisify3 } from "node:util";
2590
+ import { readdir, rm, stat } from "node:fs/promises";
2591
+ import { basename as basename2, resolve as resolve4, sep } from "node:path";
2592
+ var childProcess2 = createRequire2(import.meta.url)("node:child_process");
2593
+ var execFileAsync2 = (command, args) => promisify3(childProcess2.execFile)(command, args);
2594
+ var WORKTREES_DIR = ".claude/worktrees";
2595
+ function isManagedWorktreePath(path2) {
2596
+ return resolve4(path2).startsWith(resolve4(WORKTREES_DIR) + sep);
2597
+ }
2598
+ async function pathExists(path2) {
2599
+ try {
2600
+ await stat(path2);
2601
+ return true;
2602
+ } catch {
2603
+ return false;
2604
+ }
2605
+ }
2606
+ async function forceRemoveIfExists(path2) {
2607
+ if (!isManagedWorktreePath(path2)) {
2608
+ console.error(`[worktree] Refusing to remove path outside ${WORKTREES_DIR}: ${path2}`);
2609
+ return;
2610
+ }
2611
+ if (!await pathExists(path2)) return;
2612
+ try {
2613
+ await rm(path2, { recursive: true, force: true });
2614
+ console.log(`[worktree] Force removed remaining directory: ${path2}`);
2615
+ } catch (error) {
2616
+ console.error(`[worktree] Failed to remove directory ${path2}:`, error);
2617
+ }
2618
+ }
2619
+ async function listWorktreeEntries() {
2620
+ const { stdout } = await execFileAsync2("git", ["worktree", "list", "--porcelain"]);
2621
+ return stdout.replace(/\r\n/g, "\n").trim().split("\n\n").filter(Boolean).map((entry) => {
2622
+ const lines = entry.split("\n");
2623
+ const worktreeLine = lines.find((l) => l.startsWith("worktree "));
2624
+ const branchLine = lines.find((l) => l.startsWith("branch refs/heads/"));
2625
+ return {
2626
+ path: worktreeLine ? worktreeLine.slice("worktree ".length) : "",
2627
+ branch: branchLine?.slice("branch refs/heads/".length),
2628
+ locked: lines.some((l) => l === "locked" || l.startsWith("locked "))
2629
+ };
2630
+ }).filter((e) => e.path !== "");
2631
+ }
2632
+ async function removeRegisteredWorktree(worktreePath) {
2633
+ try {
2634
+ await execFileAsync2("git", ["worktree", "remove", "--force", "--force", worktreePath]);
2635
+ } catch (error) {
2636
+ console.error(`[worktree] Failed to remove worktree ${worktreePath}:`, error);
2637
+ }
2638
+ await forceRemoveIfExists(worktreePath);
2639
+ }
2640
+ var PROTECTED_BRANCHES = /* @__PURE__ */ new Set(["main", "master", "develop"]);
2641
+ async function getDefaultBranchName() {
2642
+ try {
2643
+ const { stdout } = await execFileAsync2("git", ["symbolic-ref", "--short", "refs/remotes/origin/HEAD"]);
2644
+ return stdout.trim().replace(/^origin\//, "");
2645
+ } catch {
2646
+ return void 0;
2647
+ }
2648
+ }
2649
+ async function deleteLocalBranch(branchName) {
2650
+ const defaultBranch = await getDefaultBranchName();
2651
+ if (PROTECTED_BRANCHES.has(branchName) || branchName === defaultBranch) {
2652
+ console.log(`[worktree] Skipping deletion of protected branch: ${branchName}`);
2653
+ return;
2654
+ }
2655
+ try {
2656
+ await execFileAsync2("git", ["branch", "-D", branchName]);
2657
+ console.log(`[worktree] Deleted local branch: ${branchName}`);
2658
+ } catch (error) {
2659
+ const stderr = String(error?.stderr ?? "");
2660
+ if (/not found/i.test(stderr)) return;
2661
+ console.error(`[worktree] Failed to delete local branch ${branchName}: ${stderr.trim()}`);
2662
+ }
2663
+ }
2664
+ async function localBranchExists(branchName) {
2665
+ try {
2666
+ await execFileAsync2("git", ["rev-parse", "--verify", "--quiet", `refs/heads/${branchName}`]);
2667
+ return true;
2668
+ } catch {
2669
+ return false;
2670
+ }
2671
+ }
2672
+ function getWorktreePath(worktreeId) {
2673
+ return `${WORKTREES_DIR}/${worktreeId}`;
2674
+ }
2675
+ var worktreeAddQueue = Promise.resolve();
2676
+ function createWorktreeFromBranch(worktreeId, baseBranch) {
2677
+ const result = worktreeAddQueue.then(() => addWorktree(worktreeId, baseBranch));
2678
+ worktreeAddQueue = result.catch(() => {
2679
+ });
2680
+ return result;
2681
+ }
2682
+ async function addWorktree(worktreeId, baseBranch) {
2683
+ const worktreePath = getWorktreePath(worktreeId);
2684
+ await execFileAsync2("git", ["worktree", "add", "--track", "-B", worktreeId, worktreePath, `origin/${baseBranch}`]);
2685
+ }
2686
+ async function removeWorktree(worktreeId) {
2687
+ const worktreePath = getWorktreePath(worktreeId);
2688
+ let entry;
2689
+ try {
2690
+ entry = (await listWorktreeEntries()).find((e) => resolve4(e.path) === resolve4(worktreePath));
2691
+ } catch (error) {
2692
+ console.error(`[worktree] Failed to list worktrees:`, error);
2693
+ }
2694
+ if (entry) {
2695
+ await removeRegisteredWorktree(worktreePath);
2696
+ } else {
2697
+ await forceRemoveIfExists(worktreePath);
2698
+ }
2699
+ const branches = /* @__PURE__ */ new Set([entry?.branch, worktreeId]);
2700
+ for (const branch of branches) {
2701
+ if (!branch) continue;
2702
+ await deleteLocalBranch(branch);
2703
+ }
2704
+ }
2705
+ async function removeWorktreeByBranch(branchName) {
2706
+ try {
2707
+ const entries = await listWorktreeEntries();
2708
+ for (const entry of entries) {
2709
+ if (entry.branch !== branchName) continue;
2710
+ if (!isManagedWorktreePath(entry.path)) {
2711
+ console.log(`[worktree] Skipping unmanaged worktree for branch ${branchName}: ${entry.path}`);
2712
+ continue;
2713
+ }
2714
+ const worktreeId = basename2(entry.path);
2715
+ if (isWorktreeInUse(worktreeId)) {
2716
+ console.log(`[worktree] Skipping worktree in use by a running task for branch ${branchName}: ${entry.path}`);
2717
+ continue;
2718
+ }
2719
+ if (entry.locked && await pathExists(entry.path)) {
2720
+ console.log(`[worktree] Skipping locked worktree for branch ${branchName}: ${entry.path}`);
2721
+ continue;
2722
+ }
2723
+ await removeRegisteredWorktree(entry.path);
2724
+ console.log(`[worktree] Removed worktree for branch ${branchName}: ${entry.path}`);
2725
+ }
2726
+ } catch (error) {
2727
+ console.error(`[worktree] Failed to remove worktree for branch ${branchName}:`, error);
2728
+ }
2729
+ }
2730
+ async function removeStaleWorktrees() {
2731
+ const worktreeIds = /* @__PURE__ */ new Set();
2732
+ try {
2733
+ for (const name of await readdir(WORKTREES_DIR)) {
2734
+ if (isGeneratedWorktreeName(name)) worktreeIds.add(name);
2735
+ }
2736
+ } catch {
2737
+ }
2738
+ try {
2739
+ for (const entry of await listWorktreeEntries()) {
2740
+ if (!isManagedWorktreePath(entry.path)) continue;
2741
+ const worktreeId = basename2(entry.path);
2742
+ if (isGeneratedWorktreeName(worktreeId)) worktreeIds.add(worktreeId);
2743
+ }
2744
+ } catch (error) {
2745
+ console.error(`[worktree] Failed to list worktrees:`, error);
2746
+ }
2747
+ for (const worktreeId of worktreeIds) {
2748
+ if (isWorktreeInUse(worktreeId)) continue;
2749
+ console.log(`[worktree] Removing stale worktree: ${worktreeId}`);
2750
+ await removeWorktree(worktreeId);
2751
+ }
2752
+ }
2753
+
2754
+ // src/workers/worker-definition.ts
2755
+ var WORKER_DEFINITION = /* @__PURE__ */ Symbol.for("claude-task-worker.worker-definition");
2756
+ function defineWorker(def) {
2757
+ return { [WORKER_DEFINITION]: true, name: def.name, kind: def.kind, start: def.start };
2758
+ }
2759
+ var KINDS = ["issue", "pr", "scheduled"];
2760
+ function isWorkerDefinition(value) {
2761
+ if (typeof value !== "object" || value === null) return false;
2762
+ const v = value;
2763
+ return v[WORKER_DEFINITION] === true && typeof v.name === "string" && v.name !== "" && typeof v.kind === "string" && KINDS.includes(v.kind) && typeof v.start === "function";
2764
+ }
2765
+
2766
+ // src/workers/issue-worker.ts
2767
+ var STICKY_LABELS = ["cc-triage-scope", "cc-issue-created"];
2768
+ var consumableTriggerLabels = (triggerLabels) => triggerLabels.filter((label) => !STICKY_LABELS.includes(label));
2769
+ var PREFLIGHT_SEARCH_LIMIT = 5;
2770
+ function createIssuePollingWorker(config) {
2771
+ const start = async (opts = {}) => {
2772
+ const epicFilters = opts.epicFilters;
2773
+ const ownNumberFilters = config.epicFilterTarget === "self" ? opts.epicFilters : void 0;
2774
+ const labelFilters = opts.labelFilters;
2775
+ const { owner, name, defaultBranch } = await getRepoInfo();
2776
+ const user = await getCurrentUser();
2777
+ const { pollingIntervalSeconds, cooldownSeconds } = getWorkerConfig(config.name);
2778
+ const pollingIntervalMs = pollingIntervalSeconds * 1e3;
2779
+ const cooldownMs = cooldownSeconds * 1e3;
2780
+ console.log(
2781
+ `[${config.name}] Polling issues every ${pollingIntervalSeconds} seconds for ${owner}/${name} (assignee: ${user})`
2782
+ );
2783
+ let lastCompletionAt = 0;
2784
+ const tick = async () => {
2785
+ if (isShuttingDown()) return;
2786
+ if (cooldownMs > 0 && lastCompletionAt > 0 && Date.now() - lastCompletionAt < cooldownMs) return;
2787
+ try {
2788
+ const excludeLabels = ["cc-in-progress", "cc-need-human-check", ...config.excludeLabels ?? []];
2789
+ const epicFilter = config.epicFilterTarget !== "self" && epicFilters && epicFilters.length > 0 ? { owner, repo: name, numbers: epicFilters } : void 0;
2790
+ const labels = labelFilters && labelFilters.length > 0 ? [...config.triggerLabels, ...labelFilters] : config.triggerLabels;
2791
+ const { maxConcurrentTasks } = getWorkerConfig(config.name);
2792
+ const searchLimit = config.preflight ? PREFLIGHT_SEARCH_LIMIT : maxConcurrentTasks;
2793
+ const candidates = ownNumberFilters && ownNumberFilters.length > 0 ? await listIssuesByNumbers(user, labels, excludeLabels, ownNumberFilters) : await listIssuesByLabel(user, labels, excludeLabels, epicFilter, searchLimit);
2794
+ for (const issue of candidates) {
2795
+ if (isRunning(issue.number)) continue;
2796
+ if (isWorkerAtCapacity(config.name)) break;
2797
+ let blocked = false;
2798
+ try {
2799
+ blocked = await hasOpenBlockers(issue.number);
2800
+ } catch (err) {
2801
+ console.error(`[${config.name}] hasOpenBlockers failed for #${issue.number}: ${err}`);
2802
+ }
2803
+ if (blocked) {
2804
+ console.log(`[${config.name}] #${issue.number}: skipped (blocked by an open issue)`);
2805
+ continue;
2806
+ }
2807
+ if (config.preflight) {
2808
+ const action = await config.preflight(issue);
2809
+ if (action === "skip") continue;
2810
+ if (action === "mark-pr-created") {
2811
+ await addLabel("issue", issue.number, "cc-pr-created").catch(
2812
+ (err) => console.error(`[${config.name}] addLabel cc-pr-created failed for #${issue.number}: ${err}`)
2813
+ );
2814
+ continue;
2815
+ }
2816
+ }
2817
+ await addLabel("issue", issue.number, "cc-in-progress");
2818
+ const worktreeId = generateWorktreeName();
2819
+ const cloud = isCloudWorker(config.name);
2820
+ try {
2821
+ const issueUrl = `https://github.com/${owner}/${name}/issues/${issue.number}`;
2822
+ syncDefaultBranch(defaultBranch);
2823
+ const { model, effort, skill, advisorModel } = getWorkerConfig(config.name);
2824
+ const command = skill || config.command;
2825
+ const parentNumber = issue.parent?.number;
2826
+ const mode = getRunMode();
2827
+ let baseBranch = defaultBranch;
2828
+ if (parentNumber !== void 0) {
2829
+ baseBranch = `cc-epic-${parentNumber}`;
2830
+ await ensureEpicBranch(baseBranch, defaultBranch);
2831
+ }
2832
+ const execution = buildClaudeExecution({
2833
+ mode,
2834
+ prompt: `${command} ${issue.number}`,
2835
+ model,
2836
+ effort,
2837
+ // config.json の advisor が false なら advisorModel の指定に関わらず渡さない。
2838
+ advisorModel: isAdvisorEnabled() ? advisorModel : "",
2839
+ permissionMode: getPermissionMode(),
2840
+ ...cloud ? { cloud: true, baseRef: baseBranch, remoteEnvId: getRemoteEnvId() ?? "" } : {}
2841
+ });
2842
+ let cwd;
2843
+ if (cloud) {
2844
+ console.log(
2845
+ `[${config.name}] #${issue.number}: cloud execution, running without worktree on ${baseBranch}`
2846
+ );
2847
+ } else {
2848
+ await createWorktreeFromBranch(worktreeId, baseBranch);
2849
+ cwd = getWorktreePath(worktreeId);
2850
+ console.log(`[${config.name}] #${issue.number}: created worktree ${worktreeId} from ${baseBranch}`);
2851
+ }
2852
+ if (cloud) {
2853
+ await removeLabel("issue", issue.number, CLOUD_DONE_LABEL).catch(
2854
+ (err) => console.error(`[${config.name}] removeLabel ${CLOUD_DONE_LABEL} failed for #${issue.number}: ${err}`)
2855
+ );
2856
+ }
2857
+ const startedAt = Date.now();
2858
+ run(
2859
+ execution.command,
2860
+ execution.args,
2861
+ issue.number,
2862
+ issue.title,
2863
+ config.name,
2864
+ cloud ? void 0 : worktreeId,
2865
+ async (status, output, cloudSessionId) => {
2866
+ lastCompletionAt = Date.now();
2867
+ for (const label of consumableTriggerLabels(config.triggerLabels)) {
2868
+ await removeLabel("issue", issue.number, label).catch(
2869
+ (err) => console.error(`[${config.name}] removeLabel ${label} failed for #${issue.number}: ${err}`)
2870
+ );
2871
+ }
2872
+ try {
2873
+ if (status === "completed") {
2874
+ const verified = await config.onCompleted?.(issue.number, worktreeId, output, {
2875
+ cloud,
2876
+ baseBranch,
2877
+ startedAt
2878
+ }) ?? true;
2879
+ if (verified === false) {
2880
+ await notifyTaskFailed(
2881
+ config.name,
2882
+ name,
2883
+ issue.number,
2884
+ issue.title,
2885
+ issueUrl,
2886
+ output,
2887
+ cloud ? { sessionId: cloudSessionId } : void 0
2888
+ );
2889
+ } else {
2890
+ await notifyTaskCompleted(
2891
+ config.name,
2892
+ name,
2893
+ issue.number,
2894
+ issue.title,
2895
+ issueUrl,
2896
+ output,
2897
+ cloud ? { sessionId: cloudSessionId } : void 0
2898
+ );
2899
+ }
2900
+ } else {
2901
+ await notifyTaskFailed(
2902
+ config.name,
2903
+ name,
2904
+ issue.number,
2905
+ issue.title,
2906
+ issueUrl,
2907
+ output,
2908
+ cloud ? { sessionId: cloudSessionId } : void 0
2909
+ );
2910
+ }
2911
+ } catch (err) {
2912
+ console.error(`[${config.name}] post-task error for #${issue.number}: ${err}`);
2913
+ await notifyTaskFailed(
2914
+ config.name,
2915
+ name,
2916
+ issue.number,
2917
+ issue.title,
2918
+ issueUrl,
2919
+ output,
2920
+ cloud ? { sessionId: cloudSessionId } : void 0
2921
+ ).catch(
2922
+ (notifyErr) => console.error(`[${config.name}] notifyTaskFailed failed for #${issue.number}: ${notifyErr}`)
2923
+ );
2924
+ } finally {
2925
+ await removeLabel("issue", issue.number, "cc-in-progress").catch(
2926
+ (err) => console.error(`[${config.name}] removeLabel cc-in-progress failed for #${issue.number}: ${err}`)
2927
+ );
2928
+ if (!cloud) {
2929
+ await removeWorktree(worktreeId).catch(
2930
+ (err) => console.error(`[${config.name}] removeWorktree failed for #${issue.number}: ${err}`)
2931
+ );
2932
+ }
2933
+ }
2934
+ },
2935
+ cwd,
2936
+ buildClaudeEnv(mode, cloud),
2937
+ execution.prompt,
2938
+ cloud,
2939
+ // クラウドでは cc-cloud-done の探索先、ローカルでは --debug の報告コメントの
2940
+ // 投稿先。どちらも対象は同じ Issue なので実行形態で出し分けない。
2941
+ { type: "issue", number: issue.number },
2942
+ model
2943
+ );
2944
+ } catch (err) {
2945
+ console.error(`[${config.name}] setup error for #${issue.number}: ${err}`);
2946
+ await removeLabel("issue", issue.number, "cc-in-progress").catch(() => {
2947
+ });
2948
+ if (!cloud) {
2949
+ await removeWorktree(worktreeId).catch(() => {
2950
+ });
2951
+ }
2952
+ await notifyError(config.name, name, err);
2953
+ }
2954
+ }
2955
+ } catch (err) {
2956
+ console.error(`[${config.name}] tick error: ${err}`);
2957
+ await notifyError(config.name, name, err);
2958
+ }
2959
+ };
2960
+ await tick();
2961
+ setInterval(tick, pollingIntervalMs);
2962
+ };
2963
+ return defineWorker({ name: config.name, kind: "issue", start });
2964
+ }
2965
+
2966
+ // src/workers/pr-worker.ts
2967
+ var LABEL_IN_PROGRESS = "cc-in-progress";
2968
+ var LABEL_TRIAGE_SCOPE = "cc-triage-scope";
2969
+ function createPrPollingWorker(config) {
2970
+ const start = async () => {
2971
+ const { owner, name, defaultBranch } = await getRepoInfo();
2972
+ const user = await getCurrentUser();
2973
+ const { pollingIntervalSeconds, cooldownSeconds } = getWorkerConfig(config.name);
2974
+ const pollingIntervalMs = pollingIntervalSeconds * 1e3;
2975
+ const cooldownMs = cooldownSeconds * 1e3;
2976
+ console.log(
2977
+ `[${config.name}] Polling PRs every ${pollingIntervalSeconds} seconds for ${owner}/${name} (assignee: ${user})`
2978
+ );
2979
+ let lastCompletionAt = 0;
2980
+ const tick = async () => {
2981
+ if (isShuttingDown()) return;
2982
+ if (cooldownMs > 0 && lastCompletionAt > 0 && Date.now() - lastCompletionAt < cooldownMs) return;
2983
+ try {
2984
+ const excludeLabels = [LABEL_IN_PROGRESS, ...config.excludeLabels ?? []];
2985
+ const candidates = await listPullRequestsWithChecks(user, config.triggerLabel, excludeLabels);
2986
+ for (const pr of candidates) {
2987
+ if (isRunning(pr.number)) continue;
2988
+ if (isWorkerAtCapacity(config.name)) break;
2989
+ const prUrl = `https://github.com/${owner}/${name}/pull/${pr.number}`;
2990
+ const hadTriageScope = pr.labels.some((l) => l.name === LABEL_TRIAGE_SCOPE);
2991
+ await addLabel("pr", pr.number, LABEL_IN_PROGRESS);
2992
+ const isCloud = isCloudWorker(config.name);
2993
+ const worktreeId = isCloud ? void 0 : generateWorktreeName();
2994
+ try {
2995
+ if (worktreeId) {
2996
+ await removeWorktreeByBranch(pr.headRefName);
2997
+ await deleteLocalBranch(pr.headRefName);
2998
+ if (await localBranchExists(pr.headRefName)) {
2999
+ console.error(
3000
+ `[${config.name}] PR #${pr.number}: branch ${pr.headRefName} is still checked out by another worktree; skipping this tick`
3001
+ );
3002
+ await removeLabel("pr", pr.number, LABEL_IN_PROGRESS).catch(() => {
3003
+ });
3004
+ continue;
3005
+ }
3006
+ syncDefaultBranch(defaultBranch);
3007
+ await createWorktreeFromBranch(worktreeId, defaultBranch);
3008
+ }
3009
+ const cwd = worktreeId ? getWorktreePath(worktreeId) : void 0;
3010
+ const { model, effort, skill, advisorModel } = getWorkerConfig(config.name);
3011
+ const command = skill || config.command;
3012
+ const mode = getRunMode();
3013
+ const execution = buildClaudeExecution({
3014
+ mode,
3015
+ prompt: `${command} ${pr.number}`,
3016
+ model,
3017
+ effort,
3018
+ // config.json の advisor が false なら advisorModel の指定に関わらず渡さない。
3019
+ advisorModel: isAdvisorEnabled() ? advisorModel : "",
3020
+ permissionMode: getPermissionMode(),
3021
+ cloud: isCloud,
3022
+ remoteEnvId: isCloud ? getRemoteEnvId() ?? "" : void 0,
3023
+ onBranch: isCloud ? pr.headRefName : void 0
3024
+ });
3025
+ if (isCloud) {
3026
+ await removeLabel("pr", pr.number, CLOUD_DONE_LABEL).catch(
3027
+ (err) => console.error(`[${config.name}] removeLabel ${CLOUD_DONE_LABEL} failed for PR #${pr.number}: ${err}`)
3028
+ );
3029
+ }
3030
+ run(
3031
+ execution.command,
3032
+ execution.args,
3033
+ pr.number,
3034
+ `PR #${pr.number} (${pr.headRefName})`,
3035
+ config.name,
3036
+ worktreeId,
3037
+ async (status, output, cloudSessionId) => {
3038
+ lastCompletionAt = Date.now();
3039
+ try {
3040
+ if (status === "completed") {
3041
+ await config.onCompleted?.(pr, output);
3042
+ await notifyTaskCompleted(
3043
+ config.name,
3044
+ name,
3045
+ pr.number,
3046
+ pr.title,
3047
+ prUrl,
3048
+ output,
3049
+ isCloud ? { sessionId: cloudSessionId } : void 0
3050
+ );
3051
+ } else {
3052
+ await notifyTaskFailed(
3053
+ config.name,
3054
+ name,
3055
+ pr.number,
3056
+ pr.title,
3057
+ prUrl,
3058
+ output,
3059
+ isCloud ? { sessionId: cloudSessionId } : void 0
3060
+ );
3061
+ }
3062
+ } catch (err) {
3063
+ console.error(`[${config.name}] post-task error for PR #${pr.number}: ${err}`);
3064
+ } finally {
3065
+ if (!config.keepTriggerLabel) {
3066
+ await removeLabel("pr", pr.number, config.triggerLabel).catch(
3067
+ (err) => console.error(
3068
+ `[${config.name}] removeLabel ${config.triggerLabel} failed for PR #${pr.number}: ${err}`
3069
+ )
3070
+ );
3071
+ }
3072
+ if (hadTriageScope) {
3073
+ await addLabel("pr", pr.number, LABEL_TRIAGE_SCOPE).catch(
3074
+ (err) => console.error(
3075
+ `[${config.name}] addLabel ${LABEL_TRIAGE_SCOPE} failed for PR #${pr.number}: ${err}`
3076
+ )
3077
+ );
3078
+ }
3079
+ if (config.onFinally) {
3080
+ await config.onFinally(pr).catch((err) => console.error(`[${config.name}] onFinally failed for PR #${pr.number}: ${err}`));
3081
+ }
3082
+ await removeLabel("pr", pr.number, LABEL_IN_PROGRESS).catch(
3083
+ (err) => console.error(
3084
+ `[${config.name}] removeLabel ${LABEL_IN_PROGRESS} failed for PR #${pr.number}: ${err}`
3085
+ )
3086
+ );
3087
+ if (worktreeId) {
3088
+ await removeWorktree(worktreeId).catch(
3089
+ (err) => console.error(`[${config.name}] removeWorktree failed for PR #${pr.number}: ${err}`)
3090
+ );
3091
+ }
3092
+ }
3093
+ },
3094
+ cwd,
3095
+ buildClaudeEnv(mode, isCloud),
3096
+ execution.prompt,
3097
+ isCloud,
3098
+ // クラウドでは cc-cloud-done の探索先、ローカルでは --debug の報告コメントの
3099
+ // 投稿先。onBranch は `--on-branch` を渡したか(=クラウドのみ)を表す。
3100
+ { type: "pr", number: pr.number, onBranch: isCloud },
3101
+ model
3102
+ );
3103
+ } catch (err) {
3104
+ console.error(`[${config.name}] setup error for PR #${pr.number}: ${err}`);
3105
+ await removeLabel("pr", pr.number, LABEL_IN_PROGRESS).catch(() => {
3106
+ });
3107
+ if (worktreeId) {
3108
+ await removeWorktree(worktreeId).catch(() => {
3109
+ });
3110
+ }
3111
+ await notifyError(config.name, name, err);
3112
+ }
3113
+ }
3114
+ } catch (err) {
3115
+ console.error(`[${config.name}] tick error: ${err}`);
3116
+ await notifyError(config.name, name, err);
3117
+ }
3118
+ };
3119
+ await tick();
3120
+ setInterval(tick, pollingIntervalMs);
3121
+ };
3122
+ return defineWorker({ name: config.name, kind: "pr", start });
3123
+ }
3124
+
3125
+ // src/last-run-pr.ts
3126
+ import { createRequire as createRequire3 } from "node:module";
3127
+ import { promisify as promisify4 } from "node:util";
3128
+ var childProcess3 = createRequire3(import.meta.url)("node:child_process");
3129
+ var execFileAsync3 = (command, args) => promisify4(childProcess3.execFile)(command, args);
3130
+ var CONFIG_FILE = "claude-task-worker.json";
3131
+ var LABEL_TRIAGE_SCOPE2 = "cc-triage-scope";
3132
+ function lastRunBranchName(workerName) {
3133
+ return `ctw-last-run-${workerName}`;
3134
+ }
3135
+ function hasLastRunChange(porcelain) {
3136
+ return porcelain.trim().length > 0;
3137
+ }
3138
+ function lastRunPrTitle(workerName) {
3139
+ return `chore: ${workerName} \u306E\u5B9F\u884C\u8A18\u9332\u3092\u66F4\u65B0`;
3140
+ }
3141
+ function lastRunPrBody(workerName, at) {
3142
+ return [
3143
+ `\`claude-task-worker\` \u306E \`${workerName}\` \u30EF\u30FC\u30AB\u30FC\u304C\u3001\u5B9A\u671F\u5B9F\u884C\u306E\u8A18\u9332\uFF08\`lastRun.${workerName}\`\uFF09\u3092\u66F4\u65B0\u3057\u307E\u3057\u305F\u3002`,
3144
+ "",
3145
+ `- \u5B9F\u884C\u6642\u523B: ${at.toISOString()}`,
3146
+ "",
3147
+ `\u3053\u306EPR\u306F\u5B9F\u884C\u8A18\u9332\u306E\u307F\u3092\u66F4\u65B0\u3057\u307E\u3059\uFF08\u6210\u679C\u7269\u306E\u5909\u66F4\u306F\u540C\u30EF\u30FC\u30AB\u30FC\u304C\u8D77\u52D5\u3057\u305F\u30B9\u30AD\u30EB\u304C\u5225PR\u3067\u51FA\u3057\u307E\u3059\uFF09\u3002`,
3148
+ `\u30DE\u30FC\u30B8\u306F \`triage-pr\` \u30EF\u30FC\u30AB\u30FC\u304C\u884C\u3044\u307E\u3059\u3002`
3149
+ ].join("\n");
3150
+ }
3151
+ async function git(cwd, args) {
3152
+ const { stdout } = await execFileAsync3("git", ["-C", cwd, ...args]);
3153
+ return stdout;
3154
+ }
3155
+ async function publishLastRunPr(workerName, defaultBranch, at) {
3156
+ const branch = lastRunBranchName(workerName);
3157
+ const cwd = getWorktreePath(branch);
3158
+ try {
3159
+ await createWorktreeFromBranch(branch, defaultBranch);
3160
+ writeLastRun(cwd, workerName, at);
3161
+ if (!hasLastRunChange(await git(cwd, ["status", "--porcelain", CONFIG_FILE]))) {
3162
+ console.log(`[${workerName}] lastRun unchanged, skipping PR`);
3163
+ return null;
3164
+ }
3165
+ await git(cwd, ["add", CONFIG_FILE]);
3166
+ await git(cwd, ["commit", "-m", lastRunPrTitle(workerName)]);
3167
+ await git(cwd, ["push", "--force", "origin", `HEAD:refs/heads/${branch}`]);
3168
+ const existing = await findOpenPrNumberByHeadRef(branch);
3169
+ const prNumber = existing ?? await createPullRequest(defaultBranch, branch, lastRunPrTitle(workerName), lastRunPrBody(workerName, at));
3170
+ console.log(`[${workerName}] ${existing !== null ? "updated" : "opened"} lastRun PR #${prNumber}`);
3171
+ await addLabel("pr", prNumber, LABEL_TRIAGE_SCOPE2).catch(
3172
+ (err) => console.error(`[${workerName}] addLabel ${LABEL_TRIAGE_SCOPE2} failed for PR #${prNumber}: ${err}`)
3173
+ );
3174
+ await addAssignee("pr", prNumber, await getCurrentUser()).catch(
3175
+ (err) => console.error(`[${workerName}] addAssignee failed for PR #${prNumber}: ${err}`)
3176
+ );
3177
+ return prNumber;
3178
+ } finally {
3179
+ await removeWorktree(branch).catch((err) => console.error(`[${workerName}] removeWorktree failed: ${err}`));
3180
+ }
3181
+ }
3182
+
3183
+ // src/workers/scheduled-worker.ts
3184
+ var SCHEDULE_INTERVAL_HOURS = 24;
3185
+ var SCHEDULE_INTERVAL_MS = SCHEDULE_INTERVAL_HOURS * 60 * 60 * 1e3;
3186
+ var SCOPE_DAYS = SCHEDULE_INTERVAL_HOURS / 24;
3187
+ var nextTaskId = -1;
3188
+ var LABEL_IN_PROGRESS2 = "cc-in-progress";
3189
+ function createScheduledWorker(config) {
3190
+ const taskId = nextTaskId--;
3191
+ const start = async () => {
3192
+ const { owner, name: repoName, defaultBranch } = await getRepoInfo();
3193
+ const { pollingIntervalSeconds } = getWorkerConfig(config.name);
3194
+ console.log(
3195
+ `[${config.name}] Checking every ${pollingIntervalSeconds} seconds (runs at most once per ${SCHEDULE_INTERVAL_HOURS}h for ${owner}/${repoName})`
3196
+ );
3197
+ let startedAt = 0;
3198
+ const tick = async () => {
3199
+ if (isShuttingDown()) return;
3200
+ if (config.enabled && !config.enabled()) return;
3201
+ if (isRunning(taskId)) return;
3202
+ const now = Date.now();
3203
+ const lastRunAt = Math.max(getLastRunAt(config.name) ?? 0, startedAt);
3204
+ if (lastRunAt > 0 && now - lastRunAt < SCHEDULE_INTERVAL_MS) return;
3205
+ const worktreeId = generateWorktreeName();
3206
+ startedAt = now;
3207
+ let cloud = false;
3208
+ try {
3209
+ syncDefaultBranch(defaultBranch);
3210
+ const { model, effort, skill, advisorModel } = getWorkerConfig(config.name);
3211
+ cloud = isCloudWorker(config.name);
3212
+ const command = skill || config.command;
3213
+ const mode = getRunMode();
3214
+ const execution = buildClaudeExecution({
3215
+ mode,
3216
+ prompt: `${command} ${SCOPE_DAYS}`,
3217
+ model,
3218
+ effort,
3219
+ advisorModel: isAdvisorEnabled() ? advisorModel : "",
3220
+ permissionMode: getPermissionMode(),
3221
+ ...cloud ? { cloud: true, baseRef: defaultBranch, remoteEnvId: getRemoteEnvId() ?? "" } : {}
3222
+ });
3223
+ const lastRunPr = await publishLastRunPr(config.name, defaultBranch, new Date(now)).catch((err) => {
3224
+ console.error(`[${config.name}] publishLastRunPr failed: ${err}`);
3225
+ return null;
3226
+ });
3227
+ if (cloud && lastRunPr === null) {
3228
+ console.warn(
3229
+ `[${config.name}] no lastRun PR to carry the cc-cloud-done label; the cloud session will be reported as completed as soon as it is created`
3230
+ );
3231
+ }
3232
+ if (cloud && lastRunPr !== null) {
3233
+ await removeLabel("pr", lastRunPr, CLOUD_DONE_LABEL).catch(
3234
+ (err) => console.error(`[${config.name}] removeLabel ${CLOUD_DONE_LABEL} failed for PR #${lastRunPr}: ${err}`)
3235
+ );
3236
+ await addLabel("pr", lastRunPr, LABEL_IN_PROGRESS2).catch(
3237
+ (err) => console.error(`[${config.name}] addLabel ${LABEL_IN_PROGRESS2} failed for PR #${lastRunPr}: ${err}`)
3238
+ );
3239
+ }
3240
+ let cwd;
3241
+ if (cloud) {
3242
+ console.log(`[${config.name}] cloud execution, running without worktree on ${defaultBranch}`);
3243
+ } else {
3244
+ await createWorktreeFromBranch(worktreeId, defaultBranch);
3245
+ cwd = getWorktreePath(worktreeId);
3246
+ console.log(`[${config.name}] created worktree ${worktreeId} from ${defaultBranch}`);
3247
+ }
3248
+ const repoUrl = `https://github.com/${owner}/${repoName}`;
3249
+ run(
3250
+ execution.command,
3251
+ execution.args,
3252
+ taskId,
3253
+ config.name,
3254
+ config.name,
3255
+ cloud ? void 0 : worktreeId,
3256
+ async (status, output) => {
3257
+ try {
3258
+ if (status === "completed") {
3259
+ await notifyTaskCompleted(config.name, repoName, taskId, config.name, repoUrl, output);
3260
+ } else {
3261
+ await notifyTaskFailed(config.name, repoName, taskId, config.name, repoUrl, output);
3262
+ }
3263
+ } catch (err) {
3264
+ console.error(`[${config.name}] post-task error: ${err}`);
3265
+ } finally {
3266
+ if (cloud && lastRunPr !== null) {
3267
+ await removeLabel("pr", lastRunPr, LABEL_IN_PROGRESS2).catch(
3268
+ (err) => console.error(
3269
+ `[${config.name}] removeLabel ${LABEL_IN_PROGRESS2} failed for PR #${lastRunPr}: ${err}`
3270
+ )
3271
+ );
3272
+ }
3273
+ if (!cloud) {
3274
+ await removeWorktree(worktreeId).catch(
3275
+ (err) => console.error(`[${config.name}] removeWorktree failed: ${err}`)
3276
+ );
3277
+ }
3278
+ }
3279
+ },
3280
+ cwd,
3281
+ buildClaudeEnv(mode, cloud),
3282
+ execution.prompt,
3283
+ cloud,
3284
+ // 定期ワーカーは Issue/PR を起点に走らないため、クラウドの cc-cloud-done も
3285
+ // ローカル --debug の報告コメントも実行記録PRを置き先にする。
3286
+ lastRunPr !== null ? { type: "pr", number: lastRunPr } : void 0,
3287
+ model
3288
+ );
3289
+ } catch (err) {
3290
+ console.error(`[${config.name}] setup error: ${err}`);
3291
+ if (!cloud) {
3292
+ await removeWorktree(worktreeId).catch(() => {
3293
+ });
3294
+ }
3295
+ await notifyError(config.name, repoName, err);
3296
+ }
3297
+ };
3298
+ await tick();
3299
+ setInterval(tick, pollingIntervalSeconds * 1e3);
3300
+ };
3301
+ return defineWorker({ name: config.name, kind: "scheduled", start });
3302
+ }
3303
+
3304
+ export {
3305
+ getCurrentUser,
3306
+ getRepoInfo,
3307
+ listIssuesByLabel,
3308
+ listIssuesByNumbers,
3309
+ findOpenPrNumberByHeadRef,
3310
+ findPrNumberByHeadRef,
3311
+ findPrStateByHeadRef,
3312
+ getPrMergeable,
3313
+ getIssueBody,
3314
+ getIssueState,
3315
+ listPrsClosingIssue,
3316
+ getPrDetail,
3317
+ closeIssue,
3318
+ parseClosingIssueNumbers,
3319
+ listPrsCrossReferencingIssue,
3320
+ linkClosingPr,
3321
+ getIssueSubIssuesSummary,
3322
+ listPullRequestsWithChecks,
3323
+ addLabel,
3324
+ addAssignee,
3325
+ hasLabel,
3326
+ hasOpenBlockers,
3327
+ removeLabel,
3328
+ commentOnPR,
3329
+ commentOnIssue,
3330
+ createLabel,
3331
+ parseProjectFilters,
3332
+ hasProjectFilter,
3333
+ assertProjectCompatibleCommand,
3334
+ hasCloudFlag,
3335
+ INHERIT_CONFIG_FLAG,
3336
+ getInheritConfigPath,
3337
+ assertCloudCompatibleCommand,
3338
+ buildForwardedCommand,
3339
+ DEFAULT_UI_DESIGN_CONFIG,
3340
+ SCHEDULED_WORKER_NAMES,
3341
+ CLOUD_DONE_LABEL,
3342
+ isCloudWorker,
3343
+ checkCloudConfig,
3344
+ DEFAULT_CONFIG,
3345
+ CONFIG_PATH,
3346
+ LOCAL_CONFIG_PATH,
3347
+ resolveWorkerFilePath,
3348
+ loadConfig,
3349
+ getWorkerConfig,
3350
+ isWorkerEnabled,
3351
+ partitionEnabledWorkers,
3352
+ disabledWorkerMessage,
3353
+ getLastRunAt,
3354
+ writeLastRun,
3355
+ getRemoteEnvId,
3356
+ getUiDesignConfig,
3357
+ UserConfigError,
3358
+ loadUserConfig,
3359
+ getRunMode,
3360
+ isAdvisorEnabled,
3361
+ getPermissionMode,
3362
+ resolveTargetProjects,
3363
+ buildScriptCommand,
3364
+ buildClaudeExecution,
3365
+ buildClaudeEnv,
3366
+ syncDefaultBranch,
3367
+ ensureEpicBranch,
3368
+ setShuttingDown,
3369
+ isShuttingDown,
3370
+ isRunning,
3371
+ isWorkerAtCapacity,
3372
+ renderTable,
3373
+ ensureRenderInterval,
3374
+ run,
3375
+ waitForAllProcesses,
3376
+ shutdown,
3377
+ generateWorktreeName,
3378
+ send,
3379
+ buildTokenLimitText,
3380
+ notifyTaskCompleted,
3381
+ notifyTaskFailed,
3382
+ notifyError,
3383
+ deleteLocalBranch,
3384
+ localBranchExists,
3385
+ getWorktreePath,
3386
+ createWorktreeFromBranch,
3387
+ removeWorktree,
3388
+ removeWorktreeByBranch,
3389
+ removeStaleWorktrees,
3390
+ defineWorker,
3391
+ isWorkerDefinition,
3392
+ createIssuePollingWorker,
3393
+ createPrPollingWorker,
3394
+ publishLastRunPr,
3395
+ createScheduledWorker
3396
+ };