🤖

VSCode拡張機能でCursor RulesとVSCode Custom Instructionsを相互変換する仕組みを作る

に公開

はじめに

近年、CursorやGitHub Copilot、Claude Codeなど、様々なAI開発支援ツールが登場しています。これらのツールは独自のルール形式を持っており、プロジェクトごとにコーディング規約やスタイルガイドを定義できます。
しかし、複数のツールを使い分ける際、それぞれの形式でルールを管理するのは煩雑です。
そこで、Cursor / VSCode 両エディタで使用できるVSCode拡張機能を作成し相互変換する仕組みを作ってみました。

本記事では、Cursor RulesとVSCode Custom Instructionsを相互変換するVSCode拡張機能の実装方法について解説します。

作成する拡張機能

ルールファイルを右クリックし、メニューからルールを変換できるVSCode拡張機能を作成します。

対象となるルール形式

Cursor Rules (.mdc)

Cursorが使用するルール形式で、.cursor/rulesディレクトリに.mdcファイルとして保存されます。

---
globs: *.test.ts,*.spec.ts
alwaysApply: false
---

# テストファイルのガイドライン
テストコードの記述方法...

VSCode Custom Instructions (.instructions.md)

VSCodeで使用されるカスタム指示形式で、.github/instructionsディレクトリに.instructions.mdファイルとして保存されます。

---
applyTo: 'src/**/*.ts'
---

# TypeScriptコーディングガイド
TypeScriptのベストプラクティス...

拡張機能の基本構造

プロジェクトのディレクトリ構造

ai-rules-converter/
├── src/
│   ├── converter.ts               # 変換ロジック
│   └── extension.ts               # 拡張機能エントリーポイント
├── out/                           # コンパイル済みJavaScript
│   ├── converter.js
│   └── extension.js
├── package.json                   # プロジェクト設定
└── tsconfig.json                  # TypeScript設定

各ディレクトリの役割

  • src/: TypeScriptソースコード

    • extension.ts: VSCode拡張機能のエントリーポイント、コマンド登録
    • converter.ts: ルール変換のコアロジック、AI連携処理
  • out/: TypeScriptコンパイル後のJavaScriptファイル

    • VSCode拡張機能として実際に実行されるコード

1. プロジェクトセットアップ

# プロジェクトの初期化
npm init -y

# 必要な依存関係のインストール
npm install openai gray-matter
npm install -D @types/node @types/vscode typescript

2. package.jsonの設定

{
  "name": "ai-rules-converter",
  "displayName": "AI Rules Converter",
  "description": "Convert between AI Tools Rules",
  "version": "0.0.1",
  "publisher": "asa918yk",
  "engines": {
    "vscode": "^1.74.0"
  },
  "categories": ["Other"],
  "activationEvents": [
    "onCommand:ai-rules-converter.convertToCursorRules",
    "onCommand:ai-rules-converter.convertToVSCodeInstructions"
  ],
  "main": "./out/extension.js",
  "contributes": {
    "commands": [
      {
        "command": "ai-rules-converter.convertToCursorRules",
        "title": "Convert to Cursor Rules"
      },
      {
        "command": "ai-rules-converter.convertToVSCodeInstructions",
        "title": "Convert to VSCode Custom Instructions"
      }
    ],
    "menus": {
      "explorer/context": [
        {
          "command": "ai-rules-converter.convertToCursorRules",
          "when": "resourceFilename =~ /\\.instructions\\.md$/",
          "group": "ai-rules-converter"
        },
        {
          "command": "ai-rules-converter.convertToVSCodeInstructions",
          "when": "resourceExtname == .mdc",
          "group": "ai-rules-converter"
        }
      ]
    },
    "configuration": {
      "title": "AI Rules Converter",
      "properties": {
        "ai-rules-converter.openaiApiKey": {
          "type": "string",
          "default": "",
          "description": "OpenAI API Key for converting rules"
        }
      }
    }
  },
  "scripts": {
    "vscode:prepublish": "npm run compile",
    "compile": "tsc -p ./",
    "watch": "tsc -watch -p ./"
  },
  "devDependencies": {
    "@types/node": "^18.11.18",
    "@types/vscode": "^1.74.0",
    "@typescript-eslint/eslint-plugin": "^5.48.0",
    "@typescript-eslint/parser": "^5.48.0",
    "eslint": "^8.31.0",
    "typescript": "^4.9.4"
  },
  "dependencies": {
    "openai": "^4.20.0",
    "gray-matter": "^4.0.3"
  }
}
  • name
    拡張機能の内部名を指定します。(小文字・ハイフン推奨)
  • displayName
    マーケットプレイスや VS Code 内で表示される名前を指定します。
  • description
    簡単な説明文を書きます。
  • version
    SemVer に従ったバージョン番号を指定します。
  • publisher
    拡張を公開するアカウント/組織名を指定します。
  • engines.vscode
    対応する VS Code のバージョン範囲を指定します。
  • categories
    Marketplace での分類を指定します。
  • activationEvents
    拡張を有効化するタイミングを指定します。
    • onCommand:command.id → コマンド実行時に有効化
  • main
    拡張機能のエントリポイントとなる JS ファイルを指定します。
  • contributes.commands
    拡張機能がVSCodeに「新しいコマンド」を登録するための宣言部分です。
    • command → コマンド登録時に使用する識別子
    • title → ユーザーに表示されるラベル
  • contributes.menus.explorer/context
    エクスプローラーでファイルやフォルダを右クリックしたときのコンテキストメニューにコマンドを追加するための設定です。
    • commandcontributes.commandsに登録されているコマンドのうちどれを呼び出すか
    • when → メニューを表示する条件
  • contributes.configuration
    拡張機能がエディタのsettings.jsonや設定画面から追加できるオプションを定義します。
    • title → 設定画面のセクション見出しに表示される名前
    • properties → 実際の設定項目
  • scripts
    vscode:prepublish はパッケージング前に自動実行されるスクリプトを指定します。

