見出し画像

Claude Codeにプラグインを入れるだけで、開発の精度が激変した話

「とりあえずコード書いて」がなくなる日

Claude Codeを使い始めた頃、こんな経験はありませんか?

「ゲーム作って」とお願いすると、いきなりコードが流れてきて、出来上がったものを動かしてみると、なんとなく動くけど作りが甘い。修正を頼むと別の場所が壊れる。「自分でやった方が早かったかも…」と思った瞬間が、私にも何度もあります。

ところが先日、プラグインを入れて同じことを試したら、まるで別ツールのような挙動になりました。

今回はその体験を、初心者の方にも分かるように整理します。プラグインを知らない方ほど読む価値があります。


何が起きたのか — 実例から

私が試したのは、たった一行のお願いでした。

「短いHTMLコードでゲーム開発をしてください」

プラグインを入れる前なら、おそらくいきなりHTMLが流れてきて終わりです。しかし `superpowers` というプラグインを入れた状態で同じ依頼をすると、Claude Codeはこう動き始めました。

  1. 「どのプロジェクトに作るか?」と確認

  2. 「どんなゲーム?」と選択肢を提示(スネーク/ブロック崩し/テトリス…)

  3. 「ビジュアルスタイルは?」(シンプル/レトロ/モダン)

  4. 「3つの実装アプローチを提案します」と設計案を比較

  5. 設計書をMarkdownファイルで保存

  6. 実装計画をタスク6個に分解して別ファイル保存

  7. タスクごとにサブエージェントを起動して実装

  8. 実装後、別のサブエージェントが仕様適合レビューとコード品質レビュー

  9. 全タスク完了後、Gitコミットまで実行

何が違うか分かりますか?

普通に頼むと「いきなり作る」のが、プラグインを入れると「設計→計画→実装→レビュー」という、人間のエンジニアが普段やっている流れに変わるのです。


プラグインって何? — 1分で分かる説明

Claude Codeのプラグインは、特定の作業フローや専門知識をパッケージ化したものです。スマホのアプリを追加するのと感覚的には同じです。

入れ方も拍子抜けするほど簡単で、Claude Codeの中で `/plugin` と打ち込むだけ。今回私が入れたのは以下の6つです。

  • superpowers — 設計・計画・実装・レビューの開発フロー全般

  • frontend-design — UI/フロントエンドのデザインガイド

  • context7 — 外部ライブラリのドキュメント参照

  • code-review — コードレビュー専用

  • security-guidance — セキュリティチェック

  • commit-commands — Git操作の補助

それぞれ役割が決まっていて、状況に応じてClaude Codeが自動で呼び出してくれます。


superpowersが効く理由 — 3つの仕組み

今回いちばん効果を実感したのが `superpowers` でした。中身を分解すると、3つの仕組みで動いています。

仕組み1:設計を先に固めることを強制する

普通のClaudeなら「ゲーム作って」と言われたら、その場でコードを書き始めます。しかしsuperpowersが入っていると、「設計が固まる前にコードを書いてはいけない」というルールが先に発動します。

結果として、ゲームの種類、ビジュアルスタイル、機能の優先度が、コードを書く前にすべて言語化されます。これが地味ですが効きます。何を作るか曖昧なまま走り出して詰む、という典型的な失敗パターンが消えるからです。

仕組み2:計画書を必ずファイルに残す

設計が固まると、次に実装計画書(Markdown形式)が `docs/superpowers/plans/` に自動保存されます。タスクは6個程度に分解され、それぞれにチェックボックスが付きます。

これが何の役に立つのかというと、後から見返せるのです。「あの時なぜこういう実装にしたんだっけ」が消えない。チームに共有することもできるし、note記事のネタにもなります。私が普段やっている「プロセスを言語化して残す」スタイルと、たまたまですが完全に一致していました。

仕組み3:実装とレビューを別エージェントに分ける

これが一番面白い部分です。

タスクごとに実装専門のサブエージェントが起動してコードを書き、終わったらレビュー専門の別のサブエージェントが独立した目線で検証します。レビューは2段階で、「仕様通りか」と「コード品質はどうか」を別々にチェックします。

人間のチーム開発でいう「実装者と別の人がレビューする」を、AIエージェント間で再現しているわけです。実装者が自分でレビューすると見逃しがちな問題が、第三者目線だと見つかる。あの構造をそのまま自動化しています。


