From 57a803e28502d935b15b76da206090216fb67344 Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Tue, 22 Sep 2026 04:04:10 +0000 Subject: [PATCH] =?UTF-8?q?Docs:=20=E4=BD=BF=E3=81=88=E3=82=8B=E8=80=85?= =?UTF-8?q?=E3=81=A0=E3=81=91=E3=81=A7=E5=A7=8B=E3=82=81=E3=82=8B=E5=8F=8E?= =?UTF-8?q?=E6=9D=9F=E3=83=AB=E3=83=BC=E3=83=97=E3=81=A8=E3=80=81=E7=B5=90?= =?UTF-8?q?=E6=9E=9C=E3=81=AA=E3=81=97=E3=81=AE=E5=8F=96=E3=82=8A=E8=BE=BC?= =?UTF-8?q?=E3=81=BF=E3=82=92=E7=A2=BA=E5=AE=9A=E4=BB=95=E6=A7=98=E3=81=AB?= =?UTF-8?q?=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 実装が済んだ 2 つのまとまりの計画・設計・要求を、現行の実装と一致する確定仕様へ 書き直した。 - cross-review-participants-and-seats.md: 使える者の解決・止めない確認・毎ラウンド 2 席の埋め方・席の名前・再開で渡した引数の反映(#727 #687 #478 #648) - cross-refactoring-apply-intake.md: 結果なしを 3 つの取り込みが同じ手順で受けること、 適用ラウンドの開き直しと試行の上限、項目の無いラウンドを作らないこと、帰属の段落の 後ろから記名を読むこと(#728 #647 #592 #553 #674) - cross-review-launch-outcome.md: 読み取りの契約が「まだ実装に入っていない」と書いて いた箇所を、実装済みの仕様への参照へ直した - README.md: 索引に 2 行を足した Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01MGCedPTy818Zw7VYdmE4GB --- docs/specifications/README.md | 2 + .../cross-refactoring-apply-intake.md | 410 ++++++++++++++++++ .../cross-review-launch-outcome.md | 6 +- .../cross-review-participants-and-seats.md | 401 +++++++++++++++++ 4 files changed, 816 insertions(+), 3 deletions(-) create mode 100644 docs/specifications/cross-refactoring-apply-intake.md create mode 100644 docs/specifications/cross-review-participants-and-seats.md diff --git a/docs/specifications/README.md b/docs/specifications/README.md index f0ecb7c6..02cdfd8b 100644 --- a/docs/specifications/README.md +++ b/docs/specifications/README.md @@ -17,6 +17,8 @@ | [ndf-issue-upkeep-root-cause.md](ndf-issue-upkeep-root-cause.md) | 溜まった課題を根本原因の場所で直す判定(ルートコーズ)と、構造の判断の担い手。親 issue とサブイシューの実測、マイルストーンを連番で読む理由。手順は `issue-upkeep` の SKILL.md と `references/` が正 | | [cross-review-evidence-based.md](cross-review-evidence-based.md) | 証拠ベースのレビューと効果の測定。状態ファイルの契約と決定の理由。手順は `cross-review` の `SKILL.md` が正 | | [cross-review-launch-outcome.md](cross-review-launch-outcome.md) | 起動 1 回の結末の語彙(理由 9 語)と起動し直しの可否、利用上限と CLI の上限の検知、プロセスグループでの起動と停止。手順は `cross-review` の `SKILL.md` と `docs/` が正 | +| [cross-review-participants-and-seats.md](cross-review-participants-and-seats.md) | 使える者だけで収束ループを始める共通層(認証の確認を止めない形・参加の母集合と足す者/外す者・毎ラウンド 2 席の埋め方・席の名前)と、再開で渡した引数の反映。手順は `cross-review` の `SKILL.md` と `docs/` が正 | +| [cross-refactoring-apply-intake.md](cross-refactoring-apply-intake.md) | 担当が結果を残さない起動を 3 つの取り込みが同じ手順で受けること(範囲の確定・未検証のコミットの取り消し・結末の記録)、適用ラウンドの開き直しの判定と試行の上限 2 回、項目の無い適用ラウンドを作らないこと、帰属の段落の後ろから必須の記名を読むこと。手順は `cross-refactoring` の `SKILL.md` と `docs/` が正 | | [ndf-cleanup-and-bundle-closing.md](ndf-cleanup-and-bundle-closing.md) | 後片付けが止まる条件(git の拒否だけ)、実行前確認の要否を決める 3 つの問い、まとまりの課題を終わりの工程で閉じる条件と結果の 4 値、配布の記録の形と読み方。手順は `merged` / `progress-tracking` / `release` の SKILL.md が正 | | [ndf-agent-layers-unattended-run.md](ndf-agent-layers-unattended-run.md) | `/goal` の工程を conductor / supervisor / worker の 3 層で通す運転。持ち場 5 つ、報告の 2 段、続けさせる回数、上限(429)で中断した層の再開。手順は `development-workflow` の `references/agent-layers.md` が正 | | [ndf-context-window-metrics.md](ndf-context-window-metrics.md) | 会話の記録から context window を 3 層で測る部品(`transcript_agents.py`)の値の取り方と、`skill-stats --agents` の 4 つの表と印。値の取り方はこの文書が正 | diff --git a/docs/specifications/cross-refactoring-apply-intake.md b/docs/specifications/cross-refactoring-apply-intake.md new file mode 100644 index 00000000..2e611a2d --- /dev/null +++ b/docs/specifications/cross-refactoring-apply-intake.md @@ -0,0 +1,410 @@ +# cross-refactoring: 実装担当が結果を残さないと同じ適用ラウンドが上限なしに開き直され、未検証のコミットが残る → 結果なしを取り込みの 1 か所で受けて取り消し、開いた回数と結末で開き直しを決める + +## 目的 + +**担当の CLI が作業結果を残さずに終わっても、3 つの取り込み(適用・修正・最終ゲートの修正)は +同じ手順で受けて終わる。** 検証を受けていないコミットは取り消され、結末は記録に残り、終了 +コードが返る。下位の読み取りが進行を打ち切ることはない。 + +**同じ適用ラウンドを開き直す回数は 2 回で止まる。** 2 回目は別の担当が試し、2 回とも結果が +残らなければその適用ラウンドを取り消して見送りへ入れる。 + +**採用が 0 件だった提案ラウンドと、項目が 1 件も無い適用ラウンドでは担当を起動しない。** + +**起動し直しても解けない結末(利用上限)では、同じ担当を同じ工程で起動し直さない。** + +**実行環境が帰属の段落を後ろへ足したコミットでも、必須の記名が読める。** 進行側はコミットを +書き換えない。 + +**手順と引数の表は +[`cross-refactoring` の SKILL.md](../../plugins/ndf/skills/cross-refactoring/SKILL.md)と +[`docs/`](../../plugins/ndf/skills/cross-refactoring/docs/02-apply-and-review.md)が正である。** +ここに書き写さない。この文書が扱うのは、そこに書かない決定の理由と、進行の内部の契約である。 + +## 用語 + +本文は左の業務用語で書く。識別子は表・コードブロック・業務用語の初出の括弧書きにだけ置く。 + +| 業務用語 | 識別子 | 何を指すか | +| --- | --- | --- | +| 提案ラウンド | `rounds[]` | 提案 → 採否 → 適用 → 検証 → 修正の 1 周 | +| 適用ラウンド | `rounds[].apply_rounds[]` | 書き換えるファイルが重ならない改善項目の集まり。1 つで 1 コミット | +| 修正ラウンド | `fix_rounds` / `--max-fix-rounds` | 検証が落ちた適用ラウンドを直す 1 周と、その上限 | +| 取り込み | `merge-apply` / `merge-fix` / `merge-final-fix` | 担当が作ったコミットを進行側が検証して受け入れるコマンド。適用・修正・最終ゲートの修正の 3 つ | +| 適用ラウンドを開く | `next-apply-round` | 次に適用する 1 つを選び、担当と項目を出す | +| 最終ゲート | `final-gate` | 全体のテストを実行して合否を判定する | +| 試行 | `attempt` | 1 つの適用ラウンドに担当を起動し、適用の取り込みで受けようとした 1 回 | +| 結末 | `LaunchOutcome` | 担当 1 回の起動の終わり方。使える結果か、結果なしの理由かを持つ | +| 結果なし | `payload` が `None` | 結果ファイルが無い、または JSON オブジェクトとして読めない | +| 起動し直しの可否 | `relaunch_same_agent` | 同じ担当を同じ条件で起動し直せば解ける結末か | +| 結末の読み取り | `gitfacts.read_result` | 状態と工程から結果ファイルの名前の幹を組み、結末を値で返す包み | +| 結末の共通層 | `lib/monitor_outcome.py` の `read_launch_outcome` | 理由の語彙と可否を持つ、2 つの Skill 共通の読み取り | +| 取り込みの共通手順 | `refactor_lib/intake.py` | 範囲の確定・取り消し・結末の記録を 3 つの取り込みで共有する層 | +| 取り込みの範囲の値 | `IntakeScope` | 取り込み 1 つ分の「どこを見て、どこへ書くか」 | +| 範囲 | — | 起点(`apply_base_sha` / `fix_base_sha`)から作業ツリーの先端まで | +| 起点 | `apply_base_sha` / `fix_base_sha` / 適用ラウンドの `base_sha` | 範囲の始まり | +| 結末の記録 | `failed_attempts[]` | 結果を残さなかった起動の記録。適用ラウンドと最終ゲートの控えが持つ | +| 取り消しの理由 | `drop_reason` | 適用ラウンドを取り消した理由(`no_result` / `empty`) | +| 開き直しの判定 | `rounds.group_reopening` | その適用ラウンドを開くか・再開するか・開かないかを返す | +| 輪番から担当を引く関数 | `rounds.impl_for_seq` | 輪番の通し番号から担当と要求モデルを引く 1 か所 | +| 試行の上限 | `vocabulary.MAX_APPLY_ATTEMPTS`(2) | 同じ適用ラウンドを開き直す回数の上限 | +| 無進捗の許容 | `IMPL_STALL_TIMEOUT` / `vocabulary.IMPL_STALL_MARGIN`(900 秒) | 担当が何も出力しないまま待てる秒数と、その余白 | +| 必須の記名 | `Item-Id` / `Round` / `Impl-Runtime` / `Impl-Model` | コミットメッセージのトレーラーとして担当が書く 4 つ | +| 帰属の段落 | `Co-Authored-By:` / `Claude-Session:` | 実行環境がコミットメッセージの末尾へ足す段落 | + +## 対象範囲 + +この文書が扱うのは、cross-refactoring の 3 つの取り込みと、適用ラウンドの進行の側である。 + +| 扱う | 扱わない | +| --- | --- | +| 結末を値で受け取る読み取りの包み、取り込みの共通手順、開き直しの判定、試行の上限と担当の交代、項目の無い適用ラウンド、修正と最終ゲートの修正の結果なし、必須の記名の読み方、無進捗の許容 | 結末の共通層そのもの(理由の語彙と可否を持つ側) | +| 輪番から担当を引く 1 つの関数へ寄せること | 参加者の決め方(別の課題が持つ) | +| 結末の記録の形(3 つの取り込みで同じ配列) | 記録を集計した実行の要約の新しい欄 | +| 進行側がコミットを書き換えないこと | 利用上限で進行全体を止めること | + +**輪番から担当を引く関数は、従来の担当の割り当てを包む形で実装されている。** 呼ぶのは 3 か所 +(適用ラウンドの割り当て・結果なしの試行の交代先・最終ゲートの修正担当)で、参加者の決め方が +変わったときに書き換える場所はこの関数の中だけである。 + +## 背景 + +**結果ファイルの読み取りが、そのまま進行の終了コードを決めていた。** 結果ファイルが無い・ +読めないときは下位の読み取りがプロセスを終わらせるため、その先にある範囲の検査・未検証の +コミットの取り消し・適用ラウンドの状態の記録へ一度も進めなかった。読み取りを呼ぶ取り込みは +適用・修正・最終ゲートの修正の 3 つあり、いずれも同じ形で止まっていた。 + +**適用ラウンドを開く側が、中断からの再開と失敗した試行のやり直しを区別していなかった。** +未着手と取り込み済みの適用ラウンドを無条件に開き直すため、結果を残さない担当に当たり続けた。 +実測では、担当が 15 秒で終わって結果を残さない構成で同じ適用ラウンドを 29 回開き直した時点で手で +止めている(PR #757)。別の実行では空振りの起動し直しが 3729 回に達した(#647)。 + +**採用が 0 件でも適用ラウンドが 1 つ作られていた。** 適用ラウンドの一覧は、鍵が無い状態と +空の配列を区別せずに 1 つの適用ラウンドを作っていた。テスト整備の採用が 0 件の実行では、項目が +1 件も無い適用ラウンドに担当を 187 回起動している(#592)。 + +**未検証のコミットが Pull Request に残りうる状態だった。** 最終ゲートの修正の取り込みが結果 +なしで止まると、担当の作ったコミットは範囲の検査も取り消しも受けない。次の最終ゲートはその +コミットを含む先端でテストし、落ちれば起点を先端へ置き直すため、そのコミットは以後どの範囲 +にも入らなかった(#674)。 + +**必須の記名が、実行環境の足す帰属の段落のために読めなかった。** git の標準の読み方は +メッセージの最後の段落しか読まない。claude が担当の適用ラウンドでは帰属の段落が後ろに付くため、 +4 つの記名が欠落と判定され、適用ラウンドが丸ごと取り消されていた(#553)。 + +**担当がテストを実行している間の無出力が、無進捗として打ち切られていた。** 適用と修正の担当は +テストを 1 回実行し、その間は何も出力しない(#647)。 + +## 決定と理由 + +| 決定 | 理由 | +| --- | --- | +| 結末の読み取りは名前を残した包みとし、結果ファイルの名前の幹をそこで 1 度だけ組んで結末を値で返す | 監視へ渡した名前の雛形と食い違う幹で読むと監視の結果ファイルを引けない。取り込みが共通層を直に呼ぶ形では、幹の組み立てが 3 か所に分かれる | +| 引数を、ファイルのパスと担当から、状態・担当・工程・提案ラウンドへ変える | 古い形の呼び出しが実行時に失敗し、契約の変更を素通りしない | +| 「範囲の確定 → 未検証のコミットの取り消し → 結末の記録」を新しい共通の層へ置き、3 つの取り込みの差は範囲の値で渡す | 取り消しの本体が 2 つあり、違いは起点の鍵と記録先だけだった。取り込みに残るのは、その結果を終了コードと適用ラウンドの状態へ写すことだけになる | +| 共通の層をコマンドの層にも、git の事実の読み取りにも置かない | 3 つのコマンドが読む層である。コマンドどうしの取り込みを作らない。取り消しは事実の読み取りではなく進行の手順である | +| 結果なしの記録は、3 つの取り込みで同じ形の配列にする | 形を 3 通りに分けると、実行の要約と改修計画が 3 通りの読み方を持つ | +| 同じ適用ラウンドの試行の上限は 2 回の固定値とし、引数を足さない | 2 回目は別の担当が試すため、2 回とも残らなければ担当ではなく適用ラウンドの側を疑える。上限を 2 つ置くと、どちらで止まったかを読み解く必要が出る | +| 開き直しの判定を 1 つの関数へ置き、適用ラウンドを開く側と適用の取り込みの両方がそれを読む | 判定が 2 か所にあると、開く側とやり直しの側で違う答えを出す。判定を 1 か所に置けば、骨組みの分岐は変わらない | +| 開いた回数だけを数えず、結末の記録の件数と合わせて判定する | 進行側が落ちて再開しただけで試行が進んでしまう | +| 2 回目の試行は次の輪番の担当が行い、その適用ラウンドで失敗した担当のどれとも違う担当が出た最初の番号を採る | 結果を残さない原因の多くは担当の CLI の側にある。輪番を 1 つ進めるだけでは、1 周して同じ担当へ戻ることがある | +| 替える先が無いときだけ、起動し直しの可否で 2 回目を開くか取り消すかを決める | 参加者が 1 者に絞られた実行では交代先が無い。利用上限で進行全体を止めると、他の担当で進められる適用ラウンドまで止まる | +| 輪番を引く呼び出しを 1 つの関数へ寄せる | 参加者の決め方が変わったときに、変える場所がその関数の中だけで済む | +| 修正の結果は、提案ラウンドの担当ではなく適用ラウンドの担当から読む | 骨組みが起動するのは適用ラウンドの担当である。一致しない適用ラウンドでは結果を一度も取り込めず、検証と修正を往復し続ける | +| 修正の結果なしは修正ラウンドを 1 つ進め、起動し直せない結末では上限の値まで進める | 進めないと見送りの判定が上限に達する条件を満たさない。替えないまま上限まで起動し直すと、利用上限の担当を上限の回数だけ空振りさせる | +| 修正の担当は替えない | 直しかけの文脈を持つ者が続けたほうが速い。最終ゲートの修正が担当を替えない理由と同じである | +| 最終ゲートの修正も同じ共通の手順を通し、結果なしは取り消して判定へ戻す | 取り消せば、次の最終ゲートは修正前の地点でテストする。修正ラウンドの数を進めるのは最終ゲートの側なので、繰り返しは上限で止まる | +| 適用ラウンドの一覧は、鍵が無いときだけ古い版として 1 つ作り、空の配列はそのまま返す | 採用 0 件の提案ラウンドは空の配列を書く。ここで作ると項目の無い適用ラウンドが生まれる | +| 採用 0 件で提案の取り込みが繰り返しを終える合図を返す形は採らない | その合図はテスト整備では構造改善へ進む前に抜けてしまう | +| 必須の記名は、末尾の段落から前へ 1 段落ずつ git の判定に掛け、判定しなかった段落で止める | トレーラーの形で書かれた署名なら、誰が何行足しても同じに読める。全文から記名の形の行を拾う形では、散文の中の行まで拾う | +| 1 段落目(題名)は判定に掛けない | 掛けると、記名の形をした題名を記名として読む | +| 進行側は取り込みでコミットを書き換えない | コミットの識別子が変わり、申告と実体の対応が切れる。適用ラウンドに複数のコミットがあると、先頭の書き換えが以降をすべて書き換える。実際に使ったモデル名は担当しか知らない | +| 雛形のコミットの規約にも、必須の記名を最後の段落に置くことを書く | 人が git の標準の読み方で集計できる。従わなくても進行側の検証は通る | +| 無進捗の許容をテストの制限時間 + 900 秒とし、雛形に進捗の記録の指示を足す | 担当はテストを 1 回実行し、その間は何も出力しない。全体の制限時間は工程ごとの上限が決めるため渡さない | + +## 仕様 + +### 常に成り立つ条件 + +| 条件 | 破れたときの扱い | +| --- | --- | +| 検証を受けていないコミットを、公開したまま次の工程へ渡さない | 3 つの取り込みが結果なしで範囲を取り消し、取り消した後の先端を次の起点にする | +| 取り消しへ着手する前に、公開の保留の印を立てて保存する | 取り消しは済んだのに公開できずに終わると、未検証の変更が Pull Request に残る | +| 取り消した後の起点は、その場で保存する | 保存せずに落ちると、次の実行が古い起点から範囲を取り直し、取り消しのコミット自体を未申告と判定する | +| 同じ工程・同じ試行番号の結末は、記録に 1 件しか入らない | 記録済みなら結果ファイルを読まずに終了コード 2 を返す。後から現れた結果ファイルを、取り消し済みの範囲の申告として取り込まない | +| 範囲の「1 件もコミットされていない」と「確定できなかった」を区別する | 混同すると、確定できないときに検査が素通りする | +| 結末の記録は追記だけで、上書きしない | 担当を替えると適用ラウンドの担当は書き換わるが、どの担当がどの試行で失敗したかは記録から読める | +| 取り消すコミットが無ければ、取り消しも公開も実行しない | 範囲が空のときは記録だけを足す | +| この変更の前に始めた実行の状態ファイルを、書き換えずに読める | 増えた鍵が無い状態は、試行 0 回・失敗なしとして読む | + +### 構成要素と責務 + +| 要素 | 責務 | 置き場所 | +| --- | --- | --- | +| 結末の読み取り | 状態と工程から結果ファイルの名前の幹を組み、結末の共通層を呼んで値を返す。中断も画面への出力もしない | `refactor_lib/gitfacts.py` の `read_result` | +| 取り込みの共通手順 | 範囲の確定・未検証のコミットの取り消し・叩き直しの判定・結果なしの一連 | `refactor_lib/intake.py` | +| 適用ラウンドの進行 | 適用ラウンドの一覧・進行中の適用ラウンド・開き直しの判定・輪番から担当を引く | `refactor_lib/rounds.py` | +| 適用の取り込みと開く側 | 開き直しの判定で開く。結果なしでは担当の交代か取り消しを決める | `refactor_lib/commands/apply.py` | +| 修正の取り込み | 適用ラウンドの担当の結果を読む。結果なしは修正ラウンドを進める | `refactor_lib/commands/converge.py` | +| 最終ゲートと最終ゲートの修正の取り込み | 共通の手順を通す。修正担当を輪番から引く | `refactor_lib/commands/gate.py` | +| 判断の基準になる値 | 試行の上限と、無進捗の許容の余白 | `refactor_lib/vocabulary.py` | +| 起動の出力 | 無進捗の許容を出す | `refactor_lib/commands/setup.py` | +| 必須の記名の読み取り | 末尾から続くトレーラーの段落を読む | `refactor_lib/gitfacts.py` の `commit_trailers` | +| 結末の共通層 | 理由の語彙と起動し直しの可否を持つ。この変更では読むだけ | `plugins/ndf/scripts/lib/monitor_outcome.py` | + +要素の関係(辺は呼び出し): + +```mermaid +graph TD + subgraph 取り込み + A[適用の取り込み] + F[修正の取り込み] + G[最終ゲートの修正の取り込み] + end + subgraph kyotsu["共通の手順"] + R[結末の読み取り] + I["取り込みの共通手順
範囲の確定 / 取り消し / 結末の記録"] + end + subgraph shinko["適用ラウンドの進行"] + N[適用ラウンドを開く] + RO[開き直しの判定] + IS[輪番から担当を引く] + end + MO[結末の共通層] + A --> R + F --> R + G --> R + R --> MO + A --> I + F --> I + G --> I + A --> RO + N --> RO + A --> IS + G --> IS +``` + +### 起動 1 回の結末を読む + +**結果ファイルを自前で開かず、結末の共通層を呼ぶ。** 包みが行うのは、状態・担当・工程・提案 +ラウンドから結果ファイルの名前の幹を組むことと、共通層の値をそのまま返すことだけである。 + +| 項目 | 内容 | +| --- | --- | +| 入力 | 状態ファイルの辞書(一時ディレクトリと実行の識別子を読む)、担当、工程(`apply` / `fix` / `final-fix`)、提案ラウンド(最終ゲートの修正では省く) | +| 出力 | 結末。使える結果、または結果なしの理由と起動し直しの可否 | +| 失敗の形 | **失敗しない。** 中断せず、標準出力と標準エラーにも書かない | + +**理由の語彙 9 語と起動し直しの可否は、結末の共通層が 1 か所で持つ。** この文書では定義せず、 +[起動 1 回の結末の仕様](cross-review-launch-outcome.md)を参照する。取り込みは理由をそのまま +記録へ写し、可否を担当の交代の分岐に使うだけで、利用上限の条件分岐を自分では持たない。 + +### 取り込み 1 回の共通手順 + +3 つの取り込みは、結果を読めたときも読めなかったときも同じことを行う。起点から先端までの +範囲を確かめ、通らなければ取り消し、起点を取り消した後の先端へ進める。違うのは起点の鍵と +記録先だけなので、その差だけを取り込みの範囲の値で受け取る。 + +| 手順 | 契約 | +| --- | --- | +| 範囲の確定 | 起点から先端までのコミットを新しい順で返す。確定できなければ「確定できなかった」を返す | +| 取り消し | 公開の保留の印を立てて保存 → 新しい順に取り消す → 起点(と適用ラウンドの起点)を先端にして保存。取り消した件数を返す | +| 叩き直しの判定 | 同じ工程・同じ試行番号の記録があれば真 | +| 結果なしの一連 | 範囲の確定 → 取り消し → 結末の記録へ 1 件を足して保存。取り消したときだけ公開する。範囲を確定できなければ、取り消しも記録も行わずに「確定できなかった」を返す | + +**何で終わるかは取り込みの側が決める。** 範囲を確定できなかったときの扱いは 3 つで異なる。 + +### 適用ラウンドを開くかどうかの判定 + +判定に使うのは適用ラウンドが持つ 2 つだけである。開いた回数と、結末の記録のうち工程が適用の +ものの件数である。 + +| 値 | 条件 | 意味 | +| --- | --- | --- | +| `empty` | 項目が 1 件も無い | 開かない。取り消し済み(理由は項目なし)にして次を探す | +| `exhausted` | 適用の失敗の件数が試行の上限に達した | 開かない。取り消し済み(理由は結果なし)にする | +| `resume` | 開いた回数が失敗の件数より多い | 開いたまま閉じていない試行の再開。起点も試行の番号も動かさない | +| `open` | 上のいずれでもない | 開いて試行の番号を進め、起点をその時点の先端にする | + +適用ラウンドを開く側の流れである。取り込み済みの適用ラウンドは判定に掛けず、起点も修正ラウンドの +数も動かさずに開き直す。適用は取り込んだが検証まで進めずに落ちた実行を再開できるようにする +ためである。 + +```mermaid +graph TD + S[未着手・取り込み済みの適用ラウンドを順に見る] --> K{状態} + K -->|無い| E1[終了コード 1] + K -->|取り込み済み| O[開き直す
起点と試行の番号を動かさない] + K -->|未着手| R{開き直しの判定} + R -->|項目なし・上限| D[取り消し済みにする] --> S + R -->|開く| INC[試行の番号を進め
起点を先端にする] + R -->|再開| OUT[担当と項目を出す] + INC --> OUT + O --> OUT +``` + +### 結果を残さなかった適用の試行の閉じ方 + +適用の取り込みは、置き土産の後始末と取り込み済みの判定を済ませ、着手前のテストが成功と確認 +できていることを確かめてから、叩き直しの判定を見て結果を読む。**着手前のテストの確認は結果を +読むより先に行う。** 成功と確認できていない状態で採ると、壊したのか元から壊れていたのかを +判別する手段が無い。 + +結果なしのときは次の順で閉じる。 + +1. 結果なしの一連を通す。範囲を確定できなければ、適用ラウンドの項目を保留にして終了コード 4 で + 中断する。試行の番号が 0 なら 1 として記録し、適用ラウンドの試行の番号を 1 にする +2. 開き直しの判定を読む。「開く」なら担当を替える。輪番の通し番号を 1 ずつ進め、その適用ラウンドで + 失敗した担当と現在の担当のどれとも違う担当が出た最初の番号を採る。参加者の数だけ進めても + 出なければ、替える先が無い +3. 替える先が無いときは、起動し直しの可否で決める。可(結果ファイルが無い・無進捗など)なら + 担当を替えずに 2 回目を開く。否(利用上限)ならその場で取り消す +4. 取り消すときは、適用ラウンドの項目を見送りへ入れて取り消し済み(理由は結果なし)にし、取り込みの + 時刻を立てて、残りの適用ラウンドの有無で次の局面を決める +5. 保存して終了コード 2 で終わる。公開は手順 1 が取り消したときに済んでいる + +**見送りの理由には、どの担当がどの理由で結果を残さなかったかを並べる。** 改修計画の見送った +項目の表にそのまま出る。 + +### 修正と最終ゲートの修正の結果なし + +どちらも叩き直しの判定 → 結末の読み取り → 結果なしの一連という同じ流れを通り、終了コード 2 で +終わる。違うのは修正ラウンドの数の進め方である。 + +| 取り込み | 範囲を確定できないとき | 起動し直しが可のとき | 起動し直しが否のとき | +| --- | --- | --- | --- | +| 修正 | 修正ラウンドを 1 進めて中断 | 修正ラウンドを 1 進める | 修正ラウンドを上限の値にする | +| 最終ゲートの修正 | そのまま中断 | 修正ラウンドを進めない(次の最終ゲートが進める) | 修正ラウンドを上限の値にする | + +**修正の担当は適用ラウンドの担当から読む。** 結末の記録も、最終ゲートの修正を除いてその適用ラウンド +が持つ。担当が適用ラウンドごとに決まるためである。 + +**最終ゲートの修正を取り消すと、次の最終ゲートは修正前の地点でテストする。** 取り消した +コミットは修正のコミットの一覧に入らない。起動し直しが否で修正ラウンドが上限の値になった後の +最終ゲートは、テストが落ちれば取り消さずに報告して終わる。押し出し済みの地点で、上限に達しても +採用した改善項目は取り消さないという既存の規則を変えない。 + +### 項目の無い適用ラウンドを作らない + +3 か所で止める。 + +| 場所 | 扱い | +| --- | --- | +| 適用ラウンドの一覧 | 鍵が無いときだけ、古い版として提案ラウンド全体を 1 つの適用ラウンドとしてまとめる。空の配列はそのまま返す | +| 適用ラウンドを開く側 | 項目の無い未着手の適用ラウンドは開かずに取り消し済み(理由は項目なし)にし、次を探す。残りが無ければ終了コード 1 | +| 適用の取り込み | 取り込み済みで採用が 0 件だった適用ラウンドは、取り消し済み(理由は項目なし)に直して終了コード 2 | + +### 必須の記名の読み方 + +コミットメッセージを空行で段落に分け、末尾の段落から前へ 1 段落ずつ git の判定に掛ける。 +判定がトレーラーの段落と認めなかった段落で止める。同じ鍵が 2 つの段落にあれば、末尾に近い +段落の値を採る。**1 段落目(題名)は判定に掛けない。** + +| 読む側 | 読み方 | どこまで読むか | +| --- | --- | --- | +| 進行側の検証 | 末尾から続くトレーラーの段落 | 帰属の段落を越えて必須の記名まで届く | +| 人の集計 | git の標準の読み方 | 最後の段落だけ | + +**判定そのものは git に委ねる。** 「何行以上なら記名の段落か」といった規則を進行側に持たない。 +段落だけを渡すと git は何も返さないため、題名の行を補って渡す。 + +雛形のコミットの規約は、必須の記名をメッセージの最後の段落に置き、実行環境が帰属行を足すときは +空行を挟まず同じ段落に続けることを求める。**従わなくても進行側の検証は通る。** 人の集計を +成り立たせるための補助である。 + +### 無進捗の打ち切り + +起動が無進捗の許容を出し、骨組みの適用・修正・最終ゲートの修正の 3 つの監視の呼び出しが、それを +無進捗の引数へ渡す。全体の制限時間の引数は渡さない。工程ごとの上限は別の仕組みが決める。 + +雛形は、作業段階が進むたびに進捗の記録へ 1 行追記することを担当へ求める。 + +### 終了コード + +| コマンド | 0 | 1 | 2 | 4 | +| --- | --- | --- | --- | --- | +| 適用ラウンドを開く | 開いた | 残りの適用ラウンドが無い | — | — | +| 適用の取り込み | 取り込んだ | — | この適用ラウンドを取り消した、または担当を替えて開き直す | 着手前のテストが成功と確認できていない・範囲を確定できない・適用ラウンドが無い | +| 修正の取り込み | 取り込んだ | — | 範囲を確定できない・結果なし | 適用ラウンドが無い | +| 最終ゲートの修正の取り込み | 取り込んだ | — | 範囲を確定できない・結果なし | 修正担当が未記録 | + +**骨組みの分岐は変わらない。** 適用の取り込みの 2 で次の適用ラウンドへ進み、4 で止まる。修正と最終 +ゲートの修正の取り込みの終了コードは骨組みが見ない。 + +## データ・設定 + +### 状態ファイルに増える項目 + +**版は上げない。** 鍵が無い状態ファイルは、試行 0 回・失敗なしとして読む。 + +適用ラウンド 1 件(`rounds[].apply_rounds[]` の要素)に足す鍵である。 + +| 項目 | 型 | 値 | 鍵が無いときの意味 | +| --- | --- | --- | --- | +| `attempt` | 整数 | いま開いている試行の番号。1 から | 0(まだ開いていない)。適用の取り込みは 0 を 1 回目として記録する | +| `failed_attempts` | 配列 | 結果を残さなかった起動。1 件の形は下の表 | 失敗なし | +| `drop_reason` | 文字列 | `no_result`(試行の上限、または起動し直せない結末で交代先なし)/ `empty`(項目なし) | 既存の経路で取り消した、または取り消していない | + +最終ゲートの控え(`final_gate`)にも、同じ形の結末の記録(`failed_attempts`)を足す。 + +**担当(`impl` / `impl_model`)は交代のときに書き換える。** 前の担当は結末の記録に残る。 + +### 結末の記録の 1 件 + +| 列 | 型 | 空を許すか | 意味 | +| --- | --- | --- | --- | +| `phase` | 文字列 | 許さない | `apply` / `fix` / `final-fix`。同じ適用ラウンドの適用と修正の記録を分ける | +| `attempt` | 整数 | 許さない | 適用は適用ラウンドの試行の番号、修正は修正の試行の番号、最終ゲートの修正は修正ラウンドの数。叩き直しの判定の鍵 | +| `impl` | 文字列 | 許さない | 起動した担当 | +| `reason` | 文字列 | 許さない | 結末の理由。語彙は結末の共通層が持つ | +| `detail` | 文字列 | 許す(空文字) | 監視の詳細。監視の結果ファイルが無ければ空文字 | +| `at` | 文字列 | 許さない | 記録した時刻 | +| `reverted` | 整数 | 許さない | その起動の範囲から取り消したコミットの数。0 = コミットなし | + +### 取り込みごとの範囲の値 + +| 取り込み | 起点を持つ控え | 起点の鍵 | 記録先 | 工程 | 起点を揃える先 | +| --- | --- | --- | --- | --- | --- | +| 適用 | 提案ラウンドの控え | `apply_base_sha` | その適用ラウンド | `apply` | その適用ラウンドの `base_sha` | +| 修正 | 提案ラウンドの控え | `fix_base_sha` | その適用ラウンド | `fix` | なし | +| 最終ゲートの修正 | 最終ゲートの控え | `fix_base_sha` | 最終ゲートの控え | `final-fix` | なし | + +### 起動の出力と骨組みの引数 + +| 値 | 決め方 | 既定 | +| --- | --- | --- | +| 無進捗の許容(`IMPL_STALL_TIMEOUT`) | テストの制限時間(`--test-timeout`)+ 余白 900 秒 | 1800 秒 | +| 試行の上限(`MAX_APPLY_ATTEMPTS`) | 固定値。引数を持たない | 2 回 | + +## テスト観点 + +| 観点 | 確かめ方 | +| --- | --- | +| 結末の読み取りが中断も出力もせず、結果なしと理由を値で返すこと | `plugins/ndf/skills/cross-refactoring/tests/test_intake.py` | +| 3 つの取り込みの形で、範囲が 1 件・0 件・確定できない場合の取り消しと記録が同じになること。叩き直しで記録が増えないこと | 同 `tests/test_intake.py` | +| 結果を残さない担当で、担当の交代 → 取り消し → 見送りまで進み、開き直しの回数が有限で終わること | 同 `tests/test_apply_attempts.py` | +| 交代先が無いとき、起動し直しの可否で 2 回目を開くか取り消すかが決まること | 同 `tests/test_apply_attempts.py` | +| 着手前のテストが成功と確認できていないとき、結果ファイルを読まずに終了コード 4 で終わること | 同 `tests/test_apply_attempts.py` | +| 開き直しの判定と輪番から担当を引く関数を差し替えると、開く側と取り込みの両方の分岐が従うこと | 同 `tests/test_apply_attempts.py` / `tests/test_final_fix.py` | +| 採用 0 件と項目の無い適用ラウンドで、適用ラウンドを作らず開かずに取り消すこと | 同 `tests/test_apply_rounds.py` | +| 修正の結果を適用ラウンドの担当から読み、結果なしで修正ラウンドが進んで見送りへ至ること | 同 `tests/test_abandon_items.py` | +| 最終ゲートの修正の結果なしで取り消し、次の最終ゲートが取り消し後の地点でテストすること | 同 `tests/test_final_fix.py` | +| 帰属の段落が後ろに付いたコミットから必須の記名を読み、散文の中の記名の形の行を読まないこと | 同 `tests/test_commit_trailers_git.py`(一時リポジトリで git を実行する) | +| 無進捗の許容が起動の出力に入り、骨組みの 3 つの監視の呼び出しへ渡ること | 同 `tests/test_init.py` / `tests/test_skill_terms.py` | +| 結果ファイルがあり検証を通る適用ラウンドが、変更の前と同じく 1 回目で取り込まれること | 同 `tests/test_merge_apply.py` | +| 文書の分量が分割の基準を超えないこと | `python3 scripts/check-doc-line-limit.py` | +| 参照のリンクが解決できること | `python3 scripts/check-markdown-links.py` | + +## 関連リンク + +- [issue #728](https://github.com/devbasex/ai-plugins/issues/728) — 結果なしを取り込みの 1 か所で受ける +- [issue #647](https://github.com/devbasex/ai-plugins/issues/647) — 同じ適用ラウンドが上限なしに再試行される +- [issue #592](https://github.com/devbasex/ai-plugins/issues/592) — 採用 0 件で項目の無い適用ラウンドが開く +- [issue #553](https://github.com/devbasex/ai-plugins/issues/553) — 帰属の段落で必須の記名が読めない +- [issue #674](https://github.com/devbasex/ai-plugins/issues/674) — 最終ゲートの修正で未検証のコミットが残る +- [PR #796](https://github.com/devbasex/ai-plugins/pull/796) — 実装 +- [起動 1 回の結末の語彙と起動し直しの可否](cross-review-launch-outcome.md) +- [参加する CLI と席の決め方](cross-review-participants-and-seats.md) +- [`cross-refactoring` の適用と検証の手順](../../plugins/ndf/skills/cross-refactoring/docs/02-apply-and-review.md) +- [`cross-refactoring` の修正と報告の手順](../../plugins/ndf/skills/cross-refactoring/docs/04-fix-and-report.md) +- [`cross-refactoring` の手順](../../plugins/ndf/skills/cross-refactoring/SKILL.md) diff --git a/docs/specifications/cross-review-launch-outcome.md b/docs/specifications/cross-review-launch-outcome.md index be6de51a..d6339487 100644 --- a/docs/specifications/cross-review-launch-outcome.md +++ b/docs/specifications/cross-review-launch-outcome.md @@ -200,8 +200,8 @@ pid だけへシグナルを送っており、CLI が起こした子プロセス ### cross-refactoring の読み取りの契約 -cross-refactoring の取り込みが従う契約を、ここで定める。**この契約の実装は #728 にあり、 -この時点では入っていない。** +cross-refactoring の取り込みが従う契約を、ここで定める。**契約を守る側の仕様は +[結果なしの取り込みと開き直し](cross-refactoring-apply-intake.md)にある。** | 項目 | 契約 | | --- | --- | @@ -248,7 +248,7 @@ cross-refactoring の取り込みが従う契約を、ここで定める。**こ - [issue #729](https://github.com/devbasex/ai-plugins/issues/729) — 結果なしの判断を共通層へ移す - [issue #619](https://github.com/devbasex/ai-plugins/issues/619) — 利用上限で止まった担当の空振りの起動し直し - [issue #584](https://github.com/devbasex/ai-plugins/issues/584) — 止めた担当が後から結果ファイルを書く -- [issue #728](https://github.com/devbasex/ai-plugins/issues/728) — cross-refactoring 側の読み取りの実装 +- [結果なしの取り込みと開き直し](cross-refactoring-apply-intake.md) — cross-refactoring 側の読み取りを使う仕様 - [PR #791](https://github.com/devbasex/ai-plugins/pull/791) — 実装 - [`cross-review` の状態ファイルと入出力の契約](../../plugins/ndf/skills/cross-review/docs/04-contracts.md) - [`cross-review` の状態とレビューの手順](../../plugins/ndf/skills/cross-review/docs/01-state-and-review.md) diff --git a/docs/specifications/cross-review-participants-and-seats.md b/docs/specifications/cross-review-participants-and-seats.md new file mode 100644 index 00000000..90c2f742 --- /dev/null +++ b/docs/specifications/cross-review-participants-and-seats.md @@ -0,0 +1,401 @@ +# cross-review: 参加する CLI が 1 者でも使えないと収束ループを開始できず、再開で渡した引数が黙って無視される → 使える者だけで開始し、毎ラウンド 2 席を確保し、再開で渡した引数は反映されるか反映しないと知らされる + +## 目的 + +**参加する CLI のどれか 1 者が導入・認証されていなくても、収束ループが始まる。** 使えない者と +その理由は、初期化の出力と状態ファイルに残る。 + +**各ラウンドに 2 つの席が確保される。** 使える者が 2 者に満たないときは、ホスト、次に同じ +ランタイムの 2 つ目が席を埋める。1 席で回るのは、利用者が 1 者指定を渡したときだけである。 + +**中断した収束ループを引数を変えて再開すると、その引数は反映されるか、反映しないと知らされる。** +黙って捨てられる引数は無い。 + +**使える者の決定・席の埋め方・再開の反映の 3 つの規則は、収束ループを回す 2 つの Skill が +共有する共通層が 1 か所ずつ持つ。** Skill の側は結果を状態ファイルと終了コードへ写すだけである。 + +**手順と引数の表は +[`cross-review` の SKILL.md](../../plugins/ndf/skills/cross-review/SKILL.md)と +[`docs/`](../../plugins/ndf/skills/cross-review/docs/05-pool-and-convergence.md)が正である。** +ここに書き写さない。この文書が扱うのは、そこに書かない決定の理由と、共通層の契約である。 + +## 用語 + +本文は左の業務用語で書く。識別子は表・コードブロック・業務用語の初出の括弧書きにだけ置く。 + +| 業務用語 | 識別子 | 何を指すか | +| --- | --- | --- | +| ランタイム | `ALL_RUNTIMES` | `claude` / `codex` / `agy` / `kiro` の 4 つ。並びは固定 | +| ホスト | `host` | 収束ループを起動している CLI | +| 参加の母集合 | `review_pool(host)` / `refactor_pool(host)` | Skill ごとに決まる参加者の出発点。cross-review は全ランタイム − ホスト | +| 参加者 | — | 参加の母集合に足す者を加え、外す者を除いた一覧。認証の確認の対象 | +| 使える者 | `participants.available` | 参加者のうち認証の確認を通った者 | +| 認証の確認 | `probe_auth` | 確認コマンドを走らせ、止めずに結果だけを返す共通層の関数 | +| 使える者の解決 | `resolve_participants` | 参加の母集合・足す者・外す者・1 者指定・確認の結果から使える者を決める共通層の関数 | +| 参加者の記録 | `participants` | 使える者の解決の結果を持つ状態ファイルの項目 | +| 席 | — | 1 ラウンドで 1 つの CLI プロセスが占める場所 | +| 席の名前 | `SEAT_PATTERN` / `seat_runtime` | ランタイム名か、その名前に `-2`〜`-9` を付けた形 | +| 席の埋め方 | `review_seats` | 使える者と埋め合わせから 2 席を返す共通層の関数 | +| 埋め合わせ | `participants.fallback` | 席が足りないときに使う者。ホストが入る | +| 適用の輪番 | `impl_assign` | 参加者から実装担当 1 者を返す共通層の関数 | +| 1 者指定 | `--only` | 担当を 1 者に固定する引数 | +| 全員を要する指定 | `--require-all` | 認証の確認の失敗が 1 者でもあれば初期化を止める引数 | +| 再開 | — | 状態ファイルが残り `final` が `null` のときの初期化 | +| 再開の反映 | `apply_resume_args` / `ResumeField` | 渡した引数を状態へ重ね、反映しない引数を知らせる共通層の関数と、表の 1 行 | +| 再開で変えた値の記録 | `resume_changes` | 再開で変えた値を積む状態ファイルの項目 | +| 初期化 / ラウンドの開始 / 結果の受け口 / 完了報告 | `init` / `start-round` / `read-result` / `report` | `state.py` の副コマンド | + +## 対象範囲 + +この文書が扱うのは、共通層と cross-review の側である。 + +| 扱う | 扱わない | +| --- | --- | +| 共通層の使える者の解決・認証の確認・席の埋め方・適用の輪番・席の名前・再開の反映 | cross-refactoring の参加の母集合と担当の差し替え(別の課題が持つ) | +| cross-review の新規と再開の初期化、担当の決まる順、席の名前が流れる経路、完了報告の参加者の節 | 起動した後に分かる使えなさで担当を自動的に外す仕組み | + +適用の輪番は共通層に入っているためここで扱う。呼び出す側は cross-refactoring だけであり、 +その置き換えはこの時点では入っていない。 + +## 背景 + +**認証の確認が関門だった。** 従来の確認は 1 件の失敗でその場で終了しており、4 つの CLI が +揃っていない環境では収束ループを開始できなかった。使える CLI が 2 者あっても、3 者目が +入っていなければ初期化ごと止まる。 + +**使える者が 2 者に満たないラウンドの扱いが無かった。** 担当を決める関数は参加の母集合を +「全ランタイム − ホスト」の定数から作り、使える者を入力に取らない。そのため一部が使えない +構成では、担当が 1 者になるか、担当そのものを決められなかった。 + +**再開で渡した引数が黙って無視されていた。** 中断した収束ループを 1 者指定やラウンドの上限を +変えて再開しても、状態ファイルの値がそのまま使われる。利用者には、渡した引数が効いたのか +どうかを知る手立てが無かった。 + +**前のラウンドの検査が、固定の 2 者で結果を数えていた。** 担当がその 2 者と違うラウンドでは、 +担当でない者を結果なしと読み、修正の記録が無いまま次のラウンドへ通していた。 + +## 決定と理由 + +| 決定 | 理由 | +| --- | --- | +| 使える者を決める規則を共通層の 1 つの関数へ置き、Skill は結果を状態ファイルと終了コードへ写すだけにする | 規則を Skill ごとに書くと、参加の母集合の作り方・除外の検査・確認の扱いが 2 か所にでき、片方だけが古くなる | +| 認証の確認は止めずに結果だけを返す形にし、確認コマンド・未認証の文言・時間切れの秒数は変えない | 確認と中断が 1 つの関数にあると、使える者で回す経路から呼べない。止めるかどうかは全員を要する指定が決める | +| 参加者は「既定に足す者を加え、外す者を除く」で決め、既定は Skill ごとの関数が持つ | 使う側を並べる形は、ホストが変わるたびに一覧を書き直すことになる | +| 全員が揃わないなら始めたくない運用のために、従来の関門を指定で選べるようにする | 既定を変えるだけでは、揃っていることを要求する運用が選べなくなる | +| 席は 2 つとし、使える者 → ホスト → 同じランタイムの 2 つ目の順で埋める | 各ラウンドで 2 つの目で見ることを最優先にする。同じ言語モデルの 2 つの文脈より、違う言語モデルの 2 つの文脈のほうが観点が分かれる | +| 埋め合わせの候補は、使える者に含まれない者だけを使う | 足す者の指定でホストが使える者に入っているとき、ホストを埋め合わせにも使うと同じ席の名前が 2 つ並ぶ | +| 1 者指定のときは埋め合わせをせず、ホストの確認も行わない | 利用者が 1 席と決めた指定である | +| 担当の単位を席の名前にし、1 つ目の席の名前はランタイム名そのままにする | 同じランタイムの 2 つ目を立てるには、結果ファイルの stem と状態ファイルの鍵を分ける名前が要る。埋め合わせが要らない実行では、従来と同じ名前しか現れない | +| 接尾辞の区切りをハイフンにする | ランタイム名にハイフンを含むものが無いため、シェル側の切り出しと共通層の解釈が同じ規則になる | +| 監視では、席の形に合わない名前をそのまま返す | 担当名を任意の骨格で受ける cross-refactoring の経路がある。形で弾くとその経路が壊れる | +| 担当はラウンドの記録から先に見る | 再開で 1 者指定を変えても、過去のラウンドの担当が変わらない | +| 前のラウンドの検査にも、そのラウンドの担当を渡す | 固定の 2 者で数えると、担当が違うラウンドで担当でない者を結果なしと読み、修正の記録が無いまま次へ通す | +| 適用の輪番は「ラウンド番号を参加者の数で割った余り」の式を保つ | ラウンド 1 が 2 番目の者から始まるため、ホストが最初に適用する形にならない | +| 再開の反映を共通層に置き、どの引数を「反映する」「知らせる」にするかは Skill ごとの表が持つ | 片方の Skill の再開の経路だけを直すと、もう片方に引数を捨てる形が残る | +| 状態ファイルに載る引数は表のどちらかに必ず載せ、引数の既定を未指定にする | 黙って捨てる引数を残さない。既定値と同じ値なら渡していないとみなす形では、上限を既定値へ戻す操作を区別できない | +| 担当に関わる引数を渡した再開でだけ、認証の確認をやり直して参加者を作り直す | 途中で担当が入れ替わると、前のラウンドの記録と突き合わせられなくなる | +| 作り直しのとき、渡さなかった引数は状態ファイルの値で補う | 初期値へ戻すと「明示した引数だけを反映する」が破れる | +| 指定を外す値は予約語 `none` にする | 骨組みは値のあるときだけ引数を渡すため、空文字列では指定を外せない | +| 参加者の作り直しは、記録へ 1 件として積む | 項目ごとに積むと 1 回の再開で最大 7 件になり、完了報告で読みにくい | +| 骨組みは 1 者指定のシェル変数で担当を絞り直さず、ラウンドの開始が返す席を使う | 返る一覧は 1 者指定と埋め合わせを反映済みである。もう一度絞ると、状態ファイルとシェル変数がずれたときに起動も監視も誰にも当たらない | +| 起動した後に分かる使えなさで担当を自動的に外す仕組みは作らない | 一時的な打ち切りでも、以後のラウンドから恒久的に外れる | +| 規則の実装は共通層、手順は各 Skill の手順書、理由はこの文書が持つ | 規則が決まっても、置き場所が決まらなければ次に使う人へ届かない | + +## 仕様 + +### 常に成り立つ条件 + +| 条件 | 破れたときの扱い | +| --- | --- | +| 使える者の並びは、ランタイムの固定の順である | 解決の中で並べ直すため、入力の順序に依らない | +| 使える者が 3 者のときの席は、この変更の前の輪番と同じ値になる | 4 つのホスト × ラウンド 1〜12 の全組で一致することをテストが固定する | +| 1 ラウンドの席は 2 つで、同じ席の名前が 2 つ並ばない | 埋め合わせの候補から使える者を除いて選ぶ | +| 使える者も埋め合わせも無ければ、席を返さず失敗する | 割り当ての失敗(`AssignmentError`)を上げ、初期化は状態ファイルを作らずに終了コード 1 | +| 状態ファイルに載る引数は、再開の表の「反映する」か「知らせる」のどちらかに載る | 表に無い引数は、状態ファイルに載らない 3 つ(作業ツリー・観点・追加指示のファイル)だけである | +| 再開で渡さなかった引数は、状態ファイルの値のまま残る | 参加者を作り直すときも、渡さなかった引数は記録から補う | +| この変更の前に始めた実行の状態ファイルを、書き換えずに読める | 項目が無いときの読み方を「項目が無いときの読み方」が決める | + +### 構成要素と責務 + +| 要素 | 責務 | 置き場所 | +| --- | --- | --- | +| 参加の母集合の既定 | Skill ごとの出発点を返す | `lib/assignment.py` の `review_pool` / `refactor_pool` | +| 使える者の解決 | 参加の母集合・ホスト・足す者・外す者・1 者指定・確認・全員を要するかから、参加者の記録を返す。名前の矛盾と欠けを例外で返す | 同 `resolve_participants` | +| 認証の確認 | 確認コマンドを走らせ、止めずに担当ごとの結果を返す | `lib/auth.py` の `probe_auth` | +| 席の埋め方 | 使える者と埋め合わせから 2 席を返す。規則の表はこの関数の docstring が正 | `lib/assignment.py` の `review_seats` | +| 適用の輪番 | 参加者から実装担当 1 者を返す | 同 `impl_assign` | +| 席の名前 | 席の名前からランタイムを引く。形の検査を持つ | 同 `seat_runtime` / `SEAT_PATTERN` | +| 再開の反映 | 表に従って状態へ書き、記録へ積み、知らせる行を返す | `lib/statefile.py` の `apply_resume_args` / `ResumeField` | +| 使える者の決定(cross-review) | 共通層を呼び、埋め合わせを決め、参加者の記録を返す。失敗を終了コード 1 へ写す | `cross-review/scripts/state.py` の `_resolve_reviewers` | +| 担当の読み出し | 決まった順で席を返す。前のラウンドの検査へその担当を渡す | 同 `_round_reviewers` / `_guard_previous_round` | +| 席の受け口 | 席の名前を受け、起動する CLI を席の名前から引く | `read-result` の引数の型、`launch-reviewer.sh`、`critique.sh`、`lib/monitor.py`、`measure.py` | +| 完了報告の参加者の節 | 参加した者・外した者・確認を通らなかった者・埋め合わせ・再開で変えた値を出す | `cross-review/scripts/state.py` の `_print_participants` | + +要素の関係(辺は呼び出し): + +```mermaid +graph TD + PL[参加の母集合の既定] --> RP[使える者の解決] + RP --> PA[認証の確認] + RI[初期化] --> RP + RI --> RA[再開の反映] + RR[担当の読み出し] --> RS[席の埋め方] + RC[席の受け口] --> SR[席の名前] +``` + +### 使える者の決め方 + +参加者を決め、認証の確認を通った者を使える者として記録する。順序は次のとおりである。 + +| 順 | 何をするか | +| ---: | --- | +| 1 | 足す者・外す者の各名前がランタイムの一覧にあり、互いに重ならないことを確かめる。外す者が「既定 ∪ 足す者」に含まれなければ弾く | +| 2 | 参加者 = 既定 ∪ 足す者 − 外す者(ランタイムの固定の順) | +| 3 | 1 者指定があれば、参加者に含まれ外す者に無いことを確かめ、参加者をその 1 者にする | +| 4 | 参加者へ認証の確認を行う。確認を飛ばす環境変数(`NDF_SKIP_AUTH_CHECK`)が立っていれば全員を通ったものとし、飛ばした印を真にする | +| 5 | 全員を要する指定が真で通らない者がいれば、欠けた者と理由を並べて失敗する | +| 6 | 通った者を使える者、通らなかった者と理由を通らなかった者として返す | + +**外した者へは確認コマンドを呼ばない。** 確認の回数は「参加者の数 + 埋め合わせが要るときの +ホストの 1 回」を超えない。 + +cross-review の新規の初期化は、この結果に席の埋め合わせを足して状態ファイルへ書く。 + +```mermaid +graph TD + H[ホストを確定] --> P[参加の母集合の既定を引く] + P --> RP[使える者の解決] + RP -->|名前の矛盾 / 欠け| F[状態ファイルを作らず終了コード 1] + RP -->|通った| N{使える者が 2 者以上} + N -->|はい| W[状態ファイルを書く] + N -->|いいえ| HP[ホストを確認し埋め合わせを決める] + HP -->|席を埋められる| W + HP -->|埋められない| F +``` + +**1 者指定のときは、この分岐へ入らない。** 埋め合わせを行わず、指定した 1 者が確認を通らない +ときだけ終了コード 1 で終わる。確認を通らない担当が席に座ると、レビューが行われないまま +収束するためである。 + +### 席の埋め方 + +| 使える者の数 | 席 | +| ---: | --- | +| 3 以上 | ラウンド番号を使える者の数で割った余りの位置と、その次の位置の 2 者。並びは使える者の順 | +| 2 | その 2 者 | +| 1 | その 1 者と、埋め合わせのうち使える者に含まれない先頭の者。そのような者が無ければ、その 1 者の 2 つ目 | +| 0 | 埋め合わせの先頭と、その 2 つ目。埋め合わせが空なら失敗する | + +**3 者のときの値は、この変更の前の輪番と一致する。** 4 者のときはラウンド 1〜4 で各者が +ちょうど 2 回担当になる。 + +### 席の名前 + +**担当の単位は席の名前である。** 形はランタイム名か、その名前にハイフンと 2〜9 を付けたもの +(`^(claude|codex|agy|kiro)(-[2-9])?$`)で、後者が同じランタイムの 2 つ目以降を表す。 + +| 受け口 | 席の名前の扱い | +| --- | --- | +| 結果の受け口の担当の引数 | 席の形を型として検査する。通らなければ終了コード 2 | +| レビューの起動(`launch-reviewer.sh`)と反証の起動(`critique.sh`) | 席の形を先頭で検査し、起動する CLI を `${SEAT%%-*}` で選ぶ。結果ファイルの stem は席の名前で組む | +| 監視(`lib/monitor.py`) | 位置引数は席の形か `both` を通す。CLI 固有のログの検査は席の名前からランタイムを引いて選ぶ。席の形に合わない名前はそのまま返す | +| 計測(`measure.py`) | 担当の記録を数えるとき、名前の一覧ではなく席の形に一致する鍵を数える | + +埋め合わせがあるラウンドで席の名前が流れる経路は次のとおりである。 + +```text +start-round → REVIEWERS="codex claude-2" + → launch-reviewer.sh claude-2 … : 起動する CLI は claude、stem は claude-2-review-pr + → monitor.py --agents codex,claude-2 + → state.py read-result claude-2 : rounds[-1]["claude-2"] へ書く + → critique-round.sh codex claude-2 → critique.sh claude-2 … +``` + +### ラウンドの担当が決まる順 + +**先に当たったものを採る。** + +| 順 | 状態 | 返る担当 | +| ---: | --- | --- | +| 1 | そのラウンドの記録に担当がある | その値 | +| 2 | 1 者指定がある | その 1 者だけ | +| 3 | 参加者の記録がある | 使える者と埋め合わせから 2 席 | +| 4 | ホストがある | 参加の母集合から 2 席(この変更の前の輪番と同じ値) | +| 5 | どれも無い | `codex` / `agy` | + +**ラウンドの記録を 1 者指定より先に見る。** 再開で 1 者指定を変えても、過去のラウンドの担当が +変わらないようにするためである。 + +**前のラウンドの検査は、そのラウンドの記録の担当で結果を数える。** 記録が無いときだけ、この順で +引き直す。 + +### 適用の輪番 + +実装担当 1 者を「ラウンド番号を参加者の数で割った余り」の位置から選ぶ。式はこの変更の前と +同じで、除数だけが参加者の数になる。ラウンド 1 が 2 番目の者から始まるため、ホストが最初に +適用する形にならない。呼び出す側は cross-refactoring だけであり、その置き換えはこの時点では +入っていない。 + +### 再開で渡した引数の扱い + +| 扱い | 引数 | 何が起きるか | +| --- | --- | --- | +| 反映する | `--max-rounds` / `--rotate-after` / `--verify-command` / `--verify-exit-code` | 状態ファイルを書き換え、記録へ 1 件積み、変更の行を出す | +| 反映し、参加者を作り直す | `--only` | 上に加えて、認証の確認をやり直して参加者の記録を置き換える。`none` で 1 者指定を外す | +| 参加者を作り直す | `--exclude` / `--include` / `--require-all` | 使える者を解決し直して参加者の記録を置き換える。`none` で一覧を空へ戻す | +| 知らせる | `--host` | 状態ファイルは変えず、状態と違うときだけ 1 行を出す | + +**値が同じ引数は、行も記録も出さない。** + +```mermaid +graph TD + A[状態ファイルを読む] --> AR[再開の反映] + AR --> N[知らせる行を出す] + N --> RA{担当に関わる引数を渡した} + RA -->|はい| RP[使える者の解決をやり直す] + RP -->|失敗| F[状態ファイルを書き換えず終了コード 1] + RP -->|通った| W[参加者の記録を書き 1 件積む] + RA -->|いいえ| W2[変えた項目だけ書く] +``` + +**作り直しの入力は 2 つである。** 渡した引数と、渡さなかった引数の状態ファイルの値(足した者・ +外した者・全員を要するかと、1 者指定)である。ホストを足して始めた実行へ外す指定だけを渡した +再開では、足した者の記録はそのまま残る。 + +**作り直しの失敗は、状態ファイルを書き換える前に起きる。** 使える者の解決を先に呼び、通って +から状態ファイルへ書く。 + +### 完了報告の「参加した者」 + +完了報告の PR 履歴の後に、7 行の節を出す。 + +```text +## 参加した者 +- 母集合: codex / agy / kiro +- 使える者: codex / kiro +- --exclude で外した者: agy +- --include で足した者: なし +- 確認を通らなかった者: なし +- 席の埋め合わせ: なし +- 再開で変えた値: 2026-09-19T12:00:00 participants … → … +``` + +参加者の記録を持たない状態ファイルでは「使える者: 記録なし」の 1 行だけを出す。確認を飛ばした +印が真のときは、通らなかった者の行に「確認を飛ばした(`NDF_SKIP_AUTH_CHECK`)」と出す。 + +## データ・設定 + +### 状態ファイルに増える 2 項目 + +形式そのものは +[04-contracts.md](../../plugins/ndf/skills/cross-review/docs/04-contracts.md)が正である。 + +| 項目 | 型 | 意味 | +| --- | --- | --- | +| `participants` | オブジェクト(下の表) | 使える者の解決の結果。項目が無いのは、この変更の前に始めた実行である | +| `resume_changes` | オブジェクトの配列 | 再開で変えた値の記録。追記だけを行う。要素は `at` / `field` / `from` / `to` | + +参加者の記録の中身は 8 項目である。共通層が 7 項目を返し、埋め合わせだけを cross-review が足す。 + +| 項目 | 型 | 意味 | +| --- | --- | --- | +| `pool` | 文字列の配列 | 参加の母集合の既定。ランタイムの固定の順 | +| `included` | 文字列の配列 | 足した者。空は「足していない」 | +| `excluded` | 文字列の配列 | 外した者。空は「外していない」 | +| `available` | 文字列の配列 | 使える者。1 者指定があればその 1 者だけ | +| `unavailable` | オブジェクト | 確認を通らなかった者と理由 | +| `probe_skipped` | 真偽値 | 確認を飛ばしたか。通らなかった者が空である理由を区別する | +| `require_all` | 真偽値 | 全員を要する指定の値。新規の既定は偽 | +| `fallback` | 文字列の配列 | 席の埋め合わせに使える者。使える者が 2 者以上か、1 者指定があれば空 | + +**ラウンドの記録の担当と鍵には席の名前が入りうる。** 過去のラウンドの担当は再開で書き換えない。 + +### 初期化の引数 + +| 引数 | 型 | 既定 | 新規の経路 | 再開の経路 | +| --- | --- | --- | --- | --- | +| `--max-rounds N` / `--rotate-after K` | 整数 | 未指定 | 無ければ 12 / 8 | 渡せば反映 | +| `--only RUNTIME` | 4 つの名前か `none` | 未指定 | `none` は無しと同じ | 渡せば反映。`none` で外す | +| `--exclude NAMES` / `--include NAMES` | カンマ区切りの名前。繰り返し可。`none` | 未指定 | 外す / 足す | 渡せば置き換え。`none` で空 | +| `--require-all` / `--no-require-all` | 真偽値 | 未指定 | 無ければ偽 | 渡せば反映 | +| `--host RUNTIME` | 4 つの名前 | 未指定 | 無ければ推定 | 反映せず、違えば 1 行 | +| `--verify-command CMD` / `--verify-exit-code N` | 繰り返し可 | 未指定 | 無ければ空 | 渡せば置き換え | + +**上限の既定を引数の側に置かない。** 新規の経路が定数を置く。引数の側に既定を置くと、再開の +たびに利用者が指定していない値で状態ファイルを上書きする。 + +**名前の検査は 2 段に分かれる。** 綴りは引数の型が弾き(終了コード 2)、参加の母集合との関係は +共通層が弾く(終了コード 1)。後者に当たるのは、外す者・1 者指定に参加の母集合に無い名前を +渡したとき、足す者と外す者が重なるとき、1 者指定と外す者が矛盾するとき、`none` と名前を +混ぜたときである。cross-review ではホストが参加の母集合に無いため、ホストを外す指定はここで +弾かれる。 + +### 出力と終了コード + +標準出力の形は変わらない。増えるのは標準エラーの行と、席の名前が取りうる値である。 + +| 場面 | 標準エラー | 終了コード | 状態ファイル | +| --- | --- | ---: | --- | +| 新規で全員が使える | 参加の母集合と使える者の 1 行 | 0 | 作る | +| 新規で確認を通らない者がいる | 通らなかった者と理由を 1 者 1 行 | 0 | 作る | +| 新規で使える者が 2 者に満たない | 埋め合わせの相手を 1 行 | 0 | 作る | +| 新規で使える者も埋め合わせも無い | 使える者がいない理由 | 1 | 作らない | +| 確認を飛ばした | 飛ばしたことを 1 行 | 0 | 作る | +| 全員を要する指定で欠けがある | 欠けた者と理由 | 1 | 作らない | +| 名前の矛盾 | 何が矛盾したか | 1 | 作らない | +| 再開で引数を反映した | 項目ごとに 1 行 | 0 | 書き換える | +| 再開で反映しない引数が状態と違う | 引数ごとに 1 行 | 0 | 変えない | +| 再開の作り直しが失敗した | 新規と同じ | 1 | 書き換えない | + +行の先頭の印は、既存の初期化の出力に揃える。 + +| 場面 | 行の形 | +| --- | --- | +| 反映した | `↻ <項目>: <旧> → <新>` | +| 反映しない | `ℹ --<引数> は再開では反映しません(状態: <値> / 指定: <値>)` | +| 確認を通らなかった | `⚠ <名前> を担当から外しました(<理由>)` | +| 席を埋めた | `⚠ 使える者が <数> 者のため、席を<相手>で埋めます(観点が減ります)` | + +### 項目が無いときの読み方 + +**この変更の前に始めた実行の状態ファイルは書き換えない。** + +| 項目 | 無いときの読み方 | +| --- | --- | +| `participants` | ホストがあれば、参加の母集合から席を決める(この変更の前の輪番と同じ値)。ホストも無ければ `codex` / `agy` | +| `resume_changes` | 空として読む | +| ラウンドの記録の担当 | 担当の決まる順で引き直す | + +**再開で担当に関わる引数を渡したときだけ、参加者の記録を書く。** 渡さない再開では書き足さない。 + +## テスト観点 + +| 観点 | 確かめ方 | +| --- | --- | +| 使える者の解決が、通らない者を外して続け、外した者へ確認を呼ばず、名前の矛盾と欠けを例外にすること | `plugins/ndf/scripts/tests/test_lib_participants.py` | +| 認証の確認が失敗で例外を上げず、理由を返すこと | 同 `test_auth_probe.py` | +| 席の埋め方が 3 者で従来の値と一致し、2 / 1 / 0 者で規則どおりに埋めること。席の名前の形 | `plugins/ndf/skills/cross-refactoring/tests/test_assignment.py` | +| 適用の輪番が参加者の数で回ること | `plugins/ndf/scripts/tests/test_lib_assignment.py` | +| 再開の反映が、表のとおりに書き換え・記録・知らせを行い、値が同じなら何もしないこと | 同 `test_lib_resume_args.py` | +| 新規の初期化が、確認を通らない者がいても状態ファイルを作り、全員を要する指定では作らないこと | `plugins/ndf/skills/cross-review/tests/test_state_review_pool.py` | +| 完了報告が参加者の節を出し、記録が無ければ「記録なし」と出すこと | 同 | +| 再開が渡した引数だけを反映し、渡さなかった引数を状態ファイルの値で補うこと | 同 `test_state_resume_args.py` | +| 席の名前が結果の受け口・起動・監視・計測を通ること | 同 `test_seat_names.py` | +| 前のラウンドの検査が、そのラウンドの担当で結果を数えること | 同 `test_state_round_guard.py` | +| 手順書が 1 者指定のシェル変数で担当を絞り直さないこと | 同 `test_skill_layout.py` | +| 文書の分量が分割の基準を超えないこと | `python3 scripts/check-doc-line-limit.py --root .` | +| 参照のリンクが解決できること | `python3 scripts/check-markdown-links.py --root .` | + +## 関連リンク + +- [issue #727](https://github.com/devbasex/ai-plugins/issues/727) — 使える者から担当を割り当てる共通層 +- [issue #687](https://github.com/devbasex/ai-plugins/issues/687) — 各ラウンドで 2 者を確保する規則と置き場所 +- [issue #478](https://github.com/devbasex/ai-plugins/issues/478) — 認証の確認を関門から把握へ +- [issue #648](https://github.com/devbasex/ai-plugins/issues/648) — 再開で渡した引数が無視される +- [PR #793](https://github.com/devbasex/ai-plugins/pull/793) — 実装 +- [`cross-review` のレビュワーの母集合と終了基準](../../plugins/ndf/skills/cross-review/docs/05-pool-and-convergence.md) +- [`cross-review` の状態ファイルと入出力の契約](../../plugins/ndf/skills/cross-review/docs/04-contracts.md) +- [`cross-review` の状態とレビューの手順](../../plugins/ndf/skills/cross-review/docs/01-state-and-review.md) +- [`cross-review` の手順](../../plugins/ndf/skills/cross-review/SKILL.md) +- [証拠ベースのレビューと効果の測定](cross-review-evidence-based.md) — 指摘の構造化・実行検証・反証と、状態ファイルのそれ以外の鍵 +- [起動 1 回の結末と上限の検知](cross-review-launch-outcome.md) — 結果なしの理由の語彙と起動し直しの可否