Skip to content
Merged
Show file tree
Hide file tree
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
37 changes: 26 additions & 11 deletions .claude/skills/python-uv-ci-setup/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,42 +1,48 @@
---
name: python-uv-ci-setup
description: uv を使う Python プロジェクトで、format/lint/静的型チェック/テスト/docstring ルールをローカルと GitHub Actions で一貫運用するためのセットアップPlaybook。`pyproject.toml` の `[dependency-groups]`、`.pre-commit-config.yaml`、`.github/workflows/ci.yml` を新規作成または更新し、`uv run pre-commit install` まで完了させる依頼で使う。
description: uv を使う Python プロジェクトで、format/lint/静的型チェック/依存方向の検証/依存の衛生/テスト/docstring ルールをローカルと GitHub Actions で一貫運用するためのセットアップPlaybook。`pyproject.toml` の `[dependency-groups]` と `[tool.importlinter]`、`.pre-commit-config.yaml`、`.github/workflows/ci.yml`、`.github/dependabot.yml` を新規作成または更新し、`uv run pre-commit install` まで完了させる依頼で使う。
---

<!-- AUTO-GENERATED FILE. DO NOT EDIT DIRECTLY. -->
<!-- source: docs/ai/canonical/playbooks/python-uv-ci-setup.md + scripts/sync_ai_context.py -->

# Python uv CIセットアップ

このPlaybookでは、`uv + ruff + mypy + pytest + pre-commit + GitHub Actions` を最小差分で導入し、ローカルとCIの品質ゲートをそろえる。
このPlaybookでは、`uv + ruff + mypy + import-linter + deptry + pytest + pre-commit + GitHub Actions` を最小差分で導入し、ローカルとCIの品質ゲートをそろえる。

## 実行フロー

1. 前提を確認する。
- ルートに `pyproject.toml` があるか確認する。なければ `uv init` を提案する。
- ルートに `pyproject.toml` があるか確認する。なければ `uv init --package --build-backend uv` を提案する。
- `pyproject.toml` に `[build-system]` があるか確認する。src レイアウトでは必須(無いと `src/` 配下を mypy / import-linter / pytest が解決できない)。
- `uv --version` と `python --version` を確認する。
- Git管理下か確認する。未初期化なら `git init` を実行してから進む。

2. 既存設定を監査する。
- `pyproject.toml` の `[dependency-groups]`、`[tool.ruff]`、`[tool.mypy]`、`[tool.pytest.*]` を確認する。
- `.pre-commit-config.yaml``.github/workflows/*.yml` を確認する。
- `pyproject.toml` の `[dependency-groups]`、`[tool.ruff]`、`[tool.mypy]`、`[tool.pytest.*]`、`[tool.coverage.*]`、`[tool.importlinter]`、`[tool.deptry]` を確認する。
- `.pre-commit-config.yaml``.github/workflows/*.yml`、`.github/dependabot.yml` を確認する。
- 既存設定がある場合は上書きせず、重複を避けて統合する。

3. `pyproject.toml` を `uv` 前提で整備する。
- 開発依存を `dependency-groups.dev` に集約する。
- 最低限の開発依存をそろえる: `ruff`, `mypy`, `pytest`, `pre-commit`。
- 最低限の開発依存をそろえる: `ruff`, `mypy`, `import-linter`, `deptry`, `pytest`, `pytest-cov`, `pre-commit`。
- ルールは `docs/ai/playbook-assets/python-uv-ci-setup/references/templates.md` の `pyproject.toml` テンプレートを基準にし、既存プロジェクトに合わせて微調整する。
- `[tool.importlinter]` の `your_project` を実際のパッケージ名に置換する。まだ無い層は `"(ports)"` のように括弧で囲んで省略可能にする。
- `forbidden_modules` にプロジェクトで使う外部技術(Web フレームワーク、DB / HTTP クライアント)を足す。

4. pre-commit を設定する。
- `.pre-commit-config.yaml` を作成または更新する。
- `pre-commit-hooks` の基本フック(秘密鍵・巨大ファイル・TOML/YAML 構文・行末)を入れる。
- `uv-pre-commit` の `uv-lock` を入れてロックファイル整合を強制する。
- `uv run` 経由で `ruff format --check`、`ruff check`、`mypy` を実行する。
- `uv run` 経由で `ruff format --check`、`ruff check`、`mypy`、`lint-imports`、`deptry` を実行する。
- `pytest` は既定で `pre-push` に配置して開発体験を維持する。全コミットで必須にしたい場合は `stages` を `pre-commit` に変更する。

