Spec Kitを用いたチーム開発記録 - part3 Spec Kitで既存コンポーネントを使わせるコツ
2026-10-05
azblob://2026/10/19/eyecatch/2026-10-19-its-not-like-ai-was-ignoring-the-design-system-000.png

本記事は生成AIを利用しています

はじめに

Spec Kitを使ったAI駆動開発で、フロントエンドを実装させていたときのことです。仕様を書いて、Planを作って、Tasksに分解して、Implementまで進める流れは快適でした。ところが、Implementで出力されたコードを動かして確認すると、こんな問題が起きていました。

  • 画面ごとにボタンの見た目や余白がバラバラ
  • 既存の Button コンポーネントがあるのに、生の <button> にスタイルを当てて実装されている
  • 同じ役割のUIなのに、画面によって別々に作られている

この記事では、Implementで出力されたあとにこのばらつきを直した方法と、その過程で分かったことをまとめます。

使用技術:Vue 3 / TypeScript / Tailwind など
使用環境:Spec Kit CLI 0.4.4.dev0 / テンプレート 1.1.0 / 使用したAIエージェント:Claude

何が起きていたのか

Spec Kitの開発プロセスは Specify → Plan → Tasks → Implement の4フェーズで進みますが、前半の3つはテキストのレビューです。Planに「ボタンを配置する」と書かれていても、既存の Button を使うのか、生の <button> を書くのかは、コードが出てくるまで分かりません。

そのため、気づくのはImplementで実装が終わり、画面を動かして確認している最中でした。

直し方:既存コンポーネントへの置き換えを依頼する

Implementで出力されたコードに対しては、「既存のコンポーネントに置き換えて」と依頼して直しました。このとき、コンポーネントの場所をファイルパスで具体的に指定しています。

 
実装済みの画面で使っている生の <button> と <input> を、既存コンポーネントに置き換えてください。

- ボタン:src/components/ui/Button.vue の Button
- 入力欄:src/components/ui/Input.vue の Input

見た目や挙動は変えないでください。
必要な場合は確認してください。

結果:ほとんどの <button> と <input> が既存コンポーネントに置き換わりました。置き換え先が分からない場合はAIが確認を求めてくるため、正確に置き換えられました。

気づき:無視していたのではなく、知らなかった

置き換えを依頼するなかで分かったのは、場所を教えれば、AIは素直に既存コンポーネントを使うということです。

つまり原因は「AIがデザインシステムを無視していた」ではなく、「AIがコンポーネントの存在や場所を知らなかった」ことだと考えています。AIはプロジェクトのコードを全部読んでから実装するわけではなく、必要そうなところを検索して読むだけです。存在を知らなければ、生の <button> を書くほうが自然です。

置き換えを依頼するときのポイント

実際に置き換えを依頼してみて、うまくいったときとそうでないときの差は、主に依頼の仕方にありました。特に効果があったのは次の3点です。

1. ファイルパスで指定する

「既存のコンポーネントを使って」とだけ伝えると、AIはそれらしいファイルを探しに行きます。ただ、似た名前のファイルが複数あったり、ディレクトリ構成が独特だったりすると、別のファイルを参照したり、新しいコンポーネントを作ってしまったりすることがあります。

src/components/ui/Button.vue のようにパスで場所を示すと、AIはまずそのファイルを読み、props や slot の構成を確認してから置き換えます。「探す」手間を省くことで、推測が入る余地を減らせます。

コンポーネントにバリアント(variant="primary" など)がある場合は、どの見た目にどのバリアントを対応させるかも書いておくと、置き換え後の見た目のズレが減ります。

 
- ボタン:src/components/ui/Button.vue の Button
  - 青い塗りのボタン → variant="primary"
  - 枠線だけのボタン → variant="secondary"
- 入力欄:src/components/ui/Input.vue の Input

2. 対象を絞る

「プロジェクト全体を置き換えて」と依頼すると、変更範囲が広すぎて差分の確認が難しくなります。また、AIが途中で対象を読み漏らすこともあります。

そこで、画面単位やディレクトリ単位で範囲を区切って依頼しました。

 
src/views/settings/ 配下の画面だけを対象にしてください。
それ以外のファイルは変更しないでください。

範囲を絞ると、1回あたりの差分が小さくなり、レビューもしやすくなります。1画面ずつ置き換えて動作確認し、問題なければ次の画面へ進む、という進め方が結果的に早く終わりました。

3. 置き換え後に確認する

AIが「すべて置き換えました」と報告しても、実際には一部が残っていることがあります。今回も、条件分岐の中にある <button> や、別ファイルに切り出された小さなコンポーネント内の <input> が残っていました。

報告をそのまま信じず、grep などで機械的に確認するのがおすすめです。

 
grep -rn "<button" src/views/
grep -rn "<input" src/views/

