Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions docs/development-history/01-2026-09-02.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# 開発履歴と知見 (2026-09-02)

**期間**: 2026-09-02
**対象**: [issue #139](https://github.com/devbasex/devbase/issues/139) / [PR #143](https://github.com/devbasex/devbase/pull/143)(設計)/ [PR #144](https://github.com/devbasex/devbase/pull/144)(実装)/ [PR #145](https://github.com/devbasex/devbase/pull/145)(v3.1.0)

`devbase build <image>` が必ず失敗する不具合を直した。`<image>` の位置引数が剥がされないまま
`docker buildx build` へ渡り、PATH が 2 つになっていた。CLI リファレンスに正式な構文として
載っているにもかかわらず、ベースイメージだけを再ビルドする手段が無い状態が続いていた。

単体ビルドの実装は shell と Python の 2 か所にあり、タグの規約も食い違っていた
(shell が `devbase-base:latest`、Python が `base:latest`)。どちらへ寄せるかを設計で決め、
Python へ集約したうえで、あわせて `v3.0.0` 以降に溜まっていた 11 件のマージを `v3.1.0` として
配布した。

## 何が起きたか

### 受け入れ条件は 2 度変わった

| いつ | 何が変わったか | きっかけ |
| --- | --- | --- |
| 設計 | AC8「単体ビルドの docker 呼び出しが 1 箇所」の対象から、shell の `build_base_image` を外した | compose ビルドの 1 段目であって単体ビルドではないため。Python へ寄せると `uv run` の往復が 3 回になり、振る舞いは変わらない |
| 実装レビュー | タグの接頭辞を「剥がしてから付け直す」から「剥がさない」へ反転した | `containers/xxx` と `containers/devbase-xxx` が同じタグを取り合い、ディレクトリ名とタグの 1:1 対応が崩れる |

どちらも取り消し線で旧記述を残し、理由と日付を添えて計画ファイルへ書いた。消して差し替えると、
「元の条件を満たしていない」のか「条件が変わった」のかがレビューで区別できなくなる。

### 手戻りは実装 PR に集中した

| PR | 内容 | round 1 | round 2 | 指摘の合計 |
| --- | --- | --- | --- | --- |
| #143 | 要求仕様 + 設計 | 両者 APPROVE | — | 0 |
| #144 | 実装 | codex APPROVE / gemini REQUEST_CHANGES | codex COMMENT / gemini APPROVE | 3(major 1 / minor 2) |

設計 PR が 1 round で通ったのは、依頼文が「確認が要る」としていた 2 点を設計の前に実測で
潰していたためと読める。

- `containers/` 配下 10 件すべてが `devbase-<name>` 規約に従うことを、`compose.yml` の `image:`・
他 Dockerfile の `FROM`・`snapshot/manager.py` の `SNAPSHOT_IMAGE` から確認した
- 「Python 側の単体ビルドは到達不能」という issue の記述が誤りで、`devbase project build <image>`
からは到達できることを確認した。この事実が「実装をどちらへ寄せるか」の決め手になった

一方、設計レビューでは「タグがディレクトリ名の単射になっているか」という観点が出ず、実装 PR で
初めて指摘された。仕様と設計のテキストだけを載せた差分では、写像の性質までは読み取りにくい。
ただし捕まえ直したコストは実装 30 行の書き換えで済んでおり、設計 PR を分けた価値
(shell / Python どちらへ寄せるかを実装前に固定できた)のほうが大きい。

### マージ前のテストは wrapper の前段を通らなかった

追加した 25 件のテストは、`bin/devbase` を実プロセスで起動しつつ `run_python` / `cmd_build` /
`compose_with_secrets` をスタブへ差し替える方式で書いた。既存の
`tests/cli/test_project_name_resolution.py` に倣ったもので、dispatch の振り分けは正しく固定できる。

しかしこの方式は、**dispatch へ入る前の name 解決を素通りする**。`_build_single_image` に入れた
image 名の検証(`[A-Za-z0-9][A-Za-z0-9._-]*`)も、name 解決に引数を消費されると届かない。

リリース後テストで実際に `devbase build ../etc` を打って初めて分かった。

```
$ devbase build ../etc
=== Building devbase images ===
[1/2] devbase-base already exists (use --no-cache to rebuild)
[2/2] Building project image...
no configuration file provided: not found
```

`$DEVBASE_ROOT/projects/../etc` が `$DEVBASE_ROOT/etc` に解決され、そこへ cd していた
([#146](https://github.com/devbasex/devbase/issues/146))。この挙動自体は今回の変更より前から
あり、`build` に限らず name 解決を通る全コマンドに共通する。

**見つかった場所が、仕様で「前提」として範囲外に置いた領域の隣だった。** 前提 2 で
「`<image>` と実在プロジェクト名の衝突は扱わない」と明記したため、レビューでもその周辺は
論点にならなかった。範囲外と宣言した領域は、レビューの視野からも外れる。

### 実機での確認は 3 分 18 秒で済んだ

`devbase build base --no-cache` のフルビルドは 3 分 18 秒(15:50:16 → 15:53:34)。
再ビルドしたイメージの中身(node v24.20.0 / gh 2.99.0 / `tmux-first` 10609 バイト)と、
`FROM devbase-base:latest` が解決できることまで確認した。マージ前に避けた理由(時間がかかる)は
実測に見合っていなかった。

## 次に変えること

| 変えること | 落とし先 | 状態 |
| --- | --- | --- |
| `bin/devbase` のテストに、name 解決を含む経路を 1 本通す。スタブ harness は dispatch 以降しか見ないため、前段の欠陥を固定できない | プロジェクトの取り決め(`tests/cli/`) | [#146](https://github.com/devbasex/devbase/issues/146) の修正に含める |
| 仕様で「前提」として範囲外に置いた項目を、リリース後テストで 1 度は実際に踏む。範囲外の宣言はレビューの視野からも外れるため、実機が最後の網になる | 次の変更で試すこと | [#146](https://github.com/devbasex/devbase/issues/146) へ由来として記録済み |
| 実行に時間がかかることを理由にマージ前の検証を落とすときは、所要時間を見積もりではなく実測で持つ。今回は 3 分 18 秒だった | 次の変更で試すこと | この記録に残す |

設計 PR を実装より先に通す運びは変えない。2 段階に分けたことで「shell / Python のどちらへ
寄せるか」という後戻りの高い判断を実装前に固定でき、実装 PR のレビューは実装の細部に
集中できた。設計段階で写像の性質まで捕まえられなかったのは、2 段階レビューが結果的に
補っている。

## 途中で起票した課題

| 番号 | 何を見つけたか | 見つけた場面 |
| --- | --- | --- |
| [#141](https://github.com/devbasex/devbase/issues/141) | CI が `tests/` の pytest を実行していない。`compileall` / `ruff`(構文相当のみ)/ `shellcheck` の 3 つだけで、テストが壊れても main へ入る | 設計中(回帰テストの追加先を確認していて気づいた) |
| [#142](https://github.com/devbasex/devbase/issues/142) | `devbase build <image>` の `<image>` が実在プロジェクト名と一致すると、name 解決に吸われて黙って別のものをビルドする。`projects/bi-tools` と `containers/bi-tools` が両方実在する | 設計中(全イメージで動くかを確認していて気づいた) |
| [#146](https://github.com/devbasex/devbase/issues/146) | name 解決が `projects/` の外へ出られる。`maybe_cd_project` が `-*` と空文字しか弾かない | リリース後テスト |

いずれも由来を `issue #139` の形で本文へ書き、#139 側にもコメントで番号を残した。
`gh issue list --state all --search "issue #139"` で 3 件とも引ける。

`quality-gates` の完了報告に挙げた「範囲外と判断したもの」は #141 / #142 の 2 件で、どちらも
起票済み。レビューの指摘 3 件はすべて修正しており、範囲外として resolve したものは無い。
取りこぼしは無かった。
Loading