cross-refactoring の是正: 範囲のテストで群を検証し、構造改善を飛ばせる判定を足す(#880 #883 #494 #723) - #917
takemi-ohama wants to merge 14 commits into
Conversation
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DhcogXCb1x3eStK4VoDDCy
SIGTERM の後の点検で親シェルを先に回収する。回収しないとゾンビがグループに 残り、子がすべて終わっていても猶予の 5 秒を毎回待っていた。判定はグループの 存否のまま変えない(SIGTERM を無視する子には SIGKILL が届く)。 打ち切りの 3 件のテストの確認の待ちを 1.5 秒へ縮める(合計 約 7.5 秒)。 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DhcogXCb1x3eStK4VoDDCy
- refactor.py に副命令 assess(--base REF、--max-lines N(既定 10))を登録する - gitfacts.py に production_code_changes を足す。git diff --numstat -z --no-renames <base>...HEAD を読み、CODE_EXTENSIONS に当たり _is_test_path が偽のファイルと 変更行(追加 + 削除)を返す。rename は旧パスと新パスの両方で判定する - CODE_EXTENSIONS は cross-refactoring に無かったため、設計の一覧どおりに gitfacts.py へ置く - 終了コードは 0 = 通す / 3 = 飛ばしてよい / 2 = 起点を解けない。出力は 判定・理由・本番コード の 3 行 - tests/test_assess.py に一時リポジトリで AC12〜AC14 を確かめるテストを 8 件足す Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DhcogXCb1x3eStK4VoDDCy
- refactor.py init に --round-test CMD を足し、--baseline-test の help を「着手前と最終ゲートで実行する全体のテスト」へ直す - init は状態へ round_test(command・status)を保存する。省くか全体テストと同じ文字列なら全体テストを 1 回だけ実行して結果を写し、違えば全体テストの後に 1 回実行して失敗(終了コード 5 を含む)で止める - --scope の関門は --round-test があればその実行集合(scope.round_test_roots)で判定する。オプションの値・作業ディレクトリの根・ラッパーのスクリプトは起点に数えない - --round-test が無く --baseline-test が範囲より広いとき、1 行の案内を出して続ける(scope.round_test_hint) - verify-round と merge-fix の修正コミットの検証は scope.round_test_command(state) を実行する。round_test の無い状態ファイルは baseline_test を返す - 最終ゲートは単独起動でも round_test が baseline_test と違えば先に全体テストを 1 回通す(--ci-check があれば継続的統合で代える) - verifications と final_gate.checks の各件へ seconds(final_gate は command も)を足す - scope から使うため gitfacts._is_test_path を公開名 is_test_path へ改名する Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DhcogXCb1x3eStK4VoDDCy
- cross-refactoring の SKILL.md: 引数の表に --round-test、検証の単位を範囲のテストへ、 前提に assess(終了コード 3 なら起動しない) - CLAUDE.md の cross-refactoring の節: 起動の例に --round-test、全体テストが 2 回に寄ること - workflow-modes.md「構造改善の退避先」: 飛ばす(assess の終了コード 3)と refactoring 単独へ 退避する(今の 3 条件)を分け、飛ばしたときの記録の残し方を書く - stage-notes.md: 構造改善に入ったら assess → --round-test に範囲のテスト → --baseline-test に全体テスト、の順を書く Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DhcogXCb1x3eStK4VoDDCy
- merge_test_proposals: target の # より前が .md で終わる提案を「文書の文言を固定するテストは足さない」で見送る(F8・AC17) - verify.py に doc_wording_tests を新設し、verify_apply_round の verify_test_changes の直後で呼ぶ(F9・AC18) - 追加行の文字列リテラルが、追跡している .md のパスか / で揃えた末尾に一致すれば当たり - テストのファイルの直下の定数と、同じディレクトリの補助モジュールから import した定数も追う(補助モジュールは群で触っていなければ git から読む) - gitfacts.py に tracked_markdown(git ls-files -z '*.md')を足し、apply.py が群ごとに 1 回読んで渡す Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DhcogXCb1x3eStK4VoDDCy
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DhcogXCb1x3eStK4VoDDCy
…-494-723-cross-refactoring-fixes
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DhcogXCb1x3eStK4VoDDCy
状態探索、適用群の絞り込み、起動失敗、壊れた提案結果、バイナリ差分の現状動作を固定する。 Item-Id: R1-001 Round: 1 Impl-Runtime: codex Impl-Model: default
改修計画 — devbasex/ai-plugins #917
ラウンド 1(実装 codex)R1-001 —
|
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| error | integration | — | codex / kiro | 採用 | 1 |
なぜ: 既存の test_paths.py は指定先優先・現在地への退避・環境変数未設定の 3 つの成功経路だけを固定している。指定先にも現在地にも対象 ID が無いときに die で中断する経路(終了コード 4)が固定されていない
手順: 1. CROSS_REFACTORING_TMP_DIR を空の一時ディレクトリへ向け、現在地にも .cross_refactoring の対象状態を置かない
2. load_state を対象 ID で呼ぶ
3. SystemExit の終了コードと、状態を返さず対象 ID を示して停止したことを比較する
R1-002 — plugins/ndf/skills/cross-refactoring/scripts/launch-cli.sh#collect_round_materials
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| branch | integration | — | claude | 採用 | 1 |
なぜ: apply / fix のプロンプトへ渡す項目(RF_ITEMS)を、ラウンドと適用群(apply_round)で絞る jq の分岐がどのテストからも観測されていない。launch-cli.sh を起動するテストは propose / propose-tests / judge-test-changes / final-fix のプロンプトと起動引数だけを見ており、群をまたいだ項目が混ざらないこと・apply_round が 0 のときはそのラウンドの全項目を渡すこと・別ラウンドの項目を渡さないことは固定されていない。launch-cli.sh は関数へ分けた直後で、構造改善の対象になりやすい
手順: 1. crossref_helpers.make_state で rounds に {round: 1, apply_round: 2} と {round: 2} を、items に R1-001(round 1, apply_round 1)・R1-002(round 1, apply_round 2)・R1-003(round 1, apply_round 未設定)・R2-001(round 2)を置いた状態を作る
2. 既存の test_excluded_items._prompt_with_deferred と同じく、PATH へ何もしない codex の代替を置き、launch-cli.sh codex apply 130 1 を起動する
3. 生成された codex-apply-r1-prompt.md に現れる項目 ID の集合を正規表現 R\d+-\d{3} で取り出し、現状の出力を期待値として記録する(R1-002 だけのはず)
4. 同じ手順を fix フェーズでも行い、同じ集合になることを比べる
5. rounds の apply_round を 0 にした状態でもう一度起動し、R1-001 / R1-002 / R1-003 が入り R2-001 が入らないことを現状の出力として記録する
R1-003 — plugins/ndf/skills/cross-refactoring/scripts/launch-cli.sh#require_round
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| error | integration | — | claude | 採用 | 1 |
なぜ: 起動の失敗経路のうち固定されているのは未知のフェーズ(test_removed_review_phase)と判定差分の欠落(test_assert_changes)だけである。ROUND を省いたときに rounds の長さで補う分岐、rounds が空で ROUND も無いときに止まる分岐、未知のランタイムで止まる分岐は、どのテストも通していない。止まる経路では CLI が起動されないことが振る舞いである
手順: 1. make_state で rounds を空にした状態を作り、PATH へ引数を書き出す codex の代替(test_launch_agy_phases の STUB と同じ形)を置く
2. launch-cli.sh codex apply 130 を ROUND なしで起動し、終了コードが 0 でないこと・代替の CLI が起動されず引数ファイルが作られないこと・プロンプトファイルが作られないことを確かめる
3. rounds に 2 件(round 1 と round 2)を置いた状態で同じく ROUND なしで起動し、生成される stem が codex-apply-r2 になることを現状の出力として記録する
4. launch-cli.sh unknown propose 130 1 を起動し、終了コードが 0 でないこと・プロンプトファイルが作られないことを確かめる(メッセージは完全一致させない)
R1-004 — plugins/ndf/skills/cross-refactoring/scripts/refactor_lib/commands/apply.py#cmd_merge_proposals
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| error | integration | — | codex | 採用 | 1 |
なぜ: 提案結果が JSON オブジェクトでない経路は固定されているが、JSON として壊れた結果を 1 者が返した経路は固定されていない。壊れた 1 件で全体を止めず、その者を提案なしとして残りを統合する振る舞いが未固定である
手順: 1. 3 者分の提案結果のうち 1 者のファイルへ不正な JSON を書き、残り 2 者には同じ有効な提案を書く
2. cmd_merge_proposals を公開コマンド入口から実行する
3. 実行が継続し、壊れた結果の提案件数が 0、有効な提案が 1 件だけ採用された状態を比較する
R1-005 — plugins/ndf/skills/cross-refactoring/scripts/refactor_lib/commands/assess.py#cmd_assess
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| boundary | integration | — | codex | 採用 | 1 |
なぜ: 本番コード 0 行・10 行・11 行と rename は固定されているが、git numstat が追加行・削除行を '-' で返すバイナリのコード拡張子ファイルは固定されていない。整数変換で落ちず、現状どおり 0 行として判定する境界が未固定である
手順: 1. 実 git リポジトリへ NUL を含む .py ファイルを追加してコミットする
2. refactor.py assess を CLI 入口から起点指定付きで実行する
3. クラッシュせず終了コード 3 となり、その本番コード 1 ファイルを 0 行として数える現在の判定結果を比較する
ラウンド 2(実装 kiro)
R2-001 — plugins/ndf/skills/cross-refactoring/scripts/refactor_lib/commands/setup.py#cmd_init
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| error | unit | — | claude / codex | 採用 | 1 |
なぜ: 着手前のテストと範囲のテストは、失敗(終了コード非 0)で止まる経路は固定されているが、打ち切り(run_with_timeout が timed_out=True を返す)で止まる経路は 2 つとも固定されていない(setup.py 630-634 と 662-663 が未到達)。打ち切りは失敗と別の分岐で、終了コードも着手前のテストは 1、範囲のテストは ABORT と異なる
手順: 1. test_init.py の test_calls と同じく patch_lib で run_with_timeout を差し替え、コマンドごとに (None, True) を返せる疑似実装にする
2. 着手前のテストだけ打ち切りにして run_init を呼び、SystemExit の終了コード(現状の値)と、状態ファイルが書かれていないことを比較する
3. 着手前のテストは (0, False)、--round-test に別のコマンドを渡してそれだけ打ち切りにして run_init を呼び、SystemExit の終了コードが refactor_abort() であることと、状態ファイルが書かれていないことを比較する
4. 出力は文言の完全一致を取らず、打ち切った秒数が含まれることだけを見る
R2-002 — plugins/ndf/skills/cross-refactoring/scripts/refactor_lib/commands/apply.py#cmd_merge_apply
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| branch | unit | — | claude | 採用 | 1 |
なぜ: 適用の検証に落ちた群の取り消しで、積み直せず run_drop が mode=round を返したときの分岐(群の全項目を abandoned にし failure_reason を付け、apply.applied を空・apply.failed を群の全項目にし、apply_base_sha を取り直す)が固定されていない。gitfacts.drop_items の round 退避は test_drop_items_git.py が固定しているが、それを受けた cmd_merge_apply 側の状態の書き換えはどのテストも通っていない(カバレッジ上 apply.py 1008-1017 が未到達)
手順: 1. 既存の merge-apply のテストと同じ形で、群に 2 項目を持つ状態ファイルと、検証に落ちる適用結果(片方の項目だけ失敗)を用意する
2. patch_lib で run_drop を {"mode": "round"} を返す疑似実装に差し替え、git_out(rev-parse HEAD) は固定の SHA を返させる
3. cmd_merge_apply を実行する
4. 状態ファイルを読み直し、群の 2 項目がともに status=abandoned で failure_reason を持つこと、apply.applied が空で apply.failed が群の全項目であること、apply_base_sha が固定の SHA へ更新されたこと、deferred_items に 2 項目が載ったことを比較する
5. mode=item を返したとき(失敗した項目だけ abandoned)を対照として 1 件並べ、分岐の両側を固定する
R2-003 — plugins/ndf/skills/cross-refactoring/scripts/refactor_lib/commands/gate.py#cmd_final_gate
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| error | unit | — | codex | 採用 | 1 |
なぜ: 最終ゲートの全体テストについて成功と非ゼロ終了は固定されているが、タイムアウトを失敗として記録し修正ラウンドへ進む経路は固定されていない
手順: 1. 全体テストの実行結果をタイムアウトへ差し替えて cmd_final_gate を実行する
2. 失敗の終了コード、最終ゲート記録の timed-out 相当の詳細、修正ラウンドへの遷移を比較する
R2-004 — plugins/ndf/skills/cross-refactoring/scripts/refactor_lib/commands/report.py#cmd_report
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| branch | unit | — | claude | 採用 | 1 |
なぜ: 完了報告の既存テストは、参加者の記録が unavailable を持ち resume_changes が空でない状態でしか通っていない。確認を飛ばした(probe_skipped=True)/ 確認で落ちた者なし、再開で変えた値なし、再開で参加者(available を持つ dict)を変えた記録、test_rounds_final がある、テスト整備の項目(case / level を持つ項目)が項目表に並ぶ、の各分岐が未到達(report.py 152, 184-187, 195, 207, 262)
手順: 1. test_rounds.py の報告用の状態ファイルを作る補助を使い、参加者の記録と resume_changes と items だけを変えた状態を分岐ごとに作る
2. parametrize で (a) unavailable 空・probe_skipped=True (b) unavailable 空・probe_skipped=False (c) resume_changes 空 (d) resume_changes に field=participants で from/to が available を持つ dict (e) test_rounds_final あり・kind がテストの項目あり を与える
3. cmd_report(metrics=False) を呼び、capsys の出力から該当する 1 行(「確認を通らなかった者」「再開で変えた値」「テスト整備の終わり方」の行、項目表のその項目の行)だけを取り出す
4. 取り出した行を現状の出力で固定する。行全体の完全一致は避け、値の部分(NDF_SKIP_AUTH_CHECK の有無・なし・available の並び・case と level の値)だけを比較する
R2-005 — plugins/ndf/skills/cross-refactoring/scripts/refactor_lib/gitfacts.py#record_observed_model
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| normal | unit | — | codex | 採用 | 1 |
なぜ: モデル文字列の解析と不一致判定は個別に固定されているが、CLI の stdout ログから得た実測モデルを適用ラウンドの状態へ反映する正常経路は固定されていない
手順: 1. 実測モデルを含む stdout ログと、指定モデルを持つラウンド状態を作る
2. record_observed_model を実行する
3. impl_model.observed に現状の実測値が保存されることを比較する
ラウンド 3(実装 claude)
R3-001 — plugins/ndf/skills/cross-refactoring/scripts/refactor_lib/commands/gate.py#cmd_final_gate
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| long_method | extract_method | major | codex / kiro | 採用 | 1 |
なぜ: 1 関数に約 90 行・5 つの結末(単独=cross-review へ委譲 / 検査の実行と記録 / 合格かつ単独 / 合格 / 上限到達 / 落ちて修正へ)が同居する。結末ごとに『gate へ書く → save → info → emit → return か sys.exit』の 4〜5 行が繰り返され、判定の流れが結末の記録に埋もれている。
手順: 1. 結末の発火を helper へ抽出する: _emit_cross_review(path, state, gate)(cross-review へ委譲)、_record_gate_check(gate, mode, command, passed, detail, seconds)(checks への 1 件追記)
2. 合格・上限到達・修正継続の 3 結末を _gate_passed, _gate_limit_reached, _gate_failing として抽出し、それぞれ save・info・emit・終了コードを閉じ込める
3. cmd_final_gate 本体は『委譲するか → 検査を実行し記録 → 合否で分岐して結末 helper を呼ぶ』の骨格だけを残す
4. tests/test_final_gate.py(21 件)を実行し、5 つの結末すべてが従来どおりの FINAL_GATE 値・終了コードを返すことを確認する
R3-002 — plugins/ndf/skills/cross-refactoring/scripts/refactor_lib/verify.py#verify_apply_round
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| long_method | split_into_pipeline | major | codex | 採用 | 1 |
なぜ: コミット存在確認、各コミットの基礎検査、test_gap、テスト変更、文言固定テスト、差分予算、粒度を順番に検査する一方、各検査の呼び出しと早期終了が手書きで並び、検査順序という規則が関数本体の制御構文に埋もれている
手順: 1. facts 全体から基礎違反を返す段を抽出する
2. test_gap とテスト変更を扱うテスト保護段を抽出する
3. 文言固定、差分予算、コミット粒度を扱う差分制約段を抽出する
4. verify_apply_round は各段へ同じコンテキストを順に渡し、最初の問題を返すパイプラインにする
5. test_merge_apply.py、test_doc_wording_tests.py、test_commit_granularity.py の既存検査順とエラー文を固定したまま実行する
R3-003 — plugins/ndf/skills/cross-refactoring/scripts/refactor_lib/commands/setup.py#cmd_init
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| long_method | split_into_pipeline | major | codex | 採用 | 1 |
なぜ: ホスト・モデルの検証、PR と worktree の準備、scope 関門、再開判定、参加者確定、2 種類のテスト実行、状態保存と出力が 1 関数に直列で同居しており、各段階の入力と終了条件を個別に追いにくい
手順: 1. ホスト・モデル・include/exclude の解決を独立した入力解決段へ抽出する
2. PR コンテキスト、worktree、scope 関門、既存状態の再開判定を準備段へ抽出する
3. 参加者確定と baseline/round test 実行を検証段へ抽出する
4. InitialContext の生成から保存・出力までを初期状態確定段として残し、各段の出力を次段へ明示的に渡す
5. test_init.py の既存正常系・再開・失敗経路を段階ごとに実行する
R3-004 — plugins/ndf/skills/cross-refactoring/scripts/refactor_lib/gitfacts.py#_record_drop_result
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| long_parameter_list | introduce_parameter_object | major | claude | 採用 | 1 |
なぜ: _record_drop_result は引数が 9 個あり、そのうち owner / keep_ids / replay / ordered / pending の 5 個は _drop_replay_plan と drop_items が組み立てた「取り消しと積み直しの計画」をばらして運んでいるだけである。同じ計画を _dry_run_drop_plan と _execute_drop_replay も一部ずつ受け取っている。さらに戻り値の形 {mode, dropped, reverted, replayed} が gitfacts.py の 5 箇所(681 / 739 / 755 / 784 行と entry.drops の 732 行)で別々に辞書リテラルとして書かれていて、キーの追加や改名で食い違う
手順: 1. drop_items の dry-run 経路(_dry_run_drop_plan を通る)と、apply_base_sha が無い旧版の経路(_drop_legacy_by_item)を現状固定テストで tests/test_drop_items_git.py に固定する(戻り値の 4 キーと、dry-run では git の履歴と state が変わらないこと)
2. gitfacts.py にフィールド pending / ordered / owner / keep_ids / replay を持つ dataclass _DropPlan を導入する
3. _drop_replay_plan が (owner, keep_ids, replay) のタプルの代わりに _DropPlan を返すようにし、drop_items で受け取る
4. _dry_run_drop_plan(plan) / _execute_drop_replay(work, plan, head) / _record_drop_result(state, entry, plan, mapping, mode) へ引数を置き換える
5. 戻り値の辞書を作る _drop_summary(mode, dropped, reverted, replayed) を抽出し、5 箇所の辞書リテラルをこれの呼び出しへ置き換える
6. 既存の test_drop_items_git.py / test_abandon_items.py / test_final_gate.py と 1 で足した固定テストを実行する
R3-005 — plugins/ndf/skills/cross-refactoring/scripts/refactor.py#main
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| long_method | extract_method | major | codex | 採用 | 1 |
なぜ: 153 行の入口で init 固有引数、単純な id コマンド、round 付きコマンド、dry-run 付きコマンド、assess、report の各パーサー構築と dispatch を一括しており、コマンド群ごとの契約変更が同じ関数へ集中している
手順: 1. init サブパーサーの構築を add_init_parser として抽出する
2. 共通形を持つ id-only、round、dry-run コマンド群の登録をそれぞれ名前付き関数へ抽出する
3. assess と report の登録をそれぞれ抽出する
4. main には ArgumentParser の生成、各登録関数の呼び出し、parse_args、dispatch だけを残す
5. test_init.py の引数解析テストと CLI 関連テストを実行する
ラウンド 4(実装 codex)
R4-001 — plugins/ndf/skills/cross-refactoring/scripts/refactor_lib/commands/setup.py#cmd_start_round
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| long_method | extract_method | major | claude / codex | 採用 | 1 |
なぜ: 78 行の 1 関数に、終了判定・新しいラウンドの記録の雛形(13 鍵の辞書リテラル)・種類ごとの表示名と上限の選択・emit が同居している。雛形の組み立ては判断を含まない値の定義で、本体の流れを読む妨げになっている
手順: 1. 新しいラウンドの辞書リテラル(round〜reviews)を _new_round_entry(state, round_no, kind) として抽出し、impl_for_seq の呼び出しもその中へ移す
2. kind が TEST かどうかで label と limit を決める分岐を _round_label_and_limit(state, kind) として抽出し、(label, limit) を返す
3. cmd_start_round は 2 つを呼ぶ形に書き換える。鍵・値・保存の順序は変えない
4. tests/test_rounds.py・test_test_rounds.py・test_start_round_emits_runtimes.py を実行して不変を確かめる
R4-002 — plugins/ndf/skills/cross-refactoring/scripts/refactor_lib/commands/converge.py#_record_deferred_abandoned_items
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| duplication | consolidate_duplication | major | codex | 採用 | 1 |
なぜ: apply.py の _defer_abandoned_items と、item_id の既登録判定、abandoned 状態の扱い、failure_reason の既定化、deferred_record の追記という同じ業務判断を別々に保持しており、見送り記録の規則変更が片方だけに入る恐れがある。
手順: 1. 2 関数が必要とする差分である対象 item_id の選別条件を明示する
2. abandoned 項目を未登録時だけ deferred_record として追記する共通ヘルパーを、見送り記録を所有する層へ置く
3. _record_deferred_abandoned_items は status と failure_reason の設定後に共通ヘルパーを呼ぶ
4. _defer_abandoned_items は既に abandoned の項目を選んで同じ共通ヘルパーを呼ぶ
5. test_abandon_items.py と test_excluded_items.py で重複防止と failure_reason が不変であることを確認する
R4-003 — plugins/ndf/skills/cross-refactoring/scripts/refactor_lib/commands/converge.py#cmd_abandon_items
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| long_method | extract_method | major | codex | 検証中 | 1 |
なぜ: 中断処理の再開、冪等ガード、対象なし、dry-run、取り消し実行、状態更新、push を直列に扱い、各終了経路の副作用を読み分ける負荷が高い。
手順: 1. pending_drop の再実行と pending_push の送信を行う前処理をヘルパーへ抽出する
2. 対象なしと dry-run の早期終了判定をヘルパーへ抽出する
3. 取り消し後の group・entry・state の更新と push を行う完了処理をヘルパーへ抽出する
4. cmd_abandon_items は各段階の呼び出し順だけを表す形にし、test_abandon_items.py の既存経路を実行する
R4-004 — plugins/ndf/skills/cross-refactoring/scripts/refactor_lib/commands/apply.py#cmd_merge_proposals
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| long_method | extract_method | major | codex | 採用 | 1 |
なぜ: 再実行時の既存結果表示、種類別の統合戦略選択、状態更新、統計表示、採用 0 件の終了制御が同居し、テスト提案と構造改善提案の分岐が処理全体へ広がっている。
手順: 1. 統合済みラウンドの表示と終了判定をヘルパーへ抽出する
2. entry kind に応じて merge_test_proposals または merge_proposals を呼ぶ処理をヘルパーへ抽出する
3. 統合後の一覧表示と採用 0 件の終了制御をヘルパーへ抽出する
4. cmd_merge_proposals は load、既処理判定、統合、状態更新、結果通知の段階だけを残し、test_merge_proposals.py と test_test_rounds.py を実行する
R4-005 — plugins/ndf/skills/cross-refactoring/scripts/refactor_lib/commands/apply.py#_apply_drop
| 兆候・経路 | 手法・階層 | 重要度 | 提案元 | 状態 | コミット |
|---|---|---|---|---|---|
| long_method | extract_method | major | codex | 検証中 | 1 |
なぜ: 取り消し結果の解釈、全件見送りへの状態変換、群の状態決定、次 phase の決定、対象外記録、保存用印の更新、push が 1 関数に連続し、再開契約の境界が埋もれている。
手順: 1. run_drop の結果から item・entry・group の採用状態を更新する部分をヘルパーへ抽出する
2. apply_base_sha と group status と次 phase を確定する部分をヘルパーへ抽出する
3. deferred 記録から pending_drop 解除、merged_at 設定、push までの永続化段階を名前付きヘルパーへ抽出する
4. _apply_drop は取り消し、状態反映、永続化の 3 段だけを残し、test_abandon_items.py の round/item 両モードと再開テストを実行する
見送った項目
| ラウンド | 対象 | 兆候・経路 | 理由 |
|---|---|---|---|
| 1 | plugins/ndf/skills/cross-refactoring/scripts/refactor_lib/commands/assess.py#cmd_assess |
branch | 1 ラウンドの採用上限 5 件を超えた |
| 1 | plugins/ndf/skills/cross-refactoring/scripts/refactor_lib/paths.py#git_out |
branch | 1 ラウンドの採用上限 5 件を超えた |
| 1 | plugins/ndf/skills/cross-refactoring/scripts/refactor_lib/scope.py#round_test_command |
branch | 1 ラウンドの採用上限 5 件を超えた |
| 2 | plugins/ndf/skills/cross-refactoring/scripts/refactor_lib/rounds.py#current_group |
branch | 1 ラウンドの採用上限 5 件を超えた |
| 2 | plugins/ndf/skills/cross-refactoring/scripts/refactor_lib/rounds.py#deferred_record |
branch | 1 ラウンドの採用上限 5 件を超えた |
| 2 | plugins/ndf/skills/cross-refactoring/scripts/refactor_lib/rounds.py#group_reopening |
branch | 1 ラウンドの採用上限 5 件を超えた |
| 2 | plugins/ndf/skills/cross-refactoring/scripts/refactor_lib/scope.py#round_test_hint |
boundary | 1 ラウンドの採用上限 5 件を超えた |
| 2 | plugins/ndf/skills/cross-refactoring/scripts/refactor_lib/vocabulary.py#vocabulary |
error | 1 ラウンドの採用上限 5 件を超えた |
| 3 | plugins/ndf/skills/cross-refactoring/tests/conftest.py#patch_lib |
mock_targets_implementation_detail | 1 ラウンドの採用上限 5 件を超えた |
| 3 | plugins/ndf/skills/cross-refactoring/scripts/refactor_lib/commands/apply.py#_revert_unverified_apply_round |
duplication | 1 ラウンドの採用上限 5 件を超えた |
| 3 | plugins/ndf/skills/cross-refactoring/scripts/refactor_lib/gitfacts.py#commit_diff_lines |
duplication | 1 ラウンドの採用上限 5 件を超えた |
| 3 | plugins/ndf/skills/cross-refactoring/scripts/refactor_lib/paths.py#git_out |
duplication | 1 ラウンドの採用上限 5 件を超えた |
| 4 | plugins/ndf/skills/cross-refactoring/scripts/refactor_lib/plan.py#publish_plan_comment |
duplication | 1 ラウンドの採用上限 5 件を超えた |
| 4 | plugins/ndf/skills/cross-refactoring/scripts/refactor_lib/measure.py#_phase_of |
long_method | 1 ラウンドの採用上限 5 件を超えた |
| 4 | plugins/ndf/skills/cross-refactoring/scripts/refactor_lib/commands/setup.py#_resume |
long_parameter_list | 1 ラウンドの採用上限 5 件を超えた |
| 4 | plugins/ndf/skills/cross-refactoring/scripts/refactor_lib/proposals.py#_normalize_proposal |
duplication | 1 ラウンドの採用上限 5 件を超えた |
| 4 | plugins/ndf/skills/cross-refactoring/scripts/refactor_lib/commands/apply.py#_drop_group_without_result |
duplication | 1 ラウンドの採用上限 5 件を超えた |
打ち切り・取り消し(mode=round)・最終ゲートの打ち切り・完了報告の分岐・ 実測モデルの反映という、これまでどのテストも通っていなかった 5 つの経路を 現状固定テストで固定する。対象のコードは変更しない。 Item-Id: R2-001 Round: 2 Impl-Runtime: kiro Impl-Model: default
…ts/refactor_lib/commands/gate.py#cmd_final_gate 提案ラウンド 3・適用ラウンド 1 の 5 項目をまとめて適用する(振る舞いは変えない)。 - R3-001 gate.py#cmd_final_gate: 結末ごとの記録を _emit_cross_review / _record_gate_check / _gate_passed / _gate_limit_reached / _gate_failing へ抽出し、本体を判定の骨格だけにする - R3-002 verify.py#verify_apply_round: 基礎検査・テスト保護・差分制約の 3 段へ分け、 _APPLY_ROUND_STAGES を順に通して最初の問題を返すパイプラインにする - R3-003 setup.py#cmd_init: 入力解決・準備・再開判定・検証・初期状態の保存の段へ分ける。 _emit_init は cmd_init から直接呼ぶ(check-skill-shell-vars.py がヘルパーを 1 段しかたどらないため) - R3-004 gitfacts.py#_record_drop_result: 取り消しと積み直しの計画を _DropPlan にまとめ、 結果の辞書を _drop_summary へ寄せる。dry-run と旧版の状態ファイルの経路を現状固定テストで固定 - R3-005 refactor.py#main: 副コマンド群ごとのパーサー登録を add_* 関数へ抽出する Item-Id: R3-001 Round: 3 Impl-Runtime: claude Impl-Model: default Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ラウンド生成と表示条件、提案統合の各段階を抽出し、取り消した項目の見送り記録を共通ヘルパーへ集約する。 Item-Id: R4-001 Round: 4 Impl-Runtime: codex Impl-Model: default
…apply_drop cmd_abandon_items をやり残しの後片づけ・対象なし処理・完了処理の 3 つの ヘルパーへ抽出し、本体は各段階の呼び出し順だけを表す形にした。_apply_drop を 取り消しと採用状態の反映・状態確定・永続化の 3 段のヘルパーへ抽出した。 振る舞いは不変で、既存テストが通る。 Item-Id: R4-003 Round: 4 Impl-Runtime: kiro Impl-Model: default
Summary
cross-refactoring を是正して、構造改善の工程にかかる費用を下げ、飛ばしてよい場合を判定できるようにする。この PR は設計のまとまり(2'')の PR 2 である(PR 1 は #885)。
issues/issue-880-885-design.md(設計 PR 設計: cross-refactoring を是正して構造改善の工程を戻せる状態にする(#880 #883 #494 #723 #885) #911 で承認済み。この PR の範囲は「実装の分け方と順序」の PR 2 の行)issues/issue-880-883-494-723-plan.mdinit --round-test CMDを足した。群の検証と修正コミットの検証は範囲のテストで行い、全体テスト(--baseline-test)はinitと最終ゲートの 2 回だけにする。単独起動でも、2 つのテストが違えば最終ゲートで全体テストを 1 回走らせる。範囲のテストが--scopeのテストを覆わなければ止める。--round-testを省いて全体テストが範囲より広いときは 1 行の案内を出す。検証の記録と最終ゲートの記録にsecondsを足したexec sleep 30、上限 1 秒で 6.06 秒かかっていたものが 1.20 秒になった)refactor.py assess --base <ref>を足した。終了コードは 0 = 通す / 3 = 飛ばしてよい / 2 = 判定できない。workflow-modes.mdの「構造改善の退避先」では、飛ばす条件と退避する条件を分けて書いた。stage-notes.mdには構造改善の順(assess →--round-test→--baseline-test)を書いたtargetが.mdを指すものを見送る。適用ラウンドでは、追跡している.mdを指すテストの追加を失敗にする(doc_wording_tests)。propose-tests.mdの「守ること」に 1 項目を足した構造改善: この PR は検査の持ち場で通す(AC20〜AC23。PR 1 を取り込んだ後に行う)。
設計と違えたところ・設計に無く決めたところ
final-gateが全体テストで 1 度だけ判定し、--ci-checkの実行では手元のテストを走らせない(cross-refactoring の全体フローを変える(コミット粒度・レビューをテストへ・最終ゲートの省略・ラウンド上限) #436 の決定 11 の排他。固定しているテストはtest_final_fix.py::test_the_commit_test_status_is_not_checked)。AC2 が守りたい「全体テストをコミットごとに走らせない」はこのままで満たす。範囲のテストを足すと決定 11 を覆すため、この PR では変えない。検査の持ち場で、AC2 の文言をこの形に揃えるかを判断する--ci-checkがあるときは、手元の全体テストの代わりに継続的統合で判定する(AC3 の「--ci-checkがあれば今のとおり継続的統合で代える」に合わせた)gitfacts._is_test_pathを公開名is_test_pathに改名した(scope.pyから使うため。モジュールの境界の検査は、非公開名をモジュールの外から使うことを禁じている)doc_wording_testsは(ファイル, 文字列)の組を返す。失敗の理由に文字列を載せるためassessとtracked_markdownはgit ... -zで読む。ASCII 以外を含むパスが引用符付きで出るのを避けるためCloses #880
Closes #883
Closes #494
Closes #723
Test plan
uv run --project plugins/playwright-kit/skills/playwright-kit-ops --with pytest pytest . -q -n 4→ 5422 passed、exit=0pytest plugins/ndf/skills/cross-refactoring/tests -q -n 4→ 854 passed(着手前は 798 件)test_init.py・test_verify_round.py・test_abandon_items.py・test_final_gate.py・test_scope_gate.pyに足したテストtest_git_facts.pyの打ち切りの 4 件。所要は 1.20 / 1.22 / 2.74 / 3.55 秒で、既存 3 件の合計は約 7.5 秒(10 秒未満)tests/test_assess.pyの 8 件test_test_rounds.pyの 2 件とtests/test_doc_wording_tests.pyの 8 件stage-notes.md・workflow-modes.md・propose-tests.mdの該当の節を読んで確かめる(文言を固定するテストは書かない)python3 plugins/ndf/skills/cross-refactoring/scripts/refactor.py assess --base origin/develop→判定: 通す/ 94 行、exit=0python3 scripts/check-skill-frontmatter.pyexit=0 /python3 scripts/check-markdown-links.pyexit=0 /claude plugin validate .exit=0 /python3 plugins/ndf/scripts/instructions-check.py --root .exit=0cross-refactoring --workflow-stepを通して数える🤖 Generated with Claude Code
https://claude.ai/code/session_01DhcogXCb1x3eStK4VoDDCy