🎨

Design AI Linter - AIでデザイントークンの品質を自動チェックする

に公開

はじめに

デザインシステムを運用する上で、デザイントークンの品質を保証することは重要な課題です。しかし、トークンの命名規則や一貫性を手動でチェックするのは時間がかかり、人間の目視レビューだけでは見落としが発生する可能性があります。

以前、「すべてのコンポーネントは提供しない」─ AIと変数が支える、余白のあるデザインシステム構想という記事で、デザインシステムにおけるガードレールの重要性について書きました。

https://zenn.dev/redamoon/articles/article34-design-system

その中で、AIによるレビューの仕組み化とガードレールの自動強化について触れましたが、今回はその具体的な実装として、Design AI Linterというツールを開発し、npmパッケージとして公開しました。

https://www.npmjs.com/package/@design-tools/design-ai-linter

本記事では、このツールがどのような課題を解決し、どのように使えるのかを紹介します。

なぜAIによるデザインレビューが必要なのか

業務において、フロントエンドや画面を担当するエンジニアがデザイナーのチェックを受けることがあります。実装されたコンポーネント(コード化)を、Figmaのデザインとの差異をチェックするという、人を介したフロー が多く発生していました。

そのため、カラーが異なったり、レイアウトの余白が数ピクセルズレていたりと、そのやりとりに時間がかかっていました。

AIが一般的になり、Figma MCPなどのツールを通して実装時からチェックすることは可能になりました。しかし、すでに存在するコンポーネントをどうチェックするのか、そしてどこを直すべきかの指標が明確ではありませんでした。エンジニア間でデザインレビューを効率化することで、お互いの時間削減につながると考え、このツールを開発しました。

デザインレビューフローの改善

従来のデザインレビューフローと、AIデザインレビューを挟んだ新しいフローを比較します。

従来のフロー

従来のフローでは、実装後にデザイナーが手動でチェックするため、以下のような課題がありました。

  • 時間がかかる: デザイナーとエンジニアのやりとりに時間がかかる
  • 見落としが発生: 人間の目視レビューでは見落としが発生する可能性がある
  • 指摘が曖昧: 「ここが違う」という指摘だけで、具体的な修正方法が不明確

AIデザインレビューを挟んだ新しいフロー

AIデザインレビューを挟むことで、以下のような改善が期待できます。

  • 自動チェック: 機械的に検出できる問題はAIが自動的にチェック
  • 即時フィードバック: 実装と同時に問題を検出できる
  • 明確な指標: どこを直すべきかが明確になる
  • UX議論への集中: デザイナーは機械的なチェックではなく、UXの本質的な価値の探求に集中できる

デザインシステムにおけるガードレールの重要性

デザインシステムを組織横断で運用する際、最小限のガードレールを設定することが重要です。

https://zenn.dev/redamoon/articles/article34-design-system

「すべてのコンポーネントは提供しない」─ AIと変数が支える、余白のあるデザインシステム構想でも述べたように、ガチガチなルールは現場との摩擦を生み、システムを形骸化させてしまいます。一方で、最低限のルールは守らなければなりません

エンジニアであれば、コードの書き方のルールを手動でチェックするのではなく、ESLintでルールを自動適用することで、ルールの適用を仕組み化しています。デザインシステムでも同様に、最低限のガードレールで認識を揃えておくことが重要です。

しかし、デザイントークンの品質チェックは、コードの静的解析ほど簡単ではありません。例えば、以下のような問題を検出する必要があります。

  • トークン名の一貫性(info vs informationのような同義語の混在)
  • スペーシングスケールの整合性
  • デザインシステムの複雑さの評価
  • コード内での生の値の使用(トークンを使うべき箇所で直接値を書いている)

これらは、単純なパターンマッチングでは検出が困難で、意味的な理解が必要です。
ここにAIの力を借りることで、人間のチェックを最小限にし、UXの本質的な価値の探求に集中できるようになります。

人間のチェックを最小限にしてUX議論に集中する

従来の人の目による目視レビューは、コストが高い上に、技術の進化(AI)によって代替可能になりました。
この「人の手による検品作業」を、テクノロジーに置き換えることで、レビューの仕組み化自体も進化しています。

