要約
Claude Codeのスキル・プラグイン・フックを組み合わせ、要件定義からリリースノート作成まで全7フェーズの開発ライフサイクルをAnthropic公式ツールだけで完結させる実践例を紹介する。題材はChrome拡張機能「Rich Markdown Preview」のIssue #10「同名ファイルが開けない」改善である。
この記事を読むことで得られるメリット
この記事を読むことで以下のことが分かる:
- Claude Codeのスキル・プラグイン・フックを組み合わせた開発ライフサイクルの全体設計
- 要件定義・設計・実装・レビュー・ドキュメントの各フェーズで使うべき公式ツールの選び方
- security-guidanceやhookifyによるガードレールの実践的な導入方法
- 実際のChrome拡張機能の機能改善を通じた具体的な適用例
この記事を読むのにかかる時間
約12分
環境
- Claude Code(スキル / プラグイン / フック)
- Chrome拡張機能「Rich Markdown Preview」
- TypeScript / Reactプロジェクト
はじめに:Claude Codeの3つの拡張機能
Claude Codeには「スキル」「プラグイン」「フック」という3種類の拡張機能がある。Anthropicはこれらを組み合わせた開発ライフサイクル全体をカバーする公式ツール群を提供している。
この記事では、実際のChrome拡張機能の機能改善を題材に、要件定義からリリースノート作成まですべてAnthropic公式ツールだけで完結させる実践例を紹介する。
全体像:Claude Codeで実現する7フェーズの開発ライフサイクル
まず使用するツールをプロジェクトのCLAUDE.mdに定義しておく。
## 開発ライフサイクル(Anthropic公式スキル構成)
| # | フェーズ | スキル / コマンド | 補足 |
|---|---|---|---|
| ① | 要件定義 | `doc-coauthoring` | 仕様書を `docs/` 配下に Markdown で作成 |
| ①' | Issue 更新 | `gh issue edit <N> --title ... --body ...` | 確定要件でタイトル・説明を上書き |
| ② | 設計 | `feature-dev` | Discovery → Architecture フェーズ |
| ③ | デザイン | `frontend-design` | 新UIコンポーネントが必要な場合のみ |
| ④ | 実装 | `feature-dev` | コーディング・ビルド・テスト |
| ⑤ | コミット/PR | `commit-commands:commit-push-pr` | semantic commit |
| ⑥ | レビュー | `pr-review-toolkit:review-pr` | code / errors / types を並列実行 |
| ⑦ | ドキュメント | `claude-md-management:revise-claude-md` / `internal-comms` | CLAUDE.md更新 + リリースノート |このテーブルをCLAUDE.mdに書いておくと、Claude Codeがセッション開始時に読み込み、どのフェーズでどのツールを使うべきかを自律的に判断できるようになる。
ガードレール:開発全工程を守る自動チェック機構
フェーズに入る前に、まずガードレールを整備する。
hookify:カスタムルールで意図しない動作をブロック
/hookifyスキルを呼び出すと、会話の中で「やめてほしい動作」をフック化できる。
/hookify "テスト実行前にビルドが成功していることを確認せずにコミットしないこと"これにより~/.claude/hookify.*.local.mdに設定が書き出され、次のセッションから自動で適用される。
security-guidance:ファイル編集時の自動セキュリティ警告
security-guidance@claude-plugins-officialプラグインをsettings.jsonで有効化すると、Edit / Writeツール実行前に自動でセキュリティスキャンが走る。
スキャン対象パターン(一部):
- 動的コード実行(XSS・インジェクション系)
- DOMへの未サニタイズなHTML挿入
- GitHub Actionsワークフロー内でのコンテキスト変数の直接使用
- OSコマンド実行のラッパー関数
実際に動作確認した際の出力例(セキュリティパターンを含むコードを書こうとした瞬間):
Error: PreToolUse:Write hook error:
[python3 ${CLAUDE_PLUGIN_ROOT}/hooks/security_reminder_hook.py]:
⚠️ Security Warning: Using dynamic code execution is a major security risk.
Consider using JSON.parse() for data parsing or alternative design patterns.
Only use this if you truly need to evaluate arbitrary code.フックはPreToolUseイベントで発火し、exit code 2を返してファイル書き込み自体をブロックする。同一ファイル×同一ルールの警告はセッション内で1回のみ表示され、2回目以降は開発者の判断に委ねられる。
① 要件定義フェーズ:doc-coauthoringで仕様書を共同執筆
/doc-coauthoringを呼び出すと、Claude Codeが対話形式で仕様書を共同執筆する。
今回のテーマ: 「同名ファイルが開けない」Issue #10
対話の中で以下の5つのセクションを詰めていく:
- 背景と問題 — 異なるディレクトリの同名ファイルが片方しか開けない
- ユーザーストーリー — 開発者が
dir1/README.mdとdir2/README.mdを同時にタブで確認したい - 受け入れ条件(AC) — サイドバー・D&D・タブ復元の各経路で同名ファイルが独立して開ける
- 影響範囲 —
tabsフィーチャー・directoryReader・D&Dフック - 制限事項 — ブラウザのFile API制約上、D&Dファイルのディレクトリ名は取得不可
成果物はdocs/issue-10-same-name-file-spec.mdとして保存される。
①' Issue更新:確定要件でGitHub Issueを上書き
要件が確定したら、対応するGitHub Issueのタイトルと説明を更新する。
gh issue edit 10 \
--repo masayan1126/chrome-extensions \
--title "同名ファイルを別タブで開けない問題を修正" \
--body "## 問題\n異なるディレクトリの同名ファイルを同時にタブで開けない..."② 設計フェーズ:feature-devのDiscovery→Architectureサブエージェント
/feature-devは7フェーズのガイド付きワークフローである。設計フェーズでは内部的にcode-explorerとcode-architectの2つのサブエージェントが並行稼働する。
code-explorerが発見した根本原因
src/shared/utils/directoryReader.ts
readDirectory() は再帰呼び出し時に parentPath を引き継いでいなかった。
そのため全ファイルのパスが "dirName/file.md" の形になり、
異なるディレクトリでも同じパスと判定されていた。code-architectが提案したアーキテクチャ
FileSystemFileHandle.isSameEntry()を使ったファイル同一性判定(最も正確)- D&Dファイルは
name::size::lastModified複合キーによる代替判定 getDirName()ユーティリティを追加し、pathから親ディレクトリ名を導出dirNameを型に持つのではなく派生データとして計算(不変条件の保護)
設計の成果物はプランファイルとして自動保存される。
③ デザインフェーズ:frontend-design(任意フェーズ)
新しいUIコンポーネントが必要な場合にのみ使用する。今回は既存タブのラベル表示を変えるだけなので、frontend-designでプロトタイプを生成し、実際のコンポーネントに反映した。
デザイン要件:
- 同名ファイルがない場合 →
README.md(従来通り) - 同名ファイルが複数タブにある場合 →
dir1/README.md/dir2/README.md
④ 実装フェーズ:feature-devでコーディング
設計をもとに3つのファイルを修正する。
修正1:directoryReader.ts — フルパス生成の根本修正
// src/shared/utils/directoryReader.ts
export const readDirectory = async (
handle: FileSystemDirectoryHandle,
depth: number = 0,
maxDepth: number = 5,
showHiddenFiles: boolean = false,
isInsideHiddenDir: boolean = false,
parentPath: string = '' // ← 追加:親パスを再帰で引き継ぐ
): Promise<DirectoryInfo> => {
const currentPath = parentPath
? `${parentPath}/${handle.name}`
: handle.name;
// ファイル追加時
files.push({
name: entry.name,
path: `${currentPath}/${entry.name}`, // ← フルパスを保証
handle: entry as FileSystemFileHandle,
isMarkdown: isMd,
});
// 再帰呼び出し時
const subDir = await readDirectory(
entry as FileSystemDirectoryHandle,
depth + 1, maxDepth, showHiddenFiles, childInsideHidden,
currentPath // ← 親パスを渡す
);これが今回の根本修正である。再帰呼び出し時にparentPathを引き継いでいなかったため、すべてのファイルがdirName/file.mdの形にしかならず、異なるディレクトリでも同じパスと誤判定されていた。
修正2:useTabOpen.ts — isSameEntry()による正確な同一性判定
// src/features/tabs/useTabOpen.ts
const openTab = useCallback(async (file: FileInfo) => {
let existingTab: OpenTab | undefined;
if (file.handle) {
// File System Access API による正確な同一性判定
for (const t of tabsRef.current) {
if (!t.file.handle) continue;
try {
if (await t.file.handle.isSameEntry(file.handle)) {
existingTab = t;
break;
}
} catch (error) {
// ハンドルが無効化された場合(権限失効など)は debug ログのみ
if (error instanceof DOMException &&
(error.name === 'InvalidStateError' || error.name === 'SecurityError')) {
console.debug('isSameEntry skipped: handle is invalid', { tabId: t.id });
} else {
console.warn('isSameEntry threw unexpected error:', error);
}
}
}
} else {
// handleなし(D&D)はパス + 名前で判定
existingTab = tabsRef.current.find(
(t) => !t.file.handle && t.file.path === file.path && t.file.name === file.name
);
}
// ...isSameEntry()はファイルシステムレベルでの同一性を判定するため、パスが文字列として異なる場合(シンボリックリンク、マウントポイント違い等)も正確に検出できる。
修正3:TabBar.tsx — 同名タブの識別表示
// src/features/tabs/TabBar.tsx
const duplicateNames = useMemo(() => {
const nameCounts = new Map<string, number>();
tabs.forEach((tab) => {
nameCounts.set(tab.file.name, (nameCounts.get(tab.file.name) ?? 0) + 1);
});
return new Set(
Array.from(nameCounts.entries())
.filter(([, count]) => count > 1)
.map(([name]) => name)
);
}, [tabs]);
const getDisplayLabel = (tab: OpenTab): string => {
if (!duplicateNames.has(tab.file.name)) {
return sanitizeFileName(tab.file.name); // "README.md"
}
const dirName = getDirName(tab.file);
if (dirName) {
return sanitizeFileName(`${dirName}/${tab.file.name}`); // "dir1/README.md"
}
return sanitizeFileName(tab.file.name);
};useMemoでタブ一覧の変化時のみ再計算されるため、タブが多い場合もパフォーマンスに影響しない。
⑤ コミット/PRフェーズ:commit-push-prで一括完了
/commit-push-prを呼ぶと、コミット・プッシュ・PR作成が一度に完了する。
生成されたセマンティックコミットメッセージ:
fix: 同名ファイルを別タブで開けない問題を修正 (#10)
- directoryReader でフルパスを正しく構築(parentPath 再帰引き継ぎ)
- useTabOpen で FileSystemFileHandle.isSameEntry() による同一性判定を導入
- D&D ファイルは name::size::lastModified 複合キーに統一
- TabBar で同名タブが複数の場合のみ parentDir/filename 形式で表示
- useTabRestore に isCancelled フラグを追加し競合状態を防止PR本文(自動生成):
## 概要
Issue #10「同名ファイルを別タブで開けない」問題を修正。
## 変更内容
- directoryReader.ts: フルパス生成の根本修正
- useTabOpen.ts: isSameEntry() による正確な重複チェック
- TabBar.tsx / TabItem.tsx: 同名タブのラベル表示改善
## テスト手順
1. 異なるディレクトリに同名 .md ファイルを用意
2. サイドバーから両ファイルをクリックして開く
3. 両方が別タブで開かれること、タブに "dir/file" 形式が表示されることを確認
Closes #10⑥ レビューフェーズ:pr-review-toolkitで複数エージェント並列実行
/pr-review-toolkit:review-prを呼ぶと、複数の専門エージェントが並列でレビューを実行する。
今回は以下の問題が発見された。
type-design-analyzerの指摘
FileInfo 型に dirName フィールドが含まれているが、
これは path から常に導出できる。
冗長なフィールドを持つと path と dirName の不整合が発生しうる。
→ dirName を型から削除し、getDirName(file) ユーティリティで計算することを推奨この指摘を受けてdirNameをFileInfo型から削除し、getDirName()をfileSystem.tsに追加した。型設計の問題をClaude Codeが自律的に発見した実例である。
silent-failure-hunterの指摘
useDragDrop.ts の catch ブロックがすべてのエラーを無音で飲み込んでいる。
getAsFileSystemHandle の失敗と openTab の失敗が区別できない。
→ console.warn でエラー内容を記録するよう修正⑦ ドキュメントフェーズ:revise-claude-md + internal-comms
CLAUDE.mdの更新
/revise-claude-mdを呼ぶと、今回のセッションで学んだことをプロジェクトメモリに反映する。今回追加された内容:
## ビルド・テスト・リリース準備
1. `public/manifest.json` の `version` を更新
2. `npm run build` — TypeScript コンパイル + Vite ビルド
3. `npm run test` — Vitest(73テスト)
4. `rm -f rich-markdown-preview.zip && zip -r rich-markdown-preview.zip dist/ public/`リリースノートの生成
/internal-commsで2種類のリリースノートを生成した。
GitHub Releases向け(技術者向け):
### バグ修正
**同名ファイルを別タブで開けない問題を修正** (#10)
- FileSystemFileHandle.isSameEntry() API を使った正確なファイル同一性判定
- directoryReader でフルパスを正しく構築
- D&D時の重複チェックを name+size+lastModified 複合キーに変更Chrome拡張ストア向け(一般ユーザー向け):
【バージョン 1.2.2】
■ バグ修正
・同名のファイルを別タブで開けない問題を修正しました
フォルダが違えば同じ名前のファイルでも
それぞれ別タブとして開けるようになりました。ユーザー層に応じて文体・詳細度を変えた2種類を一度に生成できる。
まとめ:Claude Codeで開発ライフサイクル全体を自動化
今回のフローで実証したことを振り返る。
課題 | Anthropicツールの解決策 |
|---|---|
要件の抜け漏れ |
|
危険なコードの混入 |
|
設計の誤り |
|
型設計の問題 |
|
エラーの握りつぶし |
|
ドキュメントのズレ |
|
すべてのフェーズにわたって人間の判断が必要なポイント(要件の優先度決め、設計方針の選択、レビュー指摘の受け入れ)には人間が介在し、繰り返し的・定型的な作業(コード生成、コミットメッセージ、リリースノート)はツールが自動化する分担になっている。Claude Codeを使った開発ライフサイクルは、Chrome拡張機能に限らずTypeScript/Reactプロジェクト全般に適用できる。