blog
/
Article
GitHub Copilot Agentのベストプラクティス:Claude活用&Prompt設計大全

GitHub Copilot Agentのベストプラクティス:Claude活用&Prompt設計大全

公開日 2025/3/26 / 更新日 2025/6/4

カテゴリー

Technology

タグ

AI
VS Code

GitHub Copilot Agent Modeとは

GitHubが2025年2月に提供を始めた機能です。

詳しい解説記事はこちらをご覧ください!

minabe.work

https://minabe.work/blog/1a51aee9-867c-80bf-85fa-d3c27e249dba

minabe.workからのリンク

はじめに

GitHub Copilot Agent、なんとなく使ってるけど「思ったほど使いこなせてない…」という方へ。

実はちょっとした設定や使い分けで、Copilot Agentは“ただの便利ツール”から“最強の開発パートナー”に進化します。

この記事では、以下のポイントを中心に「Copilot Agentをフル活用するベストプラクティス」をまとめました。

  • モデル選定と使い分け
  • チャット変数の活用
  • Promptファイルによる精度改善
  • 使用技術バージョンの明示と例示の重要性

Claudeモデルの選び方と使い分け

Copilot Agentでは複数のAIモデルが選べますが、筆者のおすすめは以下のとおり。(2025/3/26 現在)

Claude 3.7

  • 用途:設計レビュー、大規模なコード生成、ドキュメント生成
  • 特徴:思考力・文脈理解に優れ、複雑なタスクでも破綻しにくい
  • :プロジェクトの初期設計や大きめの機能実装の相談
  • 短所:レートリミットがすぐに来る

Claude 3.5

  • 用途:シンプルな修正、ユーティリティ関数生成、テスト作成
  • 特徴:レートリミットが比較的来にくく、軽量タスクに最適

以下のドキュメントで、各モデルについて説明されています。しかし、筆者としては上記2つ以外を使用するタイミングがありません。

docs.github.com

https://docs.github.com/en/copilot/using-github-copilot/ai-models/changing-the-ai-model-for-copilot-chat#ai-models-for-copilot-chat-1

docs.github.comからのリンク

チャット変数の活用

Github Copilot Agentには、チャット変数という機能があります。

チャット変数を上手に使うと、Copilotに自分が伝えたいことやプログラムの情報をスムーズに説明できます。

よく使うチャット変数

  • #terminalLastCommand : ターミナルの最後のコマンド実行の出力をプロンプトに差し込む
  • #terminalSelection : ターミナルで選択中のテキストをプロンプトに差し込む
  • #folder : フォルダを選択し、プロンプトに追加する
  • #file : ファイルを選択し、プロンプトに追加する
  • #codebase : Copilotが適切なファイルを自動的に見つけられるようにする

例えば、ビルド時やテスト実行時にエラーが出た際に、以下のように入力するとターミナルのエラー内容を確認してファイルを修正してくれます。

変数は他にもあるので、詳しくは公式ドキュメントを参照してください。

code.visualstudio.com

https://code.visualstudio.com/docs/copilot/copilot-vscode-features#_chat-variables

code.visualstudio.comからのリンク

プロンプトファイルによる精度改善

Github Copilot Agentは、.github/copilot-instructions.md に書かれた内容をデフォルトでプロンプトに組み込みます。

したがって、.github/copilot-instructions.md にはコーディングルールやドキュメントルールを書いておくとよいでしょう。

## TypeScript

TypeScriptでのコーディングにおける一般的なベストプラクティスをまとめます。

### 方針

- 最初に型と、それを処理する関数のインターフェースを考える
- コードのコメントとして、そのファイルがどういう仕様化を可能な限り明記する
- 実装が内部状態を持たないとき、 class による実装を避けて関数を優先する
- 副作用を抽象するために、アダプタパターンで外部依存を抽象し、テストではインメモリなアダプタで処理する

### 型の使用方針

1. 具体的な型を使用
   - any の使用を避ける
   - unknown を使用してから型を絞り込む
   - Utility Types を活用する

2. 型エイリアスの命名
   - 意味のある名前をつける
   - 型の意図を明確にする
   ```ts
   // Good
   type UserId = string;
   type UserData = {
     id: UserId;
     createdAt: Date;
   };

   // Bad
   type Data = any;
   ```

### エラー処理

1. Result型の使用
   ```ts
   import { err, ok, Result } from "npm:neverthrow";

   type ApiError =
     | { type: "network"; message: string }
     | { type: "notFound"; message: string }
     | { type: "unauthorized"; message: string };

   async function fetchUser(id: string): Promise<Result<User, ApiError>> {
     try {
       const response = await fetch(`/api/users/${id}`);
       if (!response.ok) {
         switch (response.status) {
           case 404:
             return err({ type: "notFound", message: "User not found" });
           case 401:
             return err({ type: "unauthorized", message: "Unauthorized" });
           default:
             return err({
               type: "network",
               message: `HTTP error: ${response.status}`,
             });
         }
       }
       return ok(await response.json());
     } catch (error) {
       return err({
         type: "network",
         message: error instanceof Error ? error.message : "Unknown error",
       });
     }
   }
   ```

2. エラー型の定義
   - 具体的なケースを列挙
   - エラーメッセージを含める
   - 型の網羅性チェックを活用

### 実装パターン

1. 関数ベース(状態を持たない場合)
   ```ts
   // インターフェース
   interface Logger {
     log(message: string): void;
   }

   // 実装
   function createLogger(): Logger {
     return {
       log(message: string): void {
         console.log(`[${new Date().toISOString()}] ${message}`);
       },
     };
   }
   ```

