diff --git a/.claude/AGENT.md b/.claude/AGENT.md deleted file mode 100644 index ab8dc39b..00000000 --- a/.claude/AGENT.md +++ /dev/null @@ -1,59 +0,0 @@ -# DevForge - エージェント行動ガイドライン - -## CLAUDE.md との責務分担 - -| ファイル | 責務 | -|---|---| -| `CLAUDE.md` | **何を・どう書くか** — コーディング規約・CI確認・命名・ADR | -| `AGENT.md` | **どう動くか** — 自律判断の範囲・確認基準・セキュリティ判断 | - ---- - -## 自律的に行ってよいこと(確認不要) - -- ファイルの読み取り・編集・新規作成 -- ruff / lint / テストの実行(読み取り専用の CI チェック) -- `git status` / `git diff` / `git log` などの参照系 git コマンド -- `.gitignore` や `.vscode/settings.json` などの設定ファイルの軽微な修正 - -## 確認が必要なこと(実行前に必ずユーザーに確認する) - -### Git 操作 -- `git commit` — コミットメッセージと対象ファイルを提示してから実行 -- `git push` — 明示的に依頼された場合のみ -- `git reset --hard` / `git checkout -- .` などの破壊的操作 -- force push(原則禁止。ユーザーが明示的に求めた場合のみ) - -### ブランチ操作 -- 新しいブランチの作成(ベースブランチは必ず `origin/dev`) -- ブランチの削除 - -### GitHub 操作 -- Issue の作成・編集・クローズ -- PR の作成・マージ・クローズ -- コメントの投稿 - -### インフラ・DB -- Alembic マイグレーションの実行(`alembic upgrade`) -- DB データの削除・変更を伴うスクリプトの実行 -- Terraform の `apply` / `destroy` - ---- - -## セキュリティに関する判断基準 - -- `.env` / `.env.*` ファイルは読まない・編集しない -- 秘密鍵・トークン・パスワードをコードやコメントに埋め込まない -- 環境変数の値をログや出力に含めない -- セキュリティ上のリスクを検知した場合は実装前にユーザーに報告する - ---- - -## スコープ外の判断 - -以下は自律的に判断せず、ユーザーに確認する: - -- 技術選定・アーキテクチャの変更(ADR が必要な判断) -- 既存 API の破壊的変更 -- 依存パッケージの追加・削除・バージョン変更 -- CI/CD パイプラインの変更 diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 889ecf7b..eae00197 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -1,46 +1,101 @@ # DevForge - Claude Code ガイドライン -## コーディング規約 +## このファイルの読み方 -### 共通ルール -- **コメント・ドキュメント**: コード内のコメント、docstring、JSDoc はすべて**日本語**で記述すること。 -- **エラーメッセージ**: HTTPException の `detail` 等、ユーザーに返すエラーメッセージはすべて**日本語**で記述すること。 -- **例外の握りつぶし禁止**: `except SomeException: pass` は禁止。最低でも `logger.debug/warning/error` でログを出すこと。補助的な処理(通知など)で例外を抑制する場合も `logger.warning` でログを残すこと。 +- 本ファイルは全体ルールの索引。AI エージェント(Claude Code 含む)が最初に読むべき内容を集約している。 +- 領域固有ルール(backend / frontend / infra)は `.claude/rules//*.md` に分割済み。対象パスを編集する際に自動でロードされる。重複は避け、詳細は各 rule ファイルへ寄せる。 -### Python (backend) -- ruff に準拠すること -- PEP8を守るな、PEP8を理解した上で抽象化しろ -- ruff の設定は `backend/pyproject.toml` に定義済み -- コード変更後は `make lint-backend` を実行し、違反がないことを確認すること -- 未使用の import を残さないこと(F401) +## AI エージェント実行方法 -### TypeScript/React (frontend) -- ESLint / Prettier の設定に従うこと -- `cd frontend && npm run lint` でリントチェック +**原則: 開発ツールはすべて Nix devshell 経由で実行する。** ホスト側に Python / Node / ruff / tofu / WeasyPrint 用ネイティブライブラリは入っていない前提。 + +### 第一選択: `make` ターゲット + +Makefile は `nix develop --command bash -c "..."` でラップ済み。AI は基本これを使う。 + +| 用途 | コマンド | +|---|---| +| CI 相当一括 | `make ci` (= `lint + test + build-frontend`) | +| Backend lint | `make lint-backend` | +| Backend test | `make test-backend` | +| Frontend lint | `make lint-frontend` | +| Frontend test | `make test-frontend` | +| Lint 自動修正 | `make lint-fix` | +| マイグレーション | `make migrate` / `make migrate-create MSG="..."` | +| インフラ validate | `make infra-validate` | + +### 第二選択: `nix develop --command` ラッパー + +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 frontend && npm run test:e2e" +``` + +### 禁止: 生シェルでの直接実行 + +`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 を必ず通す。 + +### Sandbox と nix の競合(重要) + +Claude Code の sandbox は `~/.cache/nix/fetcher-locks/*.lock` への書き込みを拒否する。`make lint-backend` / `make test-backend` を sandbox 有効で実行すると次のエラーになる: + +``` +error: opening lock file "~/.cache/nix/fetcher-locks/...lock": Operation not permitted +``` + +この場合は sandbox を無効化して再実行する(Bash ツールの `dangerouslyDisableSandbox: true`)。nix の lock 書き込みは安全な操作なので例外として許容してよい。 + +## コーディング規約(共通) + +- **コメント・ドキュメント**: コード内コメント・docstring・JSDoc はすべて**日本語**で記述する。 +- **エラーメッセージ**: HTTPException の `detail` 等、ユーザーに返すメッセージはすべて**日本語**。 +- **例外の握りつぶし禁止**: `except SomeException: pass` は禁止。最低でも `logger.debug/warning/error` でログを残す。補助処理(通知生成など)で抑制する場合も `logger.warning` でログを出すこと。 +- **過剰な抽象化を避ける**: PEP8 を守るな、PEP8 を理解した上で抽象化しろ。 + +言語別の詳細ルールは `.claude/rules/{backend,frontend,infra}/` を参照。 ## CI 確認ルール -アプリケーションの改修を行った場合、以下のコマンドで CI 相当のチェックをローカルで実行し、パスすることを確認すること: +アプリケーションの改修後は、ローカルで CI 相当を pass させてから完了報告する。 ```bash -# backend(nix develop 経由でシステムライブラリを解決する。Makefile が自動でラップする) +# 一括(最速・推奨) +make ci + +# 個別 make lint-backend && make test-backend +make lint-frontend && make test-frontend && make build-frontend +``` -# frontend(ユニット・ビルド) -cd frontend && npm run lint && npm test && npm run build +### E2E テストのトリガー -# frontend E2E(新機能・ページ追加・ルーティング変更・認証フロー変更を行った場合は必須) -cd frontend && npm run test:e2e -``` +以下のいずれかに該当する変更を行った場合、E2E を必ず実行する: -**E2E テスト実行のトリガー**: 以下のいずれかに該当する変更を行った場合、必ず E2E テストを実行すること: - 新しいページまたはルートの追加 - 認証・ナビゲーション・レイアウトの変更 - 通知ベルなどサイドバーコンポーネントの変更 - バックエンド API の追加・変更で、フロントエンドの UI フローに影響するもの +```bash +nix develop --command bash -c "cd frontend && npm run test:e2e" +``` + CI 定義: `.github/workflows/ci.yml` +## 失敗から学んだ知見 + +過去の手戻り・障害から導いた再発防止ルール。 + +- **テストで DB をモックしない**: 統合テストは実 DB(テスト用 SQLite セッション)に当てる。モック/本番乖離でマイグレーション失敗を見落とした実績がある。 +- **新規ブランチは `origin/dev` 起点で切る**: `main` 起点だと不要差分が大量に乗る。 +- **契約変更時は既存テストの assert を必ず見直す**: 戻り値・例外仕様を変える時、旧契約を固定化したテスト(例: `test_no_cache_returns_early` のような silent-return アサーション)が残ると修正の意図が後退する。テスト名と本体の両方を更新する。 +- **`IntegrityError` 後の再 SELECT は `None` を判定する**: ユニーク制約衝突後の再取得で他セッションが先に commit したケースを想定し、`None` ならば明示的に `RuntimeError` を上げる。戻り値型が non-Optional な関数で握りつぶさないこと。 +- **タスクハンドラの「黙って return」は禁止**: 失敗パスでは `NonRetryableError` / `RetryableError` を `raise` し、worker に `dead_letter` / `retrying` 遷移と通知発行を任せる。早期 return は呼び出し側に completed として観測される。 +- **lint 失敗時は当該ファイルだけ確認**: `make lint-backend` が他ファイルの I001 等で落ちる場合、自分の変更分は `nix develop --command bash -c "cd backend && .venv/bin/python -m ruff check "` で個別検証してから進める(既存違反を巻き込まない)。 + ## 命名規約 | 種別 | 名前 | @@ -66,6 +121,7 @@ INTERNAL_SECRET # Cloudflare Pages → Cloud Run 間の秘密ヘッダー ``` ### オプション + ``` GITHUB_CLIENT_ID # GitHub OAuth Client ID GITHUB_CLIENT_SECRET # GitHub OAuth Client Secret @@ -76,17 +132,11 @@ VERTEX_LOCATION # 例: asia-northeast1 VERTEX_MODEL # 例: gemini-2.5-flash-lite ``` -## スコープ別ルール - -バックエンド・フロントエンド・インフラ固有のルール(アーキテクチャ、DB設計、認証、LLM統合等)は `.claude/rules/` に分割済み。対象パスのファイルを編集する際に自動でロードされる。 - ## ADR(Architecture Decision Record) -技術選定・アーキテクチャ判断を行う際は必ず `docs/adr/` を確認し、 -既存の判断と矛盾しない実装を行うこと。 +技術選定・アーキテクチャ判断を行う際は必ず `docs/adr/` を確認し、既存の判断と矛盾しない実装を行うこと。 -新たに重要な技術判断を行う場合は `CONTRIBUTING.md` の ADR 運用ルールに従い、 -ADR を作成してから実装を開始すること。 +新たに重要な技術判断を行う場合は `CONTRIBUTING.md` の ADR 運用ルールに従い、ADR を作成してから実装を開始する。 - ADR 一覧: `docs/adr/` - テンプレート: `docs/adr/0000-template.md` diff --git a/.claude/rules/backend/architecture.md b/.claude/rules/backend/architecture.md index 464ccecb..6aa54e25 100644 --- a/.claude/rules/backend/architecture.md +++ b/.claude/rules/backend/architecture.md @@ -7,47 +7,109 @@ paths: ``` backend/app/ -├── main.py # FastAPI アプリ(lifespan で DB bootstrap) +├── main.py # FastAPI アプリ(lifespan で DB bootstrap・鍵検証) +├── messages.json # ユーザー向けメッセージ・通知文言の定義 ├── core/ # 設定・メッセージ・認証・暗号化などの横断基盤 │ ├── settings.py │ ├── messages.py │ ├── logging_utils.py │ ├── date_utils.py │ ├── encryption.py +│ ├── errors.py # ErrorCode / raise_app_error +│ ├── context.py # リクエスト相関 ID 等のコンテキスト +│ ├── metrics.py +│ ├── redis_client.py │ └── security/ -│ ├── auth.py +│ ├── auth.py # JWT(RS256)発行・検証 │ ├── csrf.py │ └── dependencies.py -├── db/ # DB接続・bootstrap・backup・seed・migration 補助 +├── middleware/ +│ └── request_id.py # リクエスト ID 付与 +├── db/ # DB 接続・bootstrap・migration 補助 │ ├── database.py │ ├── bootstrap.py -│ ├── backup.py │ ├── migrations.py │ ├── seed.py -│ └── sqlite_backup.py -├── routers/ # エンドポイント(auth, basic_info, resumes, rirekisho, blog, intelligence, admin, health, master_data, notifications) +│ └── seeds/ +├── routers/ # FastAPI エンドポイント +│ ├── auth/ # 認証関連(endpoints, github_auth, oauth_flow, token_manager) +│ ├── blog.py +│ ├── career_analysis.py +│ ├── download_utils.py +│ ├── health.py +│ ├── intelligence.py +│ ├── internal.py # Cloud Tasks → backend 内部 API +│ ├── master_data.py +│ ├── notifications.py +│ └── resumes.py ├── models/ # SQLAlchemy 2.0 宣言的マッピング +│ ├── user.py / blog.py / cache.py / career_analysis.py +│ ├── master_data.py / notification.py / resume.py ├── schemas/ # Pydantic リクエスト/レスポンススキーマ -├── repositories/ # データアクセス層(UserRepository, NotificationRepository 等) +│ ├── auth.py / blog.py / career_analysis.py / intelligence.py +│ ├── master_data.py / resume.py / shared.py +├── repositories/ # データアクセス層 +│ ├── base.py / user.py / blog.py / career_analysis.py +│ ├── master_data.py / notification.py / resume.py ├── services/ │ ├── blog/ # ブログ収集・技術記事判定・スコア算出 +│ │ ├── account_service.py │ │ ├── collector.py │ │ ├── scorer.py +│ │ ├── sync_service.py │ │ └── tech_keywords.json -│ ├── intelligence/ # GitHub 分析パイプラインと LLM 連携 +│ ├── career_analysis/ # キャリア分析(プロンプト組み立て・テックスタックマージ) +│ │ ├── builder.py +│ │ ├── prompt_builder.py +│ │ └── tech_stack_merger.py +│ ├── intelligence/ # GitHub 分析パイプラインと LLM 連携 │ │ ├── pipeline.py │ │ ├── github_collector.py +│ │ ├── github_analysis_service.py +│ │ ├── github/ # GitHub API クライアント・リポジトリ解析 +│ │ │ ├── api_client.py +│ │ │ └── repo_analyzer.py │ │ ├── llm_summarizer.py +│ │ ├── llm_advice_service.py │ │ ├── response_mapper.py │ │ ├── position_scorer.py -│ │ ├── skill_*.py -│ │ └── llm/ +│ │ ├── position_weights.json +│ │ ├── skill_extractor.py +│ │ ├── skill_taxonomy/ # スキル分類(言語・トピック・所有権マップ) +│ │ └── llm/ # LLM クライアント実装 │ │ ├── base.py │ │ ├── factory.py │ │ ├── ollama_client.py │ │ └── vertex_client.py +│ ├── llm/ # LLM 入出力サニタイザ等(intelligence/llm とは別) +│ │ └── sanitizer.py +│ ├── tasks/ # 非同期タスク基盤(Cloud Tasks / ローカル) +│ │ ├── base.py # TaskType 定義 +│ │ ├── exceptions.py # RetryableError / NonRetryableError +│ │ ├── worker.py # execute_task(状態遷移・通知) +│ │ ├── dispatch_service.py +│ │ ├── factory.py +│ │ ├── cloud_tasks.py # Cloud Tasks エンキュー +│ │ ├── local.py # BackgroundTasks 直接実行 +│ │ └── handlers/ # タスク種別ごとのハンドラ +│ │ ├── base.py # TaskHandler 抽象基底クラス +│ │ ├── blog_summarize.py +│ │ ├── career_analysis.py +│ │ └── github_analysis.py │ ├── markdown/ # Markdown テンプレート生成 │ ├── pdf/ # WeasyPrint による PDF 生成 +│ ├── progress_service.py # 進捗状態管理 │ └── shared/ # ドメイン横断の service util │ └── sort_utils.py +├── prompts/ # LLM プロンプトテンプレート +├── fonts/ # PDF 生成用フォント +└── utils/ + └── prompt_loader.py # プロンプトファイルローダ ``` + +## 主要モジュールのポイント + +- **routers/auth/**: パッケージ化されており、`endpoints` / `github_auth` / `oauth_flow` / `token_manager` に責務分割 +- **services/tasks/**: Cloud Tasks(本番)と BackgroundTasks(ローカル)を共通の `execute_task` でディスパッチ。状態遷移(`processing` / `completed` / `dead_letter` / `retrying`)は worker が担う +- **services/intelligence/**: GitHub 分析 → LLM 要約パイプライン。Ollama / Vertex AI を `LLMClient` 抽象で切替 +- **services/llm/ と services/intelligence/llm/**: 別物。前者は入出力サニタイザ等の横断 util、後者は LLM プロバイダクライアントの実装 diff --git a/.claude/rules/backend/auth-security.md b/.claude/rules/backend/auth-security.md index 22721959..540dac8b 100644 --- a/.claude/rules/backend/auth-security.md +++ b/.claude/rules/backend/auth-security.md @@ -7,19 +7,23 @@ paths: ## 認証 -- JWT(`python-jose`)+ bcrypt(`passlib`)、Cookie に 8 時間有効のトークンを格納 -- **`bcrypt==3.2.2` に固定**(passlib 1.7.4 は bcrypt 4.x と非互換) -- GitHub OAuth ログインに対応(`GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET` の設定が必要) +- **認証方式**: GitHub OAuth のみ。パスワード認証は実装していない(`User.hashed_password` は nullable のまま、OAuth 専用設計) +- **JWT**: `python-jose[cryptography]`、RS256 署名(`JWT_PRIVATE_KEY` / `JWT_PUBLIC_KEY`) +- **トークン有効期間**: + - アクセストークン: 15 分(Cookie 名 `access_token`) + - リフレッシュトークン: 7 日(Cookie 名 `refresh_token`) +- 起動時に `validate_jwt_key_pair()` で秘密鍵と公開鍵の整合性を検証する - GitHub OAuth の `state` は **backend 側 Cookie で検証**する。frontend だけで検証しないこと -- 認証Cookie属性は `COOKIE_SECURE` / `COOKIE_SAMESITE` で制御する +- 認証 Cookie 属性は `COOKIE_SECURE` / `COOKIE_SAMESITE` で制御する ## 暗号化 -- 履歴書(Rirekisho)の個人情報フィールド(email / phone / postal_code / address)は `encryption.py` で暗号化保存 -- `FIELD_ENCRYPTION_KEY` 環境変数(Fernet) +- 履歴書(Rirekisho)の個人情報フィールド(email / phone / postal_code / address)は `app/core/encryption.py` で暗号化保存 +- 鍵は `FIELD_ENCRYPTION_KEY` 環境変数(Fernet) ## セキュリティ -- 外部API呼び出しや LLM 実行のような**高コスト endpoint**には rate limit を付けること(slowapi) -- OAuth 開始URLは backend で発行し、許可された `CORS_ORIGINS` のみをリダイレクト先に使うこと -- cookie 認証を使う変更では `Secure` / `SameSite` / CORS の整合を必ず確認すること +- 外部 API 呼び出しや LLM 実行のような**高コスト endpoint**には rate limit を付けること(`slowapi`) +- OAuth 開始 URL は backend で発行し、許可された `CORS_ORIGINS` のみをリダイレクト先に使うこと +- Cookie 認証を使う変更では `Secure` / `SameSite` / CORS の整合を必ず確認すること +- Cloudflare Pages → Cloud Run 間は `INTERNAL_SECRET` ヘッダで認証する(local 環境では省略可) diff --git a/.claude/rules/backend/python.md b/.claude/rules/backend/python.md index 93aeb31f..aa98b8da 100644 --- a/.claude/rules/backend/python.md +++ b/.claude/rules/backend/python.md @@ -7,7 +7,8 @@ paths: - ruff に準拠すること(設定: `backend/pyproject.toml`) - PEP8を守るな、PEP8を理解した上で抽象化しろ -- コード変更後は `cd backend && .venv/bin/python -m ruff check app tests alembic_migrations` を実行し、違反がないことを確認すること +- コード変更後は `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) ## 例外処理の必須ルール @@ -30,5 +31,5 @@ paths: ## システムパッケージと Dockerfile -- Pythonライブラリがシステムパッケージ(C ライブラリ等)に依存する場合、`backend/Dockerfile` の `apt-get install` にも該当パッケージを追加すること -- ローカルで `brew install` 等を行った場合は、必ず Dockerfile 側にも対応する Debian パッケージを追加し、Cloud Run デプロイに影響がないことを確認すること +- Pythonライブラリがシステムパッケージ(C ライブラリ等)に依存する場合、ローカル環境(`flake.nix` の `devShells.default.packages`)と本番イメージ(`backend/Dockerfile` の `apt-get install`)の両方に追加すること +- `flake.nix` だけ更新して Dockerfile を忘れると Cloud Run デプロイで import エラーになる diff --git a/.claude/rules/backend/test.md b/.claude/rules/backend/test.md new file mode 100644 index 00000000..eaea6e06 --- /dev/null +++ b/.claude/rules/backend/test.md @@ -0,0 +1,48 @@ +--- +paths: + - backend/** +--- + +# Backend テスト方針 + +## いつテストを書く・回すか(トリガー) + +以下のいずれかに該当する変更を行った場合、テスト追加・更新と実行が必須: + +- **新規エンドポイント追加**: 必ず統合テスト(`tests/test_.py`)を追加し、正常系・認可エラー・バリデーションエラー・404 を最低限カバーする +- **既存エンドポイントの契約変更**: ステータスコード / レスポンス body / 副作用が変わる場合、既存テストの assert を見直す(旧契約を固定化したテストが残ると意図が後退する) +- **リポジトリ層・サービス層のロジック変更**: 該当ユニットテスト(`tests/test_.py` / `tests/services/`)を更新 +- **タスクハンドラの追加・変更**: `tests/test_worker_extended.py` または `tests/test_worker_timeout.py` に状態遷移(`processing` → `completed` / `dead_letter` / `retrying`)のテストを追加 +- **マイグレーション追加**: 実 DB に対する upgrade/downgrade が通ることを `make test-backend` で確認 +- **暗号化・認証関連**: `tests/test_auth.py` / `tests/test_encryption.py` / `tests/test_oauth_flow.py` を必ず回す + +## 実行コマンド + +```bash +make test-backend # 全テスト +``` + +特定ファイルだけ回す場合: +```bash +nix develop --command bash -c "cd backend && .venv/bin/python -m pytest tests/test_worker_extended.py -q" +``` + +## OK 基準(達成条件) + +以下をすべて満たして初めて「テスト OK」と判定する: + +1. **全テスト pass**: `make test-backend` が exit 0 +2. **新規・変更コードに対応するテストが存在する**: + - 新規エンドポイント → ハッピーパス + 認可失敗 + 不正入力(最低 3 ケース) + - 新規サービス関数 → 主要分岐ごとに 1 ケース + - タスクハンドラ → 成功 / `NonRetryableError` / `RetryableError` の 3 パス +3. **失敗パスを明示的に検証している**: 例外を `pytest.raises(ExpectedError)` で必ず assert する。silent return を許容するテスト(過去の `test_no_cache_returns_early` のようなもの)は書かない +4. **モックは最小限**: DB はモックしない(実 SQLite セッションを使う)。外部サービス(GitHub API / LLM / Cloud Tasks / Redis)はモックする +5. **lint が pass**: `make lint-backend` も同時に通ること + +## アンチパターン + +- `assert result is not None` だけで満足する(中身を検証していない) +- `try / except Exception: pass` をテストコード内で使う(失敗を隠す) +- `time.sleep` での同期待ち(フレーキーになる。`AsyncMock` / `monkeypatch` を使う) +- 過剰モック: SQLAlchemy セッション全体をモックする等。実 DB セッションを使うこと diff --git a/.claude/rules/frontend/architecture.md b/.claude/rules/frontend/architecture.md index 7e44b692..113d09e0 100644 --- a/.claude/rules/frontend/architecture.md +++ b/.claude/rules/frontend/architecture.md @@ -7,31 +7,66 @@ paths: ``` frontend/src/ +├── main.tsx # BrowserRouter + Redux Provider ラップ +├── App.tsx # 認証ステート管理 + +├── App.module.css +├── styles.css / styles/ # グローバルスタイル ├── router/ -│ ├── routes.tsx # 全ルート定義(パス↔ページ対応表) -│ └── guards.tsx # PrivateRoute / PublicRoute(Outlet パターン) -├── pages/ # ルートのエントリーポイント(薄いラッパー) +│ ├── routes.tsx # 全ルート定義(パス↔ページ対応表) +│ ├── guards.tsx # PrivateRoute / PublicRoute(Outlet パターン) +│ └── index.ts +├── pages/ # ルートのエントリーポイント(薄いラッパー) +│ ├── LoginPage.tsx / GitHubCallbackPage.tsx +│ ├── CareerPage.tsx / CareerAnalysisPage.tsx +│ ├── BlogPage.tsx / GitHubIntelligencePage.tsx +│ └── NotFoundPage.tsx ├── components/ │ ├── AuthenticatedLayout.tsx # サイドバー + (フッターに NotificationBell を配置) │ ├── LoadingOverlay.tsx # 共通ローディング UI(position:fixed; z-index:100 でビューポート全体を覆う) │ ├── NotificationBell.tsx # 通知ベル(未読バッジ・ドロップダウンパネル) -│ ├── NotificationBell.module.css -│ ├── forms/ # BasicInfoForm, CareerResumeForm, ResumeForm -│ ├── analysis/ # GitHubAnalysisPage, LanguageBar -│ ├── auth/ # LoginForm, RegisterForm -│ └── blog/ # BlogPage +│ ├── ConfirmDialog.tsx # 確認モーダル +│ ├── ErrorBoundary.tsx # 例外境界 +│ ├── TaskProgressStepper.tsx # 非同期タスク進捗ステッパー +│ ├── UserMenu.tsx +│ ├── forms/ # BasicInfoForm, CareerResumeForm, ResumeForm 等 +│ ├── analysis/ # GitHubAnalysisPage, LanguageBar 等 +│ ├── career-analysis/ # CareerAnalysisPage + 結果表示 +│ ├── auth/ # LoginForm, RegisterForm +│ ├── blog/ # BlogPage +│ ├── icons/ # アイコンコンポーネント(Bell, Eye, Qiita, Zenn 等) +│ └── ui/ # 汎用 UI(ErrorToast, InlineSpinner, Skeleton) ├── hooks/ -│ ├── useDocumentForm.ts # フォーム CRUD の共通フック(loading / saving / error 管理) -│ ├── useMasterData.ts # マスタデータのモジュールレベルキャッシュ -│ ├── useNotifications.ts # 通知ベル用フック(30秒ポーリング・パネル開閉・既読処理) -│ └── usePdfActions.ts # PDF ダウンロード/プレビュー +│ ├── useDocumentForm.ts # フォーム CRUD の共通フック(loading / saving / error 管理) +│ ├── useMasterData.ts # マスタデータのモジュールレベルキャッシュ +│ ├── useNotifications.ts # 通知ベル用フック(30秒ポーリング・パネル開閉・既読処理) +│ ├── usePdfActions.ts # PDF ダウンロード/プレビュー +│ ├── useTaskPolling.ts # 非同期タスクの進捗ポーリング +│ ├── useBlogAccountManager.ts / useBlogSummaryPolling.ts +│ ├── useCareerAnalysisPage.ts / useCareerExperienceMutators.ts +│ ├── usePhotoUpload.ts / useProjectModalState.ts +│ ├── useTheme.ts +│ └── analysis/ # useAsyncAnalysisPage(非同期分析共通) ├── api/ -│ ├── client.ts # fetch ラッパー(Cookie 認証、401 ハンドリング) -│ └── *.ts # ドメイン別 API モジュール -├── App.tsx # 認証ステート管理 + 呼び出し -└── main.tsx # BrowserRouter ラップ +│ ├── client.ts # fetch ラッパー(Cookie 認証、401 ハンドリング) +│ └── *.ts # ドメイン別 API モジュール(auth, blog, resumes, career-analysis, intelligence, master-data, notifications, download, ai-resume) +├── store/ # Redux Toolkit + redux-persist +│ ├── index.ts # store 構成 +│ ├── persistConfig.ts +│ └── formCacheSlice.ts # フォームキャッシュ +├── utils/ +│ ├── appError.ts +│ └── errorId.ts +├── constants/ + constants.ts # 定数定義 +├── types.ts # 共通型 +├── formTypes.ts / formMappers.ts / payloadBuilders.ts # フォーム入出力変換 +├── test/ + test-setup.ts # vitest セットアップ +└── styles/ ``` -**ルーティング**: react-router-dom v7。ガードは Outlet パターン(レイアウトルート)で実装。`/login`, `/signin` は PublicRoute、他は PrivateRoute でガード。 +**ルーティング**: react-router-dom v7。ガードは Outlet パターン(レイアウトルート)で実装。`/login` 系は PublicRoute、他は PrivateRoute でガード。 **フォームパターン**: `useDocumentForm` フックが load/create/update/loading/saving を一元管理。各フォームはこのフックを使い、`LoadingOverlay` でデータ取得中の操作をブロックする。 + +**状態管理**: Redux Toolkit + redux-persist。`store/formCacheSlice` でフォームの一時保持を行う。サーバ状態は各 API モジュール経由で取得し、コンポーネントローカルもしくはフックでキャッシュする方針。 + +**非同期タスクの進捗**: `useTaskPolling` / `useAsyncAnalysisPage` でバックエンドの `dead_letter` / `processing` / `completed` 状態をポーリングし、`TaskProgressStepper` で可視化する。 diff --git a/.claude/rules/frontend/test.md b/.claude/rules/frontend/test.md new file mode 100644 index 00000000..aa9bcdd8 --- /dev/null +++ b/.claude/rules/frontend/test.md @@ -0,0 +1,63 @@ +--- +paths: + - frontend/** +--- + +# Frontend テスト方針 + +## いつテストを書く・回すか(トリガー) + +### ユニット / コンポーネントテスト(vitest + node:test) + +- **新規フック追加**: 必ず `*.test.ts` を作成(loading / success / error の 3 パス最低限) +- **既存フックの契約変更**: 戻り値・副作用が変わる場合、既存 `*.test.ts` の assert を見直す +- **payloadBuilders / formMappers の変更**: `payloadBuilders.test.ts` を更新(node:test 経由) +- **api/client.ts の変更**: `api/client.test.ts` を更新(401 リダイレクト、Cookie 認証の挙動) +- **コンポーネント追加**: ロジックを含むものはテストを追加。表示のみのものは省略可 + +### E2E テスト(Playwright) + +以下のいずれかに該当する変更を行った場合、E2E を必ず実行: + +- 新しいページまたはルートの追加 +- 認証・ナビゲーション・レイアウトコンポーネントの変更 +- 通知ベル / サイドバー / `AuthenticatedLayout` の変更 +- バックエンド API の追加・変更で、frontend の UI フローに影響するもの + +## 実行コマンド + +```bash +make test-frontend # unit + vitest +nix develop --command bash -c "cd frontend && npm run test:e2e" # E2E(Playwright) +``` + +特定の vitest スイートだけ回す場合: +```bash +nix develop --command bash -c "cd frontend && npx vitest run src/hooks/useDocumentForm.test.ts" +``` + +## OK 基準(達成条件) + +以下をすべて満たして初めて「テスト OK」と判定する: + +1. **全 unit / vitest pass**: `make test-frontend` が exit 0 +2. **lint が pass**: `make lint-frontend` も同時に通ること +3. **build が通る**: `make build-frontend`(tsc + vite build)が通ること。TypeScript の型エラーが残っていないこと +4. **E2E トリガーに該当する場合は E2E pass**: 上記トリガーリストに該当する変更では `npm run test:e2e` を必ず実行し、全シナリオが green +5. **新規・変更コードに対応するテストが存在する**: + - 新規フック → 主要分岐ごとに 1 ケース(最低 3 ケース) + - 新規 API モジュール → 成功 / 4xx / 5xx の 3 パス + - E2E は authenticated layout 経由でゴールデンパスを 1 本通す + +## E2E の注意点(重要 — 過去にハマった) + +- **ルートモックは LIFO 登録**: キャッチオールを先、具体的モックを後(`.claude/rules/frontend/typescript.md` 参照) +- **`LoadingOverlay` 対策**: `waitForAuthenticatedLayout(page)` を必ず呼び出す。`position: fixed; z-index: 100` が要素クリックを邪魔する +- **キャッチオールパターン**: `http://localhost:8000/**` を使う。`**/api/**` は Vite dev server のソースファイルにもマッチして壊れる + +## アンチパターン + +- `await new Promise(r => setTimeout(r, ms))` での同期待ち(フレーキー) +- `data-testid` 過剰依存(ユーザー視点のセレクタを優先: role / name / placeholder) +- E2E でテスト用バックエンドを起動せずモックだけで済ませる(API 統合バグを取り逃す) +- snapshot testing で大きな DOM 全体をスナップショットする(差分の意味が不明瞭になる) diff --git a/.claude/rules/frontend/typescript.md b/.claude/rules/frontend/typescript.md index 926db803..50896228 100644 --- a/.claude/rules/frontend/typescript.md +++ b/.claude/rules/frontend/typescript.md @@ -6,16 +6,17 @@ paths: # TypeScript/React コーディング規約 - ESLint / Prettier の設定に従うこと -- `cd frontend && npm run lint` でリントチェック +- リントは `make lint-frontend`、テストは `make test-frontend` を使う(Nix devshell 経由で解決される) +- 個別スクリプトを叩きたい場合は `nix develop --command bash -c "cd frontend && npm run