Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
eb46896
Docs: #880 #883 #494 #723 cross-refactoring の是正の実装計画を置く
takemi-ohama Sep 23, 2026
fd62591
Fix: #883 テストの打ち切りが、子が終わった時点で戻る
takemi-ohama Sep 23, 2026
11ce27d
Add: #494 構造改善を飛ばしてよいかを差分から判定する assess を足す
takemi-ohama Sep 23, 2026
9553e35
Update: #880 群の検証を --round-test で行い、全体テストを着手前と最終ゲートへ寄せる
takemi-ohama Sep 23, 2026
f790d89
Docs: #880 #494 --round-test と assess の使い方を書き、飛ばす条件と退避する条件を分ける
takemi-ohama Sep 23, 2026
bce8a30
Update: #723 テスト整備と適用のラウンドが .md の文言を固定するテストを採らない
takemi-ohama Sep 23, 2026
a908cf1
Docs: #723 テスト整備の提案が .md の文言を固定するテストを挙げないと定める
takemi-ohama Sep 23, 2026
722ab61
Merge remote-tracking branch 'origin/develop' into feat/issue-880-883…
takemi-ohama Sep 23, 2026
39edcb6
Docs: #880 AC2 の最終ゲートの修正コミットの扱いを #436 決定 11 に合わせ、決定 14 を記録する
takemi-ohama Sep 23, 2026
31c5e5b
Test: cross-refactoring の未固定経路を追加
takemi-ohama Sep 23, 2026
9ce9ee0
Test: cross-refactoring 提案ラウンド 2 の未固定経路を現状固定テストで追加
takemi-ohama Sep 23, 2026
edaf4b4
Refactor: extract_method — plugins/ndf/skills/cross-refactoring/scrip…
takemi-ohama Sep 23, 2026
0e5ed67
Refactor: 提案ラウンド処理を抽出して見送り記録を共通化
takemi-ohama Sep 23, 2026
04791b4
Refactor: extract_method — converge.py#cmd_abandon_items / apply.py#_…
takemi-ohama Sep 23, 2026
6b88462
Refactor: extract_method — plugins/ndf/skills/cross-refactoring/scrip…
takemi-ohama Sep 23, 2026
cec4001
Fix: #880 クロスレビュー round 1: round_test_roots が値を取らないオプションの直後の対象を消さないようにする
takemi-ohama Sep 23, 2026
145e5b1
Merge remote-tracking branch 'origin/develop' into HEAD
takemi-ohama Sep 23, 2026
6ccb803
Fix: #880 CLAUDE.md の版が決まる前の段落の基底を、出た 10.17.4 へ合わせる
takemi-ohama Sep 23, 2026
13c65ed
Fix: #880 クロスレビュー round 2: 適用・修正の担当へ群の検証と同じテストを渡し、assess の例を検査の対象へ出す
takemi-ohama Sep 23, 2026
f9d362b
Fix: #880 クロスレビュー round 3: --round-test のノード ID をファイルの部分で起点に数える
takemi-ohama Sep 23, 2026
d970937
Docs: #880 最終スイープ: 詳細手順の関門・着手前・群の検証を --round-test の契約へ合わせる
takemi-ohama Sep 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,13 @@ python3 plugins/ndf/scripts/instructions-check.py --root .
`/ndf:cross-refactoring` は参加者に構造改善を提案させ、同じ参加者から輪番で選んだ 1 者が適用する。新しい提案が出なくなるまで繰り返す。参加者の既定は **codex / kiro とホスト(ホストが codex / kiro なら 2 者)** で、`--exclude` / `--include` で名指しで変える(agy は `--include agy` で戻す)。レビューは最終ゲートの `cross-review` が担う。

```bash
/ndf:cross-refactoring 130 --scope src/services --baseline-test "pytest -q"
/ndf:cross-refactoring 130 --scope src/services tests/services --round-test "pytest tests/services -q" --baseline-test "pytest -q"
/ndf:cross-refactoring 130 --scope src --model codex=gpt-5.5 --model claude=claude-opus-5
/ndf:cross-refactoring 130 --scope src --include agy --exclude kiro
```