レビューが間違えたとき、どうなるか

ここは初心者の方に特に伝えたいポイントです。

今回の開発中、レビュアーAIが「衝突判定の計算がCRITICALに間違っている」と指摘してきました。しかし指揮役のClaudeはそれを鵜呑みにせず、「数式が等価なので却下」と判断しました。一方で同じレビューに含まれていた「ボールの速度がゼロになり得る」という指摘は本物のバグだったので、修正が入りました。

つまり、レビュアーが誤検知してもメインの判断で正しくフィルタされる構造になっています。

AIに開発を任せるとき、多くの方が心配するのが「AIが暴走して変な修正を入れたらどうしよう」という点だと思います。superpowersはその心配を多段ゲートで吸収する設計になっているのです。


完成したもの — 実際に動くブロック崩しを見てください

口で説明しても伝わりにくいので、実際の画面をご覧ください。最終的に出来上がったのは、Canvas APIで動くネオン風ブロック崩しゲームです。

スタート画面


ネオン発光のタイトル、カラフルなブロック、シアンのパドル、グラデーションのボール。シンプルですが、レトロアーケードの雰囲気がきちんと出ています。

プレイ中

ブロックを破壊するとスコアが加算され、shadowBlurによる発光が効いているのが分かると思います。パドルに当てる位置で反射角が変わるので、慣れると狙ったブロックを集中的に破壊できます。

実装した機能(HTMLファイル1つ・約220行)

  • ネオン発光エフェクト(shadowBlur)

  • ラジアルグラデーションのボール

  • ブロック破壊時のフラッシュ演出(時間経過で消える)

  • パドルの当たる位置で反射角が変わる物理挙動

  • 衝突軸の判定(縦/横どちらに当たったか)でボールの跳ね方を変える

  • スコア・ライフ表示、スタート/ゲームオーバー/クリア画面

短い指示一つから、ここまで設計の通った成果物が出てくる体験は、プラグインなしの状態とは明らかに違いました。


自分で動かしてみたい方へ