システムが定義したコンテキスト(最小限のルールセット)をAI/Linterに適用することで、デザインのガードレールに対するチェックが自動で、かつ即時に行われます

  • AI/Linterの役割: トークン定義外の色や、ルールに反した余白の使用など、機械的な違反を検知し、自動でエラーを出します。
  • 結果: 人間が「間違い探し」に時間を割く必要がなくなり、**「UXの本質的な価値」**の探求に集中できるのです。

Design AI Linterとは

https://www.npmjs.com/package/@design-tools/design-ai-linter

Design AI Linterは、FigmaからエクスポートされたDesign Systemトークン(例:Figma Tokens / Token Studio経由)をリントするCLIツールです。静的解析とAIによる意味解析を組み合わせて、デザイントークンの品質を保証します。

npmパッケージとして公開されており、以下のコマンドでインストールできます。

npm install -D @design-tools/design-ai-linter

または、yarnやpnpmを使用する場合:

yarn add -D @design-tools/design-ai-linter
pnpm add -D @design-tools/design-ai-linter

静的解析とAIによる意味解析の組み合わせ

Design AI Linterは、2つのアプローチを組み合わせています。

  1. 静的解析: 命名規則、生のカラー/ピクセル値、重複などの機械的に検出できる問題をチェック
  2. AIによる意味解析: LLM(OpenAI/Gemini)を使用して、意味的な一貫性、スペーシングの整合性、デザインの複雑さをチェック

これにより、単純なパターンマッチングでは検出できない、より深い問題を発見できます。

どんなことができるのか

Design AI Linterは、以下のような機能を提供します。
実際のプロダクトで利用はしていませんが、実践したり調整したりを繰り返ししている状況です。
そのあたりは順次アップデートして行こうと思っています。

1. トークン正規化と静的ルールチェック

tokens.jsonを読み込み、解析用に正規化した上で、以下のような静的ルールをチェックします。

  • 命名規則: トークン名が指定されたパターンに一致するか
  • 生の値の検出: コードファイル内で直接使用されているカラー値やピクセル値を検出
  • 重複チェック: 同じ値を持つトークンが重複していないか

2. AIによる意味解析

各社のLLMのAPI Keyを使用して、以下のような意味的な問題を検出します。

ai-semantic-naming(意味的命名の一貫性)

