Claude CodeでVS Code拡張を自作する — 雛形からvsce公開まで
yo codeの雛形、package.jsonのactivationEventsとcontributes、テスト、vsceでのパッケージングと公開までを、Claude Codeに任せる範囲と手元で確認する範囲に分けて扱います。
VS Code拡張機能は、雛形生成、package.jsonの宣言、TypeScriptの実装、vsceでのパッケージングという定型の流れで作れます。Claude Codeはこの流れのうち、宣言と実装の往復とテストの実行を得意とします。拡張機能を実際のウィンドウで動かす確認だけは手元で行います。
ここではHello Worldの雛形から公開までを、Claude Codeに渡す規約とあわせて順に見ます。Claude Code公式拡張の使い方はClaude Code VS Code拡張機能の使い方にまとまっています。こちらは拡張機能を自分で作る側の話です。
前提
Node.jsとGitがインストール済みであることが前提です。動作確認のF5実行には、VS Code本体も使います。
雛形はyo codeで作る
VS Codeのチュートリアルは、Yeomanとgenerator-codeで雛形を作る手順です。グローバルインストールが不要なら次の1行で足ります。
npx --package yo --package generator-code -- yo code対話では、種類にNew Extension (TypeScript)、名前にHelloWorld、識別子にhelloworldを選び、残りは既定のままにします。バンドラーはunbundled、パッケージマネージャーはnpmがチュートリアルの選択です。
雛形ができたらsrc/extension.tsを開き、F5キーか、コマンドパレットのDebug: Start Debuggingを実行します。新しく開くExtension Development HostのウィンドウでHello Worldコマンドを実行すると、通知が出て成功です。コマンドが見えないときは、package.jsonのengines.vscodeが手元のVS Codeと合っているかを確認します。
この対話ウィザードは人間が答える前提の作りです。Claude Codeに任せるなら、yo codeだけは自分で実行して雛形を作り、そのディレクトリでClaude Codeを起動する分担が無理がありません。
拡張機能を構成する3つの概念
Hello Worldの拡張機能は3つのことをしています。この3つが、以降のすべての変更の置き場所を決めます。
拡張機能を動かす3つの部品
Activation Events
package.jsonのactivationEventsに書く、拡張機能が有効になる契機です。Contribution Points
package.jsonのcontributesに書く、コマンド・メニュー・キーバインドなどの静的な宣言です。VS Code API
src/extension.tsから実行時に呼ぶJavaScript APIです。
package.jsonは拡張機能のマニフェストです。nameとpublisherから<publisher>.<name>という一意のIDが決まります。mainがエントリポイント、engines.vscodeが必要とするVS Code APIの最低バージョンで、*は指定できません。
サンプルの中核は次の形です。
{
"name": "helloworld-sample",
"publisher": "vscode-samples",
"engines": { "vscode": "^1.51.0" },
"activationEvents": [],
"main": "./out/extension.js",
"contributes": {
"commands": [
{ "command": "helloworld.helloWorld", "title": "Hello World" }
]
}
}エントリファイルはactivateとdeactivateの2つを公開します。activateは登録した契機が起きたときに実行され、deactivateはアンインストールや無効化の前に後始末をする場所です。
import * as vscode from 'vscode';
export function activate(context: vscode.ExtensionContext) {
const disposable = vscode.commands.registerCommand('helloworld.helloWorld', () => {
vscode.window.showInformationMessage('Hello World!');
});
context.subscriptions.push(disposable);
}
export function deactivate() {}package.jsonのcommandとregisterCommandの第1引数は一致させます。この対応が崩れると、コマンドは見えても実行時に失敗します。Claudeに新しいコマンドを足させるときは、両方を同時に触らせる指示にすると崩れにくくなります。
activationEventsは何を書くか
VS Code 1.74.0からは、contributesで宣言したコマンド・言語・ビュー・カスタムエディター・認証プロバイダーが呼ばれたとき、対応するonCommandなどをactivationEventsに書かなくても拡張機能が有効になります。雛形のactivationEventsが空なのはこのためです。1.74より前のバージョンを対象にするなら、onCommand:helloworld.helloWorldのように明示が必要です。
明示的に書く場面は次のとおりです。
| 契機 | 書き方の例 | 向く場面 |
|---|---|---|
| ファイルの言語 | 書き方の例onLanguage:markdown | 向く場面特定言語のファイルを開いたときに動かす |
| 起動完了後 | 書き方の例onStartupFinished | 向く場面起動直後の常駐処理 |
| 起動時 | 書き方の例* | 向く場面ほかの契機で成立しないときだけ |
*はVS Codeの起動のたびに有効化されます。ほかの契機の組み合わせで成り立たない場合にだけ使うのが前提です。onStartupFinishedは*に近い効果を持ちながら、起動を遅くしません。
次の節のCLAUDE.mdに「*を使わない」という一行を入れておくと、Claudeが雛形を広げるときの歯止めになります。
CLAUDE.mdに拡張機能開発の規約を置く
CLAUDE.mdはセッションの開始時に毎回読み込まれます。目安は200行未満で、指示は検証できる具体さで書きます。「テストを実行する」ではなく「コミット前にnpm testを実行する」という書き方です。
VS Code拡張向けに書くなら、次のような形になります。以下はClaude Codeのドキュメントの書き方の原則に沿った例で、プロジェクトごとに調整する前提のひな形です。
# VS Code拡張機能
## 構成
- エントリポイントは src/extension.ts(main は ./out/extension.js)
- コマンドは package.json の contributes.commands に宣言し、
activate 内の registerCommand で同じ ID を実装する
## ルール
- activationEvents に "*" を書かない。必要なら onStartupFinished を先に検討する
- engines.vscode と @types/vscode のバージョンを揃える
- registerCommand などの戻り値は context.subscriptions に push する
- コマンド ID は helloworld.xxx の形式で、package.json と実装を同時に変更する
## 確認
- 変更後に npm run compile を実行し、エラーが 0 件であることを確認する
- コミット前に npm test を実行する構成とルールは、Claudeが推測すると外しやすい部分です。@types/vscodeのバージョンは、engines.vscodeの値に合わせて決まります。2つがずれると、対象のVS Codeにはまだ無いAPIを型チェックが通してしまいます。
保存のたびにフォーマットを走らせる
CLAUDE.mdの規約は、Claudeが読む文脈であって強制ではありません。確実に動かしたい処理は、フックで決定的に実行します。Claude Code公式の例に、ファイルを編集するたびにPrettierを走らせるPostToolUseフックがあります。.claude/settings.jsonに次のように置きます。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
}
]
}
]
}
}Edit|Writeのマッチャーにより、ファイル編集のあとだけ動きます。雛形にPrettierは入っていないので、npm i -D prettierで追加しておきます。テストやLintをフックで回す書き分けは、hooksでテストを自動実行する方法で扱っています。
コンパイル結果のout/は編集させない
操作を確実に止めたいときは、PreToolUseフックを使います。
拡張機能の開発で止めたい代表は、out/のコンパイル結果です。out/extension.jsを直接書き換えても、次のコンパイルでsrc/の内容に上書きされます。公式のprotect-files例を、保護するパターンだけ変えて使います。file_pathは絶対パスで渡り、照合は部分一致です。そのためout/だけだとworkout/やlayout/のようなディレクトリまで止まります。前にスラッシュを付けた/out/にすると、この誤爆を避けられます。
#!/bin/bash
# .claude/hooks/protect-files.sh
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
# Windowsの区切り文字(\)を/にそろえる
FILE_PATH="${FILE_PATH//\\//}"
PROTECTED_PATTERNS=("/out/" "package-lock.json")
for pattern in "${PROTECTED_PATTERNS[@]}"; do
if [[ "$FILE_PATH" == *"$pattern"* ]]; then
echo "Blocked: $FILE_PATH matches protected pattern '$pattern'" >&2
exit 2
fi
done
exit 0終了コード2がアクションを止め、stderrのメッセージがClaudeへのフィードバックになります。スクリプトに実行権限を付け、.claude/settings.jsonにPreToolUseで登録します。
chmod +x .claude/hooks/protect-files.sh{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
}
]
}
]
}
}Claude Codeで開発ループを回す
雛形ができて規約を置いたら、依頼は小さな単位で出します。1回の依頼は、次の3段階で回ります。
1回の依頼で回す3段階
- 1
小さく依頼する
「現在時刻を情報メッセージで出すコマンドを追加する」のように、1コマンド単位で頼みます。VS Codeのチュートリアルにある練習課題でもあります。Claudeが触るのは
package.jsonのcontributes.commandsとsrc/extension.tsの2か所です。 - 2
Claudeに機械で確かめさせる
npm run compileでTypeScriptのエラーが0件であること、package.jsonのコマンドIDとregisterCommandのIDが一致していること、activationEventsに不要な追記がないことを見させます。 - 3
手元で動かす
F5でExtension Development Hostを起動し、コマンドパレットからコマンドを実行します。メッセージを書き換えたときは、新しいウィンドウで
Developer: Reload Windowを実行すると、更新後のメッセージが出ます。
動作確認用のワークスペースを拡張機能のリポジトリとは別の場所に置く構成もあります。v2.1.289のclaude --helpには、--add-dir <directories...>が「ツールアクセスを許可する追加ディレクトリ」として載っています。起動時に渡せば、Claudeがそのワークスペースも読み書きできます。
claude --add-dir ../sample-workspaceデバッグはVS Code内蔵の機能をそのまま使えます。行の左をクリックしてブレークポイントを置くと、Extension Development Host側で実行を止められます。
テストを自動化する
Extension Development Hostを人が触る確認は手元の作業です。Claudeが自分で回せる検証にするには、テストCLIを入れます。
npm install --save-dev @vscode/test-cli @vscode/test-electronpackage.jsonのscriptsに"test": "vscode-test"を足し、設定ファイル.vscode-test.jsを置きます。最小の設定は次の1行です。
// .vscode-test.js
const { defineConfig } = require('@vscode/test-cli');
module.exports = defineConfig({ files: 'out/test/**/*.test.js' });テストはExtension Development Hostの中で走り、VS Code APIを使えます。内部ではMochaが動きます。これでnpm testが、Claudeが自分で実行して出力を読める検証コマンドになります。「テストを書き、npm testが通るまで直す」という反復を、CLAUDE.mdの確認欄の一行が支えます。
versionで使うVS Codeのバージョンを指定できます。既定はstableです。
vsceでパッケージングして公開する
公開の道具はvsceです。パッケージングと公開、検索やメタデータの取得までを担うCLIです。
npm install -g @vscode/vsce
vsce package
vsce publishvsce packageは.vsixファイルを作ります。VSIXは、Marketplaceを介さずに他の人へ配る形式としても使えます。
公開の前に、パッケージの中身を整えます。
README.mdをルートに置く。Marketplaceのページに表示される内容になるLICENSEファイルを置く。package.jsonのlicenseはSEE LICENSE IN <filename>の形で参照できるCHANGELOG.mdをルートに置き、変更履歴を書く。サポート窓口の案内にはSUPPORT.mdを置くiconは128x128ピクセル以上のパスを指定する.vscodeignoreに実行時に不要なファイルを列挙する。TypeScriptなら**/*.tsが例になるscriptsにvscode:prepublishを置くと、パッケージングのたびにコンパイルが走る
README内の画像はhttpsのURLで解決できる必要があります。SVGは、信頼できるバッジ提供元のものを除いて使えません。ユーザーが用意したSVG画像を含む拡張機能は、セキュリティ上の理由からvsceが公開を拒否します。
.vscodeignoreとvscode:prepublishは、Claudeに任せてよい作業です。ただし.vscodeignoreは、除外の範囲を広げすぎると実行に必要なコンパイル済みファイルまで外れかねません。パッケージング後に.vsixの中身を確認する一手を、CLAUDE.mdに入れておくと事故が減ります。
認証は移行期にある
公開には発行元(publisher)が必要です。発行元のIDは作成後に変えられません。認証の方式は2つあります。
公開時の認証の2方式
PAT
Azure DevOpsでPersonal Access Token(PAT)を作り、vsce login <publisher id>で検証する流れです。グローバルPATは2026年12月1日に廃止されます。
Microsoft Entra ID
ワークロードIDフェデレーションとマネージドIDを使う自動公開で、長期のシークレットを持たずに済みます。公開コマンドはvsce publish --azure-credentialで、vsceは2.26.1以上が必要です。
CIからの公開を組むなら、最初からEntra IDで設計する選択肢があります。後から認証を作り直さずに済みます。
先行版の出し方
先行版を配るには、vsce package --pre-releaseかvsce publish --pre-releaseを使います。バージョンはmajor.minor.patchだけで、semverのプレリリースタグは使えません。先行版と通常版でバージョンを揃えられない点にも注意します。VS Codeは利用可能な最高バージョンへ自動更新するため、先行版を選んだ利用者も、より高い通常版が出れば通常版へ移ります。
公開前に.vsixを手元のVS Codeへ入れる
vsce packageで作った.vsixは、codeコマンドで自分のVS Codeに入れて試せます。VS Code 1.99.1のcode --helpには、拡張機能の管理用に次のオプションが並びます。
code --helpExtensions Management
--extensions-dir <dir> Set the root path for extensions.
--list-extensions List the installed extensions.
--show-versions Show versions of installed extensions,
when using --list-extensions.
--install-extension <ext-id | path> Installs or updates an extension. The
argument is either an extension id or a
path to a VSIX. The identifier of an
extension is '${publisher}.${name}'.
--uninstall-extension <ext-id> Uninstalls an extension.引数にVSIXのパスを取れるので、公開せずに配布物そのものを試せます。使い分けは次のとおりです。
--install-extension ./helloworld-0.0.1.vsix: パッケージの中身がそのまま動くかを見る。.vscodeignoreの除外が広すぎないかもここで分かる--list-extensions --show-versions: 入ったIDとバージョンを確かめる。IDは<publisher>.<name>の形--install-extension <ext-id> --force: 入っている拡張機能を最新版へ更新する。先行版を入れるときは--pre-releaseを添える--uninstall-extension <publisher>.<name>: 試し終えたら外す--extensions-dir <dir>: 拡張機能の置き場を別ディレクトリにする。一時ディレクトリを指せば、普段の環境と分けて試せる--disable-extensions: 入っている拡張機能をすべて無効にする。自作の拡張だけで再現するかの切り分けに向く--inspect-extensions <port>: 拡張機能のデバッグとプロファイリングを許可する
これらはシェルのコマンドなので、Claudeに実行させて結果を読ませられます。
よくあるつまずき
- コマンドが出ない:
engines.vscodeが手元のVS Codeより新しい、またはcontributes.commandsのIDと実装がずれている - 変更が反映されない: Extension Development Host側で
Developer: Reload Windowを実行していない engines.vscodeが拒否される:*は指定できない。^1.74.0のように最低バージョンを書く- 公開できない: READMEにSVG画像が入っている、またはREADMEの画像URLがhttpsで解決できない
まとめ
Claudeに任せる範囲は、結果が出力で読める作業、つまりコンパイル・npm test・.vscodeignoreの編集までです。ウィンドウでの操作と、.vsixを入れたあとの動きは手元で見る側に残ります。
同じ考え方で、ブラウザ側の拡張はChrome拡張をManifest V3で開発する手順、ランチャー側の拡張はRaycast拡張の開発ガイドにまとめています。