アルファテックブログ

入社して半年の新人によるClaude Codeの導入と検索機能の実装

カバー

はじめに

この技術ブログには今月から検索機能が追加されています。そして今回実装したこの機能は、今年入社したばかりの新人である私がClaude Code環境をベースに実装したものです。私自身、普段から生成AIは利用しているものの、主に使っていたのはチャット形式だったため、Claude CodeのようなAIコーディングエージェントを本格的に触ることは初めての経験でした。この記事は、新人である私がインターネットで使い方を調べるところから始め、ゼロから開発環境を整えるまでの記録です。

特に、AIに何をどこまで任せてよいのかを決め、その結果が正しいことを確認する仕組みづくりには苦労したため、これらの課題に対して何を考え、どのような仕組みを構築したのかを紹介します。さらに後半では、この環境を活用して技術ブログにサイト内検索機能を実装するまでの流れを解説します。


Claude Code開発環境を作成するにあたって用意したもの

具体的な中身に入る前に、まずはClaude Code開発環境の構築にあたって作成したものを4つ紹介します。

  1. 権限設定 … Claudeが実行を許可されているコマンドとアクセス可能なファイルの範囲を宣言するもので、Claude Codeの設定ファイルに記述
  2. フック … ツールが実行される直前にスクリプトを走らせることで実行の可否を判定し、権限設定では表現できない「守るべき順序」を強制するために使用
  3. 動作確認チェックリスト … ページが生成されているか、リンクが切れていないかなど、機能追加後のテストで利用する検証項目集
  4. CLAUDE.mdとスキル … セッションごとの説明を省くための常設ドキュメントと、動作確認の手順書を主に記載

実際につまずいた点

Claude Codeのインストールは数分で終わったものの、いざ使い始めると以下のような問題に直面しました。

1. コマンドやファイルの権限設定

AIから見れば記事ファイルも同じ「編集できるファイル」です。そのため、サイトの機能修正だけを行わせたい場合でも、明示的に制限をしないと無関係の記事ファイルまで編集されてしまう可能性があります。コマンドについても同様です。AIに誤ってファイルを消されたり、書き換えてほしくないファイルを編集されたりする可能性があります。

2. テスト・検証方法の確立

Astro移行後、初の機能追加ということもあり、AIを活用した開発におけるテストの手法が整備されておらず、AIの変更内容に対して目視で確認するしかない状態でした。

3. ディレクトリ構成や注意点の説明

新しいセッションを開くたびに、サイトの構成やビルドの特徴、触れてはいけないファイルを説明し直す必要があります。説明を省くと、前のセッションで避けることができていた落とし穴にはまってしまう可能性があります。

これら3点を解決しないままでは、作業効率の低下やトラブルを引き起こす原因になりかねません。実際、作業中にClaude Code側から特定のコマンドやファイルに対して権限の追加・変更を求められたことが何度もありました。また、想定していない変更が加えられたり、成果物を本当に信じてよいのか不安になったりする場面がありました。そこで、私が実施した対策や環境整備について順に紹介していきます。


コマンドやファイルの権限設定

最初に.claude/settings.jsonを用いた権限設定を行いました。Claude Codeでは、このファイルによってコマンドやファイル操作ごとに、allow(確認なしで実行)、ask(実行前に確認)、deny(実行禁止)を指定できます。

権限は、読み取り専用やビルド・検証系の定型コマンドをallow、取り返しはつくが影響のある操作をask、外部通信や破壊的操作、機密ファイルの読み取りをdenyという方針で分類しました。下記はあくまで実例であるため、ご自身の環境や方針に合わせた設定をおすすめします。

{
"permissions": {
"allow": [
"Bash(npm ci)",
"Bash(npm run build)",
"Bash(npm run preview)",
"Bash(git status)",
"Bash(git status *)",
"Bash(git diff)",
"Bash(git diff *)",
"Bash(diff *)",
"Bash(cmp *)",
"Bash(wc *)",
"Bash(head *)",
"Bash(tail *)"
],
"ask": [
"Bash(rm *)",
"Bash(npm install *)",
"Bash(git push *)",
"Bash(git clean *)",
"Bash(chmod *)"
],
"deny": [
"Bash(curl *)",
"Bash(wget *)",
"Bash(git reset --hard*)",
"Bash(sudo *)",
"Read(.env)",
"Edit(.claude/settings.json)",
"Edit(.claude/hooks/block-*.sh)"
]
}
}