- `--scope` は必須。提案が発散して PR が肥大するのを防ぐ。**検証にも効く**ので、現状固定テストの置き場所も含める
- v10.17.4 の次の版で、群ごとの検証は `--round-test`(範囲のテスト)で走らせ、`--baseline-test`(全体のテスト)は着手前と最終ゲートの 2 回だけ走らせる。`--round-test` を省くと全体のテストが群ごとに走る
- ホストと同じランタイムが適用担当になる場合も、サブエージェントではなく **CLI プロセス**として起動する
- モデルを比べるなら `--model <ランタイム>=<name>` を参加者の全員に指定する。実際に動いたモデルを取得できるのは claude だけで、残りは指定値で代用する。指定が無いラウンドは集計から分離される
- 適用担当は参加者の数のラウンドで 1 周する。輪番は適用ラウンドごとに進むため、`--max-outer-rounds`(既定 3)が切る提案の回数とは対応しない
Expand Down
88 changes: 88 additions & 0 deletions issues/issue-880-883-494-723-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# #880 / #883 / #494 / #723: cross-refactoring の是正 — 実装計画(PR 2)

## 関連リンク

- 要求: [issue-880-885-requirements.md](issue-880-885-requirements.md)
- 設計: [issue-880-885-design.md](issue-880-885-design.md)(「実装の分け方と順序」の PR 2 の行)
- 決定: [issue-880-885-design-decisions.md](issue-880-885-design-decisions.md)
- 設計 PR #911(関門 1 承認 2026-09-23、develop `ff9754e7`)

## モード

standard(起動指示のとおり。設計は承認済みで、この PR は実装だけを載せる)。

## 目的と非目的