下のコードをコピーして、`neon-breakout.html` というファイル名で保存し、ブラウザで開けば即動きます。外部ライブラリは一切使っていません。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Neon Breakout</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { background: #0a0a1a; display: flex; justify-content: center; align-items: center; height: 100vh; overflow: hidden; }
    canvas { display: block; cursor: none; }
  </style>
</head>
<body>
  <canvas id="c"></canvas>
  <script>
    const canvas = document.getElementById('c');
    const ctx = canvas.getContext('2d');
    canvas.width = 480;
    canvas.height = 600;

    // --- 定数 ---
    const W = canvas.width, H = canvas.height;
    const PADDLE_W = 100, PADDLE_H = 12;
    const BALL_R = 8;
    const BLOCK_ROWS = 5, BLOCK_COLS = 8;
    const BLOCK_W = 50, BLOCK_H = 18, BLOCK_PAD = 4;
    const BLOCK_OFFSET_X = (W - (BLOCK_W + BLOCK_PAD) * BLOCK_COLS + BLOCK_PAD) / 2;
    const BLOCK_OFFSET_Y = 60;
    const ROW_COLORS = ['#ff4444', '#ff8800', '#ffff00', '#44ff44', '#4488ff'];

    let state = 'idle';
    let score = 0, lives = 3;

    const paddle = { x: W / 2 - PADDLE_W / 2, y: H - 40, w: PADDLE_W, h: PADDLE_H };
    const ball = { x: W / 2, y: H - 80, dx: 3, dy: -4, r: BALL_R };

    let blocks = [];
    function initBlocks() {
      blocks = [];
      for (let r = 0; r < BLOCK_ROWS; r++) {
        for (let c = 0; c < BLOCK_COLS; c++) {
          blocks.push({
            x: BLOCK_OFFSET_X + c * (BLOCK_W + BLOCK_PAD),
            y: BLOCK_OFFSET_Y + r * (BLOCK_H + BLOCK_PAD),
            w: BLOCK_W, h: BLOCK_H,
            color: ROW_COLORS[r],
            alive: true
          });
        }
      }
    }
    initBlocks();

    let flashes = [];

    function glow(color, blur = 15) { ctx.shadowColor = color; ctx.shadowBlur = blur; }
    function noGlow() { ctx.shadowBlur = 0; }

    function draw() {
      ctx.fillStyle = '#0a0a1a';
      ctx.fillRect(0, 0, W, H);

      blocks.forEach(b => {
        if (!b.alive) return;
        glow(b.color, 12);
        ctx.fillStyle = b.color;
        ctx.fillRect(b.x, b.y, b.w, b.h);
        noGlow();
      });

      glow('#00ffff', 20);
      ctx.fillStyle = '#00ffff';
      ctx.fillRect(paddle.x, paddle.y, paddle.w, paddle.h);
      noGlow();

      flashes = flashes.filter(f => f.t > 0);
      flashes.forEach(f => {
        glow(f.color, 30 * f.t);
        ctx.fillStyle = f.color + Math.floor(f.t * 255).toString(16).padStart(2, '0');
        ctx.fillRect(f.x, f.y, f.w, f.h);
        noGlow();
        f.t -= 0.15;
      });

      const grad = ctx.createRadialGradient(ball.x, ball.y, 1, ball.x, ball.y, ball.r);
      grad.addColorStop(0, '#ffffff');
      grad.addColorStop(1, '#ffaa00');
      glow('#ff8800', 15);
      ctx.beginPath();
      ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2);
      ctx.fillStyle = grad;
      ctx.fill();
      noGlow();

      ctx.fillStyle = '#ffffff';
      ctx.font = '16px monospace';
      ctx.fillText(`SCORE: ${score}`, 10, 30);
      ctx.fillText('❤'.repeat(lives), W - 80, 30);

      if (state === 'idle') {
        ctx.fillStyle = 'rgba(0,0,0,0.5)';
        ctx.fillRect(0, 0, W, H);
        glow('#00ffff', 30);
        ctx.fillStyle = '#00ffff';
        ctx.font = 'bold 32px monospace';
        ctx.textAlign = 'center';
        ctx.fillText('NEON BREAKOUT', W / 2, H / 2 - 20);
        noGlow();
        ctx.fillStyle = '#aaaaaa';
        ctx.font = '16px monospace';
        ctx.fillText('SPACE / クリック でスタート', W / 2, H / 2 + 20);
        ctx.textAlign = 'left';
      }
      if (state === 'gameover') {
        ctx.fillStyle = 'rgba(0,0,0,0.6)';
        ctx.fillRect(0, 0, W, H);
        glow('#ff4444', 30);
        ctx.fillStyle = '#ff4444';
        ctx.font = 'bold 36px monospace';
        ctx.textAlign = 'center';
        ctx.fillText('GAME OVER', W / 2, H / 2 - 20);
        noGlow();
        ctx.fillStyle = '#aaaaaa';
        ctx.font = '16px monospace';
        ctx.fillText(`SCORE: ${score}  SPACE でリスタート`, W / 2, H / 2 + 20);
        ctx.textAlign = 'left';
      }
      if (state === 'clear') {
        ctx.fillStyle = 'rgba(0,0,0,0.6)';
        ctx.fillRect(0, 0, W, H);
        glow('#ffff00', 30);
        ctx.fillStyle = '#ffff00';
        ctx.font = 'bold 36px monospace';
        ctx.textAlign = 'center';
        ctx.fillText('CLEAR!', W / 2, H / 2 - 20);
        noGlow();
        ctx.fillStyle = '#aaaaaa';
        ctx.font = '16px monospace';
        ctx.fillText(`SCORE: ${score}  SPACE でリスタート`, W / 2, H / 2 + 20);
        ctx.textAlign = 'left';
      }
    }
    draw();

    const keys = {};
    document.addEventListener('keydown', e => {
      keys[e.key] = true;
      if ((e.key === ' ' || e.key === 'Spacebar') && state !== 'playing') resetGame();
    });
    document.addEventListener('keyup', e => { keys[e.key] = false; });
    canvas.addEventListener('mousemove', e => {
      const rect = canvas.getBoundingClientRect();
      paddle.x = e.clientX - rect.left - paddle.w / 2;
      paddle.x = Math.max(0, Math.min(W - paddle.w, paddle.x));
    });
    canvas.addEventListener('click', () => {
      if (state !== 'playing') resetGame();
    });

    function resetGame() {
      score = 0; lives = 3; state = 'playing';
      ball.x = W / 2; ball.y = H - 80;
      ball.dx = 3; ball.dy = -4;
      paddle.x = W / 2 - PADDLE_W / 2;
      flashes = [];
      initBlocks();
    }

    function update() {
      if (state !== 'playing') return;

      if (keys['ArrowLeft'])  paddle.x = Math.max(0, paddle.x - 6);
      if (keys['ArrowRight']) paddle.x = Math.min(W - paddle.w, paddle.x + 6);

      ball.x += ball.dx;
      ball.y += ball.dy;

      if (ball.x - ball.r < 0)      { ball.x = ball.r; ball.dx *= -1; }
      if (ball.x + ball.r > W)      { ball.x = W - ball.r; ball.dx *= -1; }
      if (ball.y - ball.r < 0)      { ball.y = ball.r; ball.dy *= -1; }

      if (ball.dy > 0 &&
          ball.x > paddle.x && ball.x < paddle.x + paddle.w &&
          ball.y + ball.r > paddle.y && ball.y + ball.r < paddle.y + paddle.h + 4) {
        ball.dy *= -1;
        const hit = (ball.x - (paddle.x + paddle.w / 2)) / (paddle.w / 2);
        ball.dx = hit * 5;
        if (Math.abs(ball.dx) < 1) ball.dx = ball.dx < 0 ? -1 : 1;
      }

      if (ball.y - ball.r > H) {
        lives--;
        if (lives <= 0) { state = 'gameover'; return; }
        ball.x = W / 2; ball.y = H - 80;
        ball.dx = 3; ball.dy = -4;
      }

      let cleared = true;
      blocks.forEach(b => {
        if (!b.alive) return;
        cleared = false;
        if (ball.x + ball.r > b.x && ball.x - ball.r < b.x + b.w &&
            ball.y + ball.r > b.y && ball.y - ball.r < b.y + b.h) {
          b.alive = false;
          score += 10;
          flashes.push({ x: b.x, y: b.y, w: b.w, h: b.h, color: b.color, t: 1.0 });
          const overlapX = Math.min(ball.x + ball.r - b.x, b.x + b.w - ball.x + ball.r);
          const overlapY = Math.min(ball.y + ball.r - b.y, b.y + b.h - ball.y + ball.r);
          if (overlapX < overlapY) ball.dx *= -1; else ball.dy *= -1;
        }
      });
      if (cleared) state = 'clear';
    }

    function loop() {
      update();
      draw();
      requestAnimationFrame(loop);
    }
    loop();
  </script>