5. GitHub Actions を設定する。
5. GitHub Actions と Dependabot を設定する。
- `.github/workflows/ci.yml` を作成または更新する。
- `actions/setup-python` と `astral-sh/setup-uv` を使い、`uv sync --locked --dev` の後に同等チェックを実行する。
- `permissions: contents: read` と `concurrency` を置く。
- `actions/setup-python` と `astral-sh/setup-uv` を使い、`uv sync --locked --dev` の後にローカルと同じ 6 本のチェックを実行する。
- キャッシュは `setup-uv` の `enable-cache: true` を基本にする。
- `.github/dependabot.yml` を作成し、`github-actions` と `uv` を週次更新にする。

6. ローカルセットアップを完了する。
- `uv lock`
Expand All @@ -48,24 +54,33 @@ description: uv を使う Python プロジェクトで、format/lint/静的型
- `uv run ruff format --check .`
- `uv run ruff check .`
- `uv run mypy .`
- `uv run lint-imports`
- `uv run deptry src`
- `uv run pytest -q`
- import-linter が層を見つけているか確かめる: `domain` から `adapters` を import する行をわざと 1 つ足し、`uv run lint-imports` が失敗することを確認してから戻す。契約が層を見つけられていないと静かに通ってしまうため、この確認を省略しない。