トークン名の意味的一貫性をチェックします。以下の問題を検出します。

  • トークンの一貫した階層的命名の違反
  • info vs informationのような同義語の混在
  • 数値と記述的値の混在(例:spacing.4 vs spacing.small
  • 命名の深さの不一致

ai-spacing-consistency(スペーシングの整合性)

スペーシングトークンの一貫性をチェックします。

  • スペーシングスケールの整合性(例:4, 8, 16, 24のような規則的な間隔)
  • 使用されていないトークンの検出
  • スケールから外れた値の使用

ai-design-complexity(デザインの複雑さ)

デザインシステムの複雑さを評価します。

  • トークンの数
  • 階層の深さ
  • 使用パターン

これらを分析して、システムの複雑さに関する警告を提供します。

3. コードファイル解析とトークン提案

コードファイルを解析して、以下の機能を提供します。

  • 生の値の検出: コード内で直接使用されているカラー値やピクセル値を検出
  • トークン提案: 検出された生の値に対して、適切なデザイントークンへの置き換えを提案
  • 修正コードの生成: トークンを使用した修正後のコードを提案

4. Git差分のチェック

デフォルトで、ステージングされたファイルのみをチェックします。これにより、CI/CDパイプラインやpre-commitフックでの使用に適しています。

また、特定のコミット範囲の差分のみをチェックすることもできます。

# 直前のコミットとの差分
dslint lint --commit-diff HEAD~1..HEAD

# mainブランチとの差分
dslint lint --commit-diff main..HEAD

インストールとセットアップ

インストール

npmパッケージとして公開されているため、以下のコマンドでインストールできます。

npm install -D @design-tools/design-ai-linter

環境変数の設定(AI機能を使用する場合)

AI機能を使用するには、OpenAIまたはGeminiのAPIキーが必要です。プロジェクトルートに.envファイルを作成し、以下のいずれかを設定します。

OPENAI_API_KEY=sk-...
# または
GEMINI_API_KEY=AIza...

設定ファイルの作成

プロジェクトルートにdesignlintrc.jsonを作成します。

{
  "source": {
    "type": "tokensJson",
    "path": "./tokens.json"
  },
  "rules": {
    "naming-convention": {
      "severity": "error",
      "pattern": "^([a-z]+)(\\.[a-z0-9\\-]+)*$"
    }
  }
}

基本的な使用方法

インストール後、以下のコマンドでリントを実行できます。

# ステージングされたファイルをチェック(デフォルト動作)
npx dslint lint

# 全ファイルをチェック
npx dslint fix

# コードファイルを解析
npx dslint lint --files "src/**/*.{tsx,css}"

実際の使用例

基本的な使用方法は、インストールとセットアップのセクションで説明した通りです。詳細な使用例や実行結果については、使用例リポジトリを参照してください。

CI/CDパイプラインへの統合

https://github.com/redamoon/design-ai-linter-example

実際にサンプルリポジトリを作成して GitHub Actionsの利用例を作ってみました。
Design AI LinterをCI/CDパイプラインに統合することで、プルリクエスト時に自動的にデザインレビューを実行できます。

CI/CDフローの可視化

以下のmermaid図は、CI/CDパイプラインでのDesign AI Linterの動作フローを示しています。

GitHub Actionsのワークフローファイル例

以下は、GitHub ActionsでDesign AI Linterを実行するワークフローファイルの例です。

GitHub Actionsワークフローファイル(クリックして展開)
.github/workflows/design-lint.yml
name: Design AI Linter

on:
  push:
    branches:
      - main
      - develop
  pull_request:
    branches:
      - main
      - develop

jobs:
  lint:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
      issues: write

    steps:
      - name: リポジトリのチェックアウト
        uses: actions/checkout@v4

      - name: pnpmのセットアップ
        uses: pnpm/action-setup@v2
        with:
          version: 8

      - name: Node.jsのセットアップ
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'pnpm'

      - name: 依存関係のインストール
        run: pnpm install

      - name: レポートディレクトリの作成
        run: mkdir -p reports

      - name: design-ai-linterの実行
        env:
          GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
        run: |
          # 標準出力とエラー出力の両方をキャプチャ
          pnpm exec dslint lint --model gemini-2.5-flash --pr-comment > reports/lint-report.md 2>&1 || true
          exit_code=$?
          
          # レポートファイルが存在することを確認
          if [ ! -f reports/lint-report.md ]; then
            echo "⚠️ レポートファイルが生成されませんでした" > reports/lint-report.md
          fi
          
          # JSON形式のレポートも確認(designlintrc.jsonで設定済み)
          if [ ! -f reports/lint-report.json ]; then
            echo "{}" > reports/lint-report.json
          fi
          
          # エラーがあっても続行するため、exit_codeは後で使用
          echo "exit_code=$exit_code" >> $GITHUB_ENV

      - name: エラー情報の抽出と整形
        if: always()
        run: |
          node .github/scripts/parse-lint-errors.cjs reports reports/pr-comment.md || true
          
      - name: PRコメントとしてレポートを投稿
        if: always() && github.event_name == 'pull_request'
        uses: actions/github-script@v7
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          script: |
            const fs = require('fs');
            const path = require('path');
            
            // 整形されたPRコメント用のマークダウンを読み込む
            const prCommentPath = 'reports/pr-comment.md';
            let commentBody = '';
            
            if (fs.existsSync(prCommentPath)) {
              commentBody = fs.readFileSync(prCommentPath, 'utf8');
            } else {
              // フォールバック: 元のマークダウンレポートを使用
              const reportPath = 'reports/lint-report.md';
              if (fs.existsSync(reportPath)) {
                const reportContent = fs.readFileSync(reportPath, 'utf8');
                if (reportContent.trim()) {
                  commentBody = `## 🔍 Design AI Linter Report\n\n${reportContent}`;
                }
              }
            }
            
            // エラー情報のJSONも読み込む(デバッグ用)
            let errorInfo = null;
            const errorJsonPath = 'reports/errors.json';
            if (fs.existsSync(errorJsonPath)) {
              try {
                errorInfo = JSON.parse(fs.readFileSync(errorJsonPath, 'utf8'));
              } catch (e) {
                console.log('エラー情報のJSONの読み込みに失敗しました:', e.message);
              }
            }
            
            // コメントが空の場合はスキップ
            if (!commentBody.trim()) {
              console.log('コメント内容が空のため、PRコメントをスキップします');
              return;
            }
            
            // 既存のコメントを検索
            const comments = await github.rest.issues.listComments({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
            });
            
            const botComment = comments.data.find(comment => 
              comment.user.type === 'Bot' && 
              (comment.body.includes('Design AI Linter Report') || 
               comment.body.includes('Design AI Linter'))
            );
            
            // コメントを投稿または更新
            if (botComment) {
              await github.rest.issues.updateComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                comment_id: botComment.id,
                body: commentBody
              });
              console.log('PRコメントを更新しました');
            } else {
              await github.rest.issues.createComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: context.issue.number,
                body: commentBody
              });
              console.log('PRコメントを投稿しました');
            }
            
            // エラー情報がある場合はログに出力
            if (errorInfo && errorInfo.hasErrors) {
              console.log('エラーサマリー:', JSON.stringify(errorInfo.summary, null, 2));
            }

      - name: レポートのアップロード
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: design-lint-report
          path: |
            reports/lint-report.md
            reports/lint-report.json
            reports/pr-comment.md
            reports/errors.json
          retention-days: 7
          if-no-files-found: warn