読み取り専用コマンドをallowに入れる意味

settings.jsonを作成した当初、私はClaude Codeに「確認なしで実行」権限を与えることに慎重になっていたため、allowにはたった2個のコマンドしか登録しませんでした。しかし、Claude Codeを使い続けるうちに、allowに登録するコマンドを増やす必要性があると感じるようになりました。

その理由の一つとして、先ほど紹介した動作確認チェックリストでClaudeが自動でチェックできる範囲を広げることが挙げられます。チェック時に使用するコマンドがallowに登録されていない場合、実行のたびに確認ダイアログが表示されるため、結局人が張り付いて対応しなければなりません。

そこで、allowにビルド・差分比較・行数カウント・Gitの参照系を追加しました。これによりnpm run buildから差分比較まで、確認ダイアログを挟まずに連続して実行できるようになりました。

あえてallowに入れなかったもの

一方で、便利だとわかっていながらallowに入れなかったコマンドとしてfindとsedがあります。

これはfindには-deleteと-exec、sedには-i(上書き)が存在することで、後述するフック(Hooks)と呼ばれる自動実行コマンドやスクリプトの抜け道になることを防ぐ目的があります。

Terminal window
find . -name "*.tmp" -exec rm {} \;
sed -i 's/foo/bar/' src/config.js

フックでは、-exec rm {}のようにrmにフラグがないものや、find -delete、sed -iを検知できません。これはフックがrmというコマンドを見ているのであって、findの中で行われる削除処理までは把握できないためです。このような抜け道を防ぐため、フックでrm -r / rm -fを拒否したうえで、findとsedをallowに入れませんでした。

ただし、これらの変更を行ったことによる作業への影響として、Claudeが一時ファイルを作って後から削除するという一般的な手順を踏めなくなりました。Claude視点で見ると、プロセス置換などを使って一時ファイルを作らずに済ませる必要が生じて不便ではありますが、安全面を考慮した結果この構成を採用しました。

denyは「やらせない」ではなく「事故らせない」

curlとwgetをdenyに含めたのは外部への通信をClaudeに自由にさせないという一般的な理由もありますが、HTTPステータスの確認を人間の仕事として切り分けたかったという意図があります。
動作確認チェックリストの中には、「作成した404ページが正しく表示されるか」のように、Claudeによるコマンドの実行だけでは十分に確認できない項目があります。そのため、Claudeがローカル環境で確認しただけでは、実際の公開環境における動作の保証にはなりません。そういった項目はチェックリスト側で人間が確認することを前提としたうえで、Claudeが確認する手段を塞ぐことにより「人間が確認すること」を担保しています。


フックでガードレールを敷く

権限設定は「何を実行してよいか」を決めるものである一方、「どのような順序でやるべきか」までは表現できません。そこで、Claude CodeのPreToolUseというフックを使用することにしました。

これはツールが実行される直前にスクリプトを走らせ実行可否を判定するものであり、以下は今回設定した例です。

{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "bash \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-dangerous-rm.sh" },
{ "type": "command", "command": "bash \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-force-push.sh" }
]
},
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "bash \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/warn-missing-baseline.sh" }
]
}
]
}
}

matcherで対象のツールを指定し、そのツールが呼ばれる直前にcommandのスクリプトが走ります。ツール実行の可否や人間による確認の要否を、スクリプトの終了コードと出力で返す仕組みです。

上2つのフックは、「再帰・強制フラグ付きのrmを拒否するもの」と「Gitの強制pushを拒否するもの」です。どちらも定型的な処理で、コマンド文字列を正規表現で判定します。

Terminal window
# 同一コマンド区間(; & | で区切られた範囲)内で、rm に -r / -R / -f 系のフラグが付く場合のみ拒否する。
# \brm\b により confirm / charm / form などの部分一致を避ける。
if printf '%s' "$COMMAND" | grep -qE '\brm\b[^;&|]*([[:space:]]-[a-zA-Z]*[rRf]|[[:space:]]--recursive|[[:space:]]--force)'; then
decision "deny" "rm with recursive or force flag is not allowed."
fi