3. TypeScript設定 (tsconfig.json)

{
  "compilerOptions": {
    "module": "commonjs",
    "target": "ES2020",
    "outDir": "out",
    "lib": ["ES2020"],
    "sourceMap": true,
    "rootDir": "src",
    "strict": true
  },
  "exclude": ["node_modules", ".vscode-test"]
}

4. コマンドの登録

拡張機能のエントリーポイントでコマンドを登録します:

// src/extension.ts
export function activate(context: vscode.ExtensionContext) {
    const convertToCursorRulesCommand = vscode.commands.registerCommand(
        'ai-rules-converter.convertToCursorRules',
        async (uri: vscode.Uri) => {
            await handleConvertToCursorRules(uri);
        }
    );
    
    const convertToVSCodeInstructionsCommand = vscode.commands.registerCommand(
        'ai-rules-converter.convertToVSCodeInstructions',
        async (uri: vscode.Uri) => {
            await handleConvertToVSCodeInstructions(uri);
        }
    );
    
    context.subscriptions.push(convertToCursorRulesCommand);
    context.subscriptions.push(convertToVSCodeInstructionsCommand);
}

コマンド名はpackage.jsoncontributes.commandsに登録したものと一致させるようにします。

YAMLフロントマターの処理

gray-matterによるパース

ルールファイルのフロントマターをパースするためにgray-matterライブラリを使用します:

// src/converter.ts
import matter from 'gray-matter';

interface CursorRuleFrontMatter {
    description?: string;
    globs?: string;
    alwaysApply: boolean;
}

const parsed = matter(preprocessedContent);
const frontMatter = parsed.data as CursorRuleFrontMatter;
const body = parsed.content;

AI変換処理の実装

OpenAI APIの活用

ルール本文の変換にはOpenAI APIを使用し、内容の意味を保持しながら適切な形式に調整します:

// src/extension.ts
async function callOpenAI(
    apiKey: string,
    systemPrompt: string,
    userPrompt: string
): Promise<string> {
    const openai = new OpenAI({ apiKey });
    
    const completion = await openai.chat.completions.create({
        model: 'gpt-4o-mini',
        messages: [
            { role: 'system', content: systemPrompt },
            { role: 'user', content: userPrompt }
        ],
        temperature: 0.3,
        max_tokens: 2000
    });
    
    return completion.choices[0]?.message?.content || '';
}

変換ロジックの実装

各ルール形式の特性を理解し、適切な変換を行います:

// src/converter.ts
export async function convertToCursorRules(content: string, apiKey: string): Promise<string> {
    const parsed = matter(content);
    const frontMatter = parsed.data as VSCodeInstructionFrontMatter;
    const body = parsed.content;

    const systemPrompt = `あなたはVSCode Custom InstructionsをCursor Rulesに変換する専門家です。

    ${CURSOR_RULES_SPEC}

    ${VSCODE_INSTRUCTIONS_SPEC}

    ${COMMON_CONVERSION_PRINCIPLES}
    5. VSCodeのapplyToパターンは、globsに変換
    6. applyTo: '**'となっている時は、Alwaysタイプに変換
    7. フロントマターも含めて、完全なCursor Rulesファイルを生成
    8. フロントマターと本文の間は1行空ける`;

    const userPrompt = `以下のVSCode Custom Instructionsを完全なCursor Rulesファイル(フロントマター付き)に変換してください。
    内容に基づいて最適なタイプ(Always, Auto Attached, Agent Requested, Manual)を選択し、適切なフロントマターを生成してください。

    入力のフロントマター:
    ${JSON.stringify(frontMatter, null, 2)}

    入力の本文:
    ${body}

    出力フォーマット:
    ---
    [必要に応じて globs や description]
    alwaysApply: [true | false]
    ---

    [入力の本文]`;

    const converted = await callOpenAI(apiKey, systemPrompt, userPrompt);

    return converted;
}

