From e420308dc29c5e71f070ee92552a15a07151babf Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 15 Jul 2026 04:44:35 +0000 Subject: [PATCH 1/4] =?UTF-8?q?feat(backend):=20devshell=20=E3=81=AE=20Pyt?= =?UTF-8?q?hon=20=E7=92=B0=E5=A2=83=E3=82=92=20uv2nix=20=E3=81=A7=20Nix=20?= =?UTF-8?q?build=20=E5=8C=96=EF=BC=88ADR-0021=20Phase=201=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit flake.nix に uv2nix / pyproject-nix / pyproject-build-systems を導入し、 backend の Python 依存(uv.lock 準拠)を Nix build の virtualenv (devforge-backend-env)として devshell に提供する。ローカルの backend/.venv を 廃止し、ADR-0021 の発端である VS Code の .venv 絶対パス直書きを解消する。 - flake.nix: uv2nix で uv.lock から pythonSet を構成(wheel 優先)。devshell の python / pytest / ruff / alembic / uvicorn は Nix build 環境から PATH 解決 - flake.lock: 新規 3 input を追加(transitive は全て follows で既存 nixpkgs に集約) - Makefile: .venv/bin/* 参照 10 箇所を PATH 解決へ置換。migrate / migrate-create の nix wrap 漏れも修正。install-backend は devshell ビルド確認のみに変更 - pyright: pyproject / pyrightconfig.json の venv=".venv" 参照を撤廃し、実行側が --pythonpath で明示する方式へ(ローカル = devshell python3、CI = uv sync の .venv) - .vscode/settings.json: python.defaultInterpreterPath の絶対パス直書きを撤廃し direnv(use flake)経由の PATH 解決へ(本 ADR の発端の解消) - docs / rules / skills: .venv/bin/* 前提のコマンド記載を一括更新 CI(test.yml / mutation.yml)の uv sync 経路は Phase 2 で Nix 化予定のため維持。 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01AmQqSTooETKvU1jDM2mCBS --- .claude/CLAUDE.md | 10 ++-- .claude/rules/backend/database.md | 2 +- .claude/rules/backend/python.md | 2 +- .claude/rules/backend/test.md | 2 +- .claude/rules/common/tdd.md | 2 +- .claude/skills/BE_apply/SKILL.md | 4 +- .claude/skills/BE_refacter/SKILL.md | 2 +- .claude/skills/SEC_review/SKILL.md | 2 +- .claude/skills/tdd/SKILL.md | 2 +- .github/workflows/test.yml | 8 ++-- .vscode/settings.json | 3 +- Makefile | 29 +++++------ backend/pyproject.toml | 8 ++-- docs/development.md | 14 +++--- flake.lock | 74 ++++++++++++++++++++++++++++- flake.nix | 56 ++++++++++++++++++---- pyrightconfig.json | 2 - scripts/gen-third-party-licenses.py | 2 +- 18 files changed, 168 insertions(+), 56 deletions(-) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index e55bdead..200c97d2 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -36,14 +36,16 @@ Makefile は `nix develop --command bash -c "..."` でラップ済み。AI は make に無い操作(特定ファイルだけ ruff したい等)の場合のみ使う: ```bash -nix develop --command bash -c "cd backend && .venv/bin/python -m ruff check app/services/tasks/handlers/blog_summarize.py" -nix develop --command bash -c "cd backend && .venv/bin/python -m pytest tests/test_worker_extended.py -q" +nix develop --command bash -c "cd backend && ruff check app/services/tasks/handlers/blog_summarize.py" +nix develop --command bash -c "cd backend && python -m pytest tests/test_worker_extended.py -q" nix develop --command bash -c "cd web && npm run test:e2e" ``` +python / pytest / ruff / alembic は devshell の Nix build 環境(`devforge-backend-env`)から PATH で解決される(`.venv` は廃止済み / ADR-0021 Phase 1)。 + ### 禁止: 生シェルでの直接実行 -`cd backend && .venv/bin/python -m pytest ...` を nix の外で叩くと、`LD_LIBRARY_PATH` / `DYLD_LIBRARY_PATH` が未設定で WeasyPrint のインポートが `OSError: cannot load library 'libgobject-2.0-0'` で落ちる。AI は nix wrap を必ず通す。 +`cd backend && python -m pytest ...` を nix の外で叩くと、backend の Python 環境(uv2nix build)自体が PATH に無く、あったとしても `LD_LIBRARY_PATH` / `DYLD_LIBRARY_PATH` が未設定で WeasyPrint のインポートが `OSError: cannot load library 'libgobject-2.0-0'` で落ちる。AI は nix wrap を必ず通す。 ### Sandbox と nix の競合(重要) @@ -109,7 +111,7 @@ CI 定義: `.github/workflows/ci.yml` テストの検出力(弱い assertion / 実装なぞり)を週次のミューテーションテストで可視化し、CI 結果は用途別 Slack チャンネルへ通知する。詳細(ローカル実行・レポート確認・Secrets 登録手順)は `docs/development.md`「ミューテーションテスト」「Slack 通知」節が正本。 -- **ローカル実行**: `make mutation-backend`(mutmut)/ `make mutation-web`(Stryker)。**フル実行は長時間**のため、対象を絞る場合は `nix develop --command bash -c "cd backend && .venv/bin/python -m mutmut run 'app.services.shared.sort_utils*'"` / `nix develop --command bash -c "cd web && npx stryker run --mutate 'src/utils/text.ts'"` +- **ローカル実行**: `make mutation-backend`(mutmut)/ `make mutation-web`(Stryker)。**フル実行は長時間**のため、対象を絞る場合は `nix develop --command bash -c "cd backend && python -m mutmut run 'app.services.shared.sort_utils*'"` / `nix develop --command bash -c "cd web && npx stryker run --mutate 'src/utils/text.ts'"` - **対象スコープの正本**: backend = `backend/pyproject.toml` の `[tool.mutmut]`、web = `web/stryker.conf.json`。決定論的ビジネスロジックに限定(schemas / models / routers / 自動生成コード等は対象外) - **CI**: `.github/workflows/mutation.yml`(週次 月曜 3:00 JST + workflow_dispatch。**PR/push では動かない・fail しない warn-only**) - **pytest の `--cov` は addopts に戻さない**: mutmut 干渉回避のため Makefile / test.yml の呼び出し側で付与している(ADR-0017) diff --git a/.claude/rules/backend/database.md b/.claude/rules/backend/database.md index aadd8e4a..cb77c422 100644 --- a/.claude/rules/backend/database.md +++ b/.claude/rules/backend/database.md @@ -19,7 +19,7 @@ paths: - **DROP COLUMN は原則 `op.drop_column` を直接使う**: SQLite/libSQL 3.35+ は `ALTER TABLE ... DROP COLUMN` をサポートする。インデックス・FK・制約のない素のカラムはこれで消せる(テーブル再作成不要) - **FK 参照される親テーブルに `batch_alter_table` を使わない**: batch は「新テーブル作成 → 旧テーブル DROP → リネーム」で動くため、`users` のように子テーブルから FK 参照される親を batch で触ると、旧テーブル DROP 時に libSQL(`foreign_keys=ON`)で `FOREIGN KEY constraint failed` になる。標準 SQLite ドライバは `foreign_keys` がデフォルト OFF で通ってしまい差異を見落とすので注意。子テーブル(他から参照されない)の `drop_column` でのみ batch は安全 - **マイグレーション/libsql ネイティブ経路は `make test-backend`(pytest)では通らないが、CI の `smoke-backend` ジョブで検証する**: pytest は `conftest.py` の `Base.metadata.create_all` でスキーマを作るため alembic を通らず、libsql ネイティブドライバの実行パスも踏まない。これを補うため CI は本番イメージを build → 実 libSQL を起動 → alembic 適用 → `/health`(DB 接続を検証)が 200 を返すことを確認する(`.github/workflows/ci.yml` の `smoke-backend`)。ローカルで個別に migration の upgrade/downgrade を確認したい場合は **実 libSQL に対して**行うこと: - - offline SQL の事前確認: `nix develop --command bash -c "cd backend && TURSO_DATABASE_URL='file:///tmp/x.db' .venv/bin/python -m alembic upgrade : --sql"` + - offline SQL の事前確認: `nix develop --command bash -c "cd backend && TURSO_DATABASE_URL='file:///tmp/x.db' alembic upgrade : --sql"` - 実適用: docker stack を起動(`make dev-build` / 本番 arch で再現したいときは `make dev-amd64-build`)し `docker compose logs api` で適用成功を確認する - 失敗した `batch_alter_table` が残す `_alembic_tmp_` テーブルは `turso db shell http://localhost:8080 "DROP TABLE IF EXISTS _alembic_tmp_
"` で掃除する diff --git a/.claude/rules/backend/python.md b/.claude/rules/backend/python.md index f3472dcf..066c6aca 100644 --- a/.claude/rules/backend/python.md +++ b/.claude/rules/backend/python.md @@ -8,7 +8,7 @@ paths: - ruff に準拠すること(設定: `backend/pyproject.toml`) - PEP8を守るな、PEP8を理解した上で抽象化しろ - コード変更後は `make lint-backend` を実行し、違反がないことを確認すること(Nix devshell 経由で ruff が解決される) -- 特定ファイルだけ検証したい場合は `nix develop --command bash -c "cd backend && .venv/bin/python -m ruff check "` を使う。生シェルで `.venv/bin/python` を直接叩くのは禁止(WeasyPrint の動的ライブラリが解決できず import に失敗するため) +- 特定ファイルだけ検証したい場合は `nix develop --command bash -c "cd backend && ruff check "` を使う(ruff は devshell の Nix build 環境から PATH 解決される / ADR-0021 Phase 1)。生シェルで python / ruff を直接叩くのは禁止(backend の Python 環境が PATH に無く、WeasyPrint の動的ライブラリも解決できない) - **lint 失敗時は当該ファイルだけ確認する**: `make lint-backend` が他ファイルの I001 等で落ちる場合、自分の変更分は上記の個別 `ruff check ` で検証してから進める(既存違反を巻き込まない) - 未使用の import を残さないこと(F401) - 重複検知 / DRY ポリシーは `.claude/rules/common/duplication.md` を参照(抽出先は `backend/app/services/shared/` または同一サブパッケージの `_utils.py`) diff --git a/.claude/rules/backend/test.md b/.claude/rules/backend/test.md index 7d34304a..c660f542 100644 --- a/.claude/rules/backend/test.md +++ b/.claude/rules/backend/test.md @@ -28,7 +28,7 @@ make test-backend # 全テスト 特定ファイルだけ回す場合: ```bash -nix develop --command bash -c "cd backend && .venv/bin/python -m pytest tests/test_worker_extended.py -q" +nix develop --command bash -c "cd backend && python -m pytest tests/test_worker_extended.py -q" ``` ### pytest が通らない経路(コンテナ起動スモーク) diff --git a/.claude/rules/common/tdd.md b/.claude/rules/common/tdd.md index ec7a71fb..ee70d7c6 100644 --- a/.claude/rules/common/tdd.md +++ b/.claude/rules/common/tdd.md @@ -30,7 +30,7 @@ paths: ```bash # backend -nix develop --command bash -c "cd backend && .venv/bin/python -m pytest tests/test_.py -q" +nix develop --command bash -c "cd backend && python -m pytest tests/test_.py -q" # web nix develop --command bash -c "cd web && npx vitest run src//.test.ts" ``` diff --git a/.claude/skills/BE_apply/SKILL.md b/.claude/skills/BE_apply/SKILL.md index 64f95b0a..17761e20 100644 --- a/.claude/skills/BE_apply/SKILL.md +++ b/.claude/skills/BE_apply/SKILL.md @@ -97,8 +97,8 @@ sandbox が `~/.cache/nix/fetcher-locks/*.lock` で落ちる場合は `dangerous 特定ファイルだけ確認したい場合のみ: ```bash -nix develop --command bash -c "cd backend && .venv/bin/python -m ruff check " -nix develop --command bash -c "cd backend && .venv/bin/python -m pytest -q" +nix develop --command bash -c "cd backend && ruff check " +nix develop --command bash -c "cd backend && python -m pytest -q" ``` lint / test に失敗したら、原因を直してから次の検証に進む。失敗を残したまま PR レポートを書かない。`--no-verify` 等で hook を skip しない。 diff --git a/.claude/skills/BE_refacter/SKILL.md b/.claude/skills/BE_refacter/SKILL.md index 8a8fa97a..02d6a923 100644 --- a/.claude/skills/BE_refacter/SKILL.md +++ b/.claude/skills/BE_refacter/SKILL.md @@ -220,6 +220,6 @@ backend/app/ - `make test-backend` - `make dupe-check`(重複検知。`report/dupe/jscpd-report.json` を生成。sandbox は無効化して実行) -特定ファイルだけ検証したい場合は `nix develop --command bash -c "cd backend && .venv/bin/python -m ruff check "` を使う。生シェルで `.venv/bin/python` を直接叩くのは禁止(WeasyPrint の動的ライブラリが解決できず import に失敗する)。 +特定ファイルだけ検証したい場合は `nix develop --command bash -c "cd backend && ruff check "` を使う(devshell の Nix build 環境から PATH 解決 / ADR-0021 Phase 1)。生シェルで python / ruff を直接叩くのは禁止(backend の Python 環境が PATH に無く、WeasyPrint の動的ライブラリも解決できない)。 コード変更を含む場合は、少なくとも影響範囲のテストを回し、必要なら全件を回してください。 diff --git a/.claude/skills/SEC_review/SKILL.md b/.claude/skills/SEC_review/SKILL.md index 6a5e46b9..eff9c454 100644 --- a/.claude/skills/SEC_review/SKILL.md +++ b/.claude/skills/SEC_review/SKILL.md @@ -254,7 +254,7 @@ description: Use when running a security review / vulnerability check against th ## 最低限の検証コマンド - スキャンは grep / git ベースで破壊なし。差分対象は `git diff --name-only` で取得。 -- 依存監査は nix wrap 経由で実行(生シェルで `.venv/bin/` を直接叩かない。WeasyPrint の動的ライブラリ解決に失敗する)。 +- 依存監査は nix wrap 経由で実行(生シェルで python を直接叩かない。devshell 外では backend の Python 環境も WeasyPrint の動的ライブラリも解決できない)。 - `make lint-*` / `make test-*` は本 skill では必須としない(修正検証は `SEC_apply` 側で回す)。 実装変更は `SEC_apply` skill が担う。本 skill はレビューと提案までで止める。 diff --git a/.claude/skills/tdd/SKILL.md b/.claude/skills/tdd/SKILL.md index a4a8b08e..88c4bb5f 100644 --- a/.claude/skills/tdd/SKILL.md +++ b/.claude/skills/tdd/SKILL.md @@ -26,7 +26,7 @@ description: Use when implementing changes to the deterministic logic layer (mut 1. テストだけを書く(**実装コードには触れない**) 2. 対象を絞って実行する: - - backend: `nix develop --command bash -c "cd backend && .venv/bin/python -m pytest tests/test_.py -q"` + - backend: `nix develop --command bash -c "cd backend && python -m pytest tests/test_.py -q"` - web: `nix develop --command bash -c "cd web && npx vitest run src//.test.ts"` 3. **失敗出力の要点を会話に提示する**。「期待どおりの理由での失敗」であることを確認する(import エラー・collection error はテスト自体の不備なので直してから再実行) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e636847c..954f29c6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -212,12 +212,12 @@ jobs: working-directory: backend run: uv run ruff check app tests alembic_migrations - # 型チェック(pyright)。pyright は pyproject [tool.pyright] の venv=".venv" を参照するため、 - # 直前の uv pip install で .venv に依存が入っている必要がある(このジョブで担保済み)。 - # バージョンは Makefile の typecheck-backend と揃えてピン留めする(ローカル/CI の drift 防止)。 + # 型チェック(pyright)。import 解決先は uv sync 済みの .venv を --pythonpath で明示する + # (pyproject [tool.pyright] の venv 参照は ADR-0021 Phase 1 で撤廃。ローカルは devshell の + # python3 を渡す)。バージョンは Makefile の typecheck-backend と揃えてピン留めする(drift 防止)。 - name: Type check backend (pyright) working-directory: backend - run: uvx pyright@1.1.411 app tests alembic_migrations + run: uvx pyright@1.1.411 --pythonpath .venv/bin/python app tests alembic_migrations - name: Run backend tests working-directory: backend diff --git a/.vscode/settings.json b/.vscode/settings.json index e34fca62..3b90ac4e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,5 @@ { - "python.defaultInterpreterPath": "${workspaceFolder}/backend/.venv/bin/python", - "python-envs.defaultEnvManager": "ms-python.python:venv", + "python.defaultInterpreterPath": "python3", "python.testing.pytestArgs": [ "backend" ], diff --git a/Makefile b/Makefile index e392c0b1..1ba0510e 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,7 @@ help: @echo " test 全テスト (backend + web)" @echo " test-backend Backend: pytest" @echo " test-web Frontend: vitest" - @echo " mutation-backend Backend: mutmut (長時間。結果閲覧: cd backend && .venv/bin/python -m mutmut browse)" + @echo " mutation-backend Backend: mutmut (長時間。結果閲覧: nix develop 内で cd backend && mutmut browse)" @echo " mutation-web Frontend: Stryker (長時間。レポート: web/reports/mutation/)" @echo " lint 全リント (backend + web)" @echo " lint-backend Backend: ruff check" @@ -94,9 +94,10 @@ install-hooks: ./scripts/setup-git-hooks.sh # 依存の SSoT は backend/pyproject.toml [project.dependencies] + uv.lock(ADR-0021 Phase 0)。 -# uv sync が uv.lock から .venv を構成する(.venv 廃止は Phase 1)。 +# Python 依存は Nix devshell(flake.nix の uv2nix build)が提供し、.venv は作らない(Phase 1)。 +# devshell を一度ビルドしておくことがセットアップに相当する。 install-backend: - nix develop --command bash -c "cd backend && uv sync" + nix develop --command bash -c "python3 --version && echo 'backend 依存は Nix devshell が提供します(.venv 不要 / ADR-0021 Phase 1)'" install-web: nix develop --command bash -c "cd web && npm ci" @@ -148,7 +149,7 @@ test: test-backend test-web # --cov は pyproject の addopts ではなくここで付与する(mutmut 干渉回避 / ADR-0017) test-backend: - nix develop --command bash -c "cd backend && .venv/bin/python -m pytest -q --cov=app --cov-report=term-missing tests" + nix develop --command bash -c "cd backend && python -m pytest -q --cov=app --cov-report=term-missing tests" test-web: nix develop --command bash -c "cd web && npm test" @@ -157,7 +158,7 @@ test-web: # 週次の .github/workflows/mutation.yml で実行する。対象は pyproject [tool.mutmut] / # web/stryker.conf.json を参照。 mutation-backend: - nix develop --command bash -c "cd backend && .venv/bin/python -m mutmut run" + nix develop --command bash -c "cd backend && python -m mutmut run" mutation-web: nix develop --command bash -c "cd web && npm run test:mutation" @@ -165,13 +166,13 @@ mutation-web: lint: lint-backend typecheck-backend lint-web lint-web-messages lint-env-keys lint-adr-index lint-tdd lint-backend: - nix develop --command bash -c "cd backend && .venv/bin/python -m ruff check app tests alembic_migrations" + nix develop --command bash -c "cd backend && ruff check app tests alembic_migrations" -# Backend 型チェック(pyright)。pyproject [tool.pyright] の venv=".venv" を参照するため -# 事前に make install-backend で backend/.venv に依存を入れておくこと。 +# Backend 型チェック(pyright)。import 解決は devshell の python(uv2nix build 環境)を +# --pythonpath で明示する(ADR-0021 Phase 1 で .venv 参照を撤廃)。 # バージョンは CI(.github/workflows/test.yml)と揃えてピン留めする(drift 防止)。 typecheck-backend: - nix develop --command bash -c "cd backend && uvx pyright@1.1.411 app tests alembic_migrations" + nix develop --command bash -c "cd backend && uvx pyright@1.1.411 --pythonpath \"\$$(command -v python3)\" app tests alembic_migrations" lint-web: nix develop --command bash -c "cd web && npm run lint" @@ -201,7 +202,7 @@ lint-tdd: bash scripts/lint-tdd.sh lint-fix: - nix develop --command bash -c "cd backend && .venv/bin/python -m ruff check --fix app tests alembic_migrations" + nix develop --command bash -c "cd backend && ruff check --fix app tests alembic_migrations" cd web && npm run lint:fix format: @@ -251,7 +252,7 @@ gen-redirects: # web/src/api/generated.ts を再生成する。backend app の import に # WeasyPrint 等のネイティブ依存解決が必要なため Nix devshell 経由で実行する。 codegen-types: - nix develop --command bash -c "set -e; cd backend && .venv/bin/python scripts/export_openapi.py && cd ../web && node scripts/gen-types.mjs" + nix develop --command bash -c "set -e; cd backend && python scripts/export_openapi.py && cd ../web && node scripts/gen-types.mjs" # ------------------------------------------------------------------ # # 計測(AI フレンドリーさダッシュボード) @@ -271,18 +272,18 @@ metrics-ai-friendliness: # から収集し THIRD_PARTY_LICENSES.md を再生成する。importlib.metadata を使うため # backend の依存がインストール済みの Nix devshell 経由で実行する。 licenses: - nix develop --command bash -c "backend/.venv/bin/python scripts/gen-third-party-licenses.py" + nix develop --command bash -c "python3 scripts/gen-third-party-licenses.py" # ------------------------------------------------------------------ # # マイグレーション # ------------------------------------------------------------------ # migrate: - cd backend && .venv/bin/alembic upgrade head + nix develop --command bash -c "cd backend && alembic upgrade head" migrate-create: @if [ -z "$(MSG)" ]; then echo "エラー: MSG を指定してください (例: make migrate-create MSG=\"add user table\")"; exit 1; fi - cd backend && .venv/bin/alembic revision --autogenerate -m "$(MSG)" + nix develop --command bash -c "cd backend && alembic revision --autogenerate -m \"$(MSG)\"" # ------------------------------------------------------------------ # # インフラ (OpenTofu) diff --git a/backend/pyproject.toml b/backend/pyproject.toml index fc9fecb0..50cb34ae 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -54,7 +54,8 @@ dependencies = [ [tool.uv] # backend はアプリケーション(app/ レイアウト)でありパッケージとして build/install しない。 -# uv sync は依存のみを .venv へ導入する(ADR-0021 Phase 0。.venv 廃止は Phase 1) +# ローカル開発の依存は flake.nix の uv2nix build(devshell)が提供する(ADR-0021 Phase 1)。 +# uv sync(.venv 作成)は CI 経路(Phase 2 で Nix 化予定)でのみ使う package = false [tool.pytest.ini_options] @@ -95,9 +96,10 @@ line-length = 100 select = ["E", "F", "I"] ignore = ["E501"] +# import 解決先の Python は実行側が --pythonpath で明示する(ADR-0021 Phase 1): +# ローカル: make typecheck-backend が devshell の python3(uv2nix build)を渡す +# CI: test.yml が uv sync 済みの .venv/bin/python を渡す [tool.pyright] -venvPath = "." -venv = ".venv" pythonVersion = "3.13" include = ["app", "tests", "alembic_migrations"] extraPaths = ["."] diff --git a/docs/development.md b/docs/development.md index 8d0ed9a3..08518572 100644 --- a/docs/development.md +++ b/docs/development.md @@ -32,7 +32,7 @@ direnv allow # 初回のみ許可が必要 ```bash nix develop # devshell に入る(または direnv で自動) -make setup # git hooks + backend (.venv + uv) + web (npm ci) +make setup # git hooks + backend (Nix devshell が依存を提供 / .venv なし) + web (npm ci) make generate-keys # JWT RS256 鍵ペアを生成 touch backend/.env # 環境変数を設定する(必要な変数一覧: docs/api.md「環境変数」セクション参照) ``` @@ -78,7 +78,7 @@ make dev-proxy # Vite + Cloudflare Pages dev proxy(http://localhost:8788 docker compose up libsql # 別ターミナルで uvicorn 起動(nix devshell 内で実行) -nix develop --command bash -c "cd backend && .venv/bin/uvicorn app.main:app --reload --host 0.0.0.0 --port 8000" +nix develop --command bash -c "cd backend && uvicorn app.main:app --reload --host 0.0.0.0 --port 8000" ``` `backend/.env`: @@ -142,10 +142,10 @@ make lint-fix # ruff --fix(自動修正) 特定ファイルだけ ruff したい場合: ```bash -nix develop --command bash -c "cd backend && .venv/bin/python -m ruff check " +nix develop --command bash -c "cd backend && ruff check " ``` -> **pyright の前提**: 型チェックは `backend/pyproject.toml` の `[tool.pyright]`(`venv=".venv"`)を参照するため、事前に `make install-backend` で `backend/.venv` へ依存を入れておくこと。バージョンは CI(`.github/workflows/test.yml`)と Makefile でピン留めを揃えている。 +> **pyright の前提**: import 解決先の Python は実行側が `--pythonpath` で明示する(ADR-0021 Phase 1)。ローカルは `make typecheck-backend` が devshell の python3(uv2nix build)を渡し、CI は uv sync 済みの `.venv/bin/python` を渡す。バージョンは CI(`.github/workflows/test.yml`)と Makefile でピン留めを揃えている。 ### フロントエンド(ユニット・ビルド) @@ -187,7 +187,7 @@ make mutation-web # Stryker(対象: web/stryker.conf.json) ```bash # backend: ミュータント名のグロブで絞る(モジュールパス + '*') -nix develop --command bash -c "cd backend && .venv/bin/python -m mutmut run 'app.services.shared.sort_utils*'" +nix develop --command bash -c "cd backend && python -m mutmut run 'app.services.shared.sort_utils*'" # web: --mutate でファイルを絞る nix develop --command bash -c "cd web && npx stryker run --mutate 'src/utils/text.ts'" ``` @@ -196,8 +196,8 @@ nix develop --command bash -c "cd web && npx stryker run --mutate 'src/utils/tex ```bash # backend: 生存ミュータント一覧 / TUI ブラウズ / CI 用 JSON(mutants/mutmut-cicd-stats.json) -nix develop --command bash -c "cd backend && .venv/bin/python -m mutmut results" -nix develop --command bash -c "cd backend && .venv/bin/python -m mutmut browse" +nix develop --command bash -c "cd backend && python -m mutmut results" +nix develop --command bash -c "cd backend && python -m mutmut browse" # web: HTML レポート open web/reports/mutation/mutation.html ``` diff --git a/flake.lock b/flake.lock index 20db77f5..1c9e15e8 100644 --- a/flake.lock +++ b/flake.lock @@ -34,10 +34,59 @@ "type": "github" } }, + "pyproject-build-systems": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "pyproject-nix": [ + "pyproject-nix" + ], + "uv2nix": [ + "uv2nix" + ] + }, + "locked": { + "lastModified": 1782093830, + "narHash": "sha256-6gmEVe69+KlRkZD4PEEV5xAlB9CB0Y9TiuEgQjDrKTQ=", + "owner": "pyproject-nix", + "repo": "build-system-pkgs", + "rev": "430680a19bc85a3bda55f12e4cc1a1aadcf2e478", + "type": "github" + }, + "original": { + "owner": "pyproject-nix", + "repo": "build-system-pkgs", + "type": "github" + } + }, + "pyproject-nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1782905613, + "narHash": "sha256-SvXJcAemihifkTn4BGvyE5K1FJX9bl4U8DQ5pqKvD0s=", + "owner": "pyproject-nix", + "repo": "pyproject.nix", + "rev": "7af23cfe91064865ecf2e835da28b45b3c6f49fd", + "type": "github" + }, + "original": { + "owner": "pyproject-nix", + "repo": "pyproject.nix", + "type": "github" + } + }, "root": { "inputs": { "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs" + "nixpkgs": "nixpkgs", + "pyproject-build-systems": "pyproject-build-systems", + "pyproject-nix": "pyproject-nix", + "uv2nix": "uv2nix" } }, "systems": { @@ -54,6 +103,29 @@ "repo": "default", "type": "github" } + }, + "uv2nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "pyproject-nix": [ + "pyproject-nix" + ] + }, + "locked": { + "lastModified": 1783511944, + "narHash": "sha256-Z/Ss9rWw9QYcRK+Qqkmty7PB1pIik5XGbrtit+ad2qs=", + "owner": "pyproject-nix", + "repo": "uv2nix", + "rev": "83995ef5e4ece3c9c704aa645bbff439e15a0ac3", + "type": "github" + }, + "original": { + "owner": "pyproject-nix", + "repo": "uv2nix", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 8f5b030f..412114fc 100644 --- a/flake.nix +++ b/flake.nix @@ -4,12 +4,32 @@ inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; flake-utils.url = "github:numtide/flake-utils"; + + # backend Python 環境の Nix build 化(ADR-0021 Phase 1)。 + # uv.lock(正本: backend/pyproject.toml + uv.lock / Phase 0)から + # Python パッケージ一式を Nix derivation として構成する。 + pyproject-nix = { + url = "github:pyproject-nix/pyproject.nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + uv2nix = { + url = "github:pyproject-nix/uv2nix"; + inputs.pyproject-nix.follows = "pyproject-nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + pyproject-build-systems = { + url = "github:pyproject-nix/build-system-pkgs"; + inputs.pyproject-nix.follows = "pyproject-nix"; + inputs.uv2nix.follows = "uv2nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; }; - outputs = { self, nixpkgs, flake-utils }: + outputs = { self, nixpkgs, flake-utils, pyproject-nix, uv2nix, pyproject-build-systems }: flake-utils.lib.eachDefaultSystem (system: let pkgs = nixpkgs.legacyPackages.${system}; + lib = pkgs.lib; # WeasyPrint が必要とするネイティブライブラリ weasyPrintLibs = with pkgs; [ @@ -23,13 +43,34 @@ freetype harfbuzz ]; + + # --- backend Python 環境(uv2nix / ADR-0021 Phase 1) --- + # backend/pyproject.toml + uv.lock を読み、依存を Nix build で構成する。 + # backend は virtual project([tool.uv] package = false)のため、 + # mkVirtualEnv には依存のみが入る(app/ 本体は PYTHONPATH/cwd で解決)。 + workspace = uv2nix.lib.workspace.loadWorkspace { workspaceRoot = ./backend; }; + # wheel 優先: uv.lock に記録された wheel をそのまま使い、sdist ビルドの + # ツールチェーン差異(Rust / cmake 等)を持ち込まない + pyprojectOverlay = workspace.mkPyprojectOverlay { sourcePreference = "wheel"; }; + pythonSet = + (pkgs.callPackage pyproject-nix.build.packages { + python = pkgs.python313; # Python 3.13(Dockerfile / requires-python 準拠) + }).overrideScope (lib.composeManyExtensions [ + pyproject-build-systems.overlays.default + pyprojectOverlay + ]); + # 全依存入りの virtualenv(devshell の python / pytest / ruff / alembic の実体) + backendEnv = pythonSet.mkVirtualEnv "devforge-backend-env" workspace.deps.default; in { devShells.default = pkgs.mkShell { - packages = with pkgs; [ + packages = [ # --- Python (Backend) --- - python313 # Python 3.13(Dockerfile 準拠) - uv # 高速パッケージマネージャ + # uv2nix で build した全依存入り virtualenv(.venv 廃止 / ADR-0021 Phase 1)。 + # python / pytest / ruff / alembic / uvicorn 等はここから PATH に載る + backendEnv + ] ++ (with pkgs; [ + uv # uv.lock の更新(uv lock)専用。依存導入には使わない # --- Node.js (Frontend) --- nodejs_22 # Node.js 22 LTS(npm 同梱) @@ -58,7 +99,7 @@ gh # GitHub CLI curl gnumake - ]; + ]); # WeasyPrint が共有ライブラリを発見できるよう動的リンカーのパスを設定 # macOS の dyld は LD_LIBRARY_PATH を無視するため DYLD_* も設定する @@ -67,12 +108,9 @@ export DYLD_LIBRARY_PATH="${pkgs.lib.makeLibraryPath weasyPrintLibs}:''${DYLD_LIBRARY_PATH:-}" export DYLD_FALLBACK_LIBRARY_PATH="${pkgs.lib.makeLibraryPath weasyPrintLibs}:''${DYLD_FALLBACK_LIBRARY_PATH:-}" - # uv が Python 3.13 を使うよう明示 - export UV_PYTHON="${pkgs.python313}/bin/python3" - echo "" echo "DevForge 開発環境" - echo " Python : $(python3 --version)" + echo " Python : $(python3 --version) (nix build: devforge-backend-env)" echo " Node : $(node --version)" echo " npm : $(npm --version)" echo " uv : $(uv --version)" diff --git a/pyrightconfig.json b/pyrightconfig.json index 551ed4fc..90460013 100644 --- a/pyrightconfig.json +++ b/pyrightconfig.json @@ -1,6 +1,4 @@ { - "venvPath": "backend", - "venv": ".venv", "pythonVersion": "3.13", "include": [ "backend/app", diff --git a/scripts/gen-third-party-licenses.py b/scripts/gen-third-party-licenses.py index 54e08eb3..dff3dbd1 100644 --- a/scripts/gen-third-party-licenses.py +++ b/scripts/gen-third-party-licenses.py @@ -9,7 +9,7 @@ - Backend: importlib.metadata でインストール済みパッケージのメタデータを読む 依存を追加したら `make licenses` で再生成すること。 -nix devshell 経由(backend/.venv の python)で実行する前提。 +nix devshell 経由(uv2nix build の python / ADR-0021 Phase 1)で実行する前提。 """ from __future__ import annotations From a65af14640f442fb7721798c2c96050afc1edb23 Mon Sep 17 00:00:00 2001 From: Wada Yusuke Date: Wed, 15 Jul 2026 23:28:22 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat(ci):=20backend=20CI=20=E3=81=AE?= =?UTF-8?q?=E3=83=93=E3=83=AB=E3=83=89=E7=B5=8C=E8=B7=AF=E3=82=92=20Nix=20?= =?UTF-8?q?devshell=20=E3=81=B8=E4=B8=80=E8=87=B4=E3=81=95=E3=81=9B?= =?UTF-8?q?=E3=82=8B=EF=BC=88ADR-0021=20Phase=202=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test-backend / codegen-drift / mutation-backend から uv 単体経路 (setup-uv + uv sync --locked + WeasyPrint apt install)を撤去し、 dev と同じ Nix devshell(uv2nix build / flake.nix)経由に置き換える。 - cachix/install-nix-action + nix-community/cache-nix-action を SHA ピンで 導入し、Nix store を flake / uv.lock のハッシュキーでキャッシュする - lint / typecheck / test / codegen はローカルと同一の make ターゲットを 呼ぶ(コマンドの SSoT を Makefile に一本化) - uv sync --locked が担っていた lock drift 検知は uv lock --check として保持 - pip-audit は --no-deps --disable-pip へ変更(export 済み完全固定リストの 照合。Nix の python は ensurepip を持たず一時 venv 作成が SIGABRT するため) - codegen-drift の Node は devshell の nodejs_22 に統一(ローカルと一致) - timeout 10→20 分(Nix キャッシュミス時の devshell 構築を考慮) Co-Authored-By: Claude Fable 5 --- .claude/CLAUDE.md | 4 +- .github/workflows/mutation.yml | 46 ++++++------ .github/workflows/test.yml | 127 ++++++++++++++------------------- docs/development.md | 2 +- 4 files changed, 77 insertions(+), 102 deletions(-) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 200c97d2..f267a4db 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -99,7 +99,7 @@ nix develop --command bash -c "cd web && npm run test:e2e" | 正本(変更したら) | 再生成コマンド | コミットすべき生成物 | CI ジョブ | |---|---|---|---| | backend の OpenAPI スキーマ(`app/schemas/` の Pydantic、router のシグネチャ・query/path パラメータ・**endpoint/schema の docstring**) | `make codegen-types` | `web/src/api/generated.ts`(`backend/openapi.json` は gitignore で対象外) | `codegen-drift`(ADR-0007) | -| backend の依存定義(`backend/pyproject.toml` の `[project.dependencies]`) | `cd backend && uv lock`(nix devshell 経由) | `backend/uv.lock` | `test-backend` / `codegen-drift` の `uv sync --locked`(ADR-0021 Phase 0) | +| backend の依存定義(`backend/pyproject.toml` の `[project.dependencies]`) | `cd backend && uv lock`(nix devshell 経由) | `backend/uv.lock` | `test-backend` の `uv lock --check`(ADR-0021 Phase 0/2。依存導入自体は uv2nix の Nix build) | - **判定基準**: 「OpenAPI スペックに出るものを変えたか」。エンドポイントの追加・削除、リクエスト/レスポンス型の変更、query/path パラメータの増減はもちろん、**docstring の文言変更だけでも description として spec に反映される**ため再生成が要る(今回の codegen-drift はこれで発生)。 - backend の `app/schemas/` / `app/routers/` を触ったら、`make ci` 前に `make codegen-types` を回して `git diff web/src/api/generated.ts` を確認する。差分が出たら必ず同じ PR でコミットする。 @@ -114,7 +114,7 @@ CI 定義: `.github/workflows/ci.yml` - **ローカル実行**: `make mutation-backend`(mutmut)/ `make mutation-web`(Stryker)。**フル実行は長時間**のため、対象を絞る場合は `nix develop --command bash -c "cd backend && python -m mutmut run 'app.services.shared.sort_utils*'"` / `nix develop --command bash -c "cd web && npx stryker run --mutate 'src/utils/text.ts'"` - **対象スコープの正本**: backend = `backend/pyproject.toml` の `[tool.mutmut]`、web = `web/stryker.conf.json`。決定論的ビジネスロジックに限定(schemas / models / routers / 自動生成コード等は対象外) - **CI**: `.github/workflows/mutation.yml`(週次 月曜 3:00 JST + workflow_dispatch。**PR/push では動かない・fail しない warn-only**) -- **pytest の `--cov` は addopts に戻さない**: mutmut 干渉回避のため Makefile / test.yml の呼び出し側で付与している(ADR-0017) +- **pytest の `--cov` は addopts に戻さない**: mutmut 干渉回避のため Makefile(`make test-backend`。CI も同ターゲットを呼ぶ)で付与している(ADR-0017) | Slack Secret | 用途 | 送信元 workflow | |---|---|---| diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml index 5410dbc4..f4475b27 100644 --- a/.github/workflows/mutation.yml +++ b/.github/workflows/mutation.yml @@ -40,52 +40,46 @@ jobs: # 読み取り専用ジョブのため checkout の認証情報をディスクに残さない(サプライチェーン保護)。 persist-credentials: false - - name: Install uv - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 + # dev と CI のビルド経路を Nix devshell(uv2nix build)へ一致させる(ADR-0021 Phase 2)。 + # WeasyPrint のネイティブライブラリ・Python 3.13・依存パッケージはすべて devshell が提供する。 + - name: Install Nix + uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0 with: - version: "latest" - enable-cache: true - cache-dependency-glob: "backend/uv.lock" + # flake inputs(nixpkgs / uv2nix 等)の GitHub 取得でレート制限に当たらないようにする + github_access_token: ${{ github.token }} - - name: Setup Python - run: uv python install 3.13 - - - name: WeasyPrint 用システムライブラリのインストール - run: | - sudo apt-get update - sudo apt-get install -y --no-install-recommends \ - libpango-1.0-0 libpangoft2-1.0-0 libpangocairo-1.0-0 \ - libglib2.0-0 libgobject-2.0-0 libffi-dev libcairo2 + - name: Cache Nix store + uses: nix-community/cache-nix-action@7df957e333c1e5da7721f60227dbba6d06080569 # v7.0.2 + with: + primary-key: nix-devshell-${{ runner.os }}-${{ hashFiles('flake.nix', 'flake.lock', 'backend/pyproject.toml', 'backend/uv.lock') }} + restore-prefixes-first-match: nix-devshell-${{ runner.os }}- - # --locked: uv.lock が pyproject.toml と drift していたら fail する(ADR-0021 Phase 0) - - name: Install backend dependencies - working-directory: backend - run: uv sync --locked + - name: Build devshell (uv2nix) + run: nix develop --command true - # 生存ミュータントがあっても集計と通知は行うため continue-on-error にする + # 生存ミュータントがあっても集計と通知は行うため continue-on-error にする。 + # nix develop は flake.nix のあるリポジトリルートで実行する必要があるため + # working-directory は使わず、コマンド内で cd する。 - name: Run mutmut id: run - working-directory: backend continue-on-error: true - run: uv run mutmut run 2>&1 | tee mutmut-run.log + run: nix develop --command bash -c "cd backend && mutmut run" 2>&1 | tee backend/mutmut-run.log # 生存ミュータントの一覧(調査用)。run が途中で落ちても best-effort で出力する - name: 生存ミュータント一覧を出力 - working-directory: backend continue-on-error: true - run: uv run mutmut results > mutmut-results.txt + run: nix develop --command bash -c "cd backend && mutmut results" > backend/mutmut-results.txt - name: 結果集計 id: stats - working-directory: backend run: | # export-cicd-stats が mutants/mutmut-cicd-stats.json に統計を書き出す。 # score の定義(ADR-0017 / Stryker と同基準): # killed = killed + timeout(無限ループ化もテストによる検出とみなす) # survived = survived + no_tests(どのテストにも触れられない = 検出不能) # score = killed * 100 / (killed + survived + suspicious + segfault) - uv run mutmut export-cicd-stats || true - STATS=mutants/mutmut-cicd-stats.json + nix develop --command bash -c "cd backend && mutmut export-cicd-stats" || true + STATS=backend/mutants/mutmut-cicd-stats.json if [ -f "$STATS" ]; then killed=$(jq '.killed + .timeout' "$STATS") survived=$(jq '.survived + .no_tests' "$STATS") diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 954f29c6..dcdc432f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -141,7 +141,8 @@ jobs: test-backend: runs-on: ubuntu-latest - timeout-minutes: 10 + # Nix store キャッシュミス時は devshell(uv2nix build)の構築に数分かかるため余裕を持たせる + timeout-minutes: 20 if: ${{ inputs.app }} steps: @@ -169,67 +170,62 @@ jobs: - name: Lint SSoT (TDD test accompaniment) run: bash scripts/lint-tdd.sh - - name: Install uv - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 + # dev と CI のビルド経路を Nix devshell(uv2nix build)へ一致させる(ADR-0021 Phase 2)。 + # WeasyPrint のネイティブライブラリ・Python 3.13・依存パッケージはすべて devshell が提供する。 + - name: Install Nix + uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0 with: - version: "latest" - enable-cache: true - cache-dependency-glob: "backend/uv.lock" - - - name: Setup Python - run: uv python install 3.13 + # flake inputs(nixpkgs / uv2nix 等)の GitHub 取得でレート制限に当たらないようにする + github_access_token: ${{ github.token }} + + # devshell の closure(uv2nix build の Python 環境込み)を GitHub Actions cache に保存する。 + # キーは flake と uv.lock のハッシュ。ミス時は restore-prefixes-first-match で旧キャッシュから + # 差分ビルドする。古い世代は GitHub の容量上限(10GB)到達時に LRU で自動退避される。 + - name: Cache Nix store + uses: nix-community/cache-nix-action@7df957e333c1e5da7721f60227dbba6d06080569 # v7.0.2 + with: + primary-key: nix-devshell-${{ runner.os }}-${{ hashFiles('flake.nix', 'flake.lock', 'backend/pyproject.toml', 'backend/uv.lock') }} + restore-prefixes-first-match: nix-devshell-${{ runner.os }}- - - name: WeasyPrint 用システムライブラリのインストール - run: | - sudo apt-get update - sudo apt-get install -y --no-install-recommends \ - libpango-1.0-0 libpangoft2-1.0-0 libpangocairo-1.0-0 \ - libglib2.0-0 libgobject-2.0-0 libffi-dev libcairo2 + - name: Build devshell (uv2nix) + run: nix develop --command true - # --locked: uv.lock が pyproject.toml と drift していたら fail する(ADR-0021 Phase 0) - - name: Install backend dependencies - working-directory: backend - run: uv sync --locked + # uv.lock が pyproject.toml と drift していたら fail する(旧 uv sync --locked 相当 / ADR-0021 Phase 0)。 + # uv2nix は uv.lock を正として build するため、drift の検知はこのステップが担う。 + - name: uv.lock の drift 検証 + run: nix develop --command bash -c "cd backend && uv lock --check" - name: バージョン確認 - working-directory: backend - run: | - echo "Python : $(uv run --no-sync python --version)" - echo "uv : $(uv --version)" - echo "ruff : $(uv run --no-sync ruff --version)" + run: nix develop --command bash -c "python3 --version && uv --version && ruff --version" # uv.lock の全依存(推移的依存込み)を requirements 形式へ export して監査する。 - # --python 3.13: 監査時の依存解決を requires-python (==3.13.*) に合わせる - # (ツール既定の Python が 3.12 以下だと 3.13+ 必須の依存で解決に失敗する) + # --no-deps --disable-pip: export 結果は完全固定の全依存なので依存解決・install は不要。 + # (一時 venv を作らないため、ensurepip を持たない Nix の python でも動く) - name: 脆弱性スキャン (pip-audit) - working-directory: backend run: | - uv export --frozen --no-emit-project --no-hashes \ - --format requirements-txt --output-file /tmp/requirements-audit.txt - uv tool run --python 3.13 pip-audit -r /tmp/requirements-audit.txt + nix develop --command bash -c "cd backend && uv export --frozen --no-emit-project --no-hashes \ + --format requirements-txt --output-file /tmp/requirements-audit.txt \ + && uv tool run --python 3.13 pip-audit --no-deps --disable-pip -r /tmp/requirements-audit.txt" + # lint / typecheck / test はローカルと同一の make ターゲットを呼ぶ(コマンドの SSoT は Makefile)。 + # pyright の import 解決も devshell の python に一致する(.venv 参照は ADR-0021 Phase 1 で撤廃)。 - name: Lint backend - working-directory: backend - run: uv run ruff check app tests alembic_migrations + run: make lint-backend - # 型チェック(pyright)。import 解決先は uv sync 済みの .venv を --pythonpath で明示する - # (pyproject [tool.pyright] の venv 参照は ADR-0021 Phase 1 で撤廃。ローカルは devshell の - # python3 を渡す)。バージョンは Makefile の typecheck-backend と揃えてピン留めする(drift 防止)。 - name: Type check backend (pyright) - working-directory: backend - run: uvx pyright@1.1.411 --pythonpath .venv/bin/python app tests alembic_migrations + run: make typecheck-backend + # --cov は pyproject の addopts ではなく make test-backend 側で付与される(mutmut 干渉回避 / ADR-0017) - name: Run backend tests - working-directory: backend - # --cov は pyproject の addopts ではなくここで付与する(mutmut 干渉回避 / ADR-0017) - run: uv run pytest -q --cov=app --cov-report=term-missing tests + run: make test-backend # OpenAPI → TypeScript 型のドリフト検知 (ADR-0007)。 # backend schema を変更したのに web/src/api/generated.ts を再生成して # いない状態をビルドで落とす。エラーコードの型縛りと同じ思想。 codegen-drift: runs-on: ubuntu-latest - timeout-minutes: 10 + # Nix store キャッシュミス時は devshell(uv2nix build)の構築に数分かかるため余裕を持たせる + timeout-minutes: 20 if: ${{ inputs.app }} steps: @@ -240,44 +236,29 @@ jobs: with: persist-credentials: false - - name: Install uv - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 + # dev と CI のビルド経路を Nix devshell へ一致させる(ADR-0021 Phase 2)。 + # Python(WeasyPrint ネイティブ依存込み)と Node.js の両方を devshell が提供するため、 + # 型の再生成はローカルと同一の make codegen-types を呼ぶ(コマンドの SSoT は Makefile)。 + - name: Install Nix + uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0 with: - version: "latest" - enable-cache: true - cache-dependency-glob: "backend/uv.lock" - - - name: Setup Python - run: uv python install 3.13 - - - name: WeasyPrint 用システムライブラリのインストール - run: | - sudo apt-get update - sudo apt-get install -y --no-install-recommends \ - libpango-1.0-0 libpangoft2-1.0-0 libpangocairo-1.0-0 \ - libglib2.0-0 libgobject-2.0-0 libffi-dev libcairo2 - - # --locked: uv.lock が pyproject.toml と drift していたら fail する(ADR-0021 Phase 0) - - name: Install backend dependencies - working-directory: backend - run: uv sync --locked + # flake inputs(nixpkgs / uv2nix 等)の GitHub 取得でレート制限に当たらないようにする + github_access_token: ${{ github.token }} - - name: Setup Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 + - name: Cache Nix store + uses: nix-community/cache-nix-action@7df957e333c1e5da7721f60227dbba6d06080569 # v7.0.2 with: - node-version: "20" - cache: npm - cache-dependency-path: web/package-lock.json + primary-key: nix-devshell-${{ runner.os }}-${{ hashFiles('flake.nix', 'flake.lock', 'backend/pyproject.toml', 'backend/uv.lock') }} + restore-prefixes-first-match: nix-devshell-${{ runner.os }}- - - name: Install web dependencies - run: npm ci --prefix web + - name: Build devshell (uv2nix) + run: nix develop --command true - - name: Export OpenAPI schema - working-directory: backend - run: uv run python scripts/export_openapi.py + - name: Install web dependencies + run: make install-web - - name: Generate TypeScript types - run: node web/scripts/gen-types.mjs + - name: OpenAPI → TypeScript 型を再生成 + run: make codegen-types - name: Check generated types drift run: | diff --git a/docs/development.md b/docs/development.md index 08518572..b7196907 100644 --- a/docs/development.md +++ b/docs/development.md @@ -145,7 +145,7 @@ make lint-fix # ruff --fix(自動修正) nix develop --command bash -c "cd backend && ruff check " ``` -> **pyright の前提**: import 解決先の Python は実行側が `--pythonpath` で明示する(ADR-0021 Phase 1)。ローカルは `make typecheck-backend` が devshell の python3(uv2nix build)を渡し、CI は uv sync 済みの `.venv/bin/python` を渡す。バージョンは CI(`.github/workflows/test.yml`)と Makefile でピン留めを揃えている。 +> **pyright の前提**: import 解決先の Python は実行側が `--pythonpath` で明示する(ADR-0021 Phase 1)。ローカル・CI とも `make typecheck-backend` が devshell の python3(uv2nix build)を渡す(CI も同じ make ターゲットを呼ぶ / ADR-0021 Phase 2)。pyright のバージョンピンは Makefile の `typecheck-backend` が正本。 ### フロントエンド(ユニット・ビルド) From 6d25015a5b345f70438da9a3167d3f71b557a23b Mon Sep 17 00:00:00 2001 From: Wada Yusuke Date: Thu, 16 Jul 2026 00:33:35 +0900 Subject: [PATCH 3/4] =?UTF-8?q?docs:=20=E3=83=AC=E3=83=93=E3=83=A5?= =?UTF-8?q?=E3=83=BC=E6=8C=87=E6=91=98=E3=81=AE=E9=99=B3=E8=85=90=E5=8C=96?= =?UTF-8?q?=E3=82=B3=E3=83=A1=E3=83=B3=E3=83=88=E3=82=92=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=EF=BC=88uv=20lock=20=E3=81=AE=20devshell=20=E7=B5=8C=E7=94=B1?= =?UTF-8?q?=E8=A1=A8=E8=A8=98=20/=20pyproject=20=E3=81=AE=20CI=20=E7=B5=8C?= =?UTF-8?q?=E8=B7=AF=E3=82=B3=E3=83=A1=E3=83=B3=E3=83=88=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- .claude/CLAUDE.md | 2 +- backend/pyproject.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index f267a4db..abed04f5 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -99,7 +99,7 @@ nix develop --command bash -c "cd web && npm run test:e2e" | 正本(変更したら) | 再生成コマンド | コミットすべき生成物 | CI ジョブ | |---|---|---|---| | backend の OpenAPI スキーマ(`app/schemas/` の Pydantic、router のシグネチャ・query/path パラメータ・**endpoint/schema の docstring**) | `make codegen-types` | `web/src/api/generated.ts`(`backend/openapi.json` は gitignore で対象外) | `codegen-drift`(ADR-0007) | -| backend の依存定義(`backend/pyproject.toml` の `[project.dependencies]`) | `cd backend && uv lock`(nix devshell 経由) | `backend/uv.lock` | `test-backend` の `uv lock --check`(ADR-0021 Phase 0/2。依存導入自体は uv2nix の Nix build) | +| backend の依存定義(`backend/pyproject.toml` の `[project.dependencies]`) | `nix develop --command bash -c "cd backend && uv lock"` | `backend/uv.lock` | `test-backend` の `uv lock --check`(ADR-0021 Phase 0/2。依存導入自体は uv2nix の Nix build) | - **判定基準**: 「OpenAPI スペックに出るものを変えたか」。エンドポイントの追加・削除、リクエスト/レスポンス型の変更、query/path パラメータの増減はもちろん、**docstring の文言変更だけでも description として spec に反映される**ため再生成が要る(今回の codegen-drift はこれで発生)。 - backend の `app/schemas/` / `app/routers/` を触ったら、`make ci` 前に `make codegen-types` を回して `git diff web/src/api/generated.ts` を確認する。差分が出たら必ず同じ PR でコミットする。 diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 50cb34ae..e1434ab0 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -54,8 +54,8 @@ dependencies = [ [tool.uv] # backend はアプリケーション(app/ レイアウト)でありパッケージとして build/install しない。 -# ローカル開発の依存は flake.nix の uv2nix build(devshell)が提供する(ADR-0021 Phase 1)。 -# uv sync(.venv 作成)は CI 経路(Phase 2 で Nix 化予定)でのみ使う +# 依存はローカル・CI とも flake.nix の uv2nix build(devshell)が提供し、uv sync は使わない +# (ADR-0021 Phase 1/2)。uv は uv.lock の更新(uv lock)と uvx / uv tool run 専用。 package = false [tool.pytest.ini_options] From 710dadfc7bdb5567c8314772b0365e90a43381f4 Mon Sep 17 00:00:00 2001 From: Wada Yusuke Date: Thu, 16 Jul 2026 07:58:13 +0900 Subject: [PATCH 4/4] =?UTF-8?q?docs:=20pyright=20=E3=82=B3=E3=83=A1?= =?UTF-8?q?=E3=83=B3=E3=83=88=E3=81=AE=20CI=20=E7=B5=8C=E8=B7=AF=E8=A8=98?= =?UTF-8?q?=E8=BF=B0=E3=82=92=20Phase=202=20=E3=81=AB=E8=BF=BD=E5=BE=93?= =?UTF-8?q?=EF=BC=88=E3=83=AC=E3=83=93=E3=83=A5=E3=83=BC=E6=8C=87=E6=91=98?= =?UTF-8?q?=E3=81=AE=E8=A6=8B=E8=90=BD=E3=81=A8=E3=81=97=E5=88=86=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- backend/pyproject.toml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/backend/pyproject.toml b/backend/pyproject.toml index e1434ab0..404791f3 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -96,9 +96,8 @@ line-length = 100 select = ["E", "F", "I"] ignore = ["E501"] -# import 解決先の Python は実行側が --pythonpath で明示する(ADR-0021 Phase 1): -# ローカル: make typecheck-backend が devshell の python3(uv2nix build)を渡す -# CI: test.yml が uv sync 済みの .venv/bin/python を渡す +# import 解決先の Python は実行側が --pythonpath で明示する(ADR-0021 Phase 1/2): +# ローカル・CI とも make typecheck-backend が devshell の python3(uv2nix build)を渡す [tool.pyright] pythonVersion = "3.13" include = ["app", "tests", "alembic_migrations"]