ベースラインの取り忘れ防止

3つ目のフックでは、Claudeが変更を加える前に、比較の基準となるビルド成果物(ベースライン)を取得しているかを確認します。仮にベースラインが未取得の場合は変更前後の差分を比較できないため、以降の作業を開始できない仕組みとしています。

このサイトでは、ビルド時に変動するデータを参照していないため、同じソースからは常にバイト単位で同一の成果物が生成されます。そのため、変更前後のビルド成果物を比較することで、変更の影響範囲を機械的に特定できます。差分のないページは変更されていないと判断できるため、確認対象を差分が生じたページに限定できます。

当初は、変更前のビルド成果物を退避するnpmスクリプトを用意し、ユーザーが実行する形で運用をしていましたが、この方法ではベースラインの取得がユーザーに委ねられ、実行を忘れる可能性があります。実装開始後に取得漏れが判明しても、変更前の成果物は取得できません。git stashで変更前の状態に戻す方法もありますが、未コミットの変更を失うリスクがあるため採用しませんでした。

ベースラインの有無によって、実施が必要な動作確認チェックリストの項目数には約20倍の差があります。そのため、取得漏れを確実に防ぐ方法として、ユーザーによるスクリプトの実行に依存せず、フックによって作業開始前に検知する構成を採用しました。

フックの実装で気をつけたこと

先ほど紹介したベースライン取得のフックは以下の4点を考慮して実装しました。

1. 判定は短く終わらせる

退避済みの成果物がすでにあれば、それ以上の処理は不要です。