</body>
</html>

このコードはAIに「短いHTMLでゲームを作って」と頼んだだけで、設計→計画→実装→レビューのフローを経て出力されたものです。コピペで動くものを、対話を重ねて30分程度で得られたというのが今回いちばんお伝えしたい部分です。


プラグインを使うときの注意点

良いことばかりではないので、初心者の方が踏みやすい落とし穴も書いておきます。

1つ目はトークン消費が増えることです。 サブエージェントを何度も起動するため、シンプルなコード生成と比べてトークンを多く使います。Claude Proプランの会話制限に当たりやすくなる場面もあります。

2つ目は手数が増えることです。 「とにかく早く動くものが欲しい」という時にsuperpowersを起動すると、設計の質問に何度か答える必要があります。プロトタイピングと相性が悪い場合があるので、用途で使い分けるのが現実的です。

3つ目は環境依存です。 私の環境(Windows + Claude Code v2.1.122)では、`frontend-design` プラグインのビジュアルプレビューサーバーが同梱されておらず、ブラウザでの即時確認ができませんでした。プラグインによっては動作環境を選びます。


初心者の方への結論

「Claude Codeを使ってるけど、なんとなく結果に物足りなさを感じる」

そう思ったら、まず `/plugin` と打って `superpowers` を入れてみてください。たった一行のコマンドで、開発の流れがエンジニアリングのフローに変わります。

設計の質問にちゃんと答える手間は増えますが、その分**「思いつきで作る」が「設計→計画→実装→レビュー」に変わる**体験は、一度味わうと元には戻れません。

特に40年エンジニアをやってきた身としては、若い頃に叩き込まれた基本フローをAIが自動でなぞってくれる感覚は、新鮮かつ少し感慨深いものがありました。

プラグインは無料で入れられて、合わなければ外せばいいだけです。一度試してみてください。きっと「なんで早く入れなかったんだろう」と思うはずです。


最後まで読んでいただきありがとうございました。記事が役に立ったら、スキやフォローで応援していただけると嬉しいです。

いいなと思ったら応援しよう!