From ea743dc2aa8f9cdd9201ca14336cdb8eafc3db05 Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Thu, 8 Oct 2026 23:38:54 +0900 Subject: [PATCH 1/3] =?UTF-8?q?Docs:=20cli-argument-resolution.md=20?= =?UTF-8?q?=E3=82=92=E7=A2=BA=E5=AE=9A=E4=BB=95=E6=A7=98=E3=81=AB=E3=81=99?= =?UTF-8?q?=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 (1M context) --- .../specifications/cli-argument-resolution.md | 59 +++- issues/issue-435-design.md | 268 ------------------ issues/issue-435-requirements.md | 134 --------- 3 files changed, 58 insertions(+), 403 deletions(-) delete mode 100644 issues/issue-435-design.md delete mode 100644 issues/issue-435-requirements.md diff --git a/docs/specifications/cli-argument-resolution.md b/docs/specifications/cli-argument-resolution.md index 92bf9bcf..88e033b9 100644 --- a/docs/specifications/cli-argument-resolution.md +++ b/docs/specifications/cli-argument-resolution.md @@ -138,6 +138,30 @@ graph TD `--context` の指定の形で確かめ、bash は `tests/cli/test_completion.py` が `--context` の直後の候補が 0 件であることを代表の道で確かめる。 +#### 補完が `--context` を出す位置 + +| 位置 | 例 | bash | zsh | +| --- | --- | --- | --- | +| (a) 道の直後 | `devbase up -`・`devbase env exec -`・`devbase ct profile up -` | `--context` の集合のすべての道で出す | `--context` の集合のすべての道の分岐で出す | +| (b) プロジェクト名の直後 | `devbase up web -`・`devbase project open web -` | `[name]` の集合から補完の例外を引いた道で出す | `_arguments` が位置を問わず出す | +| (c) `--context` の直後(値) | `devbase up --context ` | 候補は 0 件 | 説明だけを出し、候補は 0 件 | +| (a)・(b) より後ろ | `devbase scale web 3 -`・`devbase env exec -- ls -` | 出すことを求めない | 出し得る(argparse では、`env exec` のコマンドの後の `--context` はそのコマンドへ渡る) | + +bash(`etc/devbase-completion.bash`)は、道ごとの分岐が `_devbase_complete_flags <旗の候補> "$cur" [names | 語の並び]` +を呼ぶ。補完する語が `-` で始まれば旗の候補(`ctx_flags`・`ps_flags`・`logs_flags`・`open_flags`。どれも +`--context` を含む)を `compgen -W` で補完する語に前方一致するものへ絞って出し(`devbase ps --a` は `--all` だけ)、 +始まらなければ `names` でプロジェクト名を、語の並びならその語(`login` の `1 2` など)を出す。`-` で始まらない語の +候補は旗の有無に依らない。直前の語が `--context` なら、どの分岐にも入る前に候補を空にして終える。 +連想配列を使わず、macOS の bash 3.2 で動く。 + +zsh(`etc/_devbase`)は、サブコマンドの分岐から `_arguments` を直接呼ばず、`_devbase_arguments <深さ>` を通す。 +`_arguments` は `words[1]` だけをコマンド名として飛ばすため、そのまま呼ぶと `devbase up ` の `up` が 1 番目の +引数に数えられ、`'1:name:_devbase_project_names'` が利用者の引数を指さない。`_devbase_arguments` は `words` と +`CURRENT` をサブコマンドの語の位置(`devbase up` は 2、`devbase project up` は 3)まで詰めてから `_arguments` を +呼ぶ。`--context` の指定は値の補完を持たない `'--context[Docker context]:context:'` で、`--context` の集合の +すべての道に分岐を置く(トップレベルの `build`・`container up|down|rebuild`・`env token`・入れ子の +`profile up|down|list` も `*)` に落とさない)。 + ### 名前の形 | 項目 | 内容 | @@ -572,10 +596,21 @@ Python 側の `_resolve_project_name` は同じ結果になるよう、`chdir` が決まるので、実環境の `DEVBASE_ROOT` を継承しない) - macOS の `/bin/bash`(3.2)で `tests/cli` を流すことと `shellcheck --severity=error bin/devbase` は手元で行う(CI の bash は Linux 版) +- `--context` の集合のすべての道(`container` の道は `ct` で始めても)で、bash の `devbase <道> -` の候補に + `--context` が出ること。`[name]` の集合から補完の例外を引いた道では `devbase <道> web -` でも出ること。zsh の + 道の分岐の本体に値の補完を持たない `'--context[...]:context:'` があること。bash から道 1 つの `--context` を + 外すと bash のテストが、zsh から外すと zsh のテストが、ファイルと道を挙げて落ちること + (`tests/cli/test_name_context_consistency.py`) +- bash の `-` で始まる語の候補が、旗の候補に `--context` を足した集合であること。`--context` の直後の候補が + 0 件であること(`devbase up --context`・`devbase project up web --context`・`devbase container ps --context`・ + `devbase env exec --context`)。`etc/_devbase` の `_devbase()` の中に `_devbase_arguments` を通らない + `_arguments` の呼び出しが無いこと(`tests/cli/test_completion.py`) +- zsh の補完が実際に出す候補(`devbase up ` のプロジェクト名・`devbase up -` の `--context`・ + `devbase up --context ` の候補なし)は、zsh を起動するテストが無いため手元の zsh で確かめる ## 決定の記録 -`[name]` と `--context` を受け付けるサブコマンドの列挙の正本と一致テスト(#214)の決定である。 +`[name]` と `--context` を受け付けるサブコマンドの列挙の正本と一致テスト(#214)、補完の `--context`(#435)の決定である。 ### 決定 1: 取り残した写しを 1 回の実行で全部挙げるため、写しの場所ごとに別のテストにする @@ -638,6 +673,27 @@ CI のランナーに zsh があることを前提にするため採らない。 例外の道が `[name]` の集合から消えたのに例外が残ると、次に別の理由で同じ道を外したいときに古い理由のまま 通る。例外を足すことは一致テストのファイルの差分として現れ、レビューで人が見る。 +### 決定 11: `--context` は値を補完せず、旗の名前だけを補完で出す + +`--context` を取るサブコマンドを補完から見つけられるようにする一方、値(docker context の名前)は利用者が打つ。 +bash は直前の語が `--context` なら分岐に入る前に候補を空にして終える。後から分岐を足しても、値の位置へ +プロジェクト名などの候補が漏れない。zsh は action を空にした `'--context[...]:context:'` で、値の位置では +説明だけを出す。 + +### 決定 12: bash は「名前の後でも出す道」を `[name]` の集合から補完の例外を引いた道に限る + +`devbase up web -` の `web` がプロジェクト名かは、道が `[name]` を取るかで決まる。`login` の番号・`build` の +イメージ名・`profile` のプロファイル名の後ろでは出すことを求めない。`env exec` の引数は `REMAINDER` で、 +コマンドの後の `--context` はそのコマンドへ渡るため、道の 2 つ後ろまで一律に出す形は誤った位置で候補を出す。 + +### 決定 13: zsh はサブコマンドの位置まで `words` を詰めてから `_arguments` を呼ぶ + +`_arguments` の位置引数の番号は `words[1]` の次から数える。サブコマンドの分岐でそのまま呼ぶと、`'1:name:…'` が +サブコマンドの語自身を指し、プロジェクト名と番号が補完されない(`--context` を足すために分岐を `_arguments` へ +揃えたときに起きた)。分岐ごとに `2:`・`3:` と番号を数える形は、zsh を起動する検査が無く番号の誤りを拾えないため +採らず、深さだけを渡す `_devbase_arguments` の 1 か所で詰める。素の `_arguments` が分岐に残らないことは +`tests/cli/test_completion.py` が見る。 + ## 関連リンク - [CLI リファレンス: project](../user/cli-reference/02-project.md) @@ -647,3 +703,4 @@ CI のランナーに zsh があることを前提にするため採らない。 - 実装 PR: devbasex/devbase#207(#146・#142・#196・#200) - 実装 PR: devbasex/devbase#301(#276・#226・#229・#245・#227) - 実装 PR: devbasex/devbase#428(#214。列挙の正本と一致テスト) +- 実装 PR: devbasex/devbase#450(#435。補完の `--context`) diff --git a/issues/issue-435-design.md b/issues/issue-435-design.md deleted file mode 100644 index f1d65903..00000000 --- a/issues/issue-435-design.md +++ /dev/null @@ -1,268 +0,0 @@ -# シェル補完: `--context` を受け付けるサブコマンドの多くで補完が `--context` を出さない → 35 の道すべてで bash と zsh の補完が `--context` を出し、欠けると一致テストが道を挙げて落ちる(#435) - -## 目的 - -- **何が壊れているか**: シェル補完が `--context` を出すのは `open` と `post-start` だけで、`--context` を受け付けるほかの 31 の道(`up`・`env exec`・`container` の配下など)では出ない。一致テストも補完の `--context` を比べない -- **誰が困るか**: `devbase up web --context gpu-wsl` のようにリモートの docker を使う利用者が、補完から `--context` を見つけられない。`--context` を取るサブコマンドを足した開発者は、補完の写しを取り残しても気づけない -- **直すと何が成り立つか**: `--context` の集合の 35 の道すべてで、bash と zsh の補完が `-` で始まる語に `--context` を出し、値の位置では何も出さない。補完の写しが列挙の正本からずれると、一致テストが写しのファイルと道を挙げて落ちる - -## 適用範囲 - -- **働く範囲**: このリポジトリの補完 2 ファイル(`etc/devbase-completion.bash`・`etc/_devbase`)と一致テスト。補完は `devbase shell-rc` などで利用者のシェルに読み込まれて働く。プラグインのリポジトリには及ばない -- **プロジェクトごとに違うもの**: 無し(補完の候補はプロジェクトに依らない。プロジェクト名は今と同じく `projects/` から読む) -- **当たるモード**: `standard` - -## あるべき姿の根拠 - -| 根拠 | 種類 | 何を示すか | -| --- | --- | --- | -| #435 の本文「`--context` を取るすべての副コマンドで、bash と zsh の補完が `--context` を出すようにする。値(コンテキスト名)の補完はしない」 | 利用者の指示の原文 | 出す範囲(すべての道)と、値を補完しないこと | -| `lib/devbase/cli.py` の `_create_parser()` を走査した `--context` の集合が 35 の道になる(`tests/cli/test_name_context_consistency.py` の `CONTEXT_PATHS` を手元で数えた) | 実測 | 補完が `--context` を出すべき道の数と範囲 | -| zsh のマニュアル(`zshcompsys(1)` の `_arguments`): `optspec:message:action` で action を空にすると、値の位置で説明だけを出し候補を生成しない | 外部の一次情報 | zsh で値を補完しない書き方(`'--context[...]:context:'`)。今の `open` / `post-start` がこの形 | - -要求と受け入れ条件は #435 の本文にある(コピーは `issues/issue-435-requirements.md`)。この文書は「どう作るか」だけを扱う。 - -## ドメインモデル - -### コンテキスト - -| コンテキスト | 何の語が 1 つの意味に決まるか | -| --- | --- | -| コマンドの入口(`cli`) | 列挙の正本・写し・一致テスト・補完の例外。`--context` を受け付けるサブコマンドの道の集合 | - -1 つのコンテキストに収まる。`--context` の値が指す docker の接続先(`docker-context`)は、この変更では補完しないため扱わない。 - -### 集約 - -| 集約 | 持ち主(書き換えてよいもの) | 根 | エンティティ | 値オブジェクト | -| --- | --- | --- | --- | --- | -| 列挙の正本 | `lib/devbase/cli.py`(この変更では書き換えない)。トップレベルの `build` だけは `bin/devbase` | `_create_parser()` の木 | サブコマンドの parser | 道(`("project", "up")` のような語の組)・`--context` の集合(35)・`[name]` の集合(17) | -| bash の補完の写し | `etc/devbase-completion.bash` | `_devbase_completions` | 語の位置(`cword`)ごとの分岐 | `--context` を出す道の一覧(35)・名前の後でも出す道の一覧(14)・旗の候補の集合 | -| zsh の補完の写し | `etc/_devbase` | `_devbase` | 道の分岐(`case "$words[N]"` の見出し) | `--context` の指定(`'--context[Docker context]:context:'`) | -| 一致テストの比べ方 | `tests/cli/test_name_context_consistency.py` | 写しごとのテスト | — | 補完の例外の一覧(3 つ) | - -写しの 2 つの集約は列挙の正本を道で参照するだけで、互いを参照しない。一致テストは 3 つの集約を読むだけで書き換えない。 - -### 不変条件 - -| # | 集約 | 条件 | 破れたときの扱い | -| --- | --- | --- | --- | -| I1 | bash の補完の写し | `--context` の集合のすべての道で、道の直後の位置の `-` で始まる語の候補に `--context` が入る(`container` の道は `ct` で始めても同じ) | 一致テストが「`etc/devbase-completion.bash` が道の直後で `--context` を出さない」と欠けた道を挙げて落ちる | -| I2 | bash の補完の写し | `[name]` の集合から補完の例外を引いた 14 の道で、プロジェクト名の直後の位置の `-` で始まる語の候補に `--context` が入る | 一致テストが「名前の後で `--context` を出さない」と欠けた道を挙げて落ちる | -| I3 | bash の補完の写し | 直前の語が `--context` のとき、候補は 0 件である(コンテキスト名もプロジェクト名も出ない) | `tests/cli/test_completion.py` が出た候補を挙げて落ちる | -| I4 | bash の補完の写し | `--context` の集合に無い道(`list`・`env set`・`project list` など)では、道の直後の `-` で始まる語の候補に `--context` が入らない | 一致テストが「余分」として道を挙げて落ちる | -| I5 | zsh の補完の写し | `--context` の集合のすべての道の分岐の本体に、値の補完を持たない `--context` の指定がある | 一致テストが「`etc/_devbase` の分岐に `--context` の指定が無い」と欠けた道を挙げて落ちる | -| I6 | zsh の補完の写し | `--context` の集合に無い末端の道の分岐の本体に `--context` の指定が無い | 一致テストが「余分」として道を挙げて落ちる | -| I7 | bash の補完の写し | `-` で始まらない語の候補は変わらない。`-` で始まる語の候補は、今の旗の候補に `--context` を足した集合で、ほかの旗は増えない。`--context` は補完する語で始まるときだけ足し、`--a` のように始まらない語の候補は今と変わらない | `tests/cli/test_completion.py` の今の期待値が落ちる | - -### ドメインイベント - -| # | イベント | 発生元 | 受け手 | -| --- | --- | --- | --- | -| E1 | 利用者が `--context` を受け付けるサブコマンドの後で `-` を打って補完を求めた | 利用者のシェル | bash の `_devbase_completions` / zsh の `_devbase` | -| E2 | 補完が `--context` を含む旗の候補を出した | 補完の写し(E1 を受けて) | 利用者のシェル | -| E3 | 利用者が `--context` の後で補完を求め、補完が候補を出さなかった | 補完の写し | 利用者のシェル | -| E4 | 開発者が `lib/devbase/cli.py` で `--context` を取るサブコマンドを足した | 列挙の正本 | 一致テスト(次の pytest の走査) | -| E5 | 一致テストが、補完の `--context` の欠けた道とファイルを挙げて落ちた | 一致テスト | 開発者(手元の pytest か CI) | - -### 用語 - -| 用語 | 意味 | 用語集への反映 | -| --- | --- | --- | -| 列挙の正本 | `[name]` と `--context` を受け付けるサブコマンドの集合を決める唯一の場所 | 変えない | -| 写し | 列挙の正本から手で写した集合。bash と zsh の補完は、この変更から `--context` を出す道の集合も写しとして持つ | 変えない(今の意味の「bash と zsh の補完」に含まれる) | -| 一致テスト | 列挙の正本を走査して得た集合と写しを比べ、差があれば写しの場所と差を挙げて落ちるテスト | 変えない | -| 補完の例外 | 列挙の正本では `[name]` を取るが、補完がプロジェクト名を出さないと決めた道と理由の組 | 変えない | - -新しい語は足さない。「道」は確定仕様(`cli-argument-resolution.md`)と一致テストが使っている語のまま使う。 - -## 機能一覧 - -| # | 機能 | 誰が使うか | -| --- | --- | --- | -| F1 | `--context` を受け付けるサブコマンドの後で `-` を打つと、`--context` が候補に出る(bash・zsh) | `--context` でリモートの docker を使う利用者 | -| F2 | `devbase up web -` のようにプロジェクト名を打った後でも `--context` が候補に出る(bash。zsh は位置を問わず出る) | 同上 | -| F3 | `--context ` では何も候補に出ない(値は利用者が打つ) | 同上 | -| F4 | 補完の `--context` が列挙の正本からずれると、一致テストがファイルと道を挙げて落ちる | devbase の開発者 | - -## 構成要素 - -| 要素 | 責務 | 変え方 | -| --- | --- | --- | -| `etc/devbase-completion.bash` の `_devbase_completions` | 語の位置ごとの候補を出す | 冒頭に「直前の語が `--context` なら候補を空にして終える」段を足す。末尾で `_devbase_offer_context` を呼ぶ。`open_flags` から `--context` を外し、`project post-start` の分岐は `-` で始まる語に旗を入れない(共通の段が足す) | -| `etc/devbase-completion.bash` の `_devbase_offer_context`(新規) | 補完する語が `-` で始まり、カーソルが `--context` を出す位置にあれば、候補に `compgen -W --context -- "$cur"` の結果を足す(`--context` が補完する語で始まるときだけ足す。`devbase ps --a` の候補は `--all` のまま) | 新しく作る。道の一覧 2 つ(下の「入出力の契約」)を持ち、`words[1..3]` から道を組んで引く。`ct` は `container` に畳む | -| `etc/_devbase` の道の分岐 | 道ごとに `_arguments` などで候補を出す | `--context` の集合の各道の分岐に `'--context[Docker context]:context:'` を足す。分岐の無い道(トップレベルの `build`・`container up\|down\|rebuild`・`env token`・入れ子の `profile up\|down\|list`)には分岐を足す | -| `tests/cli/test_name_context_consistency.py` | 列挙の正本と写しを比べる | bash(実行)と zsh(読む)の `--context` を比べるテストを足す(I1・I2・I4・I5・I6)。`_bash_complete` が最後の語を受け取れるようにする | -| `tests/cli/test_completion.py` | 補完の個々の振る舞いを固定する | `-` で始まる語の期待値に `--context` を足す(I7)。`devbase ps --a` → `{"--all"}`・`devbase open --o` → `{"--open-index"}` のテストを足す(I7 の前方一致)。値の位置で候補が 0 件のテストを足す(I3) | -| `docs/specifications/cli-argument-resolution.md` | 確定仕様 | 「一致テストが比べる写し」の表の bash と zsh の行に `--context` の比べ方を足し、「補完が `--context` の値を補完するかどうか(今は `open` と `post-start` だけ)は比べない」の文を新しい比べ方に書き換える | -| `CHANGELOG.md` | 利用者に見える変更の記録 | `[Unreleased]` に補完の変更を書く | - -`lib/devbase/cli.py`・`bin/devbase` は変えない(列挙の正本は読むだけ)。 - -```mermaid -graph LR - subgraph 正本 - P[cli.py の _create_parser] - W[bin/devbase の cmd_build] - end - subgraph 写し - B[bash の補完
_devbase_offer_context] - Z[zsh の補完
道の分岐] - S[確定仕様の表] - end - T[一致テスト] - C[test_completion.py] - T -->|走査| P - T -->|名指しで build| W - T -->|実行して比べる| B - T -->|読んで比べる| Z - T -->|読んで比べる| S - C -->|実行して固定| B -``` - -## 入出力の契約 - -補完の入力はシェルが渡す語の並びで、出力は候補の集合である。CLI の引数(argparse)は変えない。 - -### `--context` を出す位置 - -| 位置 | 例 | bash | zsh | -| --- | --- | --- | --- | -| (a) 道の直後 | `devbase up -`・`devbase env exec -`・`devbase ct profile up -` | 35 の道で出す | 35 の道の分岐で出す | -| (b) プロジェクト名の直後 | `devbase up web -`・`devbase project open web -` | 14 の道で出す | `_arguments` が位置を問わず出す | -| (c) `--context` の直後(値) | `devbase up --context ` | 0 件 | 説明だけを出し、候補は 0 件 | -| (a)・(b) より後ろ | `devbase scale web 3 -`・`devbase env exec -- ls -` | 出すことを求めない | 出してよい | - -bash は補完する語が `-` で始まるときだけ `--context` を足す。`-` で始まらない語の候補は、今の分岐が出すもの(プロジェクト名・`1 2`・`up down list` など)のまま変わらない。 - -### bash の 2 つの道の一覧 - -道は語を `/` で繋いで書き、空白で区切る。`ct` は引く前に `container` に直す。bash 3.2 で動くよう、連想配列を使わず文字列の照合(`[[ " $list " == *" $path "* ]]`)で引く。 - -| 一覧 | 中身 | 数 | -| --- | --- | --- | -| `--context` を出す道 | トップレベル: `build` `down` `login` `open` `ps` `rebuild` `scale` `up`。`project/` の下: `build` `down` `login` `logs` `open` `post-start` `profile/up` `profile/down` `profile/list` `ps` `rebuild` `scale` `up`。`container/` の下: `build` `down` `login` `logs` `open` `profile/up` `profile/down` `profile/list` `ps` `rebuild` `scale` `up`。`env/exec` `env/token` | 35(8 + 13 + 12 + 2) | -| 名前の後でも出す道 | トップレベル: `down` `open` `ps` `rebuild` `scale` `up`。`project/` の下: `down` `logs` `open` `post-start` `ps` `rebuild` `scale` `up` | 14(6 + 8) | - -2 つ目の一覧は `[name]` の集合(17)から補完の例外(`project/profile/up|down|list`)を引いたものに等しい。 - -### zsh の分岐ごとの変え方 - -| 道 | 今 | 変えた後 | -| --- | --- | --- | -| `up\|down\|rebuild`・`scale`(トップレベル)、`project` の `up\|down\|rebuild`・`scale` | `_devbase_project_names` を直接呼ぶ | `_arguments '--context[Docker context]:context:' '*:name:_devbase_project_names'` | -| `login`(トップレベル・`project`・`container`) | `_values 'index' 1 2` | `_arguments '--context[Docker context]:context:' '*:index:(1 2)'` | -| `container` の `scale` | `_values 'new_scale' 1 2 3 4 5` | `_arguments '--context[Docker context]:context:' '*:new_scale:(1 2 3 4 5)'` | -| `ps`(トップレベルの旗の側・`project`・`container`)、`logs`(`project`・`container`)、`build`(`project`・`container`)、`env exec` | `_arguments` で旗や位置引数を出す | 今の指定に `'--context[Docker context]:context:'` を足す | -| `open`(3 か所)・`project post-start` | `--context` の指定がある | 変えない | -| トップレベルの `build`・`container` の `up\|down\|rebuild`・`env token` | 分岐が無い(`*)` に落ちてサブコマンドの一覧を出す) | 分岐を足し、`_arguments '--context[Docker context]:context:'`(トップレベルの `build` は `'*:image:'` も) | -| `project` と `container` の `profile` | `_values 'operation' up down list` | 入れ子の `case "$words[4]"` を足す。`up\|down\|list)` は `_arguments '--context[Docker context]:context:'`、`*)` は今の `_values` | - -字下げは今の書き方(分岐の見出しは `case` の 4 つ下、入れ子の `case` の分岐はさらに 8 つ下)に合わせる。一致テストが字下げの深さで道を辿るためである。 - -## 処理の流れ - -```mermaid -sequenceDiagram - participant Sh as bash - participant C as _devbase_completions - participant O as _devbase_offer_context - Sh->>C: words, cword, cur, prev - alt prev が --context - C-->>Sh: 候補 0 件で終える - else それ以外 - C->>C: cword ごとの今の分岐で COMPREPLY を作る - C->>O: words, cword, cur - alt cur が - で始まり、道の直後か名前の直後 - O->>O: COMPREPLY に --context を足す - end - C-->>Sh: COMPREPLY - end -``` - -`_devbase_offer_context` は `i = 1..3` について `words[1..i]` から道を組み、`cword == i + 1` で 1 つ目の一覧に、`cword == i + 2` で 2 つ目の一覧にあれば `compgen -W --context -- "$cur"` の結果を候補に足して終える(補完する語が `--context` の頭でなければ何も足さない)。道の頭が `-` で始まる語でも照合は外れるだけで、誤って足さない。 - -図は補完を実行する流れだけを描き、テスト 2 本・確定仕様・`CHANGELOG.md` を含めない(どれも補完の実行の経路に乗らない。テストと写しの関係は構成要素図にある)。 - -zsh は `_arguments` が `--context` の指定から、`-` で始まる語には `--context` を、`--context` の直後には説明だけを出す。分岐の外に共通の段を置かない。 - -## 非機能の実現方式 - -| 大項目 | 条件 | 実現方式 | -| --- | --- | --- | -| 運用・保守性 | `--context` を取るサブコマンドを足したとき、補完の写しを取り残すと一致テストが道を挙げて落ちる | 一致テストの集合は `CONTEXT_PATHS`(argparse の走査)から得て、道を手で並べない。欠けと余分を `_diff_message` の形で出す | -| システム環境 | bash の補完は macOS の bash 3.2 と Linux の bash 5 で動く | 連想配列・`${var,,}`・`mapfile` を使わない。配列への追加は bash 3.1 からある `+=` を使う。`bash -n`(`test_completion.py`)と一致テストを手元の macOS と CI の Linux で通す | - -## 決定の記録 - -### 決定 1: bash で道の取り残しを 1 か所で防ぐため、`--context` は今の分岐の後の共通の段で足す - -`--context` を出す 35 の道は、`cword` の 2・3・4 の分岐と、`project` / `container` / `env` の入れ子に散らばる。半分以上の道は `-` で始まる語の分岐を持たない。道の一覧を 1 つ持つ共通の段にすると、道を足すときに直す場所が一覧の 1 行になり、一致テストが比べる写しも 1 つに決まる。 - -採らなかった案: 各分岐に `_devbase_complete_flags` の旗を足す形。直す分岐が 20 を超え、`up` のように今は名前だけを出す分岐へ `-` の分岐を新しく足すことになり、取り残しを生む場所が増える。 - -根拠: Mission(MVV 版 1) - -### 決定 2: 名前の後の位置を正しく決めるため、bash は「名前の後でも出す道」を 2 つ目の一覧として持つ - -`devbase up web -` の `web` がプロジェクト名かは、道が `[name]` を取るかで決まる。`login` の番号・`build` のイメージ名・`profile` のプロファイル名の後ろは前提 3 の対象でないため、`[name]` の集合から補完の例外を引いた 14 の道だけを一覧に置く。 - -採らなかった案: `--context` を出す道のすべてで「道の 2 つ後ろ」まで出す形。`env exec` の `argv` は `REMAINDER` で、コマンドの後の `--context` はそのコマンドへ渡る。誤った位置で候補を出す。 - -根拠: 根拠なし(MVV 版 1) - -### 決定 3: 値を補完しないことを道に依らず守るため、bash は直前の語が `--context` なら最初に候補を空にして終える - -今は値の位置で候補が出る分岐は無いが、`devbase up --context -` は共通の段が「名前の後」と読んで `--context` を足し得る。最初に終えると、後から分岐を足しても値の位置へ候補が漏れない。`open_flags` と `post-start` の分岐からは `--context` を外し、足す場所を共通の段 1 つにする(同じ語が 2 回候補に出ない)。 - -採らなかった案: 共通の段の中だけで直前の語を見る形。今の分岐が出す候補(プロジェクト名など)は止められない。 - -根拠: 根拠なし(MVV 版 1) - -### 決定 4: zsh で今の候補を崩さないため、`_arguments` へ移す分岐の位置引数は `*:` で書く - -`_devbase_project_names` や `_values` を直接呼ぶ分岐は、位置を問わず候補を出している。`_arguments` の `1:` は、マニュアルの読みでは `words` の 2 つ目(`devbase` の次の語)を 1 番目と数え、サブコマンドの後ろの位置に当たる番号は道の深さで変わる。`*:` は番号に依らず、今の「位置を問わず出す」振る舞いのまま `--context` を足せる。今 `1:` で書かれている分岐(`open`・`project ps` など)は変えない。 - -採らなかった案: 分岐ごとに番号を数えて `2:` や `3:` で書く形。zsh を起動する試験が無く(要求の対象範囲の含まない)、番号の誤りを検査で拾えない。 - -根拠: 根拠なし(MVV 版 1) - -### 決定 5: 写しが列挙の正本より広がることも防ぐため、一致テストは `--context` の欠けと余分の両方を比べる - -ほかの写し(ラッパーのリスト・確定仕様の表)の比べ方は欠けと余分の両方を挙げる。補完の `--context` も同じ形にし、`--context` を受け付けない道(`env set` など)で候補に出したら落とす。余分を比べる道は、子の parser を持たない末端の道に限る。親の道(`project profile`)の zsh の本体は子の分岐を含み、余分と読み違えるためである。 - -採らなかった案: 欠けだけを比べる形(受け入れ条件 5 はこれで満たす)。argparse が退ける旗を補完が勧めても検査で拾えない。 - -根拠: 根拠なし(MVV 版 1) - -## テスト設計 - -| 受け入れ条件・不変条件 | どの振る舞いで縛るか | どう壊したら落ちるべきか | -| --- | --- | --- | -| 受け入れ条件 1・I1 | `CONTEXT_PATHS` のすべての道(`container` の道は `ct` でも)で、`devbase <道> -` の補完に `--context` が入る | bash の 1 つ目の一覧から道を 1 つ消すと、その道を挙げて落ちる | -| 受け入れ条件 2・I2 | `NAME_PATHS` から補完の例外を引いた道で、`projects/web` のある一時の `DEVBASE_ROOT` の下の `devbase <道> web -` の補完に `--context` が入る | bash の 2 つ目の一覧から道を 1 つ消すと、その道を挙げて落ちる | -| 受け入れ条件 3・I3 | `devbase up --context ''`・`devbase project up web --context ''`・`devbase container ps --context ''`・`devbase env exec --context ''` の補完が 0 件 | 冒頭の段を消し、共通の段が値の位置でも足すように壊すと落ちる。冒頭の段を消して今の `cword` 3 の `up` にプロジェクト名を出す分岐を足すと落ちる | -| I4 | 末端の道のうち `CONTEXT_PATHS` に無いものでは、`devbase <道> -` の補完に `--context` が入らない | 1 つ目の一覧に `env/set` を足すと「余分」として落ちる | -| 受け入れ条件 4・I5 | `etc/_devbase` で `CONTEXT_PATHS` のすべての道の分岐の本体に、`:context:` の直後で指定が閉じる `--context` の指定がある | 分岐 1 つから `--context` の指定を消すと、その道を挙げて落ちる。`:context:_files` のように値の補完を足しても落ちる | -| I6 | 末端の道のうち `CONTEXT_PATHS` に無いものの分岐の本体に、`--context` の指定が無い | `env set` の分岐に `--context` の指定を足すと「余分」として落ちる | -| 受け入れ条件 5 | 上の bash と zsh のテストが別のテストで、落ちた文に `etc/devbase-completion.bash` か `etc/_devbase` と道が出る | bash から外したのに zsh のテストが落ちる(または逆)形に組むと、手動確認で外したファイルとテストの名前が合わない | -| 受け入れ条件 6・I7 | `tests/cli/test_completion.py` の `-` で始まる語の期待値(`ps` の `{"--all", "-a"}` など 9 か所)を `--context` を足した集合にし、ほかの期待値を変えずに通る。前方一致の絞り込みを縛る例として `devbase ps --a` → `{"--all"}`・`devbase open --o` → `{"--open-index"}` を足す | 共通の段がプロジェクト名や別の旗を足すように壊すと、`-` で始まらない語の期待値か旗の集合が落ちる。補完する語で絞らずに `--context` を足すと、`--a`・`--o` の期待値が落ちる | -| 受け入れ条件 7 | 確定仕様の表の bash と zsh の行が `--context` の比べ方を書いている | 検査では縛らない。レビューで確定仕様と一致テストを並べて読む | -| 受け入れ条件 8 | 全体のテスト・CI と同じ lint・固有の語の検査・`bash -n`・`zsh -n` が通る | 補完 2 ファイルの構文を壊すと `test_completion.py` の構文の検査が落ちる | - -マージ前の手動確認(要求の検証手段)として、bash と zsh の補完からそれぞれ道 1 つの `--context` を一時的に外し、一致テストが落ちた文を Pull Request に記録してから戻す。 - -## 設計の結果 - -| 課題 | 扱い | 取り込み先 | 触るファイル | -| --- | --- | --- | --- | -| #435 | 実装する | — | `etc/devbase-completion.bash`、`etc/_devbase`、`tests/cli/test_name_context_consistency.py`、`tests/cli/test_completion.py`、`docs/specifications/cli-argument-resolution.md`、`CHANGELOG.md` | - -## 未確認のまま残ること - -| 項目 | 内容 | -| --- | --- | -| zsh の実際の候補 | 一致テストは zsh を起動せず内容を読む。`_arguments` が `-` で `--context` を出し、`--context ` で候補を出さないことは、リリース後テストで手元の zsh に読み込んで確かめる(要求の検証手段)。設計の時点で zpty による実測を試みたが、端末の入出力を取れず確かめられなかった | -| 今 `1:` で書かれている zsh の分岐 | `open`・`project ps`・`project logs`・`project post-start` の `'1:name:_devbase_project_names'` が、サブコマンドの後ろの位置でプロジェクト名を出しているかは確かめていない(決定 4 の数え方なら出ていない)。この変更では触らない。リリース後テストで出ないと分かれば、範囲外の課題として起票する | -| zsh の `env exec` のコマンドの後ろ | `_arguments` はコマンドを打った後の `-` にも `--context` を出し得る。argparse ではそこでの `--context` はコマンドへ渡る。要求の前提 3 が zsh の位置を問わない出し方を認めている | -| `env` のサブコマンドの一覧 | `token`・`backend` が bash と zsh の一覧に無い(要求の前提 6)。別の課題にするかは利用者が棚卸しで決める(要求の未決) | diff --git a/issues/issue-435-requirements.md b/issues/issue-435-requirements.md deleted file mode 100644 index 75c4c9e5..00000000 --- a/issues/issue-435-requirements.md +++ /dev/null @@ -1,134 +0,0 @@ -# #435: シェル補完が --context を open と post-start でしか出さない - -正は課題の本文(#435)で、この文書はその写しである。`spec-copy.py write` で作り直す。手で直さない。 - -## 依頼(原文) - -> ## 何を見つけたか -> -> シェル補完が `--context` を出すのは `open` と `post-start` だけである(`etc/devbase-completion.bash` の 56・143 行、`etc/_devbase` の 169・224・229・263 行)。`--context` を取るほかの副コマンド(`up` `down` `ps` `logs` `login` `scale` `build` `rebuild`、`container` の配下、`env exec` など)では補完に出ない。2 つだけに出るのは機能を足した順の結果で、決めた範囲ではない。 -> -> ## どうするか -> -> `--context` を取るすべての副コマンドで、bash と zsh の補完が `--context` を出すようにする。値(コンテキスト名)の補完はしない。#214 で入れた一致テスト(補完とコマンドの集合を比べるテスト)の比べる対象を `--context` まで広げ、食い違いの再発を防ぐ。 -> -> ## 受け入れ条件 -> -> - [ ] `--context` を取るすべての副コマンド(`container` の配下・`env exec` を含む)で、bash(`etc/devbase-completion.bash`)と zsh(`etc/_devbase`)の補完が `--context` を出す。値(コンテキスト名)は補完しない -> - [ ] #214 の一致テスト(`tests/cli/test_completion.py`)が `--context` を比べる対象に含み、補完から `--context` を 1 か所外すと落ちる -> - [ ] 全体のテスト(`env -u DEVBASE_ROOT uv run --locked pytest tests/ -q`)と CI と同じ lint が通る -> -> ## 由来 -> -> #214 の要求の「未決」(補完が `--context` を補完する範囲)。m7e の棚卸しで、利用者が起票すると決めた(2026-10-07)。実害の報告は無い。 -> -> Size: 2 - -## 目的 - -- `--context NAME` を受け付けるサブコマンドなら、どれでもシェル補完で `--context` を見つけられる状態にする。今は `open` と `post-start` だけに出るため、`devbase up myapp --context gpu-wsl`([CLI リファレンス](docs/user/cli-reference/02-project.md)の例)のような使い方を、補完から知る手段が無い -- 補完の `--context` が [列挙の正本](docs/specifications/cli-argument-resolution.md#name-と---context-を受け付けるサブコマンド)(argparse)からずれたら、一致テストが落ちる状態にする。今の一致テストは補完の `--context` を比べない(確定仕様に「補完が `--context` の値を補完するかどうか(今は `open` と `post-start` だけ)は比べない」とある) - -## 前提 - -- 前提 1: 原文の「#214 の一致テスト(`tests/cli/test_completion.py`)」は `tests/cli/test_name_context_consistency.py` を指す。#214 で入れた一致テストはこのファイルで、`test_completion.py` は補完の個々の振る舞いを固定するテストである(`docs/specifications/cli-argument-resolution.md` の「一致テストが比べる写し」) -- 前提 2: 「`--context` を取るすべての副コマンド」は、一致テストが列挙の正本から得る `--context` の集合(`CONTEXT_PATHS`)の 35 の道に等しい。原文の例に無い `env token` と、入れ子の `project profile up|down|list`・`container profile up|down|list` も含む。`ct` は `container` の別名として同じ扱いを受ける -- 前提 3: 補完が `--context` を出す位置は次の 2 つに限る。(a) サブコマンドの道の直後の位置。(b) `[name]` を受け付けるサブコマンド(`[name]` の集合から補完の例外を引いたもの)で、プロジェクト名の直後の位置(`devbase up web -`・`devbase project up web -`)。ほかの位置引数(`scale` の数・`profile` のプロファイル名・`login` の番号・`build` のイメージ名・`env exec` の `--` より後ろ)の後ろは対象にしない。zsh の `_arguments` が位置を問わず出すのは構わない -- 前提 4: bash で `--context` を出すのは、補完する語が `-` で始まるときだけとする(今の `open` / `ps` / `logs` / `post-start` と同じ「`-` で始まれば旗、そうでなければこれまでの候補」の分岐)。`-` で始まらない語の候補(プロジェクト名・`login` の `1 2`・`container scale` の `1 2 3 4 5` など)は変えない -- 前提 5: `-` で始まる語の候補は、そのサブコマンドの補完がすでに出している旗(`ps` の `--all -a`、`logs` の `--follow -f --tail`、`open` の `--open-index`)に `--context` を足したものになる。今の補完が出していないほかの旗(`up` の `--open` / `--no-open`、`build` の `--expires`、`env token` の `--print` など)は足さない -- 前提 6: `env token` は bash・zsh のどちらの補完でも `env` のサブコマンドの一覧に無い。この課題では `devbase env token -` で `--context` を出すところまでを扱い、一覧へ `token` を足すことは扱わない(下の「対象範囲」の含まない) -- 前提 7: 一致テストの zsh の比べ方は、#214 と同じく zsh を起動せずに内容を読む(道の分岐の本体に `--context` の指定があるか)。入れ子の `profile` の道と、今は分岐の無い zsh のトップレベル `build`・`env token` には、道の分岐を足すことになる - -## 対象範囲 - -含む: -- `etc/devbase-completion.bash` と `etc/_devbase` で、`--context` の集合のすべての道が前提 3 の位置で `--context` を出すこと -- `--context` の直後(値の位置)で、コンテキスト名を補完しないこと -- 一致テスト(`tests/cli/test_name_context_consistency.py`)で、bash(実行して)と zsh(内容を読んで)の補完が `--context` の集合のすべての道で `--context` を出すことを比べること -- 今の振る舞いを固定している `tests/cli/test_completion.py` の期待値のうち、`-` で始まる語の候補の集合(`ps` の `{"--all", "-a"}` など)を、`--context` を足した集合へ直すこと -- 確定仕様 `docs/specifications/cli-argument-resolution.md` の「一致テストが比べる写し」の表と、「補完が `--context` の値を補完するかどうか(今は `open` と `post-start` だけ)は比べない」の文を、新しい比べ方に合わせること -- `CHANGELOG.md` の `[Unreleased]` への記入(利用者に見える変更のため) - -含まない: -- コンテキスト名(`docker context ls` の名前)の補完 -- `--context` 以外の旗を補完へ足すこと(前提 5) -- `env` のサブコマンドの一覧へ `token` や `backend` を足すこと(前提 6。別の課題の候補) -- 前提 3 の 2 つの位置より後ろでの bash の旗の補完 -- argparse の `--context` の受け付け(`lib/devbase/cli.py`)とラッパーの `cmd_build` の振る舞いの変更 -- zsh を起動して補完を実行する試験 - -## ドメインイベント - -| # | イベント | 引き金 | 失敗したとき | 順序の前提 | -| --- | --- | --- | --- | --- | -| E1 | 利用者が `--context` を受け付けるサブコマンドの後で `-` を打って補完を求めた | 利用者の Tab | — | 補完のファイルがシェルに読み込まれている | -| E2 | 補完が `--context` を含む旗の候補を出した | E1 | `--context` が候補に無い(今の `open` / `post-start` 以外の状態)。受け入れ条件 1・2 が防ぐ | E1 | -| E3 | 利用者が `--context` の後で補完を求め、補完が候補を出さなかった | `--context` の後の Tab | コンテキスト名やプロジェクト名が出る。受け入れ条件 3 が防ぐ | E2 | -| E4 | 開発者が `lib/devbase/cli.py` で `--context` を取るサブコマンドを足した | 機能の追加 | — | — | -| E5 | 一致テストが、補完の `--context` の欠けた道とファイルを挙げて落ちた | E4 の後の pytest(手元か CI) | 落ちずに通る(今の状態)。受け入れ条件 4 が防ぐ | E4 | - -## 用語 - -| 用語 | 意味 | -| --- | --- | -| 列挙の正本 | `[name]` と `--context` を受け付けるサブコマンドの集合の正本。argparse(`lib/devbase/cli.py` の `_create_parser()`) | -| 一致テスト | 列挙の正本を走査して得た集合と、写しを比べるテスト(`tests/cli/test_name_context_consistency.py`) | -| 写し | 列挙の正本を手で写した場所(`bin/devbase` の 2 つのリスト・補完 2 ファイル・確定仕様の表) | -| 補完の例外 | 補完がプロジェクト名を出さないことを理由つきで認める `[name]` の道(`project profile up|down|list`) | - -## 受け入れ条件 - -- [ ] 1. bash: `--context` の集合のすべての道(35。`ct` で始めても同じ)で、`devbase <道> -` の最後の語を補完すると、候補に `--context` が含まれる -- [ ] 2. bash: `[name]` の集合から補完の例外を引いたすべての道で、`devbase <道> web -` の最後の語を補完すると、候補に `--context` が含まれる(`projects/web` のある一時の `DEVBASE_ROOT` で) -- [ ] 3. bash: `devbase up --context ''`・`devbase project up web --context ''`・`devbase container ps --context ''`・`devbase env exec --context ''` の最後の語を補完すると、候補が 0 件である(コンテキスト名もプロジェクト名も出ない) -- [ ] 4. zsh: `etc/_devbase` で `--context` の集合のすべての道の分岐の本体に、値の補完を持たない `--context` の指定(`'--context[...]:context:'` の形。`:` の後ろに補完の関数を置かない)がある -- [ ] 5. 一致テストが受け入れ条件 1・2・4 を、列挙の正本から得た集合で比べる。bash の補完から道 1 つの `--context` を外すと bash のテストが、zsh の補完から道 1 つの `--context` を外すと zsh のテストが落ち、落ちたテストの文に外したファイルと道が出る -- [ ] 6. 退行しない: `-` で始まらない語の補完の候補は変わらない。`tests/cli/test_completion.py` と一致テストのうち、`-` で始まる語の候補の集合を比べる期待値だけを `--context` を足した集合へ直し、ほかの期待値は変えずに通る -- [ ] 7. 確定仕様 `docs/specifications/cli-argument-resolution.md` の「一致テストが比べる写し」の表の bash と zsh の行が、補完の `--context` を比べることを書き、「補完が `--context` の値を補完するかどうか(今は `open` と `post-start` だけ)は比べない」の文が新しい比べ方と食い違わない -- [ ] 8. 全体のテスト(`env -u DEVBASE_ROOT uv run --locked pytest tests/ -q`)・CI と同じ lint(`uvx ruff check --select=E9,F63,F7,F82 lib`)・固有の語の検査(`python3 .github/scripts/proper_term_check.py`)が通る。補完 2 ファイルの構文の検査(`bash -n`・`zsh -n`。`tests/cli/test_completion.py` にある)も通る - -## 非機能の条件 - -| 大項目 | 条件 | -| --- | --- | -| 運用・保守性 | `--context` を取るサブコマンドを `lib/devbase/cli.py` に足したとき、補完の写しを取り残すと一致テストが道を挙げて落ちる(受け入れ条件 5) | -| システム環境 | bash の補完は macOS の bash 3.2 と Linux の bash 5 のどちらでも動く書き方を保つ(今の補完と同じ。`bash -n` と一致テストが CI の Linux で通り、手元の macOS でも通る) | - -## 影響 - -| 対象 | 影響 | -| --- | --- | -| 公開インタフェース | 変わらない。CLI の引数は変えない。補完の候補が増えるだけで、互換は保つ | -| データ | なし | -| 既存の振る舞い | `-` で始まる語の補完の候補に `--context` が加わる(`ps` の `--all -a` → `--all -a --context` など)。`-` で始まらない語の候補は変わらない | - -## 検証手段 - -| 項目 | 手段 | -| --- | --- | -| テスト | `env -u DEVBASE_ROOT uv run --locked pytest tests/ -q`(一致テストと `tests/cli/test_completion.py` を含む) | -| 静的解析・型検査 | `uvx ruff check --select=E9,F63,F7,F82 lib`、`python3 .github/scripts/proper_term_check.py`(`語の一覧が無いため飛ばした` と出たら検査はしていない) | -| 手動確認(マージ前) | 受け入れ条件 5 の「外すと落ちる」を確かめる。bash と zsh の補完からそれぞれ道 1 つの `--context` を一時的に外して一致テストを打ち、落ちたことと出た文を Pull Request に記録してから戻す | -| 手動確認 | リリース後テストで、手元の bash と zsh に補完を読み込み、`devbase up web -`・`devbase env exec -` で `--context` が出ること、`devbase up --context ` で候補が出ないことを見る | - -## 前提とする取り決め - -| 項目 | 参照先 / 決めたこと | -| --- | --- | -| プロジェクト構造 | `AGENTS.md` の表。補完は `etc/`、テストは `tests/cli/`、確定仕様は `docs/specifications/` | -| コーディング規約 | `AGENTS.md` の「書き方」と、補完 2 ファイルの今の書き方(bash は `_devbase_complete_flags` の分岐、zsh は `_arguments` の指定)に合わせる | -| テスト戦略 | bash は補完を実行して候補を比べる。zsh は内容を読む(#214 の決定 9 と同じ、道の分岐ごとに読む)。集合は列挙の正本の走査から得て、道を手で並べない | - -## 境界 - -| 区分 | 内容 | -| --- | --- | -| 常に行う | 全体のテスト・CI と同じ lint・固有の語の検査を push の前に打つ。`env -u DEVBASE_ROOT` を外さない | -| 確認してから行う | `--context` 以外の旗を補完へ足すこと、`env` のサブコマンドの一覧を直すこと | -| 行わない | `lib/devbase/cli.py` の引数の定義を変えること、コンテキスト名を補完すること、補完の例外を広げること | - -## 未決 - -| 項目 | 誰が決めるか | 期限 | -| --- | --- | --- | -| `env` の補完のサブコマンドの一覧に `token`・`backend` が無いことを別の課題にするか(前提 6) | 利用者(棚卸しで) | この課題の振り返りまで | From 2b32704ce7915979b6e7999dc4e1eec660b5e43c Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Thu, 8 Oct 2026 23:38:54 +0900 Subject: [PATCH 2/3] =?UTF-8?q?Docs:=20post-start.md=20=E3=82=92=E7=A2=BA?= =?UTF-8?q?=E5=AE=9A=E4=BB=95=E6=A7=98=E3=81=AB=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/specifications/post-start.md | 5 +- issues/issue-444-design.md | 490 ------------------------------ issues/issue-444-requirements.md | 185 ----------- 3 files changed, 4 insertions(+), 676 deletions(-) delete mode 100644 issues/issue-444-design.md delete mode 100644 issues/issue-444-requirements.md diff --git a/docs/specifications/post-start.md b/docs/specifications/post-start.md index d7b9b7d4..9f37fd1f 100644 --- a/docs/specifications/post-start.md +++ b/docs/specifications/post-start.md @@ -146,7 +146,10 @@ Container startup failed: 2 of 3 instances did not become ready. `_run_post_start(deployment, indices, config, run_deploy=True)` は、 後処理の対象 `indices` へ次の順で処理を行う。対象が空なら何も行わない(token も発行しない)。 `deployment` は起動した構成(`_ComposeDeployment`。`project_name`・`scale`・`dev_service_name`・`compose_file` の組)で、 -`up` / `scale` / `project post-start` が組んで渡す。 +`up` / `scale` / `project post-start` が組んで渡す。4 つの値は 3 つの呼び出し元で常に組で渡り、個々の処理 +(不足リポジトリの報告・token の配布・窓のタイトル)も同じ組を受けるため、凍結した 1 つの値にまとめる。 +`indices`・`config`・`run_deploy` は呼び出しごとに意味が違い(`config` は構成ではなくプロジェクトの設定)、組に入れない。 +段は組をほどき、個々の処理へは今までと同じ引数で渡す。 | 順 | 処理 | 条件 | 失敗したとき | | --- | --- | --- | --- | diff --git a/issues/issue-444-design.md b/issues/issue-444-design.md deleted file mode 100644 index fd42730d..00000000 --- a/issues/issue-444-design.md +++ /dev/null @@ -1,490 +0,0 @@ -# devbase: `env sync` の AWS の経路・`env list`・`env keygen` など 7 つの関数が長く、起動の後の処理の段は引数が 7 個あり、sync の関数の受け方が 1 か所だけ違い、`--group` の検証とテストの補助が 2 か所に重なる → 13 件すべてを振る舞いを変えずに分け、どの関数も 50 行以下・認知的複雑度 15 以下に収まり、重なりは 1 か所にまとまる(#444) - -## 目的 - -- **何が壊れているか**: PR #442 の構造改善で時間の枠に入らなかった 13 件(R1〜R13)が残る。長い関数・長い引数の並び・形ごとの分岐の連なり・同じ処理の重なりで、振る舞いは正しいが読み解くのに手間がかかる -- **誰が困るか**: 次に `env sync` の AWS の経路・`env list`・`env keygen`・`snapshot restore`・起動の後の処理・偽の OpenBao を触る devbase の開発者と AI エージェント -- **直すと何が成り立つか**: 13 件の兆候が要求の基準で消え、sync の関数がすべて `_SyncContext` を受ける。利用者から見える出力・終了コード・書き込みは変わらない - -## 適用範囲 - -- **働く範囲**: このリポジトリの `lib/devbase/` と `tests/`。配布先は `install.sh` で `main` を取る利用者の端末だが、振る舞いは変わらない。プラグインのリポジトリ(`projects/*`)と base イメージは触らない -- **プロジェクトごとに違うもの**: 無し(新しい設定と引数を足さない) -- **当たるモード**: `standard`(本番の振る舞いを変えない構造の変更で、対象に既存のテストが十分にある) - -## あるべき姿の根拠 - -| 根拠 | 種類 | 何を示すか | -| --- | --- | --- | -| 課題 #444 の本文の「兆候が消えたと見なす基準」と前提 1〜7 | 利用者の指示の原文 | 直した後の形を数値(50 行・認知的複雑度 15・C901 5・引数 4 個)と定義の場所で決める | -| PR #442 の改修計画の I-001 の手順(入れ子の report を外へ出す → `aws_profiles` の分岐と控えの無い分岐を抽出 → `(ctx)` で振り分ける) | 既存の形 | R1 の分け方は、3 つの CLI が出した提案をそのまま使う | -| `main` = `54c1c27` で測った値が要求の「現状の値」と一致した(`uvx complexipy`・`uvx ruff check --select C901`・`ast` の行数。2026-10-08) | 実測 | 設計の前提にした大きさは今のコードのものである | -| `tests/cli/test_open_command.py` などが `from tests.cli.conftest import ...` で `conftest.py` の補助を取り込んでいる | 既存の形 | テストの補助を `conftest.py` に置いて取り込む形は既にある(R11・R12) | -| `docs/specifications/secret-backend.md` の「使えないグループ名」の行(TUI は `validate_account_group` で検証し、続けて `storage_group` を通す) | 既存の形 | TUI と CLI の検証は前段の有無が違う。まとめても前段を消さない(R2) | - -要求と受け入れ条件は #444 の本文にある(コピーは `issues/issue-444-requirements.md`)。この文書は「どう作るか」だけを扱う。 - -## ドメインモデル - -### コンテキスト - -| コンテキスト | 何の語が 1 つの意味に決まるか | -| --- | --- | -| 機密の置き場(`secret`) | `env sync`・`env list`・`env keygen`・`env project` の収集・`--group` の検証(R1・R2・R6〜R9) | -| Compose の構成(`compose`) | スケールで作るファイルの `environment` の機密の伏せ方(R3・R4)。テストの補助(R11・R12)もこの構成を読む | -| 起動の後の処理(`post-start`) | 起動の後の処理の段の受け取り方(R5) | -| スナップショット(`snapshot`) | 復元の段の並び(R10) | -| テストの実行環境(`test`) | 偽の OpenBao の書き込みの経路(R13) | - -13 件はそれぞれ 1 つのコンテキストの中で閉じる。コンテキストの間の呼び出し(`up` が起動の後の処理の段を呼ぶ・TUI が -`commands/env.py` を呼ぶなど)は今の形のまま変えず、関係も新しく結ばない。R2 で CLI と TUI が共有する検証の関数は、 -どちらも `secret` の中の部品である。 - -### 集約 - -構造の変更は集約の境界と持ち主を変えない。下の表は、変更が触る単位と、その単位を書き換えてよい構成要素である。 - -| 集約 | 持ち主(書き換えてよいもの) | 根 | エンティティ | 値オブジェクト | -| --- | --- | --- | --- | --- | -| 1 回の `sync` | `commands/env.py` の sync の関数 | `_SyncContext` | 書き込み先(`SyncTargets`)・控え(`SourcesManager`) | AWS の入れ直しの計画 `(encode_fn, selection)`・集計 `_SyncCounts` | -| `--group` の名前 | `commands/env.py` の `check_group_option`(新) | 渡された名前 | — | 検証を通った名前 | -| スケールで作るファイルの dev サービス | `volume/compose.py` | サービスの辞書 | — | `environment`(none / dict / list / その他の形) | -| 起動の後の処理の 1 回 | `commands/container.py` の `_run_post_start` | 後処理の対象 | — | 起動した構成 `_ComposeDeployment`(新) | -| 復元の 1 回 | `snapshot/manager.py` の `SnapshotManager` | 世代のディレクトリ | アーカイブ(full・incr) | 適用の上限 `point` | - -### 不変条件 - -すべて「変更の前と同じ」を縛る。I1〜I9 は振る舞い、I10 は構造の条件である。 - -| # | 集約 | 条件 | 破れたときの扱い | -| --- | --- | --- | --- | -| I1 | 1 回の `sync` | AWS の入れ直しは、控えの項目(`aws_profiles` / それ以外 / 無し)ごとに変更の前と同じ計画(書く・書かない・選択)を作り、同じ行を出す | `test_env_host_import.py`・`test_env_sync_group.py`・`test_env_sync_owner.py` が落ちる | -| I2 | `--group` の名前 | 使えない名前のときの文は、CLI が「`--group に使えない名前です: グループ名…`」、TUI が `validate_account_group` で落ちれば接頭のない「`--group に使えない名前です: DEVBASE_ACCOUNT_GROUP…`」、`storage_group` で落ちれば CLI と同じ文で、変更の前と同じである | 文を丸ごと比べるテストが落ちる | -| I3 | スケールで作るファイル | 機密のキーの値は書かれず、非機密の値と元の記法(dict / list)は残る。解釈できない形は警告を出して機密の名前の列挙に置き換える | `tests/volume/test_compose_secret_env.py` が落ちる | -| I4 | 起動の後の処理の 1 回 | 対象が空なら何もせず、それ以外は不足リポジトリの報告 → `./deploy`(`run_deploy` が真のとき)→ token の配布 → 窓のタイトルの順に、同じ引数で呼ぶ | `test_container_post_start.py`・`test_container_scale_order.py` が落ちる | -| I5 | 1 回の `sync` 以外の `env` | `env list` の節の見出し・変数の行・件数の行と、出す節の選び方が変更の前と同じである | 出力を丸ごと比べるテストが落ちる | -| I6 | `env keygen` | 鍵がある・読めない・同意しない・生成に失敗する、の各経路で出力・終了コード・鍵ファイルが変更の前と同じで、読めない鍵の判定は同意を求める前にある | `tests/commands/test_env_keygen.py` が落ちる | -| I7 | `env project` の収集 | 設定済み・自動生成(既定の長さと `:<長さ>`)・入力・必須の空、の各経路で表示と書き込みと戻り値が変更の前と同じである | `tests/commands/test_env_project.py` が落ちる | -| I8 | 復元の 1 回 | `point` の検査・世代と full の存在の確認は、控えのバックアップより前にあり、full → `point` までの incr の順に展開する | `tests/snapshot/` の復元のテストが落ちる | -| I9 | 偽の OpenBao | ログインと KV の書き込みへの応答(状態・本文・状態の更新)が変更の前と同じである | 偽の OpenBao を使う `tests/` のテストが落ちる | -| I10 | すべて | 直した関数と抜き出した関数が、要求の「兆候が消えたと見なす基準」を満たし、対象のファイルの関数で C901 と認知的複雑度が上がったものが無い | 測定の値が基準を超える(Pull Request の本文の表で見える) | - -### ドメインイベント - -要求のドメインイベントの番号を引き継ぐ。 - -| # | イベント | 発生元 | 受け手 | -| --- | --- | --- | --- | -| E1 | 着手前の全体のテストが通った | 実装の担当(`tdd-cycle` の着手) | E2 を確定させる設計の結果(既存の失敗があれば人へ戻す) | -| E2 | 13 件のそれぞれを直すか直さないかに分けた | この設計(決定 1) | 実装の計画 | -| E3 | 直す件の構造を変えた | 実装の担当 | 全体のテストと lint(E4) | -| E4 | 変更の後の全体のテストと lint が通った | 検査 | 測定(E5) | -| E5 | 直した件の兆候が基準で消えたことを測った | 測定 | Pull Request の本文の測定の表(満たさない件は E3 へ戻すか「直さない」へ移す) | - -### 用語 - -| 用語 | 意味 | 用語集への反映 | -| --- | --- | --- | -| 起動の後の処理の段 | 後処理の対象を受けて、インスタンスごとの処理を決まった順に行う 1 つの関数 | 無し(既存の語。受け取り方が変わるだけで意味は変えない) | -| 対象のグループ | 1 回の操作が読み書きするグループ | 無し(既存の語) | -| 同期済みハッシュの控え | `env sync` がソースファイルの位置とハッシュを記録する `.env.sources[.].yml` | 無し(既存の語) | - -兆候の名前(long_method など)と測定の語(認知的複雑度・C901)は構造改善の工程の語で、devbase の振る舞いの語では -ないため用語集へ足さない。 - -## 機能一覧 - -利用者から見える機能は変わらない。この変更が中身を組み直す機能を並べる。 - -| # | 機能 | 誰が使うか | -| --- | --- | --- | -| F1 | `env sync` が AWS の認証情報を控えの選択の範囲で入れ直す(R1) | 機密を同期する利用者 | -| F2 | `--group` と TUI のグループの選択が、使えない名前を拒んで理由を出す(R2) | グループ別の置き場の利用者 | -| F3 | `up` / `scale` がスケールで作るファイルで機密の値を伏せる(R3・R4) | プロジェクトの利用者 | -| F4 | `up` / `scale` / `project post-start` が起動の後の処理を行う(R5) | プロジェクトの利用者 | -| F5 | `env project` が `env.yml` の定義に従って値を集める(R6) | プロジェクトの利用者 | -| F6 | `env keygen` が age の鍵を作る(R7) | 機密を暗号化する利用者 | -| F7 | `env list` が参照ごとの変数を出す(R8・R9) | 機密を確かめる利用者 | -| F8 | `snapshot restore` が世代を書き戻す(R10) | ボリュームを戻す利用者 | -| F9 | `tests/volume/` の compose のテストと偽の OpenBao が、補助を 1 か所から使う(R11〜R13) | devbase の開発者 | - -## 構成要素 - -| 要素 | 責務 | 件 | -| --- | --- | --- | -| `commands/env.py` の `_aws_sync_plan(ctx)` | 控えの AWS の項目を見て、3 つの計画の作り方のどれかへ振り分けるだけ | R1 | -| `commands/env.py` の `_aws_plan_for_selection(ctx, profiles)`(新) | 控えが `aws_profiles` のとき、選んだプロファイルとその連なりだけを切り出す計画を作る。書かないなら理由を出して `None` | R1 | -| `commands/env.py` の `_aws_plan_for_unregistered(ctx)`(新) | 控えに項目が無いとき、参照の値のプロファイルを選択とみなす計画を作る。書かないなら理由を出して `None` | R1 | -| `commands/env.py` の `_report_unknown_profiles(payload)`・`_report_profile_conflicts(payload)`(新。入れ子から外へ出す) | 選んだのに無いプロファイルの行と、空白だけ違う見出しの警告を出す | R1 | -| `commands/env.py` の `_sync_credential_sources` | 呼び出しを `_aws_sync_plan(ctx)` に変えるだけ | R1 | -| `commands/env.py` の `check_group_option(settings, group, *, account_rule=False)`(新) | `--group` の名前を `storage_group` に通し、通らなければ「`--group に使えない名前です: …`」の `GroupOptionError` を送る。`account_rule` が真なら先に `validate_account_group` を通し、その結果(前後の空白を外した名前)を返す。偽なら渡された名前をそのまま返す | R2 | -| `commands/env.py` の `_target_group` | 名前の検証を `check_group_option(store.config.openbao, group)` へ任せる | R2 | -| `tui/actions_env_keys.py` の `group_attrs` | 名前の検証を `check_group_option(settings, name, account_rule=True)` へ任せ、`GroupOptionError` の文を出して選択へ戻る | R2 | -| `volume/compose.py` の `_mask_secret_environment` | 形を求め、形をキーにした対応表 `_ENV_MASKERS` の関数を呼び、返った値があれば `environment` へ置くだけ | R3・R4 | -| `volume/compose.py` の `_mask_env_none`・`_mask_env_dict`・`_mask_env_list`・`_mask_env_other`(新) | 形ごとに伏せた `environment` を返す(none で機密が無ければ `None` = 作らない)。その他の形だけが警告を出す | R3・R4 | -| `commands/container.py` の `_ComposeDeployment`(新。凍結した dataclass) | 起動した構成の 4 つの値(`project_name`・`scale`・`dev_service_name`・`compose_file`)を 1 つに持つ | R5 | -| `commands/container.py` の `_run_post_start(deployment, indices, config, run_deploy=True)` | 引数を 4 個にし、個々の処理(`_report_missing_repos` など)へは今と同じ引数を渡す | R5 | -| `commands/container.py` の `cmd_up`・`cmd_post_start`・`cmd_scale` | `_ComposeDeployment` を組んで `_run_post_start` を呼ぶ形に変えるだけ | R5 | -| `commands/env.py` の `_collect_from_env_yml` と `_generated_secret(spec)`・`_prompt_env_var(env_file, var)`(新) | 変数ごとに、設定済み・自動生成・入力のどれかへ振り分ける。自動生成の長さの読み方と、入力と必須の判定を抜き出す | R6 | -| `commands/env.py` の `cmd_env_keygen` と `_show_existing_key(path)`・`_refuse_unreadable_key(path)`・`_confirm_key_regeneration(devbase_root, path)`(新) | 既存の鍵の表示・読めない鍵での中止・作り直しの同意を抜き出す。理由の注記は抜き出した関数へ移す | R7 | -| `commands/env.py` の `cmd_env_list` と `_print_env_section(title, count_label, env_vars, keys_only, reveal)`・`_list_global_sections(...)`・`_list_project_sections(...)`(新) | 節の見出し・変数・件数の行を 1 つの関数で出し、チーム共通とプロジェクトの節の選び方をそれぞれ抜き出す | R8・R9 | -| `snapshot/manager.py` の `SnapshotManager.restore` と `_restore_source(name, point)`・`_apply_incrementals(...)`(新) | 復元の前の検査(`point`・世代・full)と、`point` までの差分の展開を抜き出す | R10 | -| `tests/volume/conftest.py` の `write_compose`・`load_scaled`・`mount_source`(新) | `tests/volume/` の compose のテストが共有する補助の唯一の定義 | R11・R12 | -| `tests/volume/test_compose_vscode.py`・`test_compose_group.py` | 自分の補助の定義を消し、`from tests.volume.conftest import ...` で取り込む | R11・R12 | -| `tests/conftest.py` の `_Handler.do_POST` と `_login(rec)`・`_write_secret(rec)` ほか(新) | 経路(ログイン / KV の書き込み)ごとの関数へ振り分ける。書き込みの前の拒否と CAS の更新は `_write_secret` から更に抜き出す | R13 | -| `docs/specifications/post-start.md` | 「起動の後の処理の段」の節の関数の形を新しい引数に合わせる | R5 | - -変えないもの: `_report_missing_repos`・`_push_bao_token`・`_apply_window_titles`・`_iter_env_names`・`_env_shape`・ -`SnapshotManager._backup_before_restore`・`_extract_archive`・`tests/volume/test_compose.py` の補助・`do_GET`・`do_DELETE`・ -snapshot の `--group` の検証(`commands/snapshot.py` と `tui/actions_snapshot.py`。`validate_account_group` だけを通す別の規則)。 - -### 構成要素図 - -```mermaid -graph LR - subgraph CLI["lib/devbase/commands"] - SYNC["_sync_credential_sources"] --> PLAN["_aws_sync_plan(ctx)"] - PLAN --> SEL["_aws_plan_for_selection"] - PLAN --> UNR["_aws_plan_for_unregistered"] - SEL --> REP["_report_unknown_profiles
_report_profile_conflicts"] - UNR --> REP - TG["_target_group"] --> CGO["check_group_option"] - LIST["cmd_env_list"] --> SEC["_list_global_sections
_list_project_sections"] - SEC --> PES["_print_env_section"] - UP["cmd_up / cmd_scale / cmd_post_start"] --> RPS["_run_post_start(deployment, indices, config, run_deploy)"] - end - subgraph TUI["lib/devbase/tui"] - GA["group_attrs"] --> CGO - end - subgraph VOL["lib/devbase/volume"] - MASK["_mask_secret_environment"] --> TBL["_ENV_MASKERS
none / dict / list / other"] - end - subgraph TST["tests/volume"] - VS["test_compose_vscode"] --> CF["conftest.py
write_compose / load_scaled / mount_source"] - GR["test_compose_group"] --> CF - end -``` - -R6・R7・R10・R13 は 1 つの関数から同じモジュールの関数を抜き出すだけで、要素どうしの関係を変えないため図に載せない。 - -### システム構成図 - -外部の系との関係と配置は変わらない。利用者の端末のホストで動く `devbase` の Python が、docker・OpenBao・`~/.aws` などを -今と同じ順で同じ回数だけ呼ぶ。 - -```mermaid -graph LR - U["利用者"] --> CLI["bin/devbase → lib/devbase(ホスト)"] - CLI --> D["docker / docker compose"] - CLI --> B["OpenBao(backend が openbao のとき)"] - CLI --> H["~/.aws・~/.git-credentials・age の鍵"] - T["pytest(tests/)"] --> CLI - T --> F["偽の OpenBao(tests/conftest.py・同じプロセスの HTTP サーバ)"] -``` - -### パッケージ・モジュール構成 - -```text -lib/devbase/ -├── commands/ -│ ├── env.py # R1・R2(check_group_option を足す)・R6・R7・R8・R9 -│ └── container.py # R5(_ComposeDeployment を足す) -├── tui/ -│ └── actions_env_keys.py # R2(group_attrs が check_group_option を呼ぶ) -├── volume/ -│ └── compose.py # R3・R4 -└── snapshot/ - └── manager.py # R10 -tests/ -├── conftest.py # R13 -└── volume/ - ├── conftest.py # R11・R12(補助 3 つを足す) - ├── test_compose_vscode.py - └── test_compose_group.py -docs/specifications/post-start.md # R5 の関数の形 -``` - -## 構造 - -変更が触る型だけを載せる(変更後)。`_ComposeDeployment` は新しく足す値オブジェクトで、`_SyncContext` と `GroupOptionError` は -既存の型を受け渡す先が増えるだけである。今の `_aws_sync_plan` は `_SyncContext` を受けず、`sources`・`targets`・`store` を -別々に受けている。 - -### クラス図 - -```mermaid -classDiagram - class _SyncContext { - sources - targets - store - counts - write(key, encoded, head) - } - class GroupOptionError { - exit_code - } - class _ComposeDeployment { - <> - project_name: str - scale: int - dev_service_name: str - compose_file - } - class _Handler { - do_POST() - _login(rec) - _write_secret(rec) - do_GET() - do_DELETE() - } - note for _SyncContext "_aws_sync_plan(ctx) と抜き出した 2 つも受ける" - note for GroupOptionError "check_group_option が送る(CLI と TUI が受ける)" -``` - -## 処理の流れ - -### R1: AWS の入れ直しの計画 - -振り分けの条件と、各経路が返すものは変更の前と同じである(I1)。 - -```mermaid -graph TD - S["_sync_credential_sources(ctx)"] --> P["_aws_sync_plan(ctx)"] - P --> Q{"控えの aws の項目"} - Q -->|"type が aws_profiles"| A["_aws_plan_for_selection(ctx, profiles)"] - Q -->|"それ以外の項目"| W["(_encode_aws_config_files, ALL)"] - Q -->|"無し"| N["_aws_plan_for_unregistered(ctx)"] - A --> R["_report_unknown_profiles / _report_profile_conflicts"] - N --> R - A --> X["(encode_fn, selection) か None"] - N --> X - W --> S2["_sync_credential_sources が _sync_source か _SKIP_SELECTION へ"] - X --> S2 -``` - -### R2: `--group` の名前の検証 - -```mermaid -sequenceDiagram - participant CLI as _target_group - participant TUI as group_attrs - participant C as check_group_option - participant V as validate_account_group - participant S as settings.storage_group - CLI->>C: (store.config.openbao, group) - C->>S: group - S-->>C: 通る / 誤り - C-->>CLI: group / GroupOptionError(「--group に使えない名前です: …」) - TUI->>C: (settings, name, account_rule=True) - C->>V: name - V-->>C: 前後の空白を外した名前 / 誤り - C->>S: 名前 - S-->>C: 通る / 誤り - C-->>TUI: 名前 / GroupOptionError - Note over TUI: GroupOptionError なら文を出して選択へ戻る -``` - -`check_group_option` は `DevbaseError` を捕まえて `GroupOptionError`(終了コード 2)に包む。`storage_group` が送るのは -`BackendConfigError`(`DevbaseError` の子)だけで、CLI が今 `BackendConfigError` だけを捕まえている振る舞いと同じになる。 - -### R5: 起動の後の処理 - -順と個々の処理への引数は変えない(I4)。3 つの呼び出し元が `_ComposeDeployment` を組み、段がそれをほどいて今と同じ -引数で個々の処理を呼ぶ。 - -```mermaid -sequenceDiagram - participant Caller as cmd_up / cmd_scale / cmd_post_start - participant R as _run_post_start - participant H as 個々の処理 - Caller->>R: (_ComposeDeployment(...), indices, config[, run_deploy=False]) - R->>R: indices が空なら戻る - R->>H: _report_missing_repos(config, scale, dev_service_name, project_name, compose_file=, indices=) - R->>H: ./deploy(run_deploy が真で、ファイルがあるとき) - R->>H: _push_bao_token(project_name, scale, dev_service_name, compose_file=, indices=) - R->>H: _apply_window_titles(project_name, scale, dev_service_name, compose_file=, indices=) -``` - -### R3・R4・R6〜R10・R13 - -いずれも 1 つの関数の中の段を、同じ順のまま抜き出した関数へ移す。呼ぶ順・回数・出力の順は変えない。 - -| 件 | 段の順(変更の前と同じ) | -| --- | --- | -| R3・R4 | 機密の名前の重複を除く → 形を求める → 形の関数で伏せる → 値があれば `environment` へ置く | -| R6 | 変数ごとに: 設定済みなら表示して次へ → 自動生成なら値を作って書き表示 → それ以外は入力を求め、空で必須なら誤りを出して偽を返す | -| R7 | 鍵があり `--force` でなければ表示して 0 → 読めない鍵なら中止して 1 → 鍵があり `--yes` でなければ同意を求める → 生成 → 控えの案内 | -| R8・R9 | `--group` の検証 → チーム共通の節(`-p` でなければ、持ち主ごと)→ プロジェクトの節(含めるときだけ、持ち主ごと) | -| R10 | `point` の検査 → 世代と full の確認 → 組を読む → 控えのバックアップ → full → `point` までの incr → 失った rename の警告 → 完了の行 | -| R13 | 記録 → 経路がログインなら `_login` → それ以外は `_write_secret`(mount と認可と禁止の拒否 → 失敗の注入 → `data` の検査 → CAS の更新 → 応答の注入 → 応答) | - -## 非機能の実現方式 - -| 大項目 | 実現方式 | -| --- | --- | -| 運用・保守性 | 直した関数と抜き出した関数を、要求の「検証手段」のコマンド(`uvx complexipy`・`uvx ruff check --select C901 --config 'lint.mccabe.max-complexity=0'`・`ast` の行数と引数の数)で変更の前後に測り、Pull Request の本文に並べる。対象のファイル(`commands/env.py`・`tui/actions_env_keys.py`・`volume/compose.py`・`commands/container.py`・`snapshot/manager.py`・`tests/conftest.py`)の全関数で、C901 と認知的複雑度が変更の前より上がったものが無いことを同じ測定で確かめる | -| セキュリティ | R3・R4 は形ごとの関数が返す値だけを変え、機密のキーを値なしの参照に置き換える式は今の式をそのまま移す。`tests/volume/test_compose_secret_env.py` を手を入れずに通す。R7 は鍵の値を扱う `agekeys.generate_key_file` と、公開鍵だけを出す `_print_key_backup_notice` を変えず、抜き出す関数は鍵の値を受けない | - -## 決定の記録 - -### 決定 1: 13 件を片付けるため、R1〜R13 をすべて「直す」に分け、「直さない」の件を作らない - -どの件も、関数の中の段を同じモジュールの関数へ移すか、引数を値オブジェクトへまとめるか、定義を 1 か所へ移すかで -基準を満たせ、出力・終了コード・書き込みを変える必要がない。振る舞いを縛るテストも各件にある(テスト設計)。 -直さない理由になりうる「基準を満たすには振る舞いを変える必要がある」件は見つからなかった。 - -実装で基準を満たせない件は、要求の E3 のとおり変更を戻して「直さない」へ移し、理由を Pull Request の本文に書く。 -設計の時点で一部を「直さない」に決めておく形は、根拠となる見積りが無く採らない。 - -根拠: Mission(MVV 版 1) - -### 決定 2: 要求の基準をそのまま使い、数値と定義の場所を設計で変えない - -要求の前提 2 は、設計で基準を変えるときに理由を残すよう求める。13 件はいずれも今の基準で達成でき(決定 1)、変える -理由が無い。R10(`restore`)は認知的複雑度 15 で既に基準の内にあり、行数(60 行)だけで兆候が残っているため、行数を -50 行以下にすることで消す。 - -根拠: 根拠なし(MVV 版 1) - -### 決定 3: `_aws_sync_plan` を `_SyncContext` に揃えるため、改修計画の I-001 の 4 段の分け方をそのまま採る - -3 つの CLI が出した提案で、控えの項目の 3 つの経路が関数の境界とそのまま重なる。2 つの経路が共有する入れ子の -report の関数はモジュールの関数へ出し、経路の関数から呼ぶ。表示の名前 `AWS認証` は 4 つの関数で使うため、 -モジュールの定数 `_AWS_LABEL` にする(`_sync_credential_sources` の同じ文字列は呼び出しの変更の外なので触らない)。 - -経路をキーにした対応表へ振り分ける形は、振り分けの条件が「項目の `type` の値」と「項目の有無」の 2 種で、1 つの -キーに揃わないため採らない。 - -根拠: Value 2 / Value 3(MVV 版 1) - -### 決定 4: `--group` の検証を 1 か所にするため、`check_group_option` に `account_rule` の引数を持たせて TUI の前段も中へ入れる - -TUI は `validate_account_group` を先に通し、落ちれば接頭の無い文を出す。CLI は `storage_group` だけを通し、名前の規則で -落ちた文は「グループ名: 」を接頭に持つ(`storage_group` の中で同じ規則を通すため)。2 つの文を変えずに 1 つの関数にするには、前段の有無を呼び出し側が選ぶ必要がある。 -前段を中へ入れると、「`--group に使えない名前です: `」の文を作る場所が 1 つになる。関数は TUI からも呼ぶため -`_` を付けずに `commands/env.py` に置き、`GroupOptionError` と並べる(TUI は既に同じモジュールから -`GroupOptionError` を取り込んでいる)。 - -採らなかった形: 前段を TUI に残して `storage_group` だけをまとめる形は、TUI に同じ文が 2 か所(前段の誤りと -`GroupOptionError`)残るため採らない。前段を CLI にも足す形は、CLI の文から「グループ名: 」が消えて振る舞いが変わる -ため採らない。snapshot の `--group` の検証は `validate_account_group` だけを通す別の規則で、表に無いため含めない。 - -根拠: Value 3(MVV 版 1) - -### 決定 5: `environment` の形ごとの分岐を消すため、形をキーにした対応表と形ごとの関数を使う - -`_env_shape` が既に形を 4 つの文字列に決めており、キーがそのまま揃う。形ごとの関数は伏せた値を返し、`None` は -「`environment` を作らない」(元から無く機密も無いとき)を表す。置く処理を `_mask_secret_environment` に 1 つ残すと、 -形ごとの関数はサービスの辞書を書き換えず、値を返すだけになる。 - -`if` の連なりのまま関数を分ける形は、C901 が 5 以下に下がるが、形を 1 つ足すたびに分岐も足す必要があり、要求が -並べる 2 つの形のうち対応表のほうが形と処理の対を 1 か所で読める。 - -根拠: Value 3 / C1(MVV 版 1) - -### 決定 6: `_run_post_start` の引数を 4 個にするため、起動した構成の 4 つの値を凍結した `_ComposeDeployment` にまとめる - -`project_name`・`scale`・`dev_service_name`・`compose_file` は、3 つの呼び出し元で常に組で渡り、個々の処理の 3 つが -同じ組を受ける。後処理の対象(`indices`)・`config`・`run_deploy` は呼び出しごとに意味が違うため、組に入れない。 -個々の処理(`_report_missing_repos` など)の引数は表に無く、テストが置き換えて呼び出しを見ているため変えない。 -確定仕様 `docs/specifications/post-start.md` は関数の形を書いているため、同じ Pull Request で新しい形へ直す。 - -`config` まで組に入れる形は、`config` が構成ではなくプロジェクトの設定で、`cmd_post_start` で構成を作らずに読む値で -あるため採らない。キーワード専用の引数へ変えるだけの形は、引数の数が減らず基準を満たさない。 - -根拠: Vision(MVV 版 1) - -### 決定 7: `env list` の重なりを消すため、節の 1 つ分を出す関数を作り、チーム共通とプロジェクトの節の選び方を別の関数に分ける - -2 か所の違いは、見出しの文字列と件数の行の頭の文字列だけで、見出し → 変数 → 件数の行の順は同じである。2 つの -文字列を受ける 1 つの関数にすると、出力の形が 1 か所になる。節の選び方(持ち主の巡回・ファイルの有無)は -チーム共通とプロジェクトで違うため、別の関数に残す。 - -根拠: Value 3(MVV 版 1) - -### 決定 8: R6・R7・R10 は段を抜き出すだけにし、`env keygen` の理由の注記は抜き出した関数へ一緒に移す - -3 件は 1 つの関数に段が並ぶ形で、段の境界で抜き出せば順を変えずに基準を満たせる。`cmd_env_keygen` の 93 行の -多くは、上書きの同意・読めない鍵の中止・TOCTOU を避ける `force` の渡し方の理由の注記である。注記はそれが説明する -コードの隣に置き、消さない(次に触る人が同じ検討を繰り返さないため)。生成の段(`generate_key_file` に `force` を -そのまま渡す注記)は `cmd_env_keygen` に残す。 - -根拠: Value 2 / Value 3(MVV 版 1) - -### 決定 9: `tests/volume/` の補助を 1 か所にするため、`tests/volume/conftest.py` に `_` を外した名前で置いて取り込む - -`tests/cli/conftest.py` の補助を `from tests.cli.conftest import ...` で取り込む形が既にある。取り込む名前に `_` を -付けると、モジュールの外から使う内部の名前になるため、`write_compose`・`load_scaled`・`mount_source` にする。 -2 つのテストの呼び出しは新しい名前へ書き換える(受け入れ条件は R11・R12 の移動を期待値の変更の外に置いている。 -`assert` の期待値は変えない)。`tests/volume/test_compose.py` の `_write_compose` は引数の形が違い、表に無いため触らない -(前提 6)。 - -新しいモジュール(`tests/volume/compose_helpers.py` など)を作る形は、置き場の前例が無く、要求が挙げた置き場 -(`conftest.py`)とも違うため採らない。 - -根拠: Vision(MVV 版 1) - -### 決定 10: 偽の OpenBao の `do_POST` は、経路ごとの関数に分ける(経路をキーにした対応表にしない) - -要求の未決の 2 つ目への答えである。`do_POST` の経路は「ログイン」と「それ以外のすべて(KV の書き込み)」の 2 つで、 -後者はパスの一致ではなく残りの全部である。対応表にすると、表に当たらないときの既定を別に持つ必要があり、2 つの経路に -対して仕組みが重い。KV の書き込みは 1 つの関数にしても認知的複雑度が 15 を超える見込みのため、書き込みの前の拒否 -(mount・認可・禁止・失敗の注入)と CAS の更新を更に抜き出す。`do_GET`・`do_DELETE` は表に無いため触らない。 - -根拠: Vision(MVV 版 1) - -### 決定 11: 振る舞いを丸ごと縛るテストが無い経路だけ、構造の変更の前に現状固定のテストを足す - -要求のテスト戦略のとおり、既存のテストで縛られている経路にはテストを足さない。今のテストは、`--group` の文を -TUI の部分一致(`'--group に使えない名前です' in ...`)でしか見ておらず、CLI の文は見ていない。`env list` は見出しと -グループの付いた件数の行を部分一致で見るが、「(変数なし)」と `--keys-only` の行は見ていない。決定 4 と決定 7 は -ちょうどこの文と行を組み直すため、変更の前のコードで通る現状固定のテストを先に足す。 - -根拠: Value 3(MVV 版 1) - -## テスト設計 - -### 受け入れ条件 - -| 受け入れ条件 | どの振る舞いで縛るか | どう壊したら落ちるべきか | -| --- | --- | --- | -| 13 件が「直す」か「直さない」に分かれ、「直さない」に理由がある | 決定 1(すべて「直す」)。実装で移した件は Pull Request の本文 | — (文書の確認) | -| 「直す」件が基準を満たし、測ったコマンドと結果が本文にある | 非機能の実現方式の測定 | 抜き出した先へ長さを移しただけの関数が 50 行か 15 を超える | -| 抜き出した関数も long_method の基準を満たす | 同上(抜き出した関数も測定の表に載せる) | 同上 | -| `env sync`(AWS)・`env list`・`env keygen`・TUI のグループの選択・`snapshot restore`・post-start の出力・終了コード・書き込みが同じ | I1・I4〜I8 の行 | 各行の壊し方 | -| R11・R12 の移動を除き、期待値を変えていない | `git diff` で `tests/` の `assert` の行を見る(足したテストと R11・R12 の名前の置き換えを除く) | 期待値を書き換えて通す | -| `--group` に使えない名前の文が同じ | I2 の行 | I2 の壊し方 | -| スケールで作るファイルで機密の値が書かれない | I3 の行 | I3 の壊し方 | -| 全体のテストが通る | `env -u DEVBASE_ROOT uv run --locked pytest tests/ -q` | どれかが落ちる | -| lint と固有の語の検査が通る | `uvx ruff check --select=E9,F63,F7,F82 lib`・`python3 .github/scripts/proper_term_check.py`(「語の一覧が無いため飛ばした」と出ないこと) | 未定義の名前を残す | -| 表に無い関数を変えていない | `git diff` の hunk の関数名を、構成要素の表の「変えないもの」と突き合わせる | `_backup_before_restore` などを触る | - -### 不変条件 - -| 不変条件 | どの振る舞いで縛るか | どう壊したら落ちるべきか | -| --- | --- | --- | -| I1 | 既存の `test_env_host_import.py`・`test_env_sync_group.py`・`test_env_sync_owner.py`(控えが `aws_profiles`・`tar_base64`・無しの各経路) | `_aws_plan_for_unregistered` で集合が一致しても丸ごとにしない / 選択外の値で書いてしまう / 衝突の警告を出さない | -| I2 | 足す現状固定のテスト: CLI の `--group global`(予約語)と `--group 1234`(数字だけ)の誤りの文を丸ごと比べる。TUI で「名前を入力」に `1234` と `projects` を入れたときの誤りの行を丸ごと比べる | TUI の前段を落とす(`1234` の文に「グループ名: 」が付く)/ CLI に前段を足す(文から「グループ名: 」が消える) | -| I3 | 既存の `tests/volume/test_compose_secret_env.py` | dict の形で機密の値を残す / list の形で `KEY=value` を残す / 元から無い `environment` を機密が無いのに作る | -| I4 | 既存の `test_container_post_start.py`・`test_container_scale_order.py`・`tests/cli/test_post_start_command.py` | 個々の処理の順を入れ替える / `project post-start` で `./deploy` を走らせる / 対象が空でも token を発行する | -| I5 | 足す現状固定のテスト: チーム共通・個人の共通・プロジェクトの節があるときの `env list` の標準出力を丸ごと比べる(変数の無い節と `--keys-only` の有無を含む)。既存の `test_env_user_axis.py` の節の選び方と `test_env_group_label.py` の件数の行 | 件数の行の頭をプロジェクトの節で `label` だけにする(グループの接尾を落とす)/ 変数の無い節で「(変数なし)」を出さない | -| I6 | 既存の `tests/commands/test_env_keygen.py` | 読めない鍵の判定を同意の後へ移す / `--force` を `True` に固定して渡す / 既存の鍵の表示で 1 を返す | -| I7 | 既存の `tests/commands/test_env_project.py` | `generate: token` の既定の長さを変える / 必須の空で真を返す | -| I8 | 既存の `tests/snapshot/` の復元のテスト(`point`・偽の rename・控えのバックアップ) | `point` の検査を控えのバックアップの後へ移す / `point` を超える incr を展開する | -| I9 | 偽の OpenBao を使う既存のテスト(`tests/env/`・`tests/commands/` の openbao の系) | CAS が合わないのに書く / `drop_write_response` で応答を返す | -| I10 | 非機能の実現方式の測定 | 抜き出した関数が基準を超える / 対象のファイルのほかの関数の値が上がる | - -## 設計の結果 - -| 課題 | 扱い | 取り込み先 | 触るファイル | -| --- | --- | --- | --- | -| #444 | 実装する | — | `lib/devbase/commands/env.py`、`lib/devbase/commands/container.py`、`lib/devbase/tui/actions_env_keys.py`、`lib/devbase/volume/compose.py`、`lib/devbase/snapshot/manager.py`、`tests/conftest.py`、`tests/volume/`、`tests/commands/`、`tests/cli/tui/`、`docs/specifications/post-start.md` | - -## 未確認のまま残ること - -| 項目 | 内容 | -| --- | --- | -| 抜き出した関数の測定の値 | 設計の時点では見込みで、R13 の `_write_secret` と R1 の 2 つの経路の関数が 15 以下に収まるかは実装で測って決める。超えたら更に段を抜き出す(決定 10 の方針)。それでも満たせなければ決定 1 の「直さない」へ移す | -| `cmd_env_keygen` の注記を移した後の行数 | 注記を消さない(決定 8)ため、抜き出した関数が注記込みで 50 行以下に収まるかは実装で数える | -| 足す現状固定のテストの置き場 | I2 と I5 のテストを既存のどのファイルへ足すかは実装(`tdd-cycle`)で決める。変更の前のコードで通ることを先に確かめる | diff --git a/issues/issue-444-requirements.md b/issues/issue-444-requirements.md deleted file mode 100644 index 0afbb8fb..00000000 --- a/issues/issue-444-requirements.md +++ /dev/null @@ -1,185 +0,0 @@ -# #444: PR #442 の構造改善で時間の枠を超えて見送った提案の残り 13 件(_aws_sync_plan の _SyncContext 化・env list / keygen の長い関数・テストの補助の重複) - -正は課題の本文(#444)で、この文書はその写しである。`spec-copy.py write` で作り直す。手で直さない。 - -## 何を見つけたか - -PR #442(スプリント m10a)の構造改善(cross-refactoring)は、提案のうち 4 件を適用し、13 件を PR に入れなかった。内訳は、時間の枠(budget)を超えた見送りが 12 件、締め切りまでにコミットが無かったものが 1 件である(重複として外した 1 行は数えない)。`main` = `54c1c27`(2026-10-07)で、次の 13 件がそのまま残っている。 - -| 対象 | 兆候 | 結果 | -| --- | --- | --- | -| `lib/devbase/commands/env.py#_aws_sync_plan`(838 行) | long_method(認知的複雑度 21・CC 15・69 行・return 11 本。sync の他の関数は `_SyncContext` を受ける形に揃ったが、この関数だけ `sources`・`targets`・`store` を別々に受ける)。conditional_chain の提案も同じ変更 | not_done | -| `lib/devbase/tui/actions_env_keys.py#group_attrs`(182 行) | duplication | budget | -| `lib/devbase/volume/compose.py#_mask_secret_environment`(229 行) | conditional_chain・long_method | budget | -| `lib/devbase/commands/container.py#_run_post_start`(1276 行) | long_parameter_list | budget | -| `lib/devbase/commands/env.py#_collect_from_env_yml`(1371 行) | long_method | budget | -| `lib/devbase/commands/env.py#cmd_env_keygen`(1544 行) | long_method | budget | -| `lib/devbase/commands/env.py#cmd_env_list`(1051 行) | duplication・long_method | budget | -| `lib/devbase/snapshot/manager.py#SnapshotManager.restore`(408 行) | long_method | budget | -| `tests/volume/test_compose_vscode.py#_write_compose / _load_scaled / _mount_source`(31 行〜) | duplication | budget | -| `tests/volume/test_compose_group.py#_mount_source`(40 行) | duplication | budget | -| `tests/conftest.py#_Handler.do_POST`(222 行) | long_method | budget | - -着手の注意: - -- `_aws_sync_plan` の手順は改修計画の I-001 にある(入れ子の report の関数を外へ出す → `aws_profiles` の分岐と控えの無い分岐を抽出 → `(ctx)` を受けて振り分けるだけにする)。確かめるテストは `tests/commands/test_env_host_import.py`・`test_env_sync_group.py`・`test_env_sync_owner.py` -- `tests/volume/` の 2 つの `_mount_source` は同じ補助の重複。まとめるなら共通の置き場(`conftest.py` など)へ移す - -提案ごとの理由と手順は改修計画にある(I-001 と「見送った提案」の節): https://github.com/devbasex/devbase/pull/442#issuecomment-6031923142 - -## どこで見つけたか - -PR #442 の検査のプランの cross-refactoring。想定最大時間は 30 分で、改修計画を立てた時点で使える時間は 6.92 分だった。スプリント m10a の振り返りで、見送った提案が起票されていないことを見つけた(#310 は同じ形で PR #308 から起票したもの)。 - -## なぜこの変更の範囲外なのか - -構造改善は時間の枠の中で入る分だけを適用する。13 件は枠に入らなかった。振る舞いの不具合ではなく、#425 #397 #310 #333 の受け入れ条件のどれにも関わらない。 - -## 直さないと何が起きるか - -振る舞いは変わらない。`env sync` の AWS の経路と `env list`・`env keygen` は、次に触る変更で読み解く手間がかかる。`_aws_sync_plan` だけが `_SyncContext` を受けない形のまま残り、#310 で揃えた sync の関数の受け方が 1 か所だけ違う。 - -## 由来 - -PR #442(issue #310 と同じスプリント m10a)の振り返りで見つけた。 - -## 依頼(原文) - -起票の時点の受け入れ条件を、そのまま写す。下の「受け入れ条件」はこれを観測できる形へ言い換えたもの。 - -> - [ ] 表の 13 件のそれぞれを、直す(Pull Request に入れる)か直さない(理由を本文に書く)かに分け、直したものは表の兆候が消えている -> - [ ] 直しても本番の振る舞い(`env` のコマンドと TUI、`snapshot restore`、`up` の起動の後の処理の出力・終了コード・書き込み)は変わらない。既存のテストが手を入れずに通るか、テストを直したときは理由を本文に書く -> - [ ] 全体のテスト(`env -u DEVBASE_ROOT uv run --locked pytest tests/ -q`)と CI と同じ lint が通る - -## 目的 - -- PR #442 の構造改善で時間の枠に入らなかった提案を片付け、`env sync` の AWS の経路・`env list`・`env keygen` などを、次に触る変更で読み解ける大きさにする -- sync の関数の受け方を `_SyncContext` に揃え、#310 で揃えた形の例外を無くす - -## 前提 - -- 前提 1: 「13 件」は改修計画(PR #442 のコメント)の「見送った提案」の行のうち、重複として外した 1 行を除いた 13 行で、1 件は「対象の関数 × 兆候」の組である。起票の表は 11 行だが、`cmd_env_list` と `_mask_secret_environment` が兆候を 2 つ持つため 13 件になる。下の「提案の一覧」の R1〜R13 がその 13 件である -- 前提 2: 「兆候が消えている」は、兆候ごとに下の「兆候が消えたと見なす基準」で判定する。基準の数値は、この課題で決めた値である(NDF の構造改善に兆候ごとの決まった閾値は無い)。設計で基準を変えるときは、変えた事実と理由を本文に残す -- 前提 3: 行数は `def` の行から関数の最後の行までを数える(docstring を含む)。認知的複雑度は complexipy、循環的複雑度は ruff の C901(McCabe)の値を使う。現状の値は `main` = `54c1c27` で測った -- 前提 4: 長い関数から抜き出した関数も、同じ基準(行数・認知的複雑度)を満たす。抜き出した先へ長さを移しただけのものは、兆候が消えたと見なさない -- 前提 5: R2(`group_attrs` の duplication)の重複の相手は、`lib/devbase/commands/env.py#_target_group` の `--group` の名前の検証(`openbao.storage_group(group)` を通し、通らなければ「`--group に使えない名前です: …`」を出す)である。提案の時点の記録に相手が書かれていないため、要求の時点でコードから特定した -- 前提 6: `tests/volume/test_compose.py` にも `_write_compose`・`_load_scaled` があるが、引数の形が違い、表に無い。この課題では扱わない -- 前提 7: 直さないと決めた件は、その件の行に「直さない」と理由を本文(Pull Request の本文か、この課題の本文)に書く。理由の無い「直さない」は受け入れない - -## 対象範囲 - -含む: - -- 提案の一覧の R1〜R13 の、直すか直さないかの判断と、直すものの構造の変更 -- 構造の変更に合わせた、関数の呼び出し元の書き換え(同じモジュールの中と、R2 の `_target_group`) -- テストの補助を `tests/volume/` の共通の置き場へ移すこと(R11・R12) - -含まない: - -- 本番の振る舞いの変更(出力の文言・終了コード・置き場やファイルへの書き込み・ログの文言) -- 表に無い関数の構造改善(`SnapshotManager._backup_before_restore`・`tests/volume/test_compose.py` の補助など、測ると値が高いものも含めない) -- 公開のコマンドの引数・オプションの変更 -- `CHANGELOG.md` の記入(利用者に見える変更が無いため。AGENTS.md の Pull Request の節) - -## 提案の一覧 - -| # | 対象 | 兆候 | 現状の値(`main` = `54c1c27`) | -| --- | --- | --- | --- | -| R1 | `lib/devbase/commands/env.py#_aws_sync_plan` | long_method | 69 行・認知的複雑度 21・C901 15。`sources`・`targets`・`store` を別々に受ける | -| R2 | `lib/devbase/tui/actions_env_keys.py#group_attrs` | duplication | `--group` の名前の検証が `commands/env.py#_target_group` と 2 か所にある(前提 5) | -| R3 | `lib/devbase/volume/compose.py#_mask_secret_environment` | conditional_chain | `environment` の形(none / dict / list / その他)の分岐が 1 つの関数に並ぶ。C901 8 | -| R4 | `lib/devbase/volume/compose.py#_mask_secret_environment` | long_method | 58 行・認知的複雑度 23 | -| R5 | `lib/devbase/commands/container.py#_run_post_start` | long_parameter_list | 引数 7 個 | -| R6 | `lib/devbase/commands/env.py#_collect_from_env_yml` | long_method | 33 行・認知的複雑度 19 | -| R7 | `lib/devbase/commands/env.py#cmd_env_keygen` | long_method | 93 行・認知的複雑度 13 | -| R8 | `lib/devbase/commands/env.py#cmd_env_list` | duplication | 節の見出し・変数の表示・件数の行を出す処理が、チーム共通の節とプロジェクトの節の 2 か所にある | -| R9 | `lib/devbase/commands/env.py#cmd_env_list` | long_method | 49 行・認知的複雑度 21 | -| R10 | `lib/devbase/snapshot/manager.py#SnapshotManager.restore` | long_method | 60 行・認知的複雑度 15 | -| R11 | `tests/volume/test_compose_vscode.py#_write_compose / _load_scaled / _mount_source` | duplication | 同じ 3 つの補助が `tests/volume/test_compose_group.py` にもある | -| R12 | `tests/volume/test_compose_group.py#_mount_source` | duplication | R11 の `_mount_source` と同じ補助 | -| R13 | `tests/conftest.py#_Handler.do_POST` | long_method | 46 行・認知的複雑度 20・C901 13 | - -R1 は改修計画の not_done の行(I-001)で、手順 4 の「`(ctx)` を受けて振り分けるだけにする」を含む。R11 と R12 は同じ補助の重複だが、改修計画では 2 行に分かれているため 2 件に数え、それぞれ判定する。 - -## 兆候が消えたと見なす基準 - -| 兆候 | 基準 | -| --- | --- | -| long_method | 対象の関数と、そこから抜き出した関数のそれぞれが、50 行以下かつ認知的複雑度 15 以下 | -| conditional_chain(R3) | `environment` の形ごとの処理が形ごとに別の関数(または形をキーにした対応表の 1 項目)になり、`_mask_secret_environment` の C901 が 5 以下 | -| long_parameter_list(R5) | `_run_post_start` の引数が 4 個以下 | -| duplication(R2) | `--group` の名前の検証(`storage_group` を通し、通らなければ「`--group に使えない名前です: …`」の文を作る)が 1 つの関数にあり、`_target_group` と `group_attrs` の両方がそれを呼ぶ | -| duplication(R8) | 節の見出し・変数の表示・件数の行を出す処理が `cmd_env_list` の中で 1 か所(1 つの関数)にある | -| duplication(R11) | `tests/volume/test_compose_vscode.py` に `_write_compose`・`_load_scaled`・`_mount_source` の定義が無く、同じ補助の定義が `tests/volume/` の 1 か所にある | -| duplication(R12) | `tests/volume/test_compose_group.py` に `_mount_source` の定義が無く、R11 と同じ 1 か所の定義を使う | -| R1 の追加の基準 | `_aws_sync_plan` が `_SyncContext` 1 つを受け、`_sync_credential_sources` からの呼び出しが `_aws_sync_plan(ctx)` の形になる | - -## ドメインイベント - -| # | イベント | 引き金 | 失敗したとき | 順序の前提 | -| --- | --- | --- | --- | --- | -| E1 | 着手前の全体のテストが通った | 実装の着手 | 既存の失敗があれば、構造の変更の前に記録して人へ戻す(この課題では直さない) | — | -| E2 | 13 件のそれぞれを直すか直さないかに分けた | 設計 | 決められない件は「直さない」にして理由を書く(前提 7) | E1 | -| E3 | 直す件の構造を変えた | 実装 | 基準を満たせない件は、変更を戻して「直さない」にし、理由を書く | E2 | -| E4 | 変更の後の全体のテストと lint が通った | E3 の後の検査 | テストが落ちたら、テストではなく構造の変更を直す。テストを直すときは理由を本文に書く | E3 | -| E5 | 直した件の兆候が基準で消えたことを測った | E4 の後の測定 | 基準を満たさない件は E3 へ戻すか、「直さない」へ移す | E4 | - -## 受け入れ条件 - -- [ ] R1〜R13 の 13 件のそれぞれが「直す」か「直さない」に分かれ、「直さない」の件には理由が本文に書かれている -- [ ] 「直す」とした件は、「兆候が消えたと見なす基準」の該当の行を満たす。基準の値を測ったコマンドと結果が Pull Request の本文にある -- [ ] 直した関数から抜き出した関数も、long_method の基準(50 行以下・認知的複雑度 15 以下)を満たす -- [ ] `env sync`(AWS の経路)・`env list`・`env keygen`・`env` の TUI のグループの選択・`snapshot restore`・`up` の起動の後の処理(post-start)の、標準出力・標準エラー・終了コード・ファイルと置き場への書き込みが変更の前と同じである。既存のテストが手を入れずに通ることで確かめる -- [ ] R11・R12 のテストの補助の移動を除き、`tests/` の既存のテストの期待値(assert の中身)を変えていない。変えたときは、変えたテストと理由が Pull Request の本文にある -- [ ] `--group` に使えない名前を渡したときの文(CLI の `--group に使えない名前です: …` と TUI で選択へ戻る前のエラーの行)が変更の前と同じである -- [ ] `compose` のスケールで作るファイル(`.docker-compose.scale.yml`)で、機密のキーの値が書かれず、非機密の値が残る振る舞いが変わらない(`tests/volume/test_compose_secret_env.py` が手を入れずに通る) -- [ ] 全体のテスト(`env -u DEVBASE_ROOT uv run --locked pytest tests/ -q`)が通る -- [ ] CI と同じ lint(`uvx ruff check --select=E9,F63,F7,F82 lib`)と固有の語の検査(`python3 .github/scripts/proper_term_check.py`)が通る -- [ ] 表に無い関数(「含まない」に挙げたもの)を変えていない - -## 非機能の条件 - -| 大項目 | 条件 | -| --- | --- | -| 運用・保守性 | 「兆候が消えたと見なす基準」を満たす。変更の後、対象のファイルの関数で C901 と認知的複雑度が変更の前より上がったものが無い | -| セキュリティ | R3・R4 の変更の後も、機密のキーの値がスケールで作るファイルへ書かれない(`compose` の機密のマスクの振る舞いを保つ)。R7 の変更の後も、`env keygen` が鍵の値を標準出力とログへ出す範囲が変わらない | - -## 影響 - -| 対象 | 影響 | -| --- | --- | -| 公開インタフェース | 変わらない(コマンド・オプション・出力・終了コード)。モジュールの内部の関数(`_` で始まるもの)と `actions_env_keys.group_attrs` の中身の分け方だけが変わる | -| データ | 変わらない。置き場・設定ファイル・ボリュームの形と移行は無い | -| 既存の振る舞い | 変わらない | - -## 検証手段 - -| 項目 | 手段 | -| --- | --- | -| テスト | `env -u DEVBASE_ROOT uv run --locked pytest tests/ -q` | -| 範囲のテスト | `tests/commands/test_env_host_import.py`・`test_env_sync_group.py`・`test_env_sync_owner.py`(R1)、`tests/volume/test_compose_secret_env.py`(R3・R4)、`tests/commands/test_container_post_start.py`・`tests/cli/test_post_start_command.py`(R5)、`tests/volume/test_compose_vscode.py`・`test_compose_group.py`(R11・R12)、`env list`・`env keygen`・`snapshot restore`・TUI の env のテスト(R2・R6〜R10)、R13 の `do_POST` を使うテスト | -| 静的解析 | `uvx ruff check --select=E9,F63,F7,F82 lib`、`python3 .github/scripts/proper_term_check.py` | -| 基準の測定 | `uvx ruff check --select C901 --config 'lint.mccabe.max-complexity=0' <ファイル>`(C901)、`uvx complexipy <ファイル>`(認知的複雑度)、行数と引数の数は `ast` で関数の `lineno`・`end_lineno`・引数を数える。変更の前後の値を Pull Request の本文に並べる | - -## 前提とする取り決め - -| 項目 | 参照先 / 決めたこと | -| --- | --- | -| プロジェクト構造 | `AGENTS.md` の「このリポジトリ」。実装は `lib/devbase/`、テストは `tests/`。テストの共通の補助は `tests/volume/conftest.py` など `tests/volume/` の中に置き、`lib/` へ置かない | -| コーディング規約 | 周りのコードの書き方に合わせる(`AGENTS.md` の「書き方」)。lint は上の検証手段 | -| テスト戦略 | 振る舞いは既存のテストで固定する(構造の変更のためにテストを足すのは、既存のテストが振る舞いを固定していない経路だけ。足したテストは変更の前のコードで通る)。兆候の消滅は測定で確かめる | - -## 境界 - -| 区分 | 内容 | -| --- | --- | -| 常に行う | 着手前と変更の後の全体のテスト、lint、基準の測定 | -| 確認してから行う | テストの期待値の変更(理由を本文に書いてから) | -| 行わない | 表に無い関数の構造改善、出力の文言・終了コードの変更、`env -u DEVBASE_ROOT` を外したテストの実行、base イメージの変更(P1)、実の環境での `devbase up`(P3) | - -## 未決 - -| 項目 | 誰が決めるか | 期限 | -| --- | --- | --- | -| 13 件のうち「直さない」とする件と、その理由 | 設計の担当(設計文書の決定の記録) | 設計 PR | -| R13(`tests/conftest.py#_Handler.do_POST`)を分ける形。偽のサーバの経路ごとに関数を分けるか、経路をキーにした対応表にするか | 設計の担当 | 設計 PR | From 1c661d797daaa55a9a12447fc5214862787bb042 Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Thu, 8 Oct 2026 23:38:54 +0900 Subject: [PATCH 3/3] =?UTF-8?q?Docs:=20--group=20=E3=81=AE=E5=90=8D?= =?UTF-8?q?=E5=89=8D=E3=81=AE=E6=A4=9C=E8=A8=BC=E3=82=92=201=20=E3=81=8B?= =?UTF-8?q?=E6=89=80=E3=81=AB=E3=81=BE=E3=81=A8=E3=82=81=E3=81=9F=E3=81=93?= =?UTF-8?q?=E3=81=A8=E3=82=92=E7=A2=BA=E5=AE=9A=E4=BB=95=E6=A7=98=E3=81=AB?= =?UTF-8?q?=E6=9B=B8=E3=81=8F=EF=BC=88#444=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 Claude-Session: https://claude.ai/code/session_015XbbCFwDscRn71xF8sXn5z --- docs/specifications/secret-backend.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/specifications/secret-backend.md b/docs/specifications/secret-backend.md index f69fce6b..271ba33b 100644 --- a/docs/specifications/secret-backend.md +++ b/docs/specifications/secret-backend.md @@ -814,7 +814,7 @@ TUI の「OpenBao の接続設定」は同じプロセスの中から `use openb | 欄 | 接続先(`url`、1 行)、`role_id` と `secret_id`(伏せ字)。空の欄は変えない(`use` の引き継ぎ)。`role_id` を変えるときは `secret_id` も要る。`mount` / `user` / `layout` / `group_aliases` / キャッシュの設定は変えない | | 保存の後 | `test` と同じ確認を自動で行う。失敗しても保存した設定は残し、欄を入れ直せる。`use` が失敗したら `test` を呼ばずに欄へ戻る | | 確認のグループ | グループ別の置き場(`version: 2`・`layout: group`)で、TUI の現在地(`PWD`)がプロジェクトの外のときだけ、保存の後・確認の直前に対象のグループを選ばせ、`test` へ属性 `group`(CLI の `--group` と同じ)で渡す。選択は sync / init と同じ部品(`actions_env_keys.group_attrs`。候補は宣言済みのグループと「名前を入力」)。プロジェクトの中では選ばせず、`test` がグループの宣言から決める。`version: 1` では選ばせず `group` を渡さない | -| 使えないグループ名 | 「名前を入力」の名前は `validate_account_group` で検証し、続けて CLI の `--group` と同じ `storage_group` の検査(置き場の予約語 `global` / `projects`、`group_aliases` の読み替え先)を通す。どちらかで落ちれば「--group に使えない名前です: ...」を出してグループの選択へ戻る(`test` を呼ばない)。sync / init の選択も同じ部品を通る | +| 使えないグループ名 | 「名前を入力」の名前は `validate_account_group` で検証し、続けて CLI の `--group` と同じ `storage_group` の検査(置き場の予約語 `global` / `projects`、`group_aliases` の読み替え先)を通す。どちらかで落ちれば「--group に使えない名前です: ...」を出してグループの選択へ戻る(`test` を呼ばない)。sync / init の選択も同じ部品を通る。検証は CLI の `--group`(`_target_group`)と共通の `commands/env.py` の `check_group_option(settings, group, *, account_rule=False)` 1 か所で行い、TUI は `account_rule=True` で前段の `validate_account_group` も通す。そのため名前の規則で落ちた文は、TUI では接頭が無く、CLI では `storage_group` の中の同じ規則の文で「グループ名: 」が付く | | グループの選択で戻る | Esc・← で「接続を確かめずに戻ります。保存した設定は残っています。」を出し、`test` を呼ばず、入れ直しの問いも出さずに env メニューへ戻る(`ARG_CANCEL`。一時停止しない)。Ctrl-C は TUI 全体を終える | | 入れ直した後 | 入れ直しの問いで「はい」を選ぶと欄へ戻り、次に保存した後でもう一度グループを選ぶ | | token | 入力させず、保存しない(実行のたびにブートストラップ機密で取り直す) |