Cursor Rules ── AIの振る舞いをコードベースに閉じ込める
対象読者: Cursorを日常的に使っており、「なぜかAIが自分のプロジェクトのスタイルに合ったコードを書いてくれない」「毎回同じ指示を繰り返している」と感じている開発者。
AIコードエディタを使い込んでいくと、必ずある壁にぶつかる。「毎回 use strict を使ってと言わなければいけない」「vitest じゃなく Jest のコードを生成してくる」「コンポーネントのディレクトリ構造がバラバラになる」。これらはすべて、AIがプロジェクト固有の文脈を知らないことから生まれる摩擦だ。
Cursor Rules は、この問題に対する根本的な解答である。
Section 1: Cursor Rules とは何か
Cursor Rules を一言で表すなら、「すべてのAIリクエストに自動注入される、永続的なシステムプロンプト」だ。
Cursor でコードを補完するとき、Chat で質問するとき、Composer でコードを生成するとき、それらすべてのリクエストの背後では、モデルに対してプロンプトが送信されている。Cursor Rules は、そのプロンプトの冒頭に常に差し込まれるテキストである。つまり、一度設定してしまえば、開発者が何も言わなくてもAIはそのルールを踏まえた上でコードを生成し続ける。
この仕組みは補完・Chat・Composer のすべてに効く。「TypeScriptの strictモードを使うこと」というルールを設定すれば、補完で生成されるコードも、Composerで作られる新規ファイルも、Chat で提案されるサンプルコードも、すべて strict モードを前提にした書き方になる。
.cursorrules から .mdc 形式への移行
Cursor の初期バージョンでは、プロジェクトルートに .cursorrules という単一ファイルを置く方式だった。この方式は今も動作するが、レガシーとして扱われており、2024年末以降は .mdc 形式が推奨されている。
.mdc 形式の最大の利点は「どのファイルに・どの条件で適用するか」を細かく制御できる点だ。TypeScript のルールは .ts ファイルにだけ適用し、テストのルールはテストファイルにだけ適用する、という粒度の制御が可能になる。これにより、ルールの数が増えても不必要なルールがトークンを消費することを防げる。
Section 2: .mdc 形式の詳細
.mdc ファイルは .cursor/rules/ ディレクトリ(プロジェクト固有)または ~/.cursor/rules/ ディレクトリ(グローバル設定)に配置する。
プロジェクト/
├── .cursor/
│ └── rules/
│ ├── typescript-style.mdc
│ ├── testing-patterns.mdc
│ ├── component-structure.mdc
│ └── git-workflow.mdc
├── src/
└── package.json
各 .mdc ファイルは YAML フロントマターとマークダウン本文で構成される。フロントマターの description・globs・alwaysApply の3つのフィールドが、「このルールをいつ適用するか」を制御するキーだ。
---
description: TypeScript コーディングルール
globs: ["**/*.ts", "**/*.tsx"]
alwaysApply: false
---
# TypeScript スタイルガイド
## 基本方針
- `tsconfig.json` の `strict: true` を必須とする
- `any` 型の使用を禁止する(`unknown` を代わりに使うこと)
- 非同期処理は `async/await` に統一し、`.then()` チェーンを避ける
## 型定義
- インターフェースは `I` プレフィックスをつけない(`IUser` ではなく `User`)
- 型エイリアスより `interface` を優先する(オブジェクト型の場合)
- `as` キャストは型ガードで代替できない場合のみ許可
## インポート
- パスエイリアス `@/` を使用する(`../../components` は禁止)
- named import を優先し、default import は React のみ許可
alwaysApply: false かつ globs を指定した場合、Cursor は .ts・.tsx ファイルを編集しているときだけこのルールを注入する。alwaysApply: true にするとファイルタイプに関わらず常に適用される。
Section 3: ルールの種類と適用戦略
Cursor Rules には4つの適用モードがある。プロジェクトの性質に応じてこれらを組み合わせることが、ルール設計の核心だ。
4つの適用モード
| モード | 設定方法 | 用途 |
|---|---|---|
| Always Apply | alwaysApply: true | プロジェクト全体に通底するコーディング規約 |
| Glob-based | globs: ["**/*.ts"] | 特定ファイルタイプにのみ適用するルール |
| Auto-detect | description のみ指定(globs なし) | AIがコンテキストに基づいて自動適用 |
| Manual | alwaysApply: false、globs なし | @rulename で明示的に参照したとき |
Always Apply は使いすぎると全リクエストのトークンを消費するため、本当にすべての場面で必要なルール(コミットメッセージの形式、セキュリティ要件など)に限定すべきだ。
Auto-detect は description の内容を Cursor が解釈し、関連性が高いと判断したときだけ適用する。たとえば description: "Reactコンポーネントの構造ルール" と書けば、コンポーネントに関連する作業をしているときだけ注入される。
Manual は @git-workflow のように Chat や Composer で明示的に参照したときのみ適用される。チーム向けのガイドラインや、特定の作業でのみ必要なルールに適している。
ルール適用の優先順位
複数のルールが同時に適用される場合、以下の優先順位に従う。
flowchart TD
A[AIリクエスト発生] --> B{ルールのマッチング}
B --> C[Manual ルール<br/>@参照で明示的に指定]
B --> D[Always Apply ルール<br/>alwaysApply: true]
B --> E[Glob-based ルール<br/>ファイルパターンが一致]
B --> F[Auto-detect ルール<br/>コンテキストに関連]
C --> G[優先度: 最高]
D --> H[優先度: 高]
E --> I[優先度: 中]
F --> J[優先度: 低(自動判定)]
G --> K[プロンプトに注入]
H --> K
I --> K
J --> K
style C fill:#ff6b6b,color:#fff
style D fill:#ffa94d,color:#fff
style E fill:#74c0fc,color:#fff
style F fill:#a9e34b,color:#000
矛盾するルールが同時に適用された場合(後述のアンチパターン参照)、AIは両方のルールを読んだ上で「より具体的な方」を優先しようとするが、動作は保証されない。ルール間の矛盾はそもそも発生しないよう設計すべきだ。
Section 4: 実践的なルールセット例
実際のプロジェクトで使用できる4つのルールファイルを示す。これらは Next.js + TypeScript + Vitest + React の構成を想定している。
1. typescript-style.mdc ── TypeScript コーディングスタイル
---
description: TypeScript のコーディングスタイルと型安全性ルール
globs: ["**/*.ts", "**/*.tsx"]
alwaysApply: false
---
# TypeScript スタイルガイド
## 型安全性
- `any` は使用禁止。`unknown` を使い、型ガードで絞り込むこと
- `as Type` キャストは最終手段。型ガード関数 (`is Type`) を優先する
- `!` (non-null assertion) の使用を禁止。Optional chaining か条件分岐で対処する
## 関数定義
- 関数の戻り値型は必ず明示する(推論に頼らない)
- `function` 宣言と `const arrow = () =>` は一貫して使い分ける
- コンポーネントとユーティリティ: `const`
- トップレベルの公開関数: `function` 宣言
## エラーハンドリング
- `catch (e)` の `e` は `unknown` として扱い、`instanceof Error` でチェックする
- カスタムエラークラスは `AppError` を継承すること(@src/lib/errors.ts 参照)
2. testing-patterns.mdc ── テストの書き方
---
description: Vitest を使ったテストの書き方と命名規則
globs: ["**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts"]
alwaysApply: false
---
# テストガイドライン(Vitest)
## テストの構造
- `describe` でテスト対象の単位を区切る
- `it` のdescriptionは「〜すること」という日本語で書く
- Arrange-Act-Assert パターンを明示的にコメントで分割する
```typescript
describe('calculateDiscount', () => {
it('会員ユーザーには10%の割引を適用すること', () => {
// Arrange
const user = { type: 'member' };
const price = 1000;
// Act
const result = calculateDiscount(price, user);
// Assert
expect(result).toBe(900);
});
});
モックとスタブ
- 外部 API は必ずモックする(実際のネットワークリクエストを禁止)
vi.mock()はファイルの先頭に配置する- テスト後は
vi.clearAllMocks()を afterEach で呼ぶ
カバレッジ
- ビジネスロジック関数は 90% 以上のカバレッジを目標とする
- ハッピーパスだけでなく、境界値・エラーケースを必ずテストする
### 3. `component-structure.mdc` ── React コンポーネントの構造
```yaml
---
description: React コンポーネントのファイル構造・命名・Props定義
globs: ["src/components/**/*.tsx", "src/app/**/*.tsx"]
alwaysApply: false
---
# React コンポーネントガイドライン
## ファイル構造
各コンポーネントは以下の順序で記述する:
1. import 文(外部ライブラリ → 内部モジュール)
2. 型定義(Props interface)
3. コンポーネント本体
4. デフォルトエクスポート(named export を優先)
## Props の設計
- Props は `ComponentNameProps` という名前の interface で定義する
- 必須プロパティと任意プロパティを明確に分ける
- children を受け取る場合は `React.PropsWithChildren<Props>` を使う
## Server Component / Client Component
- デフォルトは Server Component(`'use client'` を書かない)
- `useState`・`useEffect`・イベントハンドラを使う場合のみ Client Component にする
- `'use client'` は末端のインタラクティブな部分に限定し、ツリーの上位に置かない
4. git-workflow.mdc ── コミット・PR のルール
---
description: Gitコミットメッセージとプルリクエストの作成ルール
alwaysApply: false
---
# Git ワークフロー
## コミットメッセージ(Conventional Commits)
形式: `<type>(<scope>): <subject>`
- `feat`: 新機能
- `fix`: バグ修正
- `refactor`: リファクタリング(機能変更なし)
- `test`: テストの追加・修正
- `chore`: ビルドプロセス・ツール設定の変更
例:
- `feat(auth): JWTリフレッシュトークンの実装`
- `fix(checkout): カート合計額の計算ミスを修正`
## プルリクエスト
- タイトルはコミットメッセージと同じ形式にする
- PR の説明には「変更の背景」「変更内容の概要」「テスト方法」を含める
- 1 PR = 1つの論理的な変更に限定する(複数の無関係な変更を混ぜない)
Section 5: Cursor Rules のアンチパターン
ルールを運用していくと、効果が薄れる「腐敗」が始まることがある。以下は実際によく見られるアンチパターンだ。
アンチパターン 1: 500行を超える巨大ルール
Cursor Rules はトークンとして消費される。1つのルールファイルが500行を超えると、モデルはルールの後半部分を「実質的に無視する」傾向が強くなる。さらに、ルールが長すぎると開発者自身も内容を把握できなくなり、矛盾したルールが蓄積していく。
Before(アンチパターン):
---
description: プロジェクト全体のルール(533行)
alwaysApply: true
---
# 全体ルール
## TypeScriptルール
(100行)
## テストルール
(80行)
## Reactルール
(120行)
## CSSルール
(60行)
## API設計ルール
(90行)
## Git ルール
(83行)
# 以下、AIに読まれない可能性が高い...
After(推奨パターン):
.cursor/rules/
├── typescript-style.mdc (40行, globs: *.ts/*.tsx)
├── testing-patterns.mdc (35行, globs: *.test.ts)
├── component-structure.mdc (30行, globs: src/components/**)
├── api-design.mdc (45行, globs: src/api/**)
└── git-workflow.mdc (25行, alwaysApply: false)
ルールは「1つの関心事」に限定し、50〜100行を目安にする。
アンチパターン 2: コードのコピペ
ルール内に実際のコードをそのまま貼り付けるパターンがある。これは二重管理を生む。コードが変更されてもルール内のコードは古いままになり、AIが古いパターンを参考にしてしまう。
Before(アンチパターン):
## エラーハンドリングの例
以下のパターンを使うこと:
```typescript
// 300行のエラーハンドリングコード
class AppError extends Error {
// ... (古い実装がそのまま残っている)
}
After(推奨パターン):
## エラーハンドリング
- エラーハンドリングは `@src/lib/errors.ts` のパターンに従うこと
- 新しいエラークラスを追加する場合は `AppError` を継承する
@filename でファイルを参照することで、コードが変更されても常に最新のパターンを参照させることができる。
アンチパターン 3: 矛盾するルール
TypeScript の Always Apply ルールと JavaScript の Glob ルールが競合するケース、または「exportはnamed exportを使う」というルールと「default exportを使う」というルールが別々のファイルに書かれているケースがある。
矛盾の検出方法: ルールファイルを追加した後、Composer に「プロジェクトのルールを確認して、矛盾する記述があれば報告してください」と依頼することを定期的に行う。矛盾が検出されたら、より具体的なルール(glob が狭い方)を正として、もう一方を修正する。
ルール設計のチェックリスト
ルールファイルを追加・更新する際は、以下を確認する。
- 100行以内に収まっているか
- 1つの関心事に限定されているか
- コードのコピペではなく
@filename参照を使っているか - 他のルールファイルと矛盾しないか
-
globsの設定は適切か(範囲が広すぎないか) - 3ヶ月前のルールは今も有効か(定期的な棚卸し)
Cursor Rules を適切に設計することで、AIとの対話から「プロジェクト固有の文脈を毎回説明する」という摩擦がなくなる。AIはあなたのプロジェクトの「新しいチームメンバー」ではなく、「プロジェクトを熟知したコア開発者」として振る舞うようになる。
次章予告: ルールを設計したら、次はチームで Cursor を使う際の共有・管理のプラクティスを整える必要がある。次章では、
.cursor/rules/をリポジトリにコミットしてチーム全体で共有する方法と、プロジェクトが成長するにつれてルールを進化させていく戦略を解説する。