claude-task-worker 0.33.1 → 0.35.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 +140 -21
- package/dist/index.js +514 -81
- package/package.json +1 -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
|
|
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`
|
|
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
|
|
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.
|
|
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
|
-
通常ワーカー
|
|
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`
|
|
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.
|
|
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
|
@@ -164,6 +164,69 @@ var init_task_result = __esm({
|
|
|
164
164
|
}
|
|
165
165
|
});
|
|
166
166
|
|
|
167
|
+
// src/transcript.ts
|
|
168
|
+
import { existsSync, readFileSync as readFileSync3, readdirSync } from "node:fs";
|
|
169
|
+
import { homedir as homedir2 } from "node:os";
|
|
170
|
+
import { join as join3 } from "node:path";
|
|
171
|
+
function transcriptRoot() {
|
|
172
|
+
return join3(homedir2(), ".claude", "projects");
|
|
173
|
+
}
|
|
174
|
+
function findTranscriptPath(sessionId, root = transcriptRoot()) {
|
|
175
|
+
if (sessionId === "") return null;
|
|
176
|
+
let entries;
|
|
177
|
+
try {
|
|
178
|
+
entries = readdirSync(root);
|
|
179
|
+
} catch {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
for (const entry of entries) {
|
|
183
|
+
const candidate = join3(root, entry, `${sessionId}.jsonl`);
|
|
184
|
+
if (existsSync(candidate)) return candidate;
|
|
185
|
+
}
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
function textOf(entry) {
|
|
189
|
+
const content = entry.message?.content;
|
|
190
|
+
if (typeof content === "string") return content;
|
|
191
|
+
if (!Array.isArray(content)) return "";
|
|
192
|
+
return content.filter(
|
|
193
|
+
(block) => typeof block === "object" && block !== null && block.type === "text" && typeof block.text === "string"
|
|
194
|
+
).map((block) => block.text).join("\n").trim();
|
|
195
|
+
}
|
|
196
|
+
function extractFinalAssistantText(jsonl) {
|
|
197
|
+
const lines = jsonl.split("\n");
|
|
198
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
199
|
+
const line = lines[i].trim();
|
|
200
|
+
if (line === "") continue;
|
|
201
|
+
let entry;
|
|
202
|
+
try {
|
|
203
|
+
entry = JSON.parse(line);
|
|
204
|
+
} catch {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
if (entry.type !== "assistant" || entry.isSidechain === true) continue;
|
|
208
|
+
const text = textOf(entry);
|
|
209
|
+
if (text !== "") return text;
|
|
210
|
+
}
|
|
211
|
+
return "";
|
|
212
|
+
}
|
|
213
|
+
function readFinalReport(sessionId, root = transcriptRoot()) {
|
|
214
|
+
if (!sessionId) return "";
|
|
215
|
+
const path = findTranscriptPath(sessionId, root);
|
|
216
|
+
if (!path) return "";
|
|
217
|
+
try {
|
|
218
|
+
return extractFinalAssistantText(readFileSync3(path, "utf-8"));
|
|
219
|
+
} catch (err) {
|
|
220
|
+
console.error(`[transcript] failed to read ${path}: ${err}`);
|
|
221
|
+
return "";
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
var init_transcript = __esm({
|
|
225
|
+
"src/transcript.ts"() {
|
|
226
|
+
"use strict";
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
|
|
167
230
|
// src/herdr.ts
|
|
168
231
|
var herdr_exports = {};
|
|
169
232
|
__export(herdr_exports, {
|
|
@@ -171,15 +234,16 @@ __export(herdr_exports, {
|
|
|
171
234
|
HerdrError: () => HerdrError,
|
|
172
235
|
HerdrUnavailableError: () => HerdrUnavailableError,
|
|
173
236
|
agentGet: () => agentGet,
|
|
174
|
-
agentStart: () => agentStart,
|
|
175
237
|
checkHerdrAvailable: () => checkHerdrAvailable,
|
|
176
238
|
getCurrentWorkspaceId: () => getCurrentWorkspaceId,
|
|
239
|
+
launchAgentInPane: () => launchAgentInPane,
|
|
177
240
|
paneClose: () => paneClose,
|
|
178
241
|
paneGet: () => paneGet,
|
|
179
242
|
paneProcessInfo: () => paneProcessInfo,
|
|
180
243
|
paneRead: () => paneRead,
|
|
181
244
|
paneSendKeys: () => paneSendKeys,
|
|
182
245
|
paneSendText: () => paneSendText,
|
|
246
|
+
shellQuoteArgv: () => shellQuoteArgv,
|
|
183
247
|
tabClose: () => tabClose,
|
|
184
248
|
tabCreate: () => tabCreate,
|
|
185
249
|
tabList: () => tabList,
|
|
@@ -332,32 +396,12 @@ async function workspaceClose(workspaceId) {
|
|
|
332
396
|
async function workspaceFocus(workspaceId) {
|
|
333
397
|
await execHerdr(["workspace", "focus", workspaceId]);
|
|
334
398
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
env
|
|
342
|
-
}) {
|
|
343
|
-
const result = await execHerdr([
|
|
344
|
-
"agent",
|
|
345
|
-
"start",
|
|
346
|
-
name,
|
|
347
|
-
...workspaceId ? ["--workspace", workspaceId] : [],
|
|
348
|
-
...targetTabId ? ["--tab", targetTabId] : [],
|
|
349
|
-
...cwdArgs(cwd),
|
|
350
|
-
...envArgs(env),
|
|
351
|
-
"--no-focus",
|
|
352
|
-
"--",
|
|
353
|
-
...argv
|
|
354
|
-
]);
|
|
355
|
-
const paneId = result?.agent?.pane_id;
|
|
356
|
-
const tabId = result?.agent?.tab_id;
|
|
357
|
-
if (!paneId || !tabId) {
|
|
358
|
-
throw new Error("Failed to start agent: invalid response structure from herdr");
|
|
359
|
-
}
|
|
360
|
-
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");
|
|
361
405
|
}
|
|
362
406
|
function toAgentStatus(value) {
|
|
363
407
|
return value === "working" || value === "idle" || value === "blocked" || value === "done" ? value : "unknown";
|
|
@@ -368,11 +412,14 @@ async function agentGet(target) {
|
|
|
368
412
|
if (!agent?.pane_id) {
|
|
369
413
|
throw new Error(`Failed to get agent info for ${target}: invalid response structure from herdr`);
|
|
370
414
|
}
|
|
415
|
+
const session = agent.agent_session;
|
|
416
|
+
const sessionId = session?.kind === "id" && typeof session.value === "string" ? session.value : void 0;
|
|
371
417
|
return {
|
|
372
418
|
paneId: agent.pane_id,
|
|
373
419
|
tabId: agent.tab_id ?? "",
|
|
374
420
|
workspaceId: agent.workspace_id ?? "",
|
|
375
|
-
agentStatus: toAgentStatus(agent.agent_status)
|
|
421
|
+
agentStatus: toAgentStatus(agent.agent_status),
|
|
422
|
+
sessionId
|
|
376
423
|
};
|
|
377
424
|
}
|
|
378
425
|
function getCurrentWorkspaceId() {
|
|
@@ -485,10 +532,14 @@ var init_herdr = __esm({
|
|
|
485
532
|
// src/herdr-runner.ts
|
|
486
533
|
var herdr_runner_exports = {};
|
|
487
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,
|
|
488
537
|
AGENT_POLL_INTERVAL_MS: () => AGENT_POLL_INTERVAL_MS,
|
|
489
538
|
CLAUDE_EXIT_POLL_INTERVAL_MS: () => CLAUDE_EXIT_POLL_INTERVAL_MS,
|
|
490
539
|
CLAUDE_EXIT_TIMEOUT_MS: () => CLAUDE_EXIT_TIMEOUT_MS,
|
|
491
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,
|
|
492
543
|
buildHerdrTaskResult: () => buildHerdrTaskResult,
|
|
493
544
|
createCompletionTracker: () => createCompletionTracker,
|
|
494
545
|
observeAgentStatus: () => observeAgentStatus,
|
|
@@ -523,6 +574,10 @@ function observeAgentStatus(tracker, status) {
|
|
|
523
574
|
return { tracker, decision: "running" };
|
|
524
575
|
}
|
|
525
576
|
function buildHerdrTaskResult(paneOutput, options) {
|
|
577
|
+
const report = options?.report?.trim() ?? "";
|
|
578
|
+
if (report !== "") {
|
|
579
|
+
return { status: "completed", output: report };
|
|
580
|
+
}
|
|
526
581
|
const meaningful = options?.headroom ? stripHeadroomBanner(paneOutput) : paneOutput;
|
|
527
582
|
if (meaningful.trim() === "") {
|
|
528
583
|
return {
|
|
@@ -538,39 +593,89 @@ async function startHerdrTask({
|
|
|
538
593
|
argv,
|
|
539
594
|
env,
|
|
540
595
|
workspaceId,
|
|
541
|
-
herdr
|
|
596
|
+
herdr,
|
|
597
|
+
timing
|
|
542
598
|
}) {
|
|
543
599
|
const mod = herdr ?? await loadHerdr();
|
|
544
|
-
const { tabId, paneId
|
|
545
|
-
let paneId;
|
|
600
|
+
const { tabId, paneId } = await mod.tabCreate({ label, cwd, workspaceId, env });
|
|
546
601
|
try {
|
|
547
|
-
|
|
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
|
+
}
|
|
548
619
|
} catch (err) {
|
|
549
620
|
await mod.tabClose(tabId).catch(() => {
|
|
550
621
|
});
|
|
551
622
|
throw err;
|
|
552
623
|
}
|
|
553
|
-
await mod.paneClose(shellPaneId).catch((err) => {
|
|
554
|
-
console.error(`[herdr-runner] failed to close the placeholder shell pane ${shellPaneId}: ${err}`);
|
|
555
|
-
});
|
|
556
624
|
return { paneId, tabId };
|
|
557
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
|
+
}
|
|
558
660
|
async function waitForHerdrTask(paneId, options) {
|
|
559
661
|
const mod = options?.herdr ?? await loadHerdr();
|
|
560
662
|
const pollIntervalMs = options?.pollIntervalMs ?? AGENT_POLL_INTERVAL_MS;
|
|
561
663
|
let tracker = createCompletionTracker();
|
|
664
|
+
let sessionId;
|
|
562
665
|
for (; ; ) {
|
|
563
666
|
if (options?.signal?.aborted) {
|
|
564
667
|
return { status: "failed", output: "[worker] the worker is shutting down; the task was interrupted" };
|
|
565
668
|
}
|
|
566
669
|
let status;
|
|
567
670
|
try {
|
|
568
|
-
|
|
671
|
+
const agent = await mod.agentGet(paneId);
|
|
672
|
+
status = agent.agentStatus;
|
|
673
|
+
if (agent.sessionId) sessionId = agent.sessionId;
|
|
569
674
|
} catch (err) {
|
|
570
|
-
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")) {
|
|
571
676
|
return {
|
|
572
677
|
status: "failed",
|
|
573
|
-
output: "[worker] the claude
|
|
678
|
+
output: "[worker] the claude agent disappeared before the task completed (claude died or the tab was closed)"
|
|
574
679
|
};
|
|
575
680
|
}
|
|
576
681
|
console.error(`[herdr-runner] failed to read agent status for pane ${paneId}: ${err}`);
|
|
@@ -582,7 +687,8 @@ async function waitForHerdrTask(paneId, options) {
|
|
|
582
687
|
tracker = observed.tracker;
|
|
583
688
|
if (observed.decision === "completed") {
|
|
584
689
|
const output = await readPaneOutput(paneId, mod);
|
|
585
|
-
|
|
690
|
+
const report = (options?.readReport ?? readFinalReport)(sessionId);
|
|
691
|
+
return buildHerdrTaskResult(output, { headroom: options?.headroom, report });
|
|
586
692
|
}
|
|
587
693
|
if (observed.decision === "blocked-first-seen") {
|
|
588
694
|
options?.onBlocked?.();
|
|
@@ -598,15 +704,17 @@ async function readPaneOutput(paneId, mod) {
|
|
|
598
704
|
return "";
|
|
599
705
|
}
|
|
600
706
|
}
|
|
601
|
-
async function
|
|
707
|
+
async function waitForAgentGone(paneId, mod, options) {
|
|
602
708
|
const timeoutMs = options?.timeoutMs ?? CLAUDE_EXIT_TIMEOUT_MS;
|
|
603
709
|
const pollIntervalMs = options?.pollIntervalMs ?? CLAUDE_EXIT_POLL_INTERVAL_MS;
|
|
604
710
|
const deadline = Date.now() + timeoutMs;
|
|
605
711
|
for (; ; ) {
|
|
606
712
|
try {
|
|
607
|
-
await mod.
|
|
713
|
+
await mod.agentGet(paneId);
|
|
608
714
|
} catch (err) {
|
|
609
|
-
if (err instanceof mod.HerdrError && err.code === "pane_not_found")
|
|
715
|
+
if (err instanceof mod.HerdrError && (err.code === "agent_not_found" || err.code === "pane_not_found")) {
|
|
716
|
+
return true;
|
|
717
|
+
}
|
|
610
718
|
}
|
|
611
719
|
if (Date.now() >= deadline) return false;
|
|
612
720
|
await sleep(pollIntervalMs);
|
|
@@ -616,7 +724,7 @@ async function stopHerdrTask(task, herdr, options) {
|
|
|
616
724
|
const mod = herdr ?? await loadHerdr();
|
|
617
725
|
try {
|
|
618
726
|
await mod.paneSendKeys(task.paneId, "ctrl+c", "ctrl+c");
|
|
619
|
-
const exited = await
|
|
727
|
+
const exited = await waitForAgentGone(task.paneId, mod, {
|
|
620
728
|
timeoutMs: options?.exitTimeoutMs,
|
|
621
729
|
pollIntervalMs: options?.exitPollIntervalMs
|
|
622
730
|
});
|
|
@@ -638,13 +746,18 @@ async function stopHerdrTask(task, herdr, options) {
|
|
|
638
746
|
function sleep(ms) {
|
|
639
747
|
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
640
748
|
}
|
|
641
|
-
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;
|
|
642
750
|
var init_herdr_runner = __esm({
|
|
643
751
|
"src/herdr-runner.ts"() {
|
|
644
752
|
"use strict";
|
|
645
753
|
init_task_result();
|
|
754
|
+
init_transcript();
|
|
646
755
|
AGENT_POLL_INTERVAL_MS = 3 * 1e3;
|
|
647
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;
|
|
648
761
|
CLAUDE_EXIT_TIMEOUT_MS = 15 * 1e3;
|
|
649
762
|
CLAUDE_EXIT_POLL_INTERVAL_MS = 200;
|
|
650
763
|
}
|
|
@@ -697,8 +810,8 @@ var init_run_command = __esm({
|
|
|
697
810
|
var dispatcher_exports = {};
|
|
698
811
|
__export(dispatcher_exports, {
|
|
699
812
|
LABEL_PREFIX: () => LABEL_PREFIX,
|
|
700
|
-
PANE_READY_POLL_INTERVAL_MS: () =>
|
|
701
|
-
PANE_READY_TIMEOUT_MS: () =>
|
|
813
|
+
PANE_READY_POLL_INTERVAL_MS: () => PANE_READY_POLL_INTERVAL_MS2,
|
|
814
|
+
PANE_READY_TIMEOUT_MS: () => PANE_READY_TIMEOUT_MS2,
|
|
702
815
|
POLL_INTERVAL_MS: () => POLL_INTERVAL_MS,
|
|
703
816
|
SEND_MAX_ATTEMPTS: () => SEND_MAX_ATTEMPTS,
|
|
704
817
|
SHUTDOWN_RETRY_TIMEOUT_MS: () => SHUTDOWN_RETRY_TIMEOUT_MS,
|
|
@@ -718,7 +831,7 @@ __export(dispatcher_exports, {
|
|
|
718
831
|
runDispatcher: () => runDispatcher,
|
|
719
832
|
shutdownDispatcher: () => shutdownDispatcher,
|
|
720
833
|
startWorkerInPane: () => startWorkerInPane,
|
|
721
|
-
waitForPaneReady: () =>
|
|
834
|
+
waitForPaneReady: () => waitForPaneReady2,
|
|
722
835
|
waitForWorkerStartup: () => waitForWorkerStartup,
|
|
723
836
|
workspaceLabelFor: () => workspaceLabelFor
|
|
724
837
|
});
|
|
@@ -759,9 +872,9 @@ function isWorkerProcess(process2) {
|
|
|
759
872
|
function isShellProcess(process2) {
|
|
760
873
|
return SHELL_NAME_PATTERN.test(process2.name ?? "") || SHELL_NAME_PATTERN.test(process2.cmdline ?? "");
|
|
761
874
|
}
|
|
762
|
-
async function
|
|
763
|
-
const timeoutMs = options?.timeoutMs ??
|
|
764
|
-
const pollIntervalMs = options?.pollIntervalMs ??
|
|
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;
|
|
765
878
|
const deadline = Date.now() + timeoutMs;
|
|
766
879
|
for (; ; ) {
|
|
767
880
|
const content = await herdr.paneRead(paneId, { source: "visible" });
|
|
@@ -785,7 +898,7 @@ async function waitForWorkerStartup(paneId, herdr, options) {
|
|
|
785
898
|
async function startWorkerInPane(paneId, forwardedCommand, herdr, options) {
|
|
786
899
|
const maxAttempts = options?.sendMaxAttempts ?? SEND_MAX_ATTEMPTS;
|
|
787
900
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
788
|
-
const ready = await
|
|
901
|
+
const ready = await waitForPaneReady2(paneId, herdr, {
|
|
789
902
|
timeoutMs: options?.paneReadyTimeoutMs,
|
|
790
903
|
pollIntervalMs: options?.paneReadyPollIntervalMs
|
|
791
904
|
});
|
|
@@ -1087,15 +1200,15 @@ function createDispatcherShutdownHandler(shutdown2) {
|
|
|
1087
1200
|
};
|
|
1088
1201
|
return { handle, isShuttingDown: () => shuttingDown2 };
|
|
1089
1202
|
}
|
|
1090
|
-
var getDisplayWidth2, truncateToWidth2, padToWidth2, POLL_INTERVAL_MS, SHUTDOWN_TIMEOUT_MS,
|
|
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;
|
|
1091
1204
|
var init_dispatcher = __esm({
|
|
1092
1205
|
async "src/dispatcher.ts"() {
|
|
1093
1206
|
"use strict";
|
|
1094
1207
|
({ getDisplayWidth: getDisplayWidth2, truncateToWidth: truncateToWidth2, padToWidth: padToWidth2 } = await loadTable());
|
|
1095
1208
|
POLL_INTERVAL_MS = 7 * 1e3;
|
|
1096
1209
|
SHUTDOWN_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
1097
|
-
|
|
1098
|
-
|
|
1210
|
+
PANE_READY_TIMEOUT_MS2 = 30 * 1e3;
|
|
1211
|
+
PANE_READY_POLL_INTERVAL_MS2 = 200;
|
|
1099
1212
|
WORKER_STARTUP_TIMEOUT_MS = 30 * 1e3;
|
|
1100
1213
|
WORKER_STARTUP_POLL_INTERVAL_MS = 500;
|
|
1101
1214
|
SEND_MAX_ATTEMPTS = 3;
|
|
@@ -1216,6 +1329,28 @@ async function findPrNumberByHeadRef(headRefName, state = "open") {
|
|
|
1216
1329
|
const prs = JSON.parse(output);
|
|
1217
1330
|
return prs.length > 0 ? prs[0].number : null;
|
|
1218
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
|
+
}
|
|
1219
1354
|
async function getIssueState(issueNumber) {
|
|
1220
1355
|
const output = await execGh(["issue", "view", String(issueNumber), "--json", "state"]);
|
|
1221
1356
|
const parsed = JSON.parse(output);
|
|
@@ -1456,7 +1591,11 @@ function buildClaudeEnv(mode) {
|
|
|
1456
1591
|
|
|
1457
1592
|
// src/config.ts
|
|
1458
1593
|
import { readFileSync } from "node:fs";
|
|
1459
|
-
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
|
+
};
|
|
1460
1599
|
var DEFAULT_WORKER_CONFIG = {
|
|
1461
1600
|
skill: "",
|
|
1462
1601
|
model: "sonnet",
|
|
@@ -1545,10 +1684,27 @@ var WORKER_DEFAULTS = {
|
|
|
1545
1684
|
pollingIntervalSeconds: 300,
|
|
1546
1685
|
cooldownSeconds: 0,
|
|
1547
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
|
|
1548
1703
|
}
|
|
1549
1704
|
};
|
|
1550
1705
|
var DEFAULT_CONFIG = {
|
|
1551
1706
|
fixReviewPointCallbackCommentMessage: "",
|
|
1707
|
+
uiDesign: { ...DEFAULT_UI_DESIGN_CONFIG },
|
|
1552
1708
|
workers: {}
|
|
1553
1709
|
};
|
|
1554
1710
|
var CONFIG_PATH = join(process.cwd(), "claude-task-worker.json");
|
|
@@ -1616,6 +1772,35 @@ function parseWorkerEntry(name, val) {
|
|
|
1616
1772
|
}
|
|
1617
1773
|
return result;
|
|
1618
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
|
+
}
|
|
1619
1804
|
function loadConfig() {
|
|
1620
1805
|
const configPath = CONFIG_PATH;
|
|
1621
1806
|
let raw;
|
|
@@ -1623,17 +1808,20 @@ function loadConfig() {
|
|
|
1623
1808
|
raw = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
1624
1809
|
} catch (err) {
|
|
1625
1810
|
if (err.code === "ENOENT") {
|
|
1626
|
-
return { ...DEFAULT_CONFIG, workers: {} };
|
|
1811
|
+
return { ...DEFAULT_CONFIG, uiDesign: { ...DEFAULT_UI_DESIGN_CONFIG }, workers: {} };
|
|
1627
1812
|
}
|
|
1628
1813
|
throw err;
|
|
1629
1814
|
}
|
|
1630
|
-
const result = { ...DEFAULT_CONFIG, workers: {} };
|
|
1815
|
+
const result = { ...DEFAULT_CONFIG, uiDesign: { ...DEFAULT_UI_DESIGN_CONFIG }, workers: {} };
|
|
1631
1816
|
if ("fixReviewPointCallbackCommentMessage" in raw) {
|
|
1632
1817
|
const val = raw["fixReviewPointCallbackCommentMessage"];
|
|
1633
1818
|
if (typeof val === "string") {
|
|
1634
1819
|
result.fixReviewPointCallbackCommentMessage = val;
|
|
1635
1820
|
}
|
|
1636
1821
|
}
|
|
1822
|
+
if ("uiDesign" in raw) {
|
|
1823
|
+
result.uiDesign = parseUiDesignEntry(raw["uiDesign"]);
|
|
1824
|
+
}
|
|
1637
1825
|
if ("workers" in raw) {
|
|
1638
1826
|
const workers = raw["workers"];
|
|
1639
1827
|
if (typeof workers !== "object" || workers === null || Array.isArray(workers)) {
|
|
@@ -1651,6 +1839,14 @@ function getWorkerConfig(workerName) {
|
|
|
1651
1839
|
const config = loadConfig();
|
|
1652
1840
|
return config.workers[workerName] ?? { ...defaultsFor(workerName) };
|
|
1653
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
|
+
}
|
|
1654
1850
|
|
|
1655
1851
|
// src/git.ts
|
|
1656
1852
|
import { execSync, execFile } from "node:child_process";
|
|
@@ -1704,7 +1900,7 @@ init_task_result();
|
|
|
1704
1900
|
// src/user-config.ts
|
|
1705
1901
|
import { readFileSync as readFileSync2, statSync } from "node:fs";
|
|
1706
1902
|
import { homedir } from "node:os";
|
|
1707
|
-
import { isAbsolute, join as join2, resolve } from "node:path";
|
|
1903
|
+
import { isAbsolute as isAbsolute2, join as join2, resolve } from "node:path";
|
|
1708
1904
|
var RESERVED_ALL = "all";
|
|
1709
1905
|
var DEFAULT_RUN_MODE = "default";
|
|
1710
1906
|
var DEFAULT_HEADROOM = false;
|
|
@@ -1801,7 +1997,7 @@ function loadUserConfig() {
|
|
|
1801
1997
|
if (name === "__proto__") {
|
|
1802
1998
|
throw new UserConfigError(`"__proto__" cannot be used as a key in "projects": ${path}`);
|
|
1803
1999
|
}
|
|
1804
|
-
if (typeof value !== "string" || !
|
|
2000
|
+
if (typeof value !== "string" || !isAbsolute2(value)) {
|
|
1805
2001
|
console.warn(`[config] invalid projects.${name}: expected an absolute path, skipping`);
|
|
1806
2002
|
continue;
|
|
1807
2003
|
}
|
|
@@ -2352,16 +2548,16 @@ function isGeneratedWorktreeName(name) {
|
|
|
2352
2548
|
|
|
2353
2549
|
// src/slack.ts
|
|
2354
2550
|
import { exec } from "node:child_process";
|
|
2355
|
-
import { readFileSync as
|
|
2356
|
-
import { homedir as
|
|
2357
|
-
import { join as
|
|
2551
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
|
|
2552
|
+
import { homedir as homedir4 } from "node:os";
|
|
2553
|
+
import { join as join5 } from "node:path";
|
|
2358
2554
|
import { promisify as promisify2 } from "node:util";
|
|
2359
2555
|
|
|
2360
2556
|
// src/runcat.ts
|
|
2361
2557
|
import { mkdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
2362
|
-
import { homedir as
|
|
2363
|
-
import { dirname, join as
|
|
2364
|
-
var RUNCAT_OUT_FILE = process.env.RUNCAT_OUT_FILE ??
|
|
2558
|
+
import { homedir as homedir3 } from "node:os";
|
|
2559
|
+
import { dirname, join as join4 } from "node:path";
|
|
2560
|
+
var RUNCAT_OUT_FILE = process.env.RUNCAT_OUT_FILE ?? join4(process.env.HOME ?? homedir3(), ".claude", "runcat-usage.json");
|
|
2365
2561
|
var JST = "Asia/Tokyo";
|
|
2366
2562
|
function formatG(value) {
|
|
2367
2563
|
return String(Number(value.toPrecision(6)));
|
|
@@ -2425,7 +2621,7 @@ function buildRuncatSnapshot(usage, now = /* @__PURE__ */ new Date()) {
|
|
|
2425
2621
|
function writeRuncatUsage(usage, outFile = RUNCAT_OUT_FILE) {
|
|
2426
2622
|
const snapshot = buildRuncatSnapshot(usage);
|
|
2427
2623
|
const dir = dirname(outFile);
|
|
2428
|
-
const tmp =
|
|
2624
|
+
const tmp = join4(dir, `.runcat-${process.pid}-${Date.now()}.json`);
|
|
2429
2625
|
try {
|
|
2430
2626
|
mkdirSync(dir, { recursive: true });
|
|
2431
2627
|
writeFileSync(tmp, JSON.stringify(snapshot), "utf-8");
|
|
@@ -2462,13 +2658,13 @@ async function getOAuthToken() {
|
|
|
2462
2658
|
const { stdout } = await execAsync('security find-generic-password -s "Claude Code-credentials" -w');
|
|
2463
2659
|
return extractToken(JSON.parse(stdout.trim()));
|
|
2464
2660
|
} catch {
|
|
2465
|
-
const raw =
|
|
2661
|
+
const raw = readFileSync4(join5(homedir4(), ".claude", ".credentials.json"), "utf-8");
|
|
2466
2662
|
return extractToken(JSON.parse(raw));
|
|
2467
2663
|
}
|
|
2468
2664
|
}
|
|
2469
2665
|
function readUsageCache() {
|
|
2470
2666
|
try {
|
|
2471
|
-
const raw =
|
|
2667
|
+
const raw = readFileSync4(USAGE_CACHE_PATH, "utf-8");
|
|
2472
2668
|
const cached = JSON.parse(raw);
|
|
2473
2669
|
if (Date.now() - cached.timestamp < USAGE_CACHE_TTL_SECONDS * 1e3) {
|
|
2474
2670
|
return cached.data;
|
|
@@ -3156,22 +3352,247 @@ var epicIssueWorker = (opts = {}) => createIssuePollingWorker({
|
|
|
3156
3352
|
}
|
|
3157
3353
|
})();
|
|
3158
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
|
+
|
|
3159
3580
|
// src/commands/init.ts
|
|
3160
3581
|
import { mkdir as mkdir2, writeFile as writeFile2, access } from "node:fs/promises";
|
|
3161
3582
|
|
|
3162
3583
|
// src/commands/codegraph.ts
|
|
3163
3584
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3164
|
-
import { homedir as
|
|
3165
|
-
import { dirname as dirname2, join as
|
|
3585
|
+
import { homedir as homedir5 } from "node:os";
|
|
3586
|
+
import { dirname as dirname2, join as join7 } from "node:path";
|
|
3166
3587
|
async function loadRunCommand() {
|
|
3167
3588
|
return await Promise.resolve().then(() => (init_run_command(), run_command_exports));
|
|
3168
3589
|
}
|
|
3169
3590
|
var CODEGRAPH_PACKAGE = "@colbymchenry/codegraph";
|
|
3170
3591
|
var CODEGRAPH_IGNORE_ENTRY = ".codegraph/";
|
|
3171
|
-
function globalGitIgnorePath(env = process.env, home =
|
|
3592
|
+
function globalGitIgnorePath(env = process.env, home = homedir5()) {
|
|
3172
3593
|
const xdg = env.XDG_CONFIG_HOME?.trim();
|
|
3173
|
-
const base = xdg ? xdg :
|
|
3174
|
-
return
|
|
3594
|
+
const base = xdg ? xdg : join7(home, ".config");
|
|
3595
|
+
return join7(base, "git", "ignore");
|
|
3175
3596
|
}
|
|
3176
3597
|
function appendIgnoreEntry(current, entry) {
|
|
3177
3598
|
const bare = entry.replace(/\/$/, "");
|
|
@@ -3258,7 +3679,11 @@ var LABELS = [
|
|
|
3258
3679
|
{ name: "cc-triage-scope", color: "c5def5" },
|
|
3259
3680
|
{ name: "cc-resolve-conflict", color: "fbca04" },
|
|
3260
3681
|
{ name: "cc-epic-issue", color: "8b5cf6" },
|
|
3261
|
-
{ 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" }
|
|
3262
3687
|
];
|
|
3263
3688
|
var ISSUE_TEMPLATE = `name: "[claude-task-worker] Issue\u4F5C\u6210\u4F9D\u983C"
|
|
3264
3689
|
description: claude-task-worker\u3067GitHub Issue\u3092\u4F5C\u6210\u3059\u308B
|
|
@@ -3445,14 +3870,14 @@ async function update() {
|
|
|
3445
3870
|
}
|
|
3446
3871
|
|
|
3447
3872
|
// src/commands/version.ts
|
|
3448
|
-
import { readFileSync as
|
|
3449
|
-
import { dirname as dirname3, join as
|
|
3873
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
3874
|
+
import { dirname as dirname3, join as join8 } from "node:path";
|
|
3450
3875
|
import { fileURLToPath } from "node:url";
|
|
3451
3876
|
function version() {
|
|
3452
3877
|
const here = dirname3(fileURLToPath(import.meta.url));
|
|
3453
|
-
const pkgPath =
|
|
3878
|
+
const pkgPath = join8(here, "..", "package.json");
|
|
3454
3879
|
try {
|
|
3455
|
-
const pkg = JSON.parse(
|
|
3880
|
+
const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
|
|
3456
3881
|
console.log(pkg.version ?? "unknown");
|
|
3457
3882
|
} catch (err) {
|
|
3458
3883
|
console.error(`[version] Failed to read version: ${err.message}`);
|
|
@@ -3514,7 +3939,9 @@ var WORKERS = {
|
|
|
3514
3939
|
"triage-pr": triagePrWorker,
|
|
3515
3940
|
"resolve-conflict": resolveConflictWorker,
|
|
3516
3941
|
"check-dependabot": checkDependabotWorker,
|
|
3517
|
-
"epic-issue": epicIssueWorker
|
|
3942
|
+
"epic-issue": epicIssueWorker,
|
|
3943
|
+
"create-ui-design": createUiDesignWorker,
|
|
3944
|
+
"apply-ui-design": applyUiDesignWorker
|
|
3518
3945
|
};
|
|
3519
3946
|
function printUsage() {
|
|
3520
3947
|
console.log(`Usage: claude-task-worker <command> [--project <name>] [--epic <issue-number>] [--label <label-name>]
|
|
@@ -3537,6 +3964,8 @@ Workers:
|
|
|
3537
3964
|
resolve-conflict Poll cc-resolve-conflict PRs and run /resolve-conflict
|
|
3538
3965
|
check-dependabot Poll dependabot PRs every 1 hour
|
|
3539
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)
|
|
3540
3969
|
all Poll all workers except triage-created-issue, triage-pr, check-dependabot
|
|
3541
3970
|
yolo Poll all workers including triage-created-issue, triage-pr, check-dependabot
|
|
3542
3971
|
|
|
@@ -3731,7 +4160,9 @@ if (hasProjectFilter()) {
|
|
|
3731
4160
|
updateIssueWorker({ epicFilters, labelFilters }),
|
|
3732
4161
|
answerIssueQuestionsWorker({ epicFilters, labelFilters }),
|
|
3733
4162
|
resolveConflictWorker(),
|
|
3734
|
-
epicIssueWorker({ epicFilters, labelFilters })
|
|
4163
|
+
epicIssueWorker({ epicFilters, labelFilters }),
|
|
4164
|
+
createUiDesignWorker({ epicFilters, labelFilters }),
|
|
4165
|
+
applyUiDesignWorker({ epicFilters, labelFilters })
|
|
3735
4166
|
]);
|
|
3736
4167
|
})();
|
|
3737
4168
|
} else if (workerType === "yolo") {
|
|
@@ -3750,7 +4181,9 @@ if (hasProjectFilter()) {
|
|
|
3750
4181
|
checkDependabotWorker(),
|
|
3751
4182
|
triagePrWorker(),
|
|
3752
4183
|
resolveConflictWorker(),
|
|
3753
|
-
epicIssueWorker({ epicFilters, labelFilters })
|
|
4184
|
+
epicIssueWorker({ epicFilters, labelFilters }),
|
|
4185
|
+
createUiDesignWorker({ epicFilters, labelFilters }),
|
|
4186
|
+
applyUiDesignWorker({ epicFilters, labelFilters })
|
|
3754
4187
|
]);
|
|
3755
4188
|
})();
|
|
3756
4189
|
} else {
|