T-CREATOR

Cursor で差分が崩れる/意図しない大量変更が入るときの復旧プレイブック

Cursor で差分が崩れる/意図しない大量変更が入るときの復旧プレイブック

AI 駆動型エディタの Cursor は、コード補完や自動生成機能により開発効率を大きく向上させてくれます。しかし、AI による提案を適用した際に、意図しない大量の変更が混入したり、差分表示が崩れてレビューが困難になったりする経験はないでしょうか。

本記事では、Cursor で差分トラブルが発生した際の 復旧手順予防策 を体系的にまとめたプレイブックを提供します。エラーが起きたときに慌てず対処できるよう、段階的な解決策をご紹介しますね。

背景

Cursor は VSCode をベースに AI 機能を強化したエディタで、コードの自動補完や Chat 機能を通じたコード生成が可能です。これらの機能により、開発者は効率的にコードを記述できますが、一方で AI が生成するコードには以下のような特性があります。

Cursor における AI コード生成の特性

#項目説明
1複数ファイル同時編集Chat 機能で複数ファイルを一度に変更できる
2コンテキスト依存の提案プロジェクト全体の文脈から変更を提案する
3フォーマット自動適用AI が独自のフォーマットルールを適用することがある
4インデント・改行の調整既存コードのスタイルと異なる整形を行う場合がある
5依存関係の自動追加import 文やパッケージを自動で追加・削除する

以下の図は、Cursor で AI によるコード変更が適用される基本的なフローを示しています。

mermaidflowchart TB
  dev["開発者"] -->|Chat で指示| cursor["Cursor AI"]
  cursor -->|コンテキスト解析| analyzer["プロジェクト<br/>解析エンジン"]
  analyzer -->|ファイル特定| files["対象ファイル群"]
  cursor -->|変更提案| diff["差分生成"]
  diff -->|適用| apply["ファイル書き込み"]
  apply -->|結果| result["変更済みファイル"]
  result -.->|予期しない変更| problem["差分崩れ"]

このフローから分かるように、AI は複数のファイルを横断的に解析して変更を加えるため、開発者が想定していない箇所まで編集される可能性があります。

課題

Cursor を使った開発で発生する差分関連の問題には、いくつかの典型的なパターンがあります。これらの課題を理解しておくことで、トラブル時の原因特定がスムーズになるでしょう。

主な課題パターン

フォーマット変更の大量混入

AI がコード生成時に独自のフォーマットルールを適用し、変更対象外のコードまで整形してしまうケースです。

typescript// 元のコード
function getData() {
  return { name: 'test', value: 123 };
}
typescript// AI 適用後(意図しないフォーマット変更)
function getData() {
  return {
    name: 'test',
    value: 123,
  };
}

上記の例では、関数の中身を変更するつもりが、スペースやインデント、改行が全体的に変更されてしまっています。

インデント・改行の不整合

タブとスペースの混在や、予期しない改行の挿入により、差分が肥大化します。

#問題
1タブ → スペース変換\t が 2 スペースや 4 スペースに置き換わる
2改行コード変更LFCRLF の変換が発生
3末尾空白追加行末に不要な空白が追加される
4空行の挿入・削除関数間の空行が増減する

import 文の自動整理

AI が依存関係を解析して、使用されていない import を削除したり、新しい import を追加したりします。これ自体は便利な機能ですが、意図しないタイミングで発動すると混乱を招きます。

typescript// 元のコード
import { useState, useEffect, useMemo } from 'react';
import { Button } from '@/components/Button';
import { formatDate } from '@/utils/date';
typescript// AI による自動整理後
import { useState } from 'react';
import { formatDate } from '@/utils/date';

上記では、useEffectuseMemoButton が未使用と判断され削除されています。しかし、これらを今後使う予定だった場合、意図しない削除となります。

コメントや空白行の削除

AI がコードを最適化する過程で、コメントや空白行を不要と判断して削除することがあります。

以下の図は、差分が崩れる主なパターンとその影響範囲を示しています。

