AIの提案がいつも「惜しい」問題
Cursor を使い始めてしばらくすると、こんな不満が出てくる。
- コード補完が
anyを使う(TypeScript strict モードなのに) -
varで変数を宣言してくる - コンポーネントが250行を超えても分割してくれない
-
console.logを残したまま PR を作ろうとする - 自分のプロジェクト固有のユーティリティ関数を使わず、毎回ゼロから書き直す
これらは Cursor が悪いのではなく、AIにプロジェクト固有のルールが伝わっていないことが原因だ。
Cursor Rules を正しく設定すると、AIは「このプロジェクトのAIペアプログラマー」に変わる。本記事では、.cursor/rules/ ディレクトリを使ったProject Rules の実践的な設定方法を解説する。
Cursor Rulesの3種類を整理する
Cursor には現在3種類のルール設定がある。
| 種類 | 設定場所 | スコープ | バージョン管理 |
|---|---|---|---|
| Project Rules | .cursor/rules/*.mdc |
プロジェクト単位 | できる(推奨) |
| User Rules | Cursor設定画面 | 全プロジェクト共通 | できない |
| プロジェクトルート | プロジェクト単位 | できる |
.cursorrules はレガシーとして残っているが、現在は Project Rules(.cursor/rules/) が推奨される。理由はシンプルで、複数ファイルに分割して管理でき、ファイルごとに適用条件を細かく設定できるからだ。
Project Rulesのディレクトリ構成
実際に運用しているプロジェクトでは、こんな構成にしている:
.cursor/
└── rules/
├── 00-core.mdc # 常時適用:基本方針・禁止事項
├── 10-typescript.mdc # TypeScript ファイル編集時
├── 20-react-component.mdc # Reactコンポーネント編集時
├── 30-testing.mdc # テストファイル編集時
└── 90-git-commit.mdc # コミットメッセージ生成時
ファイル名の先頭に番号をつけることで、優先度の視認性が上がる。.mdc 拡張子はMarkdownにメタデータを付加したCursor独自の形式だ。
各ルールファイルの実践例
00-core.mdc(常時適用ルール)
---
alwaysApply: true
---
# Core Development Rules
## 禁止事項(絶対に守ること)
- `any` 型の使用禁止。不明な型は `unknown` を使い、型ガードで絞り込む
- `var` の使用禁止。`const` を優先し、必要な場合のみ `let` を使う
- `console.log` を本番コードに残さない
- マジックナンバーを直接使わない。名前付き定数として定義する
## プロジェクト固有のユーティリティ
- 日付フォーマットには `src/utils/date.ts` の `formatDate()` を使う
- API呼び出しには `src/lib/api.ts` の `apiClient` を使う(`fetch` を直接使わない)
- エラーハンドリングには `src/utils/error.ts` の `handleError()` を使う
## コメントルール
- 「何をしているか」ではなく「なぜそうしているか」をコメントに書く
- JSDoc はpublic関数のみ。internal関数への過剰なコメントは不要
alwaysApply: true にしたルールは、すべての会話で自動的にコンテキストに含まれる。プロジェクト全体に適用したい基本ルールをここに書く。
10-typescript.mdc(TypeScript向け)
---
alwaysApply: false
globs:
- "**/*.ts"
- "**/*.tsx"
---
# TypeScript Coding Rules
## 型定義
- オブジェクトの形状には `interface` を使う(`type` は Union/Intersection/Mapped types向け)
- 関数には必ず戻り値の型を明示する
- `as` キャストは最終手段。型ガードで絞り込む方法を先に検討する
## 関数スタイル
- `Promise` を直接扱わず、`async/await` を使う
- エラーは例外ではなく Result 型で表現する(`src/types/result.ts` 参照)
## 命名規則
- コンポーネント: PascalCase(例: `UserProfileCard`)
- 関数: camelCase + 動詞始まり(例: `fetchUserData`)
- 定数: SCREAMING_SNAKE_CASE(例: `MAX_RETRY_COUNT`)
- 型/インターフェース: PascalCase + 末尾に用途(例: `UserResponse`, `CreateUserInput`)
globs でパターンを指定すると、該当ファイルを編集するときだけ自動的にルールが適用される。
20-react-component.mdc(React向け)
---
alwaysApply: false
globs:
- "src/components/**/*.tsx"
- "src/app/**/*.tsx"
---
# React Component Rules
## コンポーネント設計
- 1ファイルにつき1コンポーネント(150行を超えたら分割を検討)
- Propsインターフェースは `[コンポーネント名]Props` と命名
- デフォルトエクスポートは使わない(名前付きエクスポートを使う)
## Hooks
- カスタムフックは `src/hooks/` に配置し、`use` プレフィックスをつける
- `useEffect` の依存配列を省略しない。ESLint の `exhaustive-deps` ルールに従う
- グローバル状態は Zustand で管理(`useState` でローカル状態を管理しすぎない)
## 例
コンポーネントの基本構造:
\`\`\`typescript
interface UserCardProps {
userId: string;
onSelect?: (id: string) => void;
}
export function UserCard({ userId, onSelect }: UserCardProps) {
// ...
}
\`\`\`
30-testing.mdc(テスト向け)
---
alwaysApply: false
globs:
- "**/*.test.ts"
- "**/*.test.tsx"
- "**/*.spec.ts"
---
# Testing Rules
## テスト構造
- `describe` ブロックは「何をテストするか」、`it` ブロックは「どういう場合に何になるか」
- テストデータは `test/fixtures/` から読み込む(インラインに大量データを書かない)
## アサーション
- `expect(value).toBe(expected)` は primitive 値のみ
- オブジェクト比較は `toEqual` または `toMatchObject`
- 非同期テストには必ず `await` をつける
## 命名
\`\`\`typescript
// Good
it("should return null when user is not found", async () => {});
it("throws AuthError when token is expired", () => {});
// Bad
it("test user fetch", () => {});
it("works correctly", () => {});
\`\`\`
ルールの適用モードを使い分ける
.mdc ファイルのフロントマターで、適用タイミングを制御できる。
---
# 常時適用(すべての会話にコンテキストとして含まれる)
alwaysApply: true
# ファイルパターンに合致したとき自動適用
globs:
- "src/components/**/*.tsx"
# AIが「関連する」と判断したときに適用(Cursorが自動判断)
description: "Use when working with database queries or Prisma schema"
---
description を設定したルールは、AIが会話の文脈から「このルールが関連する」と判断したときに自動で適用される。Prismaスキーマを編集しているときだけDBルールを適用したい、といったケースに便利だ。
チームでProject Rulesを使う際のポイント
バージョン管理に含める
# .gitignore には追加しない
git add .cursor/rules/
git commit -m "Add Cursor project rules for TypeScript/React"
Project Rules はコードと同じリポジトリで管理することで、チーム全員が同じAIアシスタントの挙動を得られる。新メンバーがクローンした瞬間から、プロジェクト固有のルールに従ったAI補完が使える。
ルールの粒度を適切に保つ
ルールが長すぎると、AIがコンテキストのどこに注目すべきか分からなくなる。目安として:
- 1つのルールファイルは 200行以下
- 1項目は 1〜3行で書く(長い説明よりも具体的な例を添える)
- ネガティブルール(「〜してはいけない」)には必ず理由か代替案を添える
Cursor自身にルールをメンテナンスさせる
「このプロジェクトでよく使うパターンをルール化して」と Cursor に依頼すると、既存コードを読んで .cursor/rules/ に追記してくれる。ルール管理のコストを大幅に減らせる技だ。
設定前後の変化
実際にプロジェクトにProject Rulesを導入した結果:
Before:
- AI提案に
anyが含まれることが多く、毎回手直しが必要 - 自前のユーティリティを使ってくれず、
fetchを直接書いてくる - テスト名が
"test1""works"のような意味のない名前になる
After:
-
unknown+ 型ガードで型安全な提案が来る -
apiClientを自然に使ったコードを提案してくれる - テスト名が
"should return 404 when user does not exist"のように意味が通る
AIへの「お願い」をその場でタイプするのではなく、ルールファイルに一度書いておけば毎回伝わる。これがProject Rulesの本質的な価値だ。
まとめ:Cursor Rulesで「プロジェクト専用AI」を育てる
| 設定 | 効果 |
|---|---|
00-core.mdc(alwaysApply) |
全会話に禁止事項と基本方針を伝える |
10-typescript.mdc(globs) |
TSファイル編集時だけ型ルールを適用 |
20-react-component.mdc(globs) |
コンポーネント編集時にコンポーネント設計を守らせる |
description 付きルール |
AIが文脈判断して自動適用 |
.cursor/rules/ はコードと同じようにバージョン管理でき、チームで共有できる。「毎回同じことをAIに説明している」と感じたら、それはルールにすべきサインだ。
Cursor Rulesは一度作れば育ち続ける資産になる。まずは 00-core.mdc に「このプロジェクトでやってはいけないこと」を3つ書くところから始めてみてほしい。