claude-task-worker 0.140.0 → 0.142.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -62,6 +62,7 @@ CLI が GitHub ラベルを検知してタスクを起動し、プラグイン
62
62
  | [CodeGraph](https://www.npmjs.com/package/@colbymchenry/codegraph) | コード探索用インデックス(任意。未導入ならテキスト検索に落ちる) |
63
63
  | [Playwright](https://playwright.dev/) のブラウザ(Google Chrome) | Playwright MCP でのブラウザ確認(起動済みの Chrome に CDP `http://localhost:9222` で接続する。Chrome を `--remote-debugging-port=9222 --user-data-dir=<任意のディレクトリ>` 付きで起動しておく)、`.pen` のスナップショット描画 |
64
64
  | [DESIGN.md CLI](https://github.com/google-labs-code/design.md) | `DESIGN.md` の lint。`update-design-md` 使用時のみ(未導入でも動く) |
65
+ | [OpenCodeReview](https://github.com/alibaba/open-code-review)(CLI `ocr` と Claude Code プラグイン) | `triage-pr` の PR 差分レビュー(`/open-code-review:delegate-review`。未導入でも動く) |
65
66
  | [herdr](https://herdr.dev) | `--project` / `mode: "herdr"` 使用時のみ(`--cloud` は herdr に依存しない) |
66
67
  | [GitHub MCP](https://github.com/github/github-mcp-server) | GitHub アクセスの高速化・クラウド実行時のプロキシ制限回避(任意。Claude 側のコネクタで有効化) |
67
68
 
@@ -73,7 +74,7 @@ CLI 本体に npm の実行時依存はない(Node.js 標準モジュールの
73
74
  npx claude-task-worker install
74
75
  ```
75
76
 
76
- マーケットプレイス追加・プラグイン導入・CLI 本体のグローバルインストール・各種 CLI(CodeGraph / DESIGN.md / Pen)と Playwright ブラウザの取得を一括で行う。いずれかが失敗しても処理は継続する。インストール後、Claude Code のセッションを再起動するとプラグインが有効になる。
77
+ マーケットプレイス追加・プラグイン導入・CLI 本体のグローバルインストール・各種 CLI(CodeGraph / DESIGN.md / OpenCodeReview)・OpenCodeReview プラグインの導入と Playwright ブラウザの取得を一括で行う。いずれかが失敗しても処理は継続する。インストール後、Claude Code のセッションを再起動するとプラグインが有効になる。
77
78
 
78
79
  個別にやる場合:
79
80
 
@@ -126,7 +127,7 @@ claude-task-worker apply-labels # ラベルだけを作成(冪等)
126
127
  ## コマンド
127
128
 
128
129
  ```bash
129
- claude-task-worker <command> [--epic <issue-number>]... [--label <label>]... [--project <name>]... [--cloud] [--debug]
130
+ claude-task-worker <command> [--epic <issue-number>]... [--label <label>]... [--exclude-label <label>]... [--project <name>]... [--cloud] [--debug]
130
131
  ```
131
132
 
132
133
  | コマンド | 内容 |
@@ -154,12 +155,22 @@ claude-task-worker all --epic 100 --epic 200
154
155
 
155
156
  ### `--label <label>`
156
157
 
157
- トリガーラベルに加えて指定ラベルが付いた Issue のみに絞る。複数指定は AND。`--epic` と併用可能。
158
+ トリガーラベルに加えて指定ラベルが付いた Issue・PR のみに絞る(Issue 系・PR 系ワーカーの両方に効く)。`exec-issue` / `epic-issue` / `create-ui-design` が Issue から作った PR にも同じラベルを付けるので、後続の PR 系ワーカーが拾える。複数指定は AND。`--epic` と併用可能。
158
159
 
159
160
  ```bash
160
161
  claude-task-worker all --label priority-high --label needs-design
161
162
  ```
162
163
 
164
+ ### `--exclude-label <label>`
165
+
166
+ 指定ラベルが付いた Issue・PR を対象から外す(Issue 系・PR 系ワーカーの両方に効く。定期ワーカーは対象を持たないため無関係)。複数指定は AND で、どれか1つでも付いていれば外れる。
167
+
168
+ ```bash
169
+ claude-task-worker all --exclude-label wip --exclude-label on-hold
170
+ ```
171
+
172
+ `--label` / `--exclude-label` は `config.json` の `projects` にプロジェクトごとの既定を書ける(下記)。`--project` 経由の起動でだけ CLI の指定に足し込まれる。
173
+
163
174
  ### `--project <name>`
164
175
 
165
176
  指定したプロジェクト(またはグループ、`all`)へ [herdr](https://herdr.dev) 経由でコマンドをディスパッチする。CLI はワーカーを直接実行せず、プロジェクトごとに独立した herdr ワークスペースを作ってそこでコマンドを実行し、稼働状況をステータステーブルに表示する。SIGTERM/SIGINT で全セッションを一括停止する。
@@ -272,7 +283,7 @@ node plugin/scripts/pen.mjs render design.pen --out out/ # Chrome で描画し
272
283
  "permission": "bypassPermissions",
273
284
  "projects": {
274
285
  "app-a": "/Users/me/repos/app-a",
275
- "app-b": "/Users/me/repos/app-b"
286
+ "app-b": { "path": "/Users/me/repos/app-b", "label": ["priority-high"], "excludeLabel": ["wip"] }
276
287
  },
277
288
  "projectGroups": {
278
289
  "frontend": ["app-a", "app-b"]
@@ -282,13 +293,13 @@ node plugin/scripts/pen.mjs render design.pen --out out/ # Chrome で描画し
282
293
 
283
294
  | キー | 既定 | 説明 |
284
295
  |---|---|---|
285
- | `projects` | - | プロジェクト名 → 絶対パス |
296
+ | `projects` | - | プロジェクト名 → 絶対パス、または `{ path, label, excludeLabel }`。`label` / `excludeLabel` は `--label` / `--exclude-label` と同じ意味で、`--project` でそのプロジェクトを指定して起動したワーカーにだけ足し込まれる(`--project` なしの起動には適用しない)。`path` の重複は可 |
286
297
  | `projectGroups` | `{}` | グループ名 → プロジェクト名配列 |
287
298
  | `mode` | `"default"` | `"default"`: `claude -p` の子プロセスとして実行 / `"herdr"`: herdr のタブ内で TUI 起動し、実行中の様子を覗ける(`--project` はこちらが必要) |
288
299
  | `advisor` | `false` | `true` で `--advisor <model>` を渡す。モデルは `claude-task-worker.json` の `advisorModel` |
289
300
  | `permission` | `"bypassPermissions"` | Claude CLI の[権限モード](https://code.claude.com/docs/ja/permission-modes)(`bypassPermissions` / `dontAsk` / `auto` / `acceptEdits` / `manual` / `plan`) |
290
301
 
291
- いずれもトップレベル一括で、プロジェクト単位・ワーカー単位の指定はできない。`permission` はワーカーに承認するユーザーがいないため、`bypassPermissions` / `dontAsk` 以外ではタスクが承認待ちで止まりうる。
302
+ `projects` 以外はいずれもトップレベル一括で、プロジェクト単位・ワーカー単位の指定はできない。`permission` はワーカーに承認するユーザーがいないため、`bypassPermissions` / `dontAsk` 以外ではタスクが承認待ちで止まりうる。
292
303
 
293
304
  > ℹ️ `mode: "herdr"` の完了通知音はワーカー側から止められない。無音にするには `~/.config/herdr/config.toml` に `[ui.sound] enabled = false` を書いて `herdr server reload-config` する(herdr サーバー全体に効く)。
294
305
 
@@ -423,7 +434,7 @@ claude-task-worker all
423
434
 
424
435
  ## プロセス管理
425
436
 
426
- 実行中のタスクはリアルタイムのステータステーブルで表示される(タスクID・タイトル・ステータス・開始時刻・経過時間)。SIGTERM/SIGINT で graceful shutdown し、もう一度送ると強制終了してラベル・worktree の後片付けを試みる。前回の異常終了で残った worktree はワーカー起動時に自動回収される。
437
+ 実行中のタスクはリアルタイムのステータステーブルで表示される(タスクID・タイトル・ステータス・開始時刻・経過時間)。SIGTERM/SIGINT で graceful shutdown し、もう一度送ると強制終了してラベル・worktree の後片付けを試みる。前回の異常終了で残った worktree は自動回収しない(同じ path のプロジェクトを並走させたとき他プロセスの作業中 worktree を消さないため)。不要なら `.claude/worktrees/` 配下を `git worktree remove` で手動削除する。
427
438
 
428
439
  ワーカーは応答するユーザーがいない状態でスキルを起動するため、処理が未完のままラベルだけ進む事故を防ぐガードを持つ(バックグラウンド実行と対話系ツールの無効化、自律実行原則のシステムプロンプト注入、PR 実在の完了検証、空出力セッションの失敗扱い、`Stop` フックによる残留プロセスの停止)。
429
440
 
@@ -16,7 +16,7 @@ import {
16
16
  pushLogLine,
17
17
  selectRecentTasks,
18
18
  writeScreen
19
- } from "./chunk-HFU27QHT.js";
19
+ } from "./chunk-BA2CE3L6.js";
20
20
 
21
21
  // src/gh.ts
22
22
  import { createRequire } from "node:module";
@@ -365,7 +365,7 @@ function excludePrsByLabels(prs, excludeLabels) {
365
365
  return prs.filter((pr) => !pr.labels.some((l) => excludeLabels.includes(l.name)));
366
366
  }
367
367
  var PR_LIST_FETCH_LIMIT = 50;
368
- async function listPullRequestsWithChecks(assignee, label, excludeLabels = [], limit = 5) {
368
+ async function listPullRequestsWithChecks(assignee, label, excludeLabels = [], limit = 5, searchExcludeLabels = []) {
369
369
  const args = [
370
370
  "pr",
371
371
  "list",
@@ -374,18 +374,22 @@ async function listPullRequestsWithChecks(assignee, label, excludeLabels = [], l
374
374
  "--json",
375
375
  "number,title,labels,headRefName",
376
376
  "--search",
377
- "sort:created-asc",
377
+ ["sort:created-asc", ...searchExcludeLabels.map((l) => `-label:"${l}"`)].join(" "),
378
378
  "--limit",
379
379
  String(PR_LIST_FETCH_LIMIT)
380
380
  ];
381
381
  if (assignee) {
382
382
  args.push("--assignee", assignee);
383
383
  }
384
- if (label) {
385
- args.push("--label", label);
384
+ for (const l of [label ?? []].flat()) {
385
+ if (!l) continue;
386
+ args.push("--label", l);
386
387
  }
387
388
  const output = await execGh(args);
388
- const prs = excludePrsByLabels(JSON.parse(output), excludeLabels).slice(0, limit);
389
+ const prs = excludePrsByLabels(JSON.parse(output), [...excludeLabels, ...searchExcludeLabels]).slice(
390
+ 0,
391
+ limit
392
+ );
389
393
  const withChecks = await Promise.all(prs.map(async (pr) => ({ ...pr, checks: await fetchPRChecks(pr.number) })));
390
394
  return withChecks.filter((pr) => isCICompleted(pr.checks));
391
395
  }
@@ -1552,7 +1556,7 @@ function resolveProjectName(cwd = process.cwd()) {
1552
1556
  return findProjectNameByPath(cwd) ?? basename(cwd);
1553
1557
  }
1554
1558
  async function runViaHerdr(args, prompt, id, onComplete, cwd, env) {
1555
- const { startHerdrTask, stopHerdrTask, taskTabLabel, waitForHerdrTask } = await import("./herdr-runner-TNX2W4G6.js");
1559
+ const { startHerdrTask, stopHerdrTask, taskTabLabel, waitForHerdrTask } = await import("./herdr-runner-S5X5NTFR.js");
1556
1560
  const { getCurrentWorkspaceId } = await import("./herdr-NDLUM6JF.js");
1557
1561
  const label = taskTabLabel(resolveProjectName(), id);
1558
1562
  let task;
@@ -1678,7 +1682,7 @@ async function flagOrphanedCloudSession(target, reason, cloudSessionId) {
1678
1682
  });
1679
1683
  }
1680
1684
  async function createCloudSession(args, initialPrompt, cwd, env) {
1681
- const { extractCloudSessionId, normalizePtyOutput } = await import("./herdr-runner-TNX2W4G6.js");
1685
+ const { extractCloudSessionId, normalizePtyOutput } = await import("./herdr-runner-S5X5NTFR.js");
1682
1686
  const spec = buildScriptCommand("claude", buildCloudCreateArgs(args, initialPrompt));
1683
1687
  const child = spawn(spec.command, spec.args, {
1684
1688
  stdio: ["ignore", "pipe", "pipe"],
@@ -2134,9 +2138,6 @@ function generateWorktreeName() {
2134
2138
  const suffix = String(randomInt(1e4)).padStart(4, "0");
2135
2139
  return `${pick(ADJECTIVES)}-${pick(NOUNS)}-${suffix}`;
2136
2140
  }
2137
- function isGeneratedWorktreeName(name) {
2138
- return /^[a-z]+-[a-z]+-\d{4}$/.test(name);
2139
- }
2140
2141
 
2141
2142
  // src/slack.ts
2142
2143
  import { exec } from "node:child_process";
@@ -2409,7 +2410,7 @@ async function notifyError(workerName, repoName, error) {
2409
2410
  // src/worktree.ts
2410
2411
  import { createRequire as createRequire2 } from "node:module";
2411
2412
  import { promisify as promisify3 } from "node:util";
2412
- import { readdir, rm, stat } from "node:fs/promises";
2413
+ import { rm, stat } from "node:fs/promises";
2413
2414
  import { basename as basename2, resolve as resolve2, sep } from "node:path";
2414
2415
  var childProcess2 = createRequire2(import.meta.url)("node:child_process");
2415
2416
  var execFileAsync2 = (command, args) => promisify3(childProcess2.execFile)(command, args);
@@ -2549,29 +2550,6 @@ async function removeWorktreeByBranch(branchName) {
2549
2550
  console.error(`[worktree] Failed to remove worktree for branch ${branchName}:`, error);
2550
2551
  }
2551
2552
  }
2552
- async function removeStaleWorktrees() {
2553
- const worktreeIds = /* @__PURE__ */ new Set();
2554
- try {
2555
- for (const name of await readdir(WORKTREES_DIR)) {
2556
- if (isGeneratedWorktreeName(name)) worktreeIds.add(name);
2557
- }
2558
- } catch {
2559
- }
2560
- try {
2561
- for (const entry of await listWorktreeEntries()) {
2562
- if (!isManagedWorktreePath(entry.path)) continue;
2563
- const worktreeId = basename2(entry.path);
2564
- if (isGeneratedWorktreeName(worktreeId)) worktreeIds.add(worktreeId);
2565
- }
2566
- } catch (error) {
2567
- console.error(`[worktree] Failed to list worktrees:`, error);
2568
- }
2569
- for (const worktreeId of worktreeIds) {
2570
- if (isWorktreeInUse(worktreeId)) continue;
2571
- console.log(`[worktree] Removing stale worktree: ${worktreeId}`);
2572
- await removeWorktree(worktreeId);
2573
- }
2574
- }
2575
2553
 
2576
2554
  // src/workers/worker-definition.ts
2577
2555
  var WORKER_DEFINITION = /* @__PURE__ */ Symbol.for("claude-task-worker.worker-definition");
@@ -2587,6 +2565,13 @@ function isWorkerDefinition(value) {
2587
2565
 
2588
2566
  // src/workers/issue-worker.ts
2589
2567
  var STICKY_LABELS = ["cc-triage-scope", "cc-issue-created"];
2568
+ async function carryLabelFiltersToPr(workerName, prNumber, labelFilters = []) {
2569
+ for (const label of labelFilters) {
2570
+ await addLabel("pr", prNumber, label).catch(
2571
+ (err) => console.error(`[${workerName}] addLabel ${label} failed for PR #${prNumber}: ${err}`)
2572
+ );
2573
+ }
2574
+ }
2590
2575
  var consumableTriggerLabels = (triggerLabels) => triggerLabels.filter((label) => !STICKY_LABELS.includes(label));
2591
2576
  var PREFLIGHT_SEARCH_LIMIT = 5;
2592
2577
  function createIssuePollingWorker(config) {
@@ -2607,7 +2592,12 @@ function createIssuePollingWorker(config) {
2607
2592
  if (isShuttingDown()) return;
2608
2593
  if (cooldownMs > 0 && lastCompletionAt > 0 && Date.now() - lastCompletionAt < cooldownMs) return;
2609
2594
  try {
2610
- const excludeLabels = ["cc-in-progress", "cc-need-human-check", ...config.excludeLabels ?? []];
2595
+ const excludeLabels = [
2596
+ "cc-in-progress",
2597
+ "cc-need-human-check",
2598
+ ...config.excludeLabels ?? [],
2599
+ ...opts.excludeLabelFilters ?? []
2600
+ ];
2611
2601
  const epicFilter = config.epicFilterTarget !== "self" && epicFilters && epicFilters.length > 0 ? { owner, repo: name, numbers: epicFilters } : void 0;
2612
2602
  const labels = labelFilters && labelFilters.length > 0 ? [...config.triggerLabels, ...labelFilters] : config.triggerLabels;
2613
2603
  const { maxConcurrentTasks } = getWorkerConfig(config.name);
@@ -2701,7 +2691,8 @@ function createIssuePollingWorker(config) {
2701
2691
  const verified = await config.onCompleted?.(issue.number, worktreeId, output, {
2702
2692
  cloud,
2703
2693
  baseBranch,
2704
- startedAt
2694
+ startedAt,
2695
+ labelFilters: labelFilters ?? []
2705
2696
  }) ?? true;
2706
2697
  if (verified === false) {
2707
2698
  await notifyTaskFailed(
@@ -2794,7 +2785,7 @@ function createIssuePollingWorker(config) {
2794
2785
  var LABEL_IN_PROGRESS = "cc-in-progress";
2795
2786
  var LABEL_TRIAGE_SCOPE = "cc-triage-scope";
2796
2787
  function createPrPollingWorker(config) {
2797
- const start = async () => {
2788
+ const start = async (opts = {}) => {
2798
2789
  const { owner, name, defaultBranch } = await getRepoInfo();
2799
2790
  const user = await getCurrentUser();
2800
2791
  const { pollingIntervalSeconds, cooldownSeconds } = getWorkerConfig(config.name);
@@ -2809,7 +2800,13 @@ function createPrPollingWorker(config) {
2809
2800
  if (cooldownMs > 0 && lastCompletionAt > 0 && Date.now() - lastCompletionAt < cooldownMs) return;
2810
2801
  try {
2811
2802
  const excludeLabels = [LABEL_IN_PROGRESS, ...config.excludeLabels ?? []];
2812
- const candidates = await listPullRequestsWithChecks(user, config.triggerLabel, excludeLabels);
2803
+ const candidates = await listPullRequestsWithChecks(
2804
+ user,
2805
+ [config.triggerLabel, ...opts.labelFilters ?? []],
2806
+ excludeLabels,
2807
+ void 0,
2808
+ opts.excludeLabelFilters ?? []
2809
+ );
2813
2810
  for (const pr of candidates) {
2814
2811
  if (isRunning(pr.number)) continue;
2815
2812
  if (isWorkerAtCapacity(config.name)) break;
@@ -3211,9 +3208,9 @@ export {
3211
3208
  createWorktreeFromBranch,
3212
3209
  removeWorktree,
3213
3210
  removeWorktreeByBranch,
3214
- removeStaleWorktrees,
3215
3211
  defineWorker,
3216
3212
  isWorkerDefinition,
3213
+ carryLabelFiltersToPr,
3217
3214
  createIssuePollingWorker,
3218
3215
  createPrPollingWorker,
3219
3216
  publishLastRunPr,
@@ -203,10 +203,13 @@ function loadUserConfig() {
203
203
  );
204
204
  }
205
205
  const projects = {};
206
- for (const [name, value] of Object.entries(rawProjects)) {
206
+ const projectLabelFilters = {};
207
+ for (const [name, entry] of Object.entries(rawProjects)) {
207
208
  if (name === "__proto__") {
208
209
  throw new UserConfigError(`"__proto__" cannot be used as a key in "projects": ${path}`);
209
210
  }
211
+ const isObjectEntry = typeof entry === "object" && entry !== null && !Array.isArray(entry);
212
+ const value = isObjectEntry ? entry["path"] : entry;
210
213
  if (typeof value !== "string" || !isAbsolute(value)) {
211
214
  console.warn(`[config] invalid projects.${name}: expected an absolute path, skipping`);
212
215
  continue;
@@ -216,6 +219,18 @@ function loadUserConfig() {
216
219
  continue;
217
220
  }
218
221
  projects[name] = value;
222
+ if (isObjectEntry) {
223
+ const obj = entry;
224
+ for (const key of Object.keys(obj)) {
225
+ if (!PROJECT_ENTRY_KEYS.includes(key)) {
226
+ console.warn(`[config] unknown key "${key}" in projects.${name}, ignoring`);
227
+ }
228
+ }
229
+ projectLabelFilters[name] = {
230
+ label: parseLabelList(obj["label"], `projects.${name}.label`),
231
+ excludeLabel: parseLabelList(obj["excludeLabel"], `projects.${name}.excludeLabel`)
232
+ };
233
+ }
219
234
  }
220
235
  const projectGroups = {};
221
236
  for (const [groupName, value] of Object.entries(rawProjectGroups)) {
@@ -236,7 +251,16 @@ function loadUserConfig() {
236
251
  }
237
252
  projectGroups[groupName] = members;
238
253
  }
239
- return { mode, advisor, permission, projects, projectGroups };
254
+ return { mode, advisor, permission, projects, projectGroups, projectLabelFilters };
255
+ }
256
+ var PROJECT_ENTRY_KEYS = ["path", "label", "excludeLabel"];
257
+ function parseLabelList(value, key) {
258
+ if (value === void 0) return [];
259
+ if (Array.isArray(value) && value.every((v) => typeof v === "string" && v.length > 0)) return value;
260
+ console.warn(
261
+ `[config] invalid ${key}: expected an array of non-empty strings, got ${JSON.stringify(value)}, ignoring`
262
+ );
263
+ return [];
240
264
  }
241
265
  var cachedRunMode;
242
266
  function getRunMode() {
@@ -292,6 +316,18 @@ function findProjectNameByPath(path) {
292
316
  }
293
317
  return void 0;
294
318
  }
319
+ function findProjectLabelFilters(projectName = process.env.CTW_PROJECT_NAME) {
320
+ const empty = { label: [], excludeLabel: [] };
321
+ if (!projectName) return empty;
322
+ let config;
323
+ try {
324
+ config = loadUserConfig();
325
+ } catch (err) {
326
+ console.warn(`[config] failed to read config file, project label filters are not applied: ${err}`);
327
+ return empty;
328
+ }
329
+ return Object.prototype.hasOwnProperty.call(config.projectLabelFilters, projectName) ? config.projectLabelFilters[projectName] : empty;
330
+ }
295
331
  function resolveTargetProjects(requested, config) {
296
332
  const resolved = /* @__PURE__ */ new Map();
297
333
  for (const name of requested) {
@@ -558,6 +594,7 @@ export {
558
594
  isAdvisorEnabled,
559
595
  getPermissionMode,
560
596
  findProjectNameByPath,
597
+ findProjectLabelFilters,
561
598
  resolveTargetProjects,
562
599
  getDisplayWidth,
563
600
  truncateToWidth,
@@ -0,0 +1,16 @@
1
+ export declare const OCR_PACKAGE = "@alibaba-group/open-code-review";
2
+ export declare const OCR_MARKETPLACE_SOURCE = "alibaba/open-code-review";
3
+ export declare const OCR_MARKETPLACE_NAME = "open-code-review";
4
+ export declare const OCR_PLUGIN = "open-code-review@open-code-review";
5
+ /**
6
+ * OpenCodeReview(OCR)の CLI と Claude Code プラグインを導入/更新する。
7
+ *
8
+ * `triage-pr` スキルが `/open-code-review:delegate-review`(OCR はファイル選定とルール解決だけを行い、
9
+ * レビュー自体はホストの claude が行う)でPRをレビューするため、CLI(`ocr`)とプラグインの両方が要る。
10
+ * delegate モードは OCR 側で LLM を呼ばないので、`ocr config provider` 等の LLM 設定は不要。
11
+ *
12
+ * CLI は self-upgrade 機構を持たないため install / update とも `npm install -g <pkg>@latest`。
13
+ * プラグインは install ならマーケットプレイス追加 → インストール、update ならマーケットプレイス更新 →
14
+ * プラグイン更新(未導入で更新に失敗した場合はインストールへフォールバック)。
15
+ */
16
+ export declare function installOpenCodeReview(logPrefix: "install" | "update"): Promise<boolean>;
@@ -3,7 +3,7 @@ async function loadHerdr() {
3
3
  return await import("./herdr-NDLUM6JF.js");
4
4
  }
5
5
  async function loadTable() {
6
- return await import("./table-OOKBQVW5.js");
6
+ return await import("./table-EENSXIIT.js");
7
7
  }
8
8
  var { getDisplayWidth, truncateToWidth, padToWidth, buildLogTableLines, logLines, writeScreen } = await loadTable();
9
9
  var POLL_INTERVAL_MS = 7 * 1e3;
package/dist/gh.d.ts CHANGED
@@ -90,7 +90,7 @@ export declare function excludePrsByLabels<T extends {
90
90
  name: string;
91
91
  }[];
92
92
  }>(prs: T[], excludeLabels: string[]): T[];
93
- export declare function listPullRequestsWithChecks(assignee?: string, label?: string, excludeLabels?: string[], limit?: number): Promise<PullRequestWithChecks[]>;
93
+ export declare function listPullRequestsWithChecks(assignee?: string, label?: string | string[], excludeLabels?: string[], limit?: number, searchExcludeLabels?: string[]): Promise<PullRequestWithChecks[]>;
94
94
  export declare function addLabel(type: "issue" | "pr", number: number, label: string): Promise<void>;
95
95
  export declare function addAssignee(type: "issue" | "pr", number: number, login: string): Promise<void>;
96
96
  export declare function hasLabel(type: "issue" | "pr", number: number, label: string): Promise<boolean>;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  CONTROL_CHARS
3
- } from "./chunk-HFU27QHT.js";
3
+ } from "./chunk-BA2CE3L6.js";
4
4
 
5
5
  // src/transcript.ts
6
6
  import { existsSync, readFileSync, readdirSync } from "node:fs";
package/dist/index.js CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  bodyMentionsIssue,
14
14
  buildScriptCommand,
15
15
  buildTokenLimitText,
16
+ carryLabelFiltersToPr,
16
17
  checkCloudConfig,
17
18
  closeIssue,
18
19
  closePullRequest,
@@ -55,7 +56,6 @@ import {
55
56
  parseClosingIssueNumbers,
56
57
  partitionEnabledWorkers,
57
58
  remoteTrackingSha,
58
- removeStaleWorktrees,
59
59
  renderTable,
60
60
  resolveInheritConfigSource,
61
61
  resolveWorkerFilePath,
@@ -65,13 +65,14 @@ import {
65
65
  shutdown,
66
66
  waitForAllProcesses,
67
67
  writeLastRun
68
- } from "./chunk-D4BPQ3K3.js";
68
+ } from "./chunk-725IRSKF.js";
69
69
  import {
70
70
  UserConfigError,
71
71
  assertCloudCompatibleCommand,
72
72
  assertProjectCompatibleCommand,
73
73
  buildForwardedCommand,
74
74
  captureConsole,
75
+ findProjectLabelFilters,
75
76
  getInheritConfigPath,
76
77
  getRunMode,
77
78
  hasCloudFlag,
@@ -79,7 +80,7 @@ import {
79
80
  loadUserConfig,
80
81
  parseProjectFilters,
81
82
  resolveTargetProjects
82
- } from "./chunk-HFU27QHT.js";
83
+ } from "./chunk-BA2CE3L6.js";
83
84
  import {
84
85
  npmInstallGlobalLatest,
85
86
  runCommand
@@ -355,7 +356,8 @@ function prMissingComment(worktreeId, output, cloud) {
355
356
  "- \u624B\u52D5\u3067PR\u3092\u4F5C\u6210\u3057\u305F\u5834\u5408\u306A\u3069\u5BFE\u5FDC\u6E08\u307F\u306E\u5834\u5408: `cc-need-human-check` \u30E9\u30D9\u30EB\u3092\u5916\u3057\u3066\u304F\u3060\u3055\u3044"
356
357
  ].join("\n");
357
358
  }
358
- async function ensurePrTriageMetadata(prNumber) {
359
+ async function ensurePrTriageMetadata(prNumber, labelFilters) {
360
+ await carryLabelFiltersToPr("exec-issue", prNumber, labelFilters);
359
361
  await addLabel("pr", prNumber, "cc-triage-scope").catch(
360
362
  (err) => console.error(`[exec-issue] addLabel cc-triage-scope failed for PR #${prNumber}: ${err}`)
361
363
  );
@@ -398,7 +400,7 @@ async function verifyPrCreated(issueNumber, worktreeId, output, ctx) {
398
400
  }
399
401
  if (prNumber !== null) {
400
402
  await addLabel("issue", issueNumber, "cc-pr-created");
401
- await ensurePrTriageMetadata(prNumber);
403
+ await ensurePrTriageMetadata(prNumber, ctx.labelFilters);
402
404
  return;
403
405
  }
404
406
  console.error(
@@ -534,6 +536,7 @@ var createUiDesignWorker = defineWorker({
534
536
  }
535
537
  try {
536
538
  await addLabel("pr", prNumber, "cc-ui-design");
539
+ await carryLabelFiltersToPr("create-ui-design", prNumber, ctx.labelFilters);
537
540
  if (yolo) {
538
541
  await addLabel("pr", prNumber, "cc-triage-scope");
539
542
  } else {
@@ -584,7 +587,7 @@ var epicIssueWorker = createIssuePollingWorker({
584
587
  if (summary.completed !== summary.total) return "skip";
585
588
  return "proceed";
586
589
  },
587
- onCompleted: async (issueNumber) => {
590
+ onCompleted: async (issueNumber, _worktreeId, _output, ctx) => {
588
591
  const prNumber = await findOpenPrNumberByHeadRef(`cc-epic-${issueNumber}`);
589
592
  if (prNumber === null) {
590
593
  console.error(`[epic-issue] Epic PR for branch cc-epic-${issueNumber} not found; skip cc-pr-created`);
@@ -597,6 +600,7 @@ var epicIssueWorker = createIssuePollingWorker({
597
600
  await addLabel("issue", issueNumber, "cc-pr-created");
598
601
  await addLabel("pr", prNumber, "cc-epic-issue");
599
602
  await addLabel("pr", prNumber, "cc-triage-scope");
603
+ await carryLabelFiltersToPr("epic-issue", prNumber, ctx.labelFilters);
600
604
  await getCurrentUser().then((user) => addAssignee("pr", prNumber, user)).catch((err) => console.error(`[epic-issue] addAssignee failed for PR #${prNumber}: ${err}`));
601
605
  }
602
606
  });
@@ -1113,10 +1117,60 @@ async function installDesignMdCli(logPrefix) {
1113
1117
  }
1114
1118
  }
1115
1119
 
1116
- // src/commands/playwright.ts
1120
+ // src/commands/open-code-review.ts
1117
1121
  async function loadRunCommand3() {
1118
1122
  return await import("./run-command-O2TMRPZK.js");
1119
1123
  }
1124
+ var OCR_PACKAGE = "@alibaba-group/open-code-review";
1125
+ var OCR_MARKETPLACE_SOURCE = "alibaba/open-code-review";
1126
+ var OCR_MARKETPLACE_NAME = "open-code-review";
1127
+ var OCR_PLUGIN = "open-code-review@open-code-review";
1128
+ async function installOpenCodeReview(logPrefix) {
1129
+ const { npmInstallGlobalLatest: npmInstallGlobalLatest2, runCommand: runCommand2 } = await loadRunCommand3();
1130
+ let ok = true;
1131
+ console.log(`[${logPrefix}] Installing OpenCodeReview CLI (npm install -g ${OCR_PACKAGE}@latest)...`);
1132
+ try {
1133
+ await npmInstallGlobalLatest2(OCR_PACKAGE);
1134
+ console.log(`[${logPrefix}] OpenCodeReview CLI installed.`);
1135
+ } catch (err) {
1136
+ console.error(`[${logPrefix}] Failed to install OpenCodeReview CLI: ${err.message}`);
1137
+ ok = false;
1138
+ }
1139
+ const addAndInstall = async () => {
1140
+ try {
1141
+ await runCommand2("claude", ["plugin", "marketplace", "add", OCR_MARKETPLACE_SOURCE]);
1142
+ } catch (err) {
1143
+ console.error(
1144
+ `[${logPrefix}] Failed to add marketplace ${OCR_MARKETPLACE_SOURCE} (already added is expected and safe to ignore): ${err.message}`
1145
+ );
1146
+ }
1147
+ await runCommand2("claude", ["plugin", "install", OCR_PLUGIN]);
1148
+ };
1149
+ console.log(`[${logPrefix}] ${logPrefix === "install" ? "Installing" : "Updating"} plugin: ${OCR_PLUGIN}...`);
1150
+ try {
1151
+ if (logPrefix === "install") {
1152
+ await addAndInstall();
1153
+ } else {
1154
+ try {
1155
+ await runCommand2("claude", ["plugin", "marketplace", "update", OCR_MARKETPLACE_NAME]);
1156
+ await runCommand2("claude", ["plugin", "update", OCR_PLUGIN]);
1157
+ } catch (err) {
1158
+ console.error(`[${logPrefix}] Failed to update ${OCR_PLUGIN}, trying install: ${err.message}`);
1159
+ await addAndInstall();
1160
+ }
1161
+ }
1162
+ console.log(`[${logPrefix}] Plugin ${OCR_PLUGIN} ready.`);
1163
+ } catch (err) {
1164
+ console.error(`[${logPrefix}] Failed to set up plugin ${OCR_PLUGIN}: ${err.message}`);
1165
+ ok = false;
1166
+ }
1167
+ return ok;
1168
+ }
1169
+
1170
+ // src/commands/playwright.ts
1171
+ async function loadRunCommand4() {
1172
+ return await import("./run-command-O2TMRPZK.js");
1173
+ }
1120
1174
  var PLAYWRIGHT_CORE_PACKAGE = "playwright-core";
1121
1175
  var PLAYWRIGHT_MCP_PACKAGE = "@playwright/mcp";
1122
1176
  var INSTALL_BROWSER_ARGS = ["-y", `${PLAYWRIGHT_MCP_PACKAGE}@latest`, "install-browser", "chrome"];
@@ -1125,7 +1179,7 @@ function buildInstallDepsCommand(uid) {
1125
1179
  return uid === 0 ? { command: "npx", args: npxArgs } : { command: "sudo", args: ["npx", ...npxArgs] };
1126
1180
  }
1127
1181
  async function installPlaywrightBrowsers(logPrefix) {
1128
- const { runCommand: runCommand2 } = await loadRunCommand3();
1182
+ const { runCommand: runCommand2 } = await loadRunCommand4();
1129
1183
  console.log(`[${logPrefix}] Installing Playwright browsers (npx ${INSTALL_BROWSER_ARGS.join(" ")})...`);
1130
1184
  try {
1131
1185
  await runCommand2("npx", INSTALL_BROWSER_ARGS);
@@ -1154,7 +1208,7 @@ async function installPlaywrightBrowsers(logPrefix) {
1154
1208
  var PLUGIN_NAME = "claude-task-worker";
1155
1209
  var MARKETPLACE_NAME = "claude-task-worker";
1156
1210
  var MARKETPLACE_SOURCE = "getty104/claude-task-worker";
1157
- var GLOBAL_NPM_PACKAGES = [PLUGIN_NAME, CODEGRAPH_PACKAGE, DESIGN_MD_PACKAGE];
1211
+ var GLOBAL_NPM_PACKAGES = [PLUGIN_NAME, CODEGRAPH_PACKAGE, DESIGN_MD_PACKAGE, OCR_PACKAGE];
1158
1212
  async function printGlobalPackageVersions(logPrefix) {
1159
1213
  console.log(`[${logPrefix}] Installed versions:`);
1160
1214
  try {
@@ -1204,8 +1258,9 @@ async function install() {
1204
1258
  const codegraphOk = await installCodegraphCli("install");
1205
1259
  const designMdOk = await installDesignMdCli("install");
1206
1260
  const playwrightOk = await installPlaywrightBrowsers("install");
1261
+ const ocrOk = await installOpenCodeReview("install");
1207
1262
  await printGlobalPackageVersions("install");
1208
- if (!pluginOk || !cliOk || !codegraphOk || !designMdOk || !playwrightOk) {
1263
+ if (!pluginOk || !cliOk || !codegraphOk || !designMdOk || !playwrightOk || !ocrOk) {
1209
1264
  process.exitCode = 1;
1210
1265
  }
1211
1266
  console.log("[install] Done.");
@@ -1344,8 +1399,9 @@ async function update() {
1344
1399
  const codegraphOk = await upgradeCodegraphCli("update");
1345
1400
  const designMdOk = await installDesignMdCli("update");
1346
1401
  const playwrightOk = await installPlaywrightBrowsers("update");
1402
+ const ocrOk = await installOpenCodeReview("update");
1347
1403
  await printGlobalPackageVersions("update");
1348
- if (!marketplaceOk || !pluginOk || !cliOk || !codegraphOk || !designMdOk || !playwrightOk) {
1404
+ if (!marketplaceOk || !pluginOk || !cliOk || !codegraphOk || !designMdOk || !playwrightOk || !ocrOk) {
1349
1405
  process.exitCode = 1;
1350
1406
  }
1351
1407
  console.log("[update] Done.");
@@ -1536,7 +1592,7 @@ async function loadCustomWorkersOrExit() {
1536
1592
  return workers;
1537
1593
  }
1538
1594
  function printUsage() {
1539
- console.log(`Usage: claude-task-worker <command> [--project <name>] [--epic <issue-number>] [--label <label-name>]
1595
+ console.log(`Usage: claude-task-worker <command> [--project <name>] [--epic <issue-number>] [--label <label-name>] [--exclude-label <label-name>]
1540
1596
 
1541
1597
  Commands:
1542
1598
  init [--force] Create required GitHub labels and config file (use --force to overwrite existing files)
@@ -1560,7 +1616,9 @@ Options:
1560
1616
  --inherit-config <path> Use the given config file as the base. claude-task-worker.json and claude-task-worker.local.json in the current directory are layered on top (their keys win). Relative paths inside the file resolve from its own directory. With init, the config file is generated at this path.
1561
1617
  --debug Post each task's final report as a comment on the target Issue/PR (off by default; the report is only sent to Slack). Works in both local and --cloud runs.
1562
1618
  --epic <number> Limit issue-based workers to sub-issues of the specified epic issue. Repeatable: any matching parent (OR).
1563
- --label <name> Limit issue-based workers to issues that also carry the specified label. Repeatable: all must be present (AND).
1619
+ --label <name> Limit issue- and PR-based workers to issues/PRs that also carry the specified label. Repeatable: all must be present (AND).
1620
+ --exclude-label <name> Skip issues and PRs that carry the specified label. Repeatable: each is applied (AND), so any listed label excludes the item.
1621
+ Per-project defaults: write a project in config.json as { "path": "...", "label": [...], "excludeLabel": [...] }; they are added to the flags only when started via --project.
1564
1622
 
1565
1623
  Example:
1566
1624
  claude-task-worker init
@@ -1569,6 +1627,7 @@ Example:
1569
1627
  claude-task-worker all --epic 100 --epic 200
1570
1628
  claude-task-worker all --label priority-high
1571
1629
  claude-task-worker all --label priority-high --label needs-design
1630
+ claude-task-worker all --exclude-label wip
1572
1631
  claude-task-worker yolo --epic 100 --epic 200 --label priority-high
1573
1632
  claude-task-worker all --project all
1574
1633
  claude-task-worker all --project web
@@ -1637,8 +1696,18 @@ function parseEpicFilters() {
1637
1696
  return num;
1638
1697
  });
1639
1698
  }
1640
- function parseLabelFilters() {
1641
- return collectFlagValues("--label");
1699
+ function parseLabelOptions() {
1700
+ const project = findProjectLabelFilters();
1701
+ const uniq = (values) => [...new Set(values)];
1702
+ const labelFilters = uniq([...collectFlagValues("--label"), ...project.label]);
1703
+ const excludeLabelFilters = uniq([...collectFlagValues("--exclude-label"), ...project.excludeLabel]);
1704
+ const overlapped = labelFilters.filter((l) => excludeLabelFilters.includes(l));
1705
+ if (overlapped.length > 0) {
1706
+ console.warn(
1707
+ `[worker] label(s) in both --label and --exclude-label, no issue/PR will match: ${overlapped.join(", ")}`
1708
+ );
1709
+ }
1710
+ return { labelFilters, excludeLabelFilters };
1642
1711
  }
1643
1712
  process.on("unhandledRejection", (err) => {
1644
1713
  console.error("[worker] unhandled rejection:", err);
@@ -1774,7 +1843,7 @@ if (hasProjectFilter()) {
1774
1843
  let sessions = /* @__PURE__ */ new Map();
1775
1844
  let monitorHandle;
1776
1845
  const herdr = await import("./herdr-NDLUM6JF.js");
1777
- const dispatcher = await import("./dispatcher-UTBV3L5Z.js");
1846
+ const dispatcher = await import("./dispatcher-M7ELTAHV.js");
1778
1847
  captureConsole();
1779
1848
  const shutdownController = dispatcher.createDispatcherShutdownHandler(
1780
1849
  (options) => dispatcher.shutdownDispatcher(sessions, monitorHandle, options)
@@ -1840,40 +1909,37 @@ if (hasProjectFilter()) {
1840
1909
  for (const c of customWorkers) printWorkerRow(c.definition.name, "custom", c.source);
1841
1910
  } else if (workerType === "all") {
1842
1911
  const epicFilters = parseEpicFilters();
1843
- const labelFilters = parseLabelFilters();
1912
+ const labelOptions = parseLabelOptions();
1844
1913
  (async () => {
1845
1914
  await assertRunPrerequisites();
1846
- await removeStaleWorktrees();
1847
1915
  await Promise.all(
1848
1916
  startEnabledWorkers(
1849
1917
  candidateNames((e) => e.inAll),
1850
- { epicFilters, labelFilters }
1918
+ { epicFilters, ...labelOptions }
1851
1919
  )
1852
1920
  );
1853
1921
  })();
1854
1922
  } else if (workerType === "yolo") {
1855
1923
  const epicFilters = parseEpicFilters();
1856
- const labelFilters = parseLabelFilters();
1924
+ const labelOptions = parseLabelOptions();
1857
1925
  (async () => {
1858
1926
  await assertRunPrerequisites();
1859
- await removeStaleWorktrees();
1860
1927
  await Promise.all(
1861
1928
  startEnabledWorkers(
1862
1929
  candidateNames((e) => e.inYolo),
1863
- { epicFilters, labelFilters }
1930
+ { epicFilters, ...labelOptions }
1864
1931
  )
1865
1932
  );
1866
1933
  })();
1867
1934
  } else {
1868
1935
  const epicFilters = parseEpicFilters();
1869
- const labelFilters = parseLabelFilters();
1936
+ const labelOptions = parseLabelOptions();
1870
1937
  (async () => {
1871
1938
  if (!isWorkerEnabled(workerType)) {
1872
1939
  console.error(`[worker] ${disabledWorkerMessage(workerType)}`);
1873
1940
  process.exit(1);
1874
1941
  }
1875
1942
  await assertRunPrerequisites();
1876
- await removeStaleWorktrees();
1877
- await WORKERS[workerType].start({ epicFilters, labelFilters });
1943
+ await WORKERS[workerType].start({ epicFilters, ...labelOptions });
1878
1944
  })();
1879
1945
  }
package/dist/lib.js CHANGED
@@ -55,12 +55,12 @@ import {
55
55
  removeWorktreeByBranch,
56
56
  run,
57
57
  syncDefaultBranch
58
- } from "./chunk-D4BPQ3K3.js";
58
+ } from "./chunk-725IRSKF.js";
59
59
  import {
60
60
  getPermissionMode,
61
61
  getRunMode,
62
62
  isAdvisorEnabled
63
- } from "./chunk-HFU27QHT.js";
63
+ } from "./chunk-BA2CE3L6.js";
64
64
  export {
65
65
  CLOUD_DONE_LABEL,
66
66
  addAssignee,
@@ -1,2 +1 @@
1
1
  export declare function generateWorktreeName(): string;
2
- export declare function isGeneratedWorktreeName(name: string): boolean;
@@ -16,7 +16,7 @@ import {
16
16
  truncateToWidth,
17
17
  wrapToWidth,
18
18
  writeScreen
19
- } from "./chunk-HFU27QHT.js";
19
+ } from "./chunk-BA2CE3L6.js";
20
20
  export {
21
21
  CLOUD_WORKTREE_LABEL,
22
22
  CONTROL_CHARS,
@@ -10,6 +10,11 @@ export interface UserConfig {
10
10
  permission: PermissionMode;
11
11
  projects: Record<string, string>;
12
12
  projectGroups: Record<string, string[]>;
13
+ projectLabelFilters: Record<string, ProjectLabelFilters>;
14
+ }
15
+ export interface ProjectLabelFilters {
16
+ label: string[];
17
+ excludeLabel: string[];
13
18
  }
14
19
  export interface ResolvedProject {
15
20
  name: string;
@@ -28,4 +33,5 @@ export declare function resetAdvisorCache(): void;
28
33
  export declare function getPermissionMode(): PermissionMode;
29
34
  export declare function resetPermissionModeCache(): void;
30
35
  export declare function findProjectNameByPath(path: string): string | undefined;
36
+ export declare function findProjectLabelFilters(projectName?: string | undefined): ProjectLabelFilters;
31
37
  export declare function resolveTargetProjects(requested: string[], config: UserConfig): ResolvedProject[];
@@ -11,5 +11,6 @@ export declare function verifyPrCreated(issueNumber: number, worktreeId: string,
11
11
  cloud: boolean;
12
12
  baseBranch: string;
13
13
  startedAt: number;
14
+ labelFilters?: string[];
14
15
  }): Promise<boolean | void>;
15
16
  export declare const execIssueWorker: import("./worker-definition").WorkerDefinition;
@@ -1,5 +1,6 @@
1
1
  import type { Issue } from "../gh";
2
2
  import type { WorkerDefinition } from "./worker-definition";
3
+ export declare function carryLabelFiltersToPr(workerName: string, prNumber: number, labelFilters?: string[]): Promise<void>;
3
4
  export declare const consumableTriggerLabels: (triggerLabels: string[]) => string[];
4
5
  export type PreflightResult = "proceed" | "skip" | "mark-pr-created";
5
6
  export interface IssueWorkerConfig {
@@ -13,6 +14,7 @@ export interface IssueWorkerConfig {
13
14
  cloud: boolean;
14
15
  baseBranch: string;
15
16
  startedAt: number;
17
+ labelFilters?: string[];
16
18
  }) => Promise<boolean | void>;
17
19
  cloudOnBranch?: (issue: Issue, baseBranch: string) => Promise<string>;
18
20
  }
@@ -2,6 +2,7 @@ export type WorkerKind = "issue" | "pr" | "scheduled";
2
2
  export interface WorkerStartOptions {
3
3
  epicFilters?: number[];
4
4
  labelFilters?: string[];
5
+ excludeLabelFilters?: string[];
5
6
  }
6
7
  export declare const WORKER_DEFINITION: unique symbol;
7
8
  export interface WorkerDefinition {
@@ -13,9 +13,3 @@ export declare function getWorktreePath(worktreeId: string): string;
13
13
  export declare function createWorktreeFromBranch(worktreeId: string, baseBranch: string): Promise<void>;
14
14
  export declare function removeWorktree(worktreeId: string): Promise<void>;
15
15
  export declare function removeWorktreeByBranch(branchName: string): Promise<void>;
16
- /**
17
- * 前回のクラッシュ・強制終了などで残った worktree を起動時に一括回収する。
18
- * generateWorktreeName() 形式(adj-noun-4桁)の名前だけを対象にすることで、
19
- * ユーザーが対話セッションで使用中の claude worktree を誤って削除しない。
20
- */
21
- export declare function removeStaleWorktrees(): Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-task-worker",
3
- "version": "0.140.0",
3
+ "version": "0.142.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",