claude-task-worker 0.104.0 → 0.106.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 (3) hide show
  1. package/README.md +2 -1
  2. package/dist/index.js +120 -15
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -105,7 +105,8 @@ claude-task-worker init --force # 強制上書き
105
105
 
106
106
  | ラベル | 用途 |
107
107
  |---|---|
108
- | `cc-triage-scope` | トリアージ対象マーク(Issue/PR |
108
+ | `cc-triage-scope` | トリアージ対象マーク(Issue/PR)=ワーカーのキュー合流口 |
109
+ | `cc-issue-request` | 人が Issue テンプレートから作成を依頼した印。起票者を assignee に自動設定するワークフローの発火条件(`cc-triage-scope` と役割を分けてあり、ワーカーの自動起票では付かない) |
109
110
  | `cc-issue-created` | `create-issue` 由来の Issue マーク |
110
111
  | `cc-update-issue` / `cc-answer-issue-questions` / `cc-exec-issue` | Issue の更新 / 確認事項回答 / 実行トリガー |
111
112
  | `cc-fix-onetime` / `cc-resolve-conflict` | PR の修正 / コンフリクト解消トリガー |
package/dist/index.js CHANGED
@@ -1470,6 +1470,61 @@ async function listPrsClosingIssue(issueNumber) {
1470
1470
  createdAt: node.createdAt ?? ""
1471
1471
  }));
1472
1472
  }