8. 結果を報告する。
- 追加・更新したファイル
- 実行コマンドと結果
- 残課題(既存コード由来のlint/type/test失敗など
- 実行コマンドと結果(逆依存で失敗した確認を含む)
- 残課題(既存コード由来のlint/type/test失敗、`fail_under` に届かないカバレッジなど

## 運用ルール

- 型チェックは `mypy` に固定し、`ty` は使わない。
- 型ヒントは必須(`disallow_untyped_defs`)。テストにも同じ基準を適用し、override で緩めない。
- 依存方向(`adapters -> application -> ports -> domain`)と `domain` / `ports` の外部技術への非依存は import-linter で検証する。文書の約束だけにしない。
- import-linter の契約を緩める(`ignore_imports` を足す)ときは理由をコメントに残す。例外が増えるなら設計を見直す。
- docstring は Google style を採用し、短文 1 行のみの記述を避ける。
- docstring の先頭では「何をする処理か」「どの条件で使うか」を日本語で具体的に説明する。
- 引数がある処理は `Args`、戻り値がある処理は `Returns`、例外を送出しうる処理は `Raises` を記載する。
- `pydocstyle` の `convention = "google"` を有効化し、必要に応じて日本語運用に不要なルールのみ最小限で除外する。
- `project.requires-python` を定義し、Ruff のバージョン推論と整合させる。
- カバレッジ閾値(`fail_under`)を下げるときは理由を ADR に残す。
- CI とローカルで実行コマンドを一致させる。

## 参照ファイル

- 設定方針と採用理由: `docs/ai/playbook-assets/python-uv-ci-setup/references/tooling-best-practices.md`
- そのまま適用できる雛形: `docs/ai/playbook-assets/python-uv-ci-setup/references/templates.md`
- 依存方向を機械検証する判断: `docs/adr/0002-enforce-hexagonal-dependencies-with-import-linter.md`
- 品質ゲート拡充の判断: `docs/adr/0003-expand-ci-quality-gates.md`
9 changes: 9 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
groups:
actions:
patterns: ["*"]
4 changes: 2 additions & 2 deletions .github/workflows/ai-context-sync.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ jobs:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v7

- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@v7
with:
python-version: "3.12"

Expand Down
47 changes: 47 additions & 0 deletions docs/adr/0002-enforce-hexagonal-dependencies-with-import-linter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# ADR 0002: Hexagonal の依存方向を import-linter で機械検証する

最終更新: 2026-09-12
- ステータス: 承認済み(accepted)
- 決定者: shogo-hs
- 関連: `docs/ai/canonical/coding-standards.md`, `docs/ai/canonical/playbooks/python-uv-ci-setup.md`, `docs/ai/playbook-assets/python-uv-ci-setup/references/templates.md`, [ADR 0003](./0003-expand-ci-quality-gates.md)

## 1. 文脈

- 本テンプレートは Hexagonal Architecture(`adapters -> application -> ports -> domain`)を採用し、「domain は外部技術へ依存しない」を設計原則にしている。
- しかし CI が回しているのは ruff / mypy / pytest だけで、依存方向は文書上の約束にとどまっていた。domain から adapters を import しても、ports に HTTP クライアントを持ち込んでも CI は通る。
- AI エージェントが実装する前提のテンプレートなので、文書の約束より機械の網のほうが違反を止めやすい。

## 2. 決定

- [import-linter](https://import-linter.readthedocs.io/) を開発依存に加え、`pyproject.toml` の `[tool.importlinter]` に 2 つの契約を置く。
- `layers` 契約: `adapters`, `application`, `ports`, `domain` の順で、上から下へだけ import を許す。
- `forbidden` 契約: `domain` と `ports` から Web フレームワーク・DB / HTTP クライアント(`fastapi`, `sqlalchemy`, `httpx`, `requests`, `boto3` を初期値)への import を禁止する。`include_external_packages = true` で未インストールの名前も判定する。
- `uv run lint-imports` を pre-commit と GitHub Actions の両方に入れ、ローカルと CI で同じ契約を検証する。
- 導入時は逆方向の import をわざと 1 つ足して失敗することを確認してから戻す。契約が層を見つけられていないときは静かに通ってしまうため。
- 優先した要件: 設計原則の違反を PR の段階で止めること。追加ツールは 1 本に抑えること。

## 3. 代替案と不採用理由

- 代替案A: 文書(`docs/rules/code_architecture/README.md`)とレビューで守る。
- 不採用理由: 現状がこれで、違反を止められていない。レビューは人の注意力に依存する。
- 代替案B: ruff の `flake8-tidy-imports`(`banned-api`)で禁止 import を列挙する。
- 不採用理由: モジュール単位の禁止は書けるが、層の順序(推移的な依存を含む)は表現できない。
- 代替案C: 自前のスクリプトで `ast` を歩いて import を検査する。
- 不採用理由: import-linter が同じことを契約の宣言だけで行える。保守対象を増やさない。
- 代替案D: pytest のテストとして依存方向を検査する(`pytest-archon` など)。
- 不採用理由: 契約が Python コードに埋まり、設定として一覧できない。テストの失敗と設計違反が混ざる。

## 4. 影響

- コードへの影響: `src/<package>/` の各層に `__init__.py` が必要(bootstrap が生成する)。既存プロジェクトで逆依存があると CI が落ちるため、導入時に修正するか `ignore_imports` に理由つきで登録する。
- 運用への影響: `pyproject.toml` に `[build-system]` が必要になる(無いとパッケージが解決できず契約が評価できない)。契約を緩めるときは理由をコメントに残す。
- ドキュメントへの影響: `python-uv-ci-setup` Playbook、`templates.md`、`tooling-best-practices.md`、bootstrap が生成する `docs/rules/code_architecture/README.md`。

## 5. フォローアップ

- [ ] テンプレートから立ち上げたプロジェクトで、`ignore_imports` が増えていないかを定期的に見る。
- [ ] `forbidden_modules` の初期値が実プロジェクトの外部技術と合っているかを見直す。

## 6. 変更履歴

- 2026-09-12: 初版作成。
61 changes: 61 additions & 0 deletions docs/adr/0003-expand-ci-quality-gates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# ADR 0003: CI の品質ゲートを ruff / mypy / pytest の 3 本から 6 本へ拡充する

最終更新: 2026-09-12
- ステータス: 承認済み(accepted)
- 決定者: shogo-hs
- 関連: `docs/ai/canonical/playbooks/python-uv-ci-setup.md`, `docs/ai/playbook-assets/python-uv-ci-setup/references/tooling-best-practices.md`, [ADR 0002](./0002-enforce-hexagonal-dependencies-with-import-linter.md)

## 1. 文脈

- `python-uv-ci-setup` Playbook の品質ゲートは ruff format / ruff check / mypy / pytest だった。
- 次の穴があった。
- pytest-cov は dev 依存に入っているだけで、カバレッジの閾値が無い。
- 未使用・未宣言の依存を見ていない。
- mypy が「型ヒント必須」の方針に対して `disallow_untyped_defs` を持たず、型の無い関数が通る。
- GitHub Actions のバージョンが固定されたまま古くなり(checkout@v5 / setup-python@v6 / setup-uv@v7)、更新する仕組みが無い。
- bootstrap が `__init__.py` も build-system も生成しないため、生成直後のプロジェクトで `src/` 配下が import できず、CI が赤から始まる。

## 2. 決定

- 品質ゲートを次の 6 本にする: ruff format / ruff check / mypy / import-linter([ADR 0002](./0002-enforce-hexagonal-dependencies-with-import-linter.md))/ deptry / pytest(coverage 閾値つき)。
- ruff の規則に `S`(flake8-bandit 相当)・`SIM`・`C4`・`RUF`・`PTH` を足す。日本語 docstring の全角括弧を誤検知する `RUF001`〜`RUF003` と、パッケージ / `__init__` の docstring を求める `D104` / `D107` は除外する。
- mypy に `disallow_untyped_defs` / `disallow_incomplete_defs` / `strict_equality` を足す。テストにも同じ基準を適用する。src レイアウト用に `mypy_path = "src"` と `explicit_package_bases = true` を置く。
- coverage を `branch = true` で計測し、`fail_under = 80` を CI の合否にする。
- pre-commit に `pre-commit-hooks`(秘密鍵・巨大ファイル・TOML/YAML 構文・行末)と import-linter・deptry を足す。
- GitHub Actions は `permissions: contents: read` と `concurrency` を置き、action を現行メジャーに更新する。以後の更新は Dependabot(`github-actions` + `uv`)に任せる。
- build backend は uv 同梱の `uv_build` にする。
- bootstrap は各層と `tests/` に `__init__.py`、smoke テスト、`.gitignore` の coverage 出力を生成し、生成直後に 6 本すべてが通る状態にする。
- 優先した要件: 生成直後に CI が緑であること。ツールを増やしすぎないこと(bandit は ruff の `S` で代替)。ローカルと CI で同じコマンドを使うこと。

## 3. 代替案と不採用理由

- 代替案A: CI に `uv audit` または `pip-audit` を入れて脆弱性を止める。
- 不採用理由: `uv audit` は uv 0.11 以降の experimental で、実行のたびに preview 警告が出て仕様が変わりうる。`pip-audit` は依存が 1 本増える。既知の脆弱性の通知は GitHub の Dependabot alerts(リポジトリ設定・CI 不要)で受ける。強化したい場合の手順は `tooling-best-practices.md` に残す。
- 代替案B: mypy を `strict = true` にする。
- 不採用理由: 既存の段階導入方針を維持する。「型ヒント必須」に直接対応する 3 つのフラグだけで目的を満たす。
- 代替案C: bandit を別ツールとして入れる。
- 不採用理由: ruff の `S` 規則で同じ検査ができる。
- 代替案D: CI を lint / type / test の 3 ジョブに分けて並列化する。
- 不採用理由: キャッシュが効けば `uv sync` は数秒で、YAML が 3 倍になる利得が無い。ステップ分割で失敗箇所は分かる。
- 代替案E: `__init__.py` にパッケージ docstring を生成して `D104` を満たす。
- 不採用理由: 定型文を bootstrap で保守することになる。パッケージの docstring は処理意図を書く場所ではない。
- 代替案F: テストを mypy の override で緩める。
- 不採用理由: 「型ヒント必須」に反する。`tests/` に `__init__.py` が無いと override 自体が効かない。テスト関数に `-> None` を書くだけで済む。
- 代替案G: build backend に hatchling を使う。
- 不採用理由: `uv_build` は uv 同梱で追加依存が無く、`uv init --package` の既定と一致する。

## 4. 影響

- コードへの影響: 既存プロジェクトに適用すると、型の無い関数・未使用依存・`S` 規則違反・カバレッジ不足で CI が落ちる。段階導入する場合は `ignore` と `fail_under` を一時的に緩め、理由を ADR に残す。
- 運用への影響: 開発依存が 2 本増える(import-linter, deptry)。pre-commit の実行時間が伸びる(実測 14 秒程度、キャッシュ後)。Dependabot の PR が週 1 本届く。
- ドキュメントへの影響: `python-uv-ci-setup` Playbook、`templates.md`、`tooling-best-practices.md`、bootstrap スクリプト、`.github/dependabot.yml`。

## 5. フォローアップ

- [ ] テンプレートから立ち上げたプロジェクトの初回 PR で、GitHub Actions 上で 6 本が通ることを確認する。
- [ ] `fail_under = 80` が実プロジェクトで妥当かを見直す。
- [ ] `uv audit` が experimental を外れたら CI への追加を再検討する。

## 6. 変更履歴

- 2026-09-12: 初版作成。
2 changes: 2 additions & 0 deletions docs/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,5 @@
## ADR 一覧

- [0001: ADR運用を導入して設計判断を記録する](./0001-record-architecture-decisions.md) - 承認済み(accepted)
- [0002: Hexagonal の依存方向を import-linter で機械検証する](./0002-enforce-hexagonal-dependencies-with-import-linter.md) - 承認済み(accepted)
- [0003: CI の品質ゲートを ruff / mypy / pytest の 3 本から 6 本へ拡充する](./0003-expand-ci-quality-gates.md) - 承認済み(accepted)
Loading