🦆

RBS Goose を Claude Plugin 化した

に公開

Claude Code で Plugin や Marketplace などが使えるようになりました。

https://note.com/npaka/n/nfa5caf284732

自分は Ruby の型宣言を LLM に書かせるツール RBS Goose を以前作っていたのですが、最近はわざわざツール化しなくても AI エージェントにやらせたほうが柔軟に動いて良いのでは? と思い、 claude-rbs-goose というカスタムスラッシュコマンドを作って試しています。

この辺の話について、Software Design 2025年12月号 で「Ruby x 静的型付け戦略」の連載に枠をいただいて寄稿しました。(宣伝)

https://gihyo.jp/magazine/SD/archive/2025/202512

寄稿記事や claude-rbs-goose の README では claude-cmd を使ってカスタムスラッシュコマンドをインストールする手順を書いていますが、 Claude 公式で Marketplace がサポートされたのでざっくり移植してみました。

Marketplace から rbs-goose Plugin をインストールする

kokuyouwind/claude-plugins が Claude Marketplace になっており、Claude Code 上で /plugin marketplace add kokuyouwind/claude-plugins を実行することでマーケットプレイスが追加されます。

$ claude
> /plugin marketplace add kokuyouwind/claude-plugins
  ⎿  Successfully added marketplace: kokuyouwind-plugins

その後、 /plugin install rbs-goose@kokuyouwind-plugins を実行すると rbs-goose プラグインをインストールできます。

> /plugin install rbs-goose@kokuyouwind-plugins
  ⎿  ✓ Installed rbs-goose. Restart Claude Code to load new plugins.

これで /rbs-goose:setup/rbs-goose:run のカスタムコマンドが実行できるようになります。

ちなみに未設定のリポジトリで /rbs-goose:run を実行すると、自動で setuprun を順に実行するため、基本的には /rbs-goose:run だけ実行すれば OK です。

Marketplace の構成

おおむね公式ドキュメント通りにディレクトリとファイルを配置しただけですが、ざっくり構成を書いておきます。

https://code.claude.com/docs/ja/plugin-marketplaces

リポジトリをマーケットプレイスとして認識させるために、 .claude-plugin/marketplace.json を配置しています。

ここにはマーケットプレイス自体の情報と各プラグインの情報を列挙します。

https://github.com/kokuyouwind/claude-plugins/blob/main/.claude-plugin/marketplace.json#L1-L27

あとは plugins ディレクトリ以下にプラグインを配置するだけです。

今回はカスタムスラッシュコマンド用の commands ディレクトリの他に、コマンドから参照する internals, templates ディレクトリを配置しています。ここに agents や skills ディレクトリを配置すれば、それぞれエージェントやスキルも使えるようになります。

claude-plugins/plugins/rbs-goose
├── commands
│   ├── rbs-goose
│   │   ├── initialize.md
│   │   ├── type_file.md
│   │   └── type_inline.md
│   └── rbs-goose.md
├── plugin.json
├── README.md
└── templates
    └── rbs_goose.template.yml

rbs-goose コマンドのプロンプト

/rbs-goose コマンドは Ruby 擬似コードで以下の手順を記述しています。

  • rbs_goose.yml が存在しない場合は初期化手順を実行する
  • rbs_goose.yml の設定に沿って、 rbs-inline を使うインラインモードか、 rbs を別ファイルで記述するファイルモードのいずれかを実行する

https://github.com/kokuyouwind/claude-plugins/blob/main/plugins/rbs-goose/commands/run.md

初期化が不要なときに初期化手順まで読み込まないよう、各手順は個別にファイルを読み込むようにしています。このあたりは Claude Skills に置き換えたほうがスムーズそうですね。

動作手順は以下のようにしています(ファイルモード)。

RBS の文法ドキュメントを読み込んだうえで、型検査エラーがなくなるまで型注釈を直し、型エラーがない範囲で型の詳細化を行うだけです。

