Issue, Pull-request, GitHub Copilotによる「普通」の一人チーム開発

こんにちは。AIやっていきチームの加瀬(@Kesin11)です。 先日開催されたVSCode Dev Days Tokyoにて「Issue, Pull-request, GitHub Copilotによる『普通』の一人チーム開発」というタイトルで発表させて頂きました。

vscode.connpass.com

この発表ではGitHub Copilotと共同で機能設計の壁打ち、タスク分解、実装、コードレビュー、という開発プロセスを使い慣れたGitHubで行う方法を紹介しました。 現地の発表ではスライドをほとんど用意せず、最初から最後まで実際のデモを行いながら説明するという一発勝負スタイルでしたので、発表スライドの代わりにデモの台本をこちらで紹介することにしました。

当日のデモでは時間の都合上、かなりの駆け足でしたし、実は泣く泣く省略した内容もたくさんありました。そのため、この台本は当日に参加された方でも改めて楽しんで頂ける内容になっていると思います。また、最後にはおまけとして当日のデモでは全く触れられなかったもののGitHub Copilotとの共同開発で面白かった一幕も紹介しています。

それでは、以下が当日のデモの台本です。

—-

冒頭

当日のタイトルスライド
それではIssue, Pull-request, GitHub Copilotによる「普通」の一人チーム開発という内容で発表させて頂きます。

自己紹介のスライド

まず自己紹介ですが、サイボウズ株式会社の加瀬(Kesin11)と申します。 去年までは生産性向上チームで主にGitHub Actionsのセルフホストランナーのインフラ基盤を運用していたのですが、今年からAIやっていきチームに異動しました。

AIやっていきチームは、主に最近のAIをプロダクトに組み込む前のR&Dであったり、AI関連ツールの社内活用の推進などを行っているチームです。 自分の普段の業務は主に後者の活動で、CursorなどのAI関連ツールのアカウント管理や、Difyのようなセルフホスト可能なツールの運用などを行っています。また、ここ半年あたりからコーディング系のAIツールやMCPなどが非常に盛り上がっているため、それらの調査や社内向けガイドラインの作成などがメインの業務になっています。

サイボウズで利用可能な AI コーディングツールの紹介 - Cybozu Inside Out | サイボウズエンジニアのブログ

趣味はGitHubの最新アップデートが掲載されている github.blog/changelog を毎朝見ることと、毎月アップデートされるVSCodeのリリースノートを見ることです。 結構長く続いている趣味で、調べてみたらgithub.blog/changelogを追うのは2021年からずっと続けていたようです。

今日の話は、自分個人の一人開発の経験になるのですが、issueやpull-requestを使ってGitHub Copilotと一緒に「普通」のチーム開発をしているという内容を紹介します。何が「普通」なのかはこの後のデモでお見せします。

デモのお題スライド

デモを始める前に、この後のデモで何を作る予定なのかを軽く紹介します。 自分はChromeに組み込まれているリーディングリスト、いわゆる"Read it Later"的な機能をよく使っているのですが、これが溜まりすぎてしまったのでそれの棚卸しをやりたい。さらに、棚卸しする際ただ既読にするのではなく、その前に記事の要約をAIに作成してもらってから既読にするというChrome拡張を作ろうとしています。

それではここからは全て実際にデモをしながら説明していきます。 スライドは全く用意していません。今回のデモの台本は後日会社のブログで公開する予定なので、デモが盛大に失敗してしまったらそちらのブログをお待ち頂ければと思いますw

Copilotと設計の壁打ち

今回デモに使うリポジトリはこちらです。

github.com

AIがコーディングしやすいように、最低限のLint、テスト、ビルドが動くようにセットアップ済みですが、機能はまだ何も存在しない状態です。

まずは、右上のCopilotのマークからチャットを開きます。 リポジトリがアタッチされた状態でチャットが開きますので、これから作りたい機能についてCopilotと壁打ちをします。最初の壁打ちでは、詳細が曖昧な場合はCopilotから人間に質問を返してもらうように依頼することが重要です。