mermaidflowchart LR
  ai["AI による変更"] --> format["フォーマット<br/>変更"]
  ai --> indent["インデント<br/>不整合"]
  ai --> imports["import 整理"]
  ai --> comments["コメント削除"]

  format --> largeDiff["差分肥大化"]
  indent --> largeDiff
  imports --> unexpectedChange["意図しない<br/>機能変更"]
  comments --> infoLoss["情報損失"]

  largeDiff --> reviewHard["レビュー困難"]
  unexpectedChange --> reviewHard
  infoLoss --> reviewHard

これらの課題が複合的に発生すると、本来確認したかった変更点が埋もれてしまい、コードレビューが非常に困難になってしまいます。

解決策

差分が崩れたり意図しない変更が入ったりした場合、慌てずに段階的に対処することが重要です。ここでは、即座に実行できる復旧手順と、今後同様の問題を防ぐための予防策をご紹介します。

即時復旧手順

トラブルが発生した際は、以下の手順で迅速に復旧しましょう。

ステップ 1: Git での差分確認

まず、実際にどのような変更が加わったのかを正確に把握します。

bash# 変更されたファイルの一覧を確認
git status

このコマンドにより、変更されたファイルが一覧表示されます。予期しないファイルが含まれていないかチェックしてください。

bash# 詳細な差分を確認
git diff

差分内容を確認し、意図した変更と意図しない変更を区別します。

bash# 特定ファイルのみの差分確認
git diff path/to/file.ts

問題のあるファイルに絞って差分を確認することで、復旧すべき内容を特定できます。

ステップ 2: 部分的な変更の取り消し

すべての変更を取り消すのではなく、意図しない変更のみを元に戻します。

bash# 特定ファイルの変更を取り消し
git checkout -- path/to/file.ts

このコマンドで、指定したファイルを最新のコミット状態に戻せます。

bash# 複数ファイルを一度に取り消し
git checkout -- file1.ts file2.ts file3.ts

意図しない変更が入った複数のファイルをまとめて復旧できます。

ステップ 3: 対話的なステージング

変更内容を細かく確認しながら、コミットに含める部分を選択します。

bash# パッチモードで対話的にステージング
git add -p

このコマンドを実行すると、変更箇所ごとに「ステージングするか」を選択できます。

各変更箇所に対して、以下のような選択肢が表示されます。

#コマンド説明
1yこの変更をステージングする
2nこの変更をスキップする
3s変更をさらに小さな単位に分割する
4e手動で編集する
5q終了する

この方法により、必要な変更だけを選択的にコミットできます。

ステップ 4: Stash を活用した一時退避

変更内容を一時的に退避させて、後で整理する方法です。

bash# 現在の変更を退避
git stash push -m "AI による変更を一時退避"

作業ディレクトリがクリーンな状態に戻ります。

bash# 退避した変更の一覧を確認
git stash list

複数の stash がある場合、どれがどの変更かを確認できます。

bash# 退避した変更を復元(stash は削除されない)
git stash apply stash@{0}

必要な変更を復元し、手動で整理してから再度コミットできます。

以下の図は、復旧手順の全体フローを示しています。

mermaidflowchart TD
  start["差分崩れ発生"] --> check["git status/diff<br/>で確認"]
  check --> judge{変更範囲は?}

  judge -->|全体的に問題| fullReset["git reset で<br/>全体リセット"]
  judge -->|部分的に問題| partialFix["git checkout で<br/>部分復旧"]
  judge -->|混在している| interactive["git add -p で<br/>対話的選択"]

  fullReset --> rework["必要な変更を<br/>再適用"]
  partialFix --> verify["git diff で<br/>最終確認"]
  interactive --> verify
  rework --> verify

  verify --> done["復旧完了"]

予防策

今後同様の問題を防ぐために、以下の設定や運用ルールを導入しましょう。

EditorConfig の設定

プロジェクトルートに .editorconfig ファイルを配置することで、エディタ間でフォーマットルールを統一できます。

ini# .editorconfig
root = true
ini# すべてのファイルに共通の設定
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
ini# TypeScript/JavaScript ファイルの設定
[*.{ts,tsx,js,jsx}]
indent_style = space
indent_size = 2
ini# JSON ファイルの設定
[*.json]
indent_style = space
indent_size = 2

この設定により、Cursor の AI も統一されたフォーマットルールに従うようになります。

Prettier の導入と設定

コードフォーマッターの Prettier を導入し、自動整形ルールを明示的に定義します。

