claude-task-worker 0.99.0 → 0.101.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 +134 -232
  2. package/dist/index.js +173 -53
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  GitHub Issues/PRを定期ポーリングし、Claude Codeに処理を委譲するCLIツール。
4
4
 
5
- 同梱の `claude-task-worker` Claude Code プラグイン(`plugin/`)と組み合わせることで、Issue の実装からPRのレビュー対応、Dependabot PR の対応までを自動化する。CLI 本体(npm パッケージ)とプラグイン(Claude Code マーケットプレイス)は同じリポジトリ・同じ名前で提供される。
5
+ 同梱の Claude Code プラグイン(`plugin/`)と組み合わせることで、Issue の実装からPRのレビュー対応、Dependabot PR の対応までを自動化する。CLI 本体(npm パッケージ)とプラグイン(Claude Code マーケットプレイス)は同じリポジトリ・同じ名前で提供される。
6
6
 
7
7
  ## アーキテクチャ
8
8
 
@@ -42,23 +42,10 @@ CLI が GitHub ラベルを検知してタスクを起動し、プラグイン
42
42
  共通の挙動:
43
43
 
44
44
  - 処理中は `cc-in-progress` を付与し、同一 Issue/PR の重複実行を防ぐ
45
- - `cc-need-human-check` が付いた Issue は全ワーカーの対象外
46
- - Issue 系ワーカーは `-is:blocked` 検索 qualifier で絞り込むため、未解決の blockedBy を持つ Issue は対象外
45
+ - `cc-need-human-check` が付いた Issue、未解決の blockedBy を持つ Issue は対象外
47
46
  - 完了時にトリガーラベルを除去し、次のワーカーへ引き継ぐラベルを付与する
48
- - `create-ui-design` / `apply-ui-design` は `uiDesign.enabled` が `true` のときだけ起動する(既定 `false`)
49
- - 定期ワーカー3つ(`update-coding-guidelines` / `update-requirement-rules` / `update-design-md`)はラベルではなく時刻を条件に24時間おきに1回動く。表の「間隔」は24時間経過したかを確認する頻度
50
- - `update-design-md` は `uiDesign.enabled` が `true` のときだけ起動する
51
-
52
- ### プラグインの構成
53
-
54
- | ディレクトリ | 内容 |
55
- |---|---|
56
- | `plugin/skills/` | ワーカーが呼ぶスキル群と、対話セッション用の補助スキル(`commit-push` / `create-pr` / `breakdown-issues` / `edit-pencil-design` など) |
57
- | `plugin/agents/` | サブエージェント定義(`explore-agent` / `frontend-implementer` / `general-purpose-assistant` / `lightweight-assistant` / `pencil-design-updater` / `requirement-todo-organizer`) |
58
- | `plugin/hooks/` | `SessionStart`(worktree セットアップ / `git fetch --prune`)と `UserPromptSubmit`(`codegraph prompt-hook`)のフック定義 |
59
- | `plugin/scripts/` | フック・スキルから呼ばれるスクリプト(`setup-worktree.sh` / `stop-servers.mjs` / `resolve-pr-comments.sh` — `resolve-pr-comments` スキルが GitHub MCP を使えない場合の `gh` フォールバック) |
60
- | `plugin/references/` | 複数スキルが共有する参照ドキュメント(GitHub アクセス方針など) |
61
- | `plugin/.mcp.json` | MCP サーバー定義(`codegraph` / `context7` / `next-devtools` / `shadcn` / `playwright`) |
47
+ - 定期ワーカー3つ(`update-*`)はラベルではなく時刻を条件に24時間おきに1回動く。表の「間隔」は24時間経過したかを確認する頻度
48
+ - UIデザイン系3ワーカー(`create-ui-design` / `apply-ui-design` / `update-design-md`)は `uiDesign.enabled` が `true` のときだけ起動する(既定 `false`)
62
49
 
63
50
  ## セットアップ
64
51
 
