概要
forge:next-spec-id(skills/next-spec-id/scripts/scan_spec_ids.py)が算出する次の連番IDが、実際には他ブランチで既に使用済みだった。ADR/DES共有採番で DES-007 を採番したが、feature/14-pyyaml-migration ブランチに既に ADR-007_pyyaml_adoption.md が存在しており衝突した(利用先: DocAdvisorリポジトリ)。
原因
get_scan_branches() が、ベースブランチ(develop/main)以外の各ブランチをスキャン対象に含めるかどうかを次の条件で判定している:
proc = subprocess.run(
['git', 'merge-base', '--is-ancestor', base_branch, branch],
...
)
if proc.returncode == 0:
scan.append(branch)
つまり「そのブランチが base_branch に追従済み(base の全コミットを含む)」場合のみスキャン対象にする。しかし、アクティブな feature ブランチが base 上の後続コミット(例: バージョンバンプ等)を取り込んでいない状態は日常的に起こり、その場合 is-ancestor は false になり、そのブランチが静かにスキャン対象から除外される。
再現手順(DocAdvisorリポジトリで確認済み):
git merge-base --is-ancestor develop feature/14-pyyaml-migration
echo $? # => 1 (false)
develop 側に feature/14-pyyaml-migration 分岐後の新規コミット(chore: bump to 0.4.5 等)があったため、上記が false になり、feature/14-pyyaml-migration 上の ADR-007 が next-spec-id のスキャン対象から漏れた。結果、branches_scanned は "成功した" 件数を報告するため、利用側が気づけないまま誤った ID を採番してしまう。
あるべき挙動
ID衝突検出の目的では「base に追従しているか」は無関係で、未マージの全アクティブブランチ(refs/heads/ + refs/remotes/origin/ の全て)を無条件でスキャンすべき。むしろ最も衝突リスクが高いのは「base から分岐したまま長期間追従していないブランチ」であり、現行ロジックはまさにそれを除外してしまう逆効果な設計になっている。
修正案
get_scan_branches() の is-ancestor フィルタを撤廃し、refs/heads/ + refs/remotes/origin/ から取得した全ブランチ(HEAD除く)を無条件でスキャン対象にする。マージ済みで内容が同一なブランチが重複スキャンされても、find_duplicates() 側で「同一パスの複数ブランチ出現は正常」として除外されるため副作用はないはず。
影響
CLAUDE.md 等で「next-spec-id の出力を鵜呑みにせず自分でも横断確認する」という運用回避策を各プロジェクトが個別に講じる必要が生じており、スキル本来の「安全に採番できる」という価値が損なわれている。
実装計画
関連ドキュメント
ルール文書
参考 PR(実装パターン)
実装スコープ
スコープ外
| 項目 |
理由 |
担当 |
| スキャン対象ブランチ数増加によるパフォーマンス最適化(並列化等) |
Issue の指摘は正確性の欠陥であり、性能要件は現時点で未報告 |
- |
TODO
着手前確認
実装する
テストを書く
確認・レビュー準備を行う
完了する
概要
forge:next-spec-id(skills/next-spec-id/scripts/scan_spec_ids.py)が算出する次の連番IDが、実際には他ブランチで既に使用済みだった。ADR/DES共有採番でDES-007を採番したが、feature/14-pyyaml-migrationブランチに既にADR-007_pyyaml_adoption.mdが存在しており衝突した(利用先: DocAdvisorリポジトリ)。原因
get_scan_branches()が、ベースブランチ(develop/main)以外の各ブランチをスキャン対象に含めるかどうかを次の条件で判定している:つまり「そのブランチが base_branch に追従済み(base の全コミットを含む)」場合のみスキャン対象にする。しかし、アクティブな feature ブランチが base 上の後続コミット(例: バージョンバンプ等)を取り込んでいない状態は日常的に起こり、その場合
is-ancestorは false になり、そのブランチが静かにスキャン対象から除外される。再現手順(DocAdvisorリポジトリで確認済み):
develop 側に
feature/14-pyyaml-migration分岐後の新規コミット(chore: bump to 0.4.5等)があったため、上記が false になり、feature/14-pyyaml-migration上のADR-007がnext-spec-idのスキャン対象から漏れた。結果、branches_scannedは "成功した" 件数を報告するため、利用側が気づけないまま誤った ID を採番してしまう。あるべき挙動
ID衝突検出の目的では「base に追従しているか」は無関係で、未マージの全アクティブブランチ(
refs/heads/+refs/remotes/origin/の全て)を無条件でスキャンすべき。むしろ最も衝突リスクが高いのは「base から分岐したまま長期間追従していないブランチ」であり、現行ロジックはまさにそれを除外してしまう逆効果な設計になっている。修正案
get_scan_branches()のis-ancestorフィルタを撤廃し、refs/heads/+refs/remotes/origin/から取得した全ブランチ(HEAD除く)を無条件でスキャン対象にする。マージ済みで内容が同一なブランチが重複スキャンされても、find_duplicates()側で「同一パスの複数ブランチ出現は正常」として除外されるため副作用はないはず。影響
CLAUDE.md 等で「next-spec-id の出力を鵜呑みにせず自分でも横断確認する」という運用回避策を各プロジェクトが個別に講じる必要が生じており、スキル本来の「安全に採番できる」という価値が損なわれている。
実装計画
関連ドキュメント
ルール文書
参考 PR(実装パターン)
実装スコープ
get_scan_branches()のis-ancestorフィルタを撤廃し、全ブランチを無条件対象にするscan_spec_ids.pyget_scan_branches()の単体テスト更新・追加test_scan_spec_ids.pyスコープ外
TODO
着手前確認
実装する
get_scan_branches()からgit merge-base --is-ancestorフィルタを削除し、for-each-ref取得の全ブランチ(HEAD 除く)を無条件でスキャン対象にするテストを書く
get_scan_branches: base に追従していないブランチも scan 対象に含まれることを確認するテストを追加get_scan_branches関連テスト(もしあれば is-ancestor 前提のもの)を新仕様に合わせて更新確認・レビュー準備を行う
python3 -m unittest discover -s tests -p 'test_*.py')dprint checkを実行する(Markdown 等変更があれば)完了する