bash# Prettier のインストール
yarn add -D prettier
json// .prettierrc
{
  "semi": true,
  "trailingComma": "es5",
  "singleQuote": true,
  "printWidth": 80,
  "tabWidth": 2,
  "useTabs": false
}

上記の設定ファイルにより、プロジェクト全体で統一されたフォーマットが適用されます。

json// .prettierignore
node_modules/
dist/
build/
.next/
coverage/

フォーマット対象外のディレクトリを指定し、不要な差分を防ぎます。

ESLint の設定最適化

ESLint を適切に設定することで、import の自動整理などの挙動を制御できます。

bash# ESLint と関連プラグインのインストール
yarn add -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
json// .eslintrc.json(基本設定)
{
  "parser": "@typescript-eslint/parser",
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended"
  ],
  "parserOptions": {
    "ecmaVersion": 2022,
    "sourceType": "module"
  }
}
json// .eslintrc.json(import 整理ルール)
{
  "rules": {
    "no-unused-vars": "warn",
    "@typescript-eslint/no-unused-vars": [
      "warn",
      {
        "argsIgnorePattern": "^_",
        "varsIgnorePattern": "^_"
      }
    ]
  }
}

未使用の import を警告として扱い、自動削除されないようにします。_ で始まる変数は意図的な未使用として扱われます。

Cursor 固有の設定

Cursor の設定ファイルで AI の挙動を調整します。

プロジェクトルートに .cursorrules ファイルを作成します。

text# .cursorrules
- コードを生成する際は、既存のフォーマットルールを厳密に守ること
- import 文の自動削除は行わないこと
- コメントは保持すること
text- 変更は必要最小限にとどめ、フォーマット変更のみの差分を作らないこと
- インデントはスペース2つを使用すること

これらのルールにより、AI の挙動をプロジェクトのガイドラインに沿わせることができます。

Git フックの活用

コミット前に自動チェックを実行し、意図しない変更を検出します。

bash# Husky のインストール
yarn add -D husky lint-staged
bash# Husky の初期化
npx husky init
json// package.json
{
  "lint-staged": {
    "*.{ts,tsx,js,jsx}": [
      "eslint --fix",
      "prettier --write"
    ]
  }
}

ステージングされたファイルに対して、自動的に ESLint と Prettier を適用します。

bash#!/bin/sh
# .husky/pre-commit
. "$(dirname "$0")/_/husky.sh"

npx lint-staged

この pre-commit フックにより、コミット前に自動的にフォーマットとリントが実行され、統一されたコードスタイルが保たれます。

トラブル発生時のチェックリスト

問題が発生したときは、以下のチェックリストに従って確認しましょう。

#確認項目コマンド例
1変更されたファイル数を確認git status
2各ファイルの差分を確認git diff
3フォーマット変更のみの差分を特定git diff -w(空白無視)
4意図した変更が含まれているか確認ファイルごとに git diff ファイル名
5import 文の変更を確認差分の先頭部分をチェック
6コメントの削除がないか確認git diff でコメント行を探す
7設定ファイルの有無を確認.editorconfig.prettierrc の存在確認

このチェックリストを活用することで、体系的に問題の原因を特定できます。

具体例

実際のトラブルシナリオと、その復旧プロセスを具体的に見ていきましょう。

シナリオ: Chat 機能で新機能追加後に大量の差分が発生

あるプロジェクトで、Cursor の Chat 機能を使って新しい認証機能を追加しました。しかし、git status を確認すると、予想外に 15 ファイルが変更されていることが判明しました。

問題の発見

bash# 変更状況の確認
git status

実行結果として、以下のような出力が表示されます。

textChanges not staged for commit:
  modified:   src/auth/login.ts
  modified:   src/auth/register.ts
  modified:   src/components/Button.tsx
  modified:   src/components/Input.tsx
  modified:   src/utils/validation.ts
  modified:   src/utils/api.ts
  ... (他9ファイル)

Button.tsxInput.tsx など、認証機能とは無関係に見えるファイルも変更されています。

bash# 詳細な差分確認
git diff src/components/Button.tsx

差分を確認すると、フォーマット変更のみで機能的な変更がないことが分かります。

