claude-task-worker 0.139.1 → 0.141.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
@@ -60,7 +60,7 @@ CLI が GitHub ラベルを検知してタスクを起動し、プラグイン
60
60
  | [Claude Code (`claude`)](https://docs.anthropic.com/en/docs/claude-code) | タスク実行エンジン |
61
61
  | [Git](https://git-scm.com/) / [jq](https://jqlang.org/) | worktree 操作 / スキル内での JSON 加工 |
62
62
  | [CodeGraph](https://www.npmjs.com/package/@colbymchenry/codegraph) | コード探索用インデックス(任意。未導入ならテキスト検索に落ちる) |
63
- | [Playwright](https://playwright.dev/) のブラウザ | Playwright MCP でのブラウザ確認、`.pen` のスナップショット描画 |
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
65
  | [herdr](https://herdr.dev) | `--project` / `mode: "herdr"` 使用時のみ(`--cloud` は herdr に依存しない) |
66
66
  | [GitHub MCP](https://github.com/github/github-mcp-server) | GitHub アクセスの高速化・クラウド実行時のプロキシ制限回避(任意。Claude 側のコネクタで有効化) |
@@ -126,7 +126,7 @@ claude-task-worker apply-labels # ラベルだけを作成(冪等)
126
126
  ## コマンド
127
127
 
128
128
  ```bash
129
- claude-task-worker <command> [--epic <issue-number>]... [--label <label>]... [--project <name>]... [--cloud] [--debug]
129
+ claude-task-worker <command> [--epic <issue-number>]... [--label <label>]... [--exclude-label <label>]... [--project <name>]... [--cloud] [--debug]
130
130
  ```
131
131
 
132
132
  | コマンド | 内容 |
@@ -154,12 +154,22 @@ claude-task-worker all --epic 100 --epic 200
154
154
 
155
155
  ### `--label <label>`
156
156
 
157
- トリガーラベルに加えて指定ラベルが付いた Issue のみに絞る。複数指定は AND。`--epic` と併用可能。
157
+ トリガーラベルに加えて指定ラベルが付いた Issue・PR のみに絞る(Issue 系・PR 系ワーカーの両方に効く)。`exec-issue` / `epic-issue` / `create-ui-design` が Issue から作った PR にも同じラベルを付けるので、後続の PR 系ワーカーが拾える。複数指定は AND。`--epic` と併用可能。
158
158
 
159
159
  ```bash
160
160
  claude-task-worker all --label priority-high --label needs-design
161
161
  ```
162
162
 
163
+ ### `--exclude-label <label>`
164
+
165
+ 指定ラベルが付いた Issue・PR を対象から外す(Issue 系・PR 系ワーカーの両方に効く。定期ワーカーは対象を持たないため無関係)。複数指定は AND で、どれか1つでも付いていれば外れる。
166
+
167
+ ```bash
168
+ claude-task-worker all --exclude-label wip --exclude-label on-hold
169
+ ```
170
+
171
+ `--label` / `--exclude-label` は `config.json` の `projects` にプロジェクトごとの既定を書ける(下記)。`--project` 経由の起動でだけ CLI の指定に足し込まれる。
172
+
163
173
  ### `--project <name>`
164
174
 
165
175
  指定したプロジェクト(またはグループ、`all`)へ [herdr](https://herdr.dev) 経由でコマンドをディスパッチする。CLI はワーカーを直接実行せず、プロジェクトごとに独立した herdr ワークスペースを作ってそこでコマンドを実行し、稼働状況をステータステーブルに表示する。SIGTERM/SIGINT で全セッションを一括停止する。
@@ -254,7 +264,7 @@ claude-task-worker init --inherit-config ~/ctw/shared.json # 指定パスへ
254
264
 
255
265
  ```bash
256
266
  node plugin/scripts/pen.mjs validate design.pen # ID重複・参照切れ・未知プロパティなどの検査
257
- node plugin/scripts/pen.mjs render design.pen --out out/ # Chromium で描画して PNG 出力
267
+ node plugin/scripts/pen.mjs render design.pen --out out/ # Chrome で描画して PNG 出力
258
268
  ```
259
269
 
260
270
  画像は pen.dev 本体ではなく HTML/CSS へ変換して描画した近似で、描画しきれない表現(取得できなかったフォント・アイコン、shader など)は警告として出る。フォントは Google Fonts、アイコンは unpkg から取得する。
@@ -272,7 +282,7 @@ node plugin/scripts/pen.mjs render design.pen --out out/ # Chromium で描画
272
282
  "permission": "bypassPermissions",
273
283
  "projects": {
274
284
  "app-a": "/Users/me/repos/app-a",
275
- "app-b": "/Users/me/repos/app-b"
285
+ "app-b": { "path": "/Users/me/repos/app-b", "label": ["priority-high"], "excludeLabel": ["wip"] }
276
286
  },
277
287
  "projectGroups": {
278
288
  "frontend": ["app-a", "app-b"]
@@ -282,13 +292,13 @@ node plugin/scripts/pen.mjs render design.pen --out out/ # Chromium で描画
282
292
 
283
293
  | キー | 既定 | 説明 |
284
294
  |---|---|---|
285
- | `projects` | - | プロジェクト名 → 絶対パス |
295
+ | `projects` | - | プロジェクト名 → 絶対パス、または `{ path, label, excludeLabel }`。`label` / `excludeLabel` は `--label` / `--exclude-label` と同じ意味で、`--project` でそのプロジェクトを指定して起動したワーカーにだけ足し込まれる(`--project` なしの起動には適用しない)。`path` の重複は可 |
286
296
  | `projectGroups` | `{}` | グループ名 → プロジェクト名配列 |
287
297
  | `mode` | `"default"` | `"default"`: `claude -p` の子プロセスとして実行 / `"herdr"`: herdr のタブ内で TUI 起動し、実行中の様子を覗ける(`--project` はこちらが必要) |
288
298
  | `advisor` | `false` | `true` で `--advisor <model>` を渡す。モデルは `claude-task-worker.json` の `advisorModel` |
289
299
  | `permission` | `"bypassPermissions"` | Claude CLI の[権限モード](https://code.claude.com/docs/ja/permission-modes)(`bypassPermissions` / `dontAsk` / `auto` / `acceptEdits` / `manual` / `plan`) |
290
300
 
291
- いずれもトップレベル一括で、プロジェクト単位・ワーカー単位の指定はできない。`permission` はワーカーに承認するユーザーがいないため、`bypassPermissions` / `dontAsk` 以外ではタスクが承認待ちで止まりうる。
301
+ `projects` 以外はいずれもトップレベル一括で、プロジェクト単位・ワーカー単位の指定はできない。`permission` はワーカーに承認するユーザーがいないため、`bypassPermissions` / `dontAsk` 以外ではタスクが承認待ちで止まりうる。
292
302
 
293
303
  > ℹ️ `mode: "herdr"` の完了通知音はワーカー側から止められない。無音にするには `~/.config/herdr/config.toml` に `[ui.sound] enabled = false` を書いて `herdr server reload-config` する(herdr サーバー全体に効く)。
294
304
 
@@ -423,7 +433,7 @@ claude-task-worker all
423
433
 
424
434
  ## プロセス管理
425
435
 
426
- 実行中のタスクはリアルタイムのステータステーブルで表示される(タスクID・タイトル・ステータス・開始時刻・経過時間)。SIGTERM/SIGINT で graceful shutdown し、もう一度送ると強制終了してラベル・worktree の後片付けを試みる。前回の異常終了で残った worktree はワーカー起動時に自動回収される。
436
+ 実行中のタスクはリアルタイムのステータステーブルで表示される(タスクID・タイトル・ステータス・開始時刻・経過時間)。SIGTERM/SIGINT で graceful shutdown し、もう一度送ると強制終了してラベル・worktree の後片付けを試みる。前回の異常終了で残った worktree は自動回収しない(同じ path のプロジェクトを並走させたとき他プロセスの作業中 worktree を消さないため)。不要なら `.claude/worktrees/` 配下を `git worktree remove` で手動削除する。
427
437
 
428
438
  ワーカーは応答するユーザーがいない状態でスキルを起動するため、処理が未完のままラベルだけ進む事故を防ぐガードを持つ(バックグラウンド実行と対話系ツールの無効化、自律実行原則のシステムプロンプト注入、PR 実在の完了検証、空出力セッションの失敗扱い、`Stop` フックによる残留プロセスの停止)。
429
439
 
@@ -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,
@@ -1,7 +1,7 @@
1
1
  export declare const PLAYWRIGHT_CORE_PACKAGE = "playwright-core";
2
2
  export declare const PLAYWRIGHT_MCP_PACKAGE = "@playwright/mcp";
3
3
  /**
4
- * `install-deps`(chromium が要求するシステムライブラリの導入)の実行コマンドを組み立てる。
4
+ * `install-deps`(chrome が要求するシステムライブラリの導入)の実行コマンドを組み立てる。
5
5
  *
6
6
  * root(コンテナなど)では `sudo` を使わない。sudo バイナリ自体が入っていない環境が普通にあり、
7
7
  * root なら昇格は不要なため。非 root では `sudo` を前置する(パスワードを求められうる)。
@@ -11,18 +11,16 @@ export declare function buildInstallDepsCommand(uid: number | undefined): {
11
11
  args: string[];
12
12
  };
13
13
  /**
14
- * Playwright MCP が使うブラウザ(chromium)をインストールする。
14
+ * Playwright MCP が使うブラウザ(Google Chrome)をインストールする。
15
15
  *
16
16
  * MCP サーバー本体(`@playwright/mcp`)は `plugin/.mcp.json` が `npx -y ...@latest` で起動するため
17
- * 事前導入が不要だが、ブラウザバイナリは共有キャッシュ(macOS なら `~/Library/Caches/ms-playwright`)へ
18
- * 落としておく必要がある。未取得だと MCP ツールの初回呼び出しが実行時に失敗する。
17
+ * 事前導入が不要だが、ブラウザ(Chrome)は事前に入れておく必要がある。未取得だと MCP ツールの初回呼び出しが実行時に失敗する。
19
18
  *
20
19
  * Linux ではさらにシステムライブラリ(`install-deps`)が必要なため、続けて実行する。同コマンドは
21
20
  * apt 等を叩くので非 root では `sudo` 経由になり、パスワード入力を求められうる。
22
21
  *
23
22
  * どちらも冪等(取得済み・導入済みならスキップされる)なので install / update で分岐しない。
24
23
  *
25
- * ponytail: chromium のみ。firefox / webkit や `--browser chrome`(ブランドChannel)が要るように
26
- * なったら browsers 引数を増やす。
24
+ * shortcut: chrome のみ。firefox / webkit が要るようになったら browsers 引数を増やす。
27
25
  */
28
26
  export declare function installPlaywrightBrowsers(logPrefix: string): 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
  });
@@ -1119,9 +1123,9 @@ async function loadRunCommand3() {
1119
1123
  }
1120
1124
  var PLAYWRIGHT_CORE_PACKAGE = "playwright-core";
1121
1125
  var PLAYWRIGHT_MCP_PACKAGE = "@playwright/mcp";
1122
- var INSTALL_BROWSER_ARGS = ["-y", `${PLAYWRIGHT_MCP_PACKAGE}@latest`, "install-browser", "--no-shell", "chromium"];
1126
+ var INSTALL_BROWSER_ARGS = ["-y", `${PLAYWRIGHT_MCP_PACKAGE}@latest`, "install-browser", "chrome"];
1123
1127
  function buildInstallDepsCommand(uid) {
1124
- const npxArgs = ["-y", `${PLAYWRIGHT_CORE_PACKAGE}@latest`, "install-deps", "chromium"];
1128
+ const npxArgs = ["-y", `${PLAYWRIGHT_CORE_PACKAGE}@latest`, "install-deps", "chrome"];
1125
1129
  return uid === 0 ? { command: "npx", args: npxArgs } : { command: "sudo", args: ["npx", ...npxArgs] };
1126
1130
  }
1127
1131
  async function installPlaywrightBrowsers(logPrefix) {
@@ -1536,7 +1540,7 @@ async function loadCustomWorkersOrExit() {
1536
1540
  return workers;
1537
1541
  }
1538
1542
  function printUsage() {
1539
- console.log(`Usage: claude-task-worker <command> [--project <name>] [--epic <issue-number>] [--label <label-name>]
1543
+ console.log(`Usage: claude-task-worker <command> [--project <name>] [--epic <issue-number>] [--label <label-name>] [--exclude-label <label-name>]
1540
1544
 
1541
1545
  Commands:
1542
1546
  init [--force] Create required GitHub labels and config file (use --force to overwrite existing files)
@@ -1560,7 +1564,9 @@ Options:
1560
1564
  --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
1565
  --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
1566
  --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).
1567
+ --label <name> Limit issue- and PR-based workers to issues/PRs that also carry the specified label. Repeatable: all must be present (AND).
1568
+ --exclude-label <name> Skip issues and PRs that carry the specified label. Repeatable: each is applied (AND), so any listed label excludes the item.
1569
+ 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
1570
 
1565
1571
  Example:
1566
1572
  claude-task-worker init
@@ -1569,6 +1575,7 @@ Example:
1569
1575
  claude-task-worker all --epic 100 --epic 200
1570
1576
  claude-task-worker all --label priority-high
1571
1577
  claude-task-worker all --label priority-high --label needs-design
1578
+ claude-task-worker all --exclude-label wip
1572
1579
  claude-task-worker yolo --epic 100 --epic 200 --label priority-high
1573
1580
  claude-task-worker all --project all
1574
1581
  claude-task-worker all --project web
@@ -1637,8 +1644,18 @@ function parseEpicFilters() {
1637
1644
  return num;
1638
1645
  });
1639
1646
  }
1640
- function parseLabelFilters() {
1641
- return collectFlagValues("--label");
1647
+ function parseLabelOptions() {
1648
+ const project = findProjectLabelFilters();
1649
+ const uniq = (values) => [...new Set(values)];
1650
+ const labelFilters = uniq([...collectFlagValues("--label"), ...project.label]);
1651
+ const excludeLabelFilters = uniq([...collectFlagValues("--exclude-label"), ...project.excludeLabel]);
1652
+ const overlapped = labelFilters.filter((l) => excludeLabelFilters.includes(l));
1653
+ if (overlapped.length > 0) {
1654
+ console.warn(
1655
+ `[worker] label(s) in both --label and --exclude-label, no issue/PR will match: ${overlapped.join(", ")}`
1656
+ );
1657
+ }
1658
+ return { labelFilters, excludeLabelFilters };
1642
1659
  }
1643
1660
  process.on("unhandledRejection", (err) => {
1644
1661
  console.error("[worker] unhandled rejection:", err);
@@ -1774,7 +1791,7 @@ if (hasProjectFilter()) {
1774
1791
  let sessions = /* @__PURE__ */ new Map();
1775
1792
  let monitorHandle;
1776
1793
  const herdr = await import("./herdr-NDLUM6JF.js");
1777
- const dispatcher = await import("./dispatcher-UTBV3L5Z.js");
1794
+ const dispatcher = await import("./dispatcher-M7ELTAHV.js");
1778
1795
  captureConsole();
1779
1796
  const shutdownController = dispatcher.createDispatcherShutdownHandler(
1780
1797
  (options) => dispatcher.shutdownDispatcher(sessions, monitorHandle, options)
@@ -1840,40 +1857,37 @@ if (hasProjectFilter()) {
1840
1857
  for (const c of customWorkers) printWorkerRow(c.definition.name, "custom", c.source);
1841
1858
  } else if (workerType === "all") {
1842
1859
  const epicFilters = parseEpicFilters();
1843
- const labelFilters = parseLabelFilters();
1860
+ const labelOptions = parseLabelOptions();
1844
1861
  (async () => {
1845
1862
  await assertRunPrerequisites();
1846
- await removeStaleWorktrees();
1847
1863
  await Promise.all(
1848
1864
  startEnabledWorkers(
1849
1865
  candidateNames((e) => e.inAll),
1850
- { epicFilters, labelFilters }
1866
+ { epicFilters, ...labelOptions }
1851
1867
  )
1852
1868
  );
1853
1869
  })();
1854
1870
  } else if (workerType === "yolo") {
1855
1871
  const epicFilters = parseEpicFilters();
1856
- const labelFilters = parseLabelFilters();
1872
+ const labelOptions = parseLabelOptions();
1857
1873
  (async () => {
1858
1874
  await assertRunPrerequisites();
1859
- await removeStaleWorktrees();
1860
1875
  await Promise.all(
1861
1876
  startEnabledWorkers(
1862
1877
  candidateNames((e) => e.inYolo),
1863
- { epicFilters, labelFilters }
1878
+ { epicFilters, ...labelOptions }
1864
1879
  )
1865
1880
  );
1866
1881
  })();
1867
1882
  } else {
1868
1883
  const epicFilters = parseEpicFilters();
1869
- const labelFilters = parseLabelFilters();
1884
+ const labelOptions = parseLabelOptions();
1870
1885
  (async () => {
1871
1886
  if (!isWorkerEnabled(workerType)) {
1872
1887
  console.error(`[worker] ${disabledWorkerMessage(workerType)}`);
1873
1888
  process.exit(1);
1874
1889
  }
1875
1890
  await assertRunPrerequisites();
1876
- await removeStaleWorktrees();
1877
- await WORKERS[workerType].start({ epicFilters, labelFilters });
1891
+ await WORKERS[workerType].start({ epicFilters, ...labelOptions });
1878
1892
  })();
1879
1893
  }
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.139.1",
3
+ "version": "0.141.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",