claude-task-worker 0.34.0 → 0.36.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 +140 -21
  2. package/dist/index.js +423 -65
  3. package/package.json +2 -1
package/README.md CHANGED
@@ -51,8 +51,12 @@ GitHub Issues/PRを定期ポーリングし、Claude Codeに処理を委譲す
51
51
  | `resolve-conflict` | `cc-resolve-conflict` (PR) | `/claude-task-worker:resolve-pr-conflict` | 1分 |
52
52
  | `check-dependabot` | `dependencies` (PR) | `/claude-task-worker:check-dependabot` | 1時間 |
53
53
  | `epic-issue` | `cc-epic-issue` (Issue, sub-issues が全て Close) | `/claude-task-worker:create-epic-pr` | 5分 |
54
+ | `create-ui-design` | `cc-create-ui-design` (Issue) | `/claude-task-worker:create-ui-design` | 1分 |
55
+ | `apply-ui-design` | `cc-ui-design-pr-created` (Issue) | `/claude-task-worker:apply-ui-design` | 1分 |
54
56
 
55
57
  > ℹ️ Issue 系ワーカーはすべて GitHub Issue Dependencies の `-is:blocked` 検索 qualifier でサーバ側絞り込みを行うため、未解決の blockedBy Issue を持つ Issue は対象外となる。
