概要
ndf:cross-review を 中断後に再開 (RESUMED) し、その再開ラウンドで即収束した場合、前回中断ラウンドで投稿された 未解決 (open) review thread が judge の収束判定で一切考慮されず、fix フェーズを経ずに approved になる。最終的には Step 7.5 スイープが拾うため事故にはならないが、「スイープが唯一のセーフティネット」になっている設計フローが docs に明記されていない。関連して result.json.comments_count(その回の投稿数)を PR 上の実 open thread 総数と取り違えやすい点も曖昧。
観測したケース
devbasex/devbase PR #67(docs PR)の cross-review で発生:
- 過去に同 PR で cross-review を起動 → round 2 で codex が
REQUEST_CHANGES(architecture.md:70 の SHORTCUTS 記述が実装と矛盾=実害ある誤記、ほか計3件のインラインコメント)を投稿した状態で中断。
- 後日
state.py init が RESUMED=1(前回中断 state から再開, round=0)で復帰。
- 再開後の round 1: codex=
APPROVE(0件) / gemini=COMMENT(minor 2件)。judge は この回の result.json の intent のみを見て「両方 APPROVE 相当。収束」と判定し final=approved。
- しかし PR 上の open thread は 5 件(前回 round 2 由来の 3 件 + 今回 gemini の 2 件)。前回の
REQUEST_CHANGES 由来 3 件は fix を一度も通らず残存。
- Step 7.5 スイープが graphql で全 open thread を洗い直して 5 件全て修正・Resolve(
remaining_open=0)。スイープのおかげで救われた。
問題点
1. judge が再開時の前ラウンド未解決 thread を考慮しない
state.py judge は当該ラウンドの result.json.intent だけで収束判定する。再開ラウンドが即収束すると、前回 REQUEST_CHANGES で投稿された未解決の major 級コメントを残したまま approved になる。Step 7.5 スイープが無ければ実害指摘を放置して終了する設計穴。
- 期待挙動の候補:
- (a)
init/start-round で「再開時に既存 open thread が N 件ある」ことを検出し、最初の round を強制的に fix 経由にする(あるいは judge で open_thread_count > 0 なら継続)。
- (b) 最低でも docs に「再開時の前ラウンド未解決 thread は judge では拾えず Step 7.5 スイープが回収する」フローと、スイープが必須である理由(APPROVE ラウンドの minor だけでなく再開引き継ぎ分も対象)を明記する。
2. comments_count と実 open thread 数の乖離が明文化されていない
result.json.comments_count は「その回 AI が新規投稿した件数」であり、PR 上の 実 open thread 総数とは一致しない(再開・複数ラウンド累積で乖離)。
docs/02-fix-and-rotation.md の fix サブエージェント起動テンプレは {CODEX_COMMENT_COUNT}件 を埋め込むため、メイン or サブエージェントが「この件数だけ対応すればよい」と誤解すると取りこぼす。
- スイープ側テンプレ(Step 7.5)は件数を渡さず「全 open review thread を gh api で洗い出し」と正しく書いているが、「件数を信じるな・graphql で実 open thread を必ず洗い直せ」という明示の注意書きがない。fix テンプレ側にも同趣旨の注意が欲しい。
提案
docs/01-state-and-review.md の ## Step 3: 判定 に、再開時の前ラウンド未解決 thread が judge 対象外である旨と、Step 7.5 スイープが回収責任を負う旨を追記。
- 可能なら
judge / start-round に「再開時 open thread 数チェック」を入れて、未解決があれば最初のラウンドを fix 経由にする(設計判断)。
- fix / スイープ両テンプレに「
comments_count は投稿数であり実 open thread 数ではない。必ず graphql で全 open thread を洗い直す」注意を明記。
環境
- plugin:
ndf 4.16.0 (skills/cross-review)
- 観測:
devbasex/devbase PR #67(最終的に approved / open thread 0 で正常終了。スイープ commit 5f7a0c6)
概要
ndf:cross-reviewを 中断後に再開 (RESUMED) し、その再開ラウンドで即収束した場合、前回中断ラウンドで投稿された 未解決 (open) review thread がjudgeの収束判定で一切考慮されず、fixフェーズを経ずにapprovedになる。最終的には Step 7.5 スイープが拾うため事故にはならないが、「スイープが唯一のセーフティネット」になっている設計フローが docs に明記されていない。関連してresult.json.comments_count(その回の投稿数)を PR 上の実 open thread 総数と取り違えやすい点も曖昧。観測したケース
devbasex/devbasePR #67(docs PR)の cross-review で発生:REQUEST_CHANGES(architecture.md:70のSHORTCUTS記述が実装と矛盾=実害ある誤記、ほか計3件のインラインコメント)を投稿した状態で中断。state.py initがRESUMED=1(前回中断 state から再開, round=0)で復帰。APPROVE(0件) / gemini=COMMENT(minor 2件)。judgeは この回の result.json の intent のみを見て「両方 APPROVE 相当。収束」と判定しfinal=approved。REQUEST_CHANGES由来 3 件はfixを一度も通らず残存。remaining_open=0)。スイープのおかげで救われた。問題点
1.
judgeが再開時の前ラウンド未解決 thread を考慮しないstate.py judgeは当該ラウンドのresult.json.intentだけで収束判定する。再開ラウンドが即収束すると、前回REQUEST_CHANGESで投稿された未解決の major 級コメントを残したままapprovedになる。Step 7.5 スイープが無ければ実害指摘を放置して終了する設計穴。init/start-roundで「再開時に既存 open thread が N 件ある」ことを検出し、最初の round を強制的にfix経由にする(あるいは judge でopen_thread_count > 0なら継続)。2.
comments_countと実 open thread 数の乖離が明文化されていないresult.json.comments_countは「その回 AI が新規投稿した件数」であり、PR 上の 実 open thread 総数とは一致しない(再開・複数ラウンド累積で乖離)。docs/02-fix-and-rotation.mdの fix サブエージェント起動テンプレは{CODEX_COMMENT_COUNT}件を埋め込むため、メイン or サブエージェントが「この件数だけ対応すればよい」と誤解すると取りこぼす。提案
docs/01-state-and-review.mdの## Step 3: 判定に、再開時の前ラウンド未解決 thread が judge 対象外である旨と、Step 7.5 スイープが回収責任を負う旨を追記。judge/start-roundに「再開時 open thread 数チェック」を入れて、未解決があれば最初のラウンドを fix 経由にする(設計判断)。comments_countは投稿数であり実 open thread 数ではない。必ず graphql で全 open thread を洗い直す」注意を明記。環境
ndf4.16.0 (skills/cross-review)devbasex/devbasePR #67(最終的にapproved/ open thread 0 で正常終了。スイープ commit5f7a0c6)