diff --git a/.claude/skills/codex-director/SKILL.md b/.claude/skills/codex-director/SKILL.md index 30dfa85..6b4cfb5 100644 --- a/.claude/skills/codex-director/SKILL.md +++ b/.claude/skills/codex-director/SKILL.md @@ -5,19 +5,15 @@ description: Claude Code をディレクターAIとして動かし、調査・ # Codex ディレクター -Claude Code を**ディレクターAI**として動かし、調査・実装・変更・テスト・検証を **Codex(実装AI)** へ委任する。対象はコード実装が中心だが、そこに限らない。ディレクターAIは発注・検収・報告を担い、原則として成果物を自分で書かない。実行は実装AIが行う。 +Claude Code を**ディレクターAI**として動かし、調査・実装・変更・テスト・検証を **Codex(実装AI)** へ委任する。ディレクターAIは発注・検収・報告を担い、原則として成果物を自分で書かない。実行は実装AIが行う。 ## 起動する場合・しない場合 判断軸は「コードか文章か」ではなく、「Codex への委任とモデル・推論量の選択が価値を生む作業か」。出力の種類で最初から締め出さない。 -**起動する**: 調査・実装・修正・テスト・レビュー・検証を Codex に委任するとき。コード実装に限らず、次も対象。 +**起動する**: 調査・実装・修正・テスト・レビュー・検証の委任。コード実装のほか、調査や事実確認を伴う文章、デモコードや手順の検証が要る技術文章、コードから生成するドキュメント、多数ファイルの一括編集・移行・翻訳も対象。 -- 調査を伴う文章・ドキュメント(事実確認や既存コードの読み取りが必要なもの) -- デモコードや手順を実際に検証する必要がある技術文章 -- コードから生成するドキュメント、多数ファイルの一括編集・移行・翻訳 - -**起動しない(Claude が直接扱う)**: 単なる質問・相談、設計案の比較、および調査も検証も伴わない単発の執筆(スタイル重視の記事・エッセイ・提案文など)。ただしこれは「文章だから使わない」ではない。委任と検証のループがないから直接書く、という判断であり、作業の途中で調査や検証が生じたら委任を検討する。 +**起動しない(Claude が直接扱う)**: 単なる質問・相談、設計案の比較、調査も検証も伴わない単発の執筆(スタイル重視の記事・エッセイ・提案文など)。文章だから使わないのではない。委任と検証のループがないから直接書く、という判断であり、作業の途中で調査や検証が生じたら委任を検討する。 ## 役割分担 @@ -28,30 +24,32 @@ Claude Code を**ディレクターAI**として動かし、調査・実装・ 1. **整理**: 目的・制約を把握する。コードベースを見れば分かることは自分で調べ、重大な仕様判断だけユーザーに確認する。質問は増やしすぎない。 2. **発注設計**: モデルと推論量を選び(`references/model-routing.md`)、単体で完結する依頼文を組み立てる(`references/delegation-template.md`)。 -3. **発注前報告**: Codex を呼ぶ直前に、選んだ「モデル・推論量・選択理由」の3点をユーザーへ短く伝える。 +3. **発注前報告**: Codex を呼ぶ直前に、選んだ「モデル・推論量・選択理由」の3点をユーザーへ短く伝える。基準より1段下げた発注なら、下げた根拠と、落ちたときの切り分け方も添える。 4. **委任**: 下記コマンドで Codex を呼ぶ。 5. **検収**: 完了後、`references/review-checklist.md` に従って実際の差分とテストを確認する。 6. **最終報告**: 変更内容、選択モデルと推論量、実行した検査、確認した内容、残る制約・未確認事項を簡潔にまとめる。 ## モデルと推論量(サマリ) -この環境では GPT-5.6 が利用可能。3役に次を割り当てる(詳細と検証結果は `references/model-routing.md`)。 +既定は `gpt-5.6-luna` + `high`。モデルを先に選ばず、推論量を主なつまみにする。選択基準と実測は `references/model-routing.md`。 -| 役割 | モデルID | 既定の推論量 | -|---|---|---| -| 高速・低コスト | `gpt-5.6-luna` | `none`(定型)/ `low`(探索あり) | -| 標準 | `gpt-5.6-terra` | `medium`(デフォルト) | -| 高性能 | `gpt-5.6-sol` | `high`(上限は `xhigh`) | +| 段 | 指定 | +|---|---| +| 定型 | `gpt-5.6-luna` + `none` | +| 単純 | `gpt-5.6-luna` + `low` | +| 小さめ | `gpt-5.6-luna` + `medium` | +| 基本 | `gpt-5.6-luna` + `high` | +| 高度 | `gpt-5.6-luna` + `xhigh` | +| 中間 | `gpt-5.6-terra` + `xhigh` | +| 難しい | `gpt-5.6-sol` + `high`(不足時のみ `xhigh`) | -判断に迷ったら `gpt-5.6-terra` + `medium`。最大の推論量を常用しない。 +モデルを上げるのは `luna` で届かないときだけ。正しさの判定が難しい作業や仮説の否定を求める調査は最初から `sol` を選ぶ(`medium` 以下では使わない)。依頼文で迷う余地を消せたぶんは段を下げられる。逆に、設計判断を渡さずに段を上げても差し戻しは減らない。 -**注意**: `--effort minimal` はプラグインのフラグ検証は通るが GPT-5.6 の API が 400 で拒否する。推論を最小にしたいときは `minimal` ではなく `none` を使う。`max`・`ultra` は rescue 経由では指定できない(フラグ上限が `xhigh`)。 +使える推論量は `none, low, medium, high, xhigh` のみ。`minimal` はフラグ検証を通るが GPT-5.6 の API が 400 で拒否するので、推論を最小にしたいときは `none` を使う。`max` は companion が受け付けない。 ## Codex 委任コマンド -委任は、公式プラグイン(`codex@openai-codex`)の companion スクリプトを **Bash から直接呼び、`run_in_background: true` で実行する**。これが検証済みの既定経路。 - -`/codex:rescue` を Skill ツール経由で呼ぶ方法は使わない。`rescue.md` の frontmatter が `context: fork` のためフォーク実行になり、`--wait` を付けてもディレクター側は待たない。さらに subagent が Codex 本体の完了前に「completed」を返すことがあり、作業ツリーが未変更のまま報告を回収できない事故が実運用で起きた。Bash 直接実行なら、プロセス終了がそのまま Codex の完了で、stdout の末尾に最終報告が入る。 +委任は、公式プラグイン(`codex@openai-codex`)の companion スクリプトを **Bash から直接呼び、`run_in_background: true` で実行する**。これが検証済みの既定経路。`/codex:rescue` を Skill ツール経由で呼ぶ方法は使わない(フォーク実行になり完了を待てず、報告を回収できない事故が起きた。詳細は `references/execution.md`)。 プラグインのパスはバージョンを含むので動的に解決する。 @@ -71,28 +69,16 @@ node "$P/scripts/codex-companion.mjs" task --write --model <モデルID> --effor node "$P/scripts/codex-companion.mjs" task --write --resume-last --model <モデルID> --effort <推論量> "$(cat "$ORDER")" ``` -- 必ず Bash ツールの `run_in_background: true` で実行する。完了通知が届いたら出力ファイルを読み、末尾の最終報告を回収する。 -- 出力の冒頭に `command not found` や `permission denied` の行が並んでいたら、依頼文がシェルに食われている。Codex を止め、ファイル渡しで出し直す。副作用で起動したプロセスが残っていないかも確認する。 -- **`Task is still running. Use /codex:status before continuing it.` で発注が即失敗したら、中断したジョブの記録が `running` のまま残っている。** ハーネス側で止めても companion の状態は更新されない。`status --all` で確認し、そのジョブを `cancel ` すると発注できる。 - - ```bash - node "$P/scripts/codex-companion.mjs" status --all - node "$P/scripts/codex-companion.mjs" cancel - ``` - - cancel 後の `--resume-last` は、まず `completed` のジョブ記録を探す。見つからなければ Codex のスレッド一覧を更新時刻順に辿るため、cancel したスレッドを再開することがある。中断分を捨てて出し直すなら `--resume-last` を外して新規タスクとして発注する。 +- 必ず Bash ツールの `run_in_background: true` で実行する。完了通知が届いたら出力ファイルを読み、末尾の最終報告を回収する。フォアグラウンド実行はしない(Bash のタイムアウト上限は10分だが、実作業は20分を超えることがある)。 - **`--write` を必ず付ける**。付けないと sandbox が `read-only` になり、Codex はファイルを変更できない。調査のみを頼むときは意図して外す。 - `--resume-last` はリポジトリ単位で直近スレッドを解決するので、この経路でも差し戻しが効く。 -- フォアグラウンド実行はしない。Bash のタイムアウト上限は10分だが、実作業は20分を超えることがある。 -- ユーザーが `/codex:rescue` の使用を明示したときや、Skill 経由で呼んでしまい報告を回収できないときは `references/execution.md` の復旧手順に従う。 +- 発注が即失敗したとき、出力の冒頭に `command not found` が並ぶときの対処は `references/execution.md`。 - 委任中にマシンを再起動するとジョブ記録ごと作業が失われる。長時間の委任中は再起動しない。 - モデル名・推論量・オプションが将来この記述と食い違ったら、想像で補わず `commands/rescue.md` と `codex --help`、`~/.codex/models_cache.json` で実際を確認する。 ## Codex のサンドボックスで実行できない検証 -Codex のサンドボックスはネットワークを閉じており、`127.0.0.1` への listen も EPERM で拒否する。`@cloudflare/vitest-pool-workers` の workers プールも `wrangler dev` も起動できない。実運用では、ローカル通し実行の委任がこの制約で作業前に止まった。 - -`~/.codex/config.toml` の `network_access = true` で解除できるが、ローカルバインドだけを開ける設定は存在せず、全 Codex 実行で外向き通信まで開く。得るものより払うものが大きいと判断して既定のまま運用している。検証の詳細と、この判断を見直す条件は `references/sandbox-network.md` にある。**`listen EPERM` を見ても運用を想像で変えず、まずそのファイルを読む。** +Codex のサンドボックスはネットワークを閉じており、`127.0.0.1` への listen も EPERM で拒否する。`@cloudflare/vitest-pool-workers` の workers プールも `wrangler dev` も起動しない。**`listen EPERM` を見ても運用を想像で変えず、まず `references/sandbox-network.md` を読む**(設定で解除できるが、採らない理由と見直す条件をそこに記録してある)。 - サーバー起動を伴う通し実行・E2E検証は委任しない。実装とサンドボックス内で完結する検査までを委任し、サーバーを起動する検証はディレクター側で実行する。 - 依頼文の `` には、サンドボックスで完走するコマンドだけを書く。workers プールを含むリポジトリなら `npm test` ではなく `npx vitest run --project node` を指定し、フルスイートはディレクター側で実行する。 @@ -102,37 +88,21 @@ Codex のサンドボックスはネットワークを閉じており、`127.0.0 - **ファイルが未変更でも失敗と判断しない**。Codex は数分から十数分を調査に使ってから書き始める。実運用では開始3分後はまだ調査中だった。 - 完了通知を受け取る前に、検収と最終報告を終えない。 -- 待機中は同じ作業ツリーを触らない。 +- 待機中は同じ作業ツリーを触らない。同一作業ツリーで複数の Codex 実装を並列実行しない。並列化が必要なら別の worktree / workspace を使う。 ## 基本規則 -- ディレクターAI自身は原則コードを変更しない。誤字・format・明白な一行修正のみ自分で直してよい。 -- Codex の報告だけを信用せず、必ず実際の差分(`git diff` など)を読む。 -- テストが通った報告だけでなく、テストの内容が変更を本当に検証しているか確認する。修正前の壊れた実装でも通るテストになっていないか見る。 -- テストを追加・変更させたときは、通ったかではなく総件数を数える。発注時に期待件数を伝えておくと差分が見つけやすい。 -- 不備を見つけても自分で直さず、具体的な修正内容を Codex へ依頼する。 -- Codex が作業中は同じ作業ツリーを編集しない。同一作業ツリーで複数の Codex 実装を並列実行しない。並列化が必要なら別の worktree / workspace を使う。 -- 変更範囲を依頼の目的より広げない。適切な抽象が明確でない限り早まった共通化をしない。 -- テストは実装詳細でなく期待する振る舞いを検証する。テストが書きにくいときは無理に複雑化せず、設計上の問題として報告する。 +- ディレクターAI自身は原則コードを変更しない。誤字・format・明白な一行修正のみ自分で直してよい。不備を見つけても自分で直さず、具体的な修正内容を Codex へ依頼する。 +- Codex の報告だけを信用せず、必ず実際の差分(`git diff` など)を読む。テストが通った報告ではなく、テストの内容が変更を本当に検証しているかを見る。 - ユーザーが明示的に許可しない限り、コミット・push・デプロイはしない。 - -## 差し戻しの上限 - -差し戻す前に、原因が依頼文の側にないか疑う。実運用では、判定条件を絞りすぎた依頼文のせいで Codex が正しく実装した挙動を不合格にした例があった。依頼文の不備なら、修正依頼ではなく条件を書き直した発注として扱い、上限のカウントにも含めない。 - -修正依頼は2回までを目安にする。2回修正しても合格しない場合は、自分で作業を引き取らず、次をユーザーへ報告する。 - -- 合格しなかった箇所 -- 試した修正 -- 解決できなかった理由 -- 高性能モデルへの切り替えで改善が見込めるか -- 人間の判断が必要な点 +- Codex 側に守らせたい規則(変更範囲、早まった共通化の禁止、テストの書き方)は依頼文へ織り込む(`references/delegation-template.md`)。 +- 修正依頼は2回までを目安にする。上限に達したときの報告項目は `references/review-checklist.md`。 ## 試験運用の記録 試験運用中は、各タスクについて `~/.claude/codex-director/trial-log.md` へ追記する(初回はディレクトリごと作成)。この Skill ディレクトリは dotfiles リポジトリ配下なので、記録をこの中に置かない。ユーザーの許可なくリポジトリへ記録ファイルを追加しない。 -記録する項目: タスク概要 / 選択モデル / 推論量 / 一度で完了したか / 修正依頼の回数 / 最終的に合格したか / モデル選択は適切だったか。モデルの性能は自己評価でなく、実際の差分・テスト・差し戻し回数で判断する。項目は箇条書きで並べ、タスクごとに `## <日付> <リポジトリ>: <タスク名>` の見出しを立てる。 +タスクごとに `## <日付> <リポジトリ>: <タスク名>` の見出しを立て、次を箇条書きで並べる。タスク概要 / 選択モデル / 推論量 / 一度で完了したか / 修正依頼の回数 / 最終的に合格したか / モデル選択は適切だったか / 1段下げて発注したか。最後の項目には、下げた場合はその根拠(依頼文で埋めた判断)と結果を、下げなかった場合は上の段が必要だと考えた理由を書く。モデルの性能は自己評価でなく、実際の差分・テスト・差し戻し回数で判断する。 環境の制約や運用の失敗に気づいたら、ログに書くだけで終わらせない。同じ状況で再発するものはこの SKILL.md か `references/` へ昇格させる。ログは一度きりの記録で、次のタスクで読まれる保証がない。 @@ -140,6 +110,6 @@ Codex のサンドボックスはネットワークを閉じており、`127.0.0 - `references/model-routing.md` — モデルと推論量の選択基準。発注設計の前に読む。 - `references/delegation-template.md` — 発注前の整理項目と依頼文テンプレート。依頼文を書く前に読む。 -- `references/review-checklist.md` — 検収の確認項目、テストを壊して確かめる手順、差し戻しの進め方。Codex 完了後に読む。 +- `references/review-checklist.md` — 検収の確認項目、移動が書き換えでないことの機械的な照合(`scripts/` の2本)、テストを壊して確かめる手順、差し戻しの進め方。Codex 完了後に読む。 - `references/sandbox-network.md` — サンドボックスのネットワーク設定と実測結果。`listen EPERM` を見たとき、テスト実行を委任できるか迷ったときに読む。 -- `references/execution.md` — Skill 経由で呼んだ場合の進行確認・完了検知・報告の復旧手順。既定経路で報告を回収できなかったときだけ読む。 +- `references/execution.md` — 発注が通らないとき、報告を回収できないとき、ユーザーが `/codex:rescue` の使用を明示したときの手順。 diff --git a/.claude/skills/codex-director/references/delegation-template.md b/.claude/skills/codex-director/references/delegation-template.md index 954f06b..2963dff 100644 --- a/.claude/skills/codex-director/references/delegation-template.md +++ b/.claude/skills/codex-director/references/delegation-template.md @@ -45,6 +45,26 @@ Codex を呼ぶ前に次を明確にする。情報が足りなくても、コ ``` +## 報告前の裏取りを依頼文で要求する + +依頼文に「報告前の裏取り(必須)」の節を置くと、報告の質が上がる。実運用では、この節を入れて以降、報告の全項目が行番号つきになり、移し漏れの照合が1件ずつ根拠つきで返ってくるようになった。Codex は求められなければ、自分の作業を成功した前提で要約する。 + +次をそのまま `` の直前に置く。 + +```text + +報告を書く前に、次を実行する。省略しない。 + +1. 報告に書こうとしている主張を、実際のコードで確認する。ファイル名と行番号を添える +2. `git diff` を最初から最後まで通しで読む。依頼していない変更が混ざっていないか確認する +3. この依頼文が「保存すべき挙動」として挙げた項目を1つずつ照合し、各項目が満たされている根拠を示す +4. 追加・変更したテストが、変更前の実装でも通ってしまわないか確かめる。通るなら、そのテストは変更を検証していない +5. 確認できていないことを「確認済み」と書かない。未確認は未確認と明示する + +``` + +この節は検収を置き換えない。裏取りを求めても、実装AIが自分の作業を検証しきれない領域は残る(テスト網羅の不足、サンドボックスで走らない検査、完了誤認)。ディレクター側の検収は同じだけ行う。 + ## 依頼文に必ず含める指示 Codex がこの Skill の基本規則に沿って動くよう、依頼文(主に ``)へ次を織り込む。 @@ -61,6 +81,18 @@ Codex がこの Skill の基本規則に沿って動くよう、依頼文(主 `` は、目的を満たす実装が複数ありうるなら許容範囲まで書く。実運用では、判定条件を1つの数値に絞った依頼文のせいで、正しく動く実装を差し戻す無駄が発生した。検証したい振る舞いを書き、実装の選び方まで決めつけない。 +## 検証できない実装詳細を受入条件に書かない + +サンドボックスの外でしか確かめられない詳細を `` に書くと、Codex は確認できないまま従い、実行段階で落ちる。差し戻しの往復がそのまま増える。 + +実運用では、本番 D1 からローカル D1 へデータを移す実装で2回続けて起きた。「除外するのは `sqlite_%` と `d1_migrations`」と書いたら内部テーブル `_cf_KV` を拾って rowid 不在で落ち、「`BEGIN` / `COMMIT` を維持する」と書いたらローカル D1 が SQL のトランザクション文を拒否して落ちた。どちらもネットワークが要るため Codex 側では検証できない。 + +判断の順は次のとおり。 + +1. その詳細をディレクター側で確かめられるなら、先に実行して確認し、結果を `` に貼る。 +2. 確かめられないなら書かない。目的と満たすべき振る舞いだけを渡し、実装の詳細は Codex に決めさせる。 +3. どうしても方針を指定するなら「未検証の想定」と明示し、違っていたら報告するよう頼む。 + ## 検査コマンドの書き方 `` には、その環境で実際に使う検査コマンドを具体的に書く(例: 型チェック・lint・テストの各コマンド)。何を実行し、何が通れば完了かを Codex 側だけで判定できるようにする。書く前に次を確認する。 diff --git a/.claude/skills/codex-director/references/execution.md b/.claude/skills/codex-director/references/execution.md index a7d1a81..bbd757f 100644 --- a/.claude/skills/codex-director/references/execution.md +++ b/.claude/skills/codex-director/references/execution.md @@ -1,6 +1,21 @@ -# 実行経路の詳細と復旧手順 +# 実行経路のトラブルと復旧 -既定経路(Bash から companion を `run_in_background: true` で実行)で問題が起きたとき、またはユーザーが `/codex:rescue` の使用を明示したときに読む。 +既定経路(Bash から companion を `run_in_background: true` で実行)で発注が通らないとき、報告を回収できないとき、またはユーザーが `/codex:rescue` の使用を明示したときに読む。 + +## 発注が即失敗する + +`Task is still running. Use /codex:status before continuing it.` で止まったら、中断したジョブの記録が `running` のまま残っている。ハーネス側で止めても companion の状態は更新されない。 + +```bash +node "$P/scripts/codex-companion.mjs" status --all +node "$P/scripts/codex-companion.mjs" cancel +``` + +cancel 後の `--resume-last` は、まず `completed` のジョブ記録を探す。見つからなければ Codex のスレッド一覧を更新時刻順に辿るため、cancel したスレッドを再開することがある。中断分を捨てて出し直すなら `--resume-last` を外して新規タスクとして発注する。 + +## 出力の冒頭に `command not found` が並ぶ + +依頼文がシェルに食われている(バックティックがコマンド置換として実行された)。Codex を止め、依頼文をファイルへ書いてから渡す方式で出し直す。副作用で起動したプロセスが残っていないかも確認する。 ## なぜ Skill 経由を既定にしないか diff --git a/.claude/skills/codex-director/references/model-routing.md b/.claude/skills/codex-director/references/model-routing.md index 952179b..a0b1fb9 100644 --- a/.claude/skills/codex-director/references/model-routing.md +++ b/.claude/skills/codex-director/references/model-routing.md @@ -6,71 +6,93 @@ | 役割 | モデルID | 選択理由 | |---|---|---| -| 高速・低コスト | `gpt-5.6-luna` | `models_cache.json` で priority 3。3つの GPT-5.6 の中で最も軽量な位置づけ(他2つが対応する `ultra` を持たない)。低推論の定型作業に割り当てる | -| 標準 | `gpt-5.6-terra` | priority 2。この環境の既定モデル(`~/.codex/config.toml` の `model`)。通常実装のデフォルト | -| 高性能 | `gpt-5.6-sol` | priority 1。`models_cache.json` の説明が "Latest frontier agentic coding model"。最難関・高リスクに割り当てる | +| 基本 | `gpt-5.6-luna` | `models_cache.json` で priority 3。推論量を主なつまみにして、単純作業から高度な作業までこの1モデルで扱う。値下げ後は `terra` の約10分の1の単価 | +| 中間 | `gpt-5.6-terra` | priority 2。`~/.codex/config.toml` の既定モデル。`luna` + `xhigh` で届かず、`sol` を出すほどでもない作業に `xhigh` で使う | +| 最上位 | `gpt-5.6-sol` | priority 1。`models_cache.json` の説明が "Latest frontier agentic coding model"。`luna` で難しい作業に `high` 以上で使う | -GPT-5.6 が使えなくなった場合は、`~/.codex/models_cache.json`(`visibility: list` かつ `supported_in_api: true`)から priority 順に「高性能・標準・高速」の3役を割り当て直し、選んだIDと理由をこの表に反映する。 +GPT-5.6 が使えなくなった場合は、`~/.codex/models_cache.json`(`visibility: list` かつ `supported_in_api: true`)から priority 順に「最上位・中間・基本」の3役を割り当て直し、選んだIDと理由をこの表に反映する。 -## 推論量の実際(検証済み) +## 使える推論量(検証済み) -- `/codex:rescue`(companion)のフラグは `none | minimal | low | medium | high | xhigh` を受理する。`max`・`ultra` はフラグ側で弾かれる。 -- ただし GPT-5.6 の API が実際に受理するのは `none, low, medium, high, xhigh` で、**`minimal` は 400(unsupported_value)で拒否される**。`none` は正常に動く(実測確認済み)。 -- したがって rescue 経由の実効レンジは `none, low, medium, high, xhigh`。「推論を最小に」は `minimal` ではなく **`none`** を使う。「`high` の一段上」は **`xhigh`**。 -- `models_cache.json` の TUI 表示レベル(sol/terra は `ultra` まで、luna は `max` まで)は rescue 経由では届かない。`max`・`ultra` が本当に必要な難題は、この Skill の rescue ベースの範囲外(Codex を直接使う判断)になる。 +委任での実効レンジは `none, low, medium, high, xhigh`。「推論を最小に」は `none`、「`high` の一段上」は `xhigh`。 -## 4段の使い分け +- companion が受理するのは `none | minimal | low | medium | high | xhigh`(`codex-companion.mjs` の `VALID_REASONING_EFFORTS` にハードコード。Codex 側の新しい版 1.0.6 も同じ集合なので、プラグイン更新では変わらない)。`max` は発注前にエラーで止まる。 +- GPT-5.6 の API が受理するのは `none, low, medium, high, xhigh` で、**`minimal` は 400(unsupported_value)で拒否される**。`none` は実測で正常に動く。 +- `max` は API 側では通る(`codex exec --model gpt-5.6-luna -c model_reasoning_effort="max"` が成功し、rollout に `"reasoning_effort":"max"` が残ることを 2026-08-01 に確認)。ただし `codex exec` の直呼びは companion のジョブ管理(`status --all` / `cancel` / `--resume-last`)の外に出るため、委任の上限は `xhigh` のままとする。`luna` + `xhigh` で足りないときは `terra` + `xhigh` へ移る。 -以下はコード作業を例に説明するが、判断の軸(探索の要否・判断の要否・リスク・完了条件を検証できるか)は非コード作業にも同じく当てはまる。調査を伴う文章や検証の要る技術文章も、この軸でティアを選ぶ。 +## 既定は luna。推論量を主なつまみにする -### 高速・低コスト(`gpt-5.6-luna`)+ `none` +モデルを先に選ばない。`gpt-5.6-luna` + `high` を基本に置き、作業の難度に応じて推論量を上下させる。モデルを上げるのは `luna` で届かないと判断したときだけ。 -次をすべて満たすときだけ使う。 +| 段 | 指定 | 使う場面 | +|---|---|---| +| 定型 | `luna` + `none` | 変更対象が明示され、実装方法が一意。リネーム、指定された API 変更への追従、既知のパッチ適用、formatter/lint の自動修正、コード生成コマンドの実行、指定した検査コマンドの実行 | +| 単純 | `luna` + `low` | 対象ファイルを探す必要がある。軽い判断を伴う。`none` で一度失敗した | +| 小さめ | `luna` + `medium` | 複数ファイルにまたがるが、設計判断は依頼文で埋まっている。テストの追加位置や境界条件まで指示できている | +| 基本 | `luna` + `high` | 既定。どこから始めるか迷ったらここ | +| 高度 | `luna` + `xhigh` | 構造の理解が要る、影響範囲が広い。委任経路での推論量の上限 | +| 中間 | `terra` + `xhigh` | `luna` + `xhigh` で合格しなかったが、`sol` を出す理由には当たらない | +| 難しい | `sol` + `high` | 下記の条件に当たる。不足したときだけ `xhigh` | + +`sol` を `medium` 以下で使わない。`sol` を選ぶのは判定の難しさが理由なので、推論量を下げると選んだ理由と噛み合わない。 + +既定を `luna` に置いたのは、試験運用の偏りへの対処である。19発注の内訳は `sol` 7件・`terra` 9件・`luna` 2件で上位に偏り、ログ自身の評価でも `sol` が不要だった発注が2件あった。一方 `luna` の2件はどちらも一度で合格している。 + +基本より下げてよいかは、依頼文を書き終えた時点で Codex に残る裁量の量で決める。対象ファイルの特定から任せる、実装方針が複数ありうる、境界や基準をこちらで決め切れていない、のいずれかが残るなら下げない。ファイル数・行数は段の根拠にしない。`terra` + `medium` は、コード・設定・生成型・仕様書・テストにまたがる7ファイルの移行を、指示していない箇所を grep で見つけて直すところまで一度で通した。以下はコード作業を例に説明するが、軸は非コード作業にも同じく当てはまる。調査を伴う文章や検証の要る技術文章も同じ順で判定する。 + +`luna` の射程は実測で `terra` をかなり引き取れている。既定を `luna` に置いた後の7発注は、`high` 3件と `medium` 2件が差し戻し0、`xhigh` 2件が差し戻し1〜2で、いずれも最終合格した。`high` の射程には、新規アダプタの追加、`defineWorkflow` の素関数化、クラスの private メソッド6本の自由関数化、`getWorkItem` の I/O と組み立ての分離が入る。2ラウンド合計3657行を12ファイルへ分割して `tests/` の差分ゼロ、既存シンボルの消失も新規混入もゼロだった。以前なら `sol` を出していた種類の作業である。結果は trial-log に残し続ける。推論量を上げれば消費トークンは増えるので、段を下げた効果は差し戻し回数と所要時間の両方で見る。 + +合格しなかったときは、段を上げる前に原因を切り分ける。依頼文に書き切れていない判断があったなら、同じ段で条件を書き足して出し直す。コードベースの構造理解が届いていないと判断できたときだけ上の段へ移る。 + +### `luna` で守ること + +**`medium` 以下では、既存の記述や実装から値を導出する計算を任せない。** 日付・期間・境界・件数が成果物の核になるなら、確定値を依頼文へ書く。実測では、`luna` + `medium` が文書の構成・参照・コード読解を正しく処理しながら、期間計算の日付を1週ずらし、境界も誤った。差し戻しで日付をこちらで確定して渡すと、同じ `luna` + `medium` で一度で通っている。導出そのものが未確定の判断なので、渡さずに投げた時点で段を下げる前提を外している。 + +**報告の裏取りが甘い。** 既存の分岐が例外を再生成すると報告したが、実際は呼び出しが `try` の外にあり再生成されなかった。コードを読めば分かる内容を推測で書いている(同種の食い違いを `sol` は前の発注で自力で3件見つけていた)。この差は依頼文の ``(`delegation-template.md`)と、差分を機械的に照合する検収(`review-checklist.md`)で埋める。格上げでは埋めない。 + +### `sol` + `high` + +次のいずれかに当たるときだけ使う。 + +- 正しさの判定が難しい実装(暗号・認証、時刻ずれや有効期間の境界、並行処理と競合状態、データ消失や移行のリスク)。実測で効果が出た領域で、時計ずれ・有効期間の上限・base64url のパディング・鍵形式の事前検出を一度で正しく書いた +- 一次資料と実装の広域照合。こちらの仮説を根拠つきで否定してほしい調査 +- 新規ファイルを含む大規模な実装で、構成そのものを組ませる + +最大の推論量を常用しない。`sol` + `high` でも不足したときにだけ `xhigh` を検討する。 + +### 格上げで解決しないこと + +次を理由に `sol` を選ばない。実測で効果が出なかったか、別の手段で解くべき領域である。 -- 変更対象または探索範囲が明示されている -- 実装方法がほぼ一意 -- 設計判断が不要 -- 完了条件をコマンドまたは差分で判定できる -- 失敗しても容易に巻き戻せる +- ファイル数・行数が多いだけ +- テスト網羅やテスト設計の不足を埋めたい。`sol` + `high` でも「テストが書きにくい設計のまま提出する」「未カバーの分岐を残す」は起きた。必要なテストは依頼文で具体的に指定するか、検収で捕まえる +- 完了誤認や事実と食い違う報告を防ぎたい。workers プールの起動失敗を「完了」と報告した事例は `sol` で起きた。件数を数える、実装を壊して確かめる、といった検収の型で防ぐ領域と混同しない +- 設計判断を丸ごと委ねたい。モデルに設計を任せているだけで、検収の負荷は下がらない -想定作業: 明示された識別子のリネーム、指定された API 変更への追従、既知のパッチ適用、formatter/lint の自動修正、コード生成コマンドの実行、指定ファイルへの機械的変更、指定された検査コマンドの実行。 +## 指示の質で段を下げる -### 高速・低コスト(`gpt-5.6-luna`)+ `low` +段を1つ下げられるかは、依頼文で Codex の迷う余地を消せたかで決まる。実測でも、`sol` から `terra` + `medium` へ意図して下げた発注(設計判断を依頼文で全部確定させた5件の修正実装)が差し戻し0で合格し、逆に判定条件を絞りすぎた依頼文が正しい実装を差し戻させた。発注前に次を確認する。 -- 小規模だが対象ファイルを探す必要がある -- 軽い判断を伴う -- 単純作業と断定するには不確実性が残る -- `none` で一度失敗した +- 設計判断(基準・境界・命名・分割の方針)を依頼文で決め切ったか +- 環境依存の事実(外部 API の応答、DB の制約、既定バージョン)を先に確かめて `` に貼ったか。確かめられない詳細は書かない(`delegation-template.md`) +- 禁止したい文字列や構文にバリアントがあるなら列挙したか。目的の記述は網羅性を保証しない -### 標準(`gpt-5.6-terra`)+ `medium` +これらを書けていない状態で段を上げても差し戻しは減らない。書き切れたなら1つ下げて発注し、結果を記録する。 -通常の実装作業のデフォルト。モデル選択に迷ったらこれ。 +## 差し戻しの段 -- 複数ファイルの変更 -- 一般的な機能追加 -- 原因がある程度絞られた不具合修正 -- テスト設計を伴う変更 -- 小規模なリファクタリング -- コードベースの構造を踏まえた判断 +差し戻しで段を上げるのは既定にしない。実測では、`sol` の初回出力に対する差し戻しが `terra` + `medium` で通り、`terra` の差し戻しが `luna` + `low` で通っている。範囲が限定された時点で必要な判断量は減っている。 -### 高性能(`gpt-5.6-sol`)+ `high` +- 原因が依頼文の不備 → 同じ段で条件を書き直して出し直す +- 修正範囲が特定のファイル・関数に絞れた → 1つ下げる +- 原因不明のまま2回落ちた → 上げる。この場合だけ `sol` を検討する -- 原因不明の不具合 -- 設計変更を伴う -- 変更の影響範囲が広い -- 認証・権限・セキュリティに関わる -- 並行処理や競合状態を扱う -- データ消失や移行リスクがある -- 間違いを自動テストだけでは検出しにくい -- 標準モデルで合格ラインに達しなかった +## コスト差の扱い -最大の推論量を常用しない。`sol` + `high` でも不足したときにだけ、一段上の `xhigh` を検討する。 +単価は段の選択の決め手にしない。動機の強さを測るために倍率だけ持っておく。 -## 実測からの補正 +2026-07-30 の値下げ後、100万トークンあたりの出力単価は `luna` $1.2 / `terra` $12 / `sol` $30、入力は $0.2 / $2 / $5([OpenAI の告知](https://openai.com/index/advancing-the-price-performance-frontier-with-gpt-5-6/)。2026-07-31 に確認)。倍率では `luna` が `terra` の約10分の1、`terra` が `sol` の約2.5分の1。改定前の `luna` は `terra` の約2.5分の1だったので、`luna` を試す動機はこの改定で強まった。 -試験運用(`~/.claude/codex-director/trial-log.md`)で分かった傾向。上の基準を実測で補正する。 +ただし発注1件のコストは、差し戻し1回で発生するディレクター側の検収サイクル(差分の読み直し、テストの再実行、追加の依頼文)より小さい。安いから下げるのではなく、依頼文で迷う余地を消せたから下げる。単価は判定の順序を覆さない。 -- `terra` + `medium` の射程は表の記述より広い。コード・設定・生成型・仕様書・テストにまたがる7ファイルの移行を、指示していない箇所を grep で見つけて直すところまで一度で通した。ファイル数の多さだけを理由に `sol` へ上げない。 -- テスト網羅とテスト設計の不足は、推論量を上げても解決しない。`sol` + `high` でも「テストが書きにくい設計のまま提出する」「未カバーの分岐を残す」は起きた。必要なテストは依頼文で具体的に指定するか、検収で捕まえる。 -- モデルの格上げで防げない失敗もある。workers プールの起動失敗を「完了」と報告した事例は `sol` で起きた。検収の型(件数を数える、実装を壊して確かめる)で防ぐ領域と、モデル選択で解く領域を混同しない。 -- 暗号・認証のような正しさの判定が難しい実装は `sol` が実際に効果を出した(時計ずれ、有効期間の上限、base64url のパディング、鍵形式の事前検出を一度で正しく書いた)。判定の難しさは引き続き格上げの理由になる。 +単価は変わる前提で扱う。`models_cache.json` に単価は載っていないので、この節の数値は確認日つきの参考であり、判断に効くほどの差を感じたら出典を引き直す。サブスクリプション経由の実行では請求は単価ではなくクレジット消費になる。 diff --git a/.claude/skills/codex-director/references/review-checklist.md b/.claude/skills/codex-director/references/review-checklist.md index f02baf3..034db33 100644 --- a/.claude/skills/codex-director/references/review-checklist.md +++ b/.claude/skills/codex-director/references/review-checklist.md @@ -27,6 +27,35 @@ Codex 完了後に読む。報告だけを信用せず、必ず実際の差分 - プールやプロジェクトを分けている構成では、プールごとの件数を見る。合計だけでは欠落が隠れる。 - 件数を確認するテスト実行はディレクター側で行う。Codex のサンドボックスでは走らない検査がある(`sandbox-network.md`)。 +## 移動であって書き換えでないことを確かめる + +ファイル分割やモジュール移動を委任したときは、「振る舞いを変えていない」という報告を目で追わない。formatter が移動先で行を再flowするため、`git diff` の行単位比較も行のソート比較も使えない。実測では、oxfmt が三項演算子を1行へ畳み、冗長な括弧を落としたので、純粋な移動でも差分が数百行になった。 + +宣言単位とリテラル単位で機械的に照合する。スクリプトは `scripts/` にある。どちらも移動前のファイルを `git show HEAD:` で取り出して渡す。 + +```bash +S= +git show HEAD:src/foo.ts > $S/old-foo.ts + +# トップレベル宣言を名前で突き合わせ、空白を潰して本体を比較する +node ~/.claude/skills/codex-director/scripts/compare-symbols.mjs \ + $S/old-foo.ts src/foo.ts src/foo-a.ts src/foo-b.ts + +# テンプレートリテラル(SQL・GraphQL・プロンプト)を多重集合として比較する +node ~/.claude/skills/codex-director/scripts/compare-literals.mjs \ + $S/old-foo.ts -- src/foo.ts src/foo-a.ts src/foo-b.ts +``` + +`compare-symbols.mjs` は、消えたシンボル・混入したシンボル・本体が変わったシンボルを一覧で返す。読み方は次のとおり。 + +- MISSING と ADDED が空でなければ、そこだけを見る。純粋な移動なら両方とも空になる +- CHANGED は本体を並べて表示する。差分が `export` の付加と formatter の再flowだけなら合格。実測の2ラウンド・3657行の分割では、CHANGED 37件がすべてこの2種類だった +- クラスのメソッドは検出しない。メソッドを自由関数へ変える委任では、この結果を出発点にして該当箇所を手で読む + +`compare-literals.mjs` は、外部へ送る文字列が1文字も変わっていないことを示す。GraphQL クエリや SQL の移動では受入の前提に置く。実測では SQL 30本・GraphQL 44本の一致を一発で確認でき、唯一出た差分も `${field}` → `${input.field}` の引数名変更だと即断できた(生成される文字列は同一)。 + +宣言もリテラルも一致したうえで、構造を変えた箇所(メソッドの自由関数化、関数の分割)だけを手で読む。検収の時間はここへ集中させる。 + ## テストが本当に欠陥を検出するか確かめる テストを読むだけでは、それが欠陥を捕まえるかは分からない。実装をわざと壊し、意図したテストだけが落ちることを確認する。実運用ではこの工程が、Codex 自身の検証をすり抜けた穴(テストが存在しない領域の欠陥)を見つけた。 @@ -61,15 +90,3 @@ Codex 完了後に読む。報告だけを信用せず、必ず実際の差分 - 解決できなかった理由 - 高性能モデル(`gpt-5.6-sol`)や一段上の推論量(`xhigh`)への切り替えで改善が見込めるか - 人間の判断が必要な点 - -## 最終報告 - -検収を通過したら、次を簡潔に報告する。 - -- 実装AIが変更した内容 -- 選択したモデルと推論量 -- 実行した検査 -- ディレクターAIが確認した内容 -- 残っている制約や未確認事項 - -モデルの性能は自己評価だけで決めない。実際の差分・テスト・差し戻し回数を判断材料にする。 diff --git a/.claude/skills/codex-director/scripts/compare-literals.mjs b/.claude/skills/codex-director/scripts/compare-literals.mjs new file mode 100644 index 0000000..23a1e4a --- /dev/null +++ b/.claude/skills/codex-director/scripts/compare-literals.mjs @@ -0,0 +1,33 @@ +// Compare the multiset of template literals (SQL + GraphQL) across a file split. +import { readFileSync } from "node:fs"; +const literals = (paths) => { + const all = []; + for (const path of paths) { + const source = readFileSync(path, "utf8"); + // crude backtick-string scanner that respects ${...} nesting + for (let i = 0; i < source.length; i += 1) { + if (source[i] !== "`") continue; + let j = i + 1, depth = 0, text = ""; + for (; j < source.length; j += 1) { + if (source[j] === "\\") { text += source.slice(j, j + 2); j += 1; continue; } + if (source[j] === "$" && source[j + 1] === "{") depth += 1; + if (source[j] === "}" && depth > 0) depth -= 1; + else if (source[j] === "`" && depth === 0) break; + text += source[j]; + } + i = j; + const normalized = text.replace(/\s+/g, " ").trim(); + if (normalized.length > 0) all.push(normalized); + } + } + return all.sort(); +}; +const split = process.argv.indexOf("--"); +const before = literals(process.argv.slice(2, split)); +const after = literals(process.argv.slice(split + 1)); +const count = (list) => list.reduce((m, v) => m.set(v, (m.get(v) ?? 0) + 1), new Map()); +const [b, a] = [count(before), count(after)]; +let mismatches = 0; +for (const [text, n] of b) if ((a.get(text) ?? 0) !== n) { mismatches += 1; console.log(`ONLY/COUNT-DIFF in OLD (x${n} vs x${a.get(text) ?? 0}):\n ${text.slice(0, 300)}\n`); } +for (const [text, n] of a) if (!b.has(text)) { mismatches += 1; console.log(`NEW-ONLY (x${n}):\n ${text.slice(0, 300)}\n`); } +console.log(`old literals: ${before.length}, new literals: ${after.length}, mismatches: ${mismatches}`); diff --git a/.claude/skills/codex-director/scripts/compare-symbols.mjs b/.claude/skills/codex-director/scripts/compare-symbols.mjs new file mode 100644 index 0000000..94ee95d --- /dev/null +++ b/.claude/skills/codex-director/scripts/compare-symbols.mjs @@ -0,0 +1,58 @@ +// Extract top-level declarations by name and compare them across a file split. +// Usage: node compare-symbols.mjs +import { readFileSync } from "node:fs"; + +const declaration = /^(?:export\s+)?(?:declare\s+)?(?:async\s+)?(?:const|let|function|class|type|interface)\s+([A-Za-z0-9_$]+)/; + +const symbols = (path) => { + const lines = readFileSync(path, "utf8").split("\n"); + const found = new Map(); + for (let index = 0; index < lines.length; index += 1) { + const match = declaration.exec(lines[index]); + if (match === null) continue; + // Consume until the declaration's brackets balance and it ends with ; or } + let depth = 0; + let text = ""; + let cursor = index; + for (; cursor < lines.length; cursor += 1) { + const line = lines[cursor]; + text += line + "\n"; + for (const character of line) { + if ("{([".includes(character)) depth += 1; + if ("})]".includes(character)) depth -= 1; + } + if (depth <= 0 && cursor > index) break; + if (depth <= 0 && /[;}]\s*$/.test(line)) break; + } + index = cursor; + found.set(match[1], text.replace(/\s+/g, " ").trim()); + } + return found; +}; + +const [oldPath, ...newPaths] = process.argv.slice(2); +const before = symbols(oldPath); +const after = new Map(); +for (const path of newPaths) { + for (const [name, body] of symbols(path)) { + if (after.has(name)) console.log(`DUPLICATE across new files: ${name}`); + after.set(name, body); + } +} + +const missing = []; +const changed = []; +const added = []; +for (const [name, body] of before) { + if (!after.has(name)) missing.push(name); + else if (after.get(name) !== body) changed.push(name); +} +for (const name of after.keys()) if (!before.has(name)) added.push(name); + +console.log(`identical: ${[...before.keys()].filter((n) => after.get(n) === before.get(n)).length}/${before.size}`); +console.log(`MISSING (in old, not in new): ${missing.join(", ") || "none"}`); +console.log(`ADDED (new only): ${added.join(", ") || "none"}`); +console.log(`CHANGED bodies: ${changed.join(", ") || "none"}`); +for (const name of changed) { + console.log(`\n--- ${name}\nOLD: ${before.get(name)}\nNEW: ${after.get(name)}`); +}