Terminal window
# 退避済みの成果物が既にあるなら判定不要。
[ -d "$PROJECT_DIR/<退避先のディレクトリ>" ] && exit 0
# 絶対パスはプロジェクト相対に直す。プロジェクト外の編集は対象外
REL=${FILE_PATH#"$PROJECT_DIR"/}
case "$REL" in
<記事コンテンツのディレクトリ>/*) exit 0 ;;
<ビルド対象のソース>) ;;
*) exit 0 ;;
esac

2. 記事コンテンツは対象外にする

記事コンテンツの置き場は機能開発の対象ではないため、記事を1文字直すたびに警告が発生するようでは実用的ではありません。重要な差分を見落とさないためにも、フックの対象範囲を絞ることが肝心です。

3. JSONの解析にjqを使わない

フックはツール入力をJSONで受け取るためjqで処理するのが手軽ですが、これは作業環境に依存してしまいます。そのため、このプロジェクトではAstroでの開発の前提となるNode.jsを利用してツール入力を解析しています。

4. 解析に失敗した場合の対処

JSONの解析に失敗した場合、通常はそのまま処理が通過してしまいますが、それではフックが壊れていることに誰も気づかないまま検知すべき操作が通り続けます。そこで、解析に失敗した際はユーザーに確認を求め、人間による判断を必須とすることで検知漏れを防ぐ設計にしました。

Terminal window
# 解析できなかった場合はユーザーに確認する
if [ "$RC" -ne 0 ]; then
decision "ask" "Hook could not parse the tool input. Manual confirmation is required."
exit 0
fi

CLAUDE.mdの作成とテスト方法の確立

権限設定とフックの整備をしたことで、想定外の動作を防ぐためのガードレールを構築できました。残る作業は、重要な情報を集約するCLAUDE.mdの作成とテスト・検証方法の確立です。

CLAUDE.mdに何を書くか

CLAUDE.mdは、Claude Codeがプロジェクトの概要や構成、ルールなどを把握するため、セッション開始ごとに自動で読み込まれるドキュメントです。一見すると、「必要な情報をすべてこのファイルに書き込んでしまえばよいのでは」と思うかもしれませんが、それでは不要な情報も毎回読み込んでしまいコンテキストを圧迫してしまいます。そのため、CLAUDE.mdに記載する内容はコードを読んだだけではわからない情報に絞りました。

書いたこと理由
ビルド時の注意事項設定ファイル(settings.json)からは読み取れない、ビルド時の注意点とその理由を補足するため
記事ファイルの格納場所ディレクトリを見ただけでは構成の意図がわからないため
動作確認チェックリストに関するルールチェックリスト原本に直接結果を記入してはいけないといった、コードからは読み取れない運用ルールを補足するため
ドキュメントの役割と参照先必要な情報を探す際のコンテキストの浪費を防ぐため

一方で、コードから把握できる情報や変更によって古くなりやすいディレクトリ構成、コンポーネントの説明は記載しませんでした。こうした情報は更新されないまま残りやすく、Claudeが古い情報を参照すると誤った判断につながる可能性があるため、CLAUDE.mdには継続的に管理できる情報のみを記載しています。

動作確認をスキルにする

CLAUDE.mdのほかにスキル(Skills)というものを用意しました。Claude Codeのスキルは、ユーザーがスラッシュコマンドで呼び出せるほか、Claude自身も必要に応じて呼び出せる手順書のようなもので、今回は動作確認に関するスキルを作成しました。

---
name: operation_check
description: 技術ブログの動作確認チェックリストを実行します。差分リグレッション(コード変更時)、フル実施(リリース前)、章指定、記事公開前チェック(記事ID)に対応します。
argument-hint: "[diff|full|<章番号>|<yyyymm_0x>]"
---

オプションによって実施範囲を切り替えることができる設計になっており、コード変更時は差分リグレッション、リリース前はフル実施といった形で運用できます。

また、冒頭で紹介した動作確認チェックリストを確認して実行するような手順になっており、項目ごとに設定されている「Claudeが自動で実行できる項目」、「人間が実行すべき項目」、「人間の確認が必要な項目」を判別してチェックを進めていきます。

チェックリストの原本と実行用ファイルを分ける

上記スキルを運用する際に工夫した点は、動作確認チェックリストの原本には結果を書かせないことです。

動作確認のたびに結果を原本へ書き込む場合、前回の結果が残った状態で次の確認を始めてしまう可能性があります。すると、チェック済みのマークが付いたリストを渡されたClaudeが未確認の項目まで確認済みと誤認するなど、確認結果の信頼性が損なわれてしまいます。このような事態を防ぐため、基本的に原本は編集せず、動作確認を実施する際は原本を複製して結果を記録する運用としました。

ただし、「原本を編集しない」を字句どおりに解釈すると新機能を実装した際に検証項目を追加できなくなってしまうため、このルールに例外として「機能を追加したときには検証項目を増やしてもいい」という文を加えました。このように、禁じたい行為の範囲を明確にすることで、必要以上にClaudeの行動を制限しないようにしています。


実際にClaude Codeを使って検索機能を実装する

ここからは、先ほど紹介したClaude Code開発環境を利用してサイト内検索機能を追加した際の内容について説明します。

採用した検索ライブラリ - Pagefind

サイト内検索を実装するにあたり、Pagefind・Fuse.js・MiniSearch・Oramaなどのライブラリを候補として検討しました。Astroで構築した静的サイトで利用することを前提に、日本語検索への対応や検索インデックスの配信方式、今後の記事数増加への対応などを比較しました。

候補特徴
Pagefind検索インデックスをチャンクに分割して必要な分だけ取得でき、日本語に対応している
Fuse.js全インデックスをクライアントにダウンロードする設計で、記事数の増加に弱い
MiniSearch軽量な全文検索ライブラリだが、日本語の対応には追加実装が必要
Orama専用トークナイザーで日本語に対応可能だが、バンドルサイズが大きい

Fuse.jsやMiniSearchでも日本語文字列の検索自体は可能ですが、日本語の全文検索では単語の分割位置を考慮しなければならず、追加実装や設定調整が必要になります。一方、Pagefindは日本語のセグメンテーションに対応しており、追加実装を抑えながら日本語の検索精度を確保できます。また、Astroで生成した静的サイトとの親和性が高く、今後の記事数増加にも対応しやすいため、Pagefindを採用しました。

引っかかった点・工夫した点

1. インデックスの生成にはAstroのビルドが必要

インデックスはnpx astro buildで生成されたHTMLファイルをもとに作成されます。そこで、package.jsonを以下のように変更し、ビルドコマンドをAstroのビルドとPagefindのインデックス生成を続けて実行する2段構成にしました

{
(略)
"scripts": {
"build": "npx astro build && npx pagefind --site blog"
},
(略)
}

前段がHTMLを生成し、後段がそのHTMLを読んでインデックスを生成するため、動作確認は必ず最新のビルド成果物に対して行う必要があります。また、ビルドを呼び出す箇所(CIの設定など)が複数ある場合、片方だけ修正するとインデックスが生成されない成果物ができてしまうため、ビルド手順を変える際は同じコマンドを呼ぶ側が他にないかを確認しておく必要があります。

2. 動的インポートの書き方

// 動かないVer
const pagefind = await import(`${BASE}/pagefind/pagefind.js`);
// 動くVer
const entry = BASE + "/pagefind/pagefind.js";
const pagefind = await import(/* @vite-ignore */ entry);

