Claude CodeでRoblox Rojoを検証する — LuauをSeleneとStyLuaで見張る
Rojoでスクリプトをファイル化し、Claude Codeが編集するたびにSeleneとStyLuaを走らせる設定を手順でまとめます。PostToolUse hookとrojo buildの使い方までです。
RobloxのLuauをClaude Codeに書かせるなら、スクリプトをファイルとして持つ構成にするのが近道です。Studio内に閉じたスクリプトはClaude Codeのファイル編集ツールから触れませんが、Rojoでファイルに出せば、編集のたびにリンターとフォーマッターを自動で走らせられます。
この記事の構成は、Rojoのプロジェクトファイル、Selene(静的解析)、StyLua(フォーマッター)、そしてそれらを呼ぶClaude Codeのhookの4つです。Studioを直接操作させたい場合の接続はClaudeでRoblox Studio MCPを使う手順が扱っています。ここでは、Studioを開かずに回せるローカルの検証ループを作ります。
Rojoでスクリプトをファイルにする理由
Rojoはファイルシステム上のファイルをRobloxのインスタンスに変換するツールです。公式の導入ページは、ファイルに出す利点として外部エディターの利用、Gitによるバージョン管理、SeleneやStyLuaといった既存ツールの利用を挙げています。
Claude Codeにとっての利点は、検証の入り口ができることです。Claude Codeはファイルを読み書きし、コマンドを実行するツールです。スクリプトがファイルなら、Edit や Write の直後にhookでコマンドを呼べます。逆に、スクリプトがStudio内にしかない状態では、この仕掛けを置く場所がありません。
導入ページには、Rojoと組み合わせて使う代表的なツールも載っています。
- Selene: Lua向けの静的解析ツール
- StyLua: コードスタイルをそろえるフォーマッター
- Wally: Robloxプロジェクト向けのパッケージマネージャー
- Moonwave: Lua向けのドキュメント生成ツール
同じページはVS Code向けにluau-lspの拡張も挙げ、Selene拡張は「luau-lspの解析と重なることが多い」と注記しています。
ファイルの置き方とインスタンスの対応
Rojoがファイル名の規則でインスタンスの種類を決めるため、Claude Codeに新しいスクリプトを作らせるときは名前の付け方が重要になります。同期の仕様ページにある対応を抜粋します。
| ファイル | 変換先 |
|---|---|
*.server.lua | 変換先Script |
*.client.lua | 変換先LocalScript |
*.lua | 変換先ModuleScript |
| ディレクトリ | 変換先Folder |
*.model.json | 変換先手書きの簡易モデル |
*.meta.json | 変換先追加のプロパティ設定 |
init.lua や init.server.lua を置くと、親ディレクトリがFolderではなくそのスクリプトになります。同じフォルダーに置ける初期化スクリプトは1つだけです。
仕様ページの表記は .lua ですが、Rojoの変更履歴には、7.4.0で rojo init が生成するファイルを *.luau に変えたという記載があります。自分のプロジェクトで使う拡張子は、rojo init が作ったファイルに合わせるのが安全です。
落とし穴
Claude Codeが作ったファイル名でスクリプトの種類が変わる
PlayerData.lua と PlayerData.server.lua は、Rojoにとって別の種類のインスタンスです。「サーバー側の処理を追加して」とだけ頼むと、拡張子の選び方が曖昧なまま作られる恐れがあります。下で書くCLAUDE.mdに、この対応を明記しておくと防げます。
プロジェクトファイルの最小構成
Rojoのプロジェクトは .project.json で終わるJSONファイルです。必須なのは name と tree の2つです。ゲーム用に、ReplicatedStorageとStarterPlayerScriptsへディレクトリを割り当てる例を示します。公式のサンプルに沿った形です。
{
"name": "MyGame",
"globIgnorePaths": ["**/*.spec.lua"],
"tree": {
"$className": "DataModel",
"ReplicatedStorage": {
"$className": "ReplicatedStorage",
"$path": "src/ReplicatedStorage"
},
"StarterPlayer": {
"$className": "StarterPlayer",
"StarterPlayerScripts": {
"$className": "StarterPlayerScripts",
"$path": "src/StarterPlayerScripts"
}
}
}
}$path はプロジェクトファイルのあるフォルダーからの相対パスです。globIgnorePaths に書いたパターンに合うファイルは同期の対象から外れ、上の例ではテストファイルがゲームに混ざりません。servePort の既定値は34872です。
このファイルはClaude Codeに書かせる対象であると同時に、検証される側でもあります。$path の綴りを1文字誤っても、Lua側のリンターは何も言いません。その確認は次のビルドが担います。
SeleneをRobloxに合わせる
Seleneは設定なしでRobloxのコードにかけると、game が未定義だというエラーを大量に出します。公式ガイドによれば、既定の標準ライブラリはLua 5.1で、Roblox用の定義を selene.toml で選ぶ必要があります。
std = "roblox"この設定にしておけば、初回の実行時にRoblox用の標準ライブラリが自動生成されます。定義は6時間ごとに自動更新され、すぐ更新したいときは selene update-roblox-std を実行します。
生成には接続が必要です。ネットワークに出せない環境では、roblox-std-source = "pinned" を足すと roblox.yml としてプロジェクト内に固定でき、このファイルは selene generate-roblox-std でも作れます。Claude Codeを権限の絞った実行環境で動かす場合は、先にこのファイルを生成してコミットしておく運用が合います。
CLIの終了コードにも注意が要ります。--allow-warnings は「警告だけなら成功にする」フラグで、付けなければ警告でも失敗扱いです。hookでは付けず、警告も修正対象にするのが素直です。
StyLuaで書式を機械に任せる
フォーマットの好みをClaude Codeに説明するより、StyLuaの設定を置いてしまうほうが確実です。設定は stylua.toml か .stylua.toml に書き、プロジェクトのルートに置けば共同作業者も同じ設定を使えます。
Luauの構文を使うなら、.stylua.toml の syntax で構文を指定します(READMEの例は syntax = "Lua52" で、Luauを使うプロジェクトではLuauの値を選びます)。StyLuaのREADMEは、Lua 5.2のgoto用ラベル(::label::)とLuauの型アサーション(x :: number)が衝突し、後者が優先されると説明しています。
どうしても整形させたくない箇所は、直前に -- stylua: ignore というコメントを置くと除外できます。Claude Codeが整形を巻き戻そうとして往復するときの逃げ道になりますが、使う場所は表形式のデータなど最小限に絞ってください。
hookで使うのは --check です。ファイルを書き換えず、整形が必要かだけを判定します。
stylua --check src/
selene src/--check は書き込みをしないため、Claude Codeが編集した直後のファイルをhookが勝手に書き換える競合を避けられます。直すのはClaude Code自身に任せ、検証だけをhookに担当させる分担です。
Claude Codeのhookにつなぐ
hookはClaude Codeのツール呼び出しの前後でコマンドを走らせる仕組みです。PostToolUse はツールが成功した直後に動き、標準入力にイベントのJSONが渡されます。Edit と Write では、tool_input.file_path に編集したファイルの絶対パスが入ります。
PostToolUse はツールがすでに実行されたあとなのでブロックはできませんが、終了コード2で終わるとstderrがClaudeに伝わります。リンターの出力を返して直させるにはこれで足ります。
.claude/hooks/check-luau.sh を次のように作ります。
#!/usr/bin/env bash
file=$(jq -r '.tool_input.file_path // empty')
case "$file" in
*.lua|*.luau) ;;
*) exit 0 ;;
esac
out=$(stylua --check "$file" 2>&1) || {
echo "StyLua: 整形が必要です" >&2
echo "$out" >&2
exit 2
}
out=$(selene "$file" 2>&1) || {
echo "$out" >&2
exit 2
}設定は .claude/settings.json に置きます。matcherで Edit|Write に絞ります。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/check-luau.sh"
}
]
}
]
}
}スクリプトには実行権限が必要です。chmod +x を忘れると、hookが動かないまま気づかない恐れがあります。
Stopの時点でテストを強制する書き方はClaude Codeのagent hookで「テストが通るまで止めない」を検証させるに、同じ型の PostToolUse 検証をGitHub Actionsのworkflowに使う例はClaude Codeのactionlint活用にあります。仕掛けは同じで、呼ぶ検証コマンドだけが変わります。
rojo buildを完了条件にする
Seleneはファイル単位の検査なので、プロジェクト全体の構成までは見ません。そこでセッションの最後に、Rojoで実際にplaceファイルを書き出させます。公式の手順では、プロジェクトのフォルダーで次を実行します。
rojo build -o build.rbxlx出力名を build.rbxl にすると、XMLではなくバイナリ形式のplaceファイルが作られます。ビルドが通ることを完了の条件にすれば、$path の誤記や、プロジェクトファイルの構造の崩れを、Studioを開く前に拾える見込みが高まります。
CLAUDE.mdには、次のように条件を書いておきます。
## Roblox / Rojo
- スクリプトは src/ 以下に置く。サーバー側は *.server.lua、クライアント側は *.client.lua、共有モジュールは *.lua
- 編集後は stylua --check src/ と selene src/ を通す(hook でも実行される)
- 作業の最後に rojo build -o build.rbxlx を実行し、失敗したら直す
- default.project.json の tree を変えるときは、変更理由を先に説明するhookは編集のたびに動く下限の検証で、CLAUDE.mdは判断を含む運用ルールです。両方を置くと、Claude Codeが検証を飛ばしにくくなります。
このループで確認できないこと
この構成が見るのは、構文、スタイル、静的に分かる誤りと、ファイル構成のビルド可否です。ゲームとして動くかどうかは別の話です。
- Rojoのライブ同期には限界があります。公式の仕様ページは、Terrainなどのバイナリデータ、
MeshPart.MeshId、HttpService.HttpEnabledをリアルタイムでは同期できない例に挙げています。そうしたプロパティは、placeファイルを生成して開く運用になります - 実行時の挙動は、Studioでの再生かStudio MCP経由のテストで確かめます。その接続はClaudeでRoblox Studio MCPを使う手順に任せます
- Seleneの標準ライブラリは、Robloxの最新APIに追従するまでに最長6時間の遅れがありえます。新しいAPIを使った直後の誤検知は、
selene update-roblox-stdで解消するかを先に試します
まとめ
Rojoでスクリプトをファイルにし、PostToolUse hookでStyLuaとSeleneを走らせ、最後に rojo build で全体を通す。この3段で、Claude Codeが書いたLuauの崩れを、Studioを開く前にかなり潰せます。
最初に用意するのは、Seleneの std = "roblox"、StyLuaの設定ファイル、CLAUDE.mdの命名規則の3つです。hookはそのあとでかまいません。順番を逆にすると、設定がない状態でリンターが警告を出し続け、Claude Codeが本題でない修正に時間を使う原因になります。