diff- function Button({onClick,children}){
-   return <button onClick={onClick}>{children}</button>
+ function Button({ onClick, children }) {
+   return <button onClick={onClick}>{children}</button>;
 }

このように、スペースやセミコロンの追加のみで、実質的な変更はありません。

復旧手順の実践

まず、意図しないフォーマット変更が入ったファイルを元に戻します。

bash# フォーマット変更のみのファイルを復旧
git checkout -- src/components/Button.tsx src/components/Input.tsx

これらのファイルが最新のコミット状態に戻ります。

次に、本当に変更が必要なファイルだけを対話的に選択します。

bash# 認証関連ファイルを対話的にステージング
git add -p src/auth/login.ts

変更箇所ごとに y(ステージング)または n(スキップ)を選択します。

textStage this hunk [y,n,q,a,d,s,e,?]? y

必要な変更のみを選択してステージングします。

bash# 他の認証ファイルも同様に処理
git add -p src/auth/register.ts
git add -p src/utils/validation.ts

すべての必要なファイルを対話的に確認しながらステージングします。

最終確認として、ステージングされた変更を確認します。

bash# ステージングされた変更の確認
git diff --cached

意図した変更のみがステージングされていることを確認できます。

bash# コミット実行
git commit -m "feat: 認証機能の追加"

これで、意図した変更のみがコミットされました。

以下の図は、この復旧プロセスの流れを示しています。

mermaidsequenceDiagram
  participant Dev as 開発者
  participant Git as Git
  participant Files as ファイル群

  Dev->>Git: git status
  Git->>Dev: 15ファイル変更を検出
  Dev->>Git: git diff Button.tsx
  Git->>Dev: フォーマット変更のみ

  Dev->>Git: git checkout -- Button.tsx
  Git->>Files: ファイルを復旧

  Dev->>Git: git add -p login.ts
  Git->>Dev: 変更箇所を表示
  Dev->>Git: y(必要な変更を選択)

  Dev->>Git: git diff --cached
  Git->>Dev: 意図した変更のみ確認
  Dev->>Git: git commit
  Git->>Dev: 復旧完了

シナリオ: import 文の自動削除による機能破壊

次のシナリオでは、AI が未使用と判断した import を削除してしまい、実行時エラーが発生しました。

問題の発生

typescript// 元のコード(src/pages/dashboard.tsx)
import { useState, useEffect } from 'react';
import { fetchUserData } from '@/api/user';
import { Chart } from '@/components/Chart';
import { Table } from '@/components/Table';

export default function Dashboard() {
  const [data, setData] = useState(null);

  // 今後 Chart と Table を使う予定
  return <div>Dashboard</div>;
}

Cursor の Chat で「ローディング表示を追加して」と依頼したところ、以下のように変更されました。

typescript// AI による変更後
import { useState } from 'react';
import { fetchUserData } from '@/api/user';

export default function Dashboard() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);

  return <div>{loading ? 'Loading...' : 'Dashboard'}</div>;
}

useEffectChartTable の import が削除され、今後の実装に支障が出てしまいました。

復旧とその後の対策

差分を確認して、削除された import を特定します。

bash# 差分を確認
git diff src/pages/dashboard.tsx

削除された import 行が赤字で表示されます。

変更全体を一旦退避します。

bash# 現在の変更を退避
git stash push -m "ローディング表示の追加"

作業ディレクトリがクリーンな状態に戻ります。

元のファイルから必要な import を確認します。

bash# 退避前の状態を確認
git stash show -p stash@{0}

削除された import を特定し、手動で復元します。

退避した変更を復元します。

bash# 変更を復元
git stash apply

ファイルを手動で編集し、削除された import を追加します。

typescript// 手動で修正したコード
import { useState, useEffect } from 'react';
import { fetchUserData } from '@/api/user';
import { Chart } from '@/components/Chart';
import { Table } from '@/components/Table';

export default function Dashboard() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);

  return <div>{loading ? 'Loading...' : 'Dashboard'}</div>;
}

必要な import を復元し、ローディング機能も追加された状態になります。

予防のための ESLint 設定

今後同様の問題を防ぐために、ESLint の設定を調整します。

