Skip to content

fix(PLAN61): bin/devbase の位置引数の解決を名前の形・衝突・ヘルプ・グループで正す (#146, #142, #196, #200) - #207

Merged
takemi-ohama merged 9 commits into
mainfrom
fix/plan61-name-resolution
Sep 19, 2026
Merged

takemi-ohama merged 9 commits into
mainfrom
fix/plan61-name-resolution

Conversation

@takemi-ohama

Copy link
Copy Markdown
Contributor

Summary

bin/devbase の name 解決と build) の分岐にかかる 4 件を 1 本で直す。

要求と受け入れ条件: issues/PLAN61_name-resolution.md(16 件、検証手段つき)/ 設計: issues/PLAN61_name-resolution-design.md(#204 でマージ済み)

Closes #146
Closes #142
Closes #196
Closes #200

Test plan

  • uv run --locked pytest -q tests/ → 2813 passed、exit=0
  • PATH="/bin:$PATH" uv run --locked pytest -q tests/cli(bash 3.2.57)→ 824 passed、exit=0
  • uvx --from shellcheck-py shellcheck --severity=error bin/devbase → exit=0
  • uvx ruff check --select=E9,F63,F7,F82 lib → exit=0
  • cross-refactoring → cross-review → quality-gates(この PR で実施)
  • リリース後テスト: 実機で devbase build --help・devbase build ../etc・devbase container up carmo を打ち、出力と終了コードを見る

🤖 Generated with Claude Code

takemi-ohama and others added 7 commits September 19, 2026 09:17
- utils/names.py を新設し、SINGLE_SEGMENT_NAME_PATTERN と is_single_segment_name に
  名前の形の規則を 1 か所で置く
- _resolve_project_name は形に合わない名前で chdir せず、env も候補も読まずに False
- _named_lifecycle_project は形に合わない名前で実在と store_for を見ずに None
- _build_single_image の _IMAGE_NAME_RE を同じ関数へ寄せる
- 実装計画を issues/PLAN61_name-resolution.md の末尾に足す

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- is_single_segment_name と _SINGLE_SEGMENT_NAME_RE を足し、maybe_cd_project の入口で
  形に合わない値 (フラグ・空・`..` や `/` を含む値) を名前として扱わない
- tests/cli/conftest.py に exec_wrapper を新設。bin/devbase を tmp へ複製して起動し、
  uv だけを PATH で差し替える (maybe_cd_project と cmd_build は本物のまま動く)
- wrapper の正規表現と Python の定義の同期テスト、LC_ALL=C の確認 (café) を足す

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- build_usage を足し、build の引数のどこかに -h / --help があれば name 解決より前に
  使い方を出して終了コード 0 で終わる (build carmo --help でも cd と env の読み込みを起こさない)
- --context --help は使い方、--context=--help は下流へ渡す (決定 8)
- exec_wrapper で cmd_build を本物のまま動かし、=== Building devbase images === が
  出ないことを確かめる

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…イメージをビルドする (#142)

- name 解決の case に build の分岐を足す。<x> が名前の形に合い containers/<x> が実在すれば
  name 解決を通さず Python の単体ビルドへ渡し、projects/<x> もあれば stderr に 1 行知らせる
- projects/ にだけある名前は今と同じくプロジェクトへ cd して cmd_build へ進む
- build 分岐の PLAN49 の注記を新しい振り分けに合わせる

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
EOF
)
- name 解決の case を project だけにし、container <sub> <name> / ct <sub> <name> は
  実在する名前でも cd せずそのまま Python へ渡す (parser が [name] を持たないため usage エラー)
- name 解決の説明コメント (衝突注意の footgun) を変更後の規則に書き替え、cli.py の
  同期注意を project だけに直す
- test_wrapper_ct_up_name_cds_and_strips を受け入れ条件 12 のテストへ置き換える

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…と CHANGELOG に書く

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Add characterization tests for R1-001, R1-002, and R1-004 without changing production code.

Item-Id: R1-001
Round: 1
Impl-Runtime: codex
Impl-Model: default
@takemi-ohama

takemi-ohama commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor Author

改修計画 — devbasex/devbase #207

/ndf:cross-refactoring が提案し、適用した改善項目の記録である。
理由と手順は提案の時点でしか残らないため、公開の直前に書き出している。

  • 対象範囲: lib/devbase/utils/names.py, lib/devbase/cli.py, lib/devbase/commands/container.py, bin/devbase, tests/cli, tests/utils
  • 着手前のテスト: uv run --locked pytest -q tests/

ラウンド 1(実装 codex / レビュー agy / kiro)

R1-001 — lib/devbase/cli.py#_dispatch

兆候・経路 手法・階層 重要度 提案元 状態 コミット
branch unit — kiro 採用 1

なぜ: _dispatch は project サブコマンドを list / migrate-config / lifecycle に振り分ける。project list・top-level list・project up の 3 分岐は test_project_list / test_project_dispatch で固定されているが、subcommand=='migrate-config' を _require_devbase_root + cmd_project_migrate_config へ振り分ける分岐は固定されていない (ハンドラ単体は別テストにあるが dispatch 経路の配線は未固定)。

手順: 1. DEVBASE_ROOT を tmp_path に設定する
2. project.cmd_project_migrate_config を差し替えて (root, args) を記録する
3. args を subcommand='migrate-config' で作り _dispatch('project', args) を呼ぶ
4. 差し替えたハンドラが tmp_path を root として 1 回呼ばれ、戻り値が伝わることを assert する

R1-002 — lib/devbase/cli.py#_expand_argv

兆候・経路 手法・階層 重要度 提案元 状態 コミット
branch unit — kiro 採用 1

なぜ: _expand_argv には 4 つの解決分岐がある (トップレベル / SUBCMD_MAP / plugin repo sub-subcommand)。トップレベルと SUBCMD_MAP の分岐は test_prefix_resolution / test_project_dispatch で固定されているが、sys.argv[1] in ('plugin','pl') and sys.argv[2]=='repo' の 4 番目の引数を repo_subcmds (add/remove/list/refresh) で解決する分岐だけが固定されていない。

手順: 1. sys.argv を ['devbase','plugin','repo','r'] にして _expand_argv を呼び、実行結果を確認する
2. sys.argv[3] が 'refresh' に解決されることを assert する
3. 一意でない prefix (例 'r' が複数候補) の現状も実行して確認し、返る値をそのまま期待値にする
4. plugin repo でない argv (例 env) では 4 番目が書き換わらないことを固定する

R1-003 — lib/devbase/cli.py#_require_devbase_root

兆候・経路 手法・階層 重要度 提案元 状態 コミット
error unit — kiro 検証中 1

なぜ: _require_devbase_root は DEVBASE_ROOT 未設定時に sys.exit(1) する共有ヘルパ (list / migrate-config / _ROOT_COMMANDS 経路が通る) だが、未設定→終了コード 1 のエラー分岐が固定されていない。設定済みで Path を返す正常系も含めて未固定。

手順: 1. DEVBASE_ROOT を delenv して _require_devbase_root() を呼び、SystemExit が送出され code が 1 であることを assert する
2. DEVBASE_ROOT を tmp_path に設定した場合は Path(tmp_path) を返すことを assert する (表示文字列ではなく戻り値と終了コードだけを見る)

R1-004 — lib/devbase/cli.py#main

兆候・経路 手法・階層 重要度 提案元 状態 コミット
error unit — codex 採用 1

なぜ: 既存テストは注入対象の選択・スキップと main からの名前伝達を固定しているが、CLI の任意注入が DevbaseError または一般例外で失敗しても処理を継続する経路は固定されていない。container の required=False のテストは別の例外処理であり、cli.main の保証にはならない。

手順: 1. sys.argv を devbase project ps、DEVBASE_ROOT をテスト用ディレクトリに設定し、公開 main を呼ぶ。_load_secret_env と _dispatch は本物を通し、外部の機密注入境界 runtime.inject とコマンドハンドラ cmd_project をスタブにする。
2. runtime.inject が DevbaseError と RuntimeError をそれぞれ投げ、cmd_project が 7 を返す独立ケースを実行する。実測済みの main の戻り値 7 を固定し、注入失敗がコマンド結果を置き換えないことを示す。
3. 別ケースとして注入を成功させ、cmd_project が DevbaseError を投げると、実測済みの戻り値 1 になることを固定する。
4. 各ケースを独立したテストにし、環境と argv を復元する。ログ全文や内部関数の呼び出し回数は検証しない。

R1-005 — lib/devbase/cli.py#main

兆候・経路 手法・階層 重要度 提案元 状態 コミット
normal unit — agy 検証中 1

なぜ: トップレベルショートカット(例: devbase up )経由で実行された場合に、解析されたプロジェクト名が _load_secret_env へ正しく渡る主要経路が main エントリで固定されていない

手順: 1. sys.argv を ['devbase', 'up', 'carmo'] に設定し、_load_secret_env と _dispatch をモックする
2. cli.main() を実行する
3. _load_secret_env に cmd='up', name='carmo' が渡されることを検証する

見送った項目

ラウンド 対象 兆候・経路 理由
1 lib/devbase/commands/container.py#cmd_build branch 1 ラウンドの採用上限 5 件を超えた
1 lib/devbase/commands/container.py#cmd_logs branch 1 ラウンドの採用上限 5 件を超えた
1 lib/devbase/commands/container.py#cmd_project error 1 ラウンドの採用上限 5 件を超えた
1 lib/devbase/utils/names.py#is_single_segment_name boundary 1 ラウンドの採用上限 5 件を超えた

現状固定テストを 2 件追加する(対象コードは変更しない)。

- test_project_dispatch.py: _require_devbase_root の DEVBASE_ROOT 未設定→
  SystemExit(1) のエラー分岐と、設定済み→Path 返却の正常系を固定する。
- test_secret_injection.py: トップレベルショートカット (devbase up <name>)
  経由でも解析された名前が cmd='up' とともに _load_secret_env へ渡る主要
  経路を main エントリで固定する。

Item-Id: R1-003
Round: 1
Impl-Runtime: kiro
Impl-Model: default

@takemi-ohama takemi-ohama left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 cross-review | round 1 | kiro | APPROVE

位置引数の名前解決を「名前の形・衝突・ヘルプ・グループ」で正す 4 件を、共有ヘルパ utils/names.is_single_segment_name に集約し、shell (bin/devbase) と Python 3 入口 (_resolve_project_name / _named_lifecycle_project / _build_single_image) の規則を一致させている。.. / の連結前弾き、build --help の name 解決前判定、build <x> の containers 優先、container/ct の name 解決除外がいずれも受け入れ条件(16 件)に検証テストで対応し、影響ファイルのテスト 294 件をローカルで再実行して全通過を確認した(uv run --locked pytest)。設計・正確性・セキュリティ(パストラバーサル対策)とも修正提案なし。

@takemi-ohama takemi-ohama left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 cross-review | round 2 | codex | APPROVE

新規の修正指摘はありません。CLI・名前検証テスト 856 件成功、bash 構文検査成功。

@takemi-ohama

Copy link
Copy Markdown
Contributor Author

検証結果(quality-gates / standard)

head 8f12bbb(cross-refactoring の現状固定テスト 2 件を含む)で実行。

段階 コマンド 対象範囲 実行時刻 結果
限定的な検証 uv run --locked pytest -q tests/cli/test_project_name_resolution.py tests/cli/test_build_image_argument.py tests/utils/test_names.py 変更した振る舞い 実装中(tdd-cycle の各サイクル) 各サイクルで失敗 → 実装 → 通過を確認
全体テスト uv run --locked pytest -q tests/ 全体 2026-09-19 11:04 2825 passed / exit=0
全体テスト(bash 3.2) PATH="/bin:$PATH" uv run --locked pytest -q tests/cli wrapper の経路(macOS 既定 /bin/bash 3.2.57) 2026-09-19 11:07 836 passed / exit=0
静的解析 uvx --from shellcheck-py shellcheck --severity=error bin/devbase bin/devbase 2026-09-19 11:08 exit=0
静的解析 uvx ruff check --select=E9,F63,F7,F82 lib lib 以下 2026-09-19 11:08 exit=0
結合(CI) gh pr checks 207 Pytest 3.10 / 3.13・syntax 3 版・ruff・shellcheck(Linux) 2026-09-19 すべて pass

構造改善: cross-refactoring(提案 9 件 → 採用 5 件 / 見送り 4 件、適用 2 ラウンド、最終ゲートの全体テストを通過)
実装レビュー: cross-review approved(kiro と codex が承認、指摘 0 件。agy は 2 ラウンドとも結果を残さず担当から外した)

受け入れ条件(issues/PLAN61_name-resolution.md): 16/16 満たす。条件ごとの検証手段は同ファイルのチェックリストに記載(テスト名つき)。

未検証の項目: 実機での devbase build --help / devbase build ../etc / devbase container up carmo の出力と終了コード → リリース後テストで確かめる
既存の失敗: なし
範囲外と判断したもの: CLI リファレンスの [name] の列挙に rebuild / open が無い → #208、名前の検証の規則が 3 か所で食い違う → #203

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment