diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index ed6d0749..9f3a1dfc 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -11,7 +11,7 @@ ### 第一選択: `make` ターゲット -Makefile は `nix develop --command bash -c "..."` でラップ済み。AI は基本これを使う。 +Makefile は `nix develop --command bash -c "..."` でラップ済み。AI は基本これを使う。**最新の一覧と詳細は `make help`** で確認する(本表は AI が即時参照する代表的なターゲットのみ)。 | 用途 | コマンド | |---|---| @@ -23,6 +23,9 @@ Makefile は `nix develop --command bash -c "..."` でラップ済み。AI は | Lint 自動修正 | `make lint-fix` | | マイグレーション | `make migrate` / `make migrate-create MSG="..."` | | インフラ validate | `make infra-validate` | +| コード重複検知 | `make dupe-check` (結果: `report/dupe/jscpd-report.json`) | + +セットアップ詳細・各コマンドの目的は `docs/development.md` を参照。 ### 第二選択: `nix develop --command` ラッパー @@ -56,6 +59,7 @@ error: opening lock file "~/.cache/nix/fetcher-locks/...lock": Operation not per - **過剰な抽象化を避ける**: PEP8 を守るな、PEP8 を理解した上で抽象化しろ。 言語別の詳細ルールは `.claude/rules/{backend,frontend,infra}/` を参照。 +領域横断の共通ルール(DRY / 重複検知)は `.claude/rules/common/duplication.md` を参照。 ## CI 確認ルール @@ -64,12 +68,10 @@ error: opening lock file "~/.cache/nix/fetcher-locks/...lock": Operation not per ```bash # 一括(最速・推奨) make ci - -# 個別 -make lint-backend && make test-backend -make lint-frontend && make test-frontend && make build-frontend ``` +詳細なローカル CI 手順・個別コマンドは `docs/development.md`「テスト・リント」セクションを参照。 + ### E2E テストのトリガー 以下のいずれかに該当する変更を行った場合、E2E を必ず実行する: @@ -105,32 +107,15 @@ CI 定義: `.github/workflows/ci.yml` > `rirekisho` は日本語ローマ字のため cSpell の警告が出るが無視してよい。 -## 環境変数(必須) - -``` -TURSO_DATABASE_URL # Turso (libSQL) 接続 URL。ローカル: http://127.0.0.1:8080(turso dev)/ 本番: libsql://.turso.io -TURSO_AUTH_TOKEN # Turso 認証トークン(Cloud Run では Secret Manager から注入) -JWT_PRIVATE_KEY # RS256署名用秘密鍵(PEM形式) -JWT_PUBLIC_KEY # RS256検証用公開鍵(PEM形式) -FIELD_ENCRYPTION_KEY # Fernet鍵 -ADMIN_TOKEN # 管理者操作用トークン -CORS_ORIGINS # 例: https://devforge-dev.example.com -COOKIE_SECURE # 例: true -COOKIE_SAMESITE # lax / strict / none -INTERNAL_SECRET # Cloudflare Pages → Cloud Run 間の秘密ヘッダー値(local 環境では省略可) -``` +## 環境変数 -### オプション +**正本**: +- 環境変数名の定数定義: `backend/app/core/env_keys.py` +- 用途と注入経路の一覧: `docs/api.md`「環境変数」セクション +- 本番(Cloud Run)の env block: `infra/modules/cloud_run/main.tf` +- ローカル開発の env: `docker-compose.yml` -``` -GITHUB_CLIENT_ID # GitHub OAuth Client ID -GITHUB_CLIENT_SECRET # GitHub OAuth Client Secret -CALLBACK_BASE_URL # GitHub OAuth redirect_uri のベース URL(例: https://app.devforge.app)。未設定時は x-forwarded-host から自動検出 -LLM_PROVIDER # ollama / vertex -VERTEX_PROJECT_ID # Vertex AI 用 -VERTEX_LOCATION # 例: asia-northeast1 -VERTEX_MODEL # 例: gemini-2.5-flash-lite -``` +backend 内で `os.getenv("XXX")` のように文字列リテラル直接参照は禁止。`from app.core import env_keys` した上で `os.getenv(env_keys.XXX)` を使う。新規環境変数を追加するときは env_keys.py のコメントに記載の手順(4 箇所同期)を必ず実行する。 ## ADR(Architecture Decision Record) diff --git a/.claude/rules/backend/python.md b/.claude/rules/backend/python.md index aa98b8da..b8565f80 100644 --- a/.claude/rules/backend/python.md +++ b/.claude/rules/backend/python.md @@ -10,6 +10,7 @@ paths: - コード変更後は `make lint-backend` を実行し、違反がないことを確認すること(Nix devshell 経由で ruff が解決される) - 特定ファイルだけ検証したい場合は `nix develop --command bash -c "cd backend && .venv/bin/python -m ruff check "` を使う。生シェルで `.venv/bin/python` を直接叩くのは禁止(WeasyPrint の動的ライブラリが解決できず import に失敗するため) - 未使用の import を残さないこと(F401) +- 重複検知 / DRY ポリシーは `.claude/rules/common/duplication.md` を参照(抽出先は `backend/app/services/shared/` または同一サブパッケージの `_utils.py`) ## 例外処理の必須ルール diff --git a/.claude/rules/common/duplication.md b/.claude/rules/common/duplication.md new file mode 100644 index 00000000..a42c0acc --- /dev/null +++ b/.claude/rules/common/duplication.md @@ -0,0 +1,106 @@ +# コード重複 / DRY ポリシー(共通) + +このルールは backend / frontend / infra すべての領域に適用される。 +領域別のコーディング規約(`.claude/rules/{backend,frontend,infra}/`)と併せて参照すること。 + +## 原則 + +### Rule of Three + +- **1 回目**: そのまま書く +- **2 回目**: 重複を認識する(まだ抽象化しない) +- **3 回目**: 抽出する。共通化先は下記「抽出先ヒエラルキー」に従う + +2 回目で先回り抽象化すると、想定外の差分が出た時に逆に複雑化する。3 つ目の利用箇所が現れた時点で、共通点と差分が明確になっているはずなので、そこで初めて抽出する。 + +### 過剰な抽象化を避ける + +CLAUDE.md にある通り「PEP8 を守るな、PEP8 を理解した上で抽象化しろ」。重複検知 (jscpd) のレポートに引っかかったからといって、機械的に DRY 化してはいけない。「同じ形をしているが意味が違う」コードは別物として残すべき。 + +判断基準: + +- **形は同じだが変更理由が違う** → 抽出しない(偶発的重複) +- **形は違うが変更理由が同じ** → 抽出する(本質的重複) + +## 禁止される重複(本質的重複) + +以下が複数箇所に書かれていたら、原則として抽出対象とする。 + +- **ドメインロジック**: スコア計算 / 正規化 / バリデーション / 状態遷移 +- **エラーマッピング**: バックエンドのエラーコード ↔ ユーザー向けメッセージの対応表 +- **API パス文字列**: `/api/v1/...` のリテラルが複数モジュールに散在 +- **環境変数名のリテラル**: `os.environ["TURSO_DATABASE_URL"]` のような文字列を `settings.py` 以外で参照 +- **DTO / 型定義**: backend `app/schemas/` ↔ frontend `src/types.ts` の二重定義(同じフィールド構造を別言語で持つこと自体は許容、ただし片方の変更がもう片方の更新を忘れさせるなら検知できる仕組みが必要) + +## 許容される類似(偶発的重複) + +機械検出 (jscpd) で重複として検出されても、抽出してはいけないもの。 + +- **pytest fixture の最小スキャフォールド**: テスト個別の準備コード(`db_session` 等の共通 fixture と区別) +- **Pydantic schema の field 列**: 似た形のレスポンス schema を 1 つにまとめると変更理由が混ざる +- **`infra/environments/{dev,stg,prod}/terraform.tfvars` の同名キー**: 値が環境別なので冗長ではない +- **テストの arrange-act-assert ブロック**: 「同じ流れ」は読みやすさのために残す +- **JSDoc / docstring のテンプレート文言**: 「### Args」「### Returns」等のセクション見出し +- **import 文の塊**: 同じライブラリ群を多くのモジュールが import すること自体 + +## 抽出先ヒエラルキー + +重複を抽出すると決めたら、以下の順で配置先を決める。 + +### Backend (FastAPI) + +1. **同一サブパッケージ内の純粋関数** → 同じディレクトリの `_utils.py` か `_helpers.py` +2. **ドメイン横断のロジック** → `backend/app/services/shared/` +3. **永続化に関する重複** → `backend/app/repositories/base.py` の共通メソッド +4. **HTTP 入出力の変換** → `backend/app/routers//_responses.py` +5. **モデル / DTO** → `backend/app/schemas/shared.py` + +参考: 既存の `backend/app/services/shared/sort_utils.py` がドメイン横断 util の配置例。 + +### Frontend (React + TypeScript) + +1. **状態管理を含む共通ロジック** → `frontend/src/hooks/` の新規フック(`useDocumentForm`, `useTaskPolling` パターン) +2. **純粋関数 / 文字列変換 / 日付処理** → `frontend/src/utils/` +3. **API クライアントの共通パターン** → `frontend/src/api/client.ts` のラッパー追加 +4. **フォーム入出力変換** → `frontend/src/formMappers.ts` / `frontend/src/payloadBuilders.ts` +5. **共通 UI コンポーネント** → `frontend/src/components/ui/`(ErrorToast, Skeleton 等の配置例) +6. **型定義** → `frontend/src/types.ts` / `frontend/src/formTypes.ts` + +### Infra (OpenTofu) + +1. **2 環境以上で同じ resource block** → `infra/modules/` に切り出し、各 environment から呼ぶ +2. **環境別の値だけが違う構成** → モジュール側を `variable` 化、`environments//main.tf` で値を渡す +3. **モジュール内部の重複** → サブモジュール化は慎重に(HCL のサブモジュール深掘りは可読性を下げる) + +参考: `infra/modules/cloud_run/` `artifact_registry/` `cloud_tasks/` `cloudflare/` `monitoring/` `service_account/` が既存モジュール例。 + +### 領域横断 (BE ↔ FE ↔ infra) + +1. **エラーコード**: backend の `app/core/errors.py` を Single Source of Truth とし、frontend の `utils/appError.ts` は OpenAPI 経由で同期できないか検討する +2. **環境変数名**: backend の `app/core/settings.py` で定義したフィールド名を、infra 側 (`infra/modules/cloud_run/main.tf` の `env` ブロック) と CI (`.github/workflows/ci.yml`) で参照する。リテラル文字列のコピペは避ける +3. **手順書 / README / docs**: 重複しがちな手順は `docs/` に正本を置き、README からはリンクで参照する + +## 検知の運用 + +### 機械検出 (jscpd) + +`make dupe-check` で `report/dupe/jscpd-report.json` を生成する。Phase 1 は warn-only(CI 落とさない)。 +PR 前に 1 度走らせて、新規重複が増えていないか確認する。 + +しきい値 (`.jscpd.json`): + +- `minTokens: 50` / `minLines: 5` — これ未満の小片は無視 +- `threshold: 0` — Phase 1 は fail させない(baseline 確定後に引き上げ) + +### AI レビュー (refacter skill) + +- 領域内: `BE_refacter` / `FE_refacter` / `INFRA_refacter` +- 領域横断: `XR_refacter` + +各 skill は `report/dupe/jscpd-*.json` を読み込んでから、「形だけ似ているのか / 本質的に重複しているのか」を判定する。 +本ルールの「禁止される重複」「許容される類似」を基準に分類する。 + +### Stop hook (`.claude/settings.local.json`) + +Claude Code のセッション終了時に `make dupe-check` を background 実行する設定が入っている場合、 +次のセッションで `report/dupe/jscpd-report.json` を最初に読むこと。古い情報を引きずらないように。 diff --git a/.claude/rules/frontend/typescript.md b/.claude/rules/frontend/typescript.md index 50896228..e9a8db12 100644 --- a/.claude/rules/frontend/typescript.md +++ b/.claude/rules/frontend/typescript.md @@ -8,6 +8,7 @@ paths: - ESLint / Prettier の設定に従うこと - リントは `make lint-frontend`、テストは `make test-frontend` を使う(Nix devshell 経由で解決される) - 個別スクリプトを叩きたい場合は `nix develop --command bash -c "cd frontend && npm run