https://github.com/kokuyouwind/claude-plugins/blob/main/plugins/rbs-goose/internal/type_file.md

rbs-goose を使って RBS 型注釈を生成する

せっかくなのでカスタムコマンドを実行してみましょう。

今回は claude_rbs_goose_examples1_lifegame/1_untyped 以下の Ruby コードに型注釈をつけてみます。

該当ディレクトリで /rbs-goose:run を実行すると、以下のように型注釈を生成します。[1]

長いので折りたたみにしてますが、「rbs-goose の疑似設定ファイル生成」「RBS などのライブラリインストール」「型注釈の生成と型検査」を順に実行しています。

rbs-goose:run の実行ログ

/rbs-goose:run is running…

⏺ ライフゲームのコードに型アノテーションを追加していきます。まず現在のファイル構造を確認して、作業計画を立てましょう。

⏺ Search(pattern: "**/*.rb")
⎿  Found 9 files (ctrl+o to expand)

⏺ Bash(ls -la)
⎿  total 64
drwxr-xr-x 14 kokuyouwind staff 448 8 21 22:15 .
drwxr-xr-x 5 kokuyouwind staff 160 8 21 22:16 ..
… +12 lines (ctrl+o to expand)

⏺ Read(rbs_goose.yml)
⎿  Error reading file

⏺ 設定ファイルがないため、まずセットアップを実行する必要があるようです。セットアップ手順を確認しましょう。

⏺ Bash(find ~/.claude/plugins -name "setup.md" -type f 2>/dev/null | head -5)
⎿  /Users/kokuyouwind/.claude/plugins/marketplaces/kokuyouwind-plugins/plugins/rbs-goose/commands/setup.md

⏺ Read(~/.claude/plugins/marketplaces/kokuyouwind-plugins/plugins/rbs-goose/commands/setup.md)
⎿  Read 64 lines

⏺ セットアップ手順が確認できました。まず設定ファイルのテンプレートを読み込んで、このプロジェクトに適した設定を作成します。

⏺ Read(~/.claude/plugins/marketplaces/kokuyouwind-plugins/plugins/rbs-goose/templates/rbs_goose.template.yml)
⎿  Read 6 lines

⏺ 設定ファイルを作成します。デフォルトのインラインモードを使用します。

⏺ Write(rbs_goose.yml)
⎿  Wrote 6 lines to rbs_goose.yml
# Type checking command
typecheck_command: "steep check"
# Type Annotation Mode (:inline, :file)
type_annotation_mode: :inline

⏺ 設定ファイルができましたので、次にRBSツールのセットアップを行います。まず現在のGemfileを確認します。

⏺ Read(Gemfile)
⎿  Read 14 lines

⏺ Gemfileを更新してRBSツールを追加します。

⏺ Update(Gemfile)
⎿  Updated Gemfile with 4 additions
11 gem "rspec", "~> 3.0"
12
13 gem "rubocop", "~> 1.21"
14 +
15 + gem "rbs", "~> 3.0"
16 + gem "rbs-inline", "~> 0.8"
17 + gem "steep", "~> 1.8"

⏺ gemをインストールします。

⏺ Bash(bundle install)
⎿  Fetching gem metadata from https://rubygems.org/............
Resolving dependencies...
Fetching uri 1.1.1
… +9 lines (ctrl+o to expand)

⏺ 次にSteepの設定ファイルを作成します。

⏺ Bash(bundle exec steep init)
⎿  Writing Steepfile...

⏺ Read(Steepfile)
⎿  Read 33 lines

⏺ Steepfileを設定します。rbs-inlineを使用するため、適切な設定に更新します。