このワークフローは、以下の動作をします。

  1. トリガー: pushまたはpull_requestイベントで起動(main/developブランチ)
  2. 環境セットアップ: pnpmとNode.jsをセットアップし、依存関係をインストール
  3. レポートディレクトリの作成: レポートファイルを保存するためのディレクトリを作成
  4. Design AI Linter実行: Gemini APIを使用してリントを実行し、結果をreports/lint-report.mdに出力
  5. エラー情報の抽出と整形: レポートからエラー情報を抽出し、PRコメント用に整形
  6. PRコメント投稿: PRの場合、検出された問題をPRコメントとして自動投稿(既存のコメントがあれば更新)
  7. レポートのアップロード: レポートファイル(Markdown、JSON、PRコメント用)をGitHub Actionsのアーティファクトとしてアップロード

GitHub Actionsでの実行例

実際のGitHub Actionsでの実行例は、以下のリポジトリで確認できます。

GitHub Actionsの参考例になります。出力方法などは、自由に改変できるため、ここでは参考として提示したものです。

実際の動作フロー

GitHub Actionsで実行されると、以下のような流れで処理が進みます。

  1. リポジトリのチェックアウト: コードを取得
  2. 環境セットアップ: pnpmとNode.jsをセットアップし、依存関係をインストール
  3. レポートディレクトリの作成: reportsディレクトリを作成
  4. Design AI Linter実行: Gemini APIを使用してリントを実行
    • 標準出力とエラー出力の両方をreports/lint-report.mdにキャプチャ
    • エラーがあっても続行するため、|| trueで終了コードを無視
  5. エラー情報の抽出と整形: parse-lint-errors.cjsスクリプトでレポートを解析し、PRコメント用に整形
  6. PRコメント投稿: PRの場合、検出された問題をPRコメントとして自動投稿
    • 既存のコメントがあれば更新、なければ新規作成
  7. レポートのアップロード: レポートファイルをアーティファクトとしてアップロード

実行ログの詳細では、以下のような情報が確認できます。

  • design-ai-linterの実行: リントの実行結果とexit code
  • エラー情報の抽出と整形: レポートの解析結果
  • PRコメント投稿: コメントの投稿・更新状況
  • エラーと警告の詳細: 検出された問題の詳細情報

エラーが検出された場合、ワークフローファイル内で|| trueを使用しているため、リントの実行自体は失敗してもCIジョブは続行されます。これにより、エラーがあってもレポートの生成とPRコメントの投稿が確実に実行されます。