達成したい状態:
- 群の検証が範囲のテストだけを走らせ、全体テストは `init` と最終ゲートの 2 回に寄る(#880)
- 打ち切りが、子が終わった時点で戻る(#883)
- 構造改善を飛ばしてよいかを差分から判定できる(#494)
- `.md` の文言を固定するテストが提案・適用ラウンドで採られない(#723)

やらないこと:
- `.md` の文言固定テストの削除・検査スクリプト・`tdd-cycle`・`quality-gates`・`AGENTS.md`(PR 1、#885 の担当)
- AC19a〜AC19c(PR 1)と AC20〜AC23(次の検査の持ち場で PR 1 の取り込み後に測る)
- `waiting.md`・`agent-layers.md`(#892 #901 が触る)、`CLAUDE.md` の cross-review の節

## 受け入れ条件

要求文書の AC1〜AC18(AC19 系は PR 1)と AC24。検証手段は設計の「テスト設計」の表に従う。
AC8・AC15・AC16 は文書を読んで確かめる(文言を固定するテストは書かない)。

## 修正対象

- `plugins/ndf/skills/cross-refactoring/scripts/refactor.py`
- `plugins/ndf/skills/cross-refactoring/scripts/refactor_lib/commands/{setup,converge,gate}.py`
- `plugins/ndf/skills/cross-refactoring/scripts/refactor_lib/commands/assess.py`(新設)
- `plugins/ndf/skills/cross-refactoring/scripts/refactor_lib/{gitfacts,scope,proposals,verify}.py`
- `plugins/ndf/skills/cross-refactoring/prompts/propose-tests.md`
- `plugins/ndf/skills/cross-refactoring/SKILL.md`
- `CLAUDE.md` の cross-refactoring の節
- `plugins/ndf/skills/development-workflow/references/{workflow-modes,stage-notes}.md`
- `plugins/ndf/skills/cross-refactoring/tests/`(テストの追加・待ちの短縮)

## タスク分解

### Task 1: 打ち切りが子の終了で戻る(#883)
- **対象:** `gitfacts.py` の `_kill_process_group`、`tests/test_git_facts.py`
- **変更:** SIGTERM 後の点検で `proc.poll()` を先に呼ぶ。既存 3 件の確認の待ちを 1.5 秒へ縮め、`exec sleep 30` のテストを足す
- **満たす AC:** AC9・AC10・AC11
- **進め方:** `exec sleep 30` が 2 秒以内に戻るテストを先に書いて落とす → 1 行の修正

### Task 2: `assess` の副命令(#494)
- **対象:** `gitfacts.py` の `production_code_changes`、`commands/assess.py`、`refactor.py`、テスト
- **変更:** `git diff --numstat --no-renames <base>...HEAD` から本番コードを数え、終了コード 0 / 3 / 2 と 3 行を出す
- **満たす AC:** AC12・AC13・AC14
- **進め方:** 一時リポジトリで各差分を作るテストを先に書く → 実装

### Task 3: `--round-test` と検証の秒数(#880)
- **対象:** `refactor.py` の `init` 引数、`commands/setup.py`・`converge.py`・`gate.py`、`scope.py` の `round_test_hint`・`round_test_roots`、テスト
- **変更:** 状態の `round_test` の保存・`init` での実行・`_round_test_command(state)`・`verify-round` と修正コミットの検証の差し替え・`seconds` の記録・単独起動の最終ゲートの全体テスト 1 回・案内の 1 行
- **満たす AC:** AC1〜AC7
- **進め方:** AC ごとに失敗するテスト → 実装

### Task 4: `.md` の文言固定テストを採らない(#723)
- **対象:** `proposals.py` の `merge_test_proposals`、`verify.py` の `doc_wording_tests` と `verify_apply_round`、`prompts/propose-tests.md`、テスト
- **満たす AC:** AC16・AC17・AC18
- **進め方:** 見送り・失敗のテストを先に書く → 実装。`propose-tests.md` は文書の追記

### Task 5: 文書(#880 #494)
- **対象:** `SKILL.md`・`CLAUDE.md`・`workflow-modes.md`・`stage-notes.md`
- **満たす AC:** AC8・AC15
- **進め方:** テスト駆動を適用しない(文書。文言を固定するテストは書かない)

## リスクと対処

| リスク | 対処 |
| --- | --- |
| `gitfacts.py`(1182 行)・`apply.py`(1156 行)が大きい | 実装の後の構造改善で足りる(触るのは関数単位で狭く、テストが厚い)。タスクごとに cross-refactoring のテストを通す |
| 既存の `.md` 文言固定テストが文書の変更で落ちる | 削除は PR 1 の担当。この PR の変更で壊れたものだけを最小に直す |

## 切り戻し手順

コードと文書だけの変更で、状態ファイルは `round_test` が無ければ変更前の振る舞いに戻る(AC7)。PR の revert で戻せる。

## 完了の定義

- [ ] AC1〜AC18(AC19 系を除く)を満たし、条件ごとにテストか文書の節が対応する
- [ ] AC24: `uv run --project plugins/playwright-kit/skills/playwright-kit-ops --with pytest pytest . -q -n 4` が通る
11 changes: 11 additions & 0 deletions issues/issue-880-885-design-decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,14 @@ cross-refactoring のコードとテストに閉じ、範囲のテストがあ
P3' を残す形は採らない。文書の値の照合であることは言い回しの照合と変わらず、残すと「どこまでが一致の
検査か」を毎回判断することになる。削った結果、文書とコードが食い違っても気付けない場面は増える。
これは #885 が受け入れた費用である。

### 決定 14: 最終ゲートの修正コミットはテストを走らせず、直後の最終ゲートが判定する

AC2 は当初、最終ゲートの修正のコミットでも範囲のテストを走らせると定めていた。実装で、これが #436 の
決定 11(`--ci-check` を渡した実行では手元のテストを 1 度も走らせない排他)と衝突すると分かった。
修正コミットごとに範囲のテストを走らせると、`--ci-check` の実行でこの経路だけが手元のテストを走らせる。

今の振る舞い(修正コミットは範囲と申告だけを見て、テストの合否は直後の `final-gate` が採った側 —
手元の全体テスト / 継続的統合 — で 1 度だけ見る)を採る。最終ゲートの修正は全体テストの失敗を
直すものであり、範囲のテストを通しても直ったかは分からない。見るべきものは直後の最終ゲートが見る。
AC2 の文言をこれに合わせて直した。
2 changes: 1 addition & 1 deletion issues/issue-880-885-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ graph LR
| 引数 | 意味 | 既定 | 実行する時点 |
| --- | --- | --- | --- |
| `--baseline-test CMD` | 全体のテスト(必須) | — | `init` の 1 回、最終ゲートの 1 回 |
| `--round-test CMD` | 範囲のテスト(任意) | `--baseline-test` と同じ | `init` の 1 回、群の検証(`verify-round`)ごと、修正コミットごと、最終ゲートの修正コミットごと |
| `--round-test CMD` | 範囲のテスト(任意) | `--baseline-test` と同じ | `init` の 1 回、群の検証(`verify-round`)ごと、修正コミットごと(最終ゲートの修正コミットは走らせない。決定 14) |

状態ファイルに足す欄:

Expand Down
2 changes: 1 addition & 1 deletion issues/issue-880-885-requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ PR #844(#829 #830)の `--scope` は 6 ファイルだった。`--baseline-te
### 群ごとの検証(#880)

- [ ] AC1: `refactor.py init` が `--round-test CMD` を受け付け、状態ファイルの `round_test.command` に保存する。渡さなければ `round_test` は `baseline_test` と同じコマンドになる(今の振る舞い)。このとき `init` のテストの実行は 1 回
- [ ] AC2: `verify-round`(群の検証)と、修正ラウンド・最終ゲートの修正のコミットの検証が、`round_test` のコマンドを実行する。`baseline_test` のコマンドを実行しない
- [ ] AC2: `verify-round`(群の検証)と、修正ラウンドの修正のコミットの検証が、`round_test` のコマンドを実行する。`baseline_test` のコマンドを実行しない。最終ゲートの修正のコミットはテストを実行せず、直後の最終ゲートが 1 度だけ判定する(決定 14)
- [ ] AC3: `--baseline-test` の実行は `init` の 1 回と最終ゲートの 1 回に限られる。最終ゲートは `--workflow-step` の有無に依らず、`round_test` が `baseline_test` と違えば手元で 1 回実行する(`--ci-check` があれば今のとおり継続的統合で代える)
- [ ] AC4: `init` は `--round-test` を 1 回実行し、成功しなければ止まる(テストが 1 件も集まらない終了コード 5 も失敗)。`--scope` のテストの置き場所が `--round-test` の実行集合に入らなければ止まる(今の `scope_problem` を `round_test` へ当てる)。実行集合の起点は `--round-test` の引数のうち、オプションの値と作業ディレクトリの根を除いた、実在するディレクトリとテストの置き場所に当たる実在するファイルである(ラッパーのスクリプトは数えない。規則は設計の `round_test_roots`)。置き場所それぞれについて起点のどれかが同じか祖先でなければならない。起点が 1 つも無いコマンドは全体を覆うとみなす
- [ ] AC5: `--round-test` を渡さず、`--baseline-test` が `--scope` のテストの置き場所より広いとき(探索の起点が無いときを含む)、`init` が 1 行の案内を出す。案内は `--round-test` に範囲のテストを渡せることを伝える。止まらない
Expand Down
23 changes: 18 additions & 5 deletions plugins/ndf/skills/cross-refactoring/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
name: cross-refactoring
description: "Let several CLIs propose, apply, and review refactorings on a PR until no new proposal appears. Use when structural improvement should converge across runtimes(クロスリファクタリング・多AIリファクタリング・収束リファクタリング)."
argument-hint: "[PR番号] --scope PATH... [--host claude|codex|agy|kiro] [--exclude NAMES] [--include NAMES] [--require-all] [--model RT=MODEL] [--baseline-test CMD] [--max-test-rounds N] [--max-outer-rounds N] [--max-fix-rounds N] [--max-items-per-round N] [--ci-check NAME] [--workflow-step]"
argument-hint: "[PR番号] --scope PATH... [--host claude|codex|agy|kiro] [--exclude NAMES] [--include NAMES] [--require-all] [--model RT=MODEL] [--baseline-test CMD] [--round-test CMD] [--max-test-rounds N] [--max-outer-rounds N] [--max-fix-rounds N] [--max-items-per-round N] [--ci-check NAME] [--workflow-step]"
allowed-tools:
- Bash
- Read
Expand Down Expand Up @@ -63,7 +63,7 @@ allowed-tools:
| --- | --- |
| 参加者 | **全員 CLI プロセス。** ホストのサブエージェント機能は使わない。ホストと同じランタイムが実装担当のラウンドでも別プロセスで起動する |
| 参加者 | **提案と適用を同じ参加者で回す。** 既定は codex / kiro とホストで、`--exclude` / `--include` で名指しで変える。確認を通らない者は外して続ける |
| 検証の単位 | **適用ラウンド(群)に対して 1 回。** 判定は `--baseline-test` の合否で決まり、レビュー CLI は起動しない |
| 検証の単位 | **適用ラウンド(群)に対して 1 回。** 判定は `--round-test`(範囲のテスト)の合否で決まり、レビュー CLI は起動しない。全体のテスト(`--baseline-test`)は着手前と最終ゲートの 2 回だけ走る |
| 収束しない項目 | **捨てる。** リファクタリングは任意の作業なので、揉める提案を Pull Request に残さない |
| コミットの単位 | **1 適用ラウンド = 1 コミット。** テストも適用ラウンドの単位で 1 回だけ求める |
| 改修計画 | **Pull Request のコメント 1 件へ残す。** 理由と手順は提案の時点でしか残らない。ラウンドが進むたびに同じコメントを編集する。URL は永続で、マージの後も開ける。`--plan-file` を明示したときだけファイルにする |
Expand Down Expand Up @@ -91,7 +91,8 @@ allowed-tools:
| `--include NAMES` | 参加者に足す者(例: `--include agy`)。再開で `none` を渡すと空へ戻す | なし |
| `--require-all` | 確認を通らない者が 1 者でもいれば中断する(終了コード 4)。付けなければ外して続ける | 外して続ける |
| `--model RT=MODEL` | ランタイムごとのモデル。繰り返し指定できる | CLI の既定 |
| `--baseline-test CMD` | 着手前と各コミットで実行するテスト。**振る舞い不変を示す手段が無い書き換えは構造改善ではないため必須** | 必須 |
| `--baseline-test CMD` | 着手前と最終ゲートで実行する全体のテスト。**振る舞い不変を示す手段が無い書き換えは構造改善ではないため必須** | 必須 |
| `--round-test CMD` | 着手前・群の検証・修正のコミットごとに実行する範囲のテスト。`--scope` のテストの置き場所を覆わなければ `init` が止まる。省くと `--baseline-test` が群ごとに走る | `--baseline-test` と同じ |
| `--max-test-rounds N` | **テスト整備ラウンド**の上限。到達したら採用が残っていても提案ラウンドへ進む | `2` |
| `--max-outer-rounds N` | **提案ラウンド**の上限。切るのは提案の回数であって、適用できる件数ではない | `3` |
| `--max-fix-rounds N` | **1 つの適用ラウンドあたり**の修正ラウンドの上限 | `3` |
Expand All @@ -104,7 +105,7 @@ allowed-tools:
| `--plan-file PATH` | 改修計画を**ファイル**へ書き出す先(**対象リポジトリからの相対パス**)。空文字を渡すと記録しない | Pull Request のコメント 1 件 |

```text
/ndf:cross-refactoring 130 --scope src/services tests/services --baseline-test "pytest -q"
/ndf:cross-refactoring 130 --scope src/services tests/services --round-test "pytest tests/services -q" --baseline-test "pytest -q"
/ndf:cross-refactoring 130 --scope src --baseline-test "pytest -q" --sync-command "make generate"
/ndf:cross-refactoring 130 --scope src --model codex=gpt-5.5 --model claude=claude-opus-5
/ndf:cross-refactoring 130 --scope src --host codex --max-outer-rounds 1
Expand Down Expand Up @@ -175,6 +176,17 @@ allowed-tools:

- 対象の Pull Request が Draft で開いている(未作成なら `/ndf:pr` で先に作る)

- 本番コードの差分がある。起動の前に `assess` で飛ばしてよいかを見る。**終了コード 3 なら
起動しない**(本番コードの差分が無いか、変更が `--max-lines`(既定 10)行以下)。2 は
判定できなかったことを示し、飛ばしてよいとは読まない。飛ばしたときの記録の残し方は
`development-workflow` の `references/workflow-modes.md`「構造改善の退避先」にある。

```bash
# 「実行」節の決め方で解決したこの Skill の scripts ディレクトリ
SCRIPTS="<この Skill のディレクトリ>/scripts"
python3 "$SCRIPTS/refactor.py" assess --base origin/develop; echo "exit=$?"
```

## 全体フロー

**ラウンドは 4 層である。** テスト整備ラウンドと提案ラウンドは、集める提案の中身が
Expand Down Expand Up @@ -293,7 +305,8 @@ rf_eval() {
}

rf_eval init "$PR" --scope $SCOPE \
--baseline-test "$BASELINE" ${HOST:+--host "$HOST"} \
--baseline-test "$BASELINE" ${ROUND_TEST:+--round-test "$ROUND_TEST"} \
${HOST:+--host "$HOST"} \
${EXCLUDE:+--exclude "$EXCLUDE"} ${INCLUDE:+--include "$INCLUDE"} \
${REQUIRE_ALL:+--require-all} \
--max-test-rounds "$MAX_TEST" --max-outer-rounds "$MAX_OUTER" \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,10 @@ export CROSS_REFACTORING_TMP_DIR="$TMP_DIR"
実行は初期化時ではなく、**push の直前**に進行側が行う。同期を実装担当の責務に
すると範囲外の変更になり、範囲の検査で全件失敗する(実測 0/5)
9. **`--scope` の関門** — テストの置き場所が範囲に含まれること**と**、その置き場所が
`--baseline-test` の実行集合に入ることを確かめる。どちらかを満たさなければ
`--round-test`(省けば `--baseline-test`)の実行集合に入ることを確かめる。どちらかを満たさなければ
**中断する**(終了コード 4)。**案内だけでは同じ失敗を繰り返す**(実測では
4 ラウンド続けて同じ理由で項目が落ちた)。止めれば利用者は 1 度だけ範囲を直せばよい
10. **着手前のテスト** — `--baseline-test` を実行する。**失敗していたら開始しない**。
10. **着手前のテスト** — `--baseline-test` を実行し、`--round-test` を渡したときはそれも 1 回実行する。**どちらかが失敗していたら開始しない**。
壊れた状態から始めると、壊したのか元から壊れていたのか区別できない。
この引数は**必須**である。振る舞いが変わっていないことを示す手段が無い書き換えは、
`refactoring` Skill の定義からして構造改善ではない
Expand All @@ -71,7 +71,7 @@ export CROSS_REFACTORING_TMP_DIR="$TMP_DIR"
| 見るもの | 見ない場合に起きること |
| --- | --- |
| `--scope` にテストの置き場所が含まれているか | テスト整備ラウンドが足すテストが範囲外になり、その項目は必ず失敗する |
| その置き場所が `--baseline-test` の実行集合に入るか | 足したテストが一度も実行されず、検証(Step 5)の判定に効かない |
| その置き場所が `--round-test`(省けば `--baseline-test`)の実行集合に入るか | 足したテストが一度も実行されず、検証(Step 5)の判定に効かない |

**2 つを 1 つの関門で見る。** 直す先はどちらも利用者が与える引数であり、別々に
止めると 2 度直すことになる。
Expand Down
Loading
Loading