⏺ Write(Steepfile)
⎿  Updated Steepfile with 9 additions and 30 removals
1 # D = Steep::Diagnostic
2 - #
3 - # target :lib do
4 - # signature "sig"
5 - # ignore_signature "sig/test"
6 - #
7 - # check "lib" # Directory name
8 - # check "path/to/source.rb" # File name
9 - # check "app/models/**/.rb" # Glob
10 - # # ignore "lib/templates/
.rb"
11 - #
12 - # # library "pathname" # Standard libraries
13 - # # library "strong_json" # Gems
14 - #
15 - # # configure_code_diagnostics(D::Ruby.default) # default diagnostics setting (applies by default)
16 - # # configure_code_diagnostics(D::Ruby.strict) # strict diagnostics setting
17 - # # configure_code_diagnostics(D::Ruby.lenient) # lenient diagnostics setting
18 - # # configure_code_diagnostics(D::Ruby.silent) # silent diagnostics setting
19 - # # configure_code_diagnostics do |hash| # You can setup everything yourself
20 - # # hash[D::Ruby::NoMethod] = :information
21 - # # end
22 - # end
2
3 - # target :test do
4 - # unreferenced! # Skip type checking the lib code when types in test target is changed
5 - # signature "sig/test" # Put RBS files for tests under sig/test
6 - # check "test" # Type check Ruby scripts under test
7 - #
8 - # configure_code_diagnostics(D::Ruby.lenient) # Weak type checking for test code
9 - #
10 - # # library "pathname" # Standard libraries
11 - # end
3 + target :lib do
4 + signature "sig"
5 +
6 + check "lib"
7 +
8 + library "pathname"
9 +
10 + configure_code_diagnostics(Steep::Diagnostic::Ruby.default)
11 + end

⏺ sigディレクトリを作成します。

⏺ Bash(mkdir -p sig)
⎿  (No content)

⏺ RBS Collectionを初期化します。

⏺ Bash(bundle exec rbs collection init)
⎿  created: rbs_collection.yaml

⏺ Bash(bundle exec rbs collection install)
⎿  Installing activesupport:7.0 (activesupport@f1d2dae32fe)
Installing ast:2.4 (ast@f1d2dae32fe)
Using base64:0.3.0 (/Users/kokuyouwind/.local/share/mise/installs/ruby/3.4.5/lib/ruby/gems/3.4.0/gems/base64-0.3.0/sig)
… +46 lines (ctrl+o to expand)

⏺ .gitignoreを更新します。

⏺ Read(.gitignore)
⎿  Read 12 lines

⏺ Update(.gitignore)
⎿  Updated .gitignore with 4 additions
9
10 # rspec failure tracking
11 .rspec_status
12 +
13 + # RBS
14 + .gem_rbs_collection/
15 + sig/generated/

⏺ セットアップが完了しました。次に各クラスのコードを確認して型アノテーションを追加していきます。まずCellクラスから始めます。

⏺ Read(lib/lifegame/cell.rb)
⎿  Read 16 lines

⏺ Cellクラスに型アノテーションを追加します。

⏺ Update(lib/lifegame/cell.rb)
⎿  Updated lib/lifegame/cell.rb with 3 additions
2
3 module Lifegame
4 class Cell
5 + # rbs @alive: bool
6 attr_accessor :alive
7
8 + # rbs alive: bool -- Initialize a cell
9 def initialize(alive: false)
10 @alive = alive
11 end
12
13 + # rbs return: bool -- Check if the cell is alive
14 def alive?
15 @alive
16 end

⏺ 次にBoardクラスに型アノテーションを追加します。

⏺ Read(lib/lifegame/board.rb)
⎿  Read 60 lines

⏺ Boardクラスに型アノテーションを追加します。