今回はChromeのリーディングリストを一定期間で自動的に既読 & 削除の棚卸しをしつつ、既読の前に記事の要約をAIで作成してからSlackにポストしてもらいたい、といった現時点で頭に浮かんでいる機能、使いたい外部サービスなどの要素を列挙しています。

これから作りたい機能についてCopilotと壁打ちするチャット1

注: アタッチされているリポジトリ名が実際にデモを行った Kesin11/vscode-dev-day-tokyo-demo とは異なりますが、これは台本の方を先に作成したためです。これ以降も台本作成時の kesin11-private/ReadingListAutoSummary というリポジトリがスクリーンショットに登場することがありますが、適宜読み替えてください。

すると伝えた要素をCopilotがまとめてくれつつ、詳細が甘い点を指摘してくれました。

  • リーディングリストを外部で管理するか
  • 「一定期間」をユーザーが変更可能なUIにするか?
  • Slackへの認証はどの方法で行うのか
  • LLMのモデル名をどのようにして取得するか
  • UIをどうするか

最初の依頼時点ではいずれも欠けていた観点でした。これらが未確定のままAIにコードを書かせたとしても、思ったものと違うものができあがってしまうのは当然ですね。 普段の仕事でも新しい機能を設計する場合は同僚とMTGを行って仕様を詰めると思いますので、それと同じだと思ってこの時点でちゃんと細かい仕様を詰めておきます。

これから作りたい機能についてCopilotと壁打ちするチャット2

すると、さらにCopilotから追加の仕様確認が来ました。

  • 本文抽出はどのように行うのか
  • 失敗時のエラー処理をどうするか
  • Slackへ投稿する際のフォーマットについて

またしても最初の依頼時には欠けていた観点ですね。 本文抽出に関しては自前でやると少し難しいことが予想されたのと、Firecrawlというサービスを使うのが簡単で性能も良いことを自分は知っているので一旦はこれを使うように依頼しました。FirecrawlのSDKのドキュメントのURLはググったらすぐに出てきたので、SDKの具体的なURLも渡しておきます。

これから作りたい機能についてCopilotと壁打ちするチャット3Copilotとまとめた仕様全体のまとめ

これで少なくとも実装開始前の段階で想定できる仕様に関しては詰めきれたと思います。 このリポジトリにはまだREADMEも一切無かったので、一応READMEに書き出してもらいます。

Copilotと作成した仕様全体のまとめ

Markdownで出力をお願いすると、CopilotのチャットUI上でプレビュー表示してくれました。 このREADME.mdはVSCodeにコピペして自分でmainブランチにコミットしました。

仕様全体をまとめたREADME

issue, sub-issueの作成

ついでにこの内容からissue作成してもらいます。これを後に作るsub-issueの親issueにしましょう。 github.comのCopilotはMCPを使わなくてもデフォルトでissueを作成する機能を持っているので、チャットで依頼すればIssueのドラフトも作ってくれます。 このあたりは過去の発表で詳しくデモをしたことがあります

Copilotがissueのドラフトを生成する

ここで作成したissueは先ほど詰めた仕様が全て記載されており、内容としては結構大きいです。この後にissue駆動でCopilotに実装してもらうため、実装のステップとしてissueを細かく分割してもらいます。 全体仕様を渡して一気にコードを書かせると早いですが、思ったものと違うときにレビューや書き直しのコストがかかるのは人間と一緒なので、個人的にはタスクは細かく分割したいです。

タスクをどのように「分解」するのかも実は簡単ではないことですが、今回は機能単位で分解して1つ1つが確実に動作するようタスクを分けてもらいました。

Copilotにissueを分解してもらう1

おそらく先ほどのREADME.mdを作る指示を引きずってmdで出してきたので、一旦それは不要であることを伝えました。 さらにユニットテストを一番最後にまとめて書く形にタスク分解してきたので、ユニットテストの実装は最後にまとめてではなく、タスクごとに細かく追加するように追加で伝えます。

Copilotにissueを分解してもらう2 Copilotにissueを分解してもらう3 Copilotにissueを分解してもらう4

