Rails 8.1 で schema.rb が ABC 順になったので structure.sql に移行してみた

いつものように Dependabot の自動アップデートで Rails 8.1 に上げたところ、急に schema.rb に不可解な差分が出るようになって驚いた。

差分をよく見ると、テーブルのカラムが ABC 順に並び替えられていることに気づく。

Active Recordは、schema.rb内のテーブルカラムをデフォルトでアルファベット順にソートするようになりました。これにより、マシン間でスキーマダンプが一貫するようになり、マイグレーションの順序によって左右されなくなり、結果としてノイズの多い差分が削減されます。structure.sqlは、カラム順序を厳密に維持するために引き続き利用できます。スキーマ変更のアルファベット順化の詳細については、#53281を参照してください。

Railsガイドで、8.1 で追加された仕様変更として解説されている。

さらに詳しい経緯は、該当の PR を読むと把握できる。

当初は「オプションで選べるようにする」機能として PR が立てられた。@byroot によって、オプションにすべきかどうかに疑問が投げかけられる。@matthewd は schema.rb でカラムの並び順を参照するユースケースについて言及するなどして、慎重な見解を述べている。

そんな中、DHH の鶴の一声で「オプションにはせず、常に ABC 順にする」という方針が決定される。

This should not be an option. This should just be default behavior. Schema dumping should be deterministic regardless of the platform.

これはオプションにするべきではない。これは単にデフォルトの挙動であるべきだ。スキーマのダンプは、プラットフォームによらず決定的であるべきだ。

Rails における DHH の、神のような決定権を感じさせる一幕だ。3つの文のすべてに should が入っていて、「そうあるべきだ」という強い意志がにじみ出ている。

方針決定後の @matthewd によるフォローのコメントでは、カラムの並び順を管理したい場合は structure.sql を使うべきであることが示唆されている。

変更前のschema.rbの課題

変更前の schema.rb の仕様では、複数の開発者が同時に DB マイグレーションを実装するような場合に問題があった。各開発者がローカル環境でどのような順番でマイグレーションを実行したかによって、カラムの並び順が変わってしまう可能性があったのだ。

そのため「 rails db:migrate:reset コマンドを実行した場合の並び順を正とする」といった運用ルールを決めておかないと、カラムの並び順が一意に定まるようにファイルを更新することができなかった。

この問題に対して、カラムを ABC 順に並べることで、常に一意の並び順に決定できるようにしたのが、この PR による仕様変更だ。

とはいえ、カラムの並び順は重要では?

その一方で、MySQL にはカラム追加の際に任意の並び順を指定できる機能があるため、可読性の観点などからカラムの並び順を意識的に管理しているケースもあるだろう。私のチームも、カラムの並び順を意識する運用にしていた。

カラムの並び順を意識する開発方針の場合、コードレビューでは「カラムの並び順が妥当かどうか」も確認対象になる。このとき、カラムの並び順の差分を確認できるファイルが存在しないと、レビュワーは手元で DBMS を動かしてスキーマを確認しなければならない。これは手間がかかるし、チームとして実装・コードレビューの運用方針を徹底することも難しくなりそうだ。

そのため、カラムの並び順を意識する開発方針を取るのであれば、「実際のカラムの並び順を参照できるファイルがあること」は、運用上ほぼ必須要件になりそうだ。

こうした背景から、私のチームでは schema.rb から structure.sqlスキーマダンプのフォーマットを移行することにした。

structure.sqlとは

config.active_record.schema_format = :sql

config.active_record.schema_format:sql に設定すると、デフォルトの schema.rb ではなく、structure.sql によってスキーマダンプが管理されるようになる。

structure.sql では、データベース固有のツールを用いてデータベースの構造がダンプされる。たとえば MySQL の場合は、mysqldump ユーティリティ が用いられる。

structure.sql の中身は、以下の部分例のように「そのまま SQL」となる。

/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `announcements`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `announcements` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `site_id` bigint NOT NULL,
  `title` varchar(255) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT 'タイトル',
  `content` text COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '内容',
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `index_announcements_on_site_id` (`site_id`),
  CONSTRAINT `fk_rails_81bca04b30` FOREIGN KEY (`site_id`) REFERENCES `sites` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='お知らせ';

structure.sql に含まれる SQL を実行することで、定義されたテーブル構造を再現できる仕組みになっている。

structure.sql では環境ごとの差異をなくすために工夫が必要

実際に試してみると、 structure.sql は、開発環境ごとの差異をなくして、純粋にスキーマの差分だけを管理できるようにするために、工夫しなければいけないことがいくつかあると気づいた。

mysqldump を実行するクライアントの違い

FROM ruby:3.4.7