⏺ Update(lib/lifegame/board.rb)
⎿  Updated lib/lifegame/board.rb with 10 additions
2
3 module Lifegame
4 class Board
5 + # rbs @width: Integer
6 + # rbs @height: Integer
7 + # rbs @cells: Array[Array[Cell]]
8 +
9 + # rbs width: Integer, height: Integer -- Initialize the board with given dimensions
10 def initialize(width, height)
11 @width = width
12 @height = height
13 @cells = Array.new(@height) { Array.new(@width) { Cell.new } }
14 end
15
16 + # rbs return: void -- Randomly set cells alive or dead
17 def randomize
18 @cells.each do |row|
19 row.each do |cell|
...
16 end
17 end
18
19 + # rbs return: void -- Display the current board state
20 def display
21 system "clear"
22 puts @cells.map { |row| row.map { |cell| cell.alive? ? "██" : " " }.join }.join("\n")
23 end
24
25 + # rbs return: void -- Update the board to the next generation
26 def update
27 new_cells = Array.new(@height) { Array.new(@width) { Cell.new } }
28 @height.times do |y|
...
31 @cells = new_cells
32 end
33
34 + # rbs x: Integer, y: Integer -- return: bool -- Calculate the next state of a cell
35 def next_cell_state(x, y)
36 alive_neighbors = count_alive_neighbors(x, y)
37 if @cells[y][x].alive?
...
40 end
41 end
42
43 + # rbs x: Integer, y: Integer -- return: Integer -- Count alive neighbors around a cell
44 def count_alive_neighbors(x, y)
45 count = 0
46 (-1..1).each do |dy|

⏺ 次にGameクラスに型アノテーションを追加します。

⏺ Read(lib/lifegame/game.rb)
⎿  Read 21 lines

⏺ Gameクラスに型アノテーションを追加します。

⏺ Update(lib/lifegame/game.rb)
⎿  Updated lib/lifegame/game.rb with 6 additions
2
3 module Lifegame
4 class Game
5 + # rbs @width: Integer
6 + # rbs @height: Integer
7 + # rbs @board: Board
8 +
9 + # rbs width: Integer, height: Integer -- Initialize the game with board dimensions
10 def initialize(width: 10, height: 10)
11 @width = width
12 @height = height
...
9 @board.randomize
10 end
11
12 + # rbs return: void -- Run the game loop
13 def run
14 loop do
15 @board.display

⏺ 他にも型アノテーションを追加すべきファイルがあるか確認します。

⏺ Read(lib/lifegame.rb)
⎿  Read 11 lines

⏺ Read(lib/lifegame/version.rb)
⎿  Read 6 lines

⏺ versionファイルは定数のみなので型アノテーションは不要です。次にrbs-inlineを使ってインラインアノテーションからRBSファイルを生成します。

⏺ Bash(bundle exec rbs-inline --output lib)
⎿  🎉 Generated 0 RBS files under sig/generated

⏺ RBSファイルが生成されなかったようです。rbs-inlineの正しい使い方を確認します。

⏺ Bash(bundle exec rbs-inline --help)
⎿  Usage: rbs-inline [options]
--base=BASE The path to calculate relative path of files (defaults to lib:app)
--output[=DEST] Save the generated RBS files under sig/generated or DEST if specified (defaults to output to STDOUT)
… +4 lines (ctrl+o to expand)

⏺ デフォルトではopt-inモードなので、ファイルに# rbs_inline: enabledを追加する必要があります。各ファイルを更新します。

⏺ Update(lib/lifegame/cell.rb)
⎿  Updated lib/lifegame/cell.rb with 1 addition
1 # frozen_string_literal: true
2 + # rbs_inline: enabled
3
4 module Lifegame
5 class Cell

⏺ Update(lib/lifegame/board.rb)
⎿  Updated lib/lifegame/board.rb with 1 addition
1 # frozen_string_literal: true
2 + # rbs_inline: enabled
3
4 module Lifegame
5 class Board

⏺ Update(lib/lifegame/game.rb)
⎿  Updated lib/lifegame/game.rb with 1 addition
1 # frozen_string_literal: true
2 + # rbs_inline: enabled
3
4 module Lifegame
5 class Game

⏺ 再度RBSファイルを生成します。

⏺ Bash(bundle exec rbs-inline --output)
⎿  🎉 Generated 0 RBS files under sig/generated