@@ -69,16 +56,15 @@ CLI が GitHub ラベルを検知してタスクを起動し、プラグイン
69
56
  | [Node.js](https://nodejs.org/) >= 22.6.0 | CLI の実行ランタイム |
70
57
  | [GitHub CLI (`gh`)](https://cli.github.com/) | 全 GitHub 操作(認証済みであること) |
71
58
  | [Claude Code (`claude`)](https://docs.anthropic.com/en/docs/claude-code) | タスク実行エンジン |
72
- | [Git](https://git-scm.com/) | worktree の作成・ブランチ操作 |
73
- | [jq](https://jqlang.org/) | プラグインスキル内での JSON 加工 |
74
- | [CodeGraph](https://www.npmjs.com/package/@colbymchenry/codegraph) | コード探索用インデックス。任意(未導入でも探索がテキスト検索に落ちるだけ) |
75
- | [Pen CLI](https://docs.pen.dev/for-developers/pen-cli) | `.pen` デザインファイルの編集・参照。UIデザイン先行ワークフロー使用時のみ(要ログイン。呼び出しは `pencil` 経由) |
76
- | [Playwright](https://playwright.dev/) のブラウザ(chromium) | Playwright MCP でのブラウザ確認。`install` / `update` が取得する(バイナリのみ。CLI のグローバル導入はしない)。Linux ではシステムライブラリ(`install-deps`)も併せて導入する(非 root では `sudo` 経由なのでパスワードを求められることがある) |
77
- | [DESIGN.md CLI](https://github.com/google-labs-code/design.md) | `DESIGN.md` lint。`update-design-md` 使用時のみ(呼び出しは `designmd` 経由。未導入でもスキルは動く) |
78
- | [herdr](https://herdr.dev) | `--project` / `mode: "herdr"` 使用時のみ |
79
- | [GitHub MCP](https://github.com/github/github-mcp-server) | GitHub アクセスの高速化・クラウド実行時のプロキシ制限回避。Claude 側のコネクタで有効化する(任意。未設定でも `gh` へフォールバックする) |
59
+ | [Git](https://git-scm.com/) / [jq](https://jqlang.org/) | worktree 操作 / スキル内での JSON 加工 |
60
+ | [CodeGraph](https://www.npmjs.com/package/@colbymchenry/codegraph) | コード探索用インデックス(任意。未導入ならテキスト検索に落ちる) |
61
+ | [Pen CLI](https://docs.pen.dev/for-developers/pen-cli) | `.pen` の編集・参照。UIデザイン先行ワークフロー使用時のみ(要ログイン) |
62
+ | [Playwright](https://playwright.dev/) のブラウザ | Playwright MCP でのブラウザ確認 |
63
+ | [DESIGN.md CLI](https://github.com/google-labs-code/design.md) | `DESIGN.md` lint。`update-design-md` 使用時のみ(未導入でも動く) |
64
+ | [herdr](https://herdr.dev) | `--project` / `mode: "herdr"` 使用時のみ(`--cloud` は起動ゲートこそ herdr を要求しないが、実行経路の分岐が未追随のため現状は `mode: "herdr"` が要る) |
65
+ | [GitHub MCP](https://github.com/github/github-mcp-server) | GitHub アクセスの高速化・クラウド実行時のプロキシ制限回避(任意。Claude 側のコネクタで有効化) |
80
66
 
81
- CLI 本体に npm の実行時依存はない(esbuild で `dist/index.js` に単一バンドルされ、Node.js 標準モジュールのみで動作する)。
67
+ CLI 本体に npm の実行時依存はない(Node.js 標準モジュールのみで動作する)。
82
68
 
83
69
  ### インストール
84
70
 
@@ -86,7 +72,7 @@ CLI 本体に npm の実行時依存はない(esbuild で `dist/index.js` に
86
72
  npx claude-task-worker install
87
73
  ```
88
74
 
89
- マーケットプレイスの追加・プラグインのインストール・CLI 本体のグローバルインストール・CodeGraph CLI / DESIGN.md CLI / Pen CLI のインストール・Playwright ブラウザ(chromium)の取得を一括で行う。いずれかが失敗しても処理は継続し、`[install]` プレフィックス付きでログ出力される(失敗時の終了コードは 1)。インストール後、Claude Code のセッションを再起動するとプラグインが有効になる。
75
+ マーケットプレイス追加・プラグイン導入・CLI 本体のグローバルインストール・各種 CLI(CodeGraph / DESIGN.md / Pen)と Playwright ブラウザの取得を一括で行う。いずれかが失敗しても処理は継続する。インストール後、Claude Code のセッションを再起動するとプラグインが有効になる。
90
76
 
91
77
  個別にやる場合:
92
78
 
@@ -96,38 +82,7 @@ claude plugin marketplace add getty104/claude-task-worker
96
82
  claude plugin install claude-task-worker@claude-task-worker
97
83
  ```
98
84
 
99
- herdr が必要な場合は `curl -fsSL https://herdr.dev/install.sh | sh` または `brew install herdr`([ドキュメント](https://herdr.dev/docs/install/))。
100
-
101
- クラウド実行(`--cloud` フラグ)を使う場合は、クラウド VM(Claude Code on the web)側にもプラグイン・CLI が必要になる。claude.ai の環境設定(Environment setup script / セットアップスクリプト欄)に `npx claude-task-worker install` を直接記載しておく。あわせて、クラウドセッションが push / PR 作成を行うには対象リポジトリの GitHub App 連携が必要。
102
-
103
- UIデザイン先行ワークフローを使う場合は、同じ claude.ai の環境設定の環境変数欄に `PEN_CLI_KEY` も設定する。`.pen` を扱うスキル(`edit-pencil-design` / `inspect-pencil-node` / `resolve-pencil-conflict`)の Pen CLI 認証に使うもので、クラウド VM では対話ログインができないため。キーの発行元と値の形式は後述の「[Pen CLI のログイン](#pen-cli-のログイン)」を参照。
104
-
105
- 詳細は後述の「[`--cloud`](#--cloud)」を参照。
106
-
107
- ### GitHub コネクタの有効化
108
-
109
- GitHub MCP は Claude 側のコネクタとして有効化する(本プラグインは `.mcp.json` で宣言しない)。claude.ai の設定 > コネクタ、または Claude Code の `/mcp` から GitHub コネクタを有効化する。
110
-
111
- 任意の設定であり、未設定でもスキルは `gh` へフォールバックして動作する。対応表は [`plugin/references/github-access.md`](./plugin/references/github-access.md) を参照。
112
-
113
- ### Pen CLI のログイン
114
-
115
- `.pen` を扱うスキル(`edit-pencil-design` / `inspect-pencil-node` / `resolve-pencil-conflict`)は Pen CLI の認証を必要とする。未ログインだと `.pen` の読み書きが失敗するため、UIデザイン先行ワークフローを使うなら**インストール後に一度ログインしておく**。
116
-
117
- ```bash
118
- pencil login # メールアドレス + パスワード、またはメールアドレス + OTP コード
119
- pencil status # 認証状態の確認
120
- ```
121
-
122
- セッショントークンは `~/.pencil/session-cli.json` に保存され、以降のコマンドで再利用される。
123
-
124
- CI やワーカーを実行するマシンなど対話ログインできない環境では、環境変数 `PEN_CLI_KEY`(pen.dev の組織設定 > Developer Keys で発行)を使う。保存済みトークンより優先される。
125
-
126
- ```bash
127
- export PEN_CLI_KEY=pencil_cli_...
128
- ```
129
-
130
- 詳細は [Pen CLI のドキュメント](https://docs.pen.dev/for-developers/pen-cli)を参照。
85
+ herdr `curl -fsSL https://herdr.dev/install.sh | sh` または `brew install herdr`([ドキュメント](https://herdr.dev/docs/install/))。
131
86
 
132
87
  ### 更新
133
88
 
@@ -135,11 +90,11 @@ export PEN_CLI_KEY=pencil_cli_...
135
90
  claude-task-worker update
136
91
  ```
137
92
 
138
- マーケットプレイス・プラグイン・CLI 本体・CodeGraph CLI / DESIGN.md CLI / Pen CLI をまとめて更新する。プラグインの反映にはセッション再起動が必要。
93
+ マーケットプレイス・プラグイン・CLI 本体・各種 CLI をまとめて更新する。プラグインの反映にはセッション再起動が必要。
139
94
 
140
95
  ### 初期化
141
96
 
142
- 対象リポジトリで実行すると、GitHub ラベル・Issue テンプレート・GitHub Actions ワークフロー・設定ファイルが作成され、CodeGraph のインデックスが構築される。
97
+ 対象リポジトリで実行すると、GitHub ラベル・Issue テンプレート・GitHub Actions ワークフロー・設定ファイル(`claude-task-worker.json`)が作成され、CodeGraph のインデックスが構築される。
143
98
 
144
99
  ```bash
145
100
  claude-task-worker init # 既存ファイルは保護
@@ -151,30 +106,17 @@ claude-task-worker init --force # 強制上書き
151
106
  | ラベル | 用途 |
152
107
  |---|---|
153
108
  | `cc-triage-scope` | トリアージ対象マーク(Issue/PR) |
154
- | `cc-issue-created` | `create-issue` 由来の Issue マーク(`triage-created-issue` のトリガー) |
155
- | `cc-update-issue` | Issue 更新トリガー |
156
- | `cc-answer-issue-questions` | Issue 確認事項への回答トリガー |
157
- | `cc-exec-issue` | Issue 実行トリガー |
158
- | `cc-fix-onetime` | PR 修正トリガー(1回) |
159
- | `cc-resolve-conflict` | PR コンフリクト解消トリガー |
109
+ | `cc-issue-created` | `create-issue` 由来の Issue マーク |
110
+ | `cc-update-issue` / `cc-answer-issue-questions` / `cc-exec-issue` | Issue の更新 / 確認事項回答 / 実行トリガー |
111
+ | `cc-fix-onetime` / `cc-resolve-conflict` | PR の修正 / コンフリクト解消トリガー |
160
112
  | `cc-in-progress` | 処理中ステータス |
161
- | `cc-need-human-check` | 人間の確認が必要(付与中は Issue ワーカーの対象外) |
113
+ | `cc-need-human-check` | 人間の確認が必要(付与中はワーカーの対象外) |
162
114
  | `cc-pr-created` | PR 作成完了マーク |
163
115
  | `cc-epic-issue` | エピックマーク(Issue: サブ全 Close で `epic-issue` 起動 / PR: リリースゲート対象) |
164
- | `cc-release-ready` | エピックPRがリリース可能と判定されたマーク(実際のマージは人間が実施) |
165
- | `cc-create-ui-design` | UIデザイン作成トリガー |
166
- | `cc-ui-design-pr-created` | デザインPR作成済み・マージ待ちマーク |
167
- | `cc-ui-design-ready` | デザイン反映済みマーク(再デザイン抑止) |
168
- | `cc-ui-design` | デザインPRのマーカー(`triage-pr` のレビュー観点切り替え用) |
169
- | `cc-cloud-done` | クラウド実行タスクの完了マーク(セッションが最後に付与し、ワーカーが検知して除去する。人が手動で付与しても同じ経路で完了扱いになるため、張り付いたクラウドタスクの救済手段としても使える) |
170
-
171
- 作成されるファイル:
172
-
173
- - `.github/ISSUE_TEMPLATE/cc-triage-scope.yml` — `cc-triage-scope` 付き Issue 作成用テンプレート
174
- - `.github/workflows/assign-creator-on-cc-triage-scope.yml` — Issue 作成者の自動アサイン
175
- - `claude-task-worker.json` — 設定ファイル。**ワーカーごとの既定値は書き出さない**(写経するとプラグイン更新で既定が変わっても古い値に固定されるため)。上書きしたいワーカーだけ手で追記する
176
-
177
- CodeGraph のセットアップとして、グローバル gitignore(`~/.config/git/ignore`)へ `.codegraph/` を冪等に追記し、`codegraph init` を実行する。CodeGraph 未インストールでも `init` 全体は失敗しない。
116
+ | `cc-release-ready` | エピックPRがリリース可能と判定されたマーク(マージは人間が実施) |
117
+ | `cc-create-ui-design` / `cc-ui-design-pr-created` / `cc-ui-design-ready` | UIデザイン先行ワークフローの各段階 |
118
+ | `cc-ui-design` | デザインPRのマーカー |
119
+ | `cc-cloud-done` | クラウド実行タスクの完了マーク(セッションが付与し、ワーカーが検知して除去する。手動付与で張り付いたタスクを救済できる) |
178
120
 
179
121
  ## コマンド
180
122
 
@@ -184,27 +126,28 @@ claude-task-worker <command> [--epic <issue-number>]... [--label <label>]... [--
184
126
 
185
127
  | コマンド | 内容 |
186
128
  |---|---|
187
- | 各ワーカー名 | 単一ワーカーを起動(`exec-issue` / `triage-pr` など。上記 Worker 表を参照) |
188
- | `all` | 通常ワーカー9つ + 定期ワーカー3つの計12ワーカーを同時にポーリング(`triage-created-issue` / `triage-pr` / `check-dependabot` を除く) |
189
- | `yolo` | 全ワーカーを同時にポーリング(`all` + `triage-created-issue` + `triage-pr` + `check-dependabot`) |
129
+ | 各ワーカー名 | 単一ワーカーを起動(`exec-issue` / `triage-pr` など) |
130
+ | `all` | 通常ワーカー9つ + 定期ワーカー3つ(`triage-created-issue` / `triage-pr` / `check-dependabot` を除く) |
131
+ | `yolo` | 全ワーカーを同時にポーリング |
190
132
  | `init` | ラベル・テンプレート・設定ファイルの作成と CodeGraph セットアップ |
191
133
  | `install` / `update` | 上記「セットアップ」を参照 |
134
+ | `cloud-setup [--force]` | クラウド VM 側の準備(下記「`--cloud`」を参照) |
192
135
  | `usage` | Claude API 使用状況(5時間/7日間の利用率とリセット時刻)を表示し、Slack にも通知 |
193
136
  | `version` | CLI のバージョンを表示(`--version` / `-v` も可) |
194
137
 
195
138
  ### `--epic <issue-number>`
196
139
 
197
- 指定したエピック Issue のサブ Issue のみを処理対象に絞る。`all` / `yolo` と Issue 系ワーカーで有効。複数指定するといずれかのエピックを親に持つサブ Issue が対象になる(OR)。
140
+ 指定したエピック Issue のサブ Issue のみを処理対象に絞る。複数指定は OR
198
141
 
199
142
  ```bash
200
143
  claude-task-worker all --epic 100 --epic 200
201
144
  ```
202
145
 
203
- `epic-issue` ワーカーだけはエピック Issue 自体が処理対象なので、指定番号は「エピック Issue 自身の番号」として照合される。
146
+ `epic-issue` ワーカーではエピック Issue 自身の番号として照合される。
204
147
 
205
148
  ### `--label <label>`
206
149
 
207
- トリガーラベルに加えて指定ラベルが付いた Issue のみに絞る。複数指定すると全ラベルの AND。`--epic` と併用可能。ユーザーのスコープ指定なので、タスク完了時にワーカーが除去することはない。
150
+ トリガーラベルに加えて指定ラベルが付いた Issue のみに絞る。複数指定は AND。`--epic` と併用可能。
208
151
 
209
152
  ```bash
210
153
  claude-task-worker all --label priority-high --label needs-design
@@ -212,117 +155,103 @@ claude-task-worker all --label priority-high --label needs-design
212
155
 
213
156
  ### `--project <name>`
214
157
 
215
- 指定したプロジェクト(またはグループ、`all`)へ [herdr](https://herdr.dev) 経由でコマンドをディスパッチする。指定するとCLIはワーカーを直接実行せず、対象プロジェクトごとに独立した herdr ワークスペースを作ってそこでコマンドを実行する。
158
+ 指定したプロジェクト(またはグループ、`all`)へ [herdr](https://herdr.dev) 経由でコマンドをディスパッチする。CLI はワーカーを直接実行せず、プロジェクトごとに独立した herdr ワークスペースを作ってそこでコマンドを実行し、稼働状況をステータステーブルに表示する。SIGTERM/SIGINT で全セッションを一括停止する。
216
159
 
217
160
  ```bash
218
161
  claude-task-worker all --project all
219
- claude-task-worker all --project frontend
220
- claude-task-worker exec-issue --project app-a --epic 100 --label priority-high
221
- ```
222
-
223
- プロジェクト名・グループ名は `$XDG_CONFIG_HOME/claude-task-worker/config.json`(未設定なら `~/.config/claude-task-worker/config.json`)で定義する。`all` は全プロジェクトを指す予約語。
224
-
225
- ```json
226
- {
227
- "mode": "default",
228
- "advisor": false,
229
- "permission": "bypassPermissions",
230
- "projects": {
231
- "app-a": "/Users/me/repos/app-a",
232
- "app-b": "/Users/me/repos/app-b"
233
- },
234
- "projectGroups": {
235
- "frontend": ["app-a", "app-b"]
236
- }
237
- }
162
+ claude-task-worker exec-issue --project app-a --epic 100
238
163
  ```
239
164
 
240
- ディスパッチャーの機能:
241
-
242
- - **一斉起動**: プロジェクトごとに `ctw:<プロジェクト名>` ラベルのワークスペースを作り、そこで(`--project` を除いた)同じコマンドを実行する。ワーカーが実際に起動したかを確認し、起動しなければ再送・失敗判定する
243
- - **稼働一覧**: プロジェクト名・ワークスペースID・ペインID・ステータス・稼働時間をステータステーブルに描画する
244
- - **一括停止**: SIGTERM/SIGINT で全セッションへ ctrl-c を送り、終了を待ってワークスペースを閉じる。もう一度送ると強制終了
165
+ プロジェクト名・グループ名は `config.json` で定義する(下記「設定ファイル」)。`all` は全プロジェクトを指す予約語。
245
166
 
246
167
  `--project` と併用できないコマンド: `init` / `install` / `update` / `usage` / `version`
247
168
 
248
169
  ### `--cloud`
249
170
 
250
- 指定したワーカーのタスクを Claude Code on the web(クラウド VM)で実行する。プロセス単位のフラグで既定は無効。クラウドで実行されるのは `exec-issue` / `fix-review-point` の2つだけで(`CLOUD_ALLOWED_WORKERS`)、それ以外のワーカーは `--cloud` を付けてもローカル実行のまま残る。どのワーカーがクラウドで走るかは起動時に1行ログで示される。
251
-
252
- 許可リスト方式にしているのは、クラウド実行が「成果ゼロでも完了扱いになり、トリガーラベルの再付与で再起動され続ける」失敗の仕方をするため。`triage-pr` などは GraphQL ゲートで判断材料を取得できず空振りするが、完了扱いになるとポーリング間隔ごとにクラウドセッションを焼き続ける。
171
+ タスクを Claude Code on the web(クラウド VM)で実行する。プロセス単位のフラグで既定は無効。
253
172
 
254
173
  ```bash
255
174
  claude-task-worker exec-issue --cloud
256
175
  claude-task-worker all --cloud
257
176
  ```
258
177
 
178
+ **クラウドで実行されるのは `exec-issue` と `fix-review-point` の2ワーカーだけ**で、それ以外は `--cloud` を付けてもローカル実行のまま残る(`all` / `yolo` にそのまま付けられる)。どのワーカーがクラウドで走るかは起動時にログへ出る。
179
+
259
180
  前提条件:
260
181
 
261
- - `config.json` `mode` が `"herdr"` であること。新しいクラウドセッションの作成には TTY が必要で、`"default"` の子プロセス実行では作れない。`mode` が `"herdr"` でないのに `--cloud` を付けた場合は**タスクを1件も起動せずエラー終了する**(`"default"` へフォールバックしない)
262
- - claude.ai アカウントでのサインインが必須。API キー認証(`ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN`)・第三者プロバイダ(Bedrock / Vertex)・カスタムエンドポイント構成では利用できない。この検査(`claude auth status --json` の実行)は `--cloud` を指定したときだけ行う
263
- - 対象リポジトリの GitHub App 連携(クラウド VM から push / PR 作成を行うため)
264
- - claude.ai の環境設定のセットアップスクリプト欄に `npx claude-task-worker install` を記載してプラグイン・CLI を導入しておくこと(手順は「[インストール](#インストール)」参照)
265
- - UIデザイン先行ワークフローを使う場合のみ、claude.ai の環境設定の環境変数欄に `PEN_CLI_KEY`(pen.dev の組織設定 > Developer Keys で発行)を設定しておくこと。`.pen` を扱う3スキルの Pen CLI 認証に使う(「[Pen CLI のログイン](#pen-cli-のログイン)」参照)
182
+ | 前提 | 備考 |
183
+ |---|---|
184
+ | `script` コマンドが使える環境(macOS / Linux) | クラウドセッションの作成には TTY が必要で、それを `script` コマンドの疑似 pty で供給する。platform が darwin/linux でない、または `script` が PATH に無い場合はタスクを1件も起動せずエラー終了する(フォールバックしない) |
185
+ | claude.ai アカウントでのサインイン | API キー認証・第三者プロバイダ(Bedrock / Vertex)構成では利用不可。`--cloud` 指定時のみ検査される |
186
+ | 対象リポジトリの GitHub App 連携 | クラウド VM から push / PR 作成を行うため |
187
+ | claude.ai の「プルリクエストを自動的に作成する」「プルリクエストの自動修正」が **OFF** | 下記 |
188
+ | VM 側のセットアップスクリプト | 下記 |
189
+ | `PEN_CLI_KEY` 環境変数 | UIデザイン先行ワークフローを使う場合のみ(下記「Pen CLI のログイン」) |
190
+
191
+ claude.ai の設定にある **「プルリクエストを自動的に作成する」「プルリクエストの自動修正」は必ず OFF にする**。どちらもタスクワーカーの制御と競合する。前者はスキルの `create-pr`(`Closes #<N>`・ベースブランチ・ラベル付与)とは別に PR を作るため PR が重複し、後者はセッションが PR 作成後に終了せずレビューを待って修正を続けるため、`cc-cloud-done` による完了検知が 4 時間のタイムアウトまで効かない。
266
192
 
267
- 上記のうち静的検査されるのは1〜2番目だけで、GitHub App 連携・クラウド VM 側の導入状況・環境変数の設定はローカルから確認できないため検査されない。
193
+ claude.ai の環境設定(セットアップスクリプト欄)に次の2行を記載しておく。
268
194
 
269
- 運用上は次の3ワーカーには `--cloud` を使わないことを推奨する: `fix-review-point` / `triage-pr` / `check-dependabot`。起動自体は許可されるが、クラウドセッションの GitHub プロキシ制限でレビューコメント・CI ステータスなど判断材料を取得できず、タスクが空振りする。ローカルのみで実行したい場合は個別のワーカー名コマンドで `--cloud` を付けずに起動する。
195
+ ```bash
196
+ npx claude-task-worker install
197
+ npx claude-task-worker cloud-setup
198
+ ```
270
199
 
271
- クラウド実行のタスクは worktree を作らない(クラウド VM が自前でリポジトリを持つため)。
200
+ `cloud-setup` VM 側の `~/.claude/settings.json` に権限モード・出力スタイル・言語を書き込み、グローバル gitignore へ `.codegraph/` を登録する。クラウドセッションは起動フラグの `--permission-mode` を反映しないため、この設定ファイルが権限モードを指定する唯一の経路になる。書き込みはキー単位のマージで既存の設定を消さない(`--force` で上書き)。
272
201
 
273
- `--project` と併用した場合、`--cloud` は各プロジェクトへそのまま転送される。`--cloud` と併用できないコマンド: `init` / `install` / `update` / `usage` / `version`
202
+ 補足:
274
203
 
275
- `claude-task-worker.json` `workers.<名前>.cloud` 設定は廃止された。設定ファイルに残っている場合は警告ログを出したうえで無視される。
204
+ - クラウド実行のタスクは worktree を作らない(VM が自前でリポジトリを持つため)
205
+ - 完了は `cc-cloud-done` ラベルで検知する。4時間で応答がなければ打ち切り、`cc-need-human-check` を付けて失敗通知する
206
+ - `--project` と併用した場合、`--cloud` は各プロジェクトへそのまま転送される
207
+ - `--cloud` と併用できないコマンド: `init` / `install` / `update` / `usage` / `version`
208
+ - **現時点では `mode: "default"` での `--cloud` はクラウド経路へ流れない**(実行経路の分岐が `mode: "herdr"` 判定のまま未追随のため)。`--cloud` を使うには `mode: "herdr"` にしておく必要がある。追随は別Issueで対応予定
276
209
 
277
210
  詳細は [`docs/prd-cloud-worker-execution.md`](./docs/prd-cloud-worker-execution.md) を参照。
278
211
 
212
+ ### Pen CLI のログイン
213
+
214
+ `.pen` を扱うスキルは Pen CLI の認証を必要とする。UIデザイン先行ワークフローを使うなら一度ログインしておく。
215
+
216
+ ```bash
217
+ pencil login # メールアドレス + パスワード、またはメールアドレス + OTP コード
218
+ pencil status # 認証状態の確認
219
+ ```
220
+
221
+ CI やクラウド VM など対話ログインできない環境では、環境変数 `PEN_CLI_KEY`(pen.dev の組織設定 > Developer Keys で発行)を使う。保存済みトークンより優先される。
222
+
279
223
  ## 設定ファイル
280
224
 
281
- グローバル設定は `config.json`(上記)、リポジトリ設定は実行ディレクトリ直下の `claude-task-worker.json`。
225
+ グローバル設定は `$XDG_CONFIG_HOME/claude-task-worker/config.json`(未設定なら `~/.config/claude-task-worker/config.json`)、リポジトリ設定は実行ディレクトリ直下の `claude-task-worker.json`。
282
226
 
283
227
  ### `config.json`(グローバル)
284
228
 
229
+ ```json
230
+ {
231
+ "mode": "default",
232
+ "advisor": false,
233
+ "permission": "bypassPermissions",
234
+ "projects": {
235
+ "app-a": "/Users/me/repos/app-a",
236
+ "app-b": "/Users/me/repos/app-b"
237
+ },
238
+ "projectGroups": {
239
+ "frontend": ["app-a", "app-b"]
240
+ }
241
+ }
242
+ ```
243
+
285
244
  | キー | 既定 | 説明 |
286
245
  |---|---|---|
287
246
  | `projects` | - | プロジェクト名 → 絶対パス |
288
247
  | `projectGroups` | `{}` | グループ名 → プロジェクト名配列 |
289
- | `mode` | `"default"` | タスクの実行形態(下記) |
290
- | `advisor` | `false` | `--advisor` を渡すか(下記) |
291
- | `permission` | `"bypassPermissions"` | Claude CLI の権限モード(下記) |
248
+ | `mode` | `"default"` | `"default"`: `claude -p` の子プロセスとして実行 / `"herdr"`: herdr のタブ内で TUI 起動し、実行中の様子を覗ける(`--project` はこちらが必要) |
249
+ | `advisor` | `false` | `true` で `--advisor <model>` を渡す。モデルは `claude-task-worker.json` `advisorModel` |
250
+ | `permission` | `"bypassPermissions"` | Claude CLI の[権限モード](https://code.claude.com/docs/ja/permission-modes)(`bypassPermissions` / `dontAsk` / `auto` / `acceptEdits` / `manual` / `plan`) |
292
251
 
293
- #### `mode`(タスクの実行形態)
252
+ いずれもトップレベル一括で、プロジェクト単位・ワーカー単位の指定はできない。`permission` はワーカーに承認するユーザーがいないため、`bypassPermissions` / `dontAsk` 以外ではタスクが承認待ちで止まりうる。
294
253
 
295
- 全ワーカー・全プロジェクトに一括適用される(個別指定は不可)。
296
-
297
- | `mode` | 挙動 |
298
- |---|---|
299
- | `"default"` | タスクを `claude -p`(非対話 print モード)の子プロセスとして実行 |
300
- | `"herdr"` | タスクを herdr のタブ内で TUI セッションとして実行。実行中の様子を herdr で覗ける |
301
-
302
- `"herdr"` では、worktree 作成後に `ctw:<プロジェクト名>:#<番号>` ラベルのタブを作り、そのルートペインで claude を TUI 起動する。agent ステータスを監視して完了を検知し、セッション transcript から最終レポートを回収して通知に使う。`blocked`(claude が入力待ち)になっても自動失敗にせず待機し、ステータステーブルに `running:blocked` と表示するので herdr のタブを開いて直接対応できる。herdr が未インストール・未起動なら起動時にエラー終了する(`"default"` へフォールバックしない)。
303
-
304
- > ℹ️ タスク完了時の通知音はワーカー側から止められない(音を鳴らすのは herdr サーバープロセスで、`HERDR_DISABLE_SOUND` もそのプロセスの環境変数として読まれるため)。無音にするには `~/.config/herdr/config.toml` に `[ui.sound] enabled = false` を書いて `herdr server reload-config` する。ただし herdr サーバー全体に効くため、対話セッションの完了音も鳴らなくなる。
305
-
306
- #### `advisor`(アドバイザーモデル)
307
-
308
- `true` にすると、タスク起動時に Claude CLI へ `--advisor <model>` を渡す。渡すモデルは `claude-task-worker.json` の `workers.<名前>.advisorModel`。`mode` と同じくトップレベル一括で、プロジェクト単位・ワーカー単位のオン/オフはできない。空文字が指定されたワーカーには渡さない。
309
-
310
- advisor は main モデル以上の能力が必要(Claude CLI の制約)。`model` が `opus` のワーカーに `opus` advisor を付けても意味がなく、既定で `sonnet` のワーカーも `opus` advisor を付けると下げたぶんのコスト削減を打ち消すため、`advisorModel` の既定値は全ワーカー空文字(advisor なし)。`sonnet` のワーカーの品質が落ちた場合の調整弁として `advisorModel: "opus"` を指定できる。
311
-
312
- #### `permission`(権限モード)
313
-
314
- タスク起動時に Claude CLI へ渡す[権限モード](https://code.claude.com/docs/ja/permission-modes)。`mode` / `advisor` と同じくトップレベル一括で、プロジェクト単位・ワーカー単位の指定はできない。
315
-
316
- | `permission` | 挙動 |
317
- |---|---|
318
- | `"bypassPermissions"`(既定) | 全許可。承認するユーザーが常駐しない自律実行のため既定 |
319
- | `"dontAsk"` | 許可されていない操作は確認せずスキップする |
320
- | `"auto"` | 安全な操作は自動承認、危険な操作のみ確認 |
321
- | `"acceptEdits"` | ファイル編集は自動承認、それ以外は都度確認 |
322
- | `"manual"` | 標準の権限確認 |
323
- | `"plan"` | 読み取りのみ。変更は行わない |
324
-
325
- 値は Claude CLI の `--permission-mode` にそのまま渡される(choices と同じ綴り)。ワーカーには承認するユーザーがいないため、`bypassPermissions` / `dontAsk` 以外ではタスクが承認待ちで止まりうる(`mode: "herdr"` なら herdr のタブを開いて手動で承認できる)。
254
+ > ℹ️ `mode: "herdr"` の完了通知音はワーカー側から止められない。無音にするには `~/.config/herdr/config.toml` に `[ui.sound] enabled = false` を書いて `herdr server reload-config` する(herdr サーバー全体に効く)。
326
255
 
327
256
  ### `claude-task-worker.json`(リポジトリ)
328
257
 
@@ -331,32 +260,34 @@ advisor は main モデル以上の能力が必要(Claude CLI の制約)。`
331
260
  | `fixReviewPointCallbackCommentMessage` | string | - | `fix-review-point` 完了時に PR へ投稿するコメント(未設定なら投稿しない) |
332
261
  | `uiDesign` | object | `{ "enabled": false, "designDir": "designs", "yolo": false }` | UIデザイン先行ワークフロー(下記) |
333
262
  | `workers` | object | `{}` | ワーカーごとの上書き設定(下記) |
334
- | `lastRun` | object | `{}` | 定期ワーカーの最終実行時刻(ワーカー名 → ISO8601)。ワーカーが自動更新するため手で編集しない |
263
+ | `lastRun` | object | `{}` | 定期ワーカーの最終実行時刻。ワーカーが自動更新するため手で編集しない |
335
264
 
336
265
  #### ワーカーごとの設定
337
266
 
338
267
  未指定のワーカー・フィールドは既定値にフォールバックする。
339
268
 
340
- | フィールド | 型 | 説明 |
341
- |---|---|---|
342
- | `skill` | string | Claude CLI の `-p` に渡すスラッシュコマンド。`"<skill> <番号>"` の形で起動される |
343
- | `model` | string | `--model` の値(`sonnet` / `opus` / `haiku`) |
344
- | `advisorModel` | string | `--advisor` の値。空文字なら advisor なし。`config.json` の `advisor: true` のときだけ参照される |
345
- | `effort` | string | `--effort` の値(`high` / `medium` / `low`) |
346
- | `pollingIntervalSeconds` | number | ポーリング間隔(秒) |
347
- | `cooldownSeconds` | number | タスク完了後にポーリングを止める時間(秒)。`0` でなし |
348
- | `maxConcurrentTasks` | number | 同時実行できるタスクの最大数 |
349
-
350
- 既定値(`skill` は「[Worker とスキルの対応](#worker-とスキルの対応)」を参照。`cooldownSeconds` は `0`、`maxConcurrentTasks` は `1`):
351
-
352
- | ワーカー | `model` | `effort` | `advisorModel` | `pollingIntervalSeconds` |
353
- |---|---|---|---|---|
354
- | `exec-issue` / `fix-review-point` / `answer-issue-questions` / `create-issue` / `create-ui-design` / `triage-pr` | `opus` | `high` | `""`(なし) | 60 |
355
- | `update-issue` / `triage-created-issue` / `resolve-conflict` | `sonnet` | `high` | `""`(なし) | 60 |
356
- | `check-dependabot` | `sonnet` | `high` | `""`(なし) | 3600 |
357
- | `epic-issue` / `apply-ui-design` | `sonnet` | `medium` | `""`(なし) | 300 |
358
- | `update-coding-guidelines` / `update-requirement-rules` / `update-design-md` | `opus` | `high` | `""`(なし) | 3600 |
359
- | (未知のワーカー名) | `opus` | `high` | `""`(なし) | 60 |
269
+ | フィールド | 説明 |
270
+ |---|---|
271
+ | `skill` | Claude CLI の `-p` に渡すスラッシュコマンド(`"<skill> <番号>"` の形で起動) |
272
+ | `model` | `--model` の値(`sonnet` / `opus` / `haiku`) |
273
+ | `advisorModel` | `--advisor` の値。空文字なら advisor なし。`config.json` の `advisor: true` のときだけ参照される |
274
+ | `effort` | `--effort` の値(`high` / `medium` / `low`) |
275
+ | `pollingIntervalSeconds` | ポーリング間隔(秒) |
276
+ | `cooldownSeconds` | タスク完了後にポーリングを止める時間(秒)。既定 `0` |
277
+ | `maxConcurrentTasks` | 同時実行できるタスクの最大数。既定 `1` |
278
+
279
+ 既定値:
280
+
281
+ | ワーカー | `model` | `effort` | `pollingIntervalSeconds` |
282
+ |---|---|---|---|
283
+ | `exec-issue` / `fix-review-point` / `answer-issue-questions` / `create-issue` / `create-ui-design` / `triage-pr` | `opus` | `high` | 60 |
284
+ | `update-issue` / `triage-created-issue` / `resolve-conflict` | `sonnet` | `high` | 60 |
285
+ | `epic-issue` / `apply-ui-design` | `sonnet` | `medium` | 300 |
286
+ | `check-dependabot` | `sonnet` | `high` | 3600 |
287
+ | `update-coding-guidelines` / `update-requirement-rules` / `update-design-md` | `opus` | `high` | 3600 |
288
+ | (未知のワーカー名) | `opus` | `high` | 60 |
289
+
290
+ `advisorModel` の既定は全ワーカー空文字(advisor なし)。
360
291
 
361
292
  設定例:
362
293
 
@@ -365,8 +296,7 @@ advisor は main モデル以上の能力が必要(Claude CLI の制約)。`
365
296
  "workers": {
366
297
  "exec-issue": { "model": "opus", "cooldownSeconds": 600, "maxConcurrentTasks": 3 },
367
298
  "fix-review-point": { "model": "sonnet", "advisorModel": "opus", "maxConcurrentTasks": 2 },
368
- "triage-pr": { "effort": "medium", "pollingIntervalSeconds": 120 },
369
- "check-dependabot": { "model": "haiku", "pollingIntervalSeconds": 7200 }
299
+ "triage-pr": { "effort": "medium", "pollingIntervalSeconds": 120 }
370
300
  }
371
301
  }
372
302
  ```
@@ -375,86 +305,58 @@ advisor は main モデル以上の能力が必要(Claude CLI の制約)。`
375
305
 
376
306
  ### Epic(親Issue)連携
377
307
 
378
- 親 Issue(Issue Dependencies の Parent)を持つサブ Issue を処理する場合、ワーカーはデフォルトブランチではなく `cc-epic-<親Issue番号>` ブランチから worktree を作成する。エピック単位でブランチをまとめることで、サブ Issue ごとのPRを単一の統合ブランチへ集約できる。エピックブランチが remote に無ければデフォルトブランチから自動派生して push される。
308
+ 親 Issue を持つサブ Issue は、デフォルトブランチではなく `cc-epic-<親Issue番号>` ブランチから worktree を作って処理される。サブ Issue ごとのPRを単一の統合ブランチへ集約するため。エピックブランチが remote に無ければ自動で派生・push される。
379
309
 
380
- サブ Issue がすべて Close されると `epic-issue` ワーカーが `/claude-task-worker:create-epic-pr` を起動し、エピックブランチからまとめてPRを作る。エピックPRは `triage-pr` がマージ可能と判定してもマージせず `cc-release-ready` を付けるだけで、実際のマージ(リリース)は人間に委ねられる。
310
+ サブ Issue がすべて Close されると `epic-issue` ワーカーがエピックブランチからまとめてPRを作る。エピックPRは `triage-pr` がマージ可能と判定してもマージせず `cc-release-ready` を付けるだけで、実際のマージ(リリース)は人間に委ねられる。
381
311
 
382
312
  ### UIデザイン先行ワークフロー
383
313
 
384
- UI実装 Issue について、実装の前に Pencil(`.pen`)でデザインを作り、独立したPRとしてマージしてから実装へ進むフロー。デザインを実装PRとは別に単体でレビュー・合意でき、合意済みデザインがリポジトリに永続化される。
385
-
386
- `uiDesign.enabled` によるオプトインで、既定(`false`)では2つのワーカーが起動しないため、Pencil を使っていないリポジトリの挙動は本機能の追加前と完全に一致する。
314
+ UI実装 Issue について、実装の前に Pencil(`.pen`)でデザインを作り、独立したPRとしてマージしてから実装へ進むフロー。`uiDesign.enabled` によるオプトインで、既定(`false`)では関連ワーカーが起動しない。
387
315
 
388
316
  | キー | 既定 | 意味 |
389
317
  |---|---|---|
390
- | `uiDesign.enabled` | `false` | 有効化。`false` の間は `triage-created-issue` がUI判定を行わず、2つのワーカーも起動しない |
318
+ | `uiDesign.enabled` | `false` | 有効化 |
391
319
  | `uiDesign.designDir` | `"designs"` | `.pen` とスナップショットの配置先(リポジトリルートからの相対パス) |
392
- | `uiDesign.yolo` | `false` | デザインPRを自動レビュー・自動マージへ流すか。`true` のときだけデザインPRに `cc-triage-scope` を付ける |
320
+ | `uiDesign.yolo` | `false` | `true` でデザインPRに `cc-triage-scope` を付け、既存フローで自動レビュー・自動マージへ流す |
393
321
 
394
322
  ```text
395
- cc-issue-created + triage-created-issue(ルーティング)
396
- ├─ UI実装タスクでない → cc-exec-issue(従来どおり)
323
+ triage-created-issue(ルーティング)
324
+ ├─ UI実装タスクでない → cc-exec-issue
397
325
  └─ UI実装タスク → cc-create-ui-design
398
- → create-ui-design ワーカー
399
- ・.pen を作成/更新 + snapshots/ PNG 出力
400
- ・ブランチ cc-ui-design-<N> push しデザインPRを作成(Refs #N。closing keyword は使わない)
401
- ・PR に cc-ui-design、Issue に cc-ui-design-pr-created を付与
402
- → yolo: true → triage-pr / fix-review-point / resolve-conflict(既存フローでレビュー・マージ)
403
- yolo: false → 人がデザインPRをレビュー・マージ
404
- → apply-ui-design ワーカー
405
- ・デザインPRが MERGED になるまで skip
406
- ・Issue description に「## UIデザイン」セクションを追記
407
- ・cc-ui-design-ready + cc-exec-issue を付与
408
- → exec-issue(デザインを参照元として実装)
326
+ → create-ui-design: .pen + snapshots を作り、デザインPR(cc-ui-design)を作成
327
+ yolo: true なら自動レビュー・マージ / false なら人がレビュー・マージ
328
+ apply-ui-design: マージ後に Issue description へ「## UIデザイン」を追記し cc-exec-issue を付与
329
+ exec-issue: デザインを参照元として実装
409
330
  ```
410
331
 
411
- `triage-pr` `cc-ui-design` 付きPRをコードレビューではなくデザイン向けの観点(差分が `.pen` とPNGに限定されているか、スナップショットからデザイン意図が読み取れるか、Issue 要件を満たしているか)で評価する。
412
-
413
- デザインが不要と判明した場合は `create-ui-design` が理由をコメントして `cc-ui-design-ready` + `cc-exec-issue` を付与し、人手を介さず実装へ復帰する。Pencil が使えない環境やデザインPRが却下された場合は `cc-need-human-check` で停止する。
332
+ デザインが不要と判明した場合は `create-ui-design` が理由をコメントして実装へ復帰する。Pencil が使えない環境やデザインPRが却下された場合は `cc-need-human-check` で停止する。
414
333
 
415
334
  ## Slack通知
416
335
 
417
- 環境変数 `CLAUDE_TASK_WORKER_SLACK_WEBHOOK_URL` に Slack Incoming Webhook URL を設定すると、各ワーカーのタスク完了時・失敗時に通知が送られる。未設定なら送信されない。
336
+ 環境変数 `CLAUDE_TASK_WORKER_SLACK_WEBHOOK_URL` に Incoming Webhook URL を設定すると、タスクの完了時・失敗時に通知が送られる。未設定なら送信されない。
418
337
 
419
338
  ```bash
420
339
  export CLAUDE_TASK_WORKER_SLACK_WEBHOOK_URL=https://hooks.slack.com/services/xxx/yyy/zzz
421
340
  claude-task-worker all
422
341
  ```
423
342
 
424
- 通知には Claude API の使用状況(5時間/7日間の利用率とリセット時刻)も含まれる。使用状況の取得は macOS では `security`(Keychain)、それ以外では `~/.claude/.credentials.json` を使う。あわせて [RunCat Neo](https://kyome.io/runcat/) 用のスナップショットを `~/.claude/runcat-usage.json`(`RUNCAT_OUT_FILE` で変更可)へ原子的に書き出す(Webhook 未設定でも更新される)。取得結果は360秒キャッシュされるため、値は最大6分古くなりうる。
425
-
426
- クラウド実行(`--cloud`)のタスクは、通知の先頭行にクラウドセッションのURL(`https://claude.ai/code/<id>`)が入る。Slack で本文が折りたたまれても先頭行は見えるため。
343
+ 通知には Claude API の使用状況も含まれる。あわせて [RunCat Neo](https://kyome.io/runcat/) 用のスナップショットを `~/.claude/runcat-usage.json`(`RUNCAT_OUT_FILE` で変更可)へ書き出す。クラウド実行のタスクは通知の先頭行にセッションURLが入る。
427
344
 
428
345
  ## プロセス管理
429
346
 
430
- 実行中のタスクはリアルタイムのステータステーブルで表示される。
431
-
432
- - タスクID・タイトル・ステータス(running/completed/failed)・開始時刻・経過時間を表示
433
- - `mode: "herdr"` では実行中の行に agent ステータスが併記される(`running:working` / `running:blocked`)
434
- - 同一 Issue/PR の重複実行を自動防止
435
- - SIGTERM/SIGINT で全子プロセスを graceful shutdown(もう一度送ると強制終了し、ラベル・worktree の後片付けを試みる)
436
- - 前回の異常終了で残った worktree はワーカー起動時に自動回収される(実行中タスク・対話セッションが掴んでいるものは保護される)
437
-
438
- ### タスク実行のガード
439
-
440
- ワーカーは応答するユーザーがいない状態でスキルを起動するため、処理が未完のままセッションが終了してラベルだけ進む事故を防ぐガードを持つ。
347
+ 実行中のタスクはリアルタイムのステータステーブルで表示される(タスクID・タイトル・ステータス・開始時刻・経過時間)。SIGTERM/SIGINT で graceful shutdown し、もう一度送ると強制終了してラベル・worktree の後片付けを試みる。前回の異常終了で残った worktree はワーカー起動時に自動回収される。
441
348
 
442
- - **バックグラウンド実行の無効化**: `CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1` を全タスクへ注入し、Bash の `run_in_background` やサブエージェントの自動バックグラウンド化を止める
443
- - **ツールの無効化**: `--disallowedTools` で `Monitor` / `ScheduleWakeup` / `AskUserQuestion` / `EnterPlanMode` / `Cron*` / `RemoteTrigger` / `EnterWorktree` を無効化する
444
- - **自律実行原則の注入**: `--append-system-prompt` で「ユーザーに質問しない・全ステップを完遂してから終了する・曖昧なら安全側を選ぶ・サブエージェントの完了報告を検証する」および CodeGraph 優先のコード探索方針を注入する
445
- - **完了検証**: `exec-issue` / `epic-issue` は PR の実在(または Issue のクローズ)を確認できるまで `cc-pr-created` を付けず、確認できなければ `cc-need-human-check` を付けて Issue にコメントを残す
446
- - **空振り検知**: 正常終了しても出力が空のセッションは失敗として分類し、失敗通知(stderr の末尾を含む)を送る
447
- - **起動プロセスの後片付け**: スキル終了時に `Stop` フックが `docker compose down` と、worktree を作業ディレクトリに持つ残留プロセスの `SIGTERM` をベストエフォートで実行する(worktree はスキル完了直後に削除されるため、残留プロセスが削除の妨げになるのを防ぐ)
349
+ ワーカーは応答するユーザーがいない状態でスキルを起動するため、処理が未完のままラベルだけ進む事故を防ぐガードを持つ(バックグラウンド実行と対話系ツールの無効化、自律実行原則のシステムプロンプト注入、PR 実在の完了検証、空出力セッションの失敗扱い、`Stop` フックによる残留プロセスの停止)。
448
350
 
449
351
  ## 開発
450
352
 
451
353
  ```bash
452
354
  npm install
453
- npm run build # 型チェック(tsc --noEmit)+ esbuild で dist/index.js にバンドル
355
+ npm run build # 型チェック + esbuild で dist/index.js にバンドル
454
356
  npm run dev # 型チェックの watch モード
455
- npm test # ユニットテスト(node --experimental-strip-types --test)
357
+ npm test # ユニットテスト
456
358
  npm run lint # ESLint(--fix で自動修正)
457
- npm run format # Prettier で整形(format:check でチェックのみ)
359
+ npm run format # Prettier で整形
458
360
  ```
459
361
 
460
362
  開発版をローカルから使う場合は `npm install && npm run build && npm link`。
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@ var __export = (target, all) => {
12
12
  // src/table.ts
13
13
  var table_exports = {};
14
14
  __export(table_exports, {
15
+ CONTROL_CHARS: () => CONTROL_CHARS,
15
16
  LOG_DISPLAY_LIMIT: () => LOG_DISPLAY_LIMIT,
16
17
  TASK_DISPLAY_LIMIT: () => TASK_DISPLAY_LIMIT,
17
18
  buildLogTableLines: () => buildLogTableLines,
@@ -190,7 +191,8 @@ var init_table = __esm({
190
191
  rawLog = console.log.bind(console);
191
192
  rawClear = console.clear.bind(console);
192
193
  consoleCaptured = false;
193
- CONTROL_CHARS = /\x1b\[[0-9;?]*[ -/]*[@-~]|[\x00-\x08\x0b-\x1f\x7f]/g;
194
+ CONTROL_CHARS = // eslint-disable-next-line no-control-regex -- ANSI/制御文字を意図的に対象にする
195
+ /\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b\[[0-9;?]*[ -/]*[@-~]|[\x00-\x08\x0b-\x1f\x7f]/g;
194
196
  }
195
197
  });
196
198
 
@@ -588,6 +590,7 @@ __export(herdr_runner_exports, {
588
590
  buildHerdrTaskResult: () => buildHerdrTaskResult,
589
591
  createCompletionTracker: () => createCompletionTracker,
590
592
  extractCloudSessionId: () => extractCloudSessionId,
593
+ normalizePtyOutput: () => normalizePtyOutput,
591
594
  observeAgentStatus: () => observeAgentStatus,
592
595
  startHerdrTask: () => startHerdrTask,
593
596
  stopHerdrTask: () => stopHerdrTask,
@@ -629,6 +632,9 @@ function observeAgentStatus(tracker, status) {
629
632
  function extractCloudSessionId(text) {
630
633
  return CLOUD_SESSION_URL_RE.exec(text)?.[1] ?? CLOUD_SESSION_CREATED_RE.exec(text)?.[1];
631
634
  }
635
+ function normalizePtyOutput(text) {
636
+ return text.replace(CONTROL_CHARS, "");
637
+ }
632
638
  function buildHerdrTaskResult(paneOutput, options) {
633
639
  const report = options?.report?.trim() ?? "";
634
640
  if (report !== "") {
@@ -810,6 +816,7 @@ var AGENT_POLL_INTERVAL_MS, PANE_OUTPUT_LINES, CLOUD_SESSION_URL_RE, CLOUD_SESSI
810
816
  var init_herdr_runner = __esm({
811
817
  "src/herdr-runner.ts"() {
812
818
  "use strict";
819
+ init_table();
813
820
  init_transcript();
814
821
  AGENT_POLL_INTERVAL_MS = 3 * 1e3;
815
822
  PANE_OUTPUT_LINES = 300;
@@ -1635,7 +1642,7 @@ import { readFileSync, writeFileSync } from "node:fs";
1635
1642
  import { isAbsolute, join, normalize, sep as SEP } from "node:path";
1636
1643
 
1637
1644
  // src/dispatch-args.ts
1638
- var FLAG_INCOMPATIBLE_COMMANDS = ["init", "install", "update", "usage", "version"];
1645
+ var FLAG_INCOMPATIBLE_COMMANDS = ["init", "install", "update", "cloud-setup", "usage", "version"];
1639
1646
  function collectFlagValues(argv, flag) {
1640
1647
  const values = [];
1641
1648
  for (let i = 0; i < argv.length; i++) {
@@ -1888,9 +1895,9 @@ function checkCloudAuth(input) {
1888
1895
  function checkCloudConfig(input) {
1889
1896
  if (!input.cloud) return [];
1890
1897
  const errors = [];
1891
- if (input.mode !== "herdr") {
1898
+ if (input.scriptAvailable === false) {
1892
1899
  errors.push(
1893
- `--cloud requires mode "herdr" but mode is "${input.mode}" (creating a new cloud session requires a TTY, which "default" mode's spawn does not have). Set mode to "herdr" in config.json, or drop the --cloud flag.`
1900
+ `--cloud requires a pty, provided via the "script" command (creating a new cloud session requires a TTY, which the worker's spawn does not have on its own). "script" is unavailable \u2014 either the platform is not darwin/linux, or "script" is not on PATH. Drop the --cloud flag, or run on darwin/linux where "script" is available.`
1894
1901
  );
1895
1902
  }
1896
1903
  if (input.auth !== void 0) errors.push(...checkCloudAuth(input.auth));
@@ -2521,15 +2528,26 @@ ${principles}`;
2521
2528
  function shellQuote2(value) {
2522
2529
  return `'${value.replace(/'/g, "'\\''")}'`;
2523
2530
  }
2531
+ function buildScriptCommand(command, args, platform = process.platform) {
2532
+ if (platform === "darwin") {
2533
+ return { command: "script", args: ["-q", "/dev/null", command, ...args] };
2534
+ }
2535
+ if (platform === "linux") {
2536
+ return { command: "script", args: ["-qec", [command, ...args].map(shellQuote2).join(" "), "/dev/null"] };
2537
+ }
2538
+ throw new Error(`unsupported platform for script(1): ${platform}`);
2539
+ }
2524
2540
  function buildClaudeExecution(invocation) {
2525
2541
  return {
2526
2542
  command: CLAUDE_COMMAND,
2527
2543
  args: buildClaudeArgs(invocation),
2528
- ...invocation.mode === "herdr" ? { prompt: invocation.prompt } : {}
2544
+ // クラウド実行では `-p` を付けない代わりに `--cloud` の値(初期プロンプト)として
2545
+ // 渡すため、mode に関わらずプロンプトを返す。
2546
+ ...invocation.mode === "herdr" || invocation.cloud === true ? { prompt: invocation.prompt } : {}
2529
2547
  };
2530
2548
  }
2531
2549
  function buildClaudeEnv(mode, cloud) {
2532
- const base = mode === "herdr" ? { CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: CLAUDE_SPAWN_ENV.CLAUDE_CODE_DISABLE_BACKGROUND_TASKS } : { ...CLAUDE_SPAWN_ENV };
2550
+ const base = mode === "herdr" || cloud ? { CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: CLAUDE_SPAWN_ENV.CLAUDE_CODE_DISABLE_BACKGROUND_TASKS } : { ...CLAUDE_SPAWN_ENV };
2533
2551
  if (!cloud) return base;
2534
2552
  return { ...base, CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1" };
2535
2553
  }
@@ -2827,13 +2845,55 @@ async function flagOrphanedCloudSession(target, id, reason, cloudSessionId) {
2827
2845
  console.error(`[worker] failed to comment on ${target} #${id} about the orphaned cloud session: ${err}`);
2828
2846
  });
2829
2847
  }
2848
+ async function createCloudSession(args, initialPrompt, cwd, env) {
2849
+ const { extractCloudSessionId: extractCloudSessionId2, normalizePtyOutput: normalizePtyOutput2 } = await Promise.resolve().then(() => (init_herdr_runner(), herdr_runner_exports));
2850
+ const spec = buildScriptCommand("claude", buildCloudCreateArgs(args, initialPrompt));
2851
+ const child = spawn(spec.command, spec.args, {
2852
+ stdio: ["ignore", "pipe", "pipe"],
2853
+ ...cwd ? { cwd } : {},
2854
+ ...env ? { env: { ...process.env, ...env } } : {}
2855
+ });
2856
+ let output = "";
2857
+ return await new Promise((resolve3, reject) => {
2858
+ let settled = false;
2859
+ const finish = (err, sessionId) => {
2860
+ if (settled) return;
2861
+ settled = true;
2862
+ clearTimeout(timer);
2863
+ clearInterval(abortCheck);
2864
+ if (err) {
2865
+ child.kill();
2866
+ reject(err);
2867
+ } else {
2868
+ resolve3(sessionId);
2869
+ }
2870
+ };
2871
+ const scan = () => {
2872
+ const sessionId = extractCloudSessionId2(normalizePtyOutput2(output));
2873
+ if (sessionId) finish(void 0, sessionId);
2874
+ };
2875
+ child.stdout?.on("data", (chunk) => {
2876
+ output += chunk.toString("utf-8");
2877
+ scan();
2878
+ });
2879
+ child.on("error", (err) => finish(new Error(`failed to spawn the cloud session command: ${err.message}`)));
2880
+ child.on("close", () => {
2881
+ scan();
2882
+ finish(new Error(`the cloud session command exited without a session id (output tail: ${output.slice(-1e3)})`));
2883
+ });
2884
+ const timer = setTimeout(
2885
+ () => finish(new Error(`timed out waiting for the cloud session id (output tail: ${output.slice(-1e3)})`)),
2886
+ CLOUD_SESSION_TIMEOUT_MS
2887
+ );
2888
+ const abortCheck = setInterval(() => {
2889
+ if (herdrAbortSignal.aborted) {
2890
+ finish(new Error("the worker is shutting down before the cloud session could be created"));
2891
+ }
2892
+ }, CLOUD_SESSION_POLL_INTERVAL_MS);
2893
+ });
2894
+ }
2830
2895
  async function runViaCloud(args, prompt, id, onComplete, cwd, env, cloudTarget, model) {
2831
- const herdrRunnerMod = await Promise.resolve().then(() => (init_herdr_runner(), herdr_runner_exports));
2832
- const { taskTabLabel: taskTabLabel2, waitForPaneReady: waitForPaneReady3, extractCloudSessionId: extractCloudSessionId2 } = herdrRunnerMod;
2833
- const herdrMod = await Promise.resolve().then(() => (init_herdr(), herdr_exports));
2834
- const { tabCreate: tabCreate2, tabClose: tabClose2, paneSendText: paneSendText2, paneSendKeys: paneSendKeys2, paneRead: paneRead2, getCurrentWorkspaceId: getCurrentWorkspaceId2 } = herdrMod;
2835
2896
  herdrTasks.set(id, { paneId: "", tabId: "" });
2836
- const label = taskTabLabel2(resolveProjectName(), id);
2837
2897
  const initialPrompt = buildCloudPrompt(
2838
2898
  prompt,
2839
2899
  model ?? "",
@@ -2842,39 +2902,7 @@ async function runViaCloud(args, prompt, id, onComplete, cwd, env, cloudTarget,
2842
2902
  let result;
2843
2903
  let cloudSessionId;
2844
2904
  try {
2845
- const created = await tabCreate2({ label, cwd: cwd ?? process.cwd(), workspaceId: getCurrentWorkspaceId2(), env });
2846
- herdrTasks.set(id, created);
2847
- try {
2848
- const ready = await waitForPaneReady3(created.paneId, herdrMod);
2849
- if (!ready) {
2850
- console.warn(`[worker] pane ${created.paneId} produced no prompt before the timeout, launching anyway`);
2851
- }
2852
- const command = ["claude", ...buildCloudCreateArgs(args, initialPrompt)].map(shellQuote2).join(" ");
2853
- await paneSendText2(created.paneId, command);
2854
- await paneSendKeys2(created.paneId, "enter");
2855
- const deadline = Date.now() + CLOUD_SESSION_TIMEOUT_MS;
2856
- for (; ; ) {
2857
- if (herdrAbortSignal.aborted) {
2858
- throw new Error("the worker is shutting down before the cloud session could be created");
2859
- }
2860
- let content = "";
2861
- try {
2862
- content = await paneRead2(created.paneId);
2863
- } catch (err) {
2864
- console.error(`[worker] failed to read pane ${created.paneId} while waiting for the cloud session: ${err}`);
2865
- }
2866
- cloudSessionId = extractCloudSessionId2(content);
2867
- if (cloudSessionId) break;
2868
- if (Date.now() >= deadline) {
2869
- throw new Error(`timed out waiting for the cloud session id (pane tail: ${content.slice(-1e3)})`);
2870
- }
2871
- await new Promise((resolve3) => setTimeout(resolve3, CLOUD_SESSION_POLL_INTERVAL_MS));
2872
- }
2873
- } finally {
2874
- await tabClose2(created.tabId).catch((err) => {
2875
- console.error(`[worker] failed to close cloud task tab ${created.tabId}: ${err}`);
2876
- });
2877
- }
2905
+ cloudSessionId = await createCloudSession(args, initialPrompt, cwd, env);
2878
2906
  const createOutput = `[worker] created cloud session ${cloudSessionId} with the task's initial prompt`;
2879
2907
  if (!cloudTarget) {
2880
2908
  console.warn(`[worker] #${id} has no completion-detection target, treating session creation as completion`);
@@ -2922,7 +2950,8 @@ async function runViaCloud(args, prompt, id, onComplete, cwd, env, cloudTarget,
2922
2950
  console.error(`[worker] failed to run #${id} via cloud: ${err}`);
2923
2951
  let orphanNote = "[worker] note: with the 1-command launch, the cloud session may already have started working independently even though this task is being reported as failed locally (orphaned session).";
2924
2952
  if (cloudTarget) {
2925
- await flagOrphanedCloudSession(cloudTarget, id, "session-id", cloudSessionId);
2953
+ const reason = herdrAbortSignal.aborted ? "shutdown" : "session-id";
2954
+ await flagOrphanedCloudSession(cloudTarget, id, reason, cloudSessionId);
2926
2955
  orphanNote += " added cc-need-human-check so a human can verify whether it completed on its own.";
2927
2956
  }
2928
2957
  result = {
@@ -2948,11 +2977,11 @@ function run(command, args, id, title, workerName, path2, onComplete, cwd, env,
2948
2977
  });
2949
2978
  ensureRenderInterval();
2950
2979
  renderTable();
2980
+ if (cloud) {
2981
+ void runViaCloud(args, prompt ?? "", id, onComplete, cwd, env, cloudTarget, model);
2982
+ return;
2983
+ }
2951
2984
  if (getRunMode() === "herdr") {
2952
- if (cloud) {
2953
- void runViaCloud(args, prompt ?? "", id, onComplete, cwd, env, cloudTarget, model);
2954
- return;
2955
- }
2956
2985
  void runViaHerdr(args, prompt ?? "", id, onComplete, cwd, env);
2957
2986
  return;
2958
2987
  }
@@ -5089,6 +5118,78 @@ async function install() {
5089
5118
  console.log("[install] Done.");
5090
5119
  }
5091
5120
 
5121
+ // src/commands/cloud-setup.ts
5122
+ import { mkdir as mkdir3, writeFile as writeFile3, readFile as readFile2 } from "node:fs/promises";
5123
+ import { homedir as homedir6 } from "node:os";
5124
+ import { join as join8 } from "node:path";
5125
+ var LOG_PREFIX = "cloud-setup";
5126
+ var CLOUD_DEFAULT_PERMISSION_MODE = "auto";
5127
+ var CLOUD_SETTINGS_DEFAULTS = {
5128
+ outputStyle: "Proactive",
5129
+ language: "Japanese"
5130
+ };
5131
+ function claudeSettingsPath() {
5132
+ const dir = process.env.CLAUDE_CONFIG_DIR;
5133
+ return join8(dir && dir.length > 0 ? dir : join8(homedir6(), ".claude"), "settings.json");
5134
+ }
5135
+ function withCloudDefaults(existing, force) {
5136
+ const parsed = existing === null ? {} : JSON.parse(existing);
5137
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
5138
+ throw new Error("not a JSON object");
5139
+ }
5140
+ const settings = { ...parsed };
5141
+ const current = settings["permissions"] ?? {};
5142
+ if (typeof current !== "object" || current === null || Array.isArray(current)) {
5143
+ throw new Error("`permissions` is not an object");
5144
+ }
5145
+ const permissions = { ...current };
5146
+ let changed = false;
5147
+ const set = (target, key, value) => {
5148
+ if (key in target && !force) return;
5149
+ if (target[key] === value) return;
5150
+ target[key] = value;
5151
+ changed = true;
5152
+ };
5153
+ set(permissions, "defaultMode", CLOUD_DEFAULT_PERMISSION_MODE);
5154
+ for (const [key, value] of Object.entries(CLOUD_SETTINGS_DEFAULTS)) set(settings, key, value);
5155
+ if (!changed) return null;
5156
+ return `${JSON.stringify({ ...settings, permissions }, null, 2)}
5157
+ `;
5158
+ }
5159
+ async function writeClaudeSettings(force) {
5160
+ const path2 = claudeSettingsPath();
5161
+ let existing;
5162
+ try {
5163
+ existing = await readFile2(path2, "utf-8");
5164
+ } catch {
5165
+ existing = null;
5166
+ }
5167
+ let next;
5168
+ try {
5169
+ next = withCloudDefaults(existing, force);
5170
+ } catch (e) {
5171
+ console.log(`[${LOG_PREFIX}] Skipped: ${path2} (${e instanceof Error ? e.message : String(e)})`);
5172
+ return;
5173
+ }
5174
+ if (next === null) {
5175
+ console.log(`[${LOG_PREFIX}] Already set: ${path2}`);
5176
+ return;
5177
+ }
5178
+ try {
5179
+ await mkdir3(join8(path2, ".."), { recursive: true });
5180
+ await writeFile3(path2, next, "utf-8");
5181
+ } catch (e) {
5182
+ console.log(`[${LOG_PREFIX}] Failed to write ${path2}: ${e instanceof Error ? e.message : String(e)}`);
5183
+ return;
5184
+ }
5185
+ console.log(`[${LOG_PREFIX}] ${existing === null ? "Created" : "Updated"}: ${path2}`);
5186
+ }
5187
+ async function cloudSetup(options = {}) {
5188
+ await writeClaudeSettings(options.force ?? false);
5189
+ await ensureCodegraphGitIgnore(LOG_PREFIX);
5190
+ console.log(`[${LOG_PREFIX}] Done.`);
5191
+ }
5192
+
5092
5193
  // src/commands/update.ts
5093
5194
  init_run_command();
5094
5195
  var PLUGIN_NAME2 = "claude-task-worker";
@@ -5144,14 +5245,14 @@ async function update() {
5144
5245
 
5145
5246
  // src/commands/version.ts
5146
5247
  import { readFileSync as readFileSync5 } from "node:fs";
5147
- import { dirname as dirname3, join as join8 } from "node:path";
5248
+ import { dirname as dirname3, join as join9 } from "node:path";
5148
5249
  import { fileURLToPath } from "node:url";
5149
5250
  import { styleText } from "node:util";
5150
5251
  var REGISTRY_URL = "https://registry.npmjs.org/claude-task-worker/latest";
5151
5252
  var FETCH_TIMEOUT_MS = 3e3;
5152
5253
  function localVersion() {
5153
5254
  const here = dirname3(fileURLToPath(import.meta.url));
5154
- const pkgPath = join8(here, "..", "package.json");
5255
+ const pkgPath = join9(here, "..", "package.json");
5155
5256
  const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
5156
5257
  return pkg.version ?? "unknown";
5157
5258
  }
@@ -5217,6 +5318,7 @@ Commands:
5217
5318
  init [--force] Create required GitHub labels and config file (use --force to overwrite existing files)
5218
5319
  install Add the claude-task-worker marketplace, install the plugin, and install/update the CLI
5219
5320
  update Update the claude-task-worker plugin/marketplace and the CLI itself
5321
+ cloud-setup [--force] Prepare a cloud session VM (writes permission mode, output style, and language into ~/.claude/settings.json). Meant for a cloud environment setup script
5220
5322
  usage Notify current usage to Slack
5221
5323
  version Print the installed claude-task-worker CLI version (aliases: --version, -v)
5222
5324
 
@@ -5267,7 +5369,7 @@ if (!workerType) {
5267
5369
  printUsage();
5268
5370
  process.exit(1);
5269
5371
  }
5270
- if (workerType !== "all" && workerType !== "yolo" && workerType !== "init" && workerType !== "install" && workerType !== "update" && workerType !== "usage" && !WORKERS[workerType]) {
5372
+ if (workerType !== "all" && workerType !== "yolo" && workerType !== "init" && workerType !== "install" && workerType !== "update" && workerType !== "cloud-setup" && workerType !== "usage" && !WORKERS[workerType]) {
5271
5373
  console.error(`Unknown command: ${workerType}`);
5272
5374
  printUsage();
5273
5375
  process.exit(1);
@@ -5344,13 +5446,27 @@ async function readCloudAuthStatus() {
5344
5446
  return { kind: "unknown" };
5345
5447
  }
5346
5448
  }
5449
+ async function resolveScriptAvailable() {
5450
+ try {
5451
+ buildScriptCommand("true", []);
5452
+ } catch {
5453
+ return false;
5454
+ }
5455
+ try {
5456
+ await execFileAsync4("which", ["script"]);
5457
+ return true;
5458
+ } catch {
5459
+ return false;
5460
+ }
5461
+ }
5347
5462
  async function assertCloudAvailable() {
5348
5463
  const cloud = hasCloudFlag();
5349
5464
  const labelReady = cloud ? await createLabel(CLOUD_DONE_LABEL, "33cfff", true) : true;
5350
5465
  const status = cloud ? await readCloudAuthStatus() : void 0;
5466
+ const scriptAvailable = cloud ? await resolveScriptAvailable() : void 0;
5351
5467
  const errors = checkCloudConfig({
5352
5468
  cloud,
5353
- mode: getRunMode(),
5469
+ scriptAvailable,
5354
5470
  auth: status ? { status, baseUrl: process.env.ANTHROPIC_BASE_URL } : void 0
5355
5471
  });
5356
5472
  if (!labelReady) {
@@ -5452,6 +5568,10 @@ if (hasProjectFilter()) {
5452
5568
  (async () => {
5453
5569
  await update();
5454
5570
  })();
5571
+ } else if (workerType === "cloud-setup") {
5572
+ (async () => {
5573
+ await cloudSetup({ force: process.argv.slice(3).includes("--force") });
5574
+ })();
5455
5575
  } else if (workerType === "usage") {
5456
5576
  (async () => {
5457
5577
  const text = await buildTokenLimitText();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-task-worker",
3
- "version": "0.99.0",
3
+ "version": "0.101.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",