epicweb-dev/epic-stack — 導出ルール集
出典: repos/epicweb-dev/epic-stack/ | 生成日: 2026-02-18 用途: CLAUDE.md にそのまま貼り付けて AI コンテキストとして活用
型安全性とバリデーション
[MUST]ランタイムバリデーションスキーマ(Zod 等)からz.inferで型を導出し、手書きの型定義との二重管理を避ける- 根拠: type-system-patterns — 全フォーム・環境変数・API レスポンスで一貫してスキーマ駆動型定義が適用されている
[MUST]外部入力(API レスポンス・フォームデータ・環境変数・Cookie)はシステム境界で必ず Zodparse()/safeParse()を通し、境界の内側では型安全な値として扱う- 根拠: type-system-patterns — GitHub API レスポンスを
GitHubUserResponseSchema.parse()で検証してから使用
- 根拠: type-system-patterns — GitHub API レスポンスを
[MUST]環境変数はアプリケーション起動時にスキーマバリデーション(Zod 等)で検証し、不正な状態での起動を即座に失敗させる。クライアントへの公開はホワイトリスト方式で選別する- 根拠: project-structure, architecture —
env.server.tsのinit()で Zod バリデーション実行、getEnv()で公開変数のみ返却
- 根拠: project-structure, architecture —
[MUST]unknown型のエラーは型ガードで安全にメッセージを取り出す —error.messageへの直接アクセスやString(error)は避ける- 根拠: error-handling-idioms —
getErrorMessageが段階的チェックでどのケースでも安全な文字列を返す
- 根拠: error-handling-idioms —
[SHOULD]Zod スキーマに.default()や.transform()がある場合、z.infer(出力型)とz.input(入力型)を区別して使い分ける- 根拠: type-system-patterns —
Toast(出力型)とToastInput(入力型)を明確に分離
- 根拠: type-system-patterns —
[SHOULD]ORM 生成型はフィールド単位の Indexed Access(Model['field'])で部分参照し、関数シグネチャをモデル全体の型に依存させない- 根拠: type-system-patterns —
User['username'],Connection['providerId']のように Prisma 型をフィールド単位で参照
- 根拠: type-system-patterns —
[SHOULD]外部ライブラリの型に合わせて Zod スキーマを書く場合、satisfies z.ZodType<ExternalType>でスキーマと型の整合性をコンパイル時に検証する- 根拠: type-system-patterns —
RegistrationResponseJSONに対するsatisfiesによる整合性保証
- 根拠: type-system-patterns —
[SHOULD]バリデーションスキーマの基本単位をフィールドレベルで定義し、各ユースケースで合成(.object(),.and(),.merge())する- 根拠: type-system-patterns —
UsernameSchema,PasswordSchemaを定義し 6 箇所以上で合成再利用
- 根拠: type-system-patterns —
[AVOID]型アサーション (as) を使ってランタイムで検証されていない構造を型付けすること — 代わりに Zod バリデーションや型ガードを使う- 根拠: type-system-patterns — プロジェクト全体で
asの使用は最小限、外部入力には一貫して Zod を使用
- 根拠: type-system-patterns — プロジェクト全体で
テスト設計
[MUST]テスト中のconsole.error/console.warnをデフォルトで例外に変換し、意図的な呼び出しのみmockImplementation(() => {})で明示的にオプトアウトする- 根拠: testing-strategy, test-infrastructure —
setup-test-env.tsでグローバルに設定、エラーメッセージに解除方法を含める
- 根拠: testing-strategy, test-infrastructure —
[MUST]外部 HTTP API のモックは MSW のようなネットワーク層インターセプトで行い、アプリ内モジュールの mock/stub は最小限にする- 根拠: testing-strategy — 全 MSW ハンドラが外部 API エンドポイントに対するもので、
vi.mockは技術的制約のケースのみ
- 根拠: testing-strategy — 全 MSW ハンドラが外部 API エンドポイントに対するもので、
[MUST]テストごとに独立した DB 状態を保証する仕組みを導入する(ファイルコピー、トランザクションロールバック、テンプレート DB 等)- 根拠: test-data-management —
beforeEachで base DB をコピーし、テスト間のデータ汚染を物理的に防止
- 根拠: test-data-management —
[MUST]並列テスト実行時はワーカー ID(VITEST_POOL_ID等)をリソース名に組み込み、共有リソースの衝突を防ぐ- 根拠: test-infrastructure — DB ファイルとモック状態ファイルの両方にプール ID を埋め込み
[MUST]テストデータの生成ロジックはテストファイル内にハードコードせず、共有ファクトリ関数に集約する- 根拠: test-data-management —
createUser()がシード、Vitest テスト、Playwright テストの全てで共有
- 根拠: test-data-management —
[MUST]faker でユニーク制約のあるフィールドを生成する場合は、一意性保証の仕組み(UniqueEnforcer等)を組み合わせる- 根拠: test-data-management —
UniqueEnforcerにより並列テスト実行でも username 重複が発生しない
- 根拠: test-data-management —
[SHOULD]ビジネスロジックのテストは純粋関数の入出力テストよりも、実際の依存(DB, セッション, HTTP)と統合した状態でのテストを優先する- 根拠: testing-strategy — OAuth コールバックテストが Prisma + セッション Cookie + loader 直接呼び出しの統合テスト
[SHOULD]テスト対象のドメインに特化したカスタムマッチャーを作成し、テストの意図を宣言的に表現する。TypeScript の module augmentation で型安全にする- 根拠: test-infrastructure —
toHaveRedirect,toHaveSessionForUser,toSendToastがAssertionとAsymmetricMatchersContainingの両方を拡張
- 根拠: test-infrastructure —
[SHOULD]テストデータのファクトリ関数は「データ生成」と「DB 書き込み」を分離し、純粋なデータ生成関数として設計する- 根拠: test-data-management —
createUser()は DB に書き込まず{ username, name, email }を返すだけ
- 根拠: test-data-management —
[SHOULD]テストデータのクリーンアップはフレームワーク機構(フィクスチャ、afterAll)に委ね、セットアップとティアダウンを対で宣言する- 根拠: test-fixture-patterns — Playwright の
use()前後で setup/teardown を対にし、クリーンアップ漏れを構造的に防止
- 根拠: test-fixture-patterns — Playwright の
[SHOULD]グローバルセットアップで生成コストの高いリソース(DB スキーマ等)はソースファイルの mtime を比較し、変更時のみ再生成する- 根拠: test-infrastructure —
global-setup.tsでスキーマと DB の mtime 比較によるスキップ最適化
- 根拠: test-infrastructure —
[SHOULD]MSW モックハンドラを開発環境とテスト環境で共用し、モック定義の重複・乖離を防ぐ- 根拠: mock-and-stub-patterns —
tests/mocks/に全ハンドラ集約、MOCKS=trueと Vitest setupFiles から同一コード参照
- 根拠: mock-and-stub-patterns —
[SHOULD]モックハンドラのデフォルトは正常系レスポンスとし、異常系は個別テストのserver.use()で上書きする- 根拠: mock-and-stub-patterns —
pwned-passwords.tsで正常系デフォルト、auth.server.test.tsで異常系上書き
- 根拠: mock-and-stub-patterns —
[AVOID]シードデータの特定レコードに依存するテストを書く — テスト自身がデータを生成すべき- 根拠: test-data-management — base DB は
--skip-seedで作成、テストがシードデータに依存しない設計
- 根拠: test-data-management — base DB は
[AVOID]E2E テストでユニットテストが適切な範囲の検証を行う — 純粋関数は Vitest で高速にカバーし、E2E はユーザージャーニーに限定する- 根拠: testing-strategy —
getErrorMessageのパターン網羅は Vitest、「ノートを作成できる」は E2E
- 根拠: testing-strategy —
E2E テスト
[MUST]E2E テストの認証フィクスチャでは、プロダクションコードのセッション生成ロジックを再利用して Cookie を作成する- 根拠: e2e-testing-patterns —
authSessionStorageとsessionKeyをアプリ本体からインポートし Cookie 生成
- 根拠: e2e-testing-patterns —
[MUST]テストデータを作成するフィクスチャは、同じフィクスチャ内にティアダウンを持ち、use()の前がセットアップ、後がティアダウンとして対称的に記述する- 根拠: e2e-testing-patterns —
insertNewUser,login,prepareGitHubUserすべてがuse()後に削除処理を実装
- 根拠: e2e-testing-patterns —
[SHOULD]認証が前提条件に過ぎないテストでは UI ログインをバイパスし、DB + Cookie 注入で認証状態を構築する- 根拠: authentication-testing —
notes.test.ts等はloginフィクスチャで即座に認証済み状態を得る
- 根拠: authentication-testing —
[SHOULD]E2E テストのセレクタは CSS クラスやdata-testidではなく ARIA ロール(getByRole)を優先する- 根拠: e2e-testing-patterns — 8つの E2E テストすべてで
getByRole,getByLabel,getByTextが一貫使用
- 根拠: e2e-testing-patterns — 8つの E2E テストすべてで
[SHOULD]テスト共通のフィクスチャはtest.extendで定義し、テスト固有のフィクスチャは同パターンで拡張する。フィクスチャ階層は2段階までに抑える- 根拠: e2e-testing-patterns — 共通フィクスチャ + テスト固有フィクスチャの2層で全テストを構成
[AVOID]page.waitForLoadState('networkidle')を E2E テストのタイミング制御に使う — 特定の要素の状態を待機する- 根拠: e2e-testing-patterns — SPA のバックグラウンドリクエストで idle にならないケースがある
エラーハンドリング
[MUST]フォームバリデーション失敗はフォームにエラーを返し、throw しない — ユーザー入力エラーは通常フローとして処理しフォーム状態を保持する- 根拠: error-handling-idioms — 全 action で
submission.reply()を返しており、throw は使わない
- 根拠: error-handling-idioms — 全 action で
[SHOULD]HTTP レスポンスを生成する事前条件チェックにはinvariantResponse(Response を throw)を使い、内部ロジックのアサーションにはinvariant(Error を throw)を使い分ける- 根拠: error-handling-idioms — loader/action の 16 箇所以上で
invariantResponseが HTTP ステータスコード付きで使用
- 根拠: error-handling-idioms — loader/action の 16 箇所以上で
[SHOULD]Error Boundary はステータスコード別ハンドラを受け取る汎用コンポーネントとして実装し、各ルートでカスタマイズする- 根拠: error-handling-idioms —
GeneralErrorBoundaryがstatusHandlersを props で受け取り、10 以上のルートで利用
- 根拠: error-handling-idioms —
[AVOID]予期された HTTP エラー(404, 403 等)を外部エラー監視サービスに送信する — Sentry 等には予期しないエラーのみを報告する- 根拠: error-handling-idioms —
isRouteErrorResponseで判定し、予期されたエラーはcaptureExceptionをスキップ
- 根拠: error-handling-idioms —
セキュリティ
[MUST]セッション Cookie にはhttpOnly,secure(本番),sameSite: 'lax'を必ず設定する- 根拠: security-practices — 全 Cookie 設定で一貫して適用
[MUST]ユーザー入力由来のリダイレクト先には安全なリダイレクト検証(safeRedirect()相当)を適用し、オープンリダイレクト攻撃を防止する- 根拠: security-practices —
safeRedirect()が全てのリダイレクト箇所で使用
- 根拠: security-practices —
[MUST]レート制限はエンドポイントの機密度に応じて段階化する — 認証エンドポイントに最も厳しい制限を適用する- 根拠: security-practices —
/login,/signup,/verifyに一般の 1/10 の制限を適用
- 根拠: security-practices —
[MUST]フォールセーフな外部 API(パスワード漏洩チェック等)は、タイムアウト・サーバーエラー・不正レスポンスの全異常系で安全側にフォールバックすることをテストする- 根拠: authentication-testing — 正常系2件 + 異常系3件を全て検証し、全異常系で
falseを期待
- 根拠: authentication-testing — 正常系2件 + 異常系3件を全て検証し、全異常系で
[SHOULD]外部サービスへの依存は「未設定でもアプリが起動・動作する」ようにフォールバックを実装する- 根拠: design-philosophy — Sentry・Resend・GitHub OAuth すべて optional、未設定時はコンソール出力やモックで代替
[SHOULD]CSP nonce はリクエストごとに暗号学的に安全な方法で生成し、アプリケーション全体に一貫して伝播させる- 根拠: security-practices —
crypto.randomBytes(16).toString('hex')で生成、NonceProviderで全コンポーネントに供給
- 根拠: security-practices —
[SHOULD]レート制限の IP 識別には CDN/PaaS が付与する信頼できるヘッダーを優先し、X-Forwarded-Forやreq.ipへの直接依存を避ける- 根拠: security-practices —
fly-client-ipを優先、IP スプーフィング耐性を確保
- 根拠: security-practices —
[AVOID]GET リクエストで状態変更を行うこと —SameSite=LaxCookie に依存する CSRF 保護が無効化される- 根拠: security-practices — CSRF トークン削除の前提条件として GET mutation が存在しないことが明記
[AVOID]フォームバリデーションのエラーレスポンスにパスワード等の機密フィールドを含めること- 根拠: security-practices —
submission.reply({ hideFields: ['password'] })で機密情報を除外
- 根拠: security-practices —
プロジェクト構成と規約
[MUST]サーバー専用コードはファイル命名規約(.server.ts)でクライアントバンドルから物理的に排除し、ビルドツールの設定と合わせて二重のガードを設ける- 根拠: project-structure, architecture —
app/routes.tsで*.server.*を除外、vite-env-onlyプラグインでも保護
- 根拠: project-structure, architecture —
[MUST]import エイリアスはビルドツール固有の設定(tsconfig paths 等)ではなく、ランタイムがネイティブにサポートする仕組み(Node.js subpath imports 等)を優先する- 根拠: dev-conventions —
package.jsonのimportsに一元化し、TypeScript・Vitest・Playwright のいずれからも解決可能
- 根拠: dev-conventions —
[MUST]コード品質ツール(ESLint・Prettier・TypeScript)の設定は共有設定パッケージまたはプリセットを使い、プロジェクト側は差分のみカスタマイズする- 根拠: dev-conventions —
@epic-web/configで3ツールの設定を集約、プロジェクト側の設定ファイルは最小限
- 根拠: dev-conventions —
[SHOULD]テストファイルはテスト対象モジュールと同一ディレクトリにコロケートする- 根拠: project-structure —
app/routes/users/$username/index.test.tsxが対象ファイルの隣に配置
- 根拠: project-structure —
[SHOULD]インフラ横断的関心事(セキュリティヘッダー、レートリミット、ログ、圧縮)はアプリケーションのルートハンドラの外側(ミドルウェア層)に集約する- 根拠: architecture —
server/index.tsに全インフラミドルウェアが集約、server/app.tsは 23 行で純粋なリクエストハンドリング
- 根拠: architecture —
[SHOULD]ORM クエリではselectを明示的に指定し、必要なフィールドのみを取得する- 根拠: architecture — 全ルートが
select使用、唯一のinclude使用箇所はコメントで理由明示
- 根拠: architecture — 全ルートが
[SHOULD]認証・認可ガードは段階的な関数チェーン(getOptionalUser→requireUser→requireUserWithPermission)で構成する- 根拠: architecture — nullable 返却、リダイレクト throw、403 throw が段階的に構成
[SHOULD]設計上の意思決定は ADR(Architecture Decision Records)として記録し、「なぜその選択をしたか」を追跡可能にする- 根拠: design-philosophy — 47 件の ADR で判断の撤回・変更も追跡可能
[AVOID]アプリケーションの本質に関係しないファイル(Dockerfile、アイコン原本、ツール設定等)をプロジェクトルートに放置すること- 根拠: project-structure —
other/ディレクトリに集約し、ルートのノイズを削減
- 根拠: project-structure —
[AVOID]ツール固有のパス解決設定(TypeScriptpaths, Vitestresolve.alias等)を複数の設定ファイルに重複定義すること- 根拠: dev-conventions —
tsconfig.jsonのpathsを廃止しpackage.jsonimportsに一元化
- 根拠: dev-conventions —
CI/CD
[MUST]CI のキャッシュキーにはファイル内容のハッシュ(hashFiles)を使い、ブランチ名やタイムスタンプに依存させない- 根拠: ci-cd —
schema.prismaとmigration.sqlのハッシュ組み合わせで決定論的キャッシュ
- 根拠: ci-cd —
[MUST]テストジョブとデプロイジョブを分離し、needsでテスト全通過をデプロイの前提条件にする- 根拠: ci-cd —
deployジョブが全ジョブの成功をneedsで要求
- 根拠: ci-cd —
[SHOULD]E2E テストの並列度とリトライ回数は環境変数で切り替え、テストコード自体は環境を意識しない設計にする- 根拠: ci-cd —
process.env.CIによる分岐を設定レイヤーに限定
- 根拠: ci-cd —
[SHOULD]コンテナイメージのビルドとデプロイを分離し、Git SHA でイメージを一意に特定する- 根拠: ci-cd —
--image-label ${{ github.sha }}でビルド、同一 SHA でデプロイ
- 根拠: ci-cd —
[AVOID]テストの重いセットアップ処理(DB 構築、シード等)を毎回のテスト実行で繰り返す — 変更検知でスキップ可能にする- 根拠: ci-cd — スキーマ mtime 比較で不要な再構築をスキップ
ルール優先度の解釈
[MUST]: 違反するとバグ・セキュリティリスク・重大な設計劣化を招くルール[SHOULD]: 従うことで品質が向上するが、文脈によっては例外を許容するルール[AVOID]: 意図的に避けるべきアンチパターン・非推奨プラクティス