⏺ まだ生成されていません。--baseオプションを使ってlibディレクトリを指定してみます。

⏺ Bash(bundle exec rbs-inline --base=lib --output lib)
⎿  🎉 Generated 3 RBS files under sig/generated

⏺ 成功しました!生成されたRBSファイルを確認します。

⏺ Bash(find sig/generated -name "*.rbs" -type f)
⎿  sig/generated/lifegame/game.rbs
sig/generated/lifegame/cell.rbs
sig/generated/lifegame/board.rbs

⏺ Read(sig/generated/lifegame/cell.rbs)
⎿  Read 15 lines

⏺ Read(sig/generated/lifegame/board.rbs)
⎿  Read 24 lines

⏺ Read(sig/generated/lifegame/game.rbs)
⎿  Read 12 lines

⏺ RBSファイルは生成されましたが、型情報がuntypedになっています。インラインアノテーションの形式を修正する必要があるようです。rbs-inlineのドキュメントを参考に、正しい形式に修正
します。

⏺ Update(lib/lifegame/cell.rb)
⎿  Updated lib/lifegame/cell.rb with 4 additions and 3 removals
3
4 module Lifegame
5 class Cell
6 - # rbs @alive: bool
6 + # @rbs @alive: bool
7 attr_accessor :alive
8
9 - # rbs alive: bool -- Initialize a cell
9 + # @rbs alive: bool -- Initialize a cell
10 + # @rbs return: void
11 def initialize(alive: false)
12 @alive = alive
13 end
14
15 - # rbs return: bool -- Check if the cell is alive
15 + # @rbs return: bool -- Check if the cell is alive
16 def alive?
17 @alive
18 end

⏺ Update(lib/lifegame/board.rb)
⎿  Updated lib/lifegame/board.rb with 15 additions and 9 removals
3
4 module Lifegame
5 class Board
6 - # rbs @width: Integer
7 - # rbs @height: Integer
8 - # rbs @cells: Array[Array[Cell]]
6 + # @rbs @width: Integer
7 + # @rbs @height: Integer
8 + # @rbs @cells: Array[Array[Cell]]
9
10 - # rbs width: Integer, height: Integer -- Initialize the board with given dimensions
10 + # @rbs width: Integer
11 + # @rbs height: Integer
12 + # @rbs return: void
13 def initialize(width, height)
14 @width = width
15 @height = height
16 @cells = Array.new(@height) { Array.new(@width) { Cell.new } }
17 end
18
19 - # rbs return: void -- Randomly set cells alive or dead
19 + # @rbs return: void
20 def randomize
21 @cells.each do |row|
22 row.each do |cell|
...
23 end
24 end
25
26 - # rbs return: void -- Display the current board state
26 + # @rbs return: void
27 def display
28 system "clear"
29 puts @cells.map { |row| row.map { |cell| cell.alive? ? "██" : " " }.join }.join("\n")
30 end
31
32 - # rbs return: void -- Update the board to the next generation
32 + # @rbs return: void
33 def update
34 new_cells = Array.new(@height) { Array.new(@width) { Cell.new } }
35 @height.times do |y|
...
40 @cells = new_cells
41 end
42
43 - # rbs x: Integer, y: Integer -- return: bool -- Calculate the next state of a cell
43 + # @rbs x: Integer
44 + # @rbs y: Integer
45 + # @rbs return: bool
46 def next_cell_state(x, y)
47 alive_neighbors = count_alive_neighbors(x, y)
48 if @cells[y][x].alive?
...
50 end
51 end
52
53 - # rbs x: Integer, y: Integer -- return: Integer -- Count alive neighbors around a cell
53 + # @rbs x: Integer
54 + # @rbs y: Integer
55 + # @rbs return: Integer
56 def count_alive_neighbors(x, y)
57 count = 0
58 (-1..1).each do |dy|

