English · 日本語
AI エージェント(と人間)で並行開発する¶
複数のセッション(人間と AI エージェント)が同時に同じリポジトリで作業しても、衝突したり 互いの機能を壊し合ったりしないための運用ガイドです。要点は
CLAUDE.mdにあり、 このページはその詳細版です。貢献が初めての方は、コントリビューターワークフローチュートリアルから 始めてください。 最初の提案と最初の実装を、手を動かしながら辿る実地の手引きです。このページは、その規則 (ゲート、ブランチ、BE ID のライフサイクル、モデルの階層、PR テンプレート)についてそこからリンクされる 詳細なリファレンスです。
設計全体を支えているのは、決定的なゲートが軽く、どこでも走り、CI(継続的インテグレーション)と完全に一致するという一つの性質です。 これがあるから作業を安全に並列展開できます。どのブランチも単独で検証可能なので「ローカルで green」が 「CI で green」を確実に予測し、テストスイートが、あるセッションの変更が別セッションの機能を壊したことを 捕まえる回帰ネットになります。
ゲート¶
make check
各ステップは .github/workflows/ci.yml と完全に一致します。
ステップの現在の一覧は、ゲートの唯一の拠り所である CLAUDE.md にあります。Python
コアは Simulator 不要なので Linux で数秒で完了します。変更を「完了」と呼ぶ前と push 前に必ず走らせて
ください。実機 E2E(macOS + Simulator)は別の重い経路で、このゲートには 含まれません。
カバレッジのラチェット(BE-0385)¶
ゲートは2つのカバレッジ下限を強制します。どちらも、放っておいて動くことはありません。
全体の下限は、pyproject.toml の [tool.coverage.report] にある
fail_under です。bajutsu パッケージ全体のカバレッジが全体の下限を割り込むと make test が
失敗します。下限は Makefile の pytest 行の --cov-fail-under フラグではなく、coverage.py 自身の
設定に置いています。下限を読む側すべてが1つの宣言的な情報源を共有するためです。シェルのレシピ行から
拾った値では、後の編集で誰にも気付かれずに下限が変わってしまいます。
パッケージ全体を1つの数値で表すやり方には、弱点が2つあります。1つ目は、その数値がスイートの実力から
離れていくことです。そこで make lint-pr は乖離アドバイザリを走らせ、実測の総計と下限を比べて、
差が2ポイントを超えたら通知します。アドバイザリがビルドを落とすことはありません。ここで強制的に
失敗させると、十分にテストされたコードを足しただけの pull request が、差をさらに広げたという理由
だけで止まってしまうからです。通知が出たら fail_under を上げ、新しい数値をコミットしてください。
2つ目の弱点は、1つの数値ではカバレッジの薄い場所が見えなくなることです。その死角を塞ぐのが
ファイル単位の下限です。ファイル単位の下限は coverage-floors.json
に置きます。各ソースファイルが最後に計測されたときのブランチカバレッジを記録した、コミット対象の
スナップショットです。make check は make lint-coverage-floors を実行し、記録された数値を下回った
ファイルが1つでもあれば失敗します。あるファイルが 65% から 40% まで落ちても、残りのコードがその損失を
吸収して総計は全体の下限より上にとどまります。それに気付けるのが、ファイル単位の下限です。
ファイル単位のチェックが止めるのは下降だけです。上昇は、スナップショットに触れなくても通ります。
カバレッジを改善したという理由で pull request を落とせば、ラチェットが促そうとしている行動そのものを
罰することになるからです。チェックはスナップショットへの書き込みも行いません。format と
format-check の分離と同じ形です。強制する側の基準を自分で書き換えるゲートは、上下どちらにも
ラチェットしてしまいます。
make coverage-floors # 直前に計測した値へ coverage-floors.json を書き換える
カバレッジが上がったら、このコマンドを意図的に実行して、結果をコミットしてください。同じコマンドは、 受け入れると判断した下降のための逃げ道でもあります。上昇と下降を分けて印字するので、受け入れた下降をコミット前に 確認できます。
make coverage-floors は、main に追いついた作業ツリーでだけ実行してください。書き換えの対象は、
きっかけになった1ファイルの下限だけではなく、計測したすべてのファイルの下限です。main より遅れた
作業ツリーは、ほかのファイルを、ブランチが分岐した時点の姿で計測します。遅れた作業ツリーから記録すると、
2種類の誤った数値が書き込まれます。1つは、ブランチが分岐したあとに main で上がった下限を下げて
しまうことです。下がった下限をゲートが捕まえることはありません。ファイル単位のチェックが止めるのは、
カバレッジの下降だけだからです。もう1つは、並行してマージされた別の pull request が下降を受け入れて
下げた下限を、古い値に書き戻すことです。どちらの pull request も、それ自体が赤くなることは
ありません。それぞれが手元の作業ツリーを計測したからです。到達できない下限が現れるのは、マージの結果
だけです。そのあとは、すべての貢献者が pre-push フックで到達できない下限に出会います。先に rebase
しておけば、スナップショットと、計測の対象になったコードとが離れずに済みます。make preflight は、
ゲートを走らせる前に fetch と rebase を行います。
到達できない下限が main に入ってしまったときも、復旧は同じコマンドで行います。新しく取得した main
で make coverage-floors を一度実行し、書き直したスナップショットをコミットしてください。
statement が10未満のファイルには、下限をまったく置きません。分岐を1つ
取りこぼしただけで、そうしたファイルの割合は数ポイント動き、回帰ではなく計測の揺れでゲートが落ちて
しまうからです。カバレッジは環境によってもわずかに変わることがあります。OS が違う場合や、あるテストが必要とする
ツールの入っていない機械で計測した場合です。ある機械で記録した下限が、別の機械では下降として読まれる
場合があります。その場合も make coverage-floors で解決します。
1 トピック 1 ブランチ¶
mainから派生します。エージェントはclaude/<トピック>、人間は<user>/<トピック>。- 各ブランチは小さく単一目的に保ちます。小さな差分は速くマージでき、衝突もまれです。
- 人間が頼まない限り PR は作りません。自分のブランチに push し、PR は人間に開いてもらいます。
赤いまま push しない¶
追跡対象の pre-push フック が make check を走らせ、失敗したら push を拒否します。
make setup # uv sync --group dev + git フックの有効化(クローン直後に 1 回)
core.hooksPath はクローンごとのローカル設定で、clone/pull では伝播しません。そのため既存クローンには
入っていませんが、覚えておく必要はありません。make check(および make hooks)が毎回これを張り直すので、
push 直前にゲートが自己修復されます。Claude Code の web セッションでも
.claude/hooks/session-start.sh が自動で設定します。
git push --no-verify は、緊急時であっても使わないでください。 フックを迂回しても時間の節約には
なりません。ターミナルの手元で止まるはずだった make check の失敗を、共有ブランチに届いた後の CI へ
先送りするだけだからです。フックが本物の不具合ではなく誤検知で赤くなっているように見えるときは、原因
そのものを直すか、クリーンな別の worktree で同じチェックを再現してから push してください。たとえば
lint-skills は、.claude/worktrees/ に残った別セッションの状態が原因で赤くなることがあります。
この規則は、git 内部の仕組みだけでは強制できません。--no-verify は、git がフラグを解釈した時点で
.githooks/pre-push を含むすべてのフックを無条件にスキップします。push という名前の git エイリアスを
用意しても、この隙間は埋まりません。git 自身のドキュメントには「既存の Git コマンドを隠すエイリアスは
無視される」(git help config)と明記されています。このリポジトリの push に対して実際に試したところ、
そのとおりでした。エイリアスを設定しても、git push と git push --no-verify のどちらも、エイリアスを
無視して組み込みコマンドをそのまま実行します。git がフラグを見る前に残っている唯一の場所は、コマンド名の
解決そのものであり、これを制御できるのは本物の git を置き換えるラッパーだけです。
scripts/install-no-verify-guard.sh は、このラッパーを導入するインストーラーです。シェルの設定
ファイルに git() 関数を追記します。この関数は、.githooks/no-verify-guard-marker をトップレベルに
持つリポジトリ(このリポジトリを含む)の中でだけ、push への --no-verify を拒否します。それ以外の
リポジトリでは発動しません。make setup は、新規チェックアウトのたびにこのインストーラーをベスト
エフォートで自動実行します。開発を始めた瞬間から保護が効くようにするためであり、時間に追われて忘れ
がちな別ステップに頼らないためです。make git-guard-install は、同じインストーラーを単独で実行する
コマンドです。この仕組みが入る前にセットアップ済みのクローンや、追記したブロックを削除した後、あるいは
BAJUTSU_GUARD_RC_FILE で別の設定ファイルを指定したいときに使います。検出には $SHELL を使いますが、
これは今動いているシェルではなく、ログインシェルの名前です。両者が異なる場合(bash のログインシェルの上で
zsh を起動している場合など)は、BAJUTSU_GUARD_RC_FILE を明示的に渡してください。
これは個人の補助であり、リポジトリ全体を保証する仕組みではありません。このブロックを削除するか、
command git push --no-verify を直接呼び出せば、素通りします。マージ前に CI が make check を独立に
再実行することが、この規則を実際に成立させている土台であり、このインストーラーはその手前の手間を
省くだけです。
同じ core.hooksPath は、追跡対象の commit-msg フック(.githooks/commit-msg、BE-0069)も配線します。subject がスコープ付きの conventional subject(type(scope): … または docs: …)でないコミットをブロックし、機械的な規約をレビューではなくコミット時に捕まえます。意図的に狭く、merge / revert / fixup / squash のコミットは通し、uv が PATH に無ければ no-op です。単発の回避は git commit --no-verify です。
挙動を変えたらテストも一緒に変えてください。スイートは、その変更から他の全セッションを守る契約です。
このルールの短縮版は CLAUDE.md にあります。
コミット前にシークレットをブロックする¶
core.hooksPath は、追跡対象の pre-commit フック(.githooks/pre-commit)と prepare-commit-msg フック(.githooks/prepare-commit-msg)も配線します。どちらも gitleaks を使います。pre-commit フックはステージ済みの全ファイルを禁止パターン(AWS の認証情報、GitHub のトークン、Anthropic の API キー、貼り付けられた秘密鍵のブロックなど)でスキャンし、一致すればコミットを拒否します。prepare-commit-msg フックは、別ブランチの履歴からシークレットを取り込んでしまう merge に対して同じチェックを行います。既存の commit-msg フック(.githooks/commit-msg)にも、コミットメッセージ本文向けの同じスキャンを、subject のスコープチェックと並べて追加しました。git は 1 つのフック名につき 1 本のスクリプトしか実行しないため、両方のチェックを 1 つのファイルにまとめています。
パターンそのものは追跡対象のファイル、.gitleaks.toml に置いてあります。ローカルの git config に設定を保存するツール(core.hooksPath と同じく clone/pull では伝播しない、クローンごとの設定です)とは違い、gitleaks はこれを普通の追跡対象ファイルとして直接読み込むため、クローンごとの登録手順を自己修復する必要がそもそもありません。gitleaks 自身の組み込みルールセット(AWS の認証情報、GitHub のトークン、PEM 秘密鍵)を拡張し、このリポジトリ固有の 2 つの形、Anthropic の API キーまたは OAuth トークンと、deploy/self-host/ の実際のデプロイ時シークレットである BAJUTSU_SERVE_TOKEN / GRAFANA_ADMIN_PASSWORD を追加しています。gitleaks 未インストールのときはすべて緩やかに縮退し、フックと(後述の)make lint-secrets はコミットやゲートをブロックせず、通知だけを出してスキップします。インストールは brew install gitleaks(macOS。Brewfile にも記載)か、リリースバイナリから行えます。パターンには一致するもののシークレットではない文字列(フィクスチャ、ドキュメント中のプレースホルダー、シェル変数の参照など)があれば、パターン自体を緩めるのではなく、.gitleaks.toml 内の絞り込んだ [[allowlists]] エントリで除外してください。これは gitleaks 自身が用意する例外の仕組みです。
ローカルのフックは、それが配線されていて --no-verify で回避されていないクローンにしか効きません。そこで CI は同じスキャンを独立に実行します。make lint-secrets は追跡対象の全ファイルを再スキャンし、make check に組み込まれています。make check 自体がローカルと CI の両方で走ることで、make setup を飛ばしたクローンにも --no-verify のコミットにも、シークレットをレビューの目をすり抜けさせません。
このルールの短縮版は CLAUDE.md にあります。
早めに rebase し、小さな衝突のうちに統合する¶
make preflight # git fetch origin && git rebase origin/main && make check のあと「完了の定義」リマインダ
make preflight(scripts/preflight.sh、BE-0069)は pre-push の手順を
早めに走らせる版です。fetch して origin/main に rebase し、ゲートを走らせたあと、「完了の定義」の
リマインダ(両言語のドキュメントを触ったか、挙動の変更と一緒にテストを変えたか、出荷なら Status を
切り替えたか)を表示します。これは助言的で、人が起動するものです。pre-push フックはすでに make check を
ゲートしているので、これは出荷前の二重のゲートではなく、完了したと思う前に自分で早めに回すためのものです。
個々の手順を覚えておく必要はなく、いつでも走らせられます。
こまめに rebase すれば、他セッションのマージ済み作業に早く出会えます。衝突が 1〜2 行のうちに解消でき、 最後にまとめて絡まったマージを解く必要がなくなります。
make hooks は、それでも残る衝突の痛みを和らげる 3 つのローカル git 設定も自己修復します(BE-0043)。手で
設定する必要はありません。
uv.lockのマージドライバ(scripts/merge-uv-lock.sh、.gitattributesでマッピング)。競合時に resolver の出力を行マージするのではなくpyproject.tomlからuv.lockを再生成します。pyproject.toml自体が競合している場合はuv lockが 失敗し、git はuv.lockを競合のまま残します。先にpyproject.tomlを解決してから再マージしてください。- APM の生成物のマージドライバ(
scripts/merge-apm-generated.sh、 同じく.gitattributesでマッピング)。同じ理由から、競合時に.apm/skills/から再生成します(BE-0390)。対象はapm installの生成物のうちコミットする 二つ、すなわちapm.lock.yamlと配置先の.claude/skills/**です。どちらも手では解決できません。 ロックファイルはファイルごとの SHA-256 を並べたものであり、いずれも正しい値がどちらの側にもない 生成バイト列だからです。正しい値は、マージ後のソースから再インストールが書き出す内容です。 分量の大半は配置先が占めます(SKILL.mdが 14 個とreferences/が 5 個。ロックファイルは 数百行です)。しかも手で解決したときの危険は配置先の方が大きく、うまくいったように見えて、 ソースと一致しない配置先が残ります。これは後からmake lint-skillsのずれとして現れ、 マージと結び付けて考えるのは困難です。したがって、手で解決するのは.apm/skills/だけにして、 残りはドライバに任せてください。ドライバが書き出す内容は、uv.lockのドライバと同じく暫定的です。 git はマージ結果を書き出す前にドライバを走らせるので、内部のapm installはマージ前の作業ツリーを 読みます。この隙間は、uv lock --checkがuv.lockのドライバを支えるのと同じくmake lint-skillsが塞ぎます。スキルの競合を解決したらmake skillsを実行し、書き換わった ロックファイルと配置先をコミットしてください。 rerere(記録した解決の再利用)。一度解決した衝突は、同じ衝突が次に現れたときに自動で再適用されます。
core.hooksPath と同様、これらは clone/pull が引き継がないクローンごとのローカル git 設定なので、
make check / make setup が毎回再配線します。
このルールの短縮版は CLAUDE.md にあります。
worktree で同時セッションを隔離する¶
2 つのエージェントが同じチェックアウトを編集してはいけません。各セッションに専用の
worktree + ブランチを与えます(.git は 1 つを共有します)。
作成方法はエージェント環境ごとに決まります。Claude Code は、独自の worktree 機能を使ってかまいません。
配置先は .claude/worktrees/ です。それ以外の環境(Cursor、Codex、通常のシェル、今後のエージェント)は、
トピック用の worktree を make worktree(下記)で作成します。これらの環境でパスを手で決めて
git worktree add しないでください。fetch、ブランチ名、配置(../bajutsu-<topic>)、make setup は
Makefile のターゲットが引き受けます。
# メインのチェックアウトから(Claude Code 以外では必須)
make worktree TOPIC=<topic> # ../bajutsu-<topic> に claude/<topic> ブランチ
make worktree TOPIC=<topic> PREFIX=<user> # 人間の <user>/<topic> ブランチ
make worktree(scripts/worktree.sh、BE-0069)が手順をまとめて実行します。
git fetch origin、git worktree add ../bajutsu-<topic> -b claude/<topic> origin/main、続いて新しいツリーで
make setup(依存と自己修復する git フック)です。ブランチの接頭辞は既定で claude ですが、人間のブランチには
PREFIX=<user> を渡します。
git fetch origin は組み込みで省略できません。origin/main は fetch したときだけ進むローカルの追跡 ref です。
これを飛ばすと前回 fetch した時点の古い main から worktree を切ることになり、他セッションが既にマージで
解消したはずの衝突を再び持ち込みます。コマンドが先に fetch するので、この落とし穴にはまることはありません。
ブランチがマージ(または破棄)されたら片付けます。
git worktree remove ../bajutsu-<topic>
生成物とスクラッチ出力(runs/、tmp/、.venv/、ビルド成果物)は意図的に gitignore 済みです。
コミットに混ぜず、worktree を独立に保ってください。
worktree 間で共有してはならない2つの設定¶
core.bare と core.worktree は、ただ1つの作業ツリーを指す設定です。そのため git は通常、共有の
.git/config に書かれた値をメインの作業ツリーだけに適用します。リンクされた worktree は、同じ値を
無視します。ところが extensions.worktreeConfig を有効にすると、git はこの例外を取り下げます。共有
された値が、すべての worktree を支配するようになります。
git-worktree(1) も、core.worktree を
「共有してはならない」、core.bare を「値が core.bare=true なら共有してはならない」と定めています。
共有された core.worktree は、すべての worktree を一度に、しかも静かに誤った場所へ向けます
(issue #1803)。--git-dir はローカルの位置を返すのに、あらゆるコマンドが読む作業ツリーだけが別の
ものになるからです。git status は、別のブランチのファイルを変更済みとして並べます。git add は、
成功したと報告しながら何も変えません。git commit --amend はファイルをコミットから落とし、
git checkout -- <path> は同時に走る別セッションのディレクトリへ書き込みます。core.worktree を
消すと、今度は共有された core.bare = true が現れます。以後あらゆる git の呼び出しが
fatal: this operation must be run in a work tree で失敗し、make check も通らなくなります。
そこで make hooks は、extensions.worktreeConfig が有効なまま、共有の設定に core.worktree か
true の core.bare があれば中断します。中断させているのは
scripts/check_worktree_config.sh です。check、setup、
worktree はいずれも make hooks を経由するので、1箇所で3つとも守れます。中断するだけで、修復は
しません。2つの設定は、このリポジトリのツールが書いたものではないからです。正しい直し方も、その
チェックアウトがメインの作業ツリーかリンクされた worktree かで変わります。推測で書き換える代わりに、
次の手当てを表示します。該当の worktree で実行してください。
GIT_WORK_TREE=. git config --unset-all core.worktree # 共有の設定にある場合だけ
GIT_WORK_TREE=. git config --unset-all core.bare # 共有の値が true の場合だけ
この2点は、どちらも省けません。GIT_WORK_TREE=. は飾りではありません。git は、依頼された処理を
実行する前にリポジトリの準備段階で core.worktree を解決します。設定が削除済みの worktree を指して
いると、修復コマンド自体が fatal: Invalid path で落ちます。上書きしない限り、この状態は続きます。
もう1点は、共有のファイルから消す必要があることです。git config --worktree core.bare false は手元の
worktree しか直しません。共有の値は残り、ほかのすべての worktree を支配し続けます。今回の設定ミスが
気付かれなかったのも、そのためです。なお --unset ではなく --unset-all を使うのは、キーが複数の値を
持つとき --unset が実行を拒むからです。値が1つなら、どちらも同じ結果になります。
どちらかの設定が本当に必要な worktree もあります。たとえば、core.bare = true を保つベアリポジトリ
です。消したあとで git config --worktree を使い、自分の設定ファイルに書き戻します。共有のファイル
には書きません。
このルールの短縮版は CLAUDE.md にあります。
エージェントスキル: 単一のソースと単一の配備先(BE-0390)¶
スキル 1 つは、ソースのディレクトリ 1 つ .apm/skills/<name>/ です。ここに SKILL.md を置き、
本体に載せるべきでない深さがあれば references/ ディレクトリを併せて置きます。SKILL.md は手順と、
その手順が使う Claude Code の道具の両方を持ちます。ホストごとのアダプターを別に保守する必要はなく、
手で保守する手順の写しも残りません。後述のとおり、配備先は make skills が書き出します。
ツリーを解決するのは APM です。ルートの apm.yml がパッケージ名を
与え、targets: [claude] を固定します。これにより APM は .claude/skills/ にだけ配備し、他のハーネス
向けのツリーを書き出しません。apm.lock.yaml には、配備ファイルごとの SHA-256 が記録されます。
make skills # uv run apm install --no-policy — .apm/skills/ を .claude/skills/ へ配備する
make lint-skills # uv run python scripts/audit_skills.py — ずれがあれば落とす(`make check` に含む)
lint-skills が apm audit を直接呼ばず、ラッパーを経由するのには理由があります。APM が統治する
範囲は .claude/ 全体であり、そこは Claude Code が並行セッションの worktree を置く場所でもあります。
そのまま監査すると他のセッションのチェックアウトまで走査し、このリポジトリが書いたのではない
第三者のファイルで落ちます。scripts/audit_skills.py は代わりに、
git が見ているファイルだけを写した一時ツリーを監査します。クローンした直後の中身に、いま手を
付けている分を足したものです。
ソースと配備先の両方をコミットします。 クローンした直後から、APM を入れる前に動くスキルセットが
手に入ります。代償は、各スキルのバイト列を 2 度追跡することです。SKILL.md を APM のサイズ予算
(およそ 500 行、5,000 トークン)に収めることで、この代償を抑えます。
したがって、編集するのはソースであり、make skills を実行して書き換わったものをコミットします。
内容が変わらない make skills は、ロックファイルに手を付けません。APM 0.28.0 は既存の
apm.lock.yaml の generated_at をそのまま残し、ロックファイルを新規に書き出すときだけ新しい値を
刻みます。タイムスタンプだけの差分は、更新ではなく再生成が起きた印です。検査はこの行を見ないので、
その差分は破棄してかまいません。
make lint-skills は、どちらの向きのずれも捕まえます。配備先を手で書き換えた場合と、ソースを編集して
make skills を忘れた場合の両方です。lint-actions や lint-secrets と違って、この段にはスキップの
分岐がありません。apm-cli は pyproject.toml で版を固定した dev の依存で、
uv sync --group dev がどのクローンにも入れるので、走らないことで通ってしまう余地がないからです。
固定は下限ではなく厳密な指定で、uv.lock にも記録されます。したがって apm.lock.yaml を書き出す版は、
手元でも CI でも同じです(このページの他の箇所にある 0.28.0 は、挙動を確認した版の記録なので、
更新の対象ではありません)。
この検査は prime directive 1 に触れません。apm audit は記録済みのハッシュと作業ツリーを比較するだけで、
ゲートに言語モデルは届きません。--no-policy は検査をオフラインにも保ちます。これがないと、組織ポリシーの
探索が api.github.com へ出ていきます。このリポジトリが実行する apm install にも、すべて同じフラグを
渡します。理由は二つあります。配備と、それを再現する検査とを一つの設定の下に置けることです。そして
「作業ツリーから解決でき、ネットワークを必要としない」という性質が、配備の側にも成り立つことです。
フラグがなくても、ネットワークのない apm install は成功します。0.28.0 はポリシーへ到達できなかった旨を
警告して先へ進むからです。それでも実行のたびにネットワークへ出ていきますし、警告は失敗のように読めます。
スキルの改名や引退に、追加の手順は要りません。 apm install は、自分の持ち物でなくなった配備先を
削除します。ソースのディレクトリを git mv して make skills を実行すれば、古い
.claude/skills/<name>/ はそれで消えます。make skills を忘れても、ゲートが捕まえます。改名後の
ソースには配備先がなく、apm audit がそれをずれとして報告するからです。
深さの置き場所。 SKILL.md は、references/ のファイルをそれが必要になる手順の位置で指します。
セッションは使う分だけを読み込みます。例外は規範のセットです。document-writing、
english-document-writing、japanese-document-writing の 3 つは、手順を追う形ではなく全体を適用する
決まりなので、分割したときは SKILL.md の冒頭で「執筆前に references/ を全部読む」と指示します。
どの手順がどの規範を必要とするかという対応が規範の側になく、必要に応じて読む形にすると、一部だけを
適用したまま規範に従ったつもりになれてしまうからです。
このルールの短い版は CLAUDE.md にあります。
自分のレーンに留まる¶
タスクに必要なファイルだけ触ります。アーキテクチャは層状です(scenario → orchestrator → driver → backend。architecture 参照)。ほとんどのタスクは 1 層に収まります。多数の モジュールを横断せざるを得ない変更(抽象 Driver API、シナリオ スキーマ、共有 config の 形を変えるなど)は、他セッションがその面を避けられる(または着地を待てる)よう、事前に宣言してください。
調整が必要な共有面:
| 面 | ファイル | 共有される理由 |
|---|---|---|
| Driver API | bajutsu/drivers/base.py |
全 backend と orchestrator が依存 |
| シナリオスキーマ | bajutsu/scenario/models/scenario.py |
ハブとなる成果物。codegen/runner/report が読む |
| config の形 | bajutsu/config/ |
全コマンドが解決する per-target レイヤリング |
CI がブランチを正直に保つ¶
CI は全 PR で同じゲートを走らせ、concurrency: ci-${{ github.ref }} と cancel-in-progress を使います。
同じブランチへの再 push は古い run を積み上げず置き換えます。各々単独で通る 2 つの PR でも挙動は衝突し
えます。マージこそが両者の出会う場です。だからこそ判定者は決定的なテストスイートであり、LLM(大規模言語モデル)でも
人間の目視でもありません。スイートを意味あるものに保ち、ブランチを rebase し続ければ、並行作業は破綻なく合成
されます。
GitHub Actions のワークフロー名とジョブ名の付け方¶
Actions タブや PR の checks 一覧でレビュアーの目に入るのは、ワークフローの name: と各ジョブの name: だけです。
その背後の YAML はもう 1 クリック先にあるので、名前はそれ単独で内容が伝わる必要があります。両方を同じ形式で名付けます。
チェックが何をするのかを示す短く平易な句に、情報が増える場合だけ対象のツールや範囲を括弧書きで添えます(E2E (Simulator)、Swift (BajutsuKit)、Web E2E (Playwright)、Dependency audit (pip-audit) など)。
実行結果を開かないと意味の通じない、単語 1 語だけの名前(docs、build、deploy)は残しません。模範例は ios-e2e.yml と swift.yml です(BE-0122)。
name: の値そのものにコロンと空白が含まれる場合は、YAML が入れ子のマッピングと解釈しないよう引用符で囲みます(name: "Roadmap: allocate BE IDs")。
リネームには一つだけ制約があります。required status check のコンテキストは、ワークフロー名ではなく ジョブの name: そのものです。
そして main のブランチ保護ルールセットは、このうちいくつかを文字列そのままで固定しています。check(ci.yml)、E2E (iOS)(ios-e2e.yml)、require two approvals for BE proposals(roadmap-proposal-approvals.yml)の 3 つです。
これらのジョブ名を、ルールセットの required_status_checks を同時に更新せずにリネームすると、開かれているすべての PR が、二度と報告されないチェックを待ち続けてマージできなくなります。
ルールセットの編集は通常の PR からは届かないリポジトリ外の管理者操作なので、この 3 つの名前は現状のまま残します。意図的にリネームする場合は、人手によるルールセットの管理者編集と必ずセットで行ってください。
プラットフォームごとの E2E ジョブ構成¶
バックエンドごとに実機・実環境の E2E ワークフローが 1 つあります。ios-e2e.yml(macOS / XCUITest)、android-e2e.yml(Linux+KVM / adb)、web-e2e.yml(Linux / Playwright)です。これらはジョブの語彙を共有しており、レビュアーはプラットフォームをまたいで同じ形を読み、新しいバックエンドも合わせるべき形を得られます。レーンごとに 1 つの束ねた合否を返すのではありません。
どのレーンも備える機能面の核が smoke です。実バックエンド上で showcase のシナリオを bajutsu run で回し、決定的に合否を判定します(「bajutsu はそのプラットフォームを操作できるか」)。ほかのジョブは特定の機能を確認するもので、そのプラットフォームに当てはまる場合だけ備えます。
| ジョブ | 確認する内容 | iOS | Android | Web |
|---|---|---|---|---|
smoke |
showcase に対する機能的な bajutsu run |
✓(run に統合) |
✓ | ✓ |
golden |
element ツリー(BE-0006)が committed ベースラインと一致するか | ✓ | ✓ | — |
visual |
committed ベースラインに対するピクセル VRT | ✓ | ✓ | — |
conformance |
実バックエンド上のドライバ契約(BE-0114) | ✓ | ✓ | ✓ |
codegen |
ネイティブテスト出力を実バックエンドに対してコンパイル・実行する | ✓ | ✓ | ✓ |
gestures |
マルチタッチ(ピンチ・回転) | ✓(run に内包) |
— | — |
fallback |
resident と uiautomator dump の読み取り経路が一致するか(BE-0245) |
— | ✓(ステップ) | — |
この構成を健全に保つ規則が 2 つあります。どのレーンも、レーンごとに必須です。 必須ステータスチェックは
ルールセットが固定するジョブの name:(前述)であり、各レーンは常に結果を報告する自分自身の集約ジョブ
(E2E (iOS)、E2E (android)、E2E (web)、BE-0279)を持ち、その集約ジョブが束ねる重いジョブを changes ジョブが
パスゲートするので、無関係な PR は走りもブロックもされません。バックエンドを一つの集約ジョブにまとめず、レーンごとに分けているのは
切り分けを保つためです。赤いチェックが壊れたバックエンドを名指しします。ホスト依存や upstream に脆いチェックは
必須ゲートから外します。 visual はレンダラによってベースラインが変わるピクセル比較であり、element ツリーの
golden は upstream の実機側依存によってドリフトしうるため、その変化は制御外です。どちらも
PR ごとにシグナルとして走らせますが、各集約ジョブの needs: からは除外するので、ドリフトはマージを止めずに
表れます。codegen も同じ「まずシグナル、安定したら必須化」という経路をプラットフォームごとの日程で
辿ります。iOS と web の codegen は、すでに自分の集約ジョブをゲートしています。Android の codegen
(BE-0294)はまだ PR ごとのシグナルにとどまり、安定を確認してから needs: に加わります。
モデルと推論エフォートの適正化(BE-0103)¶
このリポジトリはエージェント主導なので、セッションのモデルと推論エフォートはそのまま現実の反復的な
トークンコストになります。タスクの負荷に合わせて選んでください。負荷の高い作業には高エフォートの高性能モデルを
充て、機械的な雑務では引き下げます。これはあくまで指針です(難しい個別ケースでは人間がいつでも上位モデルへ
引き上げられます)。決定的な run / CI ゲートには一切触れません。ゲートは開発セッションが何で動こうとモデルを
呼ばないからです。
失敗の出方は非対称です。過剰な割り当てはトークンを目に見えず浪費し(出力は問題なく見えます)、不足は悪い結果として はっきり表面化します。そのため自然な流れは常に最上位へ傾きます。この規約が取り除くのはまさにその無駄ですが、 難しいタスクで品質を損なうほど引き下げはしません。
タスク → 能力の対応表¶
この表が唯一の典拠です。下のスキルの frontmatter とサブエージェントの指針は、これを反映します。タスクはモデルと 推論エフォートの 2 軸で、3 つの段階のいずれかに対応づきます。
| 段階 | モデル | エフォート | タスク |
|---|---|---|---|
| 重 | opus |
高 | BE 項目の実装(implement-be)、軽くないリファクタリング、アーキテクチャや設計の判断、ゲート失敗のデバッグ |
| 中 | sonnet |
中 | ロードマップの発想と起草(ideation)、技術文書執筆と翻訳のレビュー(english-document-writing・japanese-document-writing)、PR レビュー |
| 軽 | haiku |
低またはなし | ロードマップ項目の Status の切り替え、ドキュメントの整形とリンク修正、機械的なリネーム、ロックファイルや整形の雑務、中段階のレビューに回す前の翻訳の下書き |
段階からモデル id への対応はここだけにあります。新しい Claude モデルへ段階を差し替えるときは、1 箇所を 1 行
直すだけです。表のモデル id は Claude Code のエイリアス(opus / sonnet / haiku)で、背後のモデルの
バージョンが上がっても安定します。
スキルのfrontmatterに既定を埋め込む¶
各スキルは、自分の段階を SKILL.md frontmatterの model: として宣言します。宣言はソース側の
.apm/skills/<name>/SKILL.md にあり、apm install が下記の配備先へそのまま複製します(BE-0390)。
Claude Codeのハーネスはスキル実行時にこれを読み、正しいモデルを選びます。既定は上書きできます。
implement-be:opus(重)propose-and-build:opus(重)。Phase B で プロダクトコードを実装するので、implement-beと同じ段階にします。fix-issue:opus(重)。素のGitHub Issueに対して プロダクトコードを出荷するので、implement-beと同じ理由で同じ段階にします。ideation:sonnet(中)document-writing:sonnet(中)english-document-writing:sonnet(中)japanese-document-writing:sonnet(中)record-issue:sonnet(中)。軽微な気づきをGitHub Issueと して起票するスキルです (BE-0384)。気づきの分類、 重複検索で挙がった候補の見極め、Issueテンプレートに沿った本文の下書きは、いずれも文章の判断なので 軽段階では力不足です。一方でプロダクトコードは 1 行も書かないので、重段階は無駄になります。roadmap-filter:haiku(軽)。Statusで ロードマップを見渡す読み取り専用のスキルです(BE-0162)。make roadmap-status STATUS="…"を包み、 ある状態の項目だけ(たとえば未着手のProposalすべて)を、次に開くファイルパス付きで一覧します。 ダッシュボードの描画済み HTML をめくったり、項目ファイルを一つずつ開いてStatusを確認したりする 必要がありません。
軽い雑務の多くはスキルではないので、その段階はふだん下の対話操作かサブエージェントへの委譲で使います。
roadmap-filter は例外で、その仕事そのものが軽い決定論的な検索だからです。tests/test_skill_models.py が
スキルそれぞれの model: を、配備先だけでなく .apm/skills/ のソースについても、既知の妥当なidかどうか
確認するので、打ち間違いは黙って握りつぶされず、ローカルのゲートが落とします。
フェーズとサブエージェントへの委譲¶
frontmatter は対話的な作業や委譲した作業には届かないので、そこは手で選びます。
- セッション内のフェーズ:探索、調査、機械的な雑務では引き下げます(あるいは
/fast)。実装と設計では 引き上げます。/modelと/fastは、セッションの途中でモデルとエフォートを切り替えます。 - サブエージェントへの委譲:Agent ツールでサブエージェントを起動するときは、駆動側ではなく委譲するタスクに
合った
modelを渡します。広く展開するExploreのファンアウトや index の再生成は、駆動しているセッションより 安いモデルで回せます。これは、frontmatter を所有していないリポジトリ外のレビュー用プラグイン (pr-review-toolkit)に対して唯一効くつまみでもあります。起動時にモデルを指定してください。
起動時に model を渡すやり方は、仕組みではなく覚えておく作法であり、実際に忘れられます。誰も経路を
指定しなかった探索のファンアウトは、駆動側が払っているモデルのまま動きます。もっとも忘れられやすい探索
については、.claude/agents/scout.md がこの穴を塞ぎます。このエージェントは
自身の frontmatter で sonnet を固定し、返答をファイルの中身ではなくパスと行範囲に限ります。scout を指名
すれば、誰も選ばなくても安い経路になります。
意図的にゲートでは強制しません。セッションがどのモデルを使ったかは差分から復元できず、固定的に縛ると、本来は 軽いタスクが難しかったときに人間が引き上げる判断を奪うからです。これは、コントリビューターの作業フローの残り (BE-0069)と 同じ、「手順をコマンドに、ゲート強制ではなく指針」という先例に従います。
ローカル自己レビューの 2 つの役割(BE-0347)¶
push の前に CI のレビュー契約をなぞる自己レビューは、1 つのエージェントに両方をやらせるのではなく、
2 つの役割を別々に立ち上げたエージェントへ分けて走らせます。opus の review/plan パスが
.github/claude-review-prompt.md に照らして差分を判定し、修正の
指示を書きます。review/plan パスはファイルに一切手を入れません。指示を当てるのは別の implement パスで、
修正が roadmaps/ と docs/ の中で収まるなら sonnet、プロダクトコードに触れるなら opus を
使います。上の対応表がほかの場面でも使っている、タスクの重さで決める規則と同じです。手順そのものの典拠は
ideation の手順 5 で、pr-followup、propose-and-build、
implement-be は述べ直さずに同じ手順を走らせます。
効くのは役割の分離です。プロダクトコードの修正では両方の役割が opus で動くため、分離を支えている
のはモデルの違いではなく、経緯を持たない別のエージェントとして立ち上げることです。自分で挙げた指摘を自分で直す
エージェントには、そのコメントを黙らせるぶんだけ直せばよいという動機が働きます。隣にある問題は、経緯を
持たない次のレビューへ持ち越されます。push のたびに CI のレビュアーが新しい指摘を出し続けていた理由も、
おそらく役割が分かれていなかったことにあります。
対応表の PR レビュー → 中(sonnet) の行とは矛盾しません。対応表の行が指すのは、依頼を受けて他人の
pull request をレビューする仕事で、変更全体をその価値で量ります。review/plan の役割はもっと狭く、ローカルの
ループの中で、決まった契約に照らして指摘を仕分けるだけです。pull request 全体をレビューすることはありません。
2 つの行は別の問いに答えています。
CI の起動条件も合わせて狭めます。 ローカルのパスが push より前で収束するようになったので、
claude-review ワークフローは push のたびの再レビューをやめました。
自動で走るのは pull request がオープンまたは再オープンしたときだけで、以後のレビューは @claude review の
コメントで依頼します。ふだん依頼するのは pr-followup 自身で、自分の自己レビューがきれいに通った時点で
依頼します。ワークフローを廃止せずに残すのは、ローカルのパスを通らない経路が 2 つあり、そのままでは
レビューがまったく付かなくなるからです。フォークからの pull request(pull_request の実行は設計上
シークレットを持ちません)と、これらのスキルの外から push されたコミットです。
ロードマップ項目を起草し出荷する 3 つのスキル¶
アイデアを出荷可能なコードにする作業では、3 つのスキルを使い分けます。起草するか、出荷するか、 その両方かです。
ideation:起草のみ。アイデアを BE 提案に整える相談相手で、roadmaps/のファイルで止まります(プロダクトコードには触れません)。提案はBE-XXXXのプレースホルダを 持ち、実際の id は PR がマージされたあとに CI が採番します。implement-be:採番済み項目の出荷。採番済みのBE-NNNNを受け取り、その提案を仕様として実装とテストを書き、項目をStatus: Implementedに切り替え、make checkが緑であることを示します。propose-and-build:両方を、1 つの PR で。設計が固まった 小さな項目を、作者がいま実装できると確信しているときに、前の 2 つを組み合わせます。提案の起草と実装を 1 つのブランチで進め、ロードマップ項目とコードとテストをまとめて運ぶ 1 つの BE 作成 PR として出します。 項目はBE-XXXXのプレースホルダを保ったまま PR のなかでStatus: Implementedに到達します。Statusと PR 番号は id に依存しないためです。実際のBE-NNNNはマージ時に CI が採番し、プレースホルダを項目自身の ファイルのなかで書き換えます (BE-0089)。 唯一の不変条件は、プレースホルダの id が項目自身のファイル以外のどこにも現れないことです。採番はその ディレクトリだけを書き換えるためです。
どれを選ぶか。既定は直列の ideation → マージ → 採番 → implement-be の経路です。コードを書く前に設計を
レビューへ通すことを強制し、出荷する項目にだけ番号を割り当てて BE-NNNN の列を連続に保ちます
(BE-0089)。
propose-and-build に手を伸ばすのは、この経路の待ち時間が純粋なオーバーヘッドになるときだけです。つまり、
作者がレビューで設計が変わらないと見込む、小さくよく絞られた項目です。1 つの PR は設計のチェックポイントを
コードレビューと一体にするので、マージは提案と実装を一度に受け入れます。固まった設計にとっては素直な形ですが、
レビューが提案を変えれば手戻りを負うので、設計が本当に不確かなときは直列の経路に戻してください。
ロードマップ項目にならない作業には、3 つのどれも当てはまりません。兄弟となる経路が
fix-issue です
(BE-0380)。素のGitHub Issue、
つまり小さな不具合やちょっとした使い勝手の悪さ、範囲の定まった改善を、implement-be 自身の実装、
レビュー、ゲート、フォローアップの手順に載せて出荷します。違うのは 2 点だけです。担当はIssue自身の
Assignee欄で確保し、Issueを閉じるのは Status の切り替えではなく、PR 本文の Closes #<N> です。
ロードマップ項目に
するかどうかの境界も fix-issue 自身が判断し、設計の判断が必要だとわかった修正は、出荷せずに
ideation か propose-and-build へ委ねます。
まだIssueにもなっていない気づきには、その一歩手前の入口として
record-issue があります
(BE-0384)。このスキルが
やるのは起票だけです。軽微な不具合や範囲の定まった小さな改善を分類し、既存のIssueとロードマップに重複が
ないかを検索します。そのうえでリポジトリ自身のIssueテンプレートに沿って本文を下書きし、起票者が明示的に
承認してからIssueを作ります。修正は出荷せず、ブランチも切りません。record-issue が起票したIssueは、のちに
task-select が候補として並べ、fix-issue が出荷します。つまり 3 つのスキルが、作業の合間の気づきを
マージされた修正まで運びます。取りこぼすことも、目の前の変更を膨らませることもありません。record-issue はどのスキルからでも
サブステップとして呼べます。現在配線されている呼び出し元は
pr-followup で、承認の手順はどの呼び出し元も省けません。
implement-be の無人ループのなかでこの呼び出しが起きたときは、record-issue は何も起票せず、できあがった
下書きをそのイテレーションのサマリーに返します。下書きを置く欄はエスカレーションの欄と別なのでループは
止まらず、人間はあとのターンで下書きを承認します。
プルリクエスト: タイトルと本文¶
PR は、人間が頼まない限り自分では開きません(1 トピック 1 ブランチを参照)。 自分のブランチに push し、PR は人間に開いてもらいます。ただし PR を書き起こすとき、あるいは人間が 開くためのタイトルと本文を用意するときは、以下の型に従ってください。これはこのリポジトリが実際に マージしてきた PR から型を起こしたもので、これに合わせると履歴の体裁がそろい、レビュアーは毎回 同じ情報を同じ場所で見つけられます。タイトルと本文は、作業に使った言語にかかわらず常に英語で 書きます。
タイトル¶
スコープ付きの Conventional Commits の subject を 1 行で 書きます。先頭コミットの subject と同じ形です。
[BE-NNNN] type(scope): summary
type(scope)::conventional-commit の type(feat、fix、docs、chore、ci、refactor、test)と、触れる領域(run、web、codegen、audit、roadmap、hooks、jaなど)です。例:feat(audit):、fix(hooks):、docs(roadmap):。- summary:命令形、小文字始まり、末尾のピリオドなしで、レビュアーが一目で読める 1 行にします。
ロードマップ提案なら
docs(roadmap): propose <提案の内容>の形です。 [BE-NNNN]の接頭辞:PR がロードマップ項目に紐づくときだけ、スコープ付き subject の前に角括弧で 付けます(例:[BE-0017] feat(mcp): add MCP server)。ロードマップ項目に紐づかない PR は、スコープ 付き subject のままにします。PR が新しいロードマップ項目を導入する場合も、スコープ付き subject の ままにし、[BE-NNNN]接頭辞は付けません。番号はマージ後にmain上で採番されるからです (ロードマップ項目を参照)。
本文¶
リポジトリ内の .github/PULL_REQUEST_TEMPLATE.md が、この型の
正典です。GitHub が新規 PR の本文へ自動で挿入します。AI が PR を起草するときは、これに従います。
該当するセクションを埋め、残りは削除します。テンプレートにあらかじめ書き込まれている
## Prime-directive compliance と ## Verification の定型ブロックは正典の文言なので、言い回しを作り直す
のではなく、その変更が関係する範囲に削って使います。この節の残りは、テンプレート内のコメントが参照先と
して指し示している内容です。
必須は 2 つ(## Summary と検証の記述)で、残りのセクションは変更が必要とする範囲で、以下の順に
足します。詳しさは差分に合わせます。1 ファイルの修正なら、短い Summary と緑の数値で足ります。横断的な
機能なら全セクションが要ります。文章は、マージ済みの PR でこれらのセクションが実際に読める形にならって
書きます。現在形で、たどり着くまでの経緯を語るのではなく、変更が何であるかを述べます。太字は、変更の
鍵となる少数の名詞に限り、文全体には使いません。変更一覧では、繰り返し現れる **パス**:何をするか、
そしてなぜこの継ぎ目か の形に従い、単なる編集ではなく設計上の選択を書きます。
よく現れるセクションと、それぞれが担う内容は次のとおりです。
## Summary(必須):PR が何をするか、そしてなぜ重要かを、短い段落 1〜3 個で書きます。鍵となる 名詞は太字にします。経緯ではなく変更そのものから書き始めます。より大きな項目の一部を成す PR なら、 どの一部かを示し、マージによってその項目のStatusがどう動くか(例: In progress へ移る)を 述べます。## What changed/## Changes:ファイルまたはコンポーネントごとに箇条書き 1 個を当て、 パスまたはコンポーネント名を太字にし、em ダッシュに続けて、何をするかとなぜこの継ぎ目か(単なる 編集内容ではなく設計上の選択)を書きます。新規ファイルには(new)を付けます。コミット単位ではなく コンポーネント単位でまとめます。レビュアーが読むのは、たどり着いた結果であって経路ではありません。## Prime-directive compliance:変更がツールの挙動やランタイムに触れるときに置きます。判定に モデルを介在させないこと、run/ CI ゲートが決定論的なままであること、アプリごとの違いは設定に 留まることを、はっきり述べます。変更が関わるprime directiveごとに 1 行です。ドキュメントのみ、または 基盤のみの PR は、その旨を 1 文で述べれば足ります。## Scope(多くは Scope (deferred to …)):この PR に意図的に含めないものを書きます。境界を レビュアーに推測させないためです。より大きな項目の一部なら、後続の一部が負っている残りを挙げます。## Verification/## Testing/## Test plan(必須、いずれかの形で):make checkが 緑であることを、出力した具体的な数値(N passed, coverage X%)とともに示し、新しいテストが何を カバーするかを 1 文で書きます。ゲートが動かせないもの(ワークフローの実行時挙動、Simulator でしか 通らない経路)は明記し、何が証明され何が証明されていないかをレビュアーに伝えます。ここでの正確さが要で、 テストしていない経路をテストしたかのように書きません。- ロードマップ提案の場合:
## Files(両言語のペア)と## BE ID allocation(BE-XXXXプレースホルダについての注記。番号はマージ後にmain上でワークフローが採番するので、手で書き換えません)。 ## Notes:注意点、関連する、あるいは番号を争う open な PR、予期されるマージ衝突とその解消方法。
本文の末尾は、引用した項目への参照リンク([BE-0049]: roadmaps/…)と、フッタ
🤖 Generated with [Claude Code](https://claude.com/claude-code) で締めます。GitHub の > [!NOTE]
コールアウトは、レビュアーが見落としてはいけない注意点に限って使います。
小さな修正なら、必須の 2 つだけで足ります。
## Summary
Follow-up to #189: `session-start.sh` could abort the hook — and the session — under `set -e`
when `CLAUDE_PROJECT_DIR` is unset. This makes the project-dir discovery best-effort.
## Verification
`shellcheck` clean; `make check` green (1059 passed, coverage 87.4%). Repro'd that the hook now
logs the skip and exits 0 instead of aborting.
機能や、ロードマップ項目に紐づく PR は、全体の型を埋めます。
## Summary
The **<slice>** of [BE-NNNN]. <What it does and why it matters, key nouns in bold.> This moves
the item to **In progress**.
## What changed
- **`bajutsu/<file>.py` (new)** — <what it does, and why this seam>.
- **`bajutsu/<other>.py`** — <the change, and the design choice behind it>.
- **docs (en/ja)** — <what was documented>.
## Prime-directive compliance
No model is consulted on the verdict; the `run` / CI gate stays deterministic; per-target
differences stay in config.
## Scope (deferred to later BE-NNNN slices)
<What is deliberately not in this PR.>
## Verification
`make check` green: format-check / ruff / mypy (Success) / test (N passed, coverage X%). New
tests cover <…>.
[BE-NNNN]: roadmaps/BE-NNNN-<slug>/BE-NNNN-<slug>.md
🤖 Generated with [Claude Code](https://claude.com/claude-code)
このルールの短縮版は CLAUDE.md にあります。
PR レビューコメントへの対応¶
レビューには 1 件ずつ返信します。返信するのは、その pull request の担い手(人間の貢献者でも AI エージェントでも同じ)です。レビュアー(後述する自動レビュアーの Claude Code、あるいは人間)が コメントを残したときは、すべてのコメントを解消するまで作業を続け、そのうえでコメント 1 件ごとに個別に 返信します。PR にまとめて 1 つ返信するだけでは足りません。指摘が出たスレッドそのものに解消の記録が 残るよう、コメントのスレッドそれぞれに返信します。
返信では次の 2 点を必ず示します。
- その指摘に対応したこと:コードを修正したのか、意図して見送ったのか。
- その根拠:解消にあたる具体的な変更(何をどこで変えたか。コミットやファイル、行を挙げます)。 変更しない場合は、その指摘が当てはまらない具体的な理由。
「対応しました」や 👍 だけではこの規範を満たしません。根拠があってこそ、後でスレッドを読む人が解消の 妥当性を確認できます。返信は短く事実に即して書きます。重要なのは根拠であって、説明の量ではありません。
対応したコメントには、返信とスレッドの解決の両方を残します。 コードを修正した場合も、意図して 見送った場合も、まず根拠を記した返信を残し、そのうえでそのスレッドを解決済みにします。 返信はその指摘に対応した理由を示し、解決はそのスレッドが決着したことを示します。こうしておくと、開いた ままのスレッドの集合が、対応済みのコメントの山ではなく、いま対応を要するものだけを常に表します。例外は 次に述べる未決の場合だけです。意図して開いたままにしたスレッドは開いたままにし、問いにまだ答えが出て いないコメントは解決しません。
コメントへの対応に迷うとき(修正の解釈が複数ありうる、あるいはアーキテクチャ上重要な箇所に触れる場合) は、当て推量せず確認します。AI エージェントは自分を動かしている人間に、人間の貢献者はレビュアーや メンテナに確認し、判断が出るまでそのスレッドは開いたままにします。
自動レビュアー(Claude Code、BE-0203)¶
claude-review Environment にプロバイダーの資格情報(Claude Code サブスクリプションのトークン、または
Amazon Bedrock のロールと BEDROCK_MODEL_ID 変数)が設定されると、同一リポジトリのブランチからの pull request を Claude Code が自動でレビューします(フォークの PR はオンデマンドです。後述します)。
claude-review ワークフローから走り、PR がオープンしたときと
再オープンしたときにレビューします。以後は依頼を受けたときだけ走り、プッシュのたびの再レビューはあえて
しません(BE-0347。後述の「オンデマンド」を参照してください)。
.github/claude-review-prompt.md の契約に照らしてレビューし、行単位の
インラインコメント(機械的な修正が当てはまる箇所では suggestion ブロックつき)だけを投稿します。トップレベルの
要約は投稿しません。1 つの PR でジョブは何度か走るので、その都度新しい要約を出すと古い要約が残って PR 上で
矛盾するためです。同じ指摘を何度も蒸し返さず、かつ見落としも出さないため、各実行は diff 全体を読み(変更行を
一つも未レビューにしません)、あわせて PR にすでに投稿済みの指摘の一覧を(API 経由で、PR head を
チェックアウトせずに)渡して、それらは二度と再投稿しないよう指示します。つまり、見る範囲を狭めるのではなく、
既出の指摘を再投稿しないことで重複を省きます。以前の実行が見落とした箇所であっても、本物の問題を新たに見つけたら
必ず挙げます。
資格情報を用意するまではワークフローは休眠状態の green no-op で、何も投稿せず、マージを妨げることもありません。
したがって、まだレビューが付かないのは Environment が未設定なだけです。
このプロンプトはレビュアーをこのリポジトリの契約に向けます。三つの
prime directive、将来のバグに具体的に
つながる設計・保守コスト、セキュリティ、サイレント障害、そして日英の文章品質という二つのレンズです。
おかげで、汎用のレビュアーには拾えないものを拾えます。この基準は意図して機能面の影響だけに絞っています。
文体・命名・文書の書式(docstring の形式、二言語の同期、用語の統一、roadmap リンクの整備)のように
機能に影響しない指摘は人間のレビューに委ね、修正と再実行のサイクルを著者に負わせません。レビューは
(アクション既定の Sonnet ではなく)Opus で走らせ、深刻度の見極めを鋭くします。投稿するのは issue・
suggestion・question だけで、nitpick と praise は抑制します。1 つの PR で何度も走る助言レビューに、
価値の低いノイズをためないためです。
このレビューは助言であって、ゲートではありません。あえて必須のステータスチェックにはせず、ジョブの
結果を指摘の有無から切り離しています(指摘を見つけたレビューは成功したレビューなので、ジョブが赤くなるのは
インフラの失敗のときだけです)。マージを決める唯一の裁定者は、これまでどおり決定論的な check と E2E の
ゲートです。これはレビュアーであって審判ではありません(prime directive 1)。そのコメントは、ほかのどの
レビュアーのコメントとも同じく、上の返信ルールに従って扱います。
- オンデマンド。 オープンと再オープンのレビューより後は、すべて依頼して走らせます。メンテナまたは
コラボレーターが PR に
@claude reviewと書く(あるいはレビュースレッドに返信する)ほか、pr-followupも、 push した修正の自己レビューがきれいに通った時点で同じ依頼を出します。依頼したレビューは自動のレビューと 同じもので、同じ契約、同じ深刻度の基準、同じ既出指摘の一覧を受け取ります。したがってスレッドへの返信でも、 そのスレッドだけに答えるのではなくレビュー全体を走らせます。この経路は trusted actor(OWNER / MEMBER / COLLABORATOR)に限定しています。コメントイベントはフォーク PR でも リポジトリのシークレットを伴って走るためで、それ以外の人の@claude reviewは無視されます。依頼が 無視されても PR には何も残りません。依頼した側は、レビューが実際に走ったことを確かめてください。 無反応を「指摘なしのレビュー」と読み違えないためです。見るのは実行そのものではなくジョブです。 trusted actor の判定はジョブのif:にあるので、依頼が捨てられても実行自体は作られ、claude reviewジョブがskippedのまま green で終わります。実行を探すときは、コメントを投稿する前に時刻を控え、gh run list --workflow "Claude review" --event issue_comment --user <アカウント> --created "><控えた時刻>"のように依頼したアカウントと作成時刻で絞り込みます。コメント起因の実行はデフォルトブランチに対して 走るため、PR のブランチで絞り込んでも見つかりません。そのうえでgh run view <id> --json jobsで ジョブの結果を読みます。 - フォーク。 フォークからの素の
pull_requestイベントは(GitHub の設計上)シークレットを露出しないため、 自動レビューは同一リポジトリのclaude/<topic>/<user>/<topic>ブランチを対象とし、フォークの PR は 代わりにメンテナがオンデマンドでレビューします。 - Copilot からの移行(手動、リポジトリ外)。 このワークフローは Copilot のレビューを残したまま投入するので、 両者が並行して走り、比較できます。Claude Code のレビューが実力を示したら、メンテナがリポジトリまたは組織の 設定で Copilot の自動レビューを無効にします。これは PR では持ち運べない管理状態であり(BE-0122 や BE-0089 が指摘するブランチ保護ルールセットの編集と同じ形です)、明示的な手動の手順です。
文章表現だけの指摘に対する companion PR(BE-0343)¶
レビュアーのレンズのうち 2 つは、文章表現だけを見ます。日本語の文章の質と、対応する英語
(docs/*.md とロードマップの散文)の文章の質です。どちらかのレンズから出た指摘には、素の
(non-blocking) ではなく (non-blocking, prose) という装飾が付き、ほかのレンズの指摘には決して
付きません。この目印は、同じ claude-review ワークフローの
2 つ目のジョブが機械的に読み取ります。ジョブはレビュー本体の後に走ります。suggestion ブロックを
持つ指摘それぞれについて、そのブロックの文字列をそのまま companion ブランチ
(prose-fix/pr-<N>)に適用します。companion ブランチは、PR の現在の head から作り直したものです。
そして、あなたのブランチを base とする小さな
companion PR を開きます。そのうえで、指摘のスレッドにその PR の番号を添えて返信し、スレッドを
解決済みにします。
companion PR が来たときの扱い。 ほかの小さな PR と同じように、都合のよいタイミングでレビュー してマージします。マージすれば、文章の修正は通常のプッシュとしてあなたのブランチに乗ります。この 回り道の狙いは 2 つあります。挙動に影響しない変更のために PR の CI を丸ごと回さずに済むこと。そして、 マージ後に直す方式と違い、人間がまだ差分を読んでいるあいだに修正後の文章が揃っていることです。 文章に関するスレッドに自分で返信する必要はありません。ジョブがすでに返信しています。
知っておく価値のある性質は次のとおりです。
- このジョブでは LLM が走りません。 修正の文面はレビュー時にレビュアーが起草済みです。ジョブが
するのは、その文面がいまも当てはまるかの判定だけで、指摘自身の
diff_hunkと現在の head の ファイルを突き合わせて決めます。あなたが後から編集した行にかかる suggestion は適用せず、 companion PR の本文に一覧として記録します。当て推量で当てることはありません。適用できなかった 指摘はほかの理由のものも同じく記録します。失敗を隠すジョブは、ないほうがましだからです。 - 書き込める範囲を 2 つの関門で狭めています。 このジョブは自動化 App のトークンで書き込むため
です。1 つ目は、その指摘をレビュアーのアカウントが投稿したと GitHub が答えるときにだけ適用する
ことです。
🤖 **Claude Code**の接頭辞と装飾は、どちらもコメントできる人なら書ける文字列であり、 指摘の分類にはなっても本人性の保証にはなりません。2 つ目は、パスがdocs/またはroadmaps/の markdown ファイルであることです。したがって、製品コードに誤って付いた印は適用されず、あなたが判断する通常の インラインコメントとして残ります。 - ブランチは毎回作り直します。 現在の head から作り直し、そのとき投稿されている文章の指摘を すべて再適用します。新しく付いた指摘だけではありません。したがって、あなたがブランチを rebase しても companion 側ですることはありません。実行のあいだに引き継ぐものがないからです。
- あなたの作業を上書きしません。 companion ブランチの強制更新は、後述する定期リフレッシュが ローリングブランチを守るのと同じ方法で守られています。bot 自身が直前に積んだコミットが先端に あるときにしかブランチを動かさないので、companion ブランチにあなたがプッシュすると、次の実行は 上書きせずに見送ります。
- あなたのブランチはチェックアウトしません。 あなたのブランチがジョブに届くのは、データとしてだけ
です。指摘が名指ししたファイルは、実行時に解決した head のコミットから読み戻します。companion の
コミットも、作業ツリーではなく GitHub の Git Data エンドポイントで組み立てます。自動化 App の
トークンを持ったまま貢献者のブランチをチェックアウトすることを、CodeQL の
actions/untrusted-checkout-toctouは検出します。いまのジョブはこの型に当てはまりません。 チェックアウトするのは、実行するスクリプトを載せたデフォルトブランチだけです。 - 同一リポジトリのブランチだけが対象です。 このジョブは自動化 App のトークンを持ったまま PR の ブランチに書き込みます。そのため、オーナーとメンバーとコラボレーターだけがプッシュできる ブランチに限定しています。フォークの PR の文章の指摘は自動化されないままで、レビュー本体が フォークについて受け入れているのと同じ範囲です。
- 後始末も自動で付きます。 あなたの PR がマージされるとそのブランチは削除され、GitHub は開いた
ままの companion PR の base を
mainに付け替えます。以後は、独立した普通の PR としてマージする だけです。
定期リフレッシュ(Claude Code、BE-0222)¶
人手で維持しているリポジトリの部分を、出荷済みの内容と毎日照合する 2 本の定期ワークフローです。上記の自動 レビュアーに対する起草側の対応物にあたります。BE-0203 がマージを止めない AI レビュアーを足したのに対し、 こちらはマージしない AI 起草者を足します。
roadmap-refreshは、各 BE 項目のStatus/Progress/Implementing PRをmainにマージ済みの内容と照合します。docs-refreshは、挙動に対してずれる文章、すなわちdocs/architecture.md#implementation-statusと、DESIGN.md/docs/architecture.mdの文章とコードの 対応(BE-0113 のレビュー時の規範)を照合します。
どちらも 1 本の再利用ワークフロー refresh.yml の薄い呼び出し側です。
共有された型をそこにまとめることで、2 本がドリフトしないようにしています。違うのはブランチ、契約ファイル
(.github/roadmap-refresh-prompt.md、
.github/docs-refresh-prompt.md)、そしてパス許可リスト
(roadmaps/**、または docs/** と DESIGN.md。README* / CLAUDE.md の契約面は意図的に除外します)
だけです。どちらも既存の自動化と揃えてあります。
- 設定が揃うまで休止します。 各実行は、AI プロバイダ(レビュアーが使うのと同じ
claude-reviewEnvironment の資格情報)と自動化 App トークン(roadmap-id.ymlと同じ)の両方が揃わない限り、緑の no-op です。bot が開いた PR に自身のcheckCI を走らせられるのは、App の身元があるからです。設定が中途 半端なリポジトリが赤になることはありません。 - AI が起草し、ゲートと人間が判断します。 Claude Code アクションは作業ツリーを編集するだけです。その
あと決定論的なステップがパス許可リストを強制し(許可外の編集は復元します)、ジョブ内で
make checkを 実行し、ワークフローごとに1 本の更新用ドラフト PR を開きます。run/CI の合否に LLM は載りません (prime directive 1)。ready にしてマージするのは人間だけです。 - 冪等で、人間の作業を上書きしません。 差分がない日は PR を開きません。ドリフトがあれば、そのワーク
フローの固定ブランチを再利用し、その tip を bot 自身がコミットしていたときだけ
--force-with-leaseで force-update します。レビュアーがブランチに push した fixup を上書きすることはなく、その場合は声高に スキップします。
ロードマップ項目: BE ID(厳守)¶
ロードマップは roadmaps/ 配下に1 項目 1 ディレクトリで置きます。各項目は
roadmaps/BE-NNNN-<slug>/ ディレクトリに、英語版 BE-NNNN-<slug>.md と日本語版
BE-NNNN-<slug>-ja.md(ID と slug は同一)を入れます。BE は Bajutsu Evolution の略で、NNNN は
ゼロ詰め 4 桁で単調増加する ID です。すべての項目は roadmaps/ の直下にフラットに置きます。ID を
採番した時点でパスが確定し、以後は動きません(BE-0159 で、BE-0078 が導入した 状態 ごとのフォルダを
廃止しました。状態 はこの後のダッシュボードのバケットだけを決め、ファイルの場所は決めません)。
ロードマップ項目を追加するとき:
- 次の ID を採番する = 既存の最大
BE-NNNN+ 1(roadmaps/直下のすべての項目を数えます)。現在の最大は次で確認します。番号を再利用したり、飛ばしたり、当て推量したりしてはいけません。ls -d roadmaps/BE-*/ | sort | tail -1 - 項目ディレクトリと両言語のファイルを作成する(新規項目はまず提案なので
roadmaps/の直下に状態: 提案で置きます)。 すなわち、roadmaps/BE-NNNN-<slug>/BE-NNNN-<slug>.md(英語)とroadmaps/BE-NNNN-<slug>/BE-NNNN-<slug>-ja.md(日本語、ID と slug は同一)です。ほかに編集する ものはありません。ロードマップダッシュボードが ドキュメントビルドのたびに項目のメタデータから状態とトピックを直接読み取るので、 en / ja のどちらにも、保守すべきインデックス表は ありません。 - ID は不変。既存項目を採番し直しません。状態が変わっても、完了しても、表から削除しても同じです。 一度割り当てた BE ID は、その項目を永遠に指します。
番号は PR のマージ後に main 上で採番されます。PR を開いた時点ではありません
(BE-0089)。
BE-XXXX プレースホルダで書き起こすのが常道です。項目はオーサリング、レビュー、マージ自体を通じて
BE-XXXX のまま保たれ、BE 作成 PR は [BE-NNNN] 接頭辞をいっさい持ちません。本当の番号はマージ後まで
分からないので、タイトルはスコープ付き subject のままにします。マージは main への push であり、これが
roadmap-id ワークフローを起動します。ワークフローは main に対して allocator を実行し、各プレースホルダを
次の空き BE-NNNN にリネームし、そのリネームを main へ直接コミットし、採番した ID を
マージ済み PR にコメントします。採番が main 上でマージ順に走るので、BE-NNNN の並びは構成上連続します。
却下された PR はマージされないため、番号を消費しません。
保護された main へこのコミットを着地させるには、bypass の identity が要ります。main のルールセットの
bypass list に載せた専用の GitHub App で、このリポジトリにのみ contents: write と pull-requests: write
を与え、その App ID と秘密鍵を AUTOMATION_BOT_APP_ID / AUTOMATION_BOT_PRIVATE_KEY の Actions secret として
保存します。セットアップはメンテナが一度だけ行います(後述の「マージ時採番 App のセットアップ」を参照)。
secret が無いあいだワークフローは緑の no-op なので、App の用意中も main は緑のままです。ジョブはマージ後の
レビュー済みコードだけを実行し(main を checkout します)、すべての action をフルコミット SHA に pin し、
scripts/check_renumber_diff.py を実行します。これは bypass コミットが roadmaps/ の外に触れたらジョブを
失敗させ、トークンの影響範囲をそのツリーに限定します。
番号を最初から固定したいときは、これまでどおり手で採番(既存の最大 BE-NNNN + 1)してもかまいません。
BE-0061 の衝突ハードニング(原子的な refs/be-claims/* の予約と、roadmap-id-repair および
roadmap-claims-gc のワークフロー)は撤去しました。マージ時採番では main に触れる allocate の実行が
同時に最大 1 つで、つねに最新の main を読むため、番号は構成上連続し、二つのブランチが同じ番号を取り合う
ことはなくなります。予約台帳とその修復のバックストップは不要になりました。詳しくは
BE-0061 を参照してください。
マージ時採番 App のセットアップ¶
roadmap-id ワークフローが renumber コミットを main のブランチ保護を越えて push できるよう、admin 権限を
持つメンテナが一度だけ次を行います。
- GitHub App を作成します(org 所有でもリポジトリ所有でもかまいません)。webhook も callback URL も 不要です。権限は Repository permissions → Contents: Read and write(renumber を push するため)と Pull requests: Read and write(採番した ID をコメントするため)だけにします。
- このリポジトリにのみ install し、到達範囲を 1 リポジトリに限ります。
- App を
mainのルールセットの bypass list に追加します(唯一のエントリにします)。これで installation token が renumber コミットをブランチ保護を越えて push できます。 - 秘密鍵を生成し、App ID とともに
AUTOMATION_BOT_PRIVATE_KEYとAUTOMATION_BOT_APP_IDの Actions secret として保存します(mainref に紐づく Environment でスコープし、PR 起動のジョブが読めないようにします)。
ワークフローはこれらの secret から短命(約 1 時間)の installation token を作り、checkout、push、gh に使います。
App が作るコミットは署名され App に帰属するので、すべての bypass push は監査できます。
トラッキング Issue。オープンな項目の担当者を示す(BE-0109)¶
オープンなロードマップ項目(状態 が 提案 または 実装中 の項目)にはそれぞれ GitHub Issue が
あり、その Issue の標準機能である担当者(Assignees)が、誰がそれに取り組んでいるか(取り組んでいる
なら誰か)を示す一次情報になります。提案として存在した時点で Issue が起票されるので、担当者が付いて
いない Issue は、ロードマップにこれまで欠けていた「まだ誰も拾っていない」というシグナルそのものです。
二つの保存済みフィルタが、Issue 一覧をボードに変えます。
label:roadmap-tracking no:assignee。未着手のバックログです(誰も付いていない、提案と実装中の 項目)。label:roadmap-tracking assignee:<user>。一人の担当分です。
項目に着手する前に、そのトラッキング Issue を確認してください
(label:roadmap-tracking BE-NNNN in:title で検索します)。担当者が付いていなければ、作業を拾うときに、
他の GitHub Issue と同じように自分をアサインします。トラッキング Issue を手でクローズしてはいけません。
同期処理が行います。
Issue の起票とクローズは、roadmap-tracking-issues ワークフロー
(scripts/sync_roadmap_tracking_issues.py)が自動で行います。ワークフローは push: main(パス
roadmaps/**)で実行します。ライフサイクルは、各項目の現在の 状態 だけを見る関数です。対応する
オープンな Issue の無いオープンな項目には起票し、項目がすでに出荷済み(実装済み)または棚上げ
(保留 か 却下)になった Issue はクローズします。したがって同期処理は冪等で自己修復的であり
(BE-0043 や BE-0061)、再実行しても一つの項目に二つ目の Issue を作りません。追跡する二つの事実、
すなわち担当者(Assignees)と Issue がすでに存在するか(その項目の BE-NNNN をタイトルに持つ、
roadmap-tracking ラベル付きのオープンな Issue)の両方について GitHub が一次情報なので、リポジトリ
側には何も書き戻しません。ジョブは issues: write だけで済み、main へのコミットも bypass 用 App も
要りません。実行は PR ではなく main で行い、BE-XXXX プレースホルダはスキップします。実番号を
タイトルに持つ Issue は、roadmap-id が main で番号を割り当てたあとでなければ作れないからです
(BE-0089)。その割り当てコミット自体が roadmaps/** への push なので、同期処理を再び起動し、番号の
付いた項目を拾います。スクリプトはネットワーク越しに gh を呼び出すため、make check の内側では
実行しません。読み取り専用の --check モードが、何も変更せずにメンテナ向けにずれを報告します。
各ファイルは Swift-Evolution の proposal フォーマットに従います。メタデータブロック(* 提案、
* Author、* 状態、* トピック、任意で * 由来)の後に ## はじめに / ## 動機 /
## 詳細設計 / ## 検討した代替案 / ## 参考 と続けます。埋められる範囲だけ記入し、不明は TBD とします。
-ja.md ファイルのタイトル(# BE-NNNN — <タイトル> の見出し)は日本語で書き、英語ファイルの見出しを
そのまま転記しません。 地の文と同じ規範で翻訳します。訳すと不自然になる用語(selector、backend など)
は訳さず元のまま残しますが、タイトル自体は日本語にします。Author は
GitHub のアカウント名で明記します。書式は * Author: [@handle](https://github.com/handle) で、最初にその項目を
作成した人(AI 支援で書き起こした場合は、それを主導してコミットした人)のアカウントです。状態
フィールドは、項目が並ぶダッシュボードのバケットを決める唯一の基準です(BE-0078)。項目の場所は決めません。
BE-0159 以降、すべての項目はパスが固定された一つの roadmaps/BE-NNNN-<slug>/ ディレクトリに置かれます。
ディレクトリが 状態 に依存しないので、状態 とディレクトリが食い違うことはそもそも起こりません。
| 状態 | ダッシュボードのバケット |
|---|---|
実装済み |
Implemented。出荷済み |
実装中 |
In progress。可決済みで、現在構築中 |
提案 |
Proposals。検討中 |
保留 |
Deferred。棚上げ。復活の条件が本文に明記されているもの |
却下 |
Rejected。見送りが決まり、それを覆す条件が名指しされていないもの |
どのバケットの項目も、トピックごとに整理され進捗バーも付いた
ロードマップダッシュボードで
閲覧できます。このページは scripts/build_roadmap_dashboard.py(BE-0094)が、ドキュメントの
ビルドのたびに同じ項目メタデータから生成し、GitHub Pages に公開するものです。roadmaps/README.md /
README-ja.md には生成された状態の表はなく、項目の状態を閲覧できるのはこのダッシュボードだけです。
コードが状態を決めます。これは厳格なルールです。 項目の 状態 は、その実装が存在するかどうかを表すもので、
項目を前向きな提案として読ませ続けたいという好みを表すものではありません。コードのない状態で書き起こした項目は
提案 です。そのコードを出荷する PR は、同じ PR のなかで 状態 を 実装済み(一部だけを出荷するなら
実装中)に変え、対応する 進捗 のチェックを付け、その PR を 実装 PR に記録します。コードがすでに出荷された
項目に 提案 を残すことはありません。これはまさに implement-be
スキルが行う昇格であり、人にもエージェントにも等しく適用されます。(唯一の例外は新規項目を起草する場合です。
コードを出荷しない ideation 形式の提案は、まだ何も実装していないので 提案 のままにします。)
項目が進んだら状態を更新するだけです。次にダッシュボードが生成されたときに新しいバケットへ
反映され、ほかに編集するものはありません。ディレクトリは移動しません(BE-0159)。
同じ roadmaps/BE-NNNN-<slug>/ のパスがその項目を生涯
保持するので、昇格はもうその項目へ出入りするリンクを腐らせません。これがフォルダ方式に対する具体的な利点で、
フォルダ方式では項目の 状態 が変わるたびにリンクが一つ壊れていました。make lint-roadmap(make check
に含まれる)は今も相互リンクを守ります。ある項目の他項目への markdown リンクが解決しない場合(slug の打ち間違い、
リネームされた項目へのリンクなど)、または Author が [@handle](https://github.com/bajutsu-e2e/bajutsu/blob/main/docs/ja/…) リンクでない場合に失敗します。make
lint-roadmap ARGS="--fix" は壊れた項目リンクを対象の現在のパスへ書き換えます。
マイルストーン M1–M4 は BE-0001–BE-0004(実装済み)です。
これはエージェントが従うべき厳格なルールです。短縮版は CLAUDE.md にあります。
ドキュメントの書き方(全ドキュメント、両言語に適用)¶
このルールはすべてのドキュメント(docs/ の英語版と docs/ja/ の日本語ミラー)に、そして
今後のすべての更新(新規ファイルに限らない)に適用されます。エージェントは厳守してください。作業を
報告したり要約したりするときも同じく適用されます。
document-writingスキルに従う。 ここのすべての ドキュメントとすべての BE ロードマップ項目に対する、両言語の正式な散文規範です。両言語が共有する 言語に依存しない執筆技法(上から下へ推敲する、主眼を冒頭で述べる、各文の文末を最も重要な要素のために 空ける、主語と述語を近づける、能動態を選ぶ、冗語を削る、段落ごとに1つの話題だけを置いて論証を一方向に 進める(パラグラフライティング))を定めます。書いたあとではなく、書く前・ 推敲する前に呼び出します。このスキルは、英語と日本語それぞれの言語レイヤーの上位に立つ傘です。 英語の散文にはenglish-document-writingを併せて適用します (シリアルコンマ、that / which、ダッシュ、数の表記といった英語固有の作法)。日本語の散文には 下記のjapanese-document-writingを適用します。以下の ルールは、この節とこれらのスキルが共有する具体的な期待です。- 自然な文章で書く。 日本語ドキュメントは自然な日本語、英語ドキュメントは自然な英語で書きます。 ミラーは逐語的な置き換えではなく、その言語で同じ内容を自然に伝えるものにします。
- 造語禁止。 必ず一般的で広く使われている技術用語や普通の言葉を使います。語を勝手に作ったり、 通常持たない意味に拡張したりしません。
- 不自然な翻訳禁止。 用語は一般的な訳語を使います。訳すと不自然になる場合は、訳さず元の用語
(多くは英単語。例:
selector、actuator、backend、assertion)をそのまま使います。 - 省略禁止、単体で完結。
document-writingスキルの自己完結の規範に従います。読者がリポジトリの他のページを何も読んでいなくても文書を最初から 最後まで追えるようにし、略語は初出で展開し、用語は初出の箇所で定義します。これはdocs/に限らず、 ロードマップ項目を含め、用語が現れるあらゆる箇所に適用されます。 - 指示語で読者を後戻りさせない。
document-writingスキルの指示語抑制の規範に従います。先行詞が1文より前に離れる、段落・箇条書き・見出しをまたぐ、あるいは 近くに候補となる先行詞が複数ありうるときは、指示語ではなく名詞をそのまま繰り返します。 - 横断的な規範は再記述せず、リンクする(BE-0284)。 複数の文書にまたがる規則(ゲートのステップ一覧、 ロードマップの BE-ID ライフサイクル、PR のタイトルと本文の書式、このドキュメントスタイル)は、1 つの 基準ファイルに全文を書き、それ以外の言及は短いリンクでその基準ファイルを指します。規則を複製しません。 再記述した写しはそれぞれ、後の修正が見落としうるもう 1 箇所になり、2 つの写しはやがて矛盾へ乖離します。 短く行き渡らせるべき prime directives は意図的な例外です。初読で自己完結していなければならない文書は、 読者を他所へ送る代わりに、短く正確な写しを保ちます。
- 用語集の用語は初出でリンクし、その場で説明を繰り返さない(BE-0286)。 BE ロードマップ項目や
docs/配下のページの文章がglossary.mdで定義された用語を Bajutsu 固有の意味で 使うときは、最初に実質的に言及する箇所を、定義をその場で書き直すのではなく、用語集の該当項目 (glossary.md#アンカー。英語版のページからは../glossary.md#anchor)へのリンクにします。 アンカーはその用語を定義する節を指します。たとえば driver / backend / actuator / platform の いずれもglossary.md#driver-backend-actuator-platformを指します。上の横断的な規範のルールの用語単位の対応版であり、写しを増やして乖離させる代わりに 唯一の定義を指すものです。CI のゲートではなくレビュー時の規約です。用語集の語の多くはstep、target、app、platformのように普通の英単語でもあるため、ある言及が Bajutsu 固有の意味を 指しているかどうかの判断には人間の判断が必要で、prime directive 1 がその判断をrun/ CI の 経路から遠ざけているためです。手本はdrivers.mdです。 - 日本語の文章は
japanese-document-writingスキルに従う。 日本語版を新規に書くときも、英語版をdocs/ja/(やロードマップの*-ja.md)へ翻訳するときも、japanese-document-writingを適用します。これがこのリポジトリにおける日本語の文章の正式なスタイルであり、翻訳は英語の逐語訳ではなく、 この規範に沿った自然な日本語にします。これはdocument-writingの下位に位置する日本語レイヤーです(上記)。日本語の散文では両方を適用します。 - 日本語ドキュメントは敬体(ですます調)で書く。
docs/ja/配下のすべての日本語ファイルと、 ロードマップの*-ja.mdは敬体で書きます。常体(だ・である調)は使いません。文書全体で一貫させます。 敬体にするのは文末の述語だけで、連体修飾節や条件・接続の形(「〜する場合」「〜すると」「〜であり」)は従来どおり 常体のままにし、見出しや純粋な体言止めのラベルには繋辞を付けません。
このルールの短縮版は CLAUDE.md にあります。
コードのドキュメンテーションコメント(docstring、BE-0065)¶
上の「ドキュメントの書き方」は散文ドキュメント向けのルールです。こちらは Python コアの docstring
についての対のルールで、生成 API リファレンス(make docs、MkDocs + mkdocstrings)が描画する対象です。
リファレンスのビルドは make check から外した別の重い経路で、LLM を一切加えず、run の中で動くこともないので、
prime directive は構成上そのまま保たれます。
- 英語で書きます。 コード(とその docstring)は両言語にしません。両言語にするのは
docs/配下の散文ドキュメントだけです。 - 公開面は Google style。 公開 API(
bajutsu/drivers/base.pyのDriverプロトコルと共有型、CLI、MCP ツール、シナリオスキーマ、runner /assertions/networkの公開関数)は、 1 行の要約に続けてArgs:/Returns:/Raises:(必要ならYields:/Examples:)を、情報が増えるときだけ 付けます。生成リファレンスは非公開(_始まり)メンバーを除外します。 - 内部ヘルパは散文のまま。 モジュール内部の
_helperは「なぜあるか」を 1 行で書きます。小さなヘルパにArgs:ブロックを強いるのは、このリポジトリが避ける「何をするか」の説明です。 - 型は書き直しません。 型は注釈に置きます(
mypyは strict、ruffのANNルールも有効)。生成器はシグネチャ から型を読みます。Args:/Returns:は型ではなく意味(単位、制約、Noneが何を表すか)を書きます。 - なぜを書き、何をは書きません。 根拠、不変条件(特に決定論を守るもの)、トレードオフ、エッジケースを書き、
挙動の根拠は
BE-NNNN項目に結び付けます。周囲の密度に合わせ、短く目的を持って書き、ナレーションはしません。 - per-field の流儀を保ちます。
TypedDictや定数を保持するクラスでは、フィールドごとのインラインコメントが 各フィールドの「なぜ」を散文ブロックよりよく伝えるので、Args:形式に変換せずそのまま残します。
例として、公開関数は構造化セクションを持ちます(決定論の不変条件を先頭に置き、根拠を BE 項目に結び付け、型は繰り返しません)。
def resolve_unique(elements: list[Element], sel: Selector) -> Element:
"""Resolve a selector to exactly one element for a single action.
A single action requires a unique match, so an ambiguous selector fails rather than acting on
"whatever matched first" (the determinism core, BE-0001).
Args:
elements: One `query()` snapshot of the on-screen elements.
sel: The selector to resolve. `index` is honored only as a last resort, picking the nth of
several candidates.
Returns:
The one element the selector resolves to.
Raises:
ElementNotFound: Nothing matched, or `index` is out of range.
AmbiguousSelector: Two or more matched and no `index` disambiguates.
"""
内部ヘルパは「なぜ」を 1 行で残します(Args: ブロックは付けません)。
def _contains(outer: Frame, inner: Frame) -> bool:
"""Whether `inner`'s frame sits inside `outer`'s (edges inclusive)."""
移行は段階的かつ漸進的に進めます(BE-0065)。
サイトは今ある散文 docstring からすでに描画でき(型付きシグネチャだけでも有用なリファレンスになります)、公開 API
の docstring はモジュール単位の小さな PR で Google style へ移し、scoped な ruff D の強制と Pages ホスティングは
その後に入れます。無関係な変更のついでにモジュール全体の docstring を書き換えないでください。移行は 1 つずつ
小さな PR にします。
リファレンスはローカルで make docs(プレビューは make docs-serve)でビルドします。docs extra が要ります。
このルールの短縮版は CLAUDE.md にあります。
議論を繰り返さず参照する(コメントと docstring)¶
Bajutsu のコメントは、すでに「何を」ではなく「なぜ」を書く方針を取っています(CLAUDE.md
の Conventions を参照してください)。この節は、その「なぜ」をどこに置くべきかを定める補足です。コメントの
根拠がロードマップ項目や docs/ 配下のページにすでに論じられているときは、それを繰り返さず参照します。
議論を繰り返すコメントは、どちらか一方を編集した瞬間に食い違い始めますし、参照だけで済む論拠でファイルを
膨らませてしまいます。
- 参照し、結果だけを残します。 その行を触る読者が守るべき不変条件や制約を一節で述べ、末尾に参照を
添えます。ロードマップ項目には
(BE-NNNN)、docs/の散文ページには(docs/<page>.md)を使います。 検討して退けた代替案、反実仮想(「もし〜だったなら〜が壊れる」という仮定)、前提から結論までの導出は 落とします。それらは参照先にあります。 - 参照は内容の代わりになりません。 その文が成り立つために必要な事実を、参照だけで済ませてはいけません。 項目を開いていない読者にも、コメントだけで意味が伝わらなければなりません。
- 削る前に、3つの形を見分けます。
- 議論の繰り返し。 参照先の項目がすでに完全に論じている決定(退けた代替案、「〜だから〜だから」の 連鎖、具体例による裏付け)を、コメントが再び導出している形です。結果だけに畳んで参照を付けます。
- 抽象の不足。 コメントが1行のコードを説明しているのではなく、ある値や型が守るべき契約を説明して いる形です。契約を覆う範囲が伸び続けるコメントは、その契約に名前が要る合図です。クラスや関数として 切り出し、説明を docstring 側に移します(上の BE-0065 を参照してください)。コメントをさらに削るので はなく、抽象を足します。
- 独立した複数の事実。 1つの議論を追っているのではなく、無関係な複数の確定事実(スキーマのバージョン ごとの互換性の注記など)を列挙しているために長いコメントです。この形には参照を強制せず、短くする ために事実を削りません。どの行を削っても、読者が自分で再現しなければならない情報が失われます。
- コメントの長さそのものは、目標ではなく点検の合図です。 数行を超えるコメントは、たいてい上の最初の 2つの形のどちらかです。コメントが単に丁寧なだけだと決めつける前に、その論拠がすでにロードマップ項目に ないかを確かめます。
例(結果に畳み、導出は項目側に残した形)。
# A retry forces the device recovery `erase: true` would give. Skipped on `reinstall: overwrite`
# (the scenario needs its app data preserved) and on the operator's `--no-erase`; a CLI-resolved
# `erase: false` is NOT the same signal (BE-0353).
次の形にはしません。
# Skipped when the scenario declares `reinstall: overwrite` — its explicit declaration that it
# needs its app's data container preserved across a lease — since forcing `erase` would silently
# override exactly the precondition the scenario was written against. NOT skipped on
# `preconditions.erase is False`: by the time a scenario reaches here, the CLI has already resolved
# every scenario's `erase` to a concrete bool — most commonly `False`, the built-in default a
# scenario never asked for — so a guard on that value would silently disable this whole unit on the
# one path it was written for (see *Alternatives considered*: only `reinstall: overwrite` actually
# protects app data; a bare `erase: false` does not, since `reinstall`'s own default `"clean"` wipes
# the app's data regardless of `erase`).
コメントが議論を繰り返しているか、独立した事実を列挙しているかの判断には意味の理解が要ります。そのため、
このリポジトリの他のコメント規範・文章規範と同じく、レビュー時の期待にとどめ、make check のゲートには
載せません(prime directive 1)。
インラインコメント¶
上の docstring のルールは関数のドキュメントを対象にしています。こちらはその対になる、
インラインコメント(Python の # 行、Swift の // 行)のルールです。定番の文献(節末の参考文献)と、
このコードベース自身のよい書き方から抽出しました。ドキュメントの書き方のルールと同じく、これはレビュー時の
規範であって CI のゲートではありません。コメントがその場所に値するかの判断には意味的な判断が必要で、
プライムディレクティブ 1 がそのような判断を run / CI の判定経路に載せることを禁じているためです。
コメントは何のためにあるか¶
コメントは、コードが言えないことを運びます。根拠、不変条件、制約、自明でない帰結といった、書いた人の 頭の中にあって、書かなければ失われる情報です。コードとは別の水準で書きます。使える水準は 2 つで、 3 つ目は使えません。
- 精密化:単位、境界が端点を含むのかどうか、
Noneの意味、マジックナンバーの由来など、コードが 開いたままにしている点を、直後の行に対して確定させます。定番の形は行末の 1 行コメントです (bajutsu/totp.py)。
cleaned += "=" * (-len(cleaned) % 8) # b32decode requires the padding authenticators omit
- 意図:なぜこの書き方なのかを述べます。読者がコードを「単純化」して、避けていたバグに戻して しまいそうな箇所では、なぜ別の書き方ではないのかも述べます。「Deliberately not …」で始まる既存の コメント群がその形です。意図を書くかどうかはレビューのテストで決めます。次のコードレビューで その行を説明しなければならないなら、いまコメントします。
- 同じ水準の言い直し:隣の行だけから書ける内容(
i += 1 # add one)は何も伝えないので、削除します。 このコードベースにはほぼ存在しないので、この状態を保ちます。
コメントは、コード全般と同じく英語で書きます。文頭を大文字にした完全な文で書き、ブロックコメントは ピリオドで終えます(行末に置く 1 行の断片はピリオドを省けます)。
コメントが長くなったら、論証ではなく参照にする¶
数行を超えて伸び続けるコメントは、たいてい別の場所に属する論証を抱え込んでいます。ルールは上の
「議論を繰り返さず参照する(コメントと docstring)」
のとおりです。結論を述べ、検討した代替案、スコープの判断、導出の残りを持つ BE-NNNN 項目や docs/
ページを引用してそこまで削ります。正当に長いコメントもあります。
bajutsu/cloud/devicefarm.py の Device Farm ブートストラップの
ブロックはその例で、1つの論証ではなく、その行の保守に必要な運用上の事実を保持しています。削る前に、
「議論を繰り返さず参照する(コメントと docstring)」の節が挙げる 3 つの形のどれに当たるかを確かめます。
変更の経緯ではなく、現在のコードを書く¶
コメントは、PR もレビューのスレッドも、そのコードを書いたセッションも見ていない読者に通じなければ なりません。「out of scope for this item」や「changed X to Y」といった文言は、コミットメッセージや レビュー返信の内容がソースに紛れ込んだものです。そうした内容は PR に書き、 コメントには今あるコードのことを書きます。同じルールが出典の書式も定めます。
- ロードマップ項目は素の id で
(BE-0319)と引用します。id を伴わない裸の作業分解の語(「unit 1」 「Half 2」「phase 5」)は書きません。項目がマージされた後には意味が失われるためです。制約そのものを 述べ、記録は id に指させる書き方を選びます。 - PR(
PR #1492)は、その事実を扱う BE 項目がないときに限って引用します。BE id のほうが長持ちする 参照だからです。
docstring との境界¶
関数やクラスが何をするか(その契約)は、モジュールが lint-docstrings の対象一覧に入っているか
どうかによらず、docstring に属します。「これは何をするか」に答える先頭の # ブロックは、構文を
間違えた docstring なので、docstring として書きます。インラインコメントには、docstring が持つべきで
ないもの(行単位の精密化と意図)だけを書きます。TypedDict や dataclass のフィールドごとの行末
コメントは引き続き正しい書き方ですが(上の BE-0065)、型注釈の言い直しになっている部分は削ります。
たとえば line: int | None # None otherwise は言い直しで、「1-based source line」は情報を足しています。
区切り線と記号¶
節の目印が必要な新しい Python コードは 1 行の形 # --- label --- を使い、Swift は // MARK: - を
使います。複数行の飾り枠は追加しません。無関係な変更のついでに、既存ファイルの区切り線を書き換える
こともしません。コメントの散文はドキュメントと同じ記号法に従います。ダッシュは em ダッシュ(—)で、
ハイフン 2 つ(--)は使いません。
マーカーと抑止コメント¶
- TODO は
# TODO(BE-NNNN): <何が解決すれば消せるか>と書きます。追跡される id を添え、人名は 書かず、裸のTODOも書きません。一時的な回避策には、それを引退させる出来事を名指しします (「delete this block once Device Farm ships Python >= 3.13」)。「いつか」とは書きません。 - 抑止はルールのコードを名指しし、コードだけで理由が読み取れないときは理由も添えます。
# noqa: S310 # scheme validated aboveや# type: ignore[attr-defined] # boto3 ships no stubsのように書きます。古くなった抑止はすでに機械的に刈られます(ruffの RUF100 と、strict のmypyに よる未使用 ignore の警告)。理由は人間の側が受け持つ半分で、pyproject.tomlのignore =ブロックがその形を示しています。 - コメントアウトされたコードは書きません。 削除します。バージョン管理が覚えています。コードベースは 現在ゼロなので、この状態を保ちます。
密度とスコープの規律¶
周囲の密度に合わせます。短く目的のはっきりしたコメントを、コードがもっとも自明でない場所に集中させます。 変更していないコードにコメントを足しません。リファクタリングでは、既存の「なぜ」のコメントを 持ち越します。根拠のコメントを黙って落とすことが、回避策が「単純化」されて元のバグに戻る道だからです。
参考文献¶
- John Ousterhout『A Philosophy of Software Design』12〜13 章。コメントはコードに表せないことを運び、 コードより低い水準(精密化)か高い水準(意図)で書き、同じ水準では書きません。
- Steve McConnell『Code Complete 第 2 版』32 章。コメントの 6 分類のうち、価値を持つのは要約、意図、 そしてコード自体では表現できない情報です。
- Python Enhancement Proposal(PEP)8「Comments」。 コードと矛盾するコメントはないよりも悪く、コメントの更新はコード変更の一部です。
- Google Python Style Guide §3.8(レビューの テスト)と §3.12(TODO は人ではなく、追跡される参照を引用します)、および Google のレビュアーガイド (「mostly explain why instead of what」)。
- Ellen Spertus 「Best practices for writing code comments」 (Stack Overflow blog、2021 年)。慣用から外れたコードには、バグへ「直され」ないよう説明を添え、 コピーしたコードには出典をリンクします。