ただし、エラーを検出した場合にCIジョブを失敗させたい場合は、|| trueを削除するか、continue-on-error: trueを設定しないことで、デザインルールの違反を自動的に検出し、マージ前に修正を促すことができます。

CIでのレポート出力

CIで実行された結果は、以下の2つの方法で確認できます。

  1. PRコメント: 検出された問題がPRコメントとして自動的に投稿されます
  2. アーティファクト: レポートファイルがGitHub Actionsのアーティファクトとして保存され、ダウンロードして確認できます

実際のPRコメントの例:

実際のPRコメント例(クリックして展開)
## 🔍 Design AI Linter Report

### サマリー

-**エラー**: 5件
- ⚠️ **警告**: 15件
- ℹ️ **情報**: 8件

### 検出された問題

#### `tokens.json`

| 重要度 | ルール | トークン/問題 | メッセージ | 提案 |
|--------|--------|--------------|------------|------|
| ❌ エラー | naming-convention | - | naming-convention | color.primary |
| ❌ エラー | naming-convention | - | naming-convention | color.errorcolor |
| ❌ エラー | naming-convention | - | naming-convention | color.invalid-token-name |
| ❌ エラー | naming-convention | - | naming-convention | color.anotherinvalidtoken |
| ❌ エラー | naming-convention | - | naming-convention | spacing.invalid-spacing |
| ⚠️ 警告 | ai-semantic-naming | `削除、または 'color.legacy.primary' のように再命名(互換性のために残す必要がある場合)。理想的には、'color.brand.primary' に統一する。` | 一貫性のない命名規則と曖昧な目的 | 一貫した命名規則(例: すべて小文字)を採用してください。このトークンがブランドプライマリーを表す場合、'color.brand' 階層の一部として、'color.primary' パレットの特定のシェードを参照するようにすることをお勧めします。重複している場合は削除してください。 |
| ⚠️ 警告 | ai-semantic-naming | `削除` | 一貫性のない命名規則、冗長なサフィックス、および既存トークンとの同義語 | このトークンを削除し、エラー関連の色には一貫して 'color.semantic.error' を使用してください。 |
| ⚠️ 警告 | ai-semantic-naming | - | 潜在的な冗長性と整合性の欠如 | 'color.brand.primary' を 'color.primary.500' を参照するように変更し、エイリアスとして扱ってください。これにより、ブランドのプライマリーカラーがパレットの特定のシェードであることを明確に示せます。 |
| ⚠️ 警告 | ai-semantic-naming | `color.invalidtokenname` | 一貫性のない命名規則 (ハイフンとPascalCase) | 一貫した命名規則に従ってトークン名を変更してください。例えば、'color.invalidtokenname'(フラットケース)または 'color.invalid-token-name'(ケバブケースセグメントを採用する場合)。 |
| ⚠️ 警告 | ai-semantic-naming | `color.anotherinvalidtoken` | 一貫性のない命名規則 (PascalCase) | 一貫した命名規則に従ってトークン名を変更してください。例えば、'color.anotherinvalidtoken'(フラットケース)または 'color.another-invalid-token'(ケバブケース)。 |
| ⚠️ 警告 | ai-semantic-naming | `spacing.invalidspacing` | 一貫性のない命名規則 (ハイフン) | 一貫した命名規則に従ってトークン名を変更してください。例えば、'spacing.invalidspacing'。 |
| ⚠️ 警告 | ai-spacing-consistency | `spacing.3xl` | トークン値 '60px' が確立された8pxのモジュラースケールと一致していません。 | モジュール性を維持するために、値を最も近い8pxの倍数に調整してください。例えば、'48px'の後の論理的な進行を続けるために'64px'に変更するか、より小さいステップを希望する場合は'56px'を検討してください。 |
| ⚠️ 警告 | ai-spacing-consistency | `spacing.3xl` | トークン名 'Invalid-Spacing' はセマンティックではなく、確立された「Tシャツサイズ」の命名規則から逸脱しています。 | トークン値がモジュラースケールと整合する場合、Tシャツサイズの命名規則(例: 'spacing.3xl')に合わせてトークン名を変更してください。値が整合できない場合は、トークンを削除するか、非標準的な値を正当化するより適切なセマンティックな名前を検討してください。 |
| ℹ️ 情報 | ai-design-complexity | `color.Primary` | カラーシステムの冗長性と矛盾 | カラーシステム全体を再構築し、プライマリ、セカンダリ、ブランドカラーの役割を明確に定義して冗長性を排除します。例えば、`color.brand.primary``color.primary.500`へのエイリアスとして利用するか、`color.primary`をブランドの主要色とします。 |
| ℹ️ 情報 | ai-design-complexity | `color.Primary` | 命名規則の不一貫性 | 全てのトークンタイプに対して一貫した命名規則(例: ドット区切りで常に小文字と数字、または特定の場合にのみキャメルケースを許可)を確立し、強制します。存在しないか無効なトークンは削除または修正します。 |
| ℹ️ 情報 | ai-design-complexity | `color.secondary` | カラースケールの不完全性 | `color.secondary`スケールを`color.primary``color.neutral`と同様に完全なスケールに拡張することを検討します。 |
| ℹ️ 情報 | ai-design-complexity | `spacing.Invalid-Spacing` | スペーシングスケールの不規則性 | `spacing.Invalid-Spacing`を既存のスケールに合うように調整するか、より論理的なスケールの一部として再定義します。例えば、`spacing.3xl`として`64px`のように調整します。 |
| ℹ️ 情報 | ai-design-complexity | `color.Invalid-Token-Name` | 未定義または無効なトークン名の存在 | これらのトークンを削除するか、実際の用途に合わせて適切に命名し、デザインシステムに統合します。 |
| ⚠️ 警告 | ai-semantic-naming | `color.Primary` | 一貫性のない命名規則と曖昧な目的 | 一貫した命名規則(例: すべて小文字)を採用してください。このトークンがブランドプライマリーを表す場合、'color.brand' 階層の一部として、'color.primary' パレットの特定のシェードを参照するようにすることをお勧めします。重複している場合は削除してください。 |
| ⚠️ 警告 | ai-semantic-naming | `color.ErrorColor` | 一貫性のない命名規則、冗長なサフィックス、および既存トークンとの同義語 | このトークンを削除し、エラー関連の色には一貫して 'color.semantic.error' を使用してください。 |
| ⚠️ 警告 | ai-semantic-naming | `color.brand.primary` | 潜在的な冗長性と整合性の欠如 | 'color.brand.primary' を 'color.primary.500' を参照するように変更し、エイリアスとして扱ってください。これにより、ブランドのプライマリーカラーがパレットの特定のシェードであることを明確に示せます。 |
| ⚠️ 警告 | ai-semantic-naming | `color.Invalid-Token-Name` | 一貫性のない命名規則 (ハイフンとPascalCase) | 一貫した命名規則に従ってトークン名を変更してください。例えば、'color.invalidtokenname'(フラットケース)または 'color.invalid-token-name'(ケバブケースセグメントを採用する場合)。 |
| ⚠️ 警告 | ai-semantic-naming | `color.AnotherInvalidToken` | 一貫性のない命名規則 (PascalCase) | 一貫した命名規則に従ってトークン名を変更してください。例えば、'color.anotherinvalidtoken'(フラットケース)または 'color.another-invalid-token'(ケバブケース)。 |
| ⚠️ 警告 | ai-semantic-naming | `spacing.Invalid-Spacing` | 一貫性のない命名規則 (ハイフン) | 一貫した命名規則に従ってトークン名を変更してください。例えば、'spacing.invalidspacing'。 |
| ⚠️ 警告 | ai-spacing-consistency | `spacing.Invalid-Spacing` | トークン値 '60px' が確立された8pxのモジュラースケールと一致していません。 | モジュール性を維持するために、値を最も近い8pxの倍数に調整してください。例えば、'48px'の後の論理的な進行を続けるために'64px'に変更するか、より小さいステップを希望する場合は'56px'を検討してください。 |
| ℹ️ 情報 | ai-design-complexity | `color.Primary,` | カラーシステムの冗長性と矛盾 | カラーシステム全体を再構築し、プライマリ、セカンダリ、ブランドカラーの役割を明確に定義して冗長性を排除します。例えば、`color.brand.primary``color.primary.500`へのエイリアスとして利用するか、`color.primary`をブランドの主要色とします。 |
| ℹ️ 情報 | ai-design-complexity | `color.secondary.*` | カラースケールの不完全性 | `color.secondary`スケールを`color.primary``color.neutral`と同様に完全なスケールに拡張することを検討します。 |
| ℹ️ 情報 | ai-design-complexity | `color.Invalid-Token-Name,` | 未定義または無効なトークン名の存在 | これらのトークンを削除するか、実際の用途に合わせて適切に命名し、デザインシステムに統合します。 |