⏺ Update(lib/lifegame/game.rb)
⎿  Updated lib/lifegame/game.rb with 7 additions and 5 removals
3
4 module Lifegame
5 class Game
6 - # rbs @width: Integer
7 - # rbs @height: Integer
8 - # rbs @board: Board
6 + # @rbs @width: Integer
7 + # @rbs @height: Integer
8 + # @rbs @board: Board
9
10 - # rbs width: Integer, height: Integer -- Initialize the game with board dimensions
10 + # @rbs width: Integer
11 + # @rbs height: Integer
12 + # @rbs return: void
13 def initialize(width: 10, height: 10)
14 @width = width
15 @height = height
...
15 @board.randomize
16 end
17
18 - # rbs return: void -- Run the game loop
18 + # @rbs return: void
19 def run
20 loop do
21 @board.display

⏺ 再度RBSファイルを生成します。

⏺ Bash(bundle exec rbs-inline --base=lib --output lib)
⎿  🎉 Generated 3 RBS files under sig/generated

⏺ 生成されたRBSファイルを確認します。

⏺ Read(sig/generated/lifegame/cell.rbs)
⎿  Read 16 lines

⏺ 良い結果です!ただしattr_accessorの型がuntypedのままです。これを修正します。

⏺ Update(lib/lifegame/cell.rb)
⎿  Updated lib/lifegame/cell.rb with 2 additions
4 module Lifegame
5 class Cell
6 # @rbs @alive: bool
7 +
8 + # @rbs returns bool
9 attr_accessor :alive
10
11 # @rbs alive: bool -- Initialize a cell

⏺ 再度RBSファイルを生成し、型チェックを実行します。

⏺ Bash(bundle exec rbs-inline --base=lib --output lib)
⎿  🎉 Generated 1 RBS files under sig/generated

⏺ Bash(bundle exec steep check) timeout: 1m 0s
⎿  Error: Exit code 1
# Type checking files:

 ..F....F

 lib/lifegame/version.rb:4:2: [warning] Cannot find the declaration of constant: `VERSION`
 │ Diagnostic ID: Ruby::UnknownConstant
 │
 └   VERSION = "0.1.0"
     ~~~~~~~
 … +8 lines (ctrl+o to see all)

⏺ 型チェックで2つの警告が出ました。VERSIONとErrorクラスの定義が必要です。これらをRBSで定義します。

⏺ Write(sig/lifegame.rbs)
⎿  Wrote 7 lines to sig/lifegame.rbs
module Lifegame
VERSION: String
class Error < StandardError
end
end

⏺ 再度型チェックを実行します。

⏺ Bash(bundle exec steep check) timeout: 1m 0s
⎿  # Type checking files:

 .........
 … +2 lines (ctrl+o to expand)

⏺ 完璧です!型チェックが成功しました。最後にテストを実行して、型アノテーションの追加によってコードが正しく動作することを確認します。

⏺ Bash(bundle exec rspec)
⎿  Lifegame::Board
#next_cell_state
with a live cell
… +17 lines (ctrl+o to expand)

⏺ 素晴らしい!すべてのテストが成功しました。作業内容をまとめます。

完了しました!

ライフゲームのプロジェクトに型アノテーションを追加しました。以下の作業を実施しました:

セットアップ

  1. rbs_goose.yml - 設定ファイルを作成(インラインモード)
  2. Gemfile - RBSツール(rbs, rbs-inline, steep)を追加
  3. Steepfile - 型チェック設定を作成
  4. RBS Collection - 初期化とインストール
  5. .gitignore - RBS関連ファイルを追加

型アノテーションの追加