json// .eslintrc.json
{
  "rules": {
    "@typescript-eslint/no-unused-vars": [
      "warn",
      {
        "varsIgnorePattern": "^_",
        "argsIgnorePattern": "^_",
        "ignoreRestSiblings": true
      }
    ]
  }
}

この設定により、未使用の変数は警告として表示されるだけで、自動削除はされません。

さらに、.cursorrules ファイルでルールを明示します。

text# .cursorrules
- import 文は、明示的に指示されない限り削除しないこと
- 未使用の import があっても、将来使用する可能性を考慮すること

これらの設定により、AI が過度に最適化することを防げます。

シナリオ: フォーマット設定の不統一による差分肥大化

最後のシナリオでは、チームメンバー間でフォーマット設定が異なり、コミットのたびに大量の差分が発生していました。

問題の状況

開発者 A は VSCode でタブ幅 2、開発者 B は Cursor でタブ幅 4 を使用していました。その結果、以下のような差分が頻繁に発生します。

diff function calculateTotal(items) {
-  return items.reduce((sum, item) => {
-    return sum + item.price;
-  }, 0);
+    return items.reduce((sum, item) => {
+        return sum + item.price;
+    }, 0);
 }

機能的な変更はないのに、インデントのみが変わっています。

統一設定の導入

プロジェクトルートに .editorconfig を作成します。

ini# .editorconfig
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.{ts,tsx,js,jsx}]
indent_style = space
indent_size = 2

Prettier の設定を追加します。

bash# Prettier のインストール
yarn add -D prettier
json// .prettierrc
{
  "semi": true,
  "trailingComma": "es5",
  "singleQuote": true,
  "printWidth": 80,
  "tabWidth": 2,
  "useTabs": false,
  "endOfLine": "lf"
}

Git フックで自動フォーマットを設定します。

bash# Husky と lint-staged のインストール
yarn add -D husky lint-staged
npx husky init
json// package.json
{
  "lint-staged": {
    "*.{ts,tsx,js,jsx}": [
      "prettier --write",
      "eslint --fix"
    ]
  }
}
bash#!/bin/sh
# .husky/pre-commit
npx lint-staged

これにより、コミット前に自動的に統一されたフォーマットが適用されます。

フォーマット統一の実施

既存のすべてのファイルに統一フォーマットを適用します。

bash# プロジェクト全体をフォーマット
yarn prettier --write "src/**/*.{ts,tsx,js,jsx}"

すべてのファイルが統一されたフォーマットに変換されます。

bash# フォーマット変更をコミット
git add .
git commit -m "chore: コードフォーマットの統一"

この一度のコミットにより、以降はフォーマットによる差分が発生しなくなります。

以下の図は、フォーマット統一による効果を示しています。

mermaidflowchart LR
  before["統一前"] --> devA["開発者A<br/>(タブ幅2)"]
  before --> devB["開発者B<br/>(タブ幅4)"]
  devA --> conflict["差分衝突"]
  devB --> conflict

  after["統一後"] --> config["EditorConfig<br/>Prettier"]
  config --> allDev["全開発者<br/>(タブ幅2)"]
  allDev --> clean["差分クリーン"]

  style conflict fill:#ffcccc
  style clean fill:#ccffcc

まとめ

Cursor での差分トラブルは、AI の強力な機能ゆえに発生するものですが、適切な知識と対処法があれば恐れる必要はありません。

本記事でご紹介した復旧プレイブックのポイントをまとめます。

復旧の基本ステップ

#ステップキーコマンド
1差分の確認git statusgit diff
2部分的な取り消しgit checkout -- ファイル名
3対話的な選択git add -p
4一時退避git stash

予防策の実装

#予防策ファイル/コマンド
1フォーマット統一.editorconfig.prettierrc
2Lint ルール設定.eslintrc.json
3Cursor ルール定義.cursorrules
4Git フック活用Husky、lint-staged

これらの対策を実装することで、AI による意図しない変更を最小限に抑え、差分レビューを効率的に行えるようになります。トラブルが発生した際も、本記事のプレイブックに従って冷静に対処すれば、確実に復旧できるでしょう。

Cursor の AI 機能は非常に強力ですが、適切な設定とワークフローがあってこそ、その真価を発揮します。本記事が、より快適な Cursor での開発体験の一助となれば幸いです。

関連リンク