ファイル操作の実装

適切なディレクトリ構造の作成

変換後のファイルを適切な場所に保存します:

// src/extension.ts
async function handleConvertToCursorRules(uri: vscode.Uri) {
    const content = await fs.readFile(uri.fsPath, 'utf-8');
    const converted = await convertToCursorRules(content, apiKey);
    
    const workspaceFolder = vscode.workspace.getWorkspaceFolder(uri);
    const cursorRulesDir = path.join(workspaceFolder.uri.fsPath, '.cursor', 'rules');
    await fs.mkdir(cursorRulesDir, { recursive: true });
    
    const originalFileName = path.basename(uri.fsPath, '.instructions.md');
    const newFileName = `${originalFileName}.mdc`;
    const newFilePath = path.join(cursorRulesDir, newFileName);
    
    await fs.writeFile(newFilePath, converted);
    
    // 変換結果を表示
    const doc = await vscode.workspace.openTextDocument(newFilePath);
    await vscode.window.showTextDocument(doc);
}

API キーの管理

API キーは エディタ の設定として管理し、初回使用時に入力を求めます:

// src/extension.ts
async function getApiKey(): Promise<string | undefined> {
    let apiKey = vscode.workspace.getConfiguration('ai-rules-converter')
        .get<string>('openaiApiKey');
    
    if (!apiKey) {
        apiKey = await vscode.window.showInputBox({
            prompt: 'OpenAI API Keyを入力してください',
            password: true,  // パスワード入力として扱う
            placeHolder: 'sk-...'
        });
        
        if (apiKey) {
            // グローバル設定として保存
            await vscode.workspace.getConfiguration('ai-rules-converter')
                .update('openaiApiKey', apiKey, true);
        }
    }
    
    return apiKey;
}

拡張機能のデバッグ

launch.jsonの設定

launch.jsonはVSCodeの.vscode/launch.jsonに置くデバッグの設定ファイルです。
「実行とデバッグ」実行時にどんなプロセスを起動するかを定義します。

// .vscode/launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Run Extension",
            "type": "extensionHost",
            "request": "launch",
            "args": [
                "--extensionDevelopmentPath=${workspaceFolder}",
                "${workspaceFolder}/samples"
            ],
            "outFiles": [
                "${workspaceFolder}/out/**/*.js"
            ]
        }

    ]
}
  • configurations.name
    デバッグ構成の表示名を指定します。
  • configurations.type
    拡張機能の場合extensionHostを指定します。
  • configurations.request
    launch(起動する)か attach(既存プロセスにアタッチ)を指定します。
  • configurations.args
    VS Code に渡す引数を指定します。
    --extensionDevelopmentPathにはプロジェクトのルートを指定します。
    第二引数にはデバッグ時に開くディレクトリを指定します。
  • configurations.outFiles
    ビルド成果物(JS ファイル)の場所を指定します。

デバッグ

TypeScriptをコンパイルします。

npm run compile

エディタの「実行とデバッグ」ビューを表示し▶︎(F5)をクリックすると、
拡張機能開発ホストが開き、開発した拡張機能の動作確認をすることができます。

拡張機能のインストール

今回はVSIXファイルからインストールする形で進めます。
VSIXファイルを作成します。

npx vsce package

いくつか質問されるので全てyで答えます。

WARNING  A 'repository' field is missing from the 'package.json' manifest file.
Do you want to continue? [y/N] y
 WARNING  LICENSE.md, LICENSE.txt or LICENSE not found
Do you want to continue? [y/N] y
This extension consists of 1921 files, out of which 449 are JavaScript files. For performance reasons, you should bundle your extension: https://aka.ms/vscode-bundle-extension . You should also exclude unnecessary files by adding them to your .vscodeignore: https://aka.ms/vscode-vscodeignore
 DONE  Packaged: /Users/hogehoge/dev/ai-rules-converter/ai-rules-converter-0.0.1.vsix (1921 files, 2.49MB)

プロジェクトのルートディレクトリにVSIXファイルが作成されるので、
右クリックし、「拡張機能のVSIXのインストール」をクリックします。

拡張機能がインストールされました。

最後に

最後までお読みいただきありがとうございます。
異なるAIツール間でルールを共有できるようになることで、開発チーム全体の生産性向上に貢献できます。
ぜひ、チームの開発環境に導入してみてください。

リポジトリ

実装の詳細は以下のリポジトリで確認できます:
https://github.com/asa918yk/ai-rules-converter

dotD Tech Blog

Discussion