RUN apt-get update \
  && apt-get install -y --no-install-recommends default-mysql-client \
  && apt-get clean \
  && rm -rf /var/lib/apt/lists/*

例えば、上のように ruby の Docker イメージをベースに環境構築した場合、OS は Debian になるため、MySQL 互換の実装として MariaDB が標準になっている。Debian の APT の初期状態では、Oracle MySQLmysql-client はインストールできず、default-mysql-client の実態は mariadb-client となっている。

この環境では、mysqldump コマンドは mariadb-dump へのシンボリックリンクとして提供される。

mysqldump コマンドの実態が本物の mysqldump なのか、mariadb-dump へのシンボリックリンクなのかによって、利用できるオプションやスキーマダンプの生成結果に差異が生じることに注意が必要だ。

AUTO_INCREMENT= の値が開発環境ごとにばらばらになる

CREATE TABLE `announcements` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `site_id` bigint NOT NULL,
  `title` varchar(255) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT 'タイトル',
  `content` text COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '内容',
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `index_announcements_on_site_id` (`site_id`),
  CONSTRAINT `fk_rails_81bca04b30` FOREIGN KEY (`site_id`) REFERENCES `sites` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='お知らせ';

mysqldump の出力結果では、上の AUTO_INCREMENT=6 のように、開発環境のデータがどう入っているかによって「次に INSERT されるプライマリキーの採番値」に差異が生じてしまう。

スキーマロードするかマイグレーションするかで CHARACTER SET の有無が異なる

`title` varchar(255) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT 'タイトル',
`title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT 'タイトル',

上のように、varchar カラムに対する CHARACTER SET utf8mb4 の有無が、Railsスキーマロードするか、マイグレーションするかによって異なる問題に遭遇した。

これは mysqldump の内部実装による複雑な事象だが、上の記事で詳しく解説してくださっている方がいたおかげで何とか理解できた。

  • 照合順序(Collation)をデフォルトから変更した場合に、この差異が生じる
  • 差分の有無による実際の DB スキーマの仕様の違いは存在しない
  • structure.sqlSQL を実行した場合と、Rails の通常のマイグレーションで実行される SQL が異なる
  • Rails では DB マイグレーション系のコマンドの実行方法によって、 rails db:schema:load が実行され、 structure.sqlSQL が実行される場合がある
  • この違いによって mysqldump の出力結果に差異が生じる

環境差異の解消方法

structure.sql を生成する際の mysqldump の挙動をカスタマイズする方法は、どのレイヤーで上書きするかによっていくつかの選択肢がありそうだ。私はその中から、config/initializers/mysql_database_tasks.rb に以下のようなファイルを配置して設定を上書きする方法を選んだ。

# development と test 環境でのみ設定する
if Rails.env.local?
  # mysqldump で structure.sql を生成する方法を設定する
  ActiveSupport.on_load(:active_record) do
    # mysqldump 実行時に TLS 接続をスキップする
    # 開発環境ではシンボリックリンクで mariadb-dump が実行されるため、 --ssl-mode オプションが存在しない
    # GitHub Actions の環境では mysqldump が実行される
    mysqldump_flags = ENV['GITHUB_ACTIONS'] == 'true' ? %w[--ssl-mode=DISABLED] : %w[--skip-ssl]

    ActiveRecord::Tasks::DatabaseTasks.structure_dump_flags = {
      mysql2: mysqldump_flags
    }

    ActiveRecord::Tasks::DatabaseTasks.structure_load_flags = {
      mysql2: mysqldump_flags
    }

    ActiveRecord::Tasks::MySQLDatabaseTasks.prepend(Module.new do
      def structure_dump(filename, extra_flags)
        super
        content = File.read(filename)
        # AUTO_INCREMENT= の値は個別の開発者の環境のデータの入り方によって異なるため、 structure.sql から削除する
        content.gsub!(/\sAUTO_INCREMENT=\d+/, '')

        # DDL で varchar カラムに COLLATE を指定するかによって structure.sql に `CHARACTER SET utf8mb4` の有無の差異が生じる
        # Rails のマイグレーションでは COLLATE指定なし、 structure.sql をロードした場合は COLLATE 指定ありの DDL になる
        # `CHARACTER SET utf8mb4` の有無による仕様の違いは存在しないため、開発環境ごとの差異をなくす目的で、structure.sql から削除する
        content.gsub!(' CHARACTER SET utf8mb4', '')
        File.write(filename, content)
      end
    end)
  end
end
# mysqldump 実行時に TLS 接続をスキップする
# 開発環境ではシンボリックリンクで mariadb-dump が実行されるため、 --ssl-mode オプションが存在しない
# GitHub Actions の環境では mysqldump が実行される
mysqldump_flags = ENV['GITHUB_ACTIONS'] == 'true' ? %w[--ssl-mode=DISABLED] : %w[--skip-ssl]

この部分では、開発環境で実行される mysqldump の実態が mariadb-dump となっている一方で、GitHub Actions のUbuntu環境では mysqldump となっている問題に対処している。

content = File.read(filename)
# AUTO_INCREMENT= の値は個別の開発者の環境のデータの入り方によって異なるため、 structure.sql から削除する
content.gsub!(/\sAUTO_INCREMENT=\d+/, '')

# DDL で varchar カラムに COLLATE を指定するかによって structure.sql に `CHARACTER SET utf8mb4` の有無の差異が生じる
# Rails のマイグレーションでは COLLATE指定なし、 structure.sql をロードした場合は COLLATE 指定ありの DDL になる
# `CHARACTER SET utf8mb4` の有無による仕様の違いは存在しないため、開発環境ごとの差異をなくす目的で、structure.sql から削除する
content.gsub!(' CHARACTER SET utf8mb4', '')
File.write(filename, content)

この部分では、正規表現の置換を用いて、開発環境による差異が出る部分を削除している。

文字列の置換ではなく、 mysqldump のオプション指定によって解消できないかも検討したが、私の試した範囲では難しかった。インターネットで事例を検索しても、愚直に文字列を置換しているソリューションしか見つけられなかった。

この設定をすることによって、環境ごとの差異がなく、スキーマの差分のみを綺麗に確認できる structure.sql の運用が可能になった。

structure.sql を使ってみた所感

その後、structure.sql を運用してみて、実際の SQL が表示されることで MySQL 上の設定に自覚的になれることにはメリットがありそうだ。

一方で、structure.sql に書かれる SQL は情報量が多く、ごちゃごちゃしていて読みづらいのも事実だ。「schema.rb でサッとテーブル情報を確認できたのは、やっぱり良い体験だったなあ」という思いは正直なところある。

トレードオフのある論点について、ライブラリの意思決定の難しさを垣間見ることができたと感じている。

git worktree を使いこなして、AI コーディングエージェントの同時並行開発を実現する

OpenAI Codex に GPT-5-Codex が登場してから、ローカル環境で動かせる AI コーディングエージェントが、業務でも普通に使えるレベルに到達したと感じている。最近では、実装は自分で一から書くのではなく、コーディングエージェントに任せて、最後の手直しだけを自分でやることが多い。

ローカル環境でのコーディングエージェントの活用が業務の中心になってくると、ひとつ気になる制約が見えてくる。「ある Git リポジトリをコーディングエージェントが編集しているあいだに、そのリポジトリを同時に編集しづらい」という問題だ。

具体的には、次のようなことをやりたくなる。

  • 同じ Git リポジトリに対して、複数のコーディングエージェントを同時に動かしたい
    • チケット A とチケット B の開発を同時に進める
    • Codex と Claude Code に同じチケットの開発を依頼して、アウトプットの品質が良い方を採用する
  • 同じリモートブランチに対して、コーディングエージェントと人間で同時に開発したい

しかし Git では、ひとつの作業ディレクトリにつき同時にチェックアウトできるブランチは 1 つだけだ。普通に開発ブランチを切って作業している場合、そのリポジトリをコーディングエージェントが編集しているあいだに、同時に編集しようとすると、差分が混ざってしまい、訳のわからない状態になってしまう。

こういった問題を解決する存在として、2015 年にリリースされた Git 2.5 で導入された git worktree の機能が、コーディングエージェントを用いた複数同時開発の文脈で、改めて注目されている。

git worktree は、ひとつのリポジトリに対して複数の作業ディレクトリ(worktree)を作成できる機能だ。作業ディレクトリごとに別々のブランチをチェックアウトしておけば、それぞれの作業ディレクトリで、他の作業ディレクトリの編集内容に影響されることなく開発を進めることができる。

Claude Code のドキュメントでも、git worktree を使った並行開発の手法が紹介されている。

仕組みだけを見ると、「作業ディレクトリを増やして、それぞれでコーディングエージェントを動かすだけ」の簡単な話に見える。手元の開発環境で早速やってみようとなったのだが、実際に試してみると、いくつかハマりどころがあった。

試行錯誤の結果、快適に複数同時開発を回せる開発環境が整ったので、git worktree の使い方の一例として紹介したい。

この記事では、git worktree で作成されるディレクトリのことを「作業ディレクトリ」(worktreeに相当)と呼ぶ。

git worktree を初めて使ったときに困ったポイント

初めて git worktree の機能を試したときに、次のような部分が期待通りに動かなくて困った。

  • 作業ディレクトリの新規作成からコーディングエージェントに依頼すると、コーディングエージェントは作成した作業ディレクトリに移動して作業できない
    • コーディングエージェントに「起動時のカレントディレクトリ配下だけ編集可能」という制約がある場合に、それ以外のディレクトリに移動して編集することができない
  • .gitignore で指定された .env など、開発環境の動作に必要なファイルが、作成した作業ディレクトリ側には存在しない

今振り返ると、これらの問題は作業ディレクトリの新規作成からコーディングエージェントに任せようとしたことが原因だったと思う。作業ディレクトリの作成や .env の準備といった環境づくりは、自分の手で整えるのが良さそうだ。

とはいえ、開発チケットごとに毎回手作業が発生するのは面倒だ。そこで、私は「複数のチケット開発に使い回せる作業ディレクトリ」を作っておき、そこでコーディングエージェントに開発を依頼する運用にしている。以下、その作成手順を紹介したい。

コーディングエージェントを動かすための作業ディレクトリの作成手順

前提情報

Git リポジトリのデフォルトの作業ディレクトリを「人間が開発するための作業ディレクトリ」として扱い、コーディングエージェント用に新しい作業ディレクトリを追加で作成する、という想定での手順を紹介する。

以下はサンプルとして使う名称なので、実際の環境での名称に適宜置き換えてほしい。

作業ディレクトリの作成

まず、コーディングエージェント用の作業ディレクトリを git worktree で作成する。

git worktree add ../myapp-ai -b main-ai origin/main

このコマンドは、次の 2 つを同時に行っている。

  • ../myapp-ai という作業ディレクトリ(worktree)を新規に作成する
  • リモートのデフォルトブランチ(origin/main)をトラッキングする main-ai というローカルブランチを作成し、その作業ディレクトリでチェックアウトする

Git では、同じブランチを複数の作業ディレクトリで同時にチェックアウトすることはできない。そのため、デフォルトブランチと同じ名前のブランチ(main)をそのまま複数の作業ディレクトリで共有することはできないので、main-ai のように、「デフォルトブランチをトラッキングするブランチ」を作業ディレクトリごとに用意する構成にしている。

作業ディレクトリごとに「デフォルトブランチをトラッキングするブランチ」を用意しておくと、どの作業ディレクトリでも同じように、

  • デフォルトブランチ相当のブランチで git pull して最新の実装内容を同期する
  • そこから新しく開発ブランチを切って作業を始める

という開発サイクルを回せるようになる。

.gitignore されている開発環境用のファイルを整える

次に、作成した作業ディレクトリ側に、開発環境で必要なファイルを用意する。

具体的には、.env のように .gitignore で ignore されていて、かつ開発環境の動作に必要なファイルは、コーディングエージェント用の作業ディレクトリにも配置しておく必要がある。

作成した作業ディレクトリでコーディングエージェントに開発依頼する

ここまで準備できたら、myapp-ai 側をコーディングエージェント用の作業ディレクトリとして使えるようになる。

  • 人間は従来どおり myapp 側で開発する
  • コーディングエージェントは myapp-ai 側で動かす

という分担にしておくと、お互いの作業が干渉しにくくなる。

同じ手順で作業ディレクトリを増やしていけば、コーディングエージェントを 2 つ 3 つと同時に動かせるようになる。

人間とコーディングエージェントで同じリモートブランチを参照する方法

コーディングエージェントが feature/example-function というリモートブランチを作成して push したとする。このブランチを、エージェントに編集してもらいつつ、人間もローカルで編集したい、という場面がある。

その場合は、次のようにして「同じリモートブランチをトラッキングする別名のローカルブランチ」を作成すると、運用がやりやすい。

git switch -c feature/example-function-human origin/feature/example-function

このように、リモートブランチをトラッキングするローカルブランチを人間用に別名で作成しておくと、

  • リモート側では 1 本のブランチ( feature/example-function
  • 手元では、エージェント用と人間用で別々のローカルブランチ

という構成にできる。これによって、人間とコーディングエージェントで同時に同じリモートブランチに対して編集することが可能になる。

Docker Composeによる開発環境構築で困ったポイントと解決策

Docker Compose を使って開発環境を構築している場合に、worktree ごとに別プロジェクト扱いになる、という問題にもぶつかった。

Docker Compose は、デフォルトでは「ディレクトリ名」をもとにプロジェクト名を決める。そのため、 myappmyapp-ai のようにディレクトリ名が異なると、

  • コンテナ名
  • ネットワーク名
  • ボリューム名

などがそれぞれ別々に作成されてしまう。

この仕様によって、

  • 複数の作業ディレクトリで同じポートを使おうとしてエラーになる
  • 作業ディレクトリごとに Docker リソースがどんどん増えていって整理しづらくなる

といった問題が発生した。

この問題については、COMPOSE_PROJECT_NAME環境変数.env に追加することで解決できた。

COMPOSE_PROJECT_NAME=myapp

また、コマンド実行時に -p--project-name ) オプションで明示的にプロジェクト名を指定することもできる。

docker compose -p myapp run --rm app bin/rspec

こうしてプロジェクト名を固定しておくと、複数の作業ディレクトリから、同じ Docker コンテナを共有できるようになり、Docker Composeによるコマンド実行が複数同時開発時にも支障なく動作するようになった。

VSCode での git worktree のサポート

VSCode では、git worktree の機能が公式にサポートされている。独立したリポジトリを扱うような感覚で、それぞれの作業ディレクトリを別々の VSCode ウィンドウで開くことができて便利だ。

おわりに

この記事に書いた手順で、ローカル環境でのコーディングエージェントによる同時並行開発を、快適に回せるようになった。git worktree は慣れるまでに挙動が非直感的に感じられる部分もあるが、一度コツを掴んでしまえば、AI 時代の開発に必須のテクニックという印象がある。

最近はモニターを 1 枚新たに買い足して、複数のコーディングエージェントがそれぞれの作業ディレクトリで頑張って開発している様子を小脇に眺められるようにしている。仕事の風景は、ほんの半年前と比べてもすっかり様変わりしていて、時代の急速な変化を日々感じているところだ。

GitHub CLIとAGENTS.mdで「自律駆動のAIエンジニア」をローカル環境で実現する

2025年の9月に、GPT‑5-Codexが登場してから、OpenAI Codexがすごく便利だ。

It’s more steerable, adheres better to AGENTS.md instructions, and produces higher-quality code—just tell it what you need without writing long instructions on style or code cleanliness.

GPT‑5-Codexはより指示に従いやすく、AGENTS.md のガイドに忠実で、高品質なコードを出力します—スタイルやコードのクリーンさに関する冗長な指示を書かずとも「必要なこと」を伝えれば動きます。

OpenAIがこう述べているように、従来のコーディングエージェントとは異なり、AIが暴走しないように細かい指示や工夫をせずとも、簡単な依頼で高度な実装ができるようになっている。

この性能で、月額20ドルのChatGPT Plusプランでも利用できるのだから驚きだ。

最近は、仕事でも個人開発でも、Codexを使い倒している。

この記事では、私が最近ハマっている、CodexにGitHub CLIを使わせてGitHubと連携する開発手法について書きたい。手法としては、Codex以外のコーディングエージェントに対しても応用可能だと思う。

AIに人間のように自律的に開発してもらいたい気持ち

仕事でもプライベートでも、やることが非常に多く、忙しい中で、AIには、人間に仕事を依頼するときと同じように、できるだけ丸投げに近い形で、自律的にタスクをこなしてもらいたい気持ちがある。

その点で、私はDevinのユーザー体験が好きだ。Devinは「完全自律型のAIエンジニア」を標榜している、クラウドサーバーで動作するタイプのツールで、GitHubのIssueを指定して依頼すると、PR作成まで全自動で進めてくれるような機能性になっている。

CodexにもDevinのようにクラウドサーバーに開発環境をセットアップする機能が存在するが、Devinとは異なり、Docker Composeを使えないなど、実用面で今ひとつ機能が足りない印象がある。現在のCodexは、手元のローカル環境で動かす使い方がメインになるだろう。

その一方で、ローカル環境で動かすツールだとしても、GitHub CLIを使えば、人間のようにGitHubを読み書きする自律的な開発が可能になると気づいた。

以下はGitHub CLIがインストールされていて、ghコマンドが動作する環境を前提とした説明になる。

ghコマンドを活用した依頼

ghコマンドを使ってissue 123の実装をしてください

ghコマンドが動作する環境では、デフォルト設定のCodexでも、上のように依頼すれば、GitHubと連携した開発が可能になる。

これだけでも便利だが、設定ファイルのAGENTS.mdに、依頼に使えるプロンプトと作業内容を定義することで、よりまとまった量の作業を簡単に依頼できるようになる。

AGENTS.mdでのプロンプト定義

## 作業依頼のプロンプト

コマンドのようなプロンプトで、一連の作業を簡単に依頼できるようにします。

シェルコマンドのように実行できることを期待しています。

### develop issue <ISSUE_NUMBER>

- ghコマンドで指定された<ISSUE_NUMBER>のissueの内容を確認します
- 開発用のGitブランチを新しく作成します
- 開発します

### create pr

- ghコマンドでprを作成します

### review pr <PR_NUMBER>

- ghコマンドで<PR_NUMBER>のprのコードレビューをします

AGENTS.mdにこのように書いておくと、 コーディングエージェントに依頼する際に、以下のようなプロンプトが動作するようになる。

# #123のIssueを開発する
develop issue 123

# PRを作成する
create pr

# #123のPRをコードレビューする
review pr 123

# &&でプロンプトをつないでもいい感じに解釈してくれる
# #123のIssueを開発してPRを作成する
develop issue 123 && create pr

GitHub利用に関する運用ルールの追加

ただし、これだけでは現場の開発ルールに従ったアウトプットができないので、以下のように、GitHub利用に関する運用ルールをAGENTS.mdに追加する。

## Git運用

- `feature/` から始めるブランチ名とします
- コミットメッセージはConventional Commitsのスタイルとします
- コミットは無理に1つにまとめようとせずに、変更の趣旨が分かりやすいように作成します

## GitHub PR作成

GitHubのPR作成を指示された場合は、以下の方針でPRを作成します。

- タイトルは開発対象のIssueと同じとします
- PRの説明を日本語で記載します
- `Resolves` の記法を用いて開発対象のIssueと紐づけます
- assigneeに実装者を指定します

開発環境セットアップ・構文チェック・テスト実行のコマンド整備

また、開発環境のセットアップや構文チェック、テスト実行のコマンド群も記載しておくと、AIが自律的に自らのコードの正しさを検証できるようになる。

例えばRailsアプリケーションをDocker Composeで動かす場合は、以下のような記載になるだろう。

## 動作環境

Docker(docker)とGitHub CLI(gh)のコマンドを実行できることを前提としています。

## コマンドリスト

- Install: `docker compose run --rm app bundle install`
- Migrate: `docker compose run --rm app bin/rails db:migrate`
- Lint: `docker compose run --rm app bin/rubocop`
- Test: `docker compose run --rm app bin/rspec`

AGENTS.mdの全容

ここまでの内容をまとめると、以下のようなAGENTS.mdファイルになる。

# AIコーディングエージェント向けのガイドライン

AIコーディングエージェント向けのガイドラインを記載します。

## 動作環境

Docker(docker)とGitHub CLI(gh)のコマンドを実行できることを前提としています。

## コマンドリスト

- Install: `docker compose run --rm app bundle install`
- Migrate: `docker compose run --rm app bin/rails db:migrate`
- Lint: `docker compose run --rm app bin/rubocop`
- Test: `docker compose run --rm app bin/rspec`

## Git運用

- `feature/` から始めるブランチ名とします
- コミットメッセージはConventional Commitsのスタイルとします
- コミットは無理に1つにまとめようとせずに、変更の趣旨が分かりやすいように作成します

## GitHub PR作成

GitHubのPR作成を指示された場合は、以下の方針でPRを作成します。

- タイトルは開発対象のIssueと同じとします
- PRの説明を日本語で記載します
- `Resolves` の記法を用いて開発対象のIssueと紐づけます
- assigneeに実装者を指定します

## 作業依頼のプロンプト

コマンドのようなプロンプトで、一連の作業を簡単に依頼できるようにします。

以下のように、シェルコマンドのように実行できることを期待しています。

```text
# #123のIssueを開発する
develop issue 123

# #123のIssueを開発してPRを作成する
develop issue 123 && create pr
```

### develop issue <ISSUE_NUMBER>

- ghコマンドで指定された<ISSUE_NUMBER>のissueの内容を確認します
- 開発用のGitブランチを新しく作成します
- 開発します

### create pr

- ghコマンドでprを作成します

### review pr <PR_NUMBER>

- ghコマンドで<PR_NUMBER>のprのコードレビューをします

まとめ

このような設定をすることで、GitHubのIssueに開発チケットを起票してAIに依頼するだけで、開発がドンドン進む環境が手に入る。もう昔の開発スタイルには戻れないな、という感じがしている。

短歌投稿サイトUtakataのMCPサーバーを作ってみた

最近Web開発業界で、「MCPサーバー」というものが注目されている。自社で管理している任意の情報システムと生成AIツールを連携できるような仕組みとして、活用の可能性が模索されているようだ。

簡単に実装できそうな様子があったので、まずはプライベートで実験してみたいと思って、自作の短歌投稿サイトUtakataと連携するMCPサーバーを作ってみた。

MCPサーバーの構築に興味はあるが、やり方がよく分からないような方の参考になるように、作り方を紹介してみる。

今回やったこと

  • UtakataのRailsアプリケーションに、ユーザーごとの短歌の一覧をJSONの配列で返すAPIを作成する
  • ローカル環境で動作するMCPサーバーを構築し、ユーザーごとの短歌の一覧を取得するToolを作成する
  • Claude DesktopにMCPサーバーを登録し、AIとのチャットでユーザーの投稿短歌についてやりとりできるようにする

GitHubリポジトリ

今回作ってみたMCPサーバーの実装を、動作確認できる形でGitHubに公開している。

Utakataにユーザーごとの短歌一覧APIを作成

今回のユースケースでは、MCPサーバーから、連携するアプリケーションのAPIをリクエストして情報を取得する仕組みだ。

そのためのAPIをまず用意する必要がある。以下のようなJSONの配列を返すAPIを作ってみた。

GET /api/users/:user_id/posts

[
  {
    "id": 25401,
    "published_at": "2021-05-15T17:39:00.000+09:00",
    "tanka_text": "片耳にマスクをかけて池の面をながれる風に呼応している",
    "likes_count": 32
  },
  {
    "id": 23563,
    "published_at": "2021-03-29T13:45:16.888+09:00",
    "tanka_text": "人びとの残りをもとめ散る花の上を歩いてゆく鳩の群れ",
    "likes_count": 17
  }
]

RailsでのAPIの実装方法の紹介は今回の本筋でないので省略するが、興味がある方はGitHubに公開されているUtakataの実装を参考にしてみて欲しい。

MCPサーバーの実装

公式のSDKが用意されているので、これを使って開発するのがお手軽だ。現状5種類の言語に対応されているようで、今回はtypescript-sdkを用いて、Node.js環境で動作するように実装する。

SDKのREADMEで、Resources, Tools, Promptsという3つの概念が紹介されていて、使い分けの理解が難しい印象があるが、試行錯誤してみて、Claude Desktopと連携して使う用途では、Toolとして作っておくのがお手軽に実行できて良さそうだった。

index.jsのファイルを作成し、以下のように、 fetch-user-tanka のToolを実装する。

Node.jsのfetchでUtakataのAPIから情報取得し、JSONの配列を整形して以下のようなテキストをアウトプットする仕組みだ。

# ユーザーID: 5 の短歌一覧(最新順)

- 片耳にマスクをかけて池の面をながれる風に呼応している(投稿日時: 2021-05-15T17:39:00.000+09:00、いいね数: 32)
- 人びとの残りをもとめ散る花の上を歩いてゆく鳩の群れ(投稿日時: 2021-03-29T13:45:16.888+09:00、いいね数: 17)

MCPサーバーの動作確認

MCP Inspectorという便利なツールが公式で用意されていて、ローカルで動作確認できる。

npx @modelcontextprotocol/inspector node index.js

Claude Desktopとの連携

MCPサーバーの動作確認ができたら、あとは生成AIツールと連携して使ってみるだけだ。MCPサーバーとの連携に対応している任意のツールと連携可能だが、今回はClaude Desktopと連携して使ってみる。この記事ではMacの環境で確認した結果を紹介する。

まずは、Claudeのデスクトップアプリケーションをインストールする。

公式ドキュメントに記載の方法を参考に連携設定する。Macの場合、Claudeのアプリケーション内の設定ではなく、Macのヘッダーメニューの「Claude > Settings > Developer」から該当メニューに辿り着く必要がある。「Get Started」ボタンを押すと設定ファイルが ~/Library/Application Support/Claude/claude_desktop_config.json のように作成されるので、エディタで編集してMCPサーバーとの連携設定を追加する。

{
  "mcpServers": {
    "utakataTankaReader": {
      "command": "/Users/fuyu77/.nodenv/shims/node",
      "args": [
        "/Users/fuyu77/mcp-utakata/index.js"
      ]
    }
  }
}

私の環境では、上のような設定で動作した。nodeコマンドとjsファイルのパスは、個別の環境の値に置き換える必要がある。

この設定を保存して、Claudeのアプリケーションを起動すると、Utakataの情報を参照したAIとのやりとりが可能になった。

TypeScriptでの実装

説明を簡単にするために、この記事ではJavaScriptでの実装で紹介したが、TypeScriptで実装してビルドする方法が上の公式ドキュメントに解説されている。

まとめ

MCPサーバーの実装はこのように簡単にできて、任意の情報システムと生成AIツールとの連携が可能になるので、業界で大いに注目されているのも頷ける印象だ。

Utakataの投稿短歌についても、この仕組みを活用して何か面白いことができるかも知れない。もしUtakataのMCPサーバーの活用について何かアイディアがある方がいれば、連絡先に記載のXアカウントのDMや、メールアドレスに連絡いただきたい。

ブログを書けなくなった

このブログに2024年に投稿された記事が1つもない。

ブログを書きたい気持ちがなくなった訳ではなく、人知れず下書きを書いてみたりはしているのだけれど、どうにもとりとめのない内容になってしまって、形にならない。諦めて放置するのも残念な感じがするので、開き直ってブログを書けないというブログを書いておくことにした。

まず、最近は仕事を頑張りすぎている、これが大きい。

仕事を頑張っているのだから仕事論を書けば良いのでは、となったりするのだけれど、無駄に長く書いた挙句に「この内容だったらその辺の仕事本を読んだ方が良いのでは」となったりしている。

ブログっていうのは、生活上の雑感をサラッと主観的に書くのが合ってるんだよな。

今年も仕事を頑張る部分は変わらなそうだけれど、極端に入れ込まずに、生活を充実させる部分もやっていきたい気がする。

仕事でソースコードのコメントを英語で書くべきか

今年の1月から新規立ち上げの開発部署でチームリーダーをやっていて、色々と「俺の考えた最強の開発手法」的なものを試しているのだけれど、その一環で、日本語でなく英語でコミットメッセージやソースコードのコメントを書くという開発ルールを導入してみた。

その結果、メリットよりもデメリットが大きいことが判明して、チームとして英語で書くのをやめにしたので、その振り返りを書きたい。

英語でコメントを書くメリット

  • Web開発の世界でグローバルな共通語として機能している
  • 日本語入力に切り替える必要がないので、タイピング効率が良い
  • 文法的に日本語よりもロジカルに書きやすい
  • GitHub CopilotやChatGPTのような生成AIのツールとの相性が良い
  • RuboCopのRSpec/ContextWordingのように、英語利用を前提としたルールを提供しているLinterがある

当初このような点をメリットとして想定して、英語で書いてみたいと考えて、チームメンバーに相談してみたところ、特に英語ルールにネガティブな意見がなかったので、英語で書いてみることにした。

英語の確認に不毛な工数が発生

始めてみると、そもそもの話として、英語を読んだり書いたりすることが好きなのがチーム内に私しかいないということが浮き彫りになって来た。

主語、目的語、動詞の並びが間違っているなどの初歩的な文法ミスが高頻度で出現し、コードレビューが英作文教室のようになってしまった。また、通常のプログラミング上の観点などと違って、指摘しても次回以降も文法ミスがなくならず、英語のライティングというのは長期的な積み重ねが大事なスキルで、短期的な向上が難しいという性質も明らかになった。結果的に、英語の文法確認というプロダクト開発上まったく非本質的な観点に大きな工数を割く必要が出てしまった。

コメントの量と質にも悪影響が

コメントの量と質にも英語による悪影響があった。まず英語に対する苦手意識から、コメントの量が本来必要な水準に対して少ないということが起きた。また、処理の背景の説明を、英語では上手く表現できないということもあった。

日本語を解禁することに

このように、コメントを書く目的に対して、英語利用にはクリティカルなデメリットがあったので、基本的に日本語で書くように方針転換した。

その結果、コメントの量と質が改善し、レビュー負担も減って、快適になった。日本語を解禁してみると、英語必須のルールは手枷足枷をはめて仕事をしているようなものだったと思える。

とは言え英語を書きたいという気持ち

その一方で、「英語禁止」までルール化してしまうと、それはそれでつらいのではという気持ちがある。Web開発という仕事は、世界の開発者と英語でつながっていることに大きな魅力があると感じるためだ。英語を書ければ、OSSコミットなどは若干ハードルが高いとしても、開発者コミュニティやQ&Aサイトなどで、世界の開発者とちょっとした交流をするのは誰でもできるだろう。

また、上にも書いたように、英語を読む、書く、話すスキルの向上には長期的な積み重ねが必要で、日々の開発で英語を書くことをやめてしまったら、システム開発の英語ライティングのスキル向上はなくなってしまう。実際にグローバル企業で働くかどうかは別にしても、Web開発者をやる以上、「世界」とつながっている感覚をもって日々の仕事に取り組みたい気持ちが個人的にはある。

という事情があり、個人的なエゴを押し通して、コメントは各自で書きたい言語で書いて良い、ということにしてしまった。

まとめ

日本の現場で、コメントを英語に統一するルールを導入することには大きなデメリットがあり、基本的にはやめた方が良いということが分かった。

私の場合、それでも英語を書きたいので、何語で書いても良いということにしてしまったけれど、それで本当に大丈夫なのかどうかは、もう少し運用して確認してみたい。

2023/09/19追記

この記事ははてなブックマークコメントで総叩きになってしまった。コメントの内容を受けて記事の見解の修正と、経緯の補足について追記したい。

英語への未練は捨てます

後半の英語への未練を書いている部分は誤りだったと率直に認めたい。 私自身も日本語コメントで書くべきなのは確かにその通りだと思った。

経緯の補足

当初このような点をメリットとして想定して、英語で書いてみたいと考えて、チームメンバーに相談してみたところ、特に英語ルールにネガティブな意見がなかったので、英語で書いてみることにした。

この記事の上の書き方だと、何もないところから急に私が英語を強制したかのように読めることに自分で書いていて気づけなかった。

あまり仕事の話を詳しく書くのもどうかと思って、端折って書いてしまったけれど、元々新規の開発部署の前身の部署が、オフショアの開発メンバーも参加する関係で、コメントを英語で書いていた。新しい開発部署となり、オフショアの開発会社との契約も切れて、チームメンバーが日本人だけになり、改めてコメントの言語をどうしようかとなったときに、英語が望ましいと考えて、英語ルールを提案したところ、チームメンバーから「今までも英語だったし、英語で良さそう」との反応が得られて、英語で統一することにしてみた、というのが詳しい経緯だ。

ただ、前身の部署ではそもそもコメントへの意識が低かったので英語でも何となく回っていたのが、新しい部署でテストコードのシナリオなどをちゃんと書くようにしてみると、英語コメントのデメリットが際立って感じられるようになったという訳だ。

ブログを書くときは実際にあったことを簡略化・抽象化して論旨が明確になるように書くことが多いけれど、省略のしかたによっては明後日の方向にメッセージが伝わってしまうことがあると、改めて書き方の難しさを感じた。

MarkdownメモアプリのObsidianを使ってみたら評判通り捗った

最近やたらと絶賛されているMarkdownメモアプリのObsidianを使ってみたら、なるほど、これは便利だ、という感じなので、使い勝手について書きたい。

ちょっとしたメモの整理に便利

仕事でも日常生活でも、わざわざ文章にまとめて公開する程ではない、ちょっとした思いつきやToDoのようなものをメモしたいときがある。そんなとき、私は今まで仕事ではSlack、プライベートではTwitterの自分宛てのDMを使ってメモを管理していた。

ToDoの管理量を最小限にして、すぐに行動に移して消化する、という点ではセルフDMでのメモ管理もあながち悪くなかったのだけれど、如何せん機能が貧弱すぎた。TwitterのDMではメッセージの編集すらできない。

そんなちょっとしたメモの管理先として、Obsidianは圧倒的に便利だ。

関心ごとにファイルを分けて、見出しを付けるだけでも随分と整理された感じになるし、後から参照しやすい。Markdownでメモの構成をいじり回しているだけでもアイディアが頭の中で整って来る。

複数のMarkdownファイルを管理できるツールの選択肢は他にもいくらでもあるだろうけれど、ObsidianはアプリのUIが秀逸で、何だか色々と書いてみたい気持ちになる。

はてなブログの記事の下書きも、はてなブログのWebエディタで書くよりも、Gitで差分管理しつつObsidianで書いた方が捗ると思って、GitHubにアップしてみた*1

iCloudなどのクラウドストレージで同期する手段はありつつ、ローカルで使うことをベースにした硬派なアプリのObsidian、シンプルな機能性で生活に寄り添う様子は、何処かイマドキの他のアプリケーションにはない魅力がある。

*1:最近までMarkdownではなくはてな記法で記事を書いていたのが悔やまれる。既存記事の記法は後から変更できない。