2. classベース(状態を持つ場合)
   ```ts
   interface Cache<T> {
     get(key: string): T | undefined;
     set(key: string, value: T): void;
   }

   class TimeBasedCache<T> implements Cache<T> {
     private items = new Map<string, { value: T; expireAt: number }>();

     constructor(private ttlMs: number) {}

     get(key: string): T | undefined {
       const item = this.items.get(key);
       if (!item || Date.now() > item.expireAt) {
         return undefined;
       }
       return item.value;
     }

     set(key: string, value: T): void {
       this.items.set(key, {
         value,
         expireAt: Date.now() + this.ttlMs,
       });
     }
   }
   ```

3. Adapterパターン(外部依存の抽象化)
   ```ts
   // 抽象化
   type Fetcher = <T>(path: string) => Promise<Result<T, ApiError>>;

   // 実装
   function createFetcher(headers: Record<string, string>): Fetcher {
     return async <T>(path: string) => {
       try {
         const response = await fetch(path, { headers });
         if (!response.ok) {
           return err({
             type: "network",
             message: `HTTP error: ${response.status}`,
           });
         }
         return ok(await response.json());
       } catch (error) {
         return err({
           type: "network",
           message: error instanceof Error ? error.message : "Unknown error",
         });
       }
     };
   }

   // 利用
   class ApiClient {
     constructor(
       private readonly getData: Fetcher,
       private readonly baseUrl: string,
     ) {}

     async getUser(id: string): Promise<Result<User, ApiError>> {
       return await this.getData(`${this.baseUrl}/users/${id}`);
     }
   }
   ```

### 実装の選択基準

1. 関数を選ぶ場合
   - 単純な操作のみ
   - 内部状態が不要
   - 依存が少ない
   - テストが容易

2. classを選ぶ場合
   - 内部状態の管理が必要
   - 設定やリソースの保持が必要
   - メソッド間で状態を共有
   - ライフサイクル管理が必要

3. Adapterを選ぶ場合
   - 外部依存の抽象化
   - テスト時のモック化が必要
   - 実装の詳細を隠蔽したい
   - 差し替え可能性を確保したい

### 一般的なルール

1. 依存性の注入
   - 外部依存はコンストラクタで注入
   - テスト時にモックに置き換え可能に
   - グローバルな状態を避ける

2. インターフェースの設計
   - 必要最小限のメソッドを定義
   - 実装の詳細を含めない
   - プラットフォーム固有の型を避ける

3. テスト容易性
   - モックの実装を簡潔に
   - エッジケースのテストを含める
   - テストヘルパーを適切に分離

4. コードの分割
   - 単一責任の原則に従う
   - 適切な粒度でモジュール化
   - 循環参照を避ける

このサンプルファイルは、mizchiさんのリポジトリの受け売りです笑

github.com

https://github.com/mizchi/ailab/blob/main/.cline/rules/typescript.md

github.comからのリンク

ファイルにするほどではないが簡単なプロンプトを挿入したい

例えば、『日本語で出力して』とか『カジュアルに返して』とか命令したいときは、.vscode/settings.json に以下の項目を追加しましょう。

"github.copilot.chat.codeGeneration.instructions": [
    {
      "text": "コメントを多用して。"
    },
    {
      "text": "カジュアルに返して。"
    }
  ]

最近では、Qiitaの記事がバズってましたね。

qiita.com

https://qiita.com/bonanza-olaf/items/5453fc0e3ad1c8f9f971

qiita.comからのリンク

複数のプロンプトファイルを使いたい

プロンプトファイルを使用していると、TypeScriptのコーディングルールだけでなく、テストやドキュメント、またはバックエンドのコーディングルールも用意したくなるかもしれません。

その場合は、.github/prompts.prompt.md ファイルを作成します。

こうすることで、複数のプロンプトファイルを見てくれるようになります。

.vscode/settings.json に追加します。

"github.copilot.chat.promptFiles": [
    {
      ".github/prompts": false
    },
    {
      "/path/to/prompts": true
    }
  ]

上記機能はExperimental のため、今後消えるかもしれない…(2025/3/25 現在)

Custom instructionsについてのドキュメントは以下です。

code.visualstudio.com

https://code.visualstudio.com/docs/copilot/copilot-customization

code.visualstudio.comからのリンク

余談

確かに上記の方法で複数のプロンプトを渡すことができるのですが、不要なプロンプトを渡さないように、わざわざ.vscode/settings.json を編集する必要性が出てきます。(気にならない人はいいですが…)

そこで私は、複数のプロンプトファイルを.github/copilot-instructions.md にまとめるスクリプトを作成し、デフォルトのプロンプトファイルの中身を変更する方式を採用しています。

これはこれで引数としてファイルパスを渡す手間が発生するので、まだ思案中です。

使用技術のバージョン明示と例示の重要性

これは生成AIの成り立ち的に仕方のないことですが、バージョンが異なるコードを出力してしまいエラーが表示するということが多々あります。

まずはプロンプト内にバージョンを明示的に書きましょう。それでも発生してしまうものについては、プロンプトファイル内で例示してあげましょう。

最後に

AI主体のコーディングはまだまだ日が浅く、誰しもが手探りの状態です。

この手法がめっちゃ良かったよ。という秘技を編み出した際には、何卒記事に起こして共有してくださると嬉しいです!(自分もここに追記していきますので、また見にきてください)

AIコーディングをみんなで開拓していきましょう!

最後までお読みいただきありがとうございました!

0

いいね!

この記事をシェアする