import()にテンプレートリテラルを直接渡すと、Viteがその呼び出しをモジュール先読み用のヘルパーで包みます。このとき、本来はビルド時に置換されるはずのプレースホルダーが置換されないまま出力されます。その結果、ビルドは成功するのに、検索を実行した時点で初めてエラーが発生します。ビルド成功を確認しただけでは検知できない問題であったため、動作確認チェックリストに「ビルド後の成果物に対して未置換のプレースホルダーが残っていないこと」を機械的に確認する項目を追加しました。

3. 差分リグレッションで全ページが差分になった

今回の機能追加では、新たに検索ページを1ページ追加しただけで既存のページには変更を加えていません。したがって、ビルドの前後で発生する差分は追加した検索ページの分だけだと見込んでいましたが、実際に差分リグレッションを行って比較すると数百のページが「差分あり」と判定されました。

原因は、この技術ブログで採用しているCSSフレームワークのTailwind CSSが、すべてのページで使用されているスタイルを1つのCSSバンドルとして生成する仕組みにありました。この仕組みにより、検索ページで新しいユーティリティクラスを1つ使うとそのクラスがバンドルに追加され、バンドルのファイル名に含まれるハッシュが変化します。その結果、全ページの<link>タグが書き換わり、全ページが差分として検出されてしまいました。

つまり、実質的には検索ページを追加しただけですが、機械的な差分は全ページに出るということです。この性質を知らずに「差分は1ページ分である」という前提で確認していたため、差分の件数を見ただけで「何か大変なことが起きた」と誤解してしまいました。

対応策として、ファイル名に含まれるハッシュ部分を正規化したうえで比較する手順に切り替えたことで、実質的な差分は検索ページのみであることを確認できるようになりました。全ページが差分として検出される原因を推測ではなく実測で確かめられたのは、変更前のビルド成果物をあらかじめ取得していたためです。このとき、ベースラインの取り忘れを検知するフックを作っておいてよかったと実感しました。


おわりに

この記事では、Claude Codeで技術ブログを改修した過程を紹介しました。

Claude Codeの土台作りとして実施したことは主に以下の4つです。

  • 権限設定で実行してよいコマンドの範囲を宣言
  • フックで守るべき順序を強制
  • CLAUDE.mdとスキルで判断基準と手順の作成
  • 動作確認チェックリストで検証の手段を用意

今回実装した内容はどれも特別なことではなく、インターネット等で調べればすぐに得られる設定内容です。しかし、いざ取り掛かってみると「技術ブログに合わせたClaude Code環境とはどのようなものか」、「どうすればそれが実現できるか」という土台作りのほうが検索機能の実装より時間がかかりました。
入社したばかりの私にとっては、Claudeに何を任せるかを決める作業がそのままリポジトリの仕組みを理解する作業になっており、土台作りに費やした時間のほとんどはサイトそのものを学ぶ時間でもありました。Claude Code環境の構築には時間がかかる一方、今回の成果物やClaude Code環境は別のプロジェクトで再利用ができるという利点があります。特に権限設定とフックによるガードレール設定に関してはその特徴が顕著です。

この記事がこれからClaude Codeを導入する方や既に運用している方の参考になれば幸いです。

参考

記事の執筆にあたっては情報の正確性に努めておりますが、掲載されている文章やソースコード、設定ファイル等の内容について、完全な正確性や安全性を保証するものではありません。活用される際は、必ず公式ドキュメント等をご自身で確認のうえご判断ください。

▲
TOP
アルファロゴ株式会社アルファシステムズは、ITサービス事業を展開しています。このブログでは、技術的な取り組みを紹介しています。X(旧Twitter)で更新通知をしています。
X @techblogalpha