1番目のタスクでオプション画面(Chrome拡張の設定画面)を実装する際、今後実装する予定の全機能の設定画面を一括で作ろうとしていたので、これもテスト同様に各機能のタスクの中で必要になったタイミングでオプション画面に追加するように指示します。 デバッグログも7番目のタスクで最後に一括で入れようとしていたことに気がついたので、同様に各タスク中で適宜追加するように指示しました。

こんな感じで、自分が思ってたタスクの分解方法とCopilotが提案してきたものは結構違っていました。人間と一緒ですり合わせが大事ですね。

タスクの分解ができたので、それぞれのタスクを先ほど作った親issueのsub-issueとして登録してもらうように依頼します。 実はsub-issueを作ってもらえるようになったのは8/27に新しく追加されたばかりの新機能です。

Create sub-issues with Copilot in public preview - GitHub Changelog

Copilotにsub-issueに分解してもらう1

右上の「Review and create」ボタンを押すと、複数のsub-issueを一括で作成するかの確認ダイアログが表示されるのでボタンをクリックして進みます。

Copilotにsub-issueに分解してもらう2

GitHub Projectで見ると、親issue#4とそれに紐づくsub-issueとして登録されていますね。

GitHub Projectにsub-issueが登録されている様子

注: 今回はProjectへの追加を手動で行ったのでsub-issueを1つProjectに登録するのが漏れていました。実際は5つのsub-issueが登録されています

これで実装前の設計とタスク分解フェーズが完了です。ここからsub-issueを1つずつ順に実装していくことで、最終的に設計した機能全体が実装されることになります。

最初のsub-issueはこんな感じです。 Chrome拡張のreadingList APIでエントリを取得すること、デバッグログを入れること、APIをモックしたユニットテストを作る。これだけのかなり細分化されたsub-issueになっていますね。

作成された1番目のsub-issue

実装

では早速VSCodeを開いて実装してもらいましょう。

  1. GitHubのMCPでissue読みこむ
  2. 作業プランニング
  3. 人間による承認
  4. ブランチ作成
  5. 実装
  6. コミット
  7. PR作成

という一連の工程を一括でやってもらうための自作の秘伝プロンプトでissueから一気にPRまで作ってもらいます。 中身はこんな感じです。

---
mode: 'agent'
description: 'issueを実装して自動でpull requestを作成するためのプロンプトです'
---
## Steps
Planning -> Actionの順に作業してください
**PlanningとActionsの間にユーザーの承認が必要です**

### Planning
1. 与えられたissueの本文とコメントを `github` から確認してください
  - `github` が使えない場合は `gh issue view ${issue_number}` を使用してください  
2. `fix_${issue_number}/` をprefixとしてissueの内容を元にブランチ名を考えてください
3. `codebases` toolが使える場合はそれを使用して修正すべきコードを検索してから実装計画をよく考えてください
4. 設計を示す際には追加、修正、削除するファイルを示してください
5. ブランチ名と実装計画を示し、ユーザーに承認を求めてください

### Action
6. ユーザーからの承認を得たら、以降の手順で実装を行ってください
  - 承認が得られなかった場合は、再度ブランチ名と実装計画を考え直してください
7. `git status` の結果がクリーンであれば、ブランチを作成して切り替えてください
  - クリーンではなかった場合は今の状態を報告し、ユーザーの指示を待ってください
8. 実装計画のTODOリスト作成して順番に実装を行ってください
  - Conventional Commitに従いながら細かくコミットしてください
  - コミット前には後述のcheck allが成功することを確認してください
  - チェックが成功したら変更をステージングしてください
    - なるべく `git add -u` を使用して変更されたファイルのみステージングしてください
    - 新規ファイルがある場合は `git add <file>` を使用してください
9. TODOを全て完了したら現在のブランチをpushし、`github` を使用してドラフトモードのpull requestを作成してください
  - `github` が使えない場合は `gh pr create --draft` を使用してください
  - pull requestのフォーマットは後述の `pull request format` を参照してください

## Check commands for after fix code
実装後にpull requestを作成する前には必ずcheck allを行ってください。
lintやformatの修正にはfix lintやfix formatを使用してください。

- check all: `pnpm check:ai`
- fix lint: `pnpm biome:fix`
- fix format: `pnpm biome:fix`