このように、PRコメントとして問題が表示されるため、開発者はコードレビューと同時にデザインレビューも受けられます。

実際に使ってみた感想

実際にCIや手元で実行してみた結果、AIを挟むことで出力がわかりやすくなった一方で、出力までに時間がかかるという課題がありました。

AIによる意味解析は、単純なパターンマッチングでは検出できない問題を発見できるため、レポートの質は向上します。しかし、LLMのAPI呼び出しには時間がかかるため、CIでの実行時間が長くなってしまうことがあります。

この実行時間は、API経由での処理やモデルの性能によって大きく変わってきます。実際に開発時にはGemini 2.5 Flashを使用していましたが、モデルの性能や組み合わせ方によって、より早く実行できる環境を構築できると考えています。

例えば、以下のような最適化が考えられます。

  • モデルの選択: より高速なモデル(例:Gemini 3)を使用する
  • 並列処理: 複数のルールを並列で実行する
  • キャッシュ: 同じトークンセットに対する結果をキャッシュする
  • モデルの組み合わせ: 軽量なモデルでフィルタリングし、重要な問題のみを高精度なモデルで再チェックする

これらの最適化により、CIでの実行時間を短縮しつつ、高品質なレポートを提供できるようになることを目指しています。

今後の開発

現在、Design AI Linterはトークンファイルを基にしたチェック機能まで実装されています。しかし、将来的には以下のような機能拡張を計画しています。