残っていたものは、検出結果をそのままAIに渡して「この箇所も置き換えてください」と依頼すれば対応してもらえます。確認の工程まで含めて1セットと考えると、取りこぼしを防げます。

実装後に直す方法の課題

この方法で直せはしましたが、運用を続けるうえでは課題も感じています。

一度実装させてから直すので、二度手間になる

Implement で生の <button> を使って実装し、そのあとで既存コンポーネントに書き換える流れなので、同じ箇所を2回実装していることになります。AIの処理時間もトークンも余分にかかりますし、置き換えの過程で別の不具合が入り込む可能性もあります。

差分が大きくなり、レビューの負担が増える

置き換えはファイルをまたいで発生するため、差分が大きくなりがちです。しかも、見た目は変わらないことが前提の変更なので、レビューでは「挙動が変わっていないか」を一つずつ確認する必要があります。機能追加の差分と置き換えの差分が混ざると、どこが本質的な変更なのか分かりにくくなる点も気になりました。

画面ごとに毎回同じ指摘をすることになる

一度置き換えを依頼しても、AIがその内容を次の実装で覚えているとは限りません。新しい画面を Implement すると、また生の <button> が出てくることがあり、そのたびに同じ依頼を繰り返すことになりました。原因が「知らなかった」ことにあるなら、知らせる仕組みを作らない限り、同じことが繰り返されます。

今後試してみたいこと

実装後に直すのではなく、もっと前の段階で防げないかを試したいと考えています。以下はアイデアの段階で、まだ検証していません。

Specify / Plan の段階でファイルパスを指定する

置き換えの過程で、コンポーネントの場所を教えればAIは既存コンポーネントを使ってくれることが分かりました。それなら、実装を始める前に場所を伝えておけば、最初から既存コンポーネントで実装してもらえるかもしれません。Specify や Plan のプロンプトに、使ってほしいコンポーネントのパスを書き添えるだけなので、すぐに試せる方法だと考えています。

constitution や CLAUDE.md のような常設ファイルに書く

毎回プロンプトに書くのは手間ですし、書き忘れも起きます。Spec Kit の constitution や、Claude が参照する CLAUDE.md にコンポーネントの使用ルールを書いておけば、毎回指示しなくても参照されるようになるかもしれません。

 
## UIコンポーネント
- ボタンは生の <button> を使わず、src/components/ui/Button.vue を使うこと
- 入力欄は生の <input> を使わず、src/components/ui/Input.vue を使うこと

Plan テンプレートに「使用する既存コンポーネント」の欄を追加する

Spec Kit の前半3フェーズはテキストのレビューなので、コードが出てくるまでコンポーネントの選択が分からない、というのが今回の問題の出発点でした。Plan テンプレートに「使用する既存コンポーネント」の欄を設ければ、Plan をレビューする時点で「この画面は Button を使う予定か」をテキストだけで確認できるようになります。問題を Implement の後ではなく Plan の段階で見つけられるようにする、という狙いです。

lint で生の <button> を検出する

ここまでの方法は、どれもAIへの「お願い」です。お願いは守られないことがあるので、最後の砦として機械的に検出する仕組みもほしいところです。eslint-plugin-vue の vue/no-restricted-html-elements を使えば、テンプレート内の特定のHTML要素を禁止できます。

 
javascript
Javascriptrules: {
  'vue/no-restricted-html-elements': [
    'error',
    { element: 'button', message: 'src/components/ui/Button.vue の Button を使ってください' },
    { element: 'input', message: 'src/components/ui/Input.vue の Input を使ってください' }
  ]
}

エラーメッセージにコンポーネントのパスを書いておけば、AIが lint を実行したときにそのまま修正の手がかりになるはずです。ただし、Button.vue 自身の中では <button> を使うので、src/components/ui/ 配下は対象外にするなどの調整が必要になります。

コンポーネントの一覧(カタログ)を用意する

ボタンと入力欄くらいなら常設ファイルに直接書けますが、コンポーネントが増えるとパスを一つずつ書くのは現実的ではありません。コンポーネント名・パス・用途・主な props をまとめた一覧を用意し、常設ファイルからはその一覧を参照させる形にすれば、数が増えても管理しやすくなると考えています。

まとめ

Implement の出力でUIがばらついていた原因は、AIがデザインシステムを無視していたことではなく、既存コンポーネントの場所を知らなかったことでした。ファイルパスで場所を伝えれば、AIは素直に既存コンポーネントを使ってくれます。

ただ、実装後に直す方法は二度手間になるため、今後は Spec や Plan、常設ファイル、lint など、実装より前の段階で防ぐ方法を試していきたいと考えています。

同じようにAIがデザインシステムを使ってくれずに困っている方は、まずコンポーネントのファイルパスを直接伝えるところから試してみてください。最後まで読んでいただき、ありがとうございました。