1473
+ async function fetchPrRef(owner, name, prNumber) {
1474
+ try {
1475
+ const parsed = JSON.parse(await execGh(["api", `repos/${owner}/${name}/pulls/${prNumber}`]));
1476
+ return {
1477
+ number: prNumber,
1478
+ state: parsed?.merged_at ? "MERGED" : String(parsed?.state ?? "").toUpperCase(),
1479
+ headRefName: parsed?.head?.ref ?? "",
1480
+ baseRefName: parsed?.base?.ref ?? "",
1481
+ createdAt: parsed?.created_at ?? "",
1482
+ body: typeof parsed?.body === "string" ? parsed.body : ""
1483
+ };
1484
+ } catch (err) {
1485
+ console.error(`[gh] failed to read PR #${prNumber}: ${err}`);
1486
+ return null;
1487
+ }
1488
+ }
1489
+ function bodyClosesIssue(body, issueNumber) {
1490
+ const pattern = new RegExp(`\\b(close[sd]?|fix(?:e[sd])?|resolve[sd]?)\\s*:?\\s*#${issueNumber}\\b(?!\\d)`, "i");
1491
+ return pattern.test(body);
1492
+ }
1493
+ async function listPrsCrossReferencingIssue(issueNumber) {
1494
+ const { owner, name } = await getRepoInfo();
1495
+ const output = await execGh(["api", `repos/${owner}/${name}/issues/${issueNumber}/timeline`, "--paginate"]);
1496
+ const events = JSON.parse(output);
1497
+ const numbers = [
1498
+ ...new Set(
1499
+ events.filter(
1500
+ (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"
1501
+ ).map((e) => e.source?.issue?.number)
1502
+ )
1503
+ ];
1504
+ const refs = await Promise.all(numbers.map((number) => fetchPrRef(owner, name, number)));
1505
+ return refs.filter((ref) => ref !== null && bodyClosesIssue(ref.body, issueNumber)).map(({ body: _body, ...ref }) => ref);
1506
+ }
1507
+ var ADD_CLOSE_ISSUE_REFERENCES = `mutation($issueId: ID!, $prId: ID!) {
1508
+ addCloseIssueReferences(input: { issueId: $issueId, pullRequestIds: [$prId] }) { issue { number } }
1509
+ }`;
1510
+ async function linkClosingPr(issueNumber, prNumber) {
1511
+ const { owner, name } = await getRepoInfo();
1512
+ const issueId = JSON.parse(await execGh(["api", `repos/${owner}/${name}/issues/${issueNumber}`]))?.node_id;
1513
+ const prId = JSON.parse(await execGh(["api", `repos/${owner}/${name}/pulls/${prNumber}`]))?.node_id;
1514
+ if (typeof issueId !== "string" || typeof prId !== "string") {
1515
+ throw new Error(`node_id not found (issue #${issueNumber} / PR #${prNumber})`);
1516
+ }
1517
+ await execGh([
1518
+ "api",
1519
+ "graphql",
1520
+ "-f",
1521
+ `query=${ADD_CLOSE_ISSUE_REFERENCES}`,
1522
+ "-F",
1523
+ `issueId=${issueId}`,
1524
+ "-F",
1525
+ `prId=${prId}`
1526
+ ]);
1527
+ }
1473
1528
  async function getIssueSubIssuesSummary(issueNumber) {
1474
1529
  const output = await execGh(["issue", "view", String(issueNumber), "--json", "subIssuesSummary"]);
1475
1530
  const parsed = JSON.parse(output);
@@ -4007,19 +4062,31 @@ async function verifyPrCreated(issueNumber, worktreeId, output, ctx) {
4007
4062
  console.log(`[exec-issue] #${issueNumber}: issue closed by skill (no-change path), skip cc-pr-created`);
4008
4063
  return;
4009
4064
  }
4065
+ const ownership = {
4066
+ cloud: ctx.cloud,
4067
+ expectedHeadRefName: worktreeId,
4068
+ baseBranch: ctx.baseBranch,
4069
+ startedAt: ctx.startedAt,
4070
+ now: Date.now()
4071
+ };
4010
4072
  let prNumber = null;
4011
4073
  if (!ctx.cloud) {
4012
4074
  prNumber = await findPrNumberByHeadRef(worktreeId, "all");
4013
4075
  }
4014
4076
  if (prNumber === null) {
4015
- const candidates = await listPrsClosingIssue(issueNumber);
4016
- prNumber = selectOwnedClosingPr(candidates, {
4017
- cloud: ctx.cloud,
4018
- expectedHeadRefName: worktreeId,
4019
- baseBranch: ctx.baseBranch,
4020
- startedAt: ctx.startedAt,
4021
- now: Date.now()
4077
+ prNumber = selectOwnedClosingPr(await listPrsClosingIssue(issueNumber), ownership);
4078
+ }
4079
+ if (prNumber === null) {
4080
+ const candidates = await listPrsCrossReferencingIssue(issueNumber).catch((err) => {
4081
+ console.error(`[exec-issue] listPrsCrossReferencingIssue failed for #${issueNumber}: ${err}`);
4082
+ return [];
4022
4083
  });
4084
+ prNumber = selectOwnedClosingPr(candidates, ownership);
4085
+ if (prNumber !== null) {
4086
+ await linkClosingPr(issueNumber, prNumber).catch(
4087
+ (err) => console.error(`[exec-issue] linkClosingPr failed for #${issueNumber} -> #${prNumber}: ${err}`)
4088
+ );
4089
+ }
4023
4090
  }
4024
4091
  if (prNumber !== null) {
4025
4092
  await addLabel("issue", issueNumber, "cc-pr-created");
@@ -4794,7 +4861,7 @@ var updateDesignMdWorker = createScheduledWorker({
4794
4861
  init_table();
4795
4862
 
4796
4863
  // src/commands/init.ts
4797
- import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2, access } from "node:fs/promises";
4864
+ import { mkdir as mkdir2, readFile as readFile2, rm as rm2, writeFile as writeFile2, access } from "node:fs/promises";
4798
4865
  import { basename as basename3 } from "node:path";
4799
4866
 
4800
4867
  // src/commands/codegraph.ts
@@ -4917,14 +4984,20 @@ var LABELS = [
4917
4984
  // vivid bronze (H46 S100 L29 / L*50 C*56)
4918
4985
  { name: "cc-ui-design-ready", color: "0c73e9" },
4919
4986
  // vivid azure (H212 S90 L48 / L*50 C*69)
4920
- { name: CLOUD_DONE_LABEL, color: "33cfff" }
4987
+ { name: CLOUD_DONE_LABEL, color: "33cfff" },
4921
4988
  // vivid sky blue (H194 S100 L60 / L*78 C*42)
4989
+ // 追加時点で有彩色ラベルが色相環をほぼ埋めており、上記6点を全て満たす色は残っていない。
4990
+ // 最寄り色相との差は27°が上限(H185 が最大)で、その相手は cc-cloud-done(クラウド実行中だけ
4991
+ // 現れる短命なマーカー・L*78 と明度が倍近く違う)=見分けたい相手ではない側に倒してある。
4992
+ { name: "cc-issue-request", color: "03656d" }
4993
+ // vivid deep teal (H185 S95 L22 / L*39 C*24)
4922
4994
  ];
4923
4995
  var ISSUE_TEMPLATE = `name: "[claude-task-worker] Issue\u4F5C\u6210\u4F9D\u983C"
4924
4996
  description: claude-task-worker\u3067GitHub Issue\u3092\u4F5C\u6210\u3059\u308B
4925
4997
  title: "[claude-task-worker] Issue\u4F5C\u6210\u4F9D\u983C"
4926
4998
  labels:
4927
4999
  - cc-triage-scope
5000
+ - cc-issue-request
4928
5001
  body:
4929
5002
  - type: textarea
4930
5003
  id: request
@@ -4934,7 +5007,7 @@ body:
4934
5007
  validations:
4935
5008
  required: true
4936
5009
  `;
4937
- var ASSIGN_CREATOR_WORKFLOW = `name: Assign creator on cc-triage-scope
5010
+ var ASSIGN_CREATOR_WORKFLOW = `name: Assign creator on issue request
4938
5011
 
4939
5012
  on:
4940
5013
  issues:
@@ -4942,7 +5015,7 @@ on:
4942
5015
 
4943
5016
  jobs:
4944
5017
  assign:
4945
- if: contains(github.event.issue.labels.*.name, 'cc-triage-scope')
5018
+ if: contains(github.event.issue.labels.*.name, 'cc-issue-request')
4946
5019
  runs-on: ubuntu-latest
4947
5020
  permissions:
4948
5021
  issues: write
@@ -5000,6 +5073,25 @@ async function ensureLocalConfigGitIgnore() {
5000
5073
  await writeFile2(path2, next, "utf-8");
5001
5074
  console.log(`[init] Added ${entry} to ${path2}`);
5002
5075
  }
5076
+ var LEGACY_PATHS = [
5077
+ ".github/ISSUE_TEMPLATE/cc-triage-scope.yml",
5078
+ ".github/workflows/assign-creator-on-cc-triage-scope.yml"
5079
+ ];
5080
+ function isContentMismatch(result, expected, actual) {
5081
+ return result === "skipped" && actual !== expected;
5082
+ }
5083
+ async function removeLegacyIssueRequestFiles() {
5084
+ for (const path2 of LEGACY_PATHS) {
5085
+ try {
5086
+ await rm2(path2);
5087
+ console.log(`[init] Removed legacy file: ${path2}`);
5088
+ } catch (err) {
5089
+ if (err.code !== "ENOENT") {
5090
+ throw err;
5091
+ }
5092
+ }
5093
+ }
5094
+ }
5003
5095
  async function init(options = {}) {
5004
5096
  const force = options.force ?? false;
5005
5097
  console.log(`[init] Creating labels...${force ? " (force mode)" : ""}`);
@@ -5013,12 +5105,25 @@ async function init(options = {}) {
5013
5105
  }
5014
5106
  console.log("[init] Creating issue template...");
5015
5107
  await mkdir2(".github/ISSUE_TEMPLATE", { recursive: true });
5016
- const templatePath = ".github/ISSUE_TEMPLATE/cc-triage-scope.yml";
5017
- logWriteResult(await writeFileWithMode(templatePath, ISSUE_TEMPLATE, force), templatePath);
5108
+ const templatePath = ".github/ISSUE_TEMPLATE/cc-issue-request.yml";
5109
+ const templateResult = await writeFileWithMode(templatePath, ISSUE_TEMPLATE, force);
5110
+ logWriteResult(templateResult, templatePath);
5018
5111
  console.log("[init] Creating GitHub Actions workflow...");
5019
5112
  await mkdir2(".github/workflows", { recursive: true });
5020
- const workflowPath = ".github/workflows/assign-creator-on-cc-triage-scope.yml";
5021
- logWriteResult(await writeFileWithMode(workflowPath, ASSIGN_CREATOR_WORKFLOW, force), workflowPath);
5113
+ const workflowPath = ".github/workflows/assign-creator-on-issue-request.yml";
5114
+ const workflowResult = await writeFileWithMode(workflowPath, ASSIGN_CREATOR_WORKFLOW, force);
5115
+ logWriteResult(workflowResult, workflowPath);
5116
+ if (isContentMismatch(templateResult, ISSUE_TEMPLATE, await readFile2(templatePath, "utf-8"))) {
5117
+ throw new Error(
5118
+ `[init] ${templatePath} \u306F\u65E2\u5B58\u306E\u5185\u5BB9\u304C\u6700\u65B0\u306E\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u3068\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002--force \u3067\u518D\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u65E7\u30D5\u30A1\u30A4\u30EB\u306F\u524A\u9664\u3057\u3066\u3044\u307E\u305B\u3093\u3002`
5119
+ );
5120
+ }
5121
+ if (isContentMismatch(workflowResult, ASSIGN_CREATOR_WORKFLOW, await readFile2(workflowPath, "utf-8"))) {
5122
+ throw new Error(
5123
+ `[init] ${workflowPath} \u306F\u65E2\u5B58\u306E\u5185\u5BB9\u304C\u6700\u65B0\u306E\u30EF\u30FC\u30AF\u30D5\u30ED\u30FC\u3068\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002--force \u3067\u518D\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u65E7\u30D5\u30A1\u30A4\u30EB\u306F\u524A\u9664\u3057\u3066\u3044\u307E\u305B\u3093\u3002`
5124
+ );
5125
+ }
5126
+ await removeLegacyIssueRequestFiles();
5022
5127
  console.log("[init] Creating config file...");
5023
5128
  await createConfig(force);
5024
5129
  await ensureLocalConfigGitIgnore();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-task-worker",
3
- "version": "0.104.0",
3
+ "version": "0.106.0",
4
4
  "description": "CLI tool that polls GitHub Issues/PRs and delegates work to Claude CLI",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",