58
+ >
59
+ > ℹ️ `create-ui-design` / `apply-ui-design` は `claude-task-worker.json` の `uiDesign.enabled` が `true` のときだけ起動する(既定は `false`)。詳細は「[UIデザイン先行ワークフロー](#uiデザイン先行ワークフロー)」を参照。
56
60
 
57
61
  ### Epic(親Issue)連携
58
62
 
@@ -60,9 +64,63 @@ GitHub Issues/PRを定期ポーリングし、Claude Codeに処理を委譲す
60
64
 
61
65
  `epic-issue` ワーカーは、`cc-epic-issue` ラベル付きの親Issueに紐づくサブIssueがすべて Close されたタイミングで `/claude-task-worker:create-epic-pr` を起動し、エピックブランチからまとめてPRを作成する。
62
66
 
67
+ ### UIデザイン先行ワークフロー
68
+
69
+ UI実装Issueについて、実装の前に Pencil(`.pen`)でデザインを作り、それを独立したPRとしてマージしてから実装へ進むフロー。デザインの見た目・構成を実装PRとは別に単体でレビュー・合意でき、合意済みデザインがリポジトリに永続化される。
70
+
71
+ `claude-task-worker.json` の `uiDesign.enabled` によるオプトインで、既定(`false`)では2つのワーカーが起動しないため、Pencil を使っていないリポジトリの挙動は本機能の追加前と完全に一致する。
72
+
73
+ ```json
74
+ {
75
+ "uiDesign": {
76
+ "enabled": true,
77
+ "designDir": "designs"
78
+ }
79
+ }
80
+ ```
81
+
82
+ | キー | 既定値 | 意味 |
83
+ |---|---|---|
84
+ | `uiDesign.enabled` | `false` | 本ワークフローの有効化。`false` の間は `triage-created-issue` がUI判定を行わず、2つのワーカーも起動しない |
85
+ | `uiDesign.designDir` | `"designs"` | `.pen` とスナップショットの配置先(リポジトリルートからの相対パス) |
86
+
87
+ フローは以下のとおり。
88
+
89
+ ```text
90
+ cc-issue-created + triage-created-issue(ルーティング)
91
+ ├─ UI実装タスクでない → cc-exec-issue(従来どおり)
92
+ └─ UI実装タスク → cc-create-ui-design
93
+ → create-ui-design ワーカー
94
+ ・.pen を作成/更新 + snapshots/ に PNG 出力
95
+ ・ブランチ cc-ui-design-<N> を push しデザインPRを作成(Refs #N。closing keyword は使わない)
96
+ ・PR に cc-triage-scope + cc-ui-design、Issue に cc-ui-design-pr-created を付与
97
+ → triage-pr / fix-review-point / resolve-conflict(既存フローでレビュー・マージ)
98
+ → apply-ui-design ワーカー
99
+ ・デザインPRが MERGED になるまで skip
100
+ ・Issue description に「## UIデザイン」セクションを追記
101
+ ・cc-ui-design-ready + cc-exec-issue を付与
102
+ → exec-issue(デザインを参照元として実装)
103
+ ```
104
+
105
+ デザインPRは Epic PR ではないため `cc-release-ready` によるマージ保留の対象外で、`triage-pr` が通常どおりレビュー・マージする。`triage-pr` は `cc-ui-design` ラベル付きPRに対してコードレビューではなくデザイン向けの観点(差分が `.pen` とPNGに限定されているか、スナップショットとデザイン意図が読み取れるか、Issue要件を満たしているか)で評価する。
106
+
107
+ デザインが不要と判明した場合は `create-ui-design` が理由をコメントして `cc-ui-design-ready` + `cc-exec-issue` を付与し、人手を介さず実装へ復帰する。Pencil が使えない環境やデザインPRが却下された場合は `cc-need-human-check` に落ちて停止する(共通除外ラベルのため無限リトライしない)。
108
+
63
109
  ### `--project` 指定時(ディスパッチャーモード)
64
110
 
65
- `--project` オプションを指定した場合、CLIは自身でワーカーを起動する代わりに [herdr](https://herdr.dev) 経由のディスパッチャーとして動作する。指定したプロジェクトごとにherdrのタブ(ペイン)を作成し、そのプロジェクトのディレクトリで(`--project` を除いた)同じコマンドを非同期実行させ、稼働状況をステータステーブルで監視する。詳細は「[`--project <name>` オプション](#--project-name-オプション)」を参照。
111
+ `--project` オプションを指定した場合、CLIは自身でワーカーを起動する代わりに [herdr](https://herdr.dev) 経由のディスパッチャーとして動作する。指定したプロジェクトごとにherdrのワークスペースを作成し、そのルートペインで(`--project` を除いた)同じコマンドを実行させ、稼働状況をステータステーブルで監視する。詳細は「[`--project <name>` オプション](#--project-name-オプション)」を参照。
112
+
113
+ ### claude-task-worker プラグインの構成
114
+
115
+ | ディレクトリ | 内容 |
116
+ |---|---|
117
+ | `plugin/skills/` | ワーカーが呼び出すスキル群と、対話セッションから使う補助スキル(`commit-push` / `create-pr` / `breakdown-issues` / `edit-pencil-design` など) |
118
+ | `plugin/agents/` | サブエージェント定義(`explore-agent` / `frontend-implementer` / `general-purpose-assistant` / `lightweight-assistant` / `pencil-design-updater` / `requirement-todo-organizer`) |
119
+ | `plugin/hooks/` | `SessionStart`(worktree セットアップ・`git fetch --prune`)と `UserPromptSubmit`(`codegraph prompt-hook`)のフック定義 |
120
+ | `plugin/scripts/` | フックから呼ばれるスクリプト(`setup-worktree.sh` / `stop-servers.mjs`) |
121
+ | `plugin/.mcp.json` | プラグインが提供する MCP サーバー定義(下記「[プラグインが利用する MCP サーバー](#プラグインが利用する-mcp-サーバー)」) |
122
+
123
+ ワーカー起動スキルには `Stop` フック(`plugin/scripts/stop-servers.mjs`)が設定されており、スキル終了時に `docker compose down` と、worktree を作業ディレクトリに持つ残留プロセスの `SIGTERM` をベストエフォートで実行する。worktree はスキル完了直後に削除されるため、切り離されたサーバープロセスが残ると削除の妨げになるのを防ぐ。
66
124
 
67
125
  ## 必要なライブラリ
68
126
 
@@ -77,8 +135,10 @@ CLI 本体に npm の実行時依存パッケージはない(ビルド時に e
77
135
  | [Claude Code (`claude`)](https://docs.anthropic.com/en/docs/claude-code) | - | タスク実行エンジン。各ワーカーが Claude CLI プロセスとして起動する |
78
136
  | [Git (`git`)](https://git-scm.com/) | - | worktree の作成・ブランチ操作 |
79
137
  | [jq](https://jqlang.org/) | - | プラグインスキル内でのJSON加工(`check-dependabot` / `edit-pencil-design` スキルなどで使用) |
138
+ | [CodeGraph (`codegraph`)](https://www.npmjs.com/package/@colbymchenry/codegraph) | - | コード探索用のインデックス。MCP サーバー(`codegraph serve --mcp`)としてプラグインから起動される。`install` / `update` / `init` が面倒を見る。未インストールでもワーカーは動作する(探索がテキスト検索に落ちるだけ) |
80
139
  | [Pencil CLI (`pencil`)](https://docs.pencil.dev/for-developers/pencil-cli) | - | `.pen` デザインファイルの編集・参照・コンフリクト解消(`edit-pencil-design` / `inspect-pencil-node` / `resolve-pencil-conflict` スキルで使用) |
81
- | [herdr](https://herdr.dev) | - | `--project` 使用時にのみ必要(通常ワーカー実行には不要)。複数プロジェクトへのディスパッチ・セッション監視・一括停止に使用 |
140
+ | [herdr](https://herdr.dev) | - | `--project` 使用時と `mode: "herdr"` 使用時にのみ必要。複数プロジェクトへのディスパッチ・セッション監視・一括停止、タスクのTUI実行に使用 |
141
+ | [Headroom (`headroom`)](https://github.com/headroom-ai/headroom) | - | `headroom: true` 使用時にのみ必要。タスクの claude をコンテキスト圧縮プロキシ経由で起動する |
82
142
  | `claude-task-worker` プラグイン | - | 各ワーカーが呼び出すスキル群(本リポジトリの `plugin/`) |
83
143
 
84
144
  Slack通知(任意)を使う場合は Slack Incoming Webhook URL が必要。Claude API使用状況の取得には、macOSでは `security` コマンド(Keychain)を使用し、それ以外の環境では `~/.claude/.credentials.json` にフォールバックする。
@@ -102,6 +162,7 @@ Slack通知(任意)を使う場合は Slack Incoming Webhook URL が必要
102
162
 
103
163
  | サーバー | 接続方法 | 用途 |
104
164
  |---|---|---|
165
+ | `codegraph` | `codegraph serve --mcp`(stdio) | シンボルの定義元・参照元・呼び出し関係をたどるコード探索。ワーカー起動セッションは `--append-system-prompt` の指示により、`Grep`/`Glob` より優先してこの MCP を使う |
105
166
  | `context7` | HTTP(`https://mcp.context7.com/mcp`) | ライブラリの最新ドキュメント取得 |
106
167
  | `next-devtools` | `npx -y next-devtools-mcp@latest` | Next.js 開発支援 |
107
168
  | `shadcn` | `npx shadcn@latest mcp` | shadcn/ui コンポーネント情報の取得 |
@@ -114,7 +175,9 @@ Slack通知(任意)を使う場合は Slack Incoming Webhook URL が必要
114
175
  - [GitHub CLI (`gh`)](https://cli.github.com/) がインストール・認証済みであること
115
176
  - [Claude Code (`claude`)](https://docs.anthropic.com/en/docs/claude-code) がインストール済みであること
116
177
  - `claude-task-worker` プラグインがインストール済みであること(下記インストール手順を参照)
117
- - `--project` オプションを使う場合のみ、[herdr](https://herdr.dev) がインストール済みであること(通常のワーカー実行には不要)
178
+ - `--project` オプション・`mode: "herdr"` を使う場合のみ、[herdr](https://herdr.dev) がインストール済みであること
179
+ - `headroom: true` を使う場合のみ、[Headroom](https://github.com/headroom-ai/headroom) がインストール済みであること
180
+ - CodeGraph はコード探索の精度向上のために推奨(`install` / `init` が導入・インデックス構築を行う)。未導入でもワーカーは動作する
118
181
 
119
182
  詳細は上記「[必要なライブラリ](#必要なライブラリ)」を参照。
120
183
 
@@ -122,7 +185,7 @@ Slack通知(任意)を使う場合は Slack Incoming Webhook URL が必要
122
185
 
123
186
  #### 推奨手順(一発セットアップ)
124
187
 
125
- 以下のコマンド一発で、Claude Code マーケットプレイスの追加・プラグインのインストール・CLI本体のグローバルインストールをまとめて行える。
188
+ 以下のコマンド一発で、Claude Code マーケットプレイスの追加・プラグインのインストール・CLI本体のグローバルインストール・CodeGraph CLI のインストールをまとめて行える。
126
189
 
127
190
  ```bash
128
191
  npx claude-task-worker install
@@ -131,6 +194,7 @@ npx claude-task-worker install
131
194
  - `claude plugin marketplace add getty104/claude-task-worker` — マーケットプレイスの追加(追加済みの場合のエラーはログのみで無視して続行)
132
195
  - `claude plugin install claude-task-worker@claude-task-worker` — プラグインのインストール
133
196
  - `npm install -g claude-task-worker@latest` — CLI 本体のグローバルインストール(`npx` 実行時でも `claude-task-worker` コマンドを常設化する)
197
+ - `npm install -g @colbymchenry/codegraph@latest` — CodeGraph CLI のインストール(MCP サーバーの登録はプラグインの `.mcp.json` が担うため、`codegraph install` は実行しない)
134
198
 
135
199
  インストール後、Claude Code のセッションを再起動するとプラグインが有効化される。
136
200
 
@@ -173,7 +237,7 @@ brew install herdr
173
237
 
174
238
  #### 更新
175
239
 
176
- CLI とプラグイン(マーケットプレイス)をまとめて更新する。
240
+ CLI・プラグイン(マーケットプレイス)・CodeGraph CLI をまとめて更新する。
177
241
 
178
242
  ```bash
179
243
  claude-task-worker update
@@ -182,10 +246,11 @@ claude-task-worker update
182
246
  - `claude plugin marketplace update claude-task-worker` — マーケットプレイスの更新
183
247
  - `claude plugin update claude-task-worker@claude-task-worker` — プラグインの更新(反映にはセッション再起動が必要)
184
248
  - `npm install -g claude-task-worker@latest` — CLI 本体の更新
249
+ - `codegraph upgrade` — CodeGraph CLI の更新(未インストール環境ではコマンドが無く失敗するため、`npm install -g @colbymchenry/codegraph@latest` へフォールバックする)
185
250
 
186
251
  ### 初期化
187
252
 
188
- 対象リポジトリで実行すると、必要なGitHubラベル・Issueテンプレート・GitHub Actionsワークフロー・設定ファイルが作成される。既存ファイルは保護され、`--force` を指定すると上書きされる。
253
+ 対象リポジトリで実行すると、必要なGitHubラベル・Issueテンプレート・GitHub Actionsワークフロー・設定ファイルが作成され、CodeGraph のセットアップが行われる。既存ファイルは保護され、`--force` を指定すると上書きされる。
189
254
 
190
255
  ```bash
191
256
  claude-task-worker init # 既存ファイルは保護
@@ -208,12 +273,21 @@ claude-task-worker init --force # 既存ファイルを強制上書き
208
273
  | `cc-pr-created` | PR作成完了マーク |
209
274
  | `cc-epic-issue` | エピックマーク(Issueではサブ全Closeで `epic-issue` ワーカー起動、PRではリリースゲート対象を示すマーク) |
210
275
  | `cc-release-ready` | エピックPRがリリース可能(マージ問題なし)と判定されたマーク。実際のマージ(リリース)は人間が実施 |
276
+ | `cc-create-ui-design` | UIデザイン作成トリガー(`create-ui-design` ワーカー。`uiDesign.enabled` が `true` の場合のみ機能する) |
277
+ | `cc-ui-design-pr-created` | デザインPR作成済み・マージ待ちマーク(`apply-ui-design` ワーカーのトリガー) |
278
+ | `cc-ui-design-ready` | デザイン反映済みマーク(再デザイン抑止。デザイン不要と判定された場合も付与される) |
279
+ | `cc-ui-design` | デザインPRのマーカー(PR。`triage-pr` のレビュー観点切り替えに使う) |
211
280
 
212
281
  作成されるファイル:
213
282
 
214
283
  - `.github/ISSUE_TEMPLATE/cc-triage-scope.yml` — `cc-triage-scope` ラベル付きIssue作成用テンプレート
215
284
  - `.github/workflows/assign-creator-on-cc-triage-scope.yml` — Issue作成者を自動アサインするワークフロー
216
- - `claude-task-worker.json` — 設定ファイル(コマンド実行ディレクトリ直下。全ワーカーのデフォルト設定が書き込まれた状態で作成される)
285
+ - `claude-task-worker.json` — 設定ファイル(コマンド実行ディレクトリ直下。全ワーカーのデフォルト設定と `uiDesign`(既定は無効)が書き込まれた状態で作成される)
286
+
287
+ CodeGraph のセットアップ:
288
+
289
+ - グローバル gitignore(`$XDG_CONFIG_HOME/git/ignore`、未設定なら `~/.config/git/ignore`)へ `.codegraph/` を追記する(冪等)。`.codegraph/` はプロジェクトごとのローカルインデックスでコミット対象ではないが、対象リポジトリの `.gitignore` を汚さないためグローバル側に入れる
290
+ - `codegraph init` でインデックスを構築する。CodeGraph が未インストールでも `init` 全体は失敗せず、ログにエラーを出して続行する
217
291
 
218
292
  ## コマンド
219
293
 
@@ -282,9 +356,11 @@ claude-task-worker yolo --epic 100 --epic 200 --label priority-high
282
356
 
283
357
  ディスパッチャーは以下の3つの機能を持つ。
284
358
 
285
- - **一斉起動**: 対象プロジェクトごとに `ctw:<プロジェクト名>` ラベルのherdrワークスペースを作成し、そのプロジェクトのディレクトリで(`--project` を除いた)同じコマンドを非同期実行する
359
+ - **一斉起動**: 対象プロジェクトごとに `ctw:<プロジェクト名>` ラベルのherdrワークスペースを作成し、そのプロジェクトのディレクトリで(`--project` を除いた)同じコマンドを実行する。ペインのシェル初期化が終わるのを待ってからコマンドを送り、送信後はワーカープロセスが実際に起動したかを確認する(シェルのままなら再送し、それでも起動しなければそのプロジェクトは失敗としてタブを閉じる)
286
360
  - **稼働一覧**: 各セッションのプロジェクト名・ワークスペースID・ペインID・ステータス・稼働時間をステータステーブルとして定期的に画面へ描画し、対象プロセスが終了したセッションは自動的に一覧から除去する
287
- - **一括停止**: SIGTERM/SIGINTを受けると、稼働中の全セッションへ ctrl-c を送信して各プロジェクトのコマンドを終了させ、終了を待ってからherdrワークスペースを閉じる(`mode: "herdr"` でワーカーが作ったタスクタブもワークスペースごと片付く)
361
+ - **一括停止**: SIGTERM/SIGINTを受けると、稼働中の全セッションへ ctrl-c を送信して各プロジェクトのコマンドを終了させ、終了を待ってからherdrワークスペースを閉じる(`mode: "herdr"` でワーカーが作ったタスクタブもワークスペースごと片付く)。もう一度シグナルを送ると強制終了する
362
+
363
+ > ℹ️ herdr はワークスペースを閉じる際、閉じた対象がフォーカスされていなくても別のワークスペースへフォーカスを移す。ディスパッチャーはクローズ直前のフォーカス状態を控えて閉じた後に復元するため、無関係なワークスペースを見ていても表示は勝手に切り替わらない。
288
364
 
289
365
  ```bash
290
366
  claude-task-worker all --project all
@@ -312,9 +388,9 @@ claude-task-worker exec-issue --project app-a --epic 100 --label priority-high
312
388
 
313
389
  `mode: "herdr"` のときの1タスクの流れ:
314
390
 
315
- 1. worktree を作成し、`ctw:<プロジェクト名>:#<Issue/PR番号>` ラベルのタブで claude を TUI 起動する
316
- 2. herdr が持つ agent ステータスを監視し、`working` → `idle` の遷移をタスク完了とみなす
317
- 3. 完了したらペインの内容を回収して通知に使い、タブを閉じてラベル・worktree を後片付けする
391
+ 1. worktree を作成し、`ctw:<プロジェクト名>:#<Issue/PR番号>` ラベルのタブで claude を TUI 起動する(タブを先に作ってからその中で起動するため、ユーザーが見ているタブにペインが割り込むことはない)
392
+ 2. herdr が持つ agent ステータスを監視し、`done`(未確認完了)または `working` → `idle` の遷移をタスク完了とみなす。ワーカーのタスクタブは誰も開かないため、完了したタスクは通常 `done` として観測される
393
+ 3. 完了したら claude のセッション transcript(`~/.claude/projects/*/<sessionId>.jsonl`)から最終レポートを回収して通知に使い、タブを閉じてラベル・worktree を後片付けする。transcript を引けない場合のみペインの内容にフォールバックする
318
394
 
319
395
  補足:
320
396
 
@@ -347,12 +423,14 @@ claude-task-worker exec-issue --project app-a --epic 100 --label priority-high
347
423
  | `headroom` | 実行されるコマンド |
348
424
  |-----------|------------------|
349
425
  | `false`(既定) | `claude <引数>` |
350
- | `true` | `headroom wrap claude -- <引数>` |
426
+ | `true` | `headroom wrap claude --1m --memory --no-tokensave --no-serena -- <引数>` |
351
427
 
352
428
  補足:
353
429
 
354
430
  - `mode` とは独立して組み合わせられる(`mode: "herdr"` の TUI 起動も `headroom wrap claude` になる)
355
- - claude へ渡す引数はすべて `--` の後ろに置かれる。`headroom wrap claude` 自身も `--port` / `--memory` などのオプションを持ち、`-p` のように衝突しうるフラグは `--` の後ろでないと claude に届かないため
431
+ - claude へ渡す引数はすべて `--` の後ろに置かれる。`headroom wrap claude` 自身も `--port` / `--memory` などのオプションを持ち、`-p` のように衝突しうるフラグは `--` の後ろでないと claude に届かないため。逆に headroom 自身へのオプションは `--` の前に置く
432
+ - 1M コンテキストウィンドウは `--model` の値へ `[1m]` サフィックスを付けることで解放する(`--model 'sonnet[1m]'` のようになる)。headroom の `--1m` は `ANTHROPIC_MODEL` をセットするだけの実装で、CLI の `--model` が環境変数に勝つため、`--model` を明示するワーカー起動では単体では効かない
433
+ - `--no-tokensave` / `--no-serena` で headroom が既定で登録するコードグラフ系 MCP を止める。コード探索はプラグインの codegraph MCP が担うため機能面の損失はなく、タスク起動のたびに走る再登録・再インデックスと、ユーザーのグローバル設定(`~/.claude.json`)の書き換えを避けられる
356
434
  - `headroom: true` で headroom コマンドが PATH に無い場合、ワーカーは起動時にエラー終了する(`mode: "herdr"` と同じく、サイレントに直接起動へフォールバックはしない)
357
435
  - `headroom wrap claude` は claude を起動する前に起動バナー(枠線・`ANTHROPIC_BASE_URL=...` など)を stdout へ出力し、これを抑止するオプションは無い。ワーカーは空振りセッション検知(exit 0 かつ無出力を失敗とみなす判定)の前にこのバナーを取り除くため、検知は `headroom: true` でも従来どおり機能する。ただし Slack 通知の本文にはバナーがそのまま含まれる
358
436
 
@@ -430,9 +508,30 @@ claude-task-worker exec-issue --project app-a --epic 100 --label priority-high
430
508
  - 完了後、親Issueに `cc-pr-created` ラベルを付与
431
509
  - 完了後、作成されたエピックPR(`cc-epic-<親Issue番号>` ブランチ)に `cc-epic-issue` と `cc-triage-scope` ラベルを付与し、triage-pr のリリースゲート判定に引き継ぐ
432
510
 
511
+ ### create-ui-design
512
+
513
+ `cc-create-ui-design` ラベルが付いたIssueを定期取得し、`/claude-task-worker:create-ui-design` を起動して Pencil デザイン(`.pen` + スナップショットPNG)を作成し、デザインのみのPRを作成する。(デフォルト1分間隔)
514
+
515
+ - `claude-task-worker.json` の `uiDesign.enabled` が `true` の場合のみ起動する(`false` なら1行ログを出して即終了)
516
+ - `cc-ui-design-pr-created` / `cc-ui-design-ready` が付いているIssueは除外
517
+ - デザインPRは `cc-ui-design-<Issue番号>` の固定名ブランチを head とする(後段の `apply-ui-design` が一意に特定するため)
518
+ - 完了後、デザインPRの実在を確認できた場合のみ、PRに `cc-ui-design` と `cc-triage-scope`、Issueに `cc-ui-design-pr-created` を付与する
519
+ - スキルが「デザイン不要」と判定した場合(`cc-ui-design-ready` 付与済み)は完了扱いとし、そのまま実装フェーズへ流す
520
+ - PRの実在を確認できない場合は `cc-need-human-check` を付与してIssueにコメントを残す
521
+
522
+ ### apply-ui-design
523
+
524
+ `cc-ui-design-pr-created` ラベルが付いたIssueを定期取得し、デザインPRのマージ後に `/claude-task-worker:apply-ui-design` を起動して Issue description へデザイン参照セクション(`## UIデザイン`)を書き戻す。(デフォルト1分間隔)
525
+
526
+ - `claude-task-worker.json` の `uiDesign.enabled` が `true` の場合のみ起動する
527
+ - `cc-ui-design-ready` / `cc-exec-issue` が付いているIssueは除外
528
+ - preflight でデザインPR(`cc-ui-design-<Issue番号>` を head とするPR)の状態を確認し、`OPEN` の間はスキップしてマージを待つ
529
+ - 未マージのままクローズされた場合・PRが見つからない場合は `cc-need-human-check` を付与してスキップする
530
+ - 完了後、description に `## UIデザイン` セクションと `.pen` パスが載っていることを検証できた場合のみ `cc-ui-design-ready` と `cc-exec-issue` を付与する
531
+
433
532
  ### all
434
533
 
435
- 通常ワーカー7つ(exec-issue, fix-review-point, create-issue, update-issue, answer-issue-questions, resolve-conflict, epic-issue)を同時にポーリングする。`--epic` / `--label` オプションでIssue系ワーカーの処理対象を絞り込み可能(どちらも複数指定可)。
534
+ 通常ワーカー9つ(exec-issue, fix-review-point, create-issue, update-issue, answer-issue-questions, resolve-conflict, epic-issue, create-ui-design, apply-ui-design)を同時にポーリングする。`--epic` / `--label` オプションでIssue系ワーカーの処理対象を絞り込み可能(どちらも複数指定可)。UIデザイン系2ワーカーは `uiDesign.enabled` が `false` なら起動しない。
436
535
 
437
536
  ### yolo
438
537
 
@@ -444,7 +543,7 @@ claude-task-worker exec-issue --project app-a --epic 100 --label priority-high
444
543
 
445
544
  ### install
446
545
 
447
- `claude-task-worker` マーケットプレイスの追加・プラグインのインストール・CLI本体のグローバルインストールを一括で行う。
546
+ `claude-task-worker` マーケットプレイスの追加・プラグインのインストール・CLI本体のグローバルインストール・CodeGraph CLI のインストールを一括で行う。
448
547
 
449
548
  ```bash
450
549
  npx claude-task-worker install
@@ -453,12 +552,13 @@ npx claude-task-worker install
453
552
  - `claude plugin marketplace add getty104/claude-task-worker` — マーケットプレイスの追加(追加済みの場合のエラーはログのみで無視して続行)
454
553
  - `claude plugin install claude-task-worker@claude-task-worker` — プラグインのインストール
455
554
  - `npm install -g claude-task-worker@latest` — CLI 本体のグローバルインストール
555
+ - `npm install -g @colbymchenry/codegraph@latest` — CodeGraph CLI のインストール
456
556
 
457
- いずれかのステップが失敗しても処理は継続し、`[install]` プレフィックス付きでエラー内容がログ出力される。
557
+ いずれかのステップが失敗しても処理は継続し、`[install]` プレフィックス付きでエラー内容がログ出力される(失敗があった場合の終了コードは 1)。
458
558
 
459
559
  ### update
460
560
 
461
- `claude-task-worker` プラグイン/マーケットプレイスとCLI本体を更新する。
561
+ `claude-task-worker` プラグイン/マーケットプレイス・CLI本体・CodeGraph CLI を更新する。
462
562
 
463
563
  ```bash
464
564
  claude-task-worker update
@@ -467,8 +567,9 @@ claude-task-worker update
467
567
  - `claude plugin marketplace update claude-task-worker` — マーケットプレイスの更新
468
568
  - `claude plugin update claude-task-worker@claude-task-worker` — プラグインの更新(反映にはセッション再起動が必要)
469
569
  - `npm install -g claude-task-worker@latest` — CLI 本体の更新
570
+ - `codegraph upgrade` — CodeGraph CLI の更新(失敗時は `npm install -g @colbymchenry/codegraph@latest` へフォールバック)
470
571
 
471
- いずれかのステップが失敗しても処理は継続し、`[update]` プレフィックス付きでエラー内容がログ出力される。
572
+ いずれかのステップが失敗しても処理は継続し、`[update]` プレフィックス付きでエラー内容がログ出力される(失敗があった場合の終了コードは 1)。
472
573
 
473
574
  ### version
474
575
 
@@ -480,7 +581,7 @@ claude-task-worker --version
480
581
  claude-task-worker -v
481
582
  ```
482
583
 
483
- `package.json` の `version` を出力する(例: `0.6.0`)。
584
+ `package.json` の `version` を出力する(例: `0.34.0`)。
484
585
 
485
586
  ## 設定ファイル
486
587
 
@@ -489,6 +590,7 @@ claude-task-worker -v
489
590
  | キー | 型 | デフォルト | 説明 |
490
591
  |---|---|---|---|
491
592
  | `fixReviewPointCallbackCommentMessage` | string | - | fix-review-point 完了時にPRへ投稿するコメント(未設定の場合は投稿しない) |
593
+ | `uiDesign` | object | `{ "enabled": false, "designDir": "designs" }` | UIデザイン先行ワークフローの設定(詳細は「[UIデザイン先行ワークフロー](#uiデザイン先行ワークフロー)」) |
492
594
  | `workers` | object | `{}` | ワーカーごとに Claude CLI に渡すスキル、`--model` / `--effort`、ポーリング間隔、クールダウン時間、最大同時実行数を上書きする設定(詳細は下記) |
493
595
 
494
596
  ### ワーカーごとの設定
@@ -507,6 +609,8 @@ claude-task-worker -v
507
609
  | `resolve-conflict` | `/claude-task-worker:resolve-pr-conflict` | `sonnet` | `high` | 60 | 0 | 1 |
508
610
  | `check-dependabot` | `/claude-task-worker:check-dependabot` | `sonnet` | `high` | 3600 | 0 | 1 |
509
611
  | `epic-issue` | `/claude-task-worker:create-epic-pr` | `sonnet` | `high` | 300 | 0 | 1 |
612
+ | `create-ui-design` | `/claude-task-worker:create-ui-design` | `sonnet` | `high` | 60 | 0 | 1 |
613
+ | `apply-ui-design` | `/claude-task-worker:apply-ui-design` | `sonnet` | `high` | 60 | 0 | 1 |
510
614
 
511
615
  各フィールドの値:
512
616
 
@@ -543,13 +647,27 @@ claude-task-worker all
543
647
 
544
648
  通知にはClaude APIの使用状況(5時間/7日間の利用率とリセット時刻)も含まれる。未設定の場合、通知は送信されない。
545
649
 
650
+ 使用状況を組み立てる際、[RunCat Neo](https://kyome.io/runcat/) 用のスナップショットを `~/.claude/runcat-usage.json`(`RUNCAT_OUT_FILE` で変更可)へ一時ファイル + rename で原子的に書き出す。Webhook 未設定でも通知が no-op になるだけでスナップショットは更新される。使用状況の取得自体は `/tmp/claude-usage-cache.json` の360秒キャッシュを挟むため、値は最大6分古くなりうる。
651
+
546
652
  ## プロセス管理
547
653
 
548
654
  実行中のタスクはリアルタイムのステータステーブルで表示される。
549
655
 
550
656
  - タスクID・タイトル・ステータス(running/completed/failed)・開始時刻・経過時間を表示
657
+ - `mode: "herdr"` では実行中の行に agent ステータスが併記される(`running:working` / `running:blocked` など)
551
658
  - 同一Issue/PRの重複実行を自動防止
552
- - SIGTERM/SIGINTで全子プロセスをgraceful shutdown
659
+ - SIGTERM/SIGINTで全子プロセスをgraceful shutdown(もう一度送ると強制終了し、ラベル・worktree の後片付けを試みる)
660
+ - 前回の異常終了で残った worktree はワーカー起動時に自動回収される(実行中タスク・対話セッションが掴んでいる worktree は保護される)
661
+
662
+ ### タスク実行のガード
663
+
664
+ ワーカーは応答するユーザーがいない状態でスキルを起動するため、処理が未完のままセッションが終了してラベルだけ進む事故を防ぐガードを持つ。
665
+
666
+ - **バックグラウンド実行の無効化**: `CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1` を全タスクの環境変数として注入し、Bash の `run_in_background` やサブエージェントの自動バックグラウンド化を止める
667
+ - **ツールの無効化**: `--disallowedTools` で `Monitor` / `ScheduleWakeup` / `AskUserQuestion` / `EnterPlanMode` / `Cron*` / `RemoteTrigger` / `EnterWorktree` を無効化する(後続ウェイクアップ前提のもの、応答者が必要なもの、ワーカーの worktree 管理と競合するもの)
668
+ - **自律実行原則の注入**: `--append-system-prompt` で「ユーザーに質問しない・全ステップを完遂してから終了する・曖昧なら安全側を選ぶ・サブエージェントの完了報告を検証する」および CodeGraph 優先のコード探索方針を注入する
669
+ - **完了検証**: exec-issue / epic-issue は PR の実在(または Issue のクローズ)を確認できるまで `cc-pr-created` を付けず、確認できない場合は `cc-need-human-check` を付けて Issue にコメントを残す
670
+ - **空振り検知**: 正常終了しても出力が空のセッションは失敗として分類し、失敗通知(stderr の末尾を含む)を送る
553
671
 
554
672
  ## 開発
555
673
 
@@ -557,6 +675,7 @@ claude-task-worker all
557
675
  npm install
558
676
  npm run build # 型チェック(tsc --noEmit)+ esbuild で dist/index.js にバンドル
559
677
  npm run dev # 型チェックの watch モード
678
+ npm test # ユニットテスト(node --experimental-strip-types --test)
560
679
  npm run lint # ESLint
561
680
  npm run lint:fix # ESLint(自動修正)
562
681
  npm run format # Prettier で整形
package/dist/index.js CHANGED
@@ -234,15 +234,16 @@ __export(herdr_exports, {
234
234
  HerdrError: () => HerdrError,
235
235
  HerdrUnavailableError: () => HerdrUnavailableError,
236
236
  agentGet: () => agentGet,
237
- agentStart: () => agentStart,
238
237
  checkHerdrAvailable: () => checkHerdrAvailable,
239
238
  getCurrentWorkspaceId: () => getCurrentWorkspaceId,
239
+ launchAgentInPane: () => launchAgentInPane,
240
240
  paneClose: () => paneClose,
241
241
  paneGet: () => paneGet,
242
242
  paneProcessInfo: () => paneProcessInfo,
243
243
  paneRead: () => paneRead,
244
244
  paneSendKeys: () => paneSendKeys,
245
245
  paneSendText: () => paneSendText,
246
+ shellQuoteArgv: () => shellQuoteArgv,
246
247
  tabClose: () => tabClose,
247
248
  tabCreate: () => tabCreate,
248
249
  tabList: () => tabList,
@@ -395,32 +396,12 @@ async function workspaceClose(workspaceId) {
395
396
  async function workspaceFocus(workspaceId) {
396
397
  await execHerdr(["workspace", "focus", workspaceId]);
397
398
  }
398
- async function agentStart({
399
- name,
400
- cwd,
401
- argv,
402
- workspaceId,
403
- tabId: targetTabId,
404
- env
405
- }) {
406
- const result = await execHerdr([
407
- "agent",
408
- "start",
409
- name,
410
- ...workspaceId ? ["--workspace", workspaceId] : [],
411
- ...targetTabId ? ["--tab", targetTabId] : [],
412
- ...cwdArgs(cwd),
413
- ...envArgs(env),
414
- "--no-focus",
415
- "--",
416
- ...argv
417
- ]);
418
- const paneId = result?.agent?.pane_id;
419
- const tabId = result?.agent?.tab_id;
420
- if (!paneId || !tabId) {
421
- throw new Error("Failed to start agent: invalid response structure from herdr");
422
- }
423
- return { paneId, tabId };
399
+ function shellQuoteArgv(argv) {
400
+ return argv.map((arg) => `'${arg.replace(/'/g, `'\\''`)}'`).join(" ");
401
+ }
402
+ async function launchAgentInPane(paneId, argv) {
403
+ await paneSendText(paneId, shellQuoteArgv(argv));
404
+ await paneSendKeys(paneId, "enter");
424
405
  }
425
406
  function toAgentStatus(value) {
426
407
  return value === "working" || value === "idle" || value === "blocked" || value === "done" ? value : "unknown";
@@ -551,10 +532,14 @@ var init_herdr = __esm({
551
532
  // src/herdr-runner.ts
552
533
  var herdr_runner_exports = {};
553
534
  __export(herdr_runner_exports, {
535
+ AGENT_DETECT_POLL_INTERVAL_MS: () => AGENT_DETECT_POLL_INTERVAL_MS,
536
+ AGENT_DETECT_TIMEOUT_MS: () => AGENT_DETECT_TIMEOUT_MS,
554
537
  AGENT_POLL_INTERVAL_MS: () => AGENT_POLL_INTERVAL_MS,
555
538
  CLAUDE_EXIT_POLL_INTERVAL_MS: () => CLAUDE_EXIT_POLL_INTERVAL_MS,
556
539
  CLAUDE_EXIT_TIMEOUT_MS: () => CLAUDE_EXIT_TIMEOUT_MS,
557
540
  PANE_OUTPUT_LINES: () => PANE_OUTPUT_LINES,
541
+ PANE_READY_POLL_INTERVAL_MS: () => PANE_READY_POLL_INTERVAL_MS,
542
+ PANE_READY_TIMEOUT_MS: () => PANE_READY_TIMEOUT_MS,
558
543
  buildHerdrTaskResult: () => buildHerdrTaskResult,
559
544
  createCompletionTracker: () => createCompletionTracker,
560
545
  observeAgentStatus: () => observeAgentStatus,
@@ -608,23 +593,70 @@ async function startHerdrTask({
608
593
  argv,
609
594
  env,
610
595
  workspaceId,
611
- herdr
596
+ herdr,
597
+ timing
612
598
  }) {
613
599
  const mod = herdr ?? await loadHerdr();
614
- const { tabId, paneId: shellPaneId } = await mod.tabCreate({ label, cwd, workspaceId, env });
615
- let paneId;
600
+ const { tabId, paneId } = await mod.tabCreate({ label, cwd, workspaceId, env });
616
601
  try {
617
- ({ paneId } = await mod.agentStart({ name: label, cwd, argv, env, workspaceId, tabId }));
602
+ const ready = await waitForPaneReady(paneId, mod, {
603
+ timeoutMs: timing?.paneReadyTimeoutMs,
604
+ pollIntervalMs: timing?.paneReadyPollIntervalMs
605
+ });
606
+ if (!ready) {
607
+ console.warn(`[herdr-runner] pane ${paneId} produced no prompt before the timeout, launching anyway`);
608
+ }
609
+ await mod.launchAgentInPane(paneId, argv);
610
+ const detected = await waitForAgentDetected(paneId, mod, {
611
+ timeoutMs: timing?.agentDetectTimeoutMs,
612
+ pollIntervalMs: timing?.agentDetectPollIntervalMs
613
+ });
614
+ if (!detected) {
615
+ throw new Error(
616
+ `claude was not detected in pane ${paneId} after launch (it may have failed to start; e.g. a skill preamble command failed)`
617
+ );
618
+ }
618
619
  } catch (err) {
619
620
  await mod.tabClose(tabId).catch(() => {
620
621
  });
621
622
  throw err;
622
623
  }
623
- await mod.paneClose(shellPaneId).catch((err) => {
624
- console.error(`[herdr-runner] failed to close the placeholder shell pane ${shellPaneId}: ${err}`);
625
- });
626
624
  return { paneId, tabId };
627
625
  }
626
+ async function waitForPaneReady(paneId, mod, options) {
627
+ const timeoutMs = options?.timeoutMs ?? PANE_READY_TIMEOUT_MS;
628
+ const pollIntervalMs = options?.pollIntervalMs ?? PANE_READY_POLL_INTERVAL_MS;
629
+ const deadline = Date.now() + timeoutMs;
630
+ for (; ; ) {
631
+ let content = "";
632
+ try {
633
+ content = await mod.paneRead(paneId, { source: "visible" });
634
+ } catch (err) {
635
+ console.error(`[herdr-runner] failed to read pane ${paneId} while waiting for its prompt: ${err}`);
636
+ }
637
+ if (content.trim() !== "") return true;
638
+ if (Date.now() >= deadline) return false;
639
+ await sleep(pollIntervalMs);
640
+ }
641
+ }
642
+ async function waitForAgentDetected(paneId, mod, options) {
643
+ const timeoutMs = options?.timeoutMs ?? AGENT_DETECT_TIMEOUT_MS;
644
+ const pollIntervalMs = options?.pollIntervalMs ?? AGENT_DETECT_POLL_INTERVAL_MS;
645
+ const deadline = Date.now() + timeoutMs;
646
+ for (; ; ) {
647
+ try {
648
+ await mod.agentGet(paneId);
649
+ return true;
650
+ } catch (err) {
651
+ if (err instanceof mod.HerdrError && err.code === "pane_not_found") return false;
652
+ if (!(err instanceof mod.HerdrError && err.code === "agent_not_found")) {
653
+ console.error(`[herdr-runner] unexpected error while waiting for agent detection on pane ${paneId}: ${err}`);
654
+ }
655
+ }
656
+ if (Date.now() >= deadline) return false;
657
+ await sleep(pollIntervalMs);
658
+ }
659
+ }
628
660
  async function waitForHerdrTask(paneId, options) {
629
661
  const mod = options?.herdr ?? await loadHerdr();
630
662
  const pollIntervalMs = options?.pollIntervalMs ?? AGENT_POLL_INTERVAL_MS;
@@ -640,10 +672,10 @@ async function waitForHerdrTask(paneId, options) {
640
672
  status = agent.agentStatus;
641
673
  if (agent.sessionId) sessionId = agent.sessionId;
642
674
  } catch (err) {
643
- if (err instanceof mod.HerdrError && err.code === "pane_not_found") {
675
+ if (err instanceof mod.HerdrError && (err.code === "pane_not_found" || err.code === "agent_not_found")) {
644
676
  return {
645
677
  status: "failed",
646
- output: "[worker] the claude pane disappeared before the task completed (claude died or the tab was closed)"
678
+ output: "[worker] the claude agent disappeared before the task completed (claude died or the tab was closed)"
647
679
  };
648
680
  }
649
681
  console.error(`[herdr-runner] failed to read agent status for pane ${paneId}: ${err}`);
@@ -672,15 +704,17 @@ async function readPaneOutput(paneId, mod) {
672
704
  return "";
673
705
  }
674
706
  }
675
- async function waitForPaneGone(paneId, mod, options) {
707
+ async function waitForAgentGone(paneId, mod, options) {
676
708
  const timeoutMs = options?.timeoutMs ?? CLAUDE_EXIT_TIMEOUT_MS;
677
709
  const pollIntervalMs = options?.pollIntervalMs ?? CLAUDE_EXIT_POLL_INTERVAL_MS;
678
710
  const deadline = Date.now() + timeoutMs;
679
711
  for (; ; ) {
680
712
  try {
681
- await mod.paneGet(paneId);
713
+ await mod.agentGet(paneId);
682
714
  } catch (err) {
683
- if (err instanceof mod.HerdrError && err.code === "pane_not_found") return true;
715
+ if (err instanceof mod.HerdrError && (err.code === "agent_not_found" || err.code === "pane_not_found")) {
716
+ return true;
717
+ }
684
718
  }
685
719
  if (Date.now() >= deadline) return false;
686
720
  await sleep(pollIntervalMs);
@@ -690,7 +724,7 @@ async function stopHerdrTask(task, herdr, options) {
690
724
  const mod = herdr ?? await loadHerdr();
691
725
  try {
692
726
  await mod.paneSendKeys(task.paneId, "ctrl+c", "ctrl+c");
693
- const exited = await waitForPaneGone(task.paneId, mod, {
727
+ const exited = await waitForAgentGone(task.paneId, mod, {
694
728
  timeoutMs: options?.exitTimeoutMs,
695
729
  pollIntervalMs: options?.exitPollIntervalMs
696
730
  });
@@ -712,7 +746,7 @@ async function stopHerdrTask(task, herdr, options) {
712
746
  function sleep(ms) {
713
747
  return new Promise((resolve3) => setTimeout(resolve3, ms));
714
748
  }
715
- var AGENT_POLL_INTERVAL_MS, PANE_OUTPUT_LINES, CLAUDE_EXIT_TIMEOUT_MS, CLAUDE_EXIT_POLL_INTERVAL_MS;
749
+ var AGENT_POLL_INTERVAL_MS, PANE_OUTPUT_LINES, PANE_READY_TIMEOUT_MS, PANE_READY_POLL_INTERVAL_MS, AGENT_DETECT_TIMEOUT_MS, AGENT_DETECT_POLL_INTERVAL_MS, CLAUDE_EXIT_TIMEOUT_MS, CLAUDE_EXIT_POLL_INTERVAL_MS;
716
750
  var init_herdr_runner = __esm({
717
751
  "src/herdr-runner.ts"() {
718
752
  "use strict";
@@ -720,6 +754,10 @@ var init_herdr_runner = __esm({
720
754
  init_transcript();
721
755
  AGENT_POLL_INTERVAL_MS = 3 * 1e3;
722
756
  PANE_OUTPUT_LINES = 300;
757
+ PANE_READY_TIMEOUT_MS = 30 * 1e3;
758
+ PANE_READY_POLL_INTERVAL_MS = 200;
759
+ AGENT_DETECT_TIMEOUT_MS = 120 * 1e3;
760
+ AGENT_DETECT_POLL_INTERVAL_MS = 500;
723
761
  CLAUDE_EXIT_TIMEOUT_MS = 15 * 1e3;
724
762
  CLAUDE_EXIT_POLL_INTERVAL_MS = 200;
725
763
  }
@@ -772,8 +810,8 @@ var init_run_command = __esm({
772
810
  var dispatcher_exports = {};
773
811
  __export(dispatcher_exports, {
774
812
  LABEL_PREFIX: () => LABEL_PREFIX,
775
- PANE_READY_POLL_INTERVAL_MS: () => PANE_READY_POLL_INTERVAL_MS,
776
- PANE_READY_TIMEOUT_MS: () => PANE_READY_TIMEOUT_MS,
813
+ PANE_READY_POLL_INTERVAL_MS: () => PANE_READY_POLL_INTERVAL_MS2,
814
+ PANE_READY_TIMEOUT_MS: () => PANE_READY_TIMEOUT_MS2,
777
815
  POLL_INTERVAL_MS: () => POLL_INTERVAL_MS,
778
816
  SEND_MAX_ATTEMPTS: () => SEND_MAX_ATTEMPTS,
779
817
  SHUTDOWN_RETRY_TIMEOUT_MS: () => SHUTDOWN_RETRY_TIMEOUT_MS,
@@ -793,7 +831,7 @@ __export(dispatcher_exports, {
793
831
  runDispatcher: () => runDispatcher,
794
832
  shutdownDispatcher: () => shutdownDispatcher,
795
833
  startWorkerInPane: () => startWorkerInPane,
796
- waitForPaneReady: () => waitForPaneReady,
834
+ waitForPaneReady: () => waitForPaneReady2,
797
835
  waitForWorkerStartup: () => waitForWorkerStartup,
798
836
  workspaceLabelFor: () => workspaceLabelFor
799
837
  });
@@ -834,9 +872,9 @@ function isWorkerProcess(process2) {
834
872
  function isShellProcess(process2) {
835
873
  return SHELL_NAME_PATTERN.test(process2.name ?? "") || SHELL_NAME_PATTERN.test(process2.cmdline ?? "");
836
874
  }
837
- async function waitForPaneReady(paneId, herdr, options) {
838
- const timeoutMs = options?.timeoutMs ?? PANE_READY_TIMEOUT_MS;
839
- const pollIntervalMs = options?.pollIntervalMs ?? PANE_READY_POLL_INTERVAL_MS;
875
+ async function waitForPaneReady2(paneId, herdr, options) {
876
+ const timeoutMs = options?.timeoutMs ?? PANE_READY_TIMEOUT_MS2;
877
+ const pollIntervalMs = options?.pollIntervalMs ?? PANE_READY_POLL_INTERVAL_MS2;
840
878
  const deadline = Date.now() + timeoutMs;
841
879
  for (; ; ) {
842
880
  const content = await herdr.paneRead(paneId, { source: "visible" });
@@ -860,7 +898,7 @@ async function waitForWorkerStartup(paneId, herdr, options) {
860
898
  async function startWorkerInPane(paneId, forwardedCommand, herdr, options) {
861
899
  const maxAttempts = options?.sendMaxAttempts ?? SEND_MAX_ATTEMPTS;
862
900
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
863
- const ready = await waitForPaneReady(paneId, herdr, {
901
+ const ready = await waitForPaneReady2(paneId, herdr, {
864
902
  timeoutMs: options?.paneReadyTimeoutMs,
865
903
  pollIntervalMs: options?.paneReadyPollIntervalMs
866
904
  });
@@ -1162,15 +1200,15 @@ function createDispatcherShutdownHandler(shutdown2) {
1162
1200
  };
1163
1201
  return { handle, isShuttingDown: () => shuttingDown2 };
1164
1202
  }
1165
- var getDisplayWidth2, truncateToWidth2, padToWidth2, POLL_INTERVAL_MS, SHUTDOWN_TIMEOUT_MS, PANE_READY_TIMEOUT_MS, PANE_READY_POLL_INTERVAL_MS, WORKER_STARTUP_TIMEOUT_MS, WORKER_STARTUP_POLL_INTERVAL_MS, SEND_MAX_ATTEMPTS, LABEL_PREFIX, SHELL_NAME_PATTERN, SHUTDOWN_RETRY_TIMEOUT_MS, CTRL_C_KEY, shutdownPromise;
1203
+ var getDisplayWidth2, truncateToWidth2, padToWidth2, POLL_INTERVAL_MS, SHUTDOWN_TIMEOUT_MS, PANE_READY_TIMEOUT_MS2, PANE_READY_POLL_INTERVAL_MS2, WORKER_STARTUP_TIMEOUT_MS, WORKER_STARTUP_POLL_INTERVAL_MS, SEND_MAX_ATTEMPTS, LABEL_PREFIX, SHELL_NAME_PATTERN, SHUTDOWN_RETRY_TIMEOUT_MS, CTRL_C_KEY, shutdownPromise;
1166
1204
  var init_dispatcher = __esm({
1167
1205
  async "src/dispatcher.ts"() {
1168
1206
  "use strict";
1169
1207
  ({ getDisplayWidth: getDisplayWidth2, truncateToWidth: truncateToWidth2, padToWidth: padToWidth2 } = await loadTable());
1170
1208
  POLL_INTERVAL_MS = 7 * 1e3;
1171
1209
  SHUTDOWN_TIMEOUT_MS = 10 * 60 * 1e3;
1172
- PANE_READY_TIMEOUT_MS = 30 * 1e3;
1173
- PANE_READY_POLL_INTERVAL_MS = 200;
1210
+ PANE_READY_TIMEOUT_MS2 = 30 * 1e3;
1211
+ PANE_READY_POLL_INTERVAL_MS2 = 200;
1174
1212
  WORKER_STARTUP_TIMEOUT_MS = 30 * 1e3;
1175
1213
  WORKER_STARTUP_POLL_INTERVAL_MS = 500;
1176
1214
  SEND_MAX_ATTEMPTS = 3;
@@ -1291,6 +1329,28 @@ async function findPrNumberByHeadRef(headRefName, state = "open") {
1291
1329
  const prs = JSON.parse(output);
1292
1330
  return prs.length > 0 ? prs[0].number : null;
1293
1331
  }
1332
+ async function findPrStateByHeadRef(headRefName) {
1333
+ const output = await execGh([
1334
+ "pr",
1335
+ "list",
1336
+ "--state",
1337
+ "all",
1338
+ "--head",
1339
+ headRefName,
1340
+ "--json",
1341
+ "number,state,mergedAt",
1342
+ "--limit",
1343
+ "1"
1344
+ ]);
1345
+ const prs = JSON.parse(output);
1346
+ if (prs.length === 0) return null;
1347
+ return { number: prs[0].number, state: prs[0].state, mergedAt: prs[0].mergedAt ?? null };
1348
+ }
1349
+ async function getIssueBody(issueNumber) {
1350
+ const output = await execGh(["issue", "view", String(issueNumber), "--json", "body"]);
1351
+ const parsed = JSON.parse(output);
1352
+ return typeof parsed?.body === "string" ? parsed.body : "";
1353
+ }
1294
1354
  async function getIssueState(issueNumber) {
1295
1355
  const output = await execGh(["issue", "view", String(issueNumber), "--json", "state"]);
1296
1356
  const parsed = JSON.parse(output);
@@ -1531,7 +1591,11 @@ function buildClaudeEnv(mode) {
1531
1591
 
1532
1592
  // src/config.ts
1533
1593
  import { readFileSync } from "node:fs";
1534
- import { join } from "node:path";
1594
+ import { isAbsolute, join, normalize, sep as SEP } from "node:path";
1595
+ var DEFAULT_UI_DESIGN_CONFIG = {
1596
+ enabled: false,
1597
+ designDir: "designs"
1598
+ };
1535
1599
  var DEFAULT_WORKER_CONFIG = {
1536
1600
  skill: "",
1537
1601
  model: "sonnet",
@@ -1620,10 +1684,27 @@ var WORKER_DEFAULTS = {
1620
1684
  pollingIntervalSeconds: 300,
1621
1685
  cooldownSeconds: 0,
1622
1686
  maxConcurrentTasks: 1
1687
+ },
1688
+ "create-ui-design": {
1689
+ skill: "/claude-task-worker:create-ui-design",
1690
+ model: "sonnet",
1691
+ effort: "high",
1692
+ pollingIntervalSeconds: 60,
1693
+ cooldownSeconds: 0,
1694
+ maxConcurrentTasks: 1
1695
+ },
1696
+ "apply-ui-design": {
1697
+ skill: "/claude-task-worker:apply-ui-design",
1698
+ model: "sonnet",
1699
+ effort: "high",
1700
+ pollingIntervalSeconds: 300,
1701
+ cooldownSeconds: 0,
1702
+ maxConcurrentTasks: 1
1623
1703
  }
1624
1704
  };
1625
1705
  var DEFAULT_CONFIG = {
1626
1706
  fixReviewPointCallbackCommentMessage: "",
1707
+ uiDesign: { ...DEFAULT_UI_DESIGN_CONFIG },
1627
1708
  workers: {}
1628
1709
  };
1629
1710
  var CONFIG_PATH = join(process.cwd(), "claude-task-worker.json");
@@ -1691,6 +1772,35 @@ function parseWorkerEntry(name, val) {
1691
1772
  }
1692
1773
  return result;
1693
1774
  }
1775
+ function parseUiDesignEntry(val) {
1776
+ const result = { ...DEFAULT_UI_DESIGN_CONFIG };
1777
+ if (typeof val !== "object" || val === null || Array.isArray(val)) {
1778
+ console.warn(`[config] invalid uiDesign: expected object, using defaults`);
1779
+ return result;
1780
+ }
1781
+ const entry = val;
1782
+ if ("enabled" in entry) {
1783
+ if (typeof entry.enabled === "boolean") {
1784
+ result.enabled = entry.enabled;
1785
+ } else {
1786
+ console.warn(
1787
+ `[config] invalid uiDesign.enabled: ${String(entry.enabled)}, using default ${DEFAULT_UI_DESIGN_CONFIG.enabled}`
1788
+ );
1789
+ }
1790
+ }
1791
+ if ("designDir" in entry) {
1792
+ const normalized = typeof entry.designDir === "string" && entry.designDir.length > 0 ? normalize(entry.designDir) : null;
1793
+ const isContained = normalized !== null && !isAbsolute(normalized) && normalized !== ".." && !normalized.startsWith(`..${SEP}`);
1794
+ if (isContained) {
1795
+ result.designDir = normalized;
1796
+ } else {
1797
+ console.warn(
1798
+ `[config] invalid uiDesign.designDir: ${String(entry.designDir)}, using default ${DEFAULT_UI_DESIGN_CONFIG.designDir}`
1799
+ );
1800
+ }
1801
+ }
1802
+ return result;
1803
+ }
1694
1804
  function loadConfig() {
1695
1805
  const configPath = CONFIG_PATH;
1696
1806
  let raw;
@@ -1698,17 +1808,20 @@ function loadConfig() {
1698
1808
  raw = JSON.parse(readFileSync(configPath, "utf-8"));
1699
1809
  } catch (err) {
1700
1810
  if (err.code === "ENOENT") {
1701
- return { ...DEFAULT_CONFIG, workers: {} };
1811
+ return { ...DEFAULT_CONFIG, uiDesign: { ...DEFAULT_UI_DESIGN_CONFIG }, workers: {} };
1702
1812
  }
1703
1813
  throw err;
1704
1814
  }
1705
- const result = { ...DEFAULT_CONFIG, workers: {} };
1815
+ const result = { ...DEFAULT_CONFIG, uiDesign: { ...DEFAULT_UI_DESIGN_CONFIG }, workers: {} };
1706
1816
  if ("fixReviewPointCallbackCommentMessage" in raw) {
1707
1817
  const val = raw["fixReviewPointCallbackCommentMessage"];
1708
1818
  if (typeof val === "string") {
1709
1819
  result.fixReviewPointCallbackCommentMessage = val;
1710
1820
  }
1711
1821
  }
1822
+ if ("uiDesign" in raw) {
1823
+ result.uiDesign = parseUiDesignEntry(raw["uiDesign"]);
1824
+ }
1712
1825
  if ("workers" in raw) {
1713
1826
  const workers = raw["workers"];
1714
1827
  if (typeof workers !== "object" || workers === null || Array.isArray(workers)) {
@@ -1726,6 +1839,14 @@ function getWorkerConfig(workerName) {
1726
1839
  const config = loadConfig();
1727
1840
  return config.workers[workerName] ?? { ...defaultsFor(workerName) };
1728
1841
  }
1842
+ function getUiDesignConfig() {
1843
+ try {
1844
+ return loadConfig().uiDesign;
1845
+ } catch (err) {
1846
+ console.warn(`[config] failed to load uiDesign config, using defaults: ${err}`);
1847
+ return { ...DEFAULT_UI_DESIGN_CONFIG };
1848
+ }
1849
+ }
1729
1850
 
1730
1851
  // src/git.ts
1731
1852
  import { execSync, execFile } from "node:child_process";
@@ -1779,7 +1900,7 @@ init_task_result();
1779
1900
  // src/user-config.ts
1780
1901
  import { readFileSync as readFileSync2, statSync } from "node:fs";
1781
1902
  import { homedir } from "node:os";
1782
- import { isAbsolute, join as join2, resolve } from "node:path";
1903
+ import { isAbsolute as isAbsolute2, join as join2, resolve } from "node:path";
1783
1904
  var RESERVED_ALL = "all";
1784
1905
  var DEFAULT_RUN_MODE = "default";
1785
1906
  var DEFAULT_HEADROOM = false;
@@ -1876,7 +1997,7 @@ function loadUserConfig() {
1876
1997
  if (name === "__proto__") {
1877
1998
  throw new UserConfigError(`"__proto__" cannot be used as a key in "projects": ${path}`);
1878
1999
  }
1879
- if (typeof value !== "string" || !isAbsolute(value)) {
2000
+ if (typeof value !== "string" || !isAbsolute2(value)) {
1880
2001
  console.warn(`[config] invalid projects.${name}: expected an absolute path, skipping`);
1881
2002
  continue;
1882
2003
  }
@@ -3231,13 +3352,238 @@ var epicIssueWorker = (opts = {}) => createIssuePollingWorker({
3231
3352
  }
3232
3353
  })();
3233
3354
 
3355
+ // src/workers/ui-design.ts
3356
+ var DESIGN_REFERENCE_HEADING = "## UI\u30C7\u30B6\u30A4\u30F3";
3357
+ function designBranchName(issueNumber) {
3358
+ return `cc-ui-design-${issueNumber}`;
3359
+ }
3360
+ var DESIGN_REFERENCE_HEADING_LINE = /^## UIデザイン[ \t]*$/m;
3361
+ var DESIGN_FILE_LINE = /^-\s*デザインファイル:\s*`([^`]+)`/m;
3362
+ function extractDesignSection(body) {
3363
+ const headingMatch = DESIGN_REFERENCE_HEADING_LINE.exec(body);
3364
+ if (headingMatch === null) return null;
3365
+ const rest = body.slice(headingMatch.index + headingMatch[0].length);
3366
+ const nextHeadingMatch = /^## /m.exec(rest);
3367
+ return nextHeadingMatch === null ? rest : rest.slice(0, nextHeadingMatch.index);
3368
+ }
3369
+ function extractDesignFilePath(body) {
3370
+ const section = extractDesignSection(body);
3371
+ if (section === null) return null;
3372
+ const match = DESIGN_FILE_LINE.exec(section);
3373
+ if (match === null) return null;
3374
+ const path = match[1].trim();
3375
+ if (path.length === 0 || path.includes("<") || path.includes(">") || !path.endsWith(".pen")) return null;
3376
+ return path;
3377
+ }
3378
+ function classifyDesignPr(pr) {
3379
+ if (pr === null) return "needs-human";
3380
+ if (pr.state === "MERGED" || pr.mergedAt !== null) return "proceed";
3381
+ if (pr.state === "OPEN") return "wait";
3382
+ return "needs-human";
3383
+ }
3384
+ function designPrMissingComment(issueNumber) {
3385
+ return [
3386
+ "## \u30C7\u30B6\u30A4\u30F3PR\u3092\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\uFF08\u8981\u4EBA\u624B\u78BA\u8A8D\uFF09",
3387
+ `apply-ui-design \u306F\u30C7\u30B6\u30A4\u30F3\u30D6\u30E9\u30F3\u30C1 \`${designBranchName(issueNumber)}\` \u3092 head \u3068\u3059\u308BPR\u3092\u5F85\u3063\u3066\u3044\u307E\u3059\u304C\u3001\u305D\u306EPR\u304C\u898B\u3064\u304B\u3089\u306A\u3044\u304B\u3001\u30DE\u30FC\u30B8\u3055\u308C\u305A\u30AF\u30ED\u30FC\u30BA\u3055\u308C\u3066\u3044\u307E\u3059\u3002`,
3388
+ "",
3389
+ "## \u72B6\u614B\u306E\u78BA\u8A8D",
3390
+ `- \u30C7\u30B6\u30A4\u30F3PR\u304C\u5374\u4E0B\u3055\u308C\u305F\u5834\u5408: \u3053\u306EIssue\u3092\u30C7\u30B6\u30A4\u30F3\u306A\u3057\u3067\u9032\u3081\u308B\u304B\u3001\u30C7\u30B6\u30A4\u30F3\u3092\u3084\u308A\u76F4\u3059\u304B\u3092\u6C7A\u3081\u3066\u304F\u3060\u3055\u3044`,
3391
+ `- \u30C7\u30B6\u30A4\u30F3PR\u304C\u305D\u3082\u305D\u3082\u4F5C\u3089\u308C\u3066\u3044\u306A\u3044\u5834\u5408: \u30EA\u30E2\u30FC\u30C8\u30D6\u30E9\u30F3\u30C1 \`${designBranchName(issueNumber)}\` \u306E\u6709\u7121\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044`,
3392
+ "",
3393
+ "## \u5BFE\u5FDC\u5F8C\u306E\u9032\u3081\u65B9",
3394
+ "- \u30C7\u30B6\u30A4\u30F3\u3092\u3084\u308A\u76F4\u3059\u5834\u5408: `cc-need-human-check` / `cc-ui-design-pr-created` \u30E9\u30D9\u30EB\u3092\u5916\u3057\u3001`cc-create-ui-design` \u30E9\u30D9\u30EB\u3092\u4ED8\u3051\u76F4\u3057\u3066\u304F\u3060\u3055\u3044",
3395
+ "- \u30C7\u30B6\u30A4\u30F3\u306A\u3057\u3067\u5B9F\u88C5\u3078\u9032\u3081\u308B\u5834\u5408: `cc-need-human-check` / `cc-ui-design-pr-created` \u30E9\u30D9\u30EB\u3092\u5916\u3057\u3001`cc-exec-issue` \u30E9\u30D9\u30EB\u306E\u307F\u4ED8\u3051\u3066\u304F\u3060\u3055\u3044"
3396
+ ].join("\n");
3397
+ }
3398
+ function designPrNotCreatedComment(issueNumber) {
3399
+ return [
3400
+ "## \u30C7\u30B6\u30A4\u30F3PR\u672A\u4F5C\u6210\u306E\u307E\u307E\u81EA\u52D5\u5B9F\u884C\u304C\u7D42\u4E86\u3057\u307E\u3057\u305F\uFF08\u8981\u4EBA\u624B\u78BA\u8A8D\uFF09",
3401
+ `create-ui-design \u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306F\u6B63\u5E38\u7D42\u4E86\uFF08exit 0\uFF09\u3057\u307E\u3057\u305F\u304C\u3001\u30C7\u30B6\u30A4\u30F3\u30D6\u30E9\u30F3\u30C1 \`${designBranchName(issueNumber)}\` \u3092 head \u3068\u3059\u308BPR\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F\u3002PR\u4F5C\u6210\u524D\u306B\u30BB\u30C3\u30B7\u30E7\u30F3\u304C\u7D42\u4E86\u3057\u305F\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002`,
3402
+ "",
3403
+ "## \u72B6\u614B\u306E\u78BA\u8A8D",
3404
+ `- \u5909\u66F4\u304C push \u6E08\u307F\u306E\u5834\u5408\u306F\u30EA\u30E2\u30FC\u30C8\u30D6\u30E9\u30F3\u30C1 \`${designBranchName(issueNumber)}\` \u304C\u6B8B\u3063\u3066\u3044\u307E\u3059\u3002\u5185\u5BB9\u3092\u78BA\u8A8D\u3057\u3001\u5FC5\u8981\u306A\u3089\u624B\u52D5\u3067\u30C7\u30B6\u30A4\u30F3PR\u3092\u4F5C\u6210\u3057\u3066\u304F\u3060\u3055\u3044`,
3405
+ "",
3406
+ "## \u5BFE\u5FDC\u5F8C\u306E\u9032\u3081\u65B9",
3407
+ "- \u81EA\u52D5\u5B9F\u884C\u3092\u3084\u308A\u76F4\u3059\u5834\u5408: `cc-need-human-check` \u30E9\u30D9\u30EB\u3092\u5916\u3057\u3001`cc-create-ui-design` \u30E9\u30D9\u30EB\u3092\u4ED8\u3051\u76F4\u3057\u3066\u304F\u3060\u3055\u3044",
3408
+ "- \u624B\u52D5\u3067\u30C7\u30B6\u30A4\u30F3PR\u3092\u4F5C\u6210\u3057\u305F\u5834\u5408: `cc-need-human-check` \u30E9\u30D9\u30EB\u3092\u5916\u3057\u3001`cc-ui-design-pr-created` \u30E9\u30D9\u30EB\u3092\u4ED8\u3051\u3066\u304F\u3060\u3055\u3044",
3409
+ "- \u30C7\u30B6\u30A4\u30F3\u3092\u8AE6\u3081\u3066\u305D\u306E\u307E\u307E\u5B9F\u88C5\u3078\u9032\u3081\u308B\u5834\u5408: `cc-need-human-check` \u30E9\u30D9\u30EB\u3092\u5916\u3057\u3001`cc-exec-issue` \u30E9\u30D9\u30EB\u306E\u307F\u4ED8\u3051\u3066\u304F\u3060\u3055\u3044"
3410
+ ].join("\n");
3411
+ }
3412
+ function designReferenceMissingComment(issueNumber) {
3413
+ return [
3414
+ "## \u30C7\u30B6\u30A4\u30F3\u53C2\u7167\u306E\u66F8\u304D\u623B\u3057\u3092\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\uFF08\u8981\u4EBA\u624B\u78BA\u8A8D\uFF09",
3415
+ `apply-ui-design \u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306F\u6B63\u5E38\u7D42\u4E86\uFF08exit 0\uFF09\u3057\u307E\u3057\u305F\u304C\u3001Issue #${issueNumber} \u306E description \u306B \`${DESIGN_REFERENCE_HEADING}\` \u30BB\u30AF\u30B7\u30E7\u30F3\uFF08\`.pen\` \u306E\u30D1\u30B9\u3092\u542B\u3080\uFF09\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F\u3002`,
3416
+ "",
3417
+ "## \u306A\u305C\u6B62\u3081\u3066\u3044\u308B\u304B",
3418
+ "- \u30C7\u30B6\u30A4\u30F3\u53C2\u7167\u304C description \u306B\u7121\u3044\u3068\u3001`exec-issue` \u306F\u30C7\u30B6\u30A4\u30F3\u3092\u5165\u529B\u306B\u3067\u304D\u305A\u3001\u5408\u610F\u6E08\u307F\u306E\u30C7\u30B6\u30A4\u30F3\u3068\u7121\u95A2\u4FC2\u306A\u5B9F\u88C5\u306B\u306A\u308A\u307E\u3059",
3419
+ "",
3420
+ "## \u5BFE\u5FDC\u5F8C\u306E\u9032\u3081\u65B9",
3421
+ "- \u81EA\u52D5\u5B9F\u884C\u3092\u3084\u308A\u76F4\u3059\u5834\u5408: `cc-need-human-check` \u30E9\u30D9\u30EB\u3092\u5916\u3057\u3001`cc-ui-design-pr-created` \u30E9\u30D9\u30EB\u3092\u4ED8\u3051\u76F4\u3057\u3066\u304F\u3060\u3055\u3044",
3422
+ "- \u624B\u52D5\u3067 description \u306B\u53C2\u7167\u3092\u8FFD\u8A18\u3057\u305F\u5834\u5408: `cc-need-human-check` \u30E9\u30D9\u30EB\u3092\u5916\u3057\u3001`cc-ui-design-ready` \u3068 `cc-exec-issue` \u30E9\u30D9\u30EB\u3092\u4ED8\u3051\u3066\u304F\u3060\u3055\u3044"
3423
+ ].join("\n");
3424
+ }
3425
+
3426
+ // src/workers/create-ui-design.ts
3427
+ function designPrLabelingFailedComment(issueNumber, prNumber, error) {
3428
+ const message = error instanceof Error ? error.message : String(error);
3429
+ return [
3430
+ "## \u30C7\u30B6\u30A4\u30F3PR\u3078\u306E\u30E9\u30D9\u30EB\u4ED8\u4E0E\u306B\u5931\u6557\u3057\u307E\u3057\u305F\uFF08\u8981\u4EBA\u624B\u78BA\u8A8D\uFF09",
3431
+ `\u30C7\u30B6\u30A4\u30F3PR #${prNumber}\uFF08\u30D6\u30E9\u30F3\u30C1 \`${designBranchName(issueNumber)}\`\uFF09\u306F\u4F5C\u6210\u3055\u308C\u307E\u3057\u305F\u304C\u3001\u5F8C\u7D9A\u30E9\u30D9\u30EB\uFF08\`cc-ui-design\` / \`cc-triage-scope\` / \`cc-ui-design-pr-created\`\uFF09\u306E\u4ED8\u4E0E\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002`,
3432
+ "",
3433
+ "## \u8D77\u3053\u308A\u3046\u308B\u539F\u56E0",
3434
+ "- \u672C\u30EF\u30FC\u30AF\u30D5\u30ED\u30FC\u8FFD\u52A0\u6642\u306E\u30E9\u30D9\u30EB\u304C\u672A\u4F5C\u6210\u306E\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002`claude-task-worker init` \u3092\u518D\u5B9F\u884C\u3057\u3066\u30E9\u30D9\u30EB\u3092\u4F5C\u6210\u3057\u3066\u304F\u3060\u3055\u3044",
3435
+ "",
3436
+ "## \u30A8\u30E9\u30FC\u5185\u5BB9",
3437
+ "```",
3438
+ message,
3439
+ "```",
3440
+ "",
3441
+ "## \u5BFE\u5FDC\u5F8C\u306E\u9032\u3081\u65B9",
3442
+ "- \u30E9\u30D9\u30EB\u4F5C\u6210\u5F8C\u306B\u3084\u308A\u76F4\u3059\u5834\u5408: `cc-need-human-check` \u30E9\u30D9\u30EB\u3092\u5916\u3057\u3001`cc-ui-design`\u30FB`cc-triage-scope`\uFF08PR\u5074\uFF09\u3068 `cc-ui-design-pr-created`\uFF08Issue\u5074\uFF09\u3092\u624B\u52D5\u3067\u4ED8\u3051\u3066\u304F\u3060\u3055\u3044"
3443
+ ].join("\n");
3444
+ }
3445
+ var createUiDesignWorker = async (opts = {}) => {
3446
+ if (!getUiDesignConfig().enabled) {
3447
+ console.log("[create-ui-design] uiDesign.enabled is false, skipping");
3448
+ return;
3449
+ }
3450
+ await createIssuePollingWorker({
3451
+ name: "create-ui-design",
3452
+ command: "/claude-task-worker:create-ui-design",
3453
+ triggerLabels: ["cc-create-ui-design"],
3454
+ excludeLabels: ["cc-ui-design-pr-created", "cc-ui-design-ready", "cc-exec-issue", "cc-pr-created"],
3455
+ epicFilters: opts.epicFilters,
3456
+ labelFilters: opts.labelFilters,
3457
+ onCompleted: async (issueNumber) => {
3458
+ if (await hasLabel("issue", issueNumber, "cc-need-human-check")) {
3459
+ console.log(`[create-ui-design] #${issueNumber}: cc-need-human-check present, skip cc-ui-design-pr-created`);
3460
+ return false;
3461
+ }
3462
+ if (await hasLabel("issue", issueNumber, "cc-ui-design-ready")) {
3463
+ console.log(`[create-ui-design] #${issueNumber}: design not needed (cc-ui-design-ready), completing`);
3464
+ return;
3465
+ }
3466
+ const branch = designBranchName(issueNumber);
3467
+ const prNumber = await findPrNumberByHeadRef(branch, "open");
3468
+ if (prNumber === null) {
3469
+ console.error(
3470
+ `[create-ui-design] #${issueNumber}: session exited without an open design PR (branch: ${branch}); marking cc-need-human-check`
3471
+ );
3472
+ await addLabel("issue", issueNumber, "cc-need-human-check");
3473
+ await commentOnIssue(issueNumber, designPrNotCreatedComment(issueNumber)).catch(
3474
+ (err) => console.error(`[create-ui-design] commentOnIssue failed for #${issueNumber}: ${err}`)
3475
+ );
3476
+ return false;
3477
+ }
3478
+ try {
3479
+ await addLabel("pr", prNumber, "cc-ui-design");
3480
+ await addLabel("pr", prNumber, "cc-triage-scope");
3481
+ await addLabel("issue", issueNumber, "cc-ui-design-pr-created");
3482
+ } catch (err) {
3483
+ console.error(`[create-ui-design] #${issueNumber}: labeling design PR #${prNumber} failed: ${err}`);
3484
+ await addLabel("issue", issueNumber, "cc-need-human-check");
3485
+ await commentOnIssue(issueNumber, designPrLabelingFailedComment(issueNumber, prNumber, err)).catch(
3486
+ (commentErr) => console.error(`[create-ui-design] commentOnIssue failed for #${issueNumber}: ${commentErr}`)
3487
+ );
3488
+ return false;
3489
+ }
3490
+ }
3491
+ })();
3492
+ };
3493
+
3494
+ // src/workers/apply-ui-design.ts
3495
+ import { existsSync as existsSync2 } from "node:fs";
3496
+ import { join as join6 } from "node:path";
3497
+ function designFileExistsInWorktree(worktreeId, designFilePath) {
3498
+ return existsSync2(join6(getWorktreePath(worktreeId), designFilePath));
3499
+ }
3500
+ async function markDesignReferenceMissing(issueNumber, logMessage) {
3501
+ console.error(logMessage);
3502
+ await addLabel("issue", issueNumber, "cc-need-human-check").catch(
3503
+ (err) => console.error(`[apply-ui-design] addLabel cc-need-human-check failed for #${issueNumber}: ${err}`)
3504
+ );
3505
+ await commentOnIssue(issueNumber, designReferenceMissingComment(issueNumber)).catch(
3506
+ (err) => console.error(`[apply-ui-design] commentOnIssue failed for #${issueNumber}: ${err}`)
3507
+ );
3508
+ }
3509
+ var applyUiDesignWorker = async (opts = {}) => {
3510
+ if (!getUiDesignConfig().enabled) {
3511
+ console.log("[apply-ui-design] uiDesign.enabled is false, skipping");
3512
+ return;
3513
+ }
3514
+ await createIssuePollingWorker({
3515
+ name: "apply-ui-design",
3516
+ command: "/claude-task-worker:apply-ui-design",
3517
+ triggerLabels: ["cc-ui-design-pr-created"],
3518
+ excludeLabels: ["cc-ui-design-ready", "cc-exec-issue"],
3519
+ epicFilters: opts.epicFilters,
3520
+ labelFilters: opts.labelFilters,
3521
+ preflight: async (issue) => {
3522
+ const branch = designBranchName(issue.number);
3523
+ let pr;
3524
+ try {
3525
+ pr = await findPrStateByHeadRef(branch);
3526
+ } catch (err) {
3527
+ console.error(`[apply-ui-design] #${issue.number}: findPrStateByHeadRef failed for ${branch}: ${err}`);
3528
+ return "skip";
3529
+ }
3530
+ const disposition = classifyDesignPr(pr);
3531
+ if (disposition === "proceed") return "proceed";
3532
+ if (disposition === "wait") {
3533
+ console.log(`[apply-ui-design] #${issue.number}: design PR #${pr?.number} is still open, waiting for merge`);
3534
+ return "skip";
3535
+ }
3536
+ console.error(`[apply-ui-design] #${issue.number}: design PR for ${branch} is missing or closed unmerged`);
3537
+ try {
3538
+ await addLabel("issue", issue.number, "cc-need-human-check");
3539
+ await commentOnIssue(issue.number, designPrMissingComment(issue.number)).catch(
3540
+ (err) => console.error(`[apply-ui-design] commentOnIssue failed for #${issue.number}: ${err}`)
3541
+ );
3542
+ } catch (err) {
3543
+ console.error(`[apply-ui-design] addLabel cc-need-human-check failed for #${issue.number}: ${err}`);
3544
+ }
3545
+ return "skip";
3546
+ },
3547
+ onCompleted: async (issueNumber, worktreeId) => {
3548
+ let body;
3549
+ try {
3550
+ body = await getIssueBody(issueNumber);
3551
+ } catch (err) {
3552
+ console.error(`[apply-ui-design] #${issueNumber}: getIssueBody failed: ${err}`);
3553
+ body = "";
3554
+ }
3555
+ const designFilePath = extractDesignFilePath(body);
3556
+ if (designFilePath === null) {
3557
+ await markDesignReferenceMissing(
3558
+ issueNumber,
3559
+ `[apply-ui-design] #${issueNumber}: session exited without a design reference section; marking cc-need-human-check`
3560
+ );
3561
+ return false;
3562
+ }
3563
+ if (!designFileExistsInWorktree(worktreeId, designFilePath)) {
3564
+ await markDesignReferenceMissing(
3565
+ issueNumber,
3566
+ `[apply-ui-design] #${issueNumber}: design reference points to ${designFilePath}, which does not exist in worktree ${worktreeId}; marking cc-need-human-check`
3567
+ );
3568
+ return false;
3569
+ }
3570
+ await addLabel("issue", issueNumber, "cc-exec-issue").catch(
3571
+ (err) => console.error(`[apply-ui-design] addLabel cc-exec-issue failed for #${issueNumber}: ${err}`)
3572
+ );
3573
+ await addLabel("issue", issueNumber, "cc-ui-design-ready").catch(
3574
+ (err) => console.error(`[apply-ui-design] addLabel cc-ui-design-ready failed for #${issueNumber}: ${err}`)
3575
+ );
3576
+ }
3577
+ })();
3578
+ };
3579
+
3234
3580
  // src/commands/init.ts
3235
3581
  import { mkdir as mkdir2, writeFile as writeFile2, access } from "node:fs/promises";
3236
3582
 
3237
3583
  // src/commands/codegraph.ts
3238
3584
  import { mkdir, readFile, writeFile } from "node:fs/promises";
3239
3585
  import { homedir as homedir5 } from "node:os";
3240
- import { dirname as dirname2, join as join6 } from "node:path";
3586
+ import { dirname as dirname2, join as join7 } from "node:path";
3241
3587
  async function loadRunCommand() {
3242
3588
  return await Promise.resolve().then(() => (init_run_command(), run_command_exports));
3243
3589
  }
@@ -3245,8 +3591,8 @@ var CODEGRAPH_PACKAGE = "@colbymchenry/codegraph";
3245
3591
  var CODEGRAPH_IGNORE_ENTRY = ".codegraph/";
3246
3592
  function globalGitIgnorePath(env = process.env, home = homedir5()) {
3247
3593
  const xdg = env.XDG_CONFIG_HOME?.trim();
3248
- const base = xdg ? xdg : join6(home, ".config");
3249
- return join6(base, "git", "ignore");
3594
+ const base = xdg ? xdg : join7(home, ".config");
3595
+ return join7(base, "git", "ignore");
3250
3596
  }
3251
3597
  function appendIgnoreEntry(current, entry) {
3252
3598
  const bare = entry.replace(/\/$/, "");
@@ -3333,7 +3679,11 @@ var LABELS = [
3333
3679
  { name: "cc-triage-scope", color: "c5def5" },
3334
3680
  { name: "cc-resolve-conflict", color: "fbca04" },
3335
3681
  { name: "cc-epic-issue", color: "8b5cf6" },
3336
- { name: "cc-release-ready", color: "2da44e" }
3682
+ { name: "cc-release-ready", color: "2da44e" },
3683
+ { name: "cc-create-ui-design", color: "ff8ad8" },
3684
+ { name: "cc-ui-design-pr-created", color: "d4a5e8" },
3685
+ { name: "cc-ui-design-ready", color: "bfd4f2" },
3686
+ { name: "cc-ui-design", color: "ec4899" }
3337
3687
  ];
3338
3688
  var ISSUE_TEMPLATE = `name: "[claude-task-worker] Issue\u4F5C\u6210\u4F9D\u983C"
3339
3689
  description: claude-task-worker\u3067GitHub Issue\u3092\u4F5C\u6210\u3059\u308B
@@ -3521,11 +3871,11 @@ async function update() {
3521
3871
 
3522
3872
  // src/commands/version.ts
3523
3873
  import { readFileSync as readFileSync5 } from "node:fs";
3524
- import { dirname as dirname3, join as join7 } from "node:path";
3874
+ import { dirname as dirname3, join as join8 } from "node:path";
3525
3875
  import { fileURLToPath } from "node:url";
3526
3876
  function version() {
3527
3877
  const here = dirname3(fileURLToPath(import.meta.url));
3528
- const pkgPath = join7(here, "..", "package.json");
3878
+ const pkgPath = join8(here, "..", "package.json");
3529
3879
  try {
3530
3880
  const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
3531
3881
  console.log(pkg.version ?? "unknown");
@@ -3589,7 +3939,9 @@ var WORKERS = {
3589
3939
  "triage-pr": triagePrWorker,
3590
3940
  "resolve-conflict": resolveConflictWorker,
3591
3941
  "check-dependabot": checkDependabotWorker,
3592
- "epic-issue": epicIssueWorker
3942
+ "epic-issue": epicIssueWorker,
3943
+ "create-ui-design": createUiDesignWorker,
3944
+ "apply-ui-design": applyUiDesignWorker
3593
3945
  };
3594
3946
  function printUsage() {
3595
3947
  console.log(`Usage: claude-task-worker <command> [--project <name>] [--epic <issue-number>] [--label <label-name>]
@@ -3612,6 +3964,8 @@ Workers:
3612
3964
  resolve-conflict Poll cc-resolve-conflict PRs and run /resolve-conflict
3613
3965
  check-dependabot Poll dependabot PRs every 1 hour
3614
3966
  epic-issue Poll cc-epic-issue issues and create epic PR when all sub-issues are closed
3967
+ create-ui-design Poll cc-create-ui-design issues and create a Pencil design PR (requires uiDesign.enabled)
3968
+ apply-ui-design Poll cc-ui-design-pr-created issues and write the design reference back once the design PR is merged (requires uiDesign.enabled)
3615
3969
  all Poll all workers except triage-created-issue, triage-pr, check-dependabot
3616
3970
  yolo Poll all workers including triage-created-issue, triage-pr, check-dependabot
3617
3971
 
@@ -3806,7 +4160,9 @@ if (hasProjectFilter()) {
3806
4160
  updateIssueWorker({ epicFilters, labelFilters }),
3807
4161
  answerIssueQuestionsWorker({ epicFilters, labelFilters }),
3808
4162
  resolveConflictWorker(),
3809
- epicIssueWorker({ epicFilters, labelFilters })
4163
+ epicIssueWorker({ epicFilters, labelFilters }),
4164
+ createUiDesignWorker({ epicFilters, labelFilters }),
4165
+ applyUiDesignWorker({ epicFilters, labelFilters })
3810
4166
  ]);
3811
4167
  })();
3812
4168
  } else if (workerType === "yolo") {
@@ -3825,7 +4181,9 @@ if (hasProjectFilter()) {
3825
4181
  checkDependabotWorker(),
3826
4182
  triagePrWorker(),
3827
4183
  resolveConflictWorker(),
3828
- epicIssueWorker({ epicFilters, labelFilters })
4184
+ epicIssueWorker({ epicFilters, labelFilters }),
4185
+ createUiDesignWorker({ epicFilters, labelFilters }),
4186
+ applyUiDesignWorker({ epicFilters, labelFilters })
3829
4187
  ]);
3830
4188
  })();
3831
4189
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-task-worker",
3
- "version": "0.34.0",
3
+ "version": "0.36.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",
@@ -15,6 +15,7 @@
15
15
  "test": "node --experimental-strip-types --test \"src/**/*.test.ts\" \"plugin/scripts/**/*.test.mjs\"",
16
16
  "format": "prettier --write .",
17
17
  "format:check": "prettier --check .",
18
+ "version": "node scripts/sync-plugin-version.mjs && git add plugin/.claude-plugin/plugin.json",
18
19
  "prepublishOnly": "npm run build"
19
20
  },
20
21
  "files": [