summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSho Sakuma <me@m1sk9.dev>2026-09-28 21:35:52 +0900
committerSho Sakuma <me@m1sk9.dev>2026-09-28 21:35:52 +0900
commit3f47dfe08bf3a2407bbfd5d5bb3ab17ce96cda70 (patch)
tree029d82b2642a40eeba9fb8f6ffd72e9e3fa87020
parent9bb1623c32c80da097f423c1ae3c96aa11372a43 (diff)
downloadLunaticChat-3f47dfe08bf3a2407bbfd5d5bb3ab17ce96cda70.tar.gz
LunaticChat-3f47dfe08bf3a2407bbfd5d5bb3ab17ce96cda70.tar.bz2
LunaticChat-3f47dfe08bf3a2407bbfd5d5bb3ab17ce96cda70.zip
feat(claude): add release-lc, create-release-note, test-integration and correction-docs skills
release-lc replaces the release skill and carries a release through to a published GitHub Release, a listed Modrinth version and live release notes, since a pushed tag alone left drafts and broken links unnoticed. The other skills cover the steps around it: writing the site release notes from CHANGELOG.md, checking that the Paper and Velocity builds of the current tree still handshake, and auditing the docs site against the source. Co-Authored-By: Claude <noreply@anthropic.com>
-rw-r--r--.claude/skills/correction-docs/SKILL.md127
-rw-r--r--.claude/skills/create-release-note/SKILL.md101
-rw-r--r--.claude/skills/release-lc/SKILL.md179
-rw-r--r--.claude/skills/release/SKILL.md70
-rw-r--r--.claude/skills/test-integration/SKILL.md139
5 files changed, 546 insertions, 70 deletions
diff --git a/.claude/skills/correction-docs/SKILL.md b/.claude/skills/correction-docs/SKILL.md
new file mode 100644
index 0000000..8e5da7a
--- /dev/null
+++ b/.claude/skills/correction-docs/SKILL.md
@@ -0,0 +1,127 @@
+---
+description: ドキュメントサイト (website/) 全体を検査し,誤字・表記揺れ・リンク切れ・英日の抜け・ソースコードとの食い違いを洗い出して修正する.
+disable-model-invocation: true
+argument-hint: [path ...]
+allowed-tools: Bash(bun *), Bash(curl *), Bash(git *), Bash(gh *), Bash(jq *), Read, Grep, Glob, Edit, Write, Agent
+---
+
+# Correction Docs
+
+引数: `$ARGUMENTS`
+
+引数でパス (例: `website/src/docs/features`) が与えられた場合はその配下だけを対象にする.無ければ `website/` 全体が対象.
+
+ドキュメントの正しさの基準は**ソースコード**である.ドキュメント同士が一致していても,コードと食い違っていれば誤りとして扱う.
+
+## 対象
+
+| 対象 | パス |
+|------|------|
+| 英語ページ | `website/src/**/*.md` (`ja/` と `assets/` を除く) |
+| 日本語ページ | `website/src/ja/**/*.md` |
+| ナビゲーション・サイドバー | `website/.vitepress/config/en.ts`,`ja.ts`,`config.mts` |
+| コンポーネントの表示文言 | `website/.vitepress/theme/components/*.vue` |
+
+`website/src/assets/changelog-default.md` はテンプレートなので内容検査の対象外 (公開されない).
+
+## 1. 機械的な検査
+
+`website/` で実行する:
+
+```
+bun install
+bun run format:check
+bun run lint
+bun run test
+bun run build
+```
+
+`bun run build` は内部リンク切れで失敗する.エラーがあれば記録する (修正は §4 でまとめて行う).
+
+## 2. 検査の観点
+
+以下の観点で全ページを読む.ページ数が多いので,観点ごとに `Agent` (subagent_type: `Explore` など読み取り専用のもの) へ並列に委譲してよい.委譲する場合は各エージェントに「修正はせず,`ファイル:行` と誤り・根拠・修正案を列挙して返す」ことを指示する.目安は次の 4 分担:
+
+1. リファレンス (`docs/reference/`,`docs/permissions.md`,`docs/configuration.md`) とソースの照合
+2. 機能ガイド・導入 (`docs/features/`,`docs/getting-started.md`,`download.md`,`index.md`) とソースの照合
+3. 開発者向け (`docs/developers/`) とソースの照合
+4. 英日の対応と文章品質 (全ページ)
+
+### 2-1. ソースコードとの照合
+
+| ドキュメントの記述 | 照合先 |
+|------------------|--------|
+| コマンド名・エイリアス・引数・説明 | `platform-paper/.../command/impl/` の `@Command` と `LunaticSubCommand` の `literal` / `aliases`,Velocity 側のコマンド実装 |
+| 権限ノード | `engine/.../permission/LunaticChatPermissionNode.kt`,各コマンドの `@Permission` / `permissionNode` |
+| 設定キー・既定値・説明 | `platform-paper/src/main/resources/config.yml` と `platform-paper/.../config/` の設定クラス |
+| プレイヤー設定 | `engine/.../settings/` と `platform-paper/.../command/setting/` |
+| メッセージ書式・表示文言 | `platform-paper/src/main/resources/languages/{en,ja}.yml`,`i18n/` |
+| デバッグカテゴリ・環境変数 | `engine/.../debug/DebugCategory.kt`,Velocity 側の `debug/VelocityDebugSwitch.kt` |
+| 対応バージョン | `platform-paper/build.gradle.kts` の `paper-api`,`platform-velocity/build.gradle.kts` の `velocity-api`,`platform-paper/src/main/resources/paper-plugin.yml` の `api-version` |
+| プロトコル・互換性 | `engine/.../protocol/ProtocolVersion.kt` と `PluginMessage` / `PluginMessageCodec` |
+| モジュール構成・パッケージ・設計 (開発者向け) | 実際のディレクトリ構成と各モジュールの `build.gradle.kts`.CLAUDE.md の記述のとおり,開発者向けドキュメントはリファクタリングに追従できていない箇所がある (例: `converter` の所属,`ConfigManager` の読み込み方式) |
+
+ソース上に存在するのにドキュメントに無いもの (新しいコマンド・設定キー・権限) は**抜け**として扱う.
+
+### 2-2. 更新履歴
+
+- `CHANGELOG.md` にある v1.3.0 以降の各リリースについて,`website/src/changelog/` と `website/src/ja/changelog/` にページがあり,サイドバーに登録されていること
+- 各ページの Download リンクが,実在するリリースタグを指していること.v1.5.0 以降の規則は Paper が `vX.Y.Z`,Velocity が `velocity/vX.Y.Z`.それより前は同時リリースの `vX.Y.Z` に両方が入っている場合がある (例: Velocity v1.3.0 は `v1.4.0` の Release).実在するタグは `gh release list --limit 50` で確かめる
+- リリースノートの内容が `CHANGELOG.md` と矛盾しないこと (書き換え・要約は正常.事実の食い違いだけを指摘する)
+- 公開済みのリリースノートの**内容の書き換え**は,事実誤認・リンク切れ・誤字の修正に留める
+
+### 2-3. 英日の対応
+
+- すべての英語ページに対応する日本語ページがあり,逆も同様であること
+- 見出しの数と階層,表の行,コードブロック,`:::` コンテナ (tip / warning) の数が対応していること.片方にしか無い段落や項目は抜けとして扱う
+- `en.ts` と `ja.ts` の nav / sidebar が同じ構造であること
+- 日本語ページ内のリンクは `/ja/...` を指していること (英語ページへ飛ばない).英語ページも同様に `/ja/` を指さないこと
+- アンカー付きリンクは,リンク先ページに該当見出しが実在すること (日本語の見出しは日本語のままアンカーになる)
+
+### 2-4. 文章品質
+
+英語:
+
+- 綴り・文法・時制の誤り,主語と動詞の不一致,冠詞の欠落
+- 用語の揺れ (例: `direct message` / `DM`,`channel chat` / `channel`) は,同じ概念に同じ語を使っているかで判断する
+
+日本語:
+
+- 句読点は `,` `.`,括弧は半角 `()`.`、` `。` `()` を検出したら置き換える
+- 文末の句点の欠落,ですます調と常体の混在
+- 誤字・脱字・送り仮名 (例: 「有効する」→「有効にする」)
+- 表記揺れ (例: 「フォルダー」/「フォルダ」,「サーバー」/「サーバ」).サイト内で多数派の表記に揃える
+- 技術用語・固有名詞は英語のまま (Paper,Velocity,JAR など)
+
+### 2-5. 外部リンク
+
+全ページとコンポーネントから `https?://` のリンクを抜き出し,重複を除いて `curl -s -o /dev/null -w '%{http_code}' -L {url}` で確かめる.`200` 以外を記録する.レート制限 (GitHub の `429` など) は失敗扱いにせず,時間をおいて再確認する.
+
+## 3. 指摘の整理
+
+指摘を次の 2 種類に分ける.
+
+| 種類 | 例 | 扱い |
+|------|-----|------|
+| **自明な修正** | 誤字,句読点,リンク切れ (正しいリンク先が一意に決まる),英日の対応でもう一方から明らかに補える抜け,ソースと一字一句で照合できる値 (既定値・権限ノード名など) | そのまま修正する |
+| **判断を要する修正** | 説明の書き直し,節の追加・削除,ページ構成の変更,正しい記述がソースから一意に決まらないもの,公開済みリリースノートの内容変更 | 一覧にしてユーザーの承認を得てから修正する |
+
+判断を要する修正は,`ファイル:行`,現状,根拠 (ソースの `ファイル:行`),修正案 をまとめて一度に提示する.
+
+## 4. 修正
+
+- 既存ページの書きぶり (見出しの付け方,表の形,`:::` の使い方) に合わせる
+- 英語版を直したら日本語版も,日本語版を直したら英語版も対応箇所を直す
+- 開発者向けドキュメントでモジュール境界の記述を直す場合は,CLAUDE.md の記述とも矛盾しないようにする
+- サイドバーを変更した場合は `en.ts` と `ja.ts` の両方を直す
+
+修正後,§1 のコマンドをもう一度すべて実行し,通ることを確認する.
+
+## 5. 報告
+
+- 修正した内容 (ファイルごと,観点ごとの件数と主な例)
+- 承認待ちの「判断を要する修正」の一覧 (未承認のもの)
+- 外部リンクで修正できなかったもの (リンク先が消えていて代替が無い,など)
+- §1 のコマンドの最終結果
+
+コミットはユーザーに確認してから行う.メッセージ例: `docs: fix typos and stale references across the documentation site`
diff --git a/.claude/skills/create-release-note/SKILL.md b/.claude/skills/create-release-note/SKILL.md
new file mode 100644
index 0000000..97e186d
--- /dev/null
+++ b/.claude/skills/create-release-note/SKILL.md
@@ -0,0 +1,101 @@
+---
+description: CHANGELOG.md の未公開エントリから,ドキュメントサイトの英語・日本語リリースノート (website/src/changelog/, website/src/ja/changelog/) を作成する.
+disable-model-invocation: true
+argument-hint: [paper|velocity|both] [version]
+allowed-tools: Bash(git *), Bash(gh *), Bash(bun *), Bash(curl *), Read, Grep, Glob, Edit, Write
+---
+
+# Create Release Note
+
+引数: `$ARGUMENTS`
+
+`CHANGELOG.md` は開発者向けの網羅的な変更記録,サイトのリリースノートは利用者 (サーバー管理者) 向けの要約である.このスキルは前者を後者へ書き直す.逐語訳・丸写しはしない.
+
+## 1. 対象バージョンの決定
+
+1. `CHANGELOG.md` を読む.見出しはリリースタグと 1 対 1 に対応する:
+
+ | 見出し | リリース | タグ |
+ |--------|---------|------|
+ | `### vX.Y.Z` | Paper / Folia 版 | `vX.Y.Z` |
+ | その直下の `#### Velocity: vA.B.C` | 同時に出す Velocity 版 | `velocity/vA.B.C` |
+ | `### Velocity: vA.B.C` | Velocity 版の単独リリース | `velocity/vA.B.C` |
+
+ v1.4.0 以前の見出しには旧規則のもの (Velocity のためだけに作った `### vX.Y.Z` など) があるが,新しいページの対象にはならないので気にしなくてよい
+2. `gradle.properties` の `paperVersion` / `velocityVersion` と突き合わせる.CHANGELOG の先頭のエントリと一致しない場合はユーザーに報告して止まる
+3. 引数があればそれに従う.無ければ,先頭のエントリのうち `website/src/changelog/{paper,velocity}/v{version}.md` がまだ存在しないものを対象にする
+4. 対象のページが既に存在する場合は上書きせず,差分の追記か作り直しかをユーザーに確認する
+
+## 2. リンク先
+
+Download リンクはバージョン番号だけで決まる:
+
+| | GitHub | Modrinth |
+|--|--------|----------|
+| Paper | `https://github.com/m1sk9/LunaticChat/releases/tag/vX.Y.Z` | `https://modrinth.com/plugin/lunaticchat/version/X.Y.Z` |
+| Velocity | `https://github.com/m1sk9/LunaticChat/releases/tag/velocity/vA.B.C` | `https://modrinth.com/plugin/lunaticchat/version/A.B.C-velocity` |
+
+## 3. 本文の書き起こし
+
+テンプレートは `website/src/assets/changelog-default.md`.既存ページ (`website/src/changelog/paper/v1.3.0.md` など) の書きぶりに揃える.
+
+### 振り分け
+
+CHANGELOG の各項目を次の見出しに振り分ける.項目の無い見出しは削除する.
+
+| 見出し | 入れるもの |
+|--------|-----------|
+| `## New Features` | 新機能,新コマンド,新しいプラットフォームへの対応 (サポート打ち切りはここに子項目として併記),プロトコルバージョンの引き上げ |
+| `## Improvements` | 性能改善,堅牢性の向上など,利用者の操作が変わらない改善 |
+| `## Changes` | 挙動や既定値の変更で,利用者が意識すべきもの |
+| `## Bug Fixes` | 不具合修正.`Fixed ...` で始める |
+| `## Notes` | 更新時の注意 (デプロイ順序,手動作業,互換性の注意) |
+
+### 書き換えの原則
+
+- 利用者に見えない変更は落とすか一行にまとめる.テスト数の増減,内部リファクタリング,クラス名・例外名だけで説明している項目は `Performed internal cleanup.` に畳む
+- 原因の実装詳細 (例: 「`CancellationException` として報告していた」) は書かず,利用者から見えた症状と直った結果を書く
+- 設定キーは `` `features.xxx.yyy` `` のようにコード表記のまま残す
+- 項目が長い場合は親項目に結論,子項目に補足を置く (既存ページのインデントは 2 スペース)
+- プロトコルバージョンの変更は Paper・Velocity 両方のページに書く.PATCH なら「どちらを先に更新してもよい」,MINOR なら「Velocity を先に更新する」ことを必ず添える (`CLAUDE.md` の Protocol Version 節)
+- `#### Velocity:` 以下の項目は Velocity ページへ,それ以外は Paper ページへ.両方に関わる項目 (engine の変更,プロトコル) は両方へ
+- ドキュメントの該当ページへのリンクは積極的に張る.リンクは英語版は `/docs/...`,日本語版は `/ja/docs/...` の絶対パスで書き,アンカーは実在する見出しから作る (VitePress の slug 規則: 小文字化,空白は `-`,記号除去.日本語見出しはそのまま).張る前に `Grep` で見出しの存在を確かめる
+
+### 日本語版
+
+- 英語版の直訳ではなく,同じ内容を自然な日本語で書く
+- 句読点は `,` `.`,括弧は半角 `()` を使う (`、` `。` `()` は使わない)
+- 見出し (`## New Features` など) と `- Download:` は英語のまま残す (既存ページに合わせる)
+- 文末は「〜しました.」「〜できるようになりました.」のですます調で統一する
+- 技術用語・固有名詞は英語のまま (Velocity,Paper,JAR など)
+
+## 4. サイドバーへの登録
+
+`website/.vitepress/config/en.ts` と `website/.vitepress/config/ja.ts` の `'/changelog/'` / `'/ja/changelog/'` サイドバーに新ページを追加する.
+
+- 対象プラットフォーム (`Paper` / `Velocity`) の該当メジャー (`v1` など) グループの**先頭**に入れる (新しい順)
+- 新しいメジャーバージョンなら `text: 'v2'` のグループを先頭に作る
+- en と ja で同じ位置に入れる
+
+## 5. 検証
+
+`website/` で次を実行し,すべて通ることを確認する.
+
+```
+bun run check
+bun run build
+```
+
+`bun run build` は内部リンク切れで失敗するので,アンカーの誤りもここで検出される.失敗した場合は原因を直して再実行する.
+
+外部リンク (GitHub / Modrinth) はリリース前には 404 になるのが正常なので,URL の形だけを §2 の表と照合する.
+
+Velocity 単独のリリースで CHANGELOG に `### Velocity: vA.B.C` の見出しが無く,Paper の見出しの下に書かれている場合は,見出しの修正を提案する.
+
+## 6. 報告
+
+- 作成・変更したファイルの一覧
+- CHANGELOG から落とした項目・畳んだ項目とその理由
+- 判断に迷った振り分け (ユーザーが見直すべき箇所)
+
+コミットはユーザーに確認してから行う.メッセージ例: `docs(changelog): add release notes for paper v1.5.0 and velocity v1.4.0`
diff --git a/.claude/skills/release-lc/SKILL.md b/.claude/skills/release-lc/SKILL.md
new file mode 100644
index 0000000..7a2d21c
--- /dev/null
+++ b/.claude/skills/release-lc/SKILL.md
@@ -0,0 +1,179 @@
+---
+description: CHANGELOG.md を基にリリース対象を決め,リリース前チェック,署名付きタグの作成と push,ワークフロー・GitHub Release・Modrinth・ドキュメントサイトへのロールアウト完了までを監視する.
+disable-model-invocation: true
+argument-hint: [paper|velocity|both]
+allowed-tools: Bash(./gradlew *), Bash(git *), Bash(gh *), Bash(curl *), Bash(jq *), Bash(javap *), Bash(shasum *), Read, Grep, Glob
+---
+
+# Release LunaticChat
+
+引数: `$ARGUMENTS`
+
+リリースは「タグを push したら終わり」ではない.**GitHub Release が公開され,Modrinth に listed で並び,リリースノートのリンク先が生きている**ところまで確認して完了とする.
+
+副作用のある操作 (タグ作成・push・draft の公開・ワークフロー再実行) は,必ず直前にユーザーの確認を得ること.
+
+## 1. リリース対象の決定
+
+1. `gradle.properties` から `paperVersion` / `velocityVersion` を読む
+2. `CHANGELOG.md` の先頭エントリを読む.見出しがそのままタグ名になる (`### vX.Y.Z` → `vX.Y.Z`,`#### Velocity: vA.B.C` / `### Velocity: vA.B.C` → `velocity/vA.B.C`)
+3. 既存リリースを確認する:
+ ```
+ gh release list --limit 20
+ git tag --list 'v*' 'velocity/v*' --sort=-creatordate
+ ```
+4. 未リリースのバージョンから対象を決める.1 タグ = 1 プラットフォームで,両方を出す場合はタグを 2 つ作る.引数が与えられていればそれを優先し,推定と食い違えば指摘する:
+
+ | プラットフォーム | タグ | ワークフロー | タグメッセージ |
+ |-----------------|------|-------------|---------------|
+ | Paper | `v{paperVersion}` | `release-paper.yaml` (`Release Paper`) | `Paper v{paperVersion}` |
+ | Velocity | `velocity/v{velocityVersion}` | `release-velocity.yaml` (`Release Velocity`) | `Velocity v{velocityVersion}` |
+
+ - `both` は上の 2 行を両方実行する.片側が既にリリース済みのバージョンなら,その側はリリースしない (Modrinth で同一バージョンの重複公開になる)
+ - Paper を `paper/vX.Y.Z` でタグ付けしてはならない.どちらのワークフローも起動せず,配布済みの `UpdateChecker` も認識できない
+5. 決定内容 (形態・タグ名・両バージョン) をユーザーに提示する
+
+## 2. リリース前チェック
+
+すべて通るまでタグの提案に進まない.失敗したら原因を報告し,直せるものは修正方針を提案する.
+
+### リポジトリ状態
+
+- `main` ブランチにいて,working tree がクリーンであること
+- `git fetch origin` 後,`HEAD` が `origin/main` と一致すること (未 push のコミットをタグ付けしない)
+- タグがローカル・リモートの両方に存在しないこと (`git ls-remote --tags origin {tag}`)
+- `HEAD` の CI (`CI` ワークフロー) が成功していること:
+ ```
+ gh run list --workflow ci.yaml --commit $(git rev-parse HEAD) --json status,conclusion,url
+ ```
+
+### ビルドとテスト
+
+```
+./gradlew ktlintCheck
+./gradlew test
+./gradlew :platform-paper:shadowJar :platform-velocity:shadowJar
+```
+
+対象外のプラットフォームも含めて両方ビルドする (engine の変更がもう一方を壊していないことの確認を兼ねる).
+
+### プロトコル
+
+前回リリースのタグ (対象プラットフォームごと) からの差分に `engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/protocol/` の変更があるか確認する:
+
+```
+git diff --stat {前回タグ}..HEAD -- engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/protocol/
+```
+
+変更がある場合:
+
+- `ProtocolVersion.kt` が CLAUDE.md の規則どおりに上がっていること (変更の種類に対して PATCH / MINOR / MAJOR が妥当か)
+- `ProtocolBackwardCompatibilityTest` にスナップショットが追加されていること
+- `/test-integration` の実行を推奨する (未実行ならユーザーに確認する)
+- MINOR なら Velocity を先に出す必要がある.`paper` 単独リリースで Velocity 側が未対応なら止める
+
+### リリースノート
+
+- `CHANGELOG.md` に対象バージョンのエントリがあること
+- `website/src/changelog/{paper,velocity}/v{version}.md` と `website/src/ja/changelog/...` が対象の全プラットフォーム分あり,`config/en.ts` / `config/ja.ts` のサイドバーに登録されていること.無ければ `/create-release-note` を先に実行するよう伝えて止まる
+- ページの Download リンクのタグが §1 の表と一致すること (Paper は `releases/tag/v{paperVersion}`,Velocity は `releases/tag/velocity/v{velocityVersion}`)
+- **デプロイ済み**であること.Release と Modrinth の changelog はサイトを指すため,未デプロイだとリンク切れで公開される:
+ ```
+ curl -s -o /dev/null -w '%{http_code}\n' https://lc.m1sk9.dev/changelog/paper/v{paperVersion}
+ curl -s -o /dev/null -w '%{http_code}\n' https://lc.m1sk9.dev/ja/changelog/paper/v{paperVersion}
+ ```
+ サイトは `main` への push 時に CI の `build_docs` ジョブが Cloudflare Workers へデプロイする.404 なら CI の完了を待つ
+
+### Modrinth のメタデータ
+
+対象ワークフローの `MODRINTH_*_GAME_VERSIONS` / `MODRINTH_MC_VERSION` が,`platform-paper/build.gradle.kts` の `paper-api` (と `platform-velocity/build.gradle.kts` の `velocity-api`) が示す Minecraft バージョンと食い違っていないか確認する.Paper の対応バージョンを上げたリリースで更新漏れが起きやすい.
+
+## 3. タグの作成と push
+
+チェック結果の要約を示し,ユーザーの確認を得てから実行する.既存タグと同じく署名付き annotated タグにする:
+
+```
+git tag -s {tag} -m "{タグメッセージ}"
+git push origin {tag}
+```
+
+両方を出す場合は 2 つのタグを作ってから 1 回で push する.プロトコルの MINOR を上げたリリースでは Velocity が先に出ている必要があるので,Velocity のタグを先に書く:
+
+```
+git push origin velocity/v{velocityVersion} v{paperVersion}
+```
+
+2 つのワークフローが並行して走る.以降の §4〜§6 はタグごとに行う.
+
+GPG / SSH のエラーが出たら再試行せずに止まり,ユーザーに伝える.
+
+## 4. ワークフローの監視
+
+1. 起動を確認する (数十秒かかることがある):
+ ```
+ gh run list --workflow {workflow-file} --limit 3 --json databaseId,headBranch,status,event,url
+ ```
+ `headBranch` がタグ名の run を選ぶ.1 分待っても現れなければ `git ls-remote --tags origin {tag}` で push を確かめる
+2. 完了まで追う.長時間かかるので Bash の `run_in_background` で実行し,完了通知を待つ:
+ ```
+ gh run watch {run-id} --exit-status
+ ```
+3. 失敗したらジョブを切り分ける:
+ ```
+ gh run view {run-id} --log-failed
+ ```
+ - `validate` … タグと `gradle.properties` の不一致,または同名 Release が既に存在する
+ - `build` … ローカルでは通っているので CI 環境固有の問題を疑う
+ - `release` … GitHub Release 作成か Modrinth 公開の失敗.**GitHub Release 作成後に Modrinth で落ちた場合,再実行は `validate` の重複チェックで止まる**.Release とタグの状態を示したうえで,対処 (draft Release を削除して再実行する,Modrinth へ手動公開する,など) をユーザーに提案する
+ - 再実行 (`gh run rerun`) やタグの付け直しは提案に留め,承認を得てから行う
+
+## 5. draft の公開
+
+ワークフローは Release を **draft** で作る.
+
+```
+gh release view {tag} --json isDraft,name,assets,url
+```
+
+- 添付ファイルが期待どおりであること:
+ - Paper: `LunaticChat-{paperVersion}.jar`
+ - Velocity: `LunaticChat-{velocityVersion}-velocity.jar`
+- 添付 JAR をスクラッチパッドへダウンロードし,埋め込まれたプロトコル定数が `HEAD` の `ProtocolVersion.kt` と一致することを確かめる (別コミットからビルドされていないことの確認):
+ ```
+ gh release download {tag} --dir {scratchpad}/release-{tag}
+ javap -constants -cp {jar} dev.m1sk9.lunaticChat.engine.protocol.ProtocolVersion
+ ```
+- 問題なければ公開をユーザーに確認し,公開する:
+ ```
+ gh release edit v{paperVersion} --draft=false
+ gh release edit velocity/v{velocityVersion} --draft=false --latest=false
+ ```
+ GitHub は公開したリリースを既定で Latest にする.リポジトリの Latest は常に Paper のリリースにしておくため,Velocity では必ず `--latest=false` を付ける.両方を出す場合は Paper を後に公開する
+
+## 6. ロールアウトの確認
+
+以下をすべて確認し,表にして報告する.
+
+| 確認先 | 方法 | 期待値 |
+|--------|------|--------|
+| GitHub Release | `gh release view {tag} --json isDraft,assets` | `isDraft: false`,JAR 添付済み |
+| Latest | `gh api repos/m1sk9/LunaticChat/releases/latest --jq .tag_name` | 最新の Paper のタグ (`v{paperVersion}` など).`velocity/v*` になっていたら `gh release edit v{paperVersion} --latest` で戻す |
+| Modrinth (Paper) | 下記 API | `version_number == {paperVersion}`,`status == listed`,loaders に `paper` と `folia`,game_versions にワークフローの値 |
+| Modrinth (Velocity) | 下記 API | `version_number == {velocityVersion}-velocity`,`status == listed`,loaders に `velocity` |
+| リリースノート | `curl` で en / ja の changelog ページ | `200` |
+| Release 本文のリンク | 本文中の `lc.m1sk9.dev` リンクを `curl` | `200` |
+| リリースノートの Download リンク | ページ内の GitHub / Modrinth リンクを `curl` | `200` |
+
+Modrinth は公開 API をトークンなしで参照できる:
+
+```
+curl -s https://api.modrinth.com/v2/project/lunaticchat/version | jq '.[:4][] | {version_number, status, loaders, game_versions, date_published, file: .files[0].filename}'
+```
+
+Modrinth への反映は数分遅れることがある.見つからない場合は数分おいて再確認し,それでも無ければワークフローの `release` ジョブのログを確認する.
+
+## 7. 完了報告
+
+- リリースしたタグ・バージョン・Release の URL・Modrinth の URL
+- §6 の確認表
+- 未完了の項目があれば,その内容と次にユーザーが取るべき操作
diff --git a/.claude/skills/release/SKILL.md b/.claude/skills/release/SKILL.md
deleted file mode 100644
index 1f3fc1e..0000000
--- a/.claude/skills/release/SKILL.md
+++ /dev/null
@@ -1,70 +0,0 @@
----
-description: リリースの準備とタグ作成を行う。引数に paper, velocity, both のいずれかを指定。
-disable-model-invocation: true
-argument-hint: [paper|velocity|both]
-allowed-tools: Bash(./gradlew *), Bash(git *), Bash(gh *), Read, Grep, WebFetch
----
-
-# Release
-
-引数: `$ARGUMENTS`
-
-## 手順
-
-1. `gradle.properties` から `paperVersion` と `velocityVersion` を読み取る
-2. 引数に応じてリリース対象を決定:
- - `paper` → Paper のみリリース
- - `velocity` → Velocity のみリリース
- - `both` → 両方同時リリース
- - 引数なし → ユーザーに確認する
-3. リリース前チェック:
- - `./gradlew ktlintCheck` が通ること
- - `./gradlew test` が通ること
- - 対象プラットフォームの `shadowJar` がビルドできること
- - git working tree がクリーンであること
- - 該当タグがまだ存在しないこと
-4. チェックがすべて通ったら、タグの作成をユーザーに提案する:
- - `paper` → `git tag paper/v{paperVersion}`
- - `velocity` → `git tag velocity/v{velocityVersion}`
- - `both` → `git tag v{paperVersion}` (Paper/Velocity バージョンが異なる場合は注意を促す)
-5. タグを push するかユーザーに確認する (`git push origin {tag}`)
-6. push 後は「リリース後の監視」に進む
-
-## リリース後の監視
-
-タグを push したら、リリースワークフローが完走するまで見届ける。ワークフローは `validate` → `build` → `release` の 3 ジョブで、`release` ジョブが GitHub Release の作成と Modrinth への公開を行う。
-
-1. ワークフローの起動を確認する:
- ```
- gh run list --limit 5
- ```
- タグ名に対応するワークフロー (`Release Paper` / `Release Velocity` / `Release`) が起動していること。数十秒待っても現れない場合はタグの push 自体が失敗していないか確認する
-2. 完了まで追跡する:
- ```
- gh run watch {run-id} --exit-status
- ```
-3. 失敗した場合はジョブごとに原因を切り分け、ユーザーに報告する:
- ```
- gh run view {run-id} --log-failed
- ```
- - `validate` で失敗 → タグのバージョンと `gradle.properties` の不一致、または同名 Release が既に存在する
- - `build` で失敗 → リリース前チェックで通っていたはずなので、CI 環境固有の問題を疑う
- - `release` で失敗 → GitHub Release 作成か Modrinth 公開の失敗。Modrinth 側だけ失敗した場合、GitHub Release は既に作られているため再実行すると重複しうる点に注意する
-4. 成功したら成果物を確認する:
- ```
- gh release view {tag} --json isDraft,assets,url
- ```
- - **Release は draft で作成される**。JAR が添付されていることを確認したうえで、公開するかユーザーに確認する (`gh release edit {tag} --draft=false`)
- - 添付ファイル名が期待どおりであること (Paper は `LunaticChat-{version}.jar`、Velocity は `LunaticChat-{version}-velocity.jar`)
-5. リリースノートのリンク先が存在することを確認する。Release と Modrinth の changelog はどちらもドキュメントサイトを指しているため、対応するページが未デプロイだとリンク切れになる:
- - Paper: `https://lc.m1sk9.dev/changelog/paper/v{paperVersion}`
- - Velocity: `https://lc.m1sk9.dev/changelog/velocity/v{velocityVersion}`
-6. Modrinth に該当バージョンが公開されているかユーザーに確認を依頼する (Modrinth のバージョン一覧は API トークンなしでは追えないため、ここは目視確認を頼む)
-
-## 注意事項
-
-- タグの作成と push は必ずユーザーの確認を得てから行うこと
-- `both` の場合、Paper と Velocity のバージョンが異なる場合はその旨を明示すること
-- プロトコルバージョン (`ProtocolVersion.kt`) の変更がある場合は、後方互換テスト (`ProtocolBackwardCompatibilityTest`) が通っていることを確認すること
-- リリースは draft で作られる。ワークフローが成功しても、draft を公開するまでリリースは完了していない
-- 失敗したワークフローの再実行 (`gh run rerun`) は、`validate` の「Release が既に存在しないこと」チェックに引っかかる可能性がある。再実行の前に GitHub Release とタグの状態を確認し、対処方針をユーザーに提案すること
diff --git a/.claude/skills/test-integration/SKILL.md b/.claude/skills/test-integration/SKILL.md
new file mode 100644
index 0000000..863b5f1
--- /dev/null
+++ b/.claude/skills/test-integration/SKILL.md
@@ -0,0 +1,139 @@
+---
+description: 現在のコードベースからビルドした Paper 版と Velocity 版の LunaticChat が互換性を持つかを,JAR のプロトコル定数・後方互換テスト・Docker 上の実機ハンドシェイクで検証する.
+disable-model-invocation: true
+argument-hint: [--static-only]
+allowed-tools: Bash(./gradlew *), Bash(./x *), Bash(git *), Bash(javap *), Bash(docker *), Bash(curl *), Read, Grep, Glob, Edit
+---
+
+# Test Integration
+
+引数: `$ARGUMENTS`
+
+Paper ↔ Velocity の互換性は**プロトコルバージョンだけ**で決まる (プラグインバージョンは見ない).判定するのは Velocity 側で,`PluginMessageHandler` が Paper から届いた Handshake に対し「MAJOR が一致し,Paper の MINOR が `MIN_SUPPORTED_MINOR..MINOR` に入る」かを評価する.Paper 側は応答の `compatible` をそのまま受け入れる.
+
+検証は 3 段階で行う.`--static-only` のときは段階 1・2 だけ実行する.
+
+## 段階 1: ビルドと JAR の静的検査
+
+1. 古い JAR を掴まないように clean してからビルドする:
+ ```
+ ./gradlew :platform-paper:clean :platform-velocity:clean :platform-paper:shadowJar :platform-velocity:shadowJar
+ ```
+2. 生成物を特定する: `platform-paper/build/libs/LunaticChat-*.jar` と `platform-velocity/build/libs/LunaticChat-*-velocity.jar` (それぞれ 1 つだけのはず)
+3. 各 JAR に埋め込まれたプロトコル定数を取り出す:
+ ```
+ javap -constants -cp {jar} dev.m1sk9.lunaticChat.engine.protocol.ProtocolVersion
+ ```
+ `MAJOR` / `MINOR` / `PATCH` / `MIN_SUPPORTED_MINOR` の 4 値を記録する
+4. 次の組み合わせで互換性を判定する (判定式は `ProtocolVersion.isCompatible` と同じ.左が Velocity,右が Paper):
+
+ | 組み合わせ | Velocity 側の定数 | Paper 側の定数 |
+ |-----------|------------------|---------------|
+ | HEAD ↔ HEAD | HEAD の Velocity JAR | HEAD の Paper JAR |
+ | HEAD Velocity ↔ 最新リリース済み Paper | HEAD の Velocity JAR | 最新 Paper リリースのタグ時点の `ProtocolVersion.kt` |
+ | 最新リリース済み Velocity ↔ HEAD Paper | 最新 Velocity リリースのタグ時点の `ProtocolVersion.kt` | HEAD の Paper JAR |
+
+ Paper と Velocity は独立にリリースされるため,下 2 行は「片側だけ更新した運用」が成り立つかの確認になる.最新リリースのタグは `gh release list` で特定する (`velocity/vX.Y.Z` は Velocity,`vX.Y.Z` は Paper を含む.v1.4.0 以前の `vX.Y.Z` は両方を含み,`paper/vX.Y.Z` は Paper のみ.どの版が入っているかは添付 JAR の名前で判断する).タグ時点のソースは次で読む:
+ ```
+ git show {tag}:engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/protocol/ProtocolVersion.kt
+ ```
+5. 下 2 行が非互換の場合,それが意図された変更 (MAJOR / MINOR の引き上げ) であれば失敗ではなく**デプロイ順序の制約**として報告する (MINOR: Velocity を先に出す,MAJOR: 同時に出す).意図されていない非互換は失敗とする
+
+HEAD ↔ HEAD が非互換なのは両 JAR が別の engine からビルドされたことを意味するので,即座に失敗として報告する.
+
+## 段階 2: プロトコルのテスト
+
+```
+./gradlew :engine:test --tests '*ProtocolBackwardCompatibilityTest' --tests '*PluginMessageCodec*' --tests '*ProtocolVersion*'
+./gradlew :platform-velocity:test :platform-paper:test --tests '*velocity*'
+```
+
+存在しないテストクラス名のパターンで Gradle が失敗した場合は,`Grep` で実在するテストクラスを確認してパターンを直す.
+
+直近リリースタグから `engine/.../protocol/` に変更がある場合は,`ProtocolBackwardCompatibilityTest` に新しいスナップショットが追加されているかも確認する.追加されていなければ指摘する.
+
+## 段階 3: Docker 上の実機検証
+
+`./x` の Velocity 環境 (`docker/velocity/`,プロキシ `debug-velocity` + バックエンド `debug-server-s1` / `debug-server-s2`) を使う.サーバーバージョンは `./x` が `build.gradle.kts` から導出するので,compose にバージョンを書き足さないこと.
+
+### 準備
+
+1. Docker が動いていることを確認する (`docker info`).動いていなければユーザーに起動を依頼して止まる
+2. 既に `debug-*` コンテナが動いていれば,停止してよいかユーザーに確認する
+3. バックエンドの設定を確認する: `docker/velocity/plugins-paper-s1/LunaticChat/config.yml` と `plugins-paper-s2/...` の `features.velocityIntegration.enabled` が `true` であること
+ - ファイルが無い場合 (初回起動前) は,一度起動して生成させてから編集し,再起動する
+ - `false` の場合は変更内容をユーザーに伝えてから `true` に書き換える (このディレクトリは gitignore 済みのローカルデータ).元の値を報告に残す
+ - `crossServerGlobalChat` / `crossServerDirectMessage` も現在値を記録しておく (段階 3 の任意項目で使う)
+
+### 起動
+
+`./x start velocity` はビルドと JAR の配置を行ったうえで `docker compose up` をフォアグラウンドで実行する.Bash の `run_in_background` で起動し,3 コンテナのヘルスチェックが `healthy` になるまで待つ:
+
+```
+docker inspect -f '{{.Name}} {{.State.Health.Status}}' debug-velocity debug-server-s1 debug-server-s2
+```
+
+5 分経っても `healthy` にならなければ `docker logs` を確認して原因を報告する.
+
+### 起動時ログの検査
+
+```
+docker logs debug-velocity 2>&1 | grep -iE 'lunaticchat|plugin message|presence'
+docker logs debug-server-s1 2>&1 | grep -iE 'lunaticchat|velocity'
+docker logs debug-server-s2 2>&1 | grep -iE 'lunaticchat|velocity'
+```
+
+期待する行:
+
+| コンテナ | 期待するログ |
+|---------|-------------|
+| `debug-velocity` | `LunaticChat Velocity plugin initialized successfully`,`Plugin message handler registered for channel:` |
+| `debug-server-s1` / `s2` | `Velocity integration channel registered:`,`Velocity integration initialized. Waiting for first player join to perform handshake.` |
+
+LunaticChat に起因する `ERROR` / `SEVERE` / 例外スタックトレースが無いことも確認する.
+
+### ハンドシェイク (プレイヤーの接続が必要)
+
+Plugin Messaging はプレイヤーの接続を経由して流れるため,ハンドシェイクは**最初のプレイヤーが参加した 1 秒後**に始まる.Claude からはプレイヤーを接続できないので,ユーザーに次を依頼する:
+
+1. Minecraft クライアント (バージョンは `platform-paper/build.gradle.kts` の `paper-api` が示すもの) で `localhost:25577` に接続する.offline mode なので任意のプレイヤー名でよい
+2. s1 に入ったら,`/server s2` で s2 に移動する
+
+依頼を出したら,ログにハンドシェイクの結果が出るまで待つ.期待する行:
+
+| コンテナ | 成功 | 失敗 |
+|---------|------|------|
+| `debug-velocity` | `Handshake successful with s1` (s2 も同様) | `Protocol version incompatible: Paper=..., Velocity=...` |
+| `debug-server-s1` / `s2` | `Successfully connected to Velocity (version: ..., protocol: ...)` | `Velocity handshake failed:` / `Handshake timeout` |
+
+ログの `protocol:` が段階 1 で取り出した値と一致することも確かめる.
+
+続けて,プレイヤーが接続したまま console からステータス要求を送り,往復を確認する:
+
+```
+docker exec debug-server-s1 rcon-cli lcv status
+docker logs debug-server-s1 2>&1 | grep 'Received status response from Velocity'
+```
+
+### 任意: サーバー間機能
+
+ユーザーがクライアントを 2 つ用意できる場合に限り実施する (s1 と s2 に 1 人ずつ).
+
+- `crossServerGlobalChat: true` なら,一方のグローバルチャットがもう一方に届くこと
+- `crossServerDirectMessage: true` なら,`/tell {player}@{server}` が届き,`/reply` で返せること
+
+結果はユーザーに目視で確認してもらい,報告に含める.
+
+### 後片付け
+
+環境を止めるかユーザーに確認する.止める場合は `./x stop velocity` (ボリュームは残す).書き換えた `config.yml` を元に戻すかも確認する.
+
+## 報告
+
+次の形でまとめる.
+
+1. **判定**: 互換 / 非互換 / デプロイ順序の制約つきで互換
+2. **プロトコル定数表**: HEAD Paper・HEAD Velocity・最新リリース済み Paper・最新リリース済み Velocity の 4 値と,段階 1 の 3 組み合わせの判定
+3. **テスト結果**: 段階 2 の成否 (失敗したテスト名)
+4. **実機検証**: 起動ログ・ハンドシェイク・ステータス往復・(実施した場合) サーバー間機能の結果.実施しなかった項目は「未実施」と明記する
+5. **設定の変更**: 段階 3 で書き換えたファイルと元の値