Figmaとの直接連携

現在は、Figmaからエクスポートしたtokens.jsonを読み込む形ですが、将来的にはFigma APIと直接連携し、デザインファイルから直接トークンを取得できるようにします。

想定では、以下のようなワークフローが実現できると考えています。

  1. Figmaでデザイントークンを定義
  2. Design AI LinterがFigma APIから直接トークンを取得
  3. コードとデザインの整合性を自動チェック
  4. 問題があればPRコメントとして通知

さらなる機能拡張

  • デザインコンポーネントの解析: Figmaのコンポーネント情報を解析し、コードとの整合性をチェック
  • デザインレビューの自動化: PR作成時に自動的にデザインレビューを実行し、問題を検出
  • カスタムルールの拡張: より柔軟なカスタムルールの定義と実行(開発中)
  • AIの出力精度や速度改善: モデルの選択や組み合わせ、キャッシュ機能などによる実行時間の短縮と精度の向上

まとめ

Design AI Linterは、デザインシステムにおけるガードレールを自動化するツールです。
静的解析とAIによる意味解析を組み合わせることで、デザイントークンの品質を自動的に保証する仕組み化を目指しています。

このツールにより、以下のような効果が期待できます。

  • 品質の自動保証: デザインルールの違反を自動的に検出
  • レビューコストの削減: 人間のチェック時間を削減
  • UX議論への集中: 機械的なチェックはAIに任せ、UXの本質的な価値の探求に集中

デザインシステムは 「縛るためではなく、解き放つための」 ツールであるべきです。
Design AI Linterは、その実現を支援するツールとして、今後も進化させていきたいと思っています。

Figma MakeやFigma MCPなど、AI連携ツールが次々と登場しています。新しいやり方やツールも出てくると思いますが、Design AI Linterは、デザインシステムの仕組み化の一つとして、これらのツールと組み合わせながら取り入れていきたいと考えています。

参考リンク

Discussion