目次を表示する

Cursor:AI統合エディタプラットフォーム

Cursor Rules ── AIの振る舞いをコードベースに閉じ込める

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 フロントマターとマークダウン本文で構成される。フロントマターの descriptionglobsalwaysApply の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 ApplyalwaysApply: trueプロジェクト全体に通底するコーディング規約
Glob-basedglobs: ["**/*.ts"]特定ファイルタイプにのみ適用するルール
Auto-detectdescription のみ指定(globs なし)AIがコンテキストに基づいて自動適用
ManualalwaysApply: 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/ をリポジトリにコミットしてチーム全体で共有する方法と、プロジェクトが成長するにつれてルールを進化させていく戦略を解説する。