各クラスに@rbsコメントを使ったインライン型アノテーションを追加しました:

  1. Cell クラス (lib/lifegame/cell.rb:5-19)
    - インスタンス変数 @alive: bool
    - initialize, alive? メソッドに型情報
  2. Board クラス (lib/lifegame/board.rb:5-75)
    - インスタンス変数 @width, @height: Integer, @cells: Array[Array[Cell]]
    - 全メソッドに型情報(引数と戻り値)
  3. Game クラス (lib/lifegame/game.rb:5-29)
    - インスタンス変数 @width, @height: Integer, @board: Board
    - 全メソッドに型情報
  4. モジュール定義 (sig/lifegame.rbs)
    - VERSION: String と Error クラスの定義

検証結果

  • ✅ RBSファイルが正しく生成されました(sig/generated/)
  • ✅ 型チェックが成功しました(steep check)
  • ✅ 全テストが成功しました(8 examples, 0 failures)

プロジェクトに型安全性が追加され、Steepによる型チェックが可能になりました!

こんな感じで、 /rbs-goose:run とするだけで全クラスに RBS 注釈を生成し、 steep check が通るまで修正できました。

Marketplace の利点・気になる点

Claude Code で Marketplace を使って Plugin を配布する機能を作ってみて、良さそうだと思ったこと・気になったことをざっくり書いてみます。

カスタムスラッシュコマンドやスキルなどをまとめて配布できる

今回はカスタムスラッシュコマンドだけを含んだプラグインですが、 RBS の文法や型エラーの修正方法などはスキルとしてまとめたほうが賢くなりそうです。

スキルやサブエージェントがある前提でカスタムスラッシュコマンドのプロンプトを記述できるなど、これらがひとまとまりで配布できるメリットは結構大きいように思えますね。

プロジェクト単位で依存するプラグインを管理できる

チームマーケットプレイスの設定 に書かれているとおり、 .claude/settings.jsonextraKnownMarketplacesenabledPlugins を記載しておくことで、自動でマーケットプレイス追加・プラグインインストールをしてくれます。

https://code.claude.com/docs/ja/plugin-marketplaces#チームマーケットプレイスの設定

これまではチームで統一したカスタムスラッシュコマンドを使えるよう管理するのが難しかったので、この仕組みは嬉しいです。

バージョン管理ができるかわからない

マーケットプレイスとプラグインの両方に version が記載できますが、ドキュメントを見ても Git Tag を見てくれるなどの記述は見つかりませんでした。

現状試した限りだと、マーケットプレイスは常にリポジトリのデフォルトブランチを参照しているようにみえます。

このため「チームで見るプラグインのバージョンを固定したい」場合に、いまいちマーケットプレイスやプラグインのバージョン管理をどうすればいいのかがわかりませんでした。

またマーケットプレイス管理下のプラグイン開発時も、変更を都度デフォルトブランチにマージしただけで動作が変わってしまうのも怖いところです。

バージョンロックファイルを作ってリリースから読んでくれる claude-cmd のようになってほしいところです。

コーディングエージェントの共通仕様が欲しい

これはマーケットプレイスに限った話ではないんですが、 Claude Code 以外に Codex や Gemini CLI など色々なコーディングエージェントがあります。

Claude Code でしか使えないカスタムツールだと他のツール派の人に使ってもらえないですし、かといってそれぞれに合わせて作ったものを保守するのはかなり面倒です。

マーケットプレイスという便利な配布の仕組みができたので、他のコーディングエージェントでも同じ仕組みに乗ってもらったり、共通仕様を作ってもらえると便利そうですね。

まとめ

既存のカスタムスラッシュコマンドをマーケットプレイスに移行するのは、公式ドキュメントを Claude Code に読ませて大体おまかせで完了しました。

もし個別にカスタムスラッシュコマンドを管理していたり、そもそもバージョン管理下におかずコピペしているようなケースでは、マーケットプレイスに寄せていくのがおすすめです。

脚注
  1. 動作モードを選択できるようプロンプトを設定しているのに確認してくれないなど、claude-cmd でカスタムコマンドだけ入れたときより指示への忠実性が下がり試行錯誤も多くなっている気がするので、 Plugin 用にプロンプトを調整したほうが良さそうです。 ↩︎

Discussion