Open19

GitHub Copilot Workspaceを実際のOSS開発で試してみたメモ

Kesin11Kesin11

GitHub Copilot Workspaceがやっと使えるようになった。Copilot Workspaceがどこまでやれるのか色々なブログ記事を見てみたが、どれもちょっと試してみただけだったりゼロからアプリのコードを書かせるみたいな記事が多く、実際の開発現場とはだいぶ異なるという印象が多かった。

自分はコード規模はそれほど大きくないものの、数年間ずっと保守し続けているOSSをいくつか管理している。これらに対してCopilot Workspaceを使ってみた実際の記録をこのスクラップに残していく。
面白いものがあったら別途記事にするかもしれないし、しないかもしれない

Kesin11Kesin11

1. CIAnalyzerにBigQuery以外にGCSへのエクスポート機能を追加する

https://github.com/Kesin11/CIAnalyzer

自作のOSSの中では一番コード量が多い。
これはGitHub ActionsなどのAPIを叩いて取得したCIに関係するデータをBigQueryにエクスポートするのが主な機能で、exporterとしてBQ以外にも保存できるインターフェースは用意しているが実質的にBQしかサポートしていなかった。

ただ最近ではBQに直接データを置かずともGCSにJSONLとして保存しておけばBQから外部テーブルとして参照できるので、やりたいとは前から思っていた。(が特にそういう要望をもらったことがないので優先度は低かった)

第一弾としてこれを題材として試してみる

Kesin11Kesin11

まずは日本語でこういう機能を追加したいと書いてみる。
GCSにJSONLを配置する際にBQでパーティションを有効化できるようにファイルパスはユーザーがある程度任意で組み立てられるようにする必要があることは前から考えていた。
JSONLをGCSに置くという基本機能に加えて、この機能も伝えてみる。

ブレストの内容を見た感じ、全然ダメそう。今のCIAnalyzerのクラス設計の理解が足りていないように見える

The GcsStore class in src/store/gcs_store.ts handles reading and writing files to GCS but does not include logic for generating filenames with {workflow_id}-YYYYMMddhhmmss.json.

ややこしいのだが、gcs_store.tsはデータのエクスポートではなくて、最後にAPIからデータを取得したジョブのID番号をGCSに保存するクラスである。今回はExporterとしてGCSに保存するクラスが新規に必要になるはずなので間違っている

The BigqueryExporter class in src/exporter/bigquery_exporter.ts handles exporting reports to BigQuery but does not include logic for generating filenames with {workflow_id}-YYYYMMddhhmmss.json.

これもほぼ同じ話だが、BigqueryExporterはBQにエクスポートするためのクラスなのでGCSにエクスポートする機能はここに追加するのではなく、新しいクラスを作りたい

Kesin11Kesin11

これを見てCopilot Workspace使えないなーと決めるのは簡単だが、ブレストの段階で訂正できるので頑張ってみる。

まず下のチャット欄に入力したら追加で指示を出せるのだろうか?と思ったので試してみる

そうしたらブレストの内容が全部書き換わってしまった?

と思ったら、これは新しいブレストを作成したということらしく、最初のブレスト(How do I solve this task?)というログはちゃんと残っていた。

ということは、issueを解決するためのブレスト -> プラン -> コード生成は一発の一筆書きではなく、自分でサブタスクに分解して個々にブレストをから始められるということだろうか?

Kesin11Kesin11

改めて先ほど新しく作成したブレストの内容を見てみる

GCSにJSONLをエクスポートする機能はsrc/exporter/gcs_exporter.tsという新しいファイルを作成し、Exporterインターフェースを継承したGcsExporterクラスを作成してそこに実装してください

これに対して、

To implement the functionality to export JSONL to GCS, you need to create a new file src/exporter/gcs_exporter.ts and implement the GcsExporter class that extends the Exporter interface. Here are the steps to achieve this:

  • Create a new file src/exporter/gcs_exporter.ts.
  • Implement the GcsExporter class in this file.
  • Ensure that the GcsExporter class extends the Exporter interface.

このあたりまでは日本語を英訳したぐらいの情報しかない。

You can refer to the existing BigqueryExporter class in
src/exporter/bigquery_exporter.ts
and LocalExporter class in
src/exporter/local_exporter.ts
for guidance on how to structure the new GcsExporter class.

ここからは特に自分は指示していないのでCopilotが自力で導いている。特にBigqueryExporterに加えてLocalExporterを参照することは自分は一言も書いていなかった。けどこの2つ同じExporterクラスを継承しているため、参照することは正しい。

Additionally, you may need to update the configuration schema in
src/config/schema.ts
to include the new GCS exporter configuration options.

ここは完全に指示していなかった。たしかにGCSのバケットや配置するパスなどを指定するためにconfig.ymlの設定を拡張する必要があり、config/schema.tsは設定ファイルの型定義をzodで行っているので、ここを拡張することも正しい。

Kesin11Kesin11

もともとセットされていた How do I solve this task? というブレストを削除して、代わりに今作成したブレストを Add to task ボタンでセット。
これでPlanを作成するとこうなった

