From 17e9d80d0c3e15d8d19cc2b3e9ab2a021c2be64d Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Mon, 5 Oct 2026 13:05:44 +0900 Subject: [PATCH 1/3] =?UTF-8?q?Docs:=20#214=20=E3=81=AE=E8=A6=81=E6=B1=82?= =?UTF-8?q?=E3=81=A8=E5=8F=97=E3=81=91=E5=85=A5=E3=82=8C=E6=9D=A1=E4=BB=B6?= =?UTF-8?q?=E3=82=92=E6=9B=B8=E3=81=8D=E3=80=81=E7=94=A8=E8=AA=9E=E9=9B=86?= =?UTF-8?q?=E3=81=AB=E3=80=8C=E5=88=97=E6=8C=99=E3=81=AE=E6=AD=A3=E6=9C=AC?= =?UTF-8?q?=E3=80=8D=E3=80=8C=E4=B8=80=E8=87=B4=E3=83=86=E3=82=B9=E3=83=88?= =?UTF-8?q?=E3=80=8D=E3=82=92=E8=B6=B3=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- docs/glossary.md | 2 + docs/glossary/glossary.json | 14 ++ issues/issue-214-requirements.md | 223 +++++++++++++++++++++++++++++++ 3 files changed, 239 insertions(+) create mode 100644 issues/issue-214-requirements.md diff --git a/docs/glossary.md b/docs/glossary.md index dea07970..1251b1ec 100644 --- a/docs/glossary.md +++ b/docs/glossary.md @@ -27,6 +27,8 @@ bin/devbase の位置引数の解決とビルドの入口(docs/specifications/ | 単体ビルド | — | $DEVBASE_ROOT/containers/ を devbase-:latest として 1 つだけ作るビルド | — | — | `docs/specifications/cli-argument-resolution.md` | | プロジェクトとして数える名前 | — | projects/ の直下の名前のうち、同期・一覧・状態・機密の操作がプロジェクトとして扱うもの。空でなく . で始まらない名前(utils/names.py の counts_as_project) | — | — | `docs/specifications/cli-argument-resolution.md` | | 名前の形の説明 | — | 名前の形を利用者へ説明する文(utils/names.py の NAME_FORM_HINT) | — | — | `docs/specifications/cli-argument-resolution.md` | +| 列挙の正本 | — | [name] と --context を受け付けるサブコマンドの集合を決める唯一の場所。argparse の _create_parser()(トップレベルの build の --context だけはラッパー) | — | — | — | +| 一致テスト | — | 列挙の正本を走査して得た集合と、写し(ラッパーのリスト・補完・確定仕様の表)を比べ、差があれば写しの場所と差を挙げて落ちるテスト | — | — | — | ## Compose の構成(`compose`) diff --git a/docs/glossary/glossary.json b/docs/glossary/glossary.json index f86efc6f..b67b9547 100644 --- a/docs/glossary/glossary.json +++ b/docs/glossary/glossary.json @@ -882,6 +882,20 @@ "context": "image-lineage", "meaning": "Dockerfile の 1 行から直の親イメージを読む規則。正規表現の値を lib/devbase/utils/dockerfile.py が正本として持ち、bin/devbase が同じ値を写す", "source": "docs/specifications/base-image-chain-build.md" + }, + { + "term": "列挙の正本", + "context": "cli", + "meaning": "[name] と --context を受け付けるサブコマンドの集合を決める唯一の場所。argparse の _create_parser()(トップレベルの build の --context だけはラッパー)", + "source": "", + "pending_source": "issues/issue-214-requirements.md" + }, + { + "term": "一致テスト", + "context": "cli", + "meaning": "列挙の正本を走査して得た集合と、写し(ラッパーのリスト・補完・確定仕様の表)を比べ、差があれば写しの場所と差を挙げて落ちるテスト", + "source": "", + "pending_source": "issues/issue-214-requirements.md" } ] } diff --git a/issues/issue-214-requirements.md b/issues/issue-214-requirements.md new file mode 100644 index 00000000..51223aea --- /dev/null +++ b/issues/issue-214-requirements.md @@ -0,0 +1,223 @@ +# #214: 列挙の正本を一元化し、argparse と文書の一致をテストで固定する([name] / --context) + +正は課題の本文(#214)で、この文書はその写しである。`spec-copy.py write` で作り直す。手で直さない。 + +## 依頼(原文) + +課題の起票の本文(PR #213 の範囲外として起こしたもの)をそのまま写す。 + +> ## 何を見つけたか +> +> `[name]` 引数と `--context` を取るサブコマンドの集合が、**5 か所に手で複製されている**。 +> +> | 複製先 | 実体 | +> | --- | --- | +> | `bin/devbase` | `_PROJECT_NAME_SUBCOMMANDS` / `_NAME_RESOLVABLE_SHORTCUTS`(`:583-584`) | +> | `lib/devbase/cli.py` | `_add_project_parser` などでの `name` positional と `--context` の付与 | +> | 文書 7 ファイル | `docs/user/cli-reference/02-project.md` / `docs/user/cli-reference/README.md` / `docs/user/container-operations.md` / `docs/user/environment-variables.md` / `docs/specifications/compose-profiles.md` / `docs/specifications/remote-docker-context.md` / `docs/specifications/cli-argument-resolution.md` | +> | 補完 2 ファイル | `etc/devbase-completion.bash` / `etc/_devbase` | +> +> 同期はコメントの申し合わせ(`bin/devbase:573-582`、`lib/devbase/cli.py:32-37` と `:287` 付近)だけで担保されている。**集合の一致を固定するテストは無い。** `tests/cli/test_rebuild.py:152-153` が `bin/devbase` の 2 つのリストに `rebuild` が含まれることを見ているだけで、`_PROJECT_NAME_SUBCOMMANDS` と argparse の一致も、文書・補完との一致も固定されていない。 +> +> `lib/devbase/cli.py` の `_create_parser()` を走査すると、集合は次のとおりである(d433e55)。 +> +> | 対象 | サブコマンド | +> | --- | --- | +> | `project` で `name` positional を持つ | `up` `down` `ps` `logs` `scale` `rebuild` `open` `post-start` | +> | `project` で `--context` を持つ | `up` `down` `ps` `logs` `login` `scale` `build` `rebuild` `open` `post-start` | +> | `container` で `--context` を持つ | `up` `down` `ps` `logs` `login` `scale` `build` `rebuild` `open` | +> | `container` で `name` positional を持つ | なし | +> +> `profile` は `[name]` と `--context` を入れ子の parser(`profile up` / `profile down` / `profile list`)で持つため、上の走査では拾えない。文書は `profile` も `[name]` / `--context` を取るものとして並べている。 +> +> `bin/devbase` の 2 つのリスト・補完 2 ファイル・`docs/specifications/cli-argument-resolution.md` の表は argparse と一致している。一方、`docs/specifications/remote-docker-context.md:42-44` の「`--context` を受け付けるもの」の列挙には `post-start` が無い。 +> +> PLAN49(`rebuild` の追加)・PLAN59(`open` の追加)・#371(`post-start` の追加)の 3 回とも、コードと補完は更新されたが文書のどこかが取り残された。同じ漏れが 3 度起きている。 +> +> ## 現象レイヤー +> +> 文書 7 ファイルの列挙、`bin/devbase` の 2 つのリスト、補完 2 ファイル。どれも argparse の定義を手で写したもので、写し漏れが起きる。 +> +> ## 修正レイヤー +> +> 列挙の**正本は argparse(`lib/devbase/cli.py` の `_create_parser()`)**に置く。定義そのものがそこにあり、他の複製はすべてそこから派生する。 +> +> 1. `tests/cli/` 配下に一致テストを足し、`_create_parser()` を走査して得た集合に次を合わせる +> - `bin/devbase` の `_PROJECT_NAME_SUBCOMMANDS` / `_NAME_RESOLVABLE_SHORTCUTS`(`login` / `build` を意図的に除く・含める規則はコメントのとおり) +> - 補完 2 ファイル(`etc/devbase-completion.bash` / `etc/_devbase`) +> - `docs/specifications/cli-argument-resolution.md` の表 +> - 入れ子の `profile` も走査の対象に含める +> 2. 他の文書は列挙を持たず、`docs/specifications/cli-argument-resolution.md` へのリンクにする。`docs/specifications/remote-docker-context.md:42-44` の列挙(`post-start` が無い)もここでリンクに置き換える +> +> 走査の実装は PR #213 で使った、`_create_parser()` から `name` positional と `--context` を持つ parser を集める形がそのまま使える。 +> +> ## どこで見つけたか +> +> #208 の本文「修正レイヤー」。PR #213 は現象レイヤー(文書への追記)だけを直した。 +> +> ## なぜこの変更の範囲外なのか +> +> #208 の本文が「恒久策(正本の一元化と一致テスト)は、この issue の範囲を超えるため着手の時点で分ける」と明示している。PR #213 の受け入れ条件は文書の列挙が argparse と一致することまでで、正本の移動とテストの追加は含まない。また PR #213 は `docs/` 配下だけに閉じる `light` の変更で、`tests/` を触らない。 +> +> ## 直さないと何が起きるか +> +> `[name]` または `--context` を取るサブコマンドを次に増やしたとき、同じ取り残しが起きる。文書だけが古いため CI も補完も気づかず、利用者が「そのコマンドは名前を取れない」と読んで遠回りする。影響の範囲は文書の読み手全体で、コードの振る舞いには及ばない。 +> +> ## 由来 +> +> PR #213(#208 / #195 の現象レイヤーの修正) +> +> ## 直す場所 +> +> | 項目 | 内容 | +> | --- | --- | +> | 現れている場所 | 文書 7 ファイルの列挙(`remote-docker-context.md:42-44` は `post-start` が欠けている)・bin/devbase の _PROJECT_NAME_SUBCOMMANDS / _NAME_RESOLVABLE_SHORTCUTS・補完 2 ファイル(etc/devbase-completion.bash / etc/_devbase) | +> | 直す場所 | lib/devbase/cli.py の _create_parser() を正本にし、tests/cli に一致テストを新設(手: 統合 consolidate_duplication + 新設)。他の文書は cli-argument-resolution.md へのリンクにする | +> + +## 目的 + +- `[name]`(プロジェクト名)と `--context` を受け付けるサブコマンドの集合について、正本を argparse(`lib/devbase/cli.py` の `_create_parser()`)の 1 か所にする。サブコマンドを足し引きして写しが取り残されたら、CI のテストが落ちる状態にする +- 文書は列挙を持たず、確定仕様 `docs/specifications/cli-argument-resolution.md` の表へのリンクで済ませる。表は一致テストが argparse と比べる +- PLAN49(`rebuild`)・PLAN59(`open`)・#371(`post-start`)で 3 度起きた文書の取り残しを、4 度目に起こさない + +## 前提 + +- 前提 1: 一致を見る基準は、この課題の着手の時点の `main`(a120d15)で `_create_parser()` を走査した集合である。走査の結果は次の「現状の集合」の節のとおりで、課題の本文の表(d433e55)と比べて `env exec` / `env token` の `--context` と、入れ子の `profile up` / `profile down` / `profile list` が増えている +- 前提 2: 「`[name]` を取る」は、`dest` が `name` の位置引数のうち**プロジェクト名を表すもの**に限る。対象は `project` グループ(入れ子の `profile` を含む)とトップレベルの parser である。`plugin` / `pl`・`snapshot` / `ss`・`env backend use` の `name` はプラグイン・スナップショット・backend の名前で、この課題の集合に入れない +- 前提 3: 「`--context` を取る」は、`--context` を持つすべての parser である(`project` / `container` / `ct` の配下・入れ子の `profile`・トップレベル・`env exec` / `env token`)。argparse に parser の無いトップレベルの `build`(シェルの `cmd_build` が `--context` を抜き取る)は、ラッパーの実装を正本とする唯一の例外として、一致テストが名指しで足す +- 前提 4: ラッパーの 2 つのリストは、argparse の集合から次の規則で導ける形のまま残す。`_PROJECT_NAME_SUBCOMMANDS` は「`project` の直下の parser で `[name]` を取るもの」(入れ子の `profile` は 3 番目の引数が `up` / `down` / `list` になるため除く。`docs/specifications/compose-profiles.md` の既存の決定)。`_NAME_RESOLVABLE_SHORTCUTS` は「`cli.SHORTCUTS` のキー」と「`build`」の和(`login` は parser が `[name]` を持たないが、ラッパーの name 解決だけが名前の指定の手段として含める。`cli-argument-resolution.md` の既存の決定)。リストをシェルから消して Python から生成する形には変えない(name 解決のたびに `uv run` を起こさないため。同じ理由で名前の形の正規表現も文字列で写している) +- 前提 5: 補完の一致は「`[name]` を取る位置でプロジェクト名を補完すること」と「`[name]` か `--context` を取るサブコマンドが、`project` / `container` の補完が出すサブコマンドの一覧に含まれること」の 2 つで見る(argparse の `project` には補完に無い `migrate-config` / `migrate-volume` があり、一覧の全体の一致はこの課題の集合の外である)。`--context` を補完するかどうかは一致の対象に入れない(現状は `open` と `post-start` だけが補完し、揃えると補完の振る舞いが広く変わるため。下の「未決」) +- 前提 6: 入れ子の `profile` の `[name]` は、補完の一致の対象から名指しで外す。`project profile up` の 1 つ目の位置引数は、値が 1 個ならプロファイル名、2 個ならプロジェクト名になり、補完の時点ではどちらか決まらないためである。一致テストは外す理由を例外の一覧に持つ +- 前提 7: 「列挙」は、2 つ以上のサブコマンドを並べて「これらが `[name]` / `--context` を取る」と述べる文・箇条・表の行を指す。1 つのコマンドの使い方の行(`devbase project up [name] [--context NAME]`)、1 行 1 コマンドのショートカットの対応表、コマンドの地図(`docs/user/cli-reference/README.md` の mermaid の図)は列挙に当たらず、そのまま残す +- 前提 8: 一致テストの置き場は `tests/cli/` とする。bash の補完は既存の `tests/cli/test_completion.py` と同じく実際に source して確かめ、zsh の補完は CI のランナーに依らないよう内容(`etc/_devbase` の分岐)を静的に見る +- 前提 9: 利用者に見える振る舞いは変わらない(テストの追加と文書の列挙の置き換えだけ)。`CHANGELOG.md` の `[Unreleased]` には書かない。文書の誤り(`remote-docker-context.md` の `post-start` の欠け)の訂正も、文書の変更として書かない + +## 現状の集合(a120d15 で `_create_parser()` を走査した結果) + +| 対象 | `[name]` を取る | `--context` を取る | +| --- | --- | --- | +| `project` の直下 | `up` `down` `ps` `logs` `scale` `rebuild` `open` `post-start` | `up` `down` `ps` `logs` `login` `scale` `build` `rebuild` `open` `post-start` | +| `project profile` | `up` `down` `list` | `up` `down` `list` | +| `container` / `ct` の直下 | なし | `up` `down` `ps` `logs` `login` `scale` `build` `rebuild` `open` | +| `container profile` / `ct profile` | なし | `up` `down` `list` | +| トップレベル(argparse) | `up` `down` `ps` `scale` `rebuild` `open` | `up` `down` `ps` `login` `scale` `rebuild` `open` | +| トップレベル(ラッパー) | — | `build`(シェルの `cmd_build`) | +| `env` | なし | `exec` `token` | + +| 写し | 現状 | +| --- | --- | +| `bin/devbase` の 2 つのリスト | 前提 4 の規則と一致している | +| `etc/devbase-completion.bash` | `[name]` を取る位置(`profile` を除く)でプロジェクト名を補完する。`--context` は `open` / `post-start` だけ補完する | +| `etc/_devbase` | 同上(静的に見た範囲) | +| `docs/specifications/cli-argument-resolution.md` | `[name]` の表はある。`--context` の表は無い | +| `docs/specifications/remote-docker-context.md`(「context の解決」) | `--context` の列挙に `post-start` が無い | +| `docs/user/environment-variables.md`(`DEVBASE_DOCKER_CONTEXT` の行) | `--context` の列挙に `post-start` と `profile` が無い | +| ほかの列挙 | `docs/user/cli-reference/02-project.md`(`profile` の注記と「`--context NAME`(共通オプション)」)・`docs/user/container-operations.md` の冒頭の注記・`docs/specifications/compose-profiles.md`(`--context` の一文と `_PROJECT_NAME_SUBCOMMANDS` の写し)。いずれも現状は argparse と一致している | + +## 対象範囲 + +含む: +- argparse を走査して `[name]` の集合と `--context` の集合を得る一致テストを `tests/cli/` に足す(入れ子の `profile` を含む) +- 一致テストが比べる写し: `bin/devbase` の `_PROJECT_NAME_SUBCOMMANDS` / `_NAME_RESOLVABLE_SHORTCUTS`、補完 2 ファイル(前提 5・6 の範囲)、`docs/specifications/cli-argument-resolution.md` の表 +- `docs/specifications/cli-argument-resolution.md` に `--context` を受け付けるサブコマンドの表を足す(`[name]` の表と同じ節か隣の節) +- ほかの文書の列挙(前提 7 の意味)を、`cli-argument-resolution.md` の表へのリンクに置き換える。`remote-docker-context.md` の `post-start` の欠けは、この置き換えで消える +- `bin/devbase` と `lib/devbase/cli.py` の「同期注意」のコメントと、`cli-argument-resolution.md` の「運用」の該当の箇条を、一致テストを指す形に直す +- 既存の `tests/cli/test_rebuild.py` の `test_wrapper_rebuild_in_name_resolvable` は、一致テストが包むなら外してよい(外すかは設計で決める) + +含まない: +- argparse の定義そのもの(どのサブコマンドが `[name]` / `--context` を取るか)を変えること +- ラッパーのリストを Python から生成する形に変えること(前提 4) +- 補完が `--context` を補完する範囲を広げること(前提 5。下の「未決」) +- `profile` の `[name]` を補完すること(前提 6) +- `plugin` / `snapshot` / `env backend use` の `name`(前提 2) +- 1 コマンドの使い方の行・ショートカットの対応表・コマンドの地図の書き換え(前提 7) +- `container` / `ct` に `[name]` を持たせること(PLAN61 決定 10 のまま) + +## ドメインイベント + +| # | イベント | 引き金 | 失敗したとき | 順序の前提 | +| --- | --- | --- | --- | --- | +| E1 | 開発者が argparse のサブコマンド・`name` の位置引数・`--context` を足した(または外した) | 新しいサブコマンドの追加(PLAN49・PLAN59・#371 と同じ形) | — | — | +| E2 | 一致テストが `_create_parser()` を走査して `[name]` の集合と `--context` の集合を得た | pytest の実行(手元と CI) | parser の構造が変わって走査が集合を取れない(例: 入れ子の parser を拾えない)。テストは空の集合で通らず、集合が空であることで落ちる | E1 | +| E3 | 一致テストがラッパーの 2 つのリストを `bin/devbase` から読み、前提 4 の規則で導いた集合と比べた | E2 の後 | リストの行が見つからない・形が変わった。テストは読めなかったことで落ちる | E2 | +| E4 | 一致テストが bash の補完を実行し、zsh の補完を静的に読んで、前提 5・6 の範囲で比べた | E2 の後 | bash が無い・補完の関数が終了コード 0 以外を返した。テストは落ちる | E2 | +| E5 | 一致テストが `cli-argument-resolution.md` の 2 つの表を読み、argparse の集合と比べた | E2 の後 | 表の見出しが見つからない・表の形が変わった。テストは読めなかったことで落ちる | E2 | +| E6 | 一致テストが、欠けたサブコマンドと余分なサブコマンドを写しの場所ごとに挙げて落ちた | E3〜E5 のどれかで差が出た | — | E3〜E5 | +| E7 | 開発者が写しを直し、一致テストが通った | E6 | — | E6 | + +E3〜E5 は互いの順序を仮定しない(どれか 1 つが落ちてもほかの比べ方は結果を出す)。 + +## 用語 + +| 用語 | 意味 | +| --- | --- | +| 列挙の正本 | `[name]` と `--context` を受け付けるサブコマンドの集合を決める唯一の場所。この課題では argparse の `_create_parser()`(トップレベルの `build` の `--context` だけはラッパー) | +| 一致テスト | 列挙の正本を走査して得た集合と、写し(ラッパーのリスト・補完・確定仕様の表)を比べ、差があれば写しの場所と差を挙げて落ちるテスト | + +## 受け入れ条件 + +- [ ] AC1: 一致テストが `_create_parser()` を走査して得る `[name]` の集合が、前提 1 の表の「`[name]` を取る」列と一致する(`project` の直下 8 つ・`project profile` の 3 つ・トップレベル 6 つ)。`plugin` / `snapshot` / `env backend use` の `name` を含まない +- [ ] AC2: 一致テストが得る `--context` の集合が、前提 1 の表の「`--context` を取る」列と一致する(`env exec` / `env token`・入れ子の `profile`・トップレベルの `build` を含む) +- [ ] AC3: `bin/devbase` の `_PROJECT_NAME_SUBCOMMANDS` が「`project` の直下で `[name]` を取る parser」の集合と一致しなければ、一致テストが落ちる。確かめ方: 作業用の写しで `_PROJECT_NAME_SUBCOMMANDS` から `post-start` を消すと落ち、余分に `login` を足しても落ちる +- [ ] AC4: `bin/devbase` の `_NAME_RESOLVABLE_SHORTCUTS` が「`cli.SHORTCUTS` のキー ∪ {`build`}」と一致しなければ、一致テストが落ちる。確かめ方: `open` を消すと落ち、`logs` を足しても落ちる +- [ ] AC5: argparse の `project` の直下に `[name]` を取るサブコマンドを 1 つ足し、ラッパー・補完・表のどれも直さないとき、一致テストが落ちる。失敗の出力に、足したサブコマンドの名前と、取り残した写しの場所(ファイル)がそれぞれ出る +- [ ] AC6: bash の補完(`etc/devbase-completion.bash`)で、`project` の直下で `[name]` を取る 8 つと、トップレベルで `[name]` を取る 6 つの名前の位置で、`projects/` のプロジェクト名が候補に出る。どれか 1 つの分岐からプロジェクト名の補完を外すと一致テストが落ちる +- [ ] AC7: zsh の補完(`etc/_devbase`)で、AC6 と同じサブコマンドの分岐がプロジェクト名の補完(`_devbase_project_names`)を持つ。どれか 1 つから外すと一致テストが落ちる +- [ ] AC8: argparse の `project` / `container` の直下で `[name]` か `--context` を取るサブコマンド(入れ子の `profile` は親の `profile` として数える)が、bash と zsh の補完が出す `project` / `container` のサブコマンドの一覧にすべて含まれる。どちらかから 1 つ消すと一致テストが落ちる(`migrate-config` / `migrate-volume` のように、どちらも取らないサブコマンドが補完に無いことは問わない) +- [ ] AC9: 入れ子の `profile` の `[name]` を補完の一致から外していることが、一致テストの例外の一覧に理由つきで書かれている。例外の一覧に無いサブコマンドを補完が欠いたら落ちる(例外は黙って広がらない) +- [ ] AC10: `docs/specifications/cli-argument-resolution.md` に `[name]` を受け付けるサブコマンドの表と、`--context` を受け付けるサブコマンドの表がある。一致テストがこの 2 つの表を読んで argparse の集合(AC1・AC2)と比べ、表から 1 つ消すか余分に足すと落ちる +- [ ] AC11: `docs/` の下で、前提 7 の意味の列挙が `cli-argument-resolution.md` の外に残っていない。対象は少なくとも `docs/specifications/remote-docker-context.md` の「context の解決」・`docs/user/environment-variables.md` の `DEVBASE_DOCKER_CONTEXT` の行・`docs/user/cli-reference/02-project.md` の `profile` の注記と「`--context NAME`(共通オプション)」・`docs/user/container-operations.md` の冒頭の注記・`docs/specifications/compose-profiles.md` の `--context` の一文と `_PROJECT_NAME_SUBCOMMANDS` の写し。それぞれ、`cli-argument-resolution.md` の該当の表へのリンクになっている +- [ ] AC12: 置き換えた文書のリンクが、`cli-argument-resolution.md` の実在する見出しの位置を指す(リンクを辿って表に着く) +- [ ] AC13: `bin/devbase` と `lib/devbase/cli.py` の「同期注意」のコメント、`cli-argument-resolution.md` の「運用」の箇条が、手での同期の申し合わせではなく一致テストのファイルを指している +- [ ] AC14: 退行しない: `env -u DEVBASE_ROOT uv run --locked pytest tests/ -q` が全件通る。argparse の定義・ラッパーの 2 つのリストの中身・補完の振る舞いは変わらない(`git diff` で `lib/devbase/cli.py` の parser の定義、`bin/devbase` の 2 つのリストの値、`etc/` の補完の分岐に差が無い。コメントの差は許す) +- [ ] AC15: 一致テストは実の `DEVBASE_ROOT`・実の docker・実のプロジェクトに触らない(補完の確かめは一時ディレクトリの `projects/` で行う) + +## 非機能の条件 + +| 大項目 | 条件 | +| --- | --- | +| 性能・拡張性 | 一致テストの追加で `tests/cli` の実行時間が手元で 10 秒を超えて延びない(bash の補完の起動はサブコマンドごとに 1 回で、数十回に収まる) | +| 運用・保守性 | サブコマンドを足した開発者が、失敗の出力だけで直す場所(ファイル)と足りないサブコマンドの名前を知れる(AC5) | +| システム環境 | 一致テストは CI(ubuntu-latest)と macOS の手元の両方で通る。zsh が無い環境でも落ちない(zsh は静的に読む) | + +## 影響 + +| 対象 | 影響 | +| --- | --- | +| 公開インタフェース | 変わらない(CLI の引数・補完の振る舞い・終了コード) | +| データ | 無し | +| 既存の振る舞い | 変わらない。変わるのは文書の列挙(リンクへ)とテストの追加 | + +## 検証手段 + +| 項目 | 手段 | +| --- | --- | +| テスト | `env -u DEVBASE_ROOT uv run --locked pytest tests/cli -q`、全体は `env -u DEVBASE_ROOT uv run --locked pytest tests/ -q` | +| 静的解析・型検査 | `uvx ruff check --select=E9,F63,F7,F82 lib`、`python3 .github/scripts/proper_term_check.py` | +| テストが落ちることの確認(AC3〜AC10) | 作業用の写しで写しを 1 つずつ崩し、一致テストが落ちて差と場所を出すことを確かめる(記録は PR 本文に残す。コミットには崩した状態を残さない) | +| 文書のリンク(AC11・AC12) | `docs/` を `post-start`・`rebuild` / `open`・`--context` で検索し、列挙が `cli-argument-resolution.md` の外に無いことと、リンク先の見出しが実在することを確かめる | + +## 前提とする取り決め + +| 項目 | 参照先 / 決めたこと | +| --- | --- | +| プロジェクト構造 | `AGENTS.md` の「このリポジトリ」。テストは `tests/cli/`、確定仕様は `docs/specifications/`。`projects/*` と `repos/` は触らない | +| コーディング規約 | 周りのテスト(`tests/cli/test_completion.py`・`tests/cli/test_project_name_resolution.py`)の書き方に合わせる。検査は `AGENTS.md` の「検査」 | +| テスト戦略 | 単体の階層で argparse を走査し、写しを読んで比べる。bash の補完は実行して確かめ、zsh は内容を読む。ラッパーの name 解決の振る舞いは既存の `tests/cli/test_project_name_resolution.py` が担い、この課題では足さない | + +## 境界 + +| 区分 | 内容 | +| --- | --- | +| 常に行う | `env -u DEVBASE_ROOT` を付けた全体のテスト、CI と同じ lint と固有の語の検査、作業は `.worktrees/` の worktree で行う | +| 確認してから行う | argparse の定義・ラッパーのリストの値・補完の振る舞いを変えること(この課題の範囲の外。必要が出たら止めて人へ戻す) | +| 行わない | 範囲外の文書の書き換え(使い方の行・対応表・地図)、`plugin` / `snapshot` の `name` の扱いの変更、`projects/*` の変更 | + +## 未決 + +| 項目 | 誰が決めるか | 期限 | +| --- | --- | --- | +| 補完が `--context` を補完する範囲(いまは `open` / `post-start` だけ)を、`--context` を取るすべてのサブコマンドへ揃えるか。揃えるなら補完の振る舞いが変わるため、別の課題として起こすかを決める | 利用者(課題の棚卸し) | この課題のリリースまで | +| 既存の `tests/cli/test_rebuild.py` の `test_wrapper_rebuild_in_name_resolvable` を一致テストへ寄せて消すか残すか | 設計(`design`) | 設計 PR | +| `cli-argument-resolution.md` の `--context` の表を置く節(`[name]` の表と同じ節か、別の節か)と、表の機械の読み方(見出しで探すか、印を置くか) | 設計(`design`) | 設計 PR | From 8c2a2f5bed61cc0c6e4b923aebbf89aec12dce49 Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Mon, 5 Oct 2026 13:12:07 +0900 Subject: [PATCH 2/3] =?UTF-8?q?Docs:=20#214=20=E3=81=AE=E8=A8=AD=E8=A8=88?= =?UTF-8?q?=E3=82=92=E6=9B=B8=E3=81=8D=E3=80=81=E7=94=A8=E8=AA=9E=E9=9B=86?= =?UTF-8?q?=E3=81=AB=E3=80=8C=E5=86=99=E3=81=97=E3=80=8D=E3=80=8C=E8=A3=9C?= =?UTF-8?q?=E5=AE=8C=E3=81=AE=E4=BE=8B=E5=A4=96=E3=80=8D=E3=82=92=E8=B6=B3?= =?UTF-8?q?=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 一致テストを tests/cli/test_name_context_consistency.py に置き、argparse の走査で得た [name] / --context の集合とラッパー・補完・確定仕様の 2 つの表を写しの場所ごとに比べる。 ほかの文書の列挙は確定仕様の表へのリンクに置き換える。 Co-Authored-By: Claude Opus 5.5 --- docs/glossary.md | 2 + docs/glossary/glossary.json | 14 ++ issues/issue-214-design.md | 386 ++++++++++++++++++++++++++++++++++++ 3 files changed, 402 insertions(+) create mode 100644 issues/issue-214-design.md diff --git a/docs/glossary.md b/docs/glossary.md index 1251b1ec..a03ea210 100644 --- a/docs/glossary.md +++ b/docs/glossary.md @@ -29,6 +29,8 @@ bin/devbase の位置引数の解決とビルドの入口(docs/specifications/ | 名前の形の説明 | — | 名前の形を利用者へ説明する文(utils/names.py の NAME_FORM_HINT) | — | — | `docs/specifications/cli-argument-resolution.md` | | 列挙の正本 | — | [name] と --context を受け付けるサブコマンドの集合を決める唯一の場所。argparse の _create_parser()(トップレベルの build の --context だけはラッパー) | — | — | — | | 一致テスト | — | 列挙の正本を走査して得た集合と、写し(ラッパーのリスト・補完・確定仕様の表)を比べ、差があれば写しの場所と差を挙げて落ちるテスト | — | — | — | +| 写し | — | 列挙の正本から手で写した集合(ラッパーの 2 つのリスト・bash と zsh の補完・確定仕様の 2 つの表) | — | — | — | +| 補完の例外 | — | 列挙の正本では [name] を取るが、補完がプロジェクト名を出さないと決めたコマンドの道と理由の組。一致テストが一覧として持つ | — | — | — | ## Compose の構成(`compose`) diff --git a/docs/glossary/glossary.json b/docs/glossary/glossary.json index b67b9547..a420b1df 100644 --- a/docs/glossary/glossary.json +++ b/docs/glossary/glossary.json @@ -896,6 +896,20 @@ "meaning": "列挙の正本を走査して得た集合と、写し(ラッパーのリスト・補完・確定仕様の表)を比べ、差があれば写しの場所と差を挙げて落ちるテスト", "source": "", "pending_source": "issues/issue-214-requirements.md" + }, + { + "term": "写し", + "context": "cli", + "meaning": "列挙の正本から手で写した集合(ラッパーの 2 つのリスト・bash と zsh の補完・確定仕様の 2 つの表)", + "source": "", + "pending_source": "issues/issue-214-design.md" + }, + { + "term": "補完の例外", + "context": "cli", + "meaning": "列挙の正本では [name] を取るが、補完がプロジェクト名を出さないと決めたコマンドの道と理由の組。一致テストが一覧として持つ", + "source": "", + "pending_source": "issues/issue-214-design.md" } ] } diff --git a/issues/issue-214-design.md b/issues/issue-214-design.md new file mode 100644 index 00000000..848206c0 --- /dev/null +++ b/issues/issue-214-design.md @@ -0,0 +1,386 @@ +# サブコマンドを足すたびに文書の `[name]` / `--context` の列挙が取り残され、利用者が「そのコマンドは名前を取れない」と読む → 列挙の正本を argparse の 1 か所にし、写しが外れたら CI のテストが場所と名前を挙げて落ちる(#214) + +## 目的 + +- **何が壊れているか**: `[name]` と `--context` を受け付けるサブコマンドの集合が、ラッパー・補完・文書 7 ファイルに手で写されている。一致を見るテストが無く、PLAN49・PLAN59・#371 の 3 回とも文書のどこかが取り残された(`remote-docker-context.md` は今も `post-start` を欠く) +- **誰が困るか**: 文書を読む利用者(名前を渡せるコマンドを渡せないと読んで遠回りする)と、サブコマンドを足す開発者(直す場所を覚えておくしかない) +- **直すと何が成り立つか**: 写しは argparse と比べられ、外れると CI で落ちる。文書は確定仕様の 1 組の表だけが列挙を持ち、ほかはそこへのリンクになる + +## 適用範囲 + +- **働く範囲**: このリポジトリだけ(`tests/cli/` の一致テストと `docs/` の文書)。配布物の振る舞いは変えない +- **プロジェクトごとに違うもの**: 無し(プロジェクトの設定にも引数にも依らない) +- **当たるモード**: `standard` + +## あるべき姿の根拠 + +| 根拠 | 種類 | 何を示すか | +| --- | --- | --- | +| 課題 #214 の本文「修正レイヤー」: 列挙の正本は argparse に置き、他の文書はリンクにする | 利用者の指示の原文 | 正本の置き場と、文書を列挙からリンクへ変える方向 | +| a120d15 で `_create_parser()` を走査した結果が、要求の前提 1 の表と一致した(下の「実測」) | 実測 | parser の木を辿るだけで、入れ子の `profile` と `env exec` / `env token` を含む集合が取れる | +| PLAN49・PLAN59・#371 の 3 回とも、コードと補完は直り文書が取り残された(課題 #214 の本文) | 実測 | テストで縛られていない写しだけが外れる。縛れば外れない | + +要求と受け入れ条件は #214 の本文にある(コピーは `issues/issue-214-requirements.md`)。この文書は「どう作るか」だけを扱う。 + +実測(a120d15、`lib/` を import して parser の木を辿り、位置引数 `name` と `--context` を持つ parser の道を集めた): + +```text +[name](plugin / pl / snapshot / ss / env を除く): + up down open ps rebuild scale + project {down logs open post-start ps rebuild scale up} + project profile {down list up} +[name](除いたもの): + env backend use / plugin・pl {info uninstall update repo remove repo refresh} / snapshot・ss {copy delete restore} +--context: + up down login open ps rebuild scale + project {build down login logs open post-start ps rebuild scale up} / project profile {down list up} + container・ct {build down login logs open ps rebuild scale up} / container・ct profile {down list up} + env {exec token} +``` + +## ドメインモデル + +### コンテキスト + +| コンテキスト | 何の語が 1 つの意味に決まるか | +| --- | --- | +| コマンドの入口(`cli`) | サブコマンドが `[name]`(プロジェクト名)と `--context` を受け付けるかどうか。どの写しもこの語で集合を表す | + +1 つのコンテキストに閉じる。`--context` の意味(どの daemon へ向けるか)は `docker-context` のコンテキストが持ち、この変更は「どのサブコマンドが受け付けるか」の集合だけを扱う。 + +### 集約 + +| 集約 | 持ち主(書き換えてよいもの) | 根 | エンティティ | 値オブジェクト | +| --- | --- | --- | --- | --- | +| 受け付けの集合 | `lib/devbase/cli.py` の `_create_parser()`(トップレベルの `build` の `--context` だけは `bin/devbase` の `build` の分岐) | parser の木 | サブコマンドの parser | コマンドの道(`("project", "profile", "up")` のような語の並び) | +| 写し | 写しのファイルごとの開発者(一致テストは読むだけで書かない) | 写しの場所(ファイルと、その中の箇所) | — | 写された集合 | + +写しは 4 種ある。ラッパーの 2 つのリスト(`bin/devbase`)、bash の補完(`etc/devbase-completion.bash`)、zsh の補完(`etc/_devbase`)、確定仕様の 2 つの表(`docs/specifications/cli-argument-resolution.md`)。写しは受け付けの集合をコマンドの道で参照し、集合を書き換えない。 + +### 不変条件 + +| # | 集約 | 条件 | 破れたときの扱い | +| --- | --- | --- | --- | +| I1 | 受け付けの集合 | 走査で得る `[name]` の集合と `--context` の集合は空でなく、入れ子の `project profile up` と `env exec` を含み、`plugin info` / `snapshot restore` / `env backend use` を含まない | 一致テストが「走査が集合を取れていない」として落ちる(ほかの比べ方は結果を出す) | +| I2 | 写し | `_PROJECT_NAME_SUBCOMMANDS` は「`project` の直下で `[name]` を取る parser」の名前の集合に等しい | 一致テストが `bin/devbase` と欠け・余分を挙げて落ちる | +| I3 | 写し | `_NAME_RESOLVABLE_SHORTCUTS` は「`cli.SHORTCUTS` のキー ∪ {`build`}」に等しい | 同上 | +| I4 | 写し | bash の補完は、`[name]` の集合から補完の例外を引いたすべての道(今は `project` の直下 8 つとトップレベル 6 つ)の名前の位置で、`projects/` のプロジェクト名を候補に出す | 一致テストが `etc/devbase-completion.bash` と、名前を補完しない道を挙げて落ちる | +| I5 | 写し | zsh の補完は、I4 と同じ道の `case` の腕に `_devbase_project_names` を持つ。腕が無い道は読めなかったとして扱う | 一致テストが `etc/_devbase` と、腕に持たないサブコマンドを挙げて落ちる | +| I6 | 写し | `project` / `container` の直下で `[name]` か `--context` を取るサブコマンド(入れ子は親の名前で数える)は、bash と zsh の補完のサブコマンドの一覧にすべて含まれる | 一致テストが補完のファイルと、一覧に無いサブコマンドを挙げて落ちる | +| I7 | 写し | 補完の例外は 1 件ごとに理由を持ち、受け付けの集合に実在する道だけを指す | 一致テストが、集合に無い道を指す例外を挙げて落ちる | +| I8 | 写し | 確定仕様の `[name]` の表は `[name]` の集合に、`--context` の表は `--context` の集合 ∪ {トップレベルの `build`} に等しい | 一致テストが `cli-argument-resolution.md` と欠け・余分を挙げて落ちる | +| I9 | 写し | `docs/` の下で、`cli-argument-resolution.md` の 2 つの表の外に列挙(要求の前提 7 の意味)が無い | 機械では検出しない。設計 PR・実装 PR のレビューと、検証の検索(要求の「検証手段」)で見る | + +### ドメインイベント + +| # | イベント | 発生元 | 受け手 | +| --- | --- | --- | --- | +| E1 | 開発者が argparse のサブコマンド・`name` の位置引数・`--context` を足した(外した) | 開発者 | 一致テスト(次の実行で走査する) | +| E2 | 一致テストが `_create_parser()` を走査して 2 つの集合を得た | 一致テストの走査 | ラッパー・補完・表の比べ方 | +| E3 | 一致テストがラッパーの 2 つのリストを読んで比べた | ラッパーの比べ方 | 失敗の出力(E6) | +| E4 | 一致テストが bash の補完を実行し、zsh の補完を読んで比べた | 補完の比べ方 | 失敗の出力(E6) | +| E5 | 一致テストが確定仕様の 2 つの表を読んで比べた | 表の比べ方 | 失敗の出力(E6) | +| E6 | 一致テストが、写しの場所ごとに欠け・余分を挙げて落ちた | E3〜E5 の比べ方 | 開発者 | +| E7 | 開発者が写しを直し、一致テストが通った | 開発者 | CI | + +### 用語 + +| 用語 | 意味 | 用語集への反映 | +| --- | --- | --- | +| 列挙の正本 | `[name]` と `--context` を受け付けるサブコマンドの集合を決める唯一の場所。argparse の `_create_parser()`(トップレベルの `build` の `--context` だけはラッパー) | 無し(要求で追加済み) | +| 一致テスト | 列挙の正本を走査して得た集合と写しを比べ、差があれば写しの場所と差を挙げて落ちるテスト | 無し(要求で追加済み) | +| 写し | 列挙の正本から手で写した集合(ラッパーの 2 つのリスト・bash と zsh の補完・確定仕様の 2 つの表) | 追加(`cli`) | +| 補完の例外 | 列挙の正本では `[name]` を取るが、補完がプロジェクト名を出さないと決めたコマンドの道と理由の組。一致テストが一覧として持つ | 追加(`cli`) | + +## 機能一覧 + +| # | 機能 | 誰が使うか | +| --- | --- | --- | +| F1 | サブコマンドを足し引きしたとき、取り残した写しの場所とサブコマンドの名前を CI と手元のテストが挙げる | サブコマンドを足す開発者 | +| F2 | `[name]` と `--context` を受け付けるサブコマンドを、確定仕様の 1 組の表で引ける | 文書を読む利用者・開発者 | +| F3 | ほかの文書から、列挙の代わりにリンクで表へ辿れる | 文書を読む利用者 | + +## 構成要素 + +| 要素 | 責務 | +| --- | --- | +| 一致テスト `tests/cli/test_name_context_consistency.py`(新設) | `_create_parser()` を走査して 2 つの集合を得て、写しの 4 種と比べる。写しの場所ごとに別のテストにし、差を場所と名前で出す | +| 走査(一致テストの中の関数) | parser の木を辿り、位置引数 `name` を持つ道と `--context` を持つ道を集める。`[name]` は除外のグループを外す。`--context` は名指しの追加(トップレベルの `build`)を足す | +| 補完の例外の一覧(一致テストの中の定数) | `project profile up` / `down` / `list` の 3 つと理由。補完の比べ方だけが使う | +| 確定仕様の表 `docs/specifications/cli-argument-resolution.md`(変更) | `## 仕様` に「プロジェクト名を受け付けるサブコマンド」と「docker context を受け付けるサブコマンド」の 2 節と表を足す。概要の表と「運用」の箇条を表と一致テストを指す形に直す | +| ほかの文書(変更) | 列挙を表へのリンクに置き換える(下の「文書の置き換え」) | +| `bin/devbase` / `lib/devbase/cli.py` のコメント(変更) | 「同期注意」を一致テストのファイルを指す形に直す。リストの値と parser の定義は変えない | +| `tests/cli/test_rebuild.py`(変更) | `test_wrapper_rebuild_in_name_resolvable` を消す(決定 7) | + +構成要素図(読む向きの関係だけを描く。一致テストはどれも書き換えない): + +```mermaid +graph LR + subgraph 正本 + P[cli.py
_create_parser] + S[cli.py
SHORTCUTS] + B[bin/devbase
build の分岐] + end + subgraph 写し + W[bin/devbase
2 つのリスト] + CB[etc/
devbase-completion.bash] + CZ[etc/_devbase] + T[cli-argument-resolution.md
2 つの表] + end + subgraph 一致テスト + R[走査] + X[補完の例外] + C[比べ方
写しの場所ごと] + end + D[ほかの文書] -->|リンク| T + R --> P + C --> R + C --> S + C -.名指し.-> B + C --> X + C --> W + C --> CB + C --> CZ + C --> T +``` + +システムの文脈と配置: 一致テストは pytest の 1 ファイルとして、手元(macOS)と CI(ubuntu-latest)で動く。外へ出るのは bash の子プロセス(補完の実行)だけで、docker・実の `DEVBASE_ROOT`・ネットワークに触らない。 + +```mermaid +graph LR + subgraph 手元か CI のランナー + PY[pytest
一致テスト] --> BA[bash 子プロセス
補完を source] + BA --> TMP[一時ディレクトリの
projects/] + PY --> SRC[リポジトリの
ファイル] + end +``` + +### パッケージ・モジュール構成 + +```text +tests/cli/ +├── test_name_context_consistency.py # 新設: 一致テスト +├── test_completion.py # 変更なし(_bash_complete の形を参考にする) +└── test_rebuild.py # 変更: test_wrapper_rebuild_in_name_resolvable を消す +docs/specifications/cli-argument-resolution.md # 2 節と表を足す・概要と運用を直す +docs/specifications/remote-docker-context.md # 列挙をリンクへ +docs/specifications/compose-profiles.md # 列挙と写しをリンクへ +docs/user/cli-reference/02-project.md # 列挙と写しをリンクへ +docs/user/container-operations.md # 列挙をリンクへ +docs/user/environment-variables.md # 列挙をリンクへ +bin/devbase # コメントだけ +lib/devbase/cli.py # コメントだけ +``` + +### 確定仕様の表の形 + +一致テストが読む約束である。2 つの節は `## 仕様` の下に並べ、見出しの文字列で探す。 + +| 項目 | 形 | +| --- | --- | +| 見出し | `### プロジェクト名を受け付けるサブコマンド` と `### docker context を受け付けるサブコマンド`(リンクの錨は `#プロジェクト名を受け付けるサブコマンド` と `#docker-context-を受け付けるサブコマンド`) | +| 読む表 | 見出しの後で最初に現れる表。次の見出しまでに表が無ければ読めなかったとして落ちる | +| 1 列目 | 入口の形をバッククォートで書く(`devbase project `・`devbase project profile `・`devbase `)。別名は同じセルに並べる(`devbase container ` / `devbase ct `)。`devbase` と `` の間の語の並びが道の頭になる | +| 2 列目 | 受け付けるサブコマンドをバッククォートで 1 つずつ書く。道は「1 列目の頭 + この語」 | +| 3 列目以降 | 読み手への注記(ラッパーが受け付けるトップレベルの `build` など)。一致テストは読まない | +| 受け付けるものが無い入口 | 行を置かない(`[name]` の表の `container` の行)。集合の全体を比べるため、行を置いても置かなくても差は出る | + +`[name]` の表は 3 行(`project`・`project profile`・トップレベル)、`--context` の表は 6 行(`project`・`project profile`・`container` / `ct`・`container profile` / `ct profile`・トップレベル・`env`)になる。トップレベルの `build` は `--context` の表のトップレベルの行の 2 列目に置き、3 列目に「ラッパーの `cmd_build` が受け付ける(argparse に parser が無い)」と書く。 + +### 文書の置き換え + +置き換える列挙(要求の AC11)と、置き換えた後の形。リンク先はどれも上の 2 つの錨のどちらかである。 + +| 文書と箇所 | 置き換えた後 | +| --- | --- | +| `remote-docker-context.md` の「context の解決」の 2 段落目 | 受け付けるサブコマンドは `cli-argument-resolution.md` の docker context の表にある、の一文。空文字を終了コード 2 で拒む文は残す | +| `environment-variables.md` の `DEVBASE_DOCKER_CONTEXT` の行 | 「`--context` を受け付けるコマンド(表へのリンク)が向ける docker context」 | +| `02-project.md` の「プロジェクト名指定」の冒頭の文 | 「`[name]` を取るサブコマンドは表(リンク)にあります」。例のコードブロックは残す | +| `02-project.md` の `profile` の注記の `_PROJECT_NAME_SUBCOMMANDS` の括弧書き | 括弧の列挙を消す。`profile` が入らない理由の文は残す | +| `02-project.md` の「`--context NAME`(共通オプション)」の冒頭の文 | 「受け付けるサブコマンドは表(リンク)にあります」 | +| `container-operations.md` の冒頭の注記 | `[name]` の列挙を表へのリンクに置き換え、`profile` の解決の違いの文はリンクと 1 文で済ませる | +| `compose-profiles.md` の「どれも `--context NAME` を受け付ける」 | 表へのリンク。`remote-docker-context.md` へのリンクは残す | +| `compose-profiles.md` の `_PROJECT_NAME_SUBCOMMANDS` の括弧書き | 括弧の列挙を消し、`profile` を入れない理由は残す | +| `cli-argument-resolution.md` の概要の表の「解決するもの」の 2 セル | ショートカットの行は「[name] の表のトップレベルの行に `login` と `build` を足したもの」、`project` の行は「[name] の表の `project` の行」とし、それぞれリンクにする(決定 6) | + +使い方の行(`devbase project up [name] [--context NAME]`)・ショートカットの対応表・`cli-reference/README.md` の地図と一覧は、要求の前提 7 のとおり残す。`post-start.md` と `editor-open.md` の構成要素の表が `_PROJECT_NAME_SUBCOMMANDS` の名前を挙げる箇所は、集合を写していないため残す。 + +## 構造 + +一致テストが内部で使う型だけを描く。parser の木(`argparse.ArgumentParser`)は既存の型として名前だけを置く。 + +```mermaid +classDiagram + class コマンドの道 { + +語の並び tuple~str~ + } + class 写しの差 { + +場所 str + +欠け set~コマンドの道~ + +余分 set~コマンドの道~ + +文面() str + } + class 補完の例外 { + +道 コマンドの道 + +理由 str + } + ArgumentParser "1" --> "*" コマンドの道 : 走査が集める + 写しの差 "1" --> "*" コマンドの道 + 補完の例外 "1" --> "1" コマンドの道 +``` + +- コマンドの道は語の並びのタプルで足りる。型を新しく作るかは実装で決める +- 写しの差は比べ方が 1 つの写しについて作り、欠けも余分も空でなければテストを落とす。文面は「場所: 欠けている <道…> / 余分な <道…>」の形で、場所にはファイルのパスと箇所(`_PROJECT_NAME_SUBCOMMANDS`・`[name]` の表など)を入れる +- 補完の例外は道ごとに理由の文字列を持つ。理由の無い例外は書けない形にする(辞書の値を理由にする) + +## 処理の流れ + +```mermaid +graph TD + A[pytest の実行] --> B[走査: parser の木を辿る] + B --> C{集合は I1 を満たすか} + C -->|満たさない| F1[走査の健全性のテストが落ちる] + C -->|満たす| D[比べ方を写しの場所ごとに実行
補完は補完の例外を引く] + D --> W[ラッパー: bin/devbase の 2 行を読む] + D --> BB[bash: 一時の projects/ で補完を実行] + D --> Z[zsh: case の腕を読む] + D --> T[表: 見出しの後の表を読む] + W --> R{差があるか} + BB --> R + Z --> R + T --> R + R -->|ある| F2[場所と欠け・余分を挙げて落ちる] + R -->|無い| OK[通る] +``` + +図はテストの実行時に動くものだけを描く。ほかの文書・コメント・`test_rebuild.py` の変更は実行時に動かないため図に現れない。 + +比べ方は互いの順序を仮定しない。走査はモジュールの読み込みか fixture で 1 回だけ行い、比べ方の各テストが共有する。走査の健全性(I1)が落ちても、比べ方の各テストは結果を出す(空の集合と比べて差を出す)。 + +写しを読めないとき(リストの行が無い・見出しが無い・`case` の腕が無い・bash が終了コード 0 以外を返した)は、差ではなく「読めなかった」として、場所と読もうとしたものを挙げて落ちる。 + +## 非機能の実現方式 + +| 大項目 | 条件 | 実現方式 | +| --- | --- | --- | +| 性能・拡張性 | `tests/cli` の実行時間が 10 秒を超えて延びない | bash の子プロセスは、名前の位置の確かめ(`project` 直下 8・トップレベル 6)とサブコマンドの一覧(2)で 16 回。1 回は数十ミリ秒で、合わせて 1 秒に届かない。走査は 1 回だけ行い共有する | +| 運用・保守性 | 失敗の出力だけで直す場所と名前が分かる | 写しの場所ごとに別のテストにし(決定 1)、写しの差の文面にファイル・箇所・道を入れる | +| システム環境 | CI と macOS で通り、zsh が無くても落ちない | zsh は起動せず、ファイルの `case` の腕を読む。bash の補完は既存の `test_completion.py` と同じく bash 3.2 でも動く形で source する。一時ディレクトリの `projects/` を `DEVBASE_ROOT` に渡し、実の `DEVBASE_ROOT` を継がない | + +## 決定の記録 + +### 決定 1: 取り残した写しを 1 回の実行で全部挙げるため、写しの場所ごとに別のテストにする + +1 つのテストで全部の写しを順に `assert` すると、最初の差で止まり、2 つ目以降の取り残しが直した後の実行まで見えない。場所ごとのテスト(`pytest.mark.parametrize` でも関数を分けてもよい)にすれば、サブコマンドを 1 つ足したときに取り残した写しがすべて一度に並ぶ(AC5)。 + +差をまとめて 1 つの失敗の文面にする形も採らない。どの写しが落ちたかが pytest のテスト名から読めなくなる。 + +根拠: Value 1(MVV 版 1) + +### 決定 2: 写しを 5 か所目に増やさないため、期待の集合をテストに書き写さず走査で得る + +一致テストが要求の前提 1 の表を定数として持つと、それ自体が新しい写しになり、サブコマンドを足すたびにテストも直す場所に加わる。走査で得た集合を正とし、走査が壊れていないことは I1 の名指しの確かめ(空でない・入れ子の `profile` と `env exec` を含む・`plugin` などの `name` を含まない)だけで見る。前提 1 の表との一致(AC1・AC2)は、確定仕様の表との一致(I8)を通して成り立つ。表は人が書く写しで、テストが走査と比べるためである。 + +前提 1 の表をテストの定数にする形は、走査の誤りを直接捕まえられるが、上の理由で採らない。 + +根拠: Value 1(MVV 版 1) + +### 決定 3: 新しいグループの `name` が黙って集合から外れないよう、`[name]` の集合は除外のグループで絞る + +`[name]` の集合は、parser の木で位置引数 `name` を持つ道から、頭が `plugin` / `pl` / `snapshot` / `ss` / `env` の道を除いて作る。除外の一覧は理由(プラグイン・スナップショット・backend の名前)と並べて持つ。将来 `name` を持つグループが増えると集合に入り、表と比べて差が出るため、開発者が含めるか除くかを決める契機になる。 + +含める側の一覧(`project` とトップレベルだけを見る)は、`name` を持つ新しいグループが比べる対象から黙って外れるため採らない。 + +根拠: Value 1(MVV 版 1) + +### 決定 4: 確定仕様の表を人のリンク先と機械の読み口に兼ねるため、`## 仕様` に 2 節を足して見出しの文字列で探す + +`[name]` の表と `--context` の表は別の見出しの節に置き、隣り合わせる。見出しがリンクの錨にもなり、ほかの文書からどちらの表へも直接辿れる(AC12)。一致テストは見出しの文字列で節を探し、その後で最初の表を読む。 + +表の手前に HTML コメントの印を置いて探す形は、読み手に見えない印を誰かが消しても気づきにくく、見出しと印の 2 つを保つことになるため採らない。既存の概要の表に列を足す形は、概要の表がラッパーの name 解決(`login` / `build` を含む)を表しており argparse の集合と食い違うため採らない。 + +根拠: 根拠なし(MVV 版 1) + +### 決定 5: 読み手が集合を見渡せるよう、表は 1 行に 1 つの入口を置きサブコマンドを並べる + +1 列目に入口の形(`devbase project ` など)、2 列目に受け付けるサブコマンドを並べる。`--context` の表は 6 行で収まり、入口ごとの違い(`container` に `post-start` が無いなど)が並べて読める。別名(`container` / `ct`)は同じセルに並べ、一致テストは両方の道へ展開する。 + +1 行に 1 つのコマンドの道を置く形は機械には読みやすいが、`--context` の表が 40 行を超え、入口ごとの違いが読めなくなるため採らない。 + +根拠: 根拠なし(MVV 版 1) + +### 決定 6: 確定仕様の中にテストで縛らない列挙を残さないため、概要の表の「解決するもの」をリンクと規則に置き換える + +概要の表の 2 つのセルは、ラッパーの 2 つのリストを写している。一致テストはラッパーのリストを規則(要求の前提 4)で縛るため、概要の表は「`[name]` の表のトップレベルの行に `login` と `build` を足したもの」のように、表へのリンクと規則で書けば写しを持たずに済む。 + +概要の表も一致テストで読む形は、比べる写しが 1 つ増え、表の形の約束も 2 つになるため採らない。 + +根拠: 根拠なし(MVV 版 1) + +### 決定 7: 同じことを 2 か所で縛らないため、`test_wrapper_rebuild_in_name_resolvable` を消す + +このテストは `rebuild` が `bin/devbase` の 2 つのリストにあることだけを見る。一致テストは 2 つのリストが argparse と `SHORTCUTS` から導いた集合に等しいことを見ており、`rebuild` を含むことはその一部である。残すと、`rebuild` を外すときに 2 つのテストを直すことになる。 + +同じファイルの `build)` の分岐を見るテストは、一致テストが見ない振る舞い(`cmd_build` への委譲)を縛るため残す。 + +根拠: 根拠なし(MVV 版 1) + +### 決定 8: 振る舞いの確かめを既存のテストに任せるため、トップレベルの `build` の `--context` は名指しの一覧で足す + +argparse にトップレベルの `build` の parser は無く、`--context` はラッパーの `build` の分岐が抜き取る。一致テストは `--context` の集合にトップレベルの `build` を名指しで足し、足す理由を一覧に添える。受け付けること自体は既存の `tests/cli/test_wrapper_build_context.py` がラッパーを実行して確かめている。 + +`bin/devbase` の `build` の分岐を文字列で探して `--context` の有無を見る形は、コメントや使い方の文に `--context` があるだけで通り、走査とは別の確かめ方になるため採らない。 + +根拠: 根拠なし(MVV 版 1) + +### 決定 9: 別の分岐の記述で通ってしまわないよう、zsh の補完は `case` の腕ごとに読む + +`etc/_devbase` の全体で `_devbase_project_names` を探すと、ほかのサブコマンドの腕にあるだけで通る。一致テストは、トップレベルの `case "$words[2]"` の腕と、`project)` の腕の中の `case "$words[3]"` の腕を、パターン(`up|down|rebuild)` のような `|` 区切り)と本体(次の `;;` まで)の組として読み、サブコマンドの腕の本体に `_devbase_project_names` があるかを見る。 + +zsh を起動して補完を実行する形は、要求の前提 8 のとおり CI のランナーに zsh があることを前提にするため採らない。 + +根拠: 根拠なし(MVV 版 1) + +### 決定 10: 例外が黙って広がらないよう、補完の例外は理由を持ち、実在する道だけを指す + +補完の例外は `project profile up` / `down` / `list` の 3 つで、理由は「1 つ目の位置引数が値の数でプロファイル名にもプロジェクト名にもなり、補完の時点では決まらない」。一致テストは例外の道が `[name]` の集合に実在することも確かめる。`profile` の `[name]` が消えたのに例外が残ると、次に別の理由で同じ道を外したいときに古い理由のまま通るためである。 + +例外を持たず、補完の比べ方の対象を `project` の直下とトップレベルに限る形は、例外の理由がテストから読めず、どれを外したかが走査の条件に埋もれるため採らない。 + +根拠: 根拠なし(MVV 版 1) + +## テスト設計 + +| 受け入れ条件・不変条件 | どの振る舞いで縛るか | どう壊したら落ちるべきか | +| --- | --- | --- | +| AC1・I1 | 走査の `[name]` の集合が空でなく、`project profile up` を含み、`plugin info`・`snapshot restore`・`env backend use` を含まない。前提 1 の表との一致は AC10 の表を通して成り立つ | 走査が入れ子の parser を辿らないようにすると落ちる。除外のグループから `plugin` を外すと落ちる | +| AC2・I1 | 走査の `--context` の集合が空でなく、`env exec` と `project profile list` を含み、名指しの追加でトップレベルの `build` を含む | 走査を `project` / `container` の配下だけにすると落ちる。名指しの追加を消すと AC10 の `--context` の表との比べ方で落ちる | +| AC3・I2 | `bin/devbase` の `_PROJECT_NAME_SUBCOMMANDS` が「`project` の直下で `[name]` を取る parser」の名前の集合に等しい | 作業用の写しで `post-start` を消すと落ち、`login` を足しても落ちる。出力に `bin/devbase` と道が出る | +| AC4・I3 | `_NAME_RESOLVABLE_SHORTCUTS` が `cli.SHORTCUTS` のキー ∪ {`build`} に等しい | `open` を消すと落ち、`logs` を足しても落ちる | +| AC5 | `project` の直下に `[name]` を取るサブコマンドを足した parser では、ラッパー・bash・zsh・表の比べ方がそれぞれ落ち、出力に足した名前とファイルが出る | 作業用の写しで `cli.py` に `[name]` 付きのサブコマンドを 1 つ足し、写しを直さずに実行して 4 つ以上の失敗が並ぶことを確かめる(記録は PR 本文) | +| AC6・I4 | 一時の `projects/` に置いたプロジェクト名が、`project` の直下 8 つとトップレベル 6 つの名前の位置で bash の候補に出る | `etc/devbase-completion.bash` の 1 つの腕(例: トップレベルの `scale`)からプロジェクト名の補完を外すと落ちる | +| AC7・I5 | `etc/_devbase` で、同じサブコマンドの `case` の腕の本体が `_devbase_project_names` を持つ | 1 つの腕(例: `project` の `logs`)から外すと落ちる。別の腕にだけ残っていても落ちる | +| AC8・I6 | `project` / `container` の直下で `[name]` か `--context` を取るサブコマンドが、bash の補完が出すサブコマンドの一覧と zsh の `project_subcommands` / `container_subcommands` に含まれる | どちらかの一覧から `post-start`(`project`)か `open`(`container`)を消すと落ちる。`migrate-config` が無いことでは落ちない | +| AC9・I7 | 補完の例外の各道が空でない理由を持ち、`[name]` の集合に実在する。補完の比べ方は `[name]` の集合から例外を引いたすべての道を見るため、例外に無い道を補完が欠くと AC6・AC7 の比べ方で落ちる | 例外に集合に無い道(`project profile stop`)を足すと落ちる。理由を空にすると落ちる。例外から `project profile up` を外すと、補完がその位置でプロジェクト名を出さないため AC6・AC7 の比べ方で落ちる | +| AC10・I8 | 確定仕様の 2 つの表を読んだ集合が、`[name]` の集合と `--context` の集合(トップレベルの `build` を含む)に等しい | 表から 1 つ消すと落ち、余分に足しても落ちる。見出しを変えると「読めなかった」で落ちる | +| AC11・I9 | テストを書かない。検証で `docs/` を `post-start`・`rebuild`・`open`・`--context` で検索し、2 つの表の外に列挙が無いことを確かめる | — | +| AC12 | テストを書かない。置き換えたリンクの錨が、表の見出しから作られる錨と一致することを検証で確かめる | — | +| AC13 | テストを書かない。`bin/devbase`・`cli.py` の「同期注意」と「運用」の箇条が一致テストのファイル名を含むことを差分のレビューで見る | — | +| AC14 | 全体の pytest が通る。`git diff main -- lib/devbase/cli.py bin/devbase etc/` の差がコメントの行だけである | — | +| AC15 | 一致テストは補完に一時ディレクトリを `DEVBASE_ROOT` として渡し、docker を呼ぶ関数を import 以外で呼ばない | 既存の `tests/cli/conftest.py` の隔離のもとで動く。実の `DEVBASE_ROOT` を渡すように壊すと、一時の `projects/` の名前が候補に出ず落ちる | + +補完の例外を足すことは、一致テストのファイルの差分として現れ、レビューで人が見る。一致テストは例外の一覧を別の写しと比べない(一覧そのものが、補完がプロジェクト名を出さないと決めた記録であるため)。 + +## 設計の結果 + +| 課題 | 扱い | 取り込み先 | 触るファイル | +| --- | --- | --- | --- | +| #214 | 実装する | — | `tests/cli/test_name_context_consistency.py`、`tests/cli/test_rebuild.py`、`docs/specifications/cli-argument-resolution.md`、`docs/specifications/remote-docker-context.md`、`docs/specifications/compose-profiles.md`、`docs/user/cli-reference/02-project.md`、`docs/user/container-operations.md`、`docs/user/environment-variables.md`、`bin/devbase`、`lib/devbase/cli.py` | + +## 未確認のまま残ること + +| 項目 | 内容 | +| --- | --- | +| zsh の `case` の腕の読み取りの頑健さ | 腕の本体が `;;` を含む入れ子の `case` を持つ形(`plugin` の `repo`)は、`project` とトップレベルの腕には今は無い。読み取りが入れ子に対応するかは実装(`tdd-cycle`)で走らせて決める。対応しない場合も、読めなかったときは落ちる | +| 日本語の見出しの錨 | GitHub が `### docker context を受け付けるサブコマンド` から作る錨を `#docker-context-を受け付けるサブコマンド` と見込んでいる。実装の PR の表示で辿れることを確かめる(AC12) | +| 補完の `--context` の範囲 | 要求の「未決」のとおり、`open` / `post-start` 以外で `--context` を補完するかは課題の棚卸しで決める。一致テストは今の範囲を縛らない | From 11c9cfc9bc49221da909824ebfce3b7f2597a608 Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Mon, 5 Oct 2026 13:17:01 +0900 Subject: [PATCH 3/3] =?UTF-8?q?docs(design):=20I1=20=E3=82=92=E9=9B=86?= =?UTF-8?q?=E5=90=88=E3=81=94=E3=81=A8=E3=81=AE=E6=9D=A1=E4=BB=B6=E3=81=AB?= =?UTF-8?q?=E5=88=86=E3=81=91=E3=80=81env=20exec=20=E3=81=AF=20--context?= =?UTF-8?q?=20=E3=81=AE=E9=9B=86=E5=90=88=E3=81=A0=E3=81=91=E3=81=AB?= =?UTF-8?q?=E8=A6=81=E6=B1=82=E3=81=99=E3=82=8B=EF=BC=88#214=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- issues/issue-214-design.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/issues/issue-214-design.md b/issues/issue-214-design.md index 848206c0..d71d2c2d 100644 --- a/issues/issue-214-design.md +++ b/issues/issue-214-design.md @@ -61,7 +61,7 @@ | # | 集約 | 条件 | 破れたときの扱い | | --- | --- | --- | --- | -| I1 | 受け付けの集合 | 走査で得る `[name]` の集合と `--context` の集合は空でなく、入れ子の `project profile up` と `env exec` を含み、`plugin info` / `snapshot restore` / `env backend use` を含まない | 一致テストが「走査が集合を取れていない」として落ちる(ほかの比べ方は結果を出す) | +| I1 | 受け付けの集合 | 走査で得る `[name]` の集合は空でなく、入れ子の `project profile up` を含み、`plugin info` / `snapshot restore` / `env backend use` を含まない。`--context` の集合は空でなく、`env exec` と入れ子の `project profile list` を含み、名指しの追加でトップレベルの `build` を含む | 一致テストが「走査が集合を取れていない」として落ちる(ほかの比べ方は結果を出す) | | I2 | 写し | `_PROJECT_NAME_SUBCOMMANDS` は「`project` の直下で `[name]` を取る parser」の名前の集合に等しい | 一致テストが `bin/devbase` と欠け・余分を挙げて落ちる | | I3 | 写し | `_NAME_RESOLVABLE_SHORTCUTS` は「`cli.SHORTCUTS` のキー ∪ {`build`}」に等しい | 同上 | | I4 | 写し | bash の補完は、`[name]` の集合から補完の例外を引いたすべての道(今は `project` の直下 8 つとトップレベル 6 つ)の名前の位置で、`projects/` のプロジェクト名を候補に出す | 一致テストが `etc/devbase-completion.bash` と、名前を補完しない道を挙げて落ちる | @@ -279,7 +279,7 @@ graph TD ### 決定 2: 写しを 5 か所目に増やさないため、期待の集合をテストに書き写さず走査で得る -一致テストが要求の前提 1 の表を定数として持つと、それ自体が新しい写しになり、サブコマンドを足すたびにテストも直す場所に加わる。走査で得た集合を正とし、走査が壊れていないことは I1 の名指しの確かめ(空でない・入れ子の `profile` と `env exec` を含む・`plugin` などの `name` を含まない)だけで見る。前提 1 の表との一致(AC1・AC2)は、確定仕様の表との一致(I8)を通して成り立つ。表は人が書く写しで、テストが走査と比べるためである。 +一致テストが要求の前提 1 の表を定数として持つと、それ自体が新しい写しになり、サブコマンドを足すたびにテストも直す場所に加わる。走査で得た集合を正とし、走査が壊れていないことは I1 の名指しの確かめ(どちらの集合も空でない・`[name]` は入れ子の `profile` を含み `plugin` などの `name` を含まない・`--context` は `env exec` を含む)だけで見る。前提 1 の表との一致(AC1・AC2)は、確定仕様の表との一致(I8)を通して成り立つ。表は人が書く写しで、テストが走査と比べるためである。 前提 1 の表をテストの定数にする形は、走査の誤りを直接捕まえられるが、上の理由で採らない。