## pull request format
- pull requestのタイトルは実装内容を簡潔にまとめてください
- pull requestの本文の先頭には `fix: #${issue番号} AIによる自動PR` という文字を入れてください
- pull requestの本文には以下の内容を含めてください
  - 実装内容の説明
  - 実装にあたって参考にした情報や、特に注意した点
  - 実装にあたっての質問や懸念点
- pull requestのブランチは `main` を指定してください

これを.github/promptsに配置してあるのでスラッシュコマンドで呼び出せます。 今回は一番最初のissueということで、周りに参考にできるコードが何も無いのでsub-issueの狭いコンテキストだけではAIが実装方針の迷子になる可能性を心配し、全体設計を出力していたREADMEも与えてみました。

実装の秘伝のプロンプトにissueのURLを渡す

いきなりコードを実装されてからレビューすると人間側の負担が大きいので、まずはCopilotに作業プランを出してもらいそれをレビューします。 自分の頭の中で何となく考えていたプランニングと明らかに違和感を感じる箇所があれば、まずこの段階で指摘して修正してもらいます。

今回はChromeの型定義パッケージを追加、どのファイルにどういう名前の関数を追加しようとしているか、設定のデフォルト値、などのプランを出してくれました。

よく見るとChrome APIの型定義を自前で書いて追加しようとしていたので、@types/chromeをpackage.jsonに追加してもらうように指示を追加しました。 それと自分の経験上、Claude Sonnet 4であってもテストコードのmockの使い方が下手なことが多いので、vitestのモックについて事前に調べるように指示してみました。

Copilotが提示した実装計画1 Copilotが提示した実装計画2

実装開始にGOを出したつもりはないのですが(w)作られたTODOリストを見るとちゃんと指示通りに計画を修正してくれたみたいなので、このまま実装してもらいます。 Chromeの型定義はちゃんと@types/chromeのパッケージを追加してくれました。

Copilotによる実装1

次はvitestのモックをvitestのヘルプから探そうとしてくれました。manから探す、Linuxの基本ですね。素晴らしい。 だけど残念ながらvitest –helpにはモックの情報は書かれていないので、今回は自分の方でvitestのドキュメントのURLを教えてあげました。 CopilotはURLを与えるとVSCodeのfetchツールを使おうとしてくれるので、これを許可します。

Copilotによる実装2

あとは最後まで自走して実装してくれました。 自分の場合は、TypeScriptの型チェック -> Lint(Biome)->テスト->ビルド(今回はviteでChrome拡張のパッケージング)を一括で行うコマンドをcheck:aiとしてpackage.jsonに用意済みです。コミット前にこれを実行するように最初に渡したプロンプトで指示しているので、失敗したときのエラーログからひたすら修正を続けてくれます。 check:aiが通ると、これも最初のプロンプトに含まれる指示通り、git add -> commit -> push -> MCPでPR作成までやってくれました。

Copilotによる実装3 Copilotによる実装4

コードレビュー

作成されたPRをブラウザで開きます。今回はGitHub Actionsも用意済みなのでPRが作られたら自動でCIも動いてます。最低限、CIが通っていることを確認してからコードレビューに入ります。ここから既に人間相手のコードレビューと同じです。

Copilotが自動的に作成したpull-requestの本文

GitHubのコードレビューの画面を開くと自分の中のレビューモードのスイッチが入りますので、後は今まで経験してきた「普通」のチーム開発と全く一緒です。自分が何となく想像していたコードとCopilotが実装したコードの間で大きく異なる部分を見つけたらコメントを付けていきます。

今回はデバッグログを出しすぎていたのと、テストコードの中でconsoleを実質的に無効化するためだけにモックを使っていたのでそれを指摘しました。 vitest –silientでテストを実行すればconsoleのログ出力は無効化できるので、テストコード内でいちいち無効化する必要はないです。

Copilotのコードに対しても人間同様にレビューを行う

ちなみに裏でCopilot code reviewにもレビューを依頼していたので、ここで人間とCopilotのレビューが並びます。 Copilotのレビューコメントは「ReadingListの型定義を自前で作っているが、それはChromeの型定義にビルトインされているので自前で作るのは無駄である」という指摘でした。素晴らしい!自分は見落としていましたが完全に正しいと思います。 人間は自分ひとりしかいませんが、AIと二人で「普通」のコードレビューをしました。

GitHub Copilot code reviewと人間のレビューがpull-requestの画面に並ぶ

レビューを受けてコード修正

コードレビューが終わったら、Copilotにコメントを確認してもらい修正してもらいます。 これも自分は秘伝のプロンプトがあるのでそれを使います。

レビュー修正用の秘伝のプロンプトにpull-requestのURLを渡す

---
mode: 'agent'
description: 'pull_requestのレビューコメントを確認し、必要な修正を行うプロンプトです'
---
## Steps
以下の手順に従って、pull_requestのレビューコメントを確認し、必要な修正やコメント返信を行ってください。

1. 与えられたpull_requestのレビューコメントの取得と分類
  - レビューコメントと通常のコメントを `github` を使用して確認する。 `github` が利用できない場合は `gh` コマンドを代わりに使用する
  - resolved済みのレビューコメントは無視
  - 同じ行に対して複数のレビューコメントがある場合は、Kesin11からのコメントを優先
  - 必須修正(セキュリティ、バグ、機能要件)
  - 推奨修正(パフォーマンス、可読性、保守性)
2. レビューコメントの分析と修正計画の作成
  - 各レビューコメントに対して、修正が必要かどうかを判断する
    - 破壊的変更の可能性を評価
    - 修正が不要な場合は、その理由を簡潔に説明する
    - 修正が必要な場合は、具体的な修正内容を明確にする
  - 修正の順序と依存関係の整理
    - 修正が他の部分に与える影響を分析
    - 関連するファイルやモジュールの特定
    - テストが必要な修正の特定
  - すべてのレビューコメントに対して、修正計画をまとめる
3. 修正計画のTODOリストを作成する
  - TODOの最後には必ず check all を行うこと
4. TODOリストの全ての項目を完了したら、変更をステージングする
  - なるべく `git add -u` を使用して変更されたファイルのみステージングする
  - 新規ファイルがある場合は `git add <file>` を使用する
5. コミットを行う
  - コミットメッセージは実装内容を簡潔にまとめる
6. 現在のブランチをpushしてpull-requestを更新する

## Check commands for after fix code
実装後にpull requestを作成する前には必ずcheck allを行ってください。
lintやformatの修正にはfix lintやfix formatを使用してください。

- check all: `pnpm check:ai`
- fix lint: `pnpm biome:fix`
- fix format: `pnpm biome:fix`

GitHub MCPでレビューコメントを確認 -> コード修正 -> commit -> pushまで自動で行ってもらうというものです。 あとはresolve済みのコメントは無視するようにもプロンプトに書いているのですが、MCPだと残念ながらresolve済みかどうかの情報が取れないらしく、コメントは全部読みとってしまいます。 MCPではなくてghを使わせればresolve済みかどうかの情報も取得できたかもしれませんが、ちょっと忘れました。

このプロンプトも.github/promptsに配置済みなのでスラッシュコマンドで呼び出してpull-requestのURLだけ渡します。 すると修正計画を考えてくれて、自動で修正をしてくれます。

Copilotによるレビュー修正の実装の様子1 Copilotによるレビュー修正の実装の様子2

修正されたコードを再度レビューします。人によると思いますが、自分は追加でpushされたコミットの差分だけのdiffを見てレビューします。 レビューコメントに対して正しく修正されていることを確認したら、前に付けたコメントをresolveしていきます。自分のコメントに加えて、Copilot code reviewがレビューしてくれたコメントも同様に対応します。

修正されたコードを確認して対応済みレビューコメントをresolveにする

全てのコメントがresolveできたらLGTMということで、一人開発ですのでapproveの代わりにpull-requestをマージします。 この工程も「普通」のチーム開発と同じだと思います。

こうして1つのsub-issueの実装が完了したので、その次のsub-issueを同様の手順で実装していきます。 このようにして、自分は個人開発であっても今まで仕事でチーム開発してきたときと同様にGitHubを中心にして「普通」に開発をしています。 AIエージェントの管理ツールや、Spec駆動開発をするためのツールとか派手なものが流行っていますが、昔からGitHubで開発している人ほどGitHubがあれば十分なんじゃないかなと個人的には思ってます。

親issueに紐づくsub-issueがクローズされていく様子

デモは以上です。ご清聴ありがとうございました。

おまけ

ここからはイベント当日のデモでは時間の都合上全く触れられなかったものの、GitHub Copilotとの共同開発で面白かった一幕をいくつか紹介します。

sub-issue分割とGitHub Copilot coding agentの相性の良さ

今回デモの題材にしたChrome拡張は、その後全てのsub-issueを実装するまで開発を続けましたが、仕様の考慮漏れやリファクタリングなどが途中で必要になり、結局完成するまでにsub-issueの数は当初の5から2倍の10に増えました。 途中で追加したsub-issueは、VSCode上で「こういう問題があるから解決するための方法を計画してissueを作成して」などと依頼してMCPからsub-issueを作成してもらっています。

デモでは最初にgithub.com上でCopilotとチャットしてissueを作成しましたが、ある程度コードが増えてくるとVSCode内のCopilotの方がコード探索やターミナルでコマンドを実行して自律的に情報収集してくれるのでより精度の高い設計をしてくれるように思います。

完成までには結局sub-issueは10まで増えた様子

ちなみに右側のassigneeマークにCopilotのマークが付いているものは、GitHub Copilot coding agentに任せてみたissueです。さすがに一発で完璧な実装をしてくれることはなかったのですが、sub-issue自体に既にかなり細かい実装計画が書かれているため、かなり自分の想定に近い80点ぐらいの実装を毎回出してくれました。

Copilotで大きなissueをsub-issueに分割してもらうのは一瞬ですし、かつその中身も人間が書く場合よりしっかり書いてくれるので、この開発手法はcoding agentのような非同期型エージェントともかなり相性が良さそうだと思っています。

レビュー時にドキュメントのURLを渡した場合のCopilotの挙動

リーディングリストのエントリを既読とする前に記事の要約をAIで作成する機能を実装し終わった後のことです。記事本文を抽出するために使っていたFirecrawlですが、最初にCopilotが書いてくれたコードでも動いていたものの、実はAPIのバージョンが古いということが後で分かりました。 これをレビューで指摘して、自分が見つけた最新バージョンのAPIリファレンスのURLだけを渡しました。

Firecrawlの最新エンドポイントのリファランスのURLをレビューで教えた様子

そしてVSCodeで先ほどと同様に、レビューコメントをMCPで取得して修正させるプロンプトを流しただけですが、Copilotが自らコメントで伝えたFirecrawlのリファレンスのURLをfetchしてくれました。 あとは正しくv2のAPIを叩けるようにエンドポイントを書き換えたり型定義を書き換えたりと完璧に修正してくれました。 本当に人間同士のレビュー風景と何ら変わりませんね。

CopilotがリファレンスのURLを自動でfetchしてくれた様子

レビューコメントが対立した場合

次はCopilotのレビューコメントと、自分の意見が真っ向から対立したケースです。

Copilot code reviewがコード内コメントでの日本語の軽微な書き換えを指摘してくれましたが、自分としては必要を感じなかったので対応不要とコメントしました。

Copilotと自分のレビューが対立した様子

自分の秘伝のタレのプロンプトでは、同じ行に対して複数人がコメントした場合には Kesin11のコメントを最優先するように という指示を与えています。

同じ行に対して複数のレビューコメントがある場合は、Kesin11からのコメントを優先

Copilotはこれをちゃんと守ってくれて、Kesin11の「不要」というコメントを優先してCopilot code reviewの提案を棄却してくれています。 自分の場合は一人開発なのでこんなルールでも十分機能しているのですが、複数の人間が関わる実際のプロジェクトの場合はもう少し工夫が必要そうです。 例えば「Copilotの指摘は優先度を低くして」とか「複数人が異なる意見をコメントした場合は修正しないで保留にして」みたいなプロンプトが考えられるかもしれませんね。

CopilotがKesin11のコメントを優先してCopilotの提案を棄却した様子