ちゃんとさっきのブレストの内容を反映したプランになっていて、最初に考えていたYYYYMMddhhのようなプレースホルダーの話は消えている。
その機能は後で実装しよう。

Kesin11Kesin11

このプランでコード生成させてみる。

おいおい、プランには特に書かれていなかったYYYYMMDDhhmmssのプレースホルダーも実装してくれたらしい。(余計なお世話・・・)

このコードがちゃんと動くかどうかは精査する必要があるが、その前に新しく追加する @google-cloud/storage はpackage.jsonに追加しないと絶対に動かないと分かっているのでそれは追加してもらう。

下のチャット欄にこんな感じで @google-cloud/storage を追加してもらうように指示を出してみた

Kesin11Kesin11

するとプランに Commands が追加されて、 npm install @google-cloud/storage というタスクが追加された。

いかにも実行できそうボタンが右側にあるので実行してみるとCodespaceの立ち上げが始まった。コマンドを実行するためにはさすがにLinux環境が必要なのでCodespacesで用意するのか。すごい。

Codespaceが立ち上がって npm install が実行されるとpackage.jsonとpackge-lock.jsonのdiffも追加された。

diffを見たら新規追加ではなくてバージョンアップになっていた。そういえばCIAnalyzerはもともと @google-cloud/storage を使っていたのでインストールする必要はなかったのか。
codespacesのターミナルが開いているのでここで git restore で差分を消しておく。

Kesin11Kesin11

ここまででパッと見た感じでは、ci_analyzer.yamlにエクスポート先のGCSのバケットのパスを指定する設定を追加して動かせば動きそうに見えなくもない。
けど実際に動かしてみないと本当に動くかどうか作者の自分でもよくわからない。このブラウザのCopilot Workspaceの画面では1ファイルが表示されている領域が狭いのと、diffがあるファイルしか表示されていないので既存コードとの比較がしにくい。

Copilot Workspaceからpull-requestも作れるみたいだが、いったん別ブランチにコミットして保存しておく。

"Push to new branch"に切り替えてボタンをクリックしたらコミットメッセージの画面が出てきた。

適当にコミットメッセージを書いてpush。続きは自分のVSCodeでやろう

Kesin11Kesin11

実はVSCodeにもCopilot WorkspaceのExtensionがあるらしい(11/21時点ではpreview)ので入れてみた。
https://marketplace.visualstudio.com/items?itemName=GitHub.copilot-workspace

さっきまでブラウザで行っていたCopilot Workspaceのセッションを開いてみるとこんな感じ。

ブレストの細かい指示文章が表示されていないが、Planの方は反映されている。ということは、ここからPlanに指示を追加してコード生成をやりなおさせたりもできるのかな?

Kesin11Kesin11

改めてコードを読んでみた感じ、気になる点がいくつかあった

  • ci_analyzer.yamlに追加された pathTemplate: ci_analyzer/{type}/{YYYY}/{MM}/{DD}/{hh}/{mm}/{ss}.json このGCSのパスの先頭に gs:// を付けたい
    • GCSのSDKを使う際にgs://は不要なので実際はただ余計なのだが、慣習としてGCSのパスにはgs://を付けたい
  • 同じくこのパスのテンプレートの{type}は何のことか分かりにくいので{reportType}にしたい

もはや自分で書き直せというぐらい自明なのだが、試しにVSCodeのCopilot WorkspaceでRevise Planに以下の指示をだしてみた。

fix `pathTemplate: ci_analyzer/{type}/{YYYY}/{MM}/{DD}/{hh}/{mm}/{ss}.json` to `pathTemplate: gs://ci_analyzer/{reportType}/{YYYY}/{MM}/{DD}/{hh}/{mm}/{ss}.json`

VSCodeの中でPlanに追加してくれはしたが、指示とは全く無関係の追加が行われた。

たしかに?exporter.tsに今回追加したGCS用のクラスの分岐を追加しないと動かなかったのでその修正は必要ではあるけど、今回と指示とは全然違うじゃん・・・
しかもlocal_exporter.tsの方にいたっては全く不要の修正である。

これぐらい修正方法と修正箇所が自明であれば自分の手作業 + 従来のGitHub Copilotで直しちゃった方が早そう。

Kesin11Kesin11

もはやgs://と{reportType}は自分で書き直したが、最終的にGCSのSDKに渡すパスとしてはgs://を削除する必要があるのと、パスのテンプレートの{YYYY}や{MM}といった時刻はともかく{reportType}は必ず与えられている必要があることに気が付いた。

これも自分でコードを書いてもいいのだが、せっかくなのでVSCode内のCopilot Workspaceに今度は手動でPlanの箇条書きに追加してみる

この状態でPlanから再実装をさせてみたら{reportType}が含まれているかどうかのチェックは出してくれた。


    if (!this.pathTemplate.includes("{reportType}")) {
      throw new Error(
        "The 'pathTemplate' parameter must include '{reportType}' variable.",
      );
    }

が、gs://を消すという実装は実装してくれなかった。

Kesin11Kesin11

Copilot Workspaceの共有URL https://copilot-workspace.githubnext.com/Kesin11/CIAnalyzer?shareId=1587f71b-1588-4cb7-8f74-53686d500800

ここまでの感想としては、Copilot Workspaceに任せる価値がある仕事はコード全体にまたがって大枠の修正や新規ファイルの追加が必要な初期実装かなと思った。

1ファイルの差分行を細かく見ていくとCopilot Workspaceが出してくれたコードには観点が抜けていたり、細かい部分で気に入らない実装があったりする。けどそういう部分に関しては手作業や、従来のCopilotのタブ補完とかCopilot Chatで直していく方が早い。

抽象的な概念で説明すると、今まで既存のコードベースに機能を追加する場合に自分はこのように作業していた

  1. 出力する成果物のフォーマットや、設定ファイルに追加するスキーマを考える
  2. コード全体から追加機能に関連しそうなファイルを調べ、きれいにハマりそうなクラス・インターフェース設計を考える
  3. 新規クラスや関数が必要な場合は、似ている既存のコードをコピペしてきてから修正する
  4. 実際に動かしてみて、細かいところを修正する
  5. 思い付くコーナーケースについてユニットテストを書いて、ちゃんとカバーできているかを確認する

Copilot Workspaceを使うと、1-3までの工程の大枠はやってくれそうな雰囲気を感じる。5も多分Planに追加すればテストコードを作ってくれると思うが、自分の場合はテストコードを書く目的がコーナーケースのカバーであることが多いので、そのような目的だとCopilot Workspaceの段階よりは後にならざるを得ないので従来のCopilot Chatとかにお願いした方が効率がよさそうな気がした。

Kesin11Kesin11

2. ts-junit2jsonのテストをjestからnode --test --experimental-strip-types に置き換える

https://github.com/Kesin11/ts-junit2json はTypeScriptとスナップショットテスト、ドッグフーディングのためにJUnit形式でテスト結果を出力したいという要件があったので今までJestを使っていた。

Node.js v22から.tsのテストも直接実行できるようになったらしい。
https://shisama.hatenablog.com/entry/2024/05/13/083000
https://www.mizdra.net/entry/2024/11/22/114114

strip typeのオプションができてtsもそのまま実行できるようになったのが大きい。
js-junit2jsonの要件としてはjunitレポーターとスナップショットが必須だが、どちらもサポートされているらしい。
https://nodejs.org/docs/latest-v22.x/api/test.html

要件的にはすべて満たせているので置き換え可能に思える

Kesin11Kesin11

という文章をそのままCopilot Workspaceに与えてみた。

ほう、ブレストの段階の回答としては何かやってくれそうな雰囲気を感じる。

ただ--experimental-strip-typesはNode.js v22のかなり新しい方法なので、ちゃんとやってくれるのかどうかが心配ではある。今 node --test とだけ書かれている場所を node --test --experimental-strip-types に明示的に書き換えてみる

Kesin11Kesin11

このブレストでPlanを作成してもらい、コードも実装してもらう。
package.jsonからjest関連のパッケージも消してくれるし、expect().toEqual() -> assert.deepStrictEqual() に置き換えもしてくれた。
ただ、snapshotテストだけは全然ダメそうだった

Kesin11Kesin11

node:testのsnapshot testの使い方はこんな感じらしい。
https://nodejs.org/api/test.html#snapshot-testing

Copilot Workspaceの下のチャットでこれを雑に教えてみる。

__tests__/snapshot.test.ts using assert.snapshot()
see: https://nodejs.org/api/test.html#snapshot-testing

ドキュメントの中身まで読みにいってくれたのかは分からんけど、パッと見た感じでは合ってそう?

と思ったがsnapshot()のリファレンスを確認したところ、第2引数はoptionsでObjectを受け取るものなので、第二引数がstringのCopilotが生成したコードは間違っていた。
https://nodejs.org/docs/latest-v22.x/api/test.html#contextassertsnapshotvalue-options

Kesin11Kesin11

ここでCopilot Workspaceの画面の右上にSettingsの項目があることにやっと気がついた。

Automatically include external URLs in context

もしかしてこの項目をONにしてAPIリファレンスのURLを渡したらちゃんと解釈できる?
試してみたら第2引数をObjectにしてくれたものの、リファレンスには存在しない description にセットしている。実行時には無視されるだろうから動きはするだろうけど・・・これはハルシネーション。

Copilot Workspaceが得意な段階はこのあたりまでかな。ここから先の細かい修正はVSCode上で普通にCopilotを使ったほうが早そう。

Kesin11Kesin11

今回のまとめ
Copilot Workspaceの共有URL: https://copilot-workspace.githubnext.com/Kesin11/ts-junit2json?shareId=0b7fb86a-5f08-4b38-a628-51b44ad6a164

Copilot Workspaceから作成したpull-request: https://github.com/Kesin11/ts-junit2json/pull/347

結局、Copilot Workspaceが生成してくれたコードから割と多くの部分を手動で直した。ついでにリファクタリングも結構してしまったので、コミット単位でdiffを見てもらえると。