AIコーディングエージェントに読ませる「スキル」は、いつの間にかコピペで配られるのが当たり前になった。誰かのリポジトリから .claude/skills/ へフォルダごと貼り付ける。バージョンは付いていない。誰が書いたかも、貼ったあとで中身が変わったかも分からない。
qvr(quiver、astra-sh/qvr・MIT・Go製)は、そこにnpmやuvが20年かけて確立した問いを持ち込む。どこから来たのか。どのバージョンで固定されているのか。スキャンは済んでいるのか。別のマシンで同じ状態を再現できるのか。
実際に動かして、速度もスキャナの挙動も計測した。結論から言うと設計は堅実だが、既定設定のままでは危険なスキルが通る。順を追って書く。
security.block_severity = critical のまま、qvr自身の悪性テストフィクスチャを順に qvr add した実録。プロンプトインジェクション・データ持ち出し・MCPツール汚染は ✓ Added で通過し、認証情報を含むものだけが遮断される(2026-08-26 実測)- ・正体:エージェントスキルを依存関係として管理する単一バイナリCLI。Gitリポジトリをレジストリに使い、サービス常駐なし
- ・何ができる:SHA固定・実バイト列のハッシュ記録・インストール前スキャン・
qvr syncによる再現・SARIF出力 - ・実測(速度):キャッシュ済みインストールは小さいスキルで0.02秒、248KBのスキルで1.21秒。差はスキャン費用で、60倍の幅がある
- ・実測(安全性):スキャナの15カテゴリはソースと完全一致。ただし既定の遮断しきい値が
criticalのため、自前の悪性サンプル7件中3件が通る - ・対策:
qvr config set security.block_severity warningで3件とも遮断されることを確認 - ・注意:★14・fork 1・最終push 2026-06-30(約8週間停止)。設計は参考になるが本番依存は慎重に
エージェントにスキルを持たせるという枠組み自体の全体像はClaude Code|2026年版・インストールからCLAUDE.md・Hooks・本番運用までの実装手引きにまとめてある。本記事は、そこで増えていくスキルをどう管理するかという一段あとの話である。
qvrとは——スキルを「依存関係」として扱う
qvrの出発点はREADMEの1文に集約されている。スキルとは、エージェントが読み込んであなたの代わりに実行する命令とスクリプトの入ったフォルダである。だとすればそれは依存関係だ、という主張だ。
なぜスキルが依存関係なのかは、実行される内容を考えると腑に落ちる。スキルの中には自然言語の手順書だけでなく、エージェントが呼び出すスクリプトが入っていることが多い。エージェントはそれを読み、多くの場合は確認を挟まずに実行する。つまりスキルを1つ貼るという行為は、他人が書いたコードを自分の権限で走らせる許可を与えることと変わらない。npmパッケージを npm install するのと、危険度の構造は同じである。
にもかかわらず、扱いは同じになっていない。npmには package-lock.json があり、レジストリがあり、npm audit があり、パッケージの公開者が記録される。スキルにはどれも無いまま、コピペで配られてきた。数が少ないうちは目視で足りたが、1つのプロジェクトに10も20も入り始めると、どれをいつどこから持ってきたのか誰も覚えていない状態になる。
この見方をすると、扱いが変わる。依存関係にはバージョン固定が要る。出所の記録が要る。導入前の検査が要る。再現可能性が要る。qvrはこれを2つのファイルで表現する。
・qvr.toml — 意図の宣言。どのスキルをどのエージェントに入れたいか
・qvr.lock — 解決結果の記録。実際に何が入ったかの証拠
実際に生成された qvr.lock の1エントリはこうなっていた。
[[skill]]
name = 'skill-creator'
registry = 'anthropics/skills'
path = 'skills/skill-creator'
commit = '3b3fad96af16a10759d930941b4520ba0c40edae'
subtreeHash = 'sha256:33f375d0640d894bef630733f4b6f3fd119a348345f9e4fa584fa6b47313dc75'
targets = ['claude']
scan = {counts = {critical = 0, high = 0, medium = 0, low = 0, info = 0}, decision = 'allowed'}
provenance = {commitAuthor = 'CJ Avilla <[email protected]>', signatureStatus = 'none'}
qvr.lock の1エントリが持つ情報。解決結果だけでなく、検査の判定と出所まで同じ場所に記録されるcommit で解決先のSHAを固定し、subtreeHash で実際にインストールされたバイト列を記録する。この2つがあると「レジストリ側でタグが張り替えられた」「ディスク上のファイルが後から書き換えられた」のどちらも検出できる。scan にはスキャン結果の判定が、provenance にはコミット作者と署名の状態が入る。
意図の宣言"] --> B["resolve
SHAへ解決"] B --> C["scan
15カテゴリで検査"] C --> D{"しきい値を
超えたか?"} D -- "超えた" --> E["インストール中止"] D -- "超えない" --> F["immutable install
SHA-keyed"] F --> G["symlink を張る
エージェントの読み先へ"] G --> H["qvr.lock
解決結果と証拠を記録"]
この5段階のうち、既存のスキル運用に欠けているのは resolve と scan の2つである。コピペで貼る運用にも install と読み取りは存在するが、どのバージョンに解決したのかを記録する工程と、入れる前に中身を検査する工程は存在しない。qvrが足しているのは本質的にこの2つだと考えてよい。
保存方式も特徴的だ。レジストリごとに1つのbare cloneを持ち、スキルごとにsparse worktreeを切る。インストールはSHAをキーにした不変(immutable)な実体で、同じSHAに固定された2つのプロジェクトはディスク上で1つの実体を共有する。バージョンの切り替えは再cloneではなくシンボリックリンクの張り替えになる。
そしてエージェントから見た読み取り経路は、単なるシンボリックリンクである。エージェントがスキルを読むとき、gitもネットワークもqvr自身も介在しない。インストールが終わった瞬間にツールは経路から消えるという設計になっている。
① 何ができる:スキルをSHAで固定し、スキャンを通し、別マシンでバイト単位で同じ状態を再現できる。
② 何を解決する:コピペ配布で失われる「出所・バージョン・検査済みかどうか」の情報を取り戻す。
③ 何を代替できる:手作業でのスキルフォルダのコピーと、その運用ルールをドキュメントで縛る運用。
qvrの実測:インストール速度は何で決まるか
READMEはトップに性能のグラフを置いている。「名前を指定した部分インストール」で、初回(cold)が約1.3秒、キャッシュ済み(warm)が約0.02秒。比較対象として skillshare(1.62 / 0.44秒)、apm(3.25 / 1.97秒)、asm(8.31秒)が並ぶ。
実際に測った。環境はApple Silicon・Go 1.26.5で、レジストリには anthropics/skills を使った。
| 操作 | 対象 | 実測 |
|---|---|---|
qvr registry add(bare clone + 19スキルの索引) |
anthropics/skills | 1.29秒 |
cold qvr add(初回) |
skill-creator | 3.15秒 |
warm qvr add(キャッシュ済み・既定) |
skill-creator(248KB / 18ファイル) | 1.21秒 |
warm qvr add(キャッシュ済み・既定) |
clean-skill(4KB / 1ファイル) | 0.02秒 |
warm qvr add --no-scan |
skill-creator | 0.02秒 |
qvr sync(同期済み) |
— | 1.19秒 |
いずれも3回計測してすべて同じ値だった。バイナリの起動自体は0.01秒なので、差は起動コストではない。
読み取れることは明快だ。キャッシュ済みインストールの時間は、スキルの中身の量でほぼ決まる。小さいスキルなら公称値どおり0.02秒で終わる。248KBのスキルでは1.21秒かかる。--no-scan を付けると後者も0.02秒になることから、差分のほぼ全部がインストール前に走るセキュリティスキャンの費用だと分かる。
公称値の0.02秒が嘘というわけではない。小さいスキルなら、スキャンを有効にしたままその値が出る。問題は benchmark.svg に計測条件が何も書かれていないことだ。どのレジストリの、何個の、どれくらいの大きさのスキルを、どんなハードウェアで、何回試したのかが分からない。読者は自分のスキルがグラフのどちら側に来るのかを判断できない。
これは速度そのものより、比較グラフとしての扱いに関わる。競合3ツールと横並びにするなら、条件の明示は要るはずだ。とはいえ、スキャンを掛けたうえで248KBのスキルが1.21秒というのは十分に速い部類ではある。
qvrのスキャナは何を見て、どこで止めるのか
qvrがスキルを「依存関係」と呼ぶ根拠は、インストール前に必ず走るスキャナにある。READMEは「15カテゴリの検出分類」を掲げる。
まずこの数字を確認した。internal/security/rules.go の Category 定数を数えると、ちょうど15個だった。
| # | カテゴリ | # | カテゴリ |
|---|---|---|---|
| 1 | prompt_injection | 9 | tool_misuse |
| 2 | system_prompt_leakage | 10 | rogue_agent |
| 3 | data_exfiltration | 11 | harmful_content |
| 4 | privilege_escalation | 12 | trigger_abuse |
| 5 | supply_chain | 13 | yara_match |
| 6 | excessive_agency | 14 | mcp_least_privilege |
| 7 | output_handling | 15 | mcp_tool_poisoning |
| 8 | memory_poisoning |
公称値と実装が一致している。この15カテゴリを、10個の検査モジュール(scanner.go に登録されている)が埋める構成だ。スキャナは何も実行しない——すべてのファイルを文字列として読み、実行ビットは尊重せず報告するだけ、と明記されている。
動作も確認した。リポジトリには testdata/ に悪性スキルのフィクスチャが揃っているので、そのままスキャンに掛けられる。
git clone https://github.com/astra-sh/qvr.git && cd qvr
go build -o qvr ./main.go
./qvr scan testdata/clean-skill # 対照群:Scan clean (10 checks ran) / exit 0
./qvr scan testdata/malicious-skill-secrets # critical 4件 / exit 1
対照群の clean-skill は正しく「clean」と判定され、認証情報を埋め込んだフィクスチャでは AWSアクセスキー・GitHubトークン・JWT・PEM秘密鍵の4件をすべて critical として検出した。検出そのものは、きちんと機能している。
検出の粒度も見ておきたい。プロンプトインジェクションのフィクスチャを掛けると、ルールIDと行番号つきで8件が返ってくる。
SEVERITY CHECK LOCATION MESSAGE
warning prompt_injection SKILL.md:8 P11: role-reassignment phrasing
warning prompt_injection SKILL.md:9 P1: instruction-override phrasing
warning prompt_injection SKILL.md:14 P12: tool-invocation coercion
warning prompt_injection SKILL.md:14 P9: forged conversation boundary or chat-template token
warning prompt_injection SKILL.md:18 P13: outbound HTTP call with query-string payload
warning prompt_injection SKILL.md:21 P10: known jailbreak phrase
「役割の再指定」「指示の上書き」「ツール呼び出しの強要」「偽の会話境界やチャットテンプレートのトークン」「既知のjailbreak語句」と、プロンプトインジェクションの手口が個別のルールに分解されている。行番号が出るので、指摘された箇所を自分で読んで判断できる。
特徴的な検査として mcp_least_privilege がある。これはSKILL.mdで宣言された権限と、コードが実際に使っている能力を突き合わせるもので、データ持ち出しのフィクスチャではこう出た。
error mcp_least_privilege scripts/exfil.py:3 LP1: code exercises env_access capability, but no matching permission declared in SKILL.md
error mcp_least_privilege scripts/exfil.py:5 LP1: code exercises network capability, but no matching permission declared in SKILL.md
warning mcp_least_privilege SKILL.md LP3: skill exercises capabilities (env_access, network) but declares no permissions
宣言していない能力を使っているコードを名指しする。「読めば分かる」ではなく「宣言と実装の差」を機械的に出すという方向は、スキルのスキル管理という文脈では正しいアプローチだと思う。
「スキャン済み」の穴を自分で申告する
もうひとつ、設計者の姿勢が出ていると感じた検査がある。coverage チェックだ。
スキャナには1ファイルあたりのサイズ上限がある。上限を超えたファイルは中身が読まれない。バイナリファイルも、誤検出を避けるため文字列検査の対象外になる。ここで素朴に実装すると、読まれなかったファイルがあっても「Scan clean」と表示されてしまう。
qvrはこれを検査対象にしている。ソースのコメントには、そのものずばりの経緯が書かれていた。1MiBの上限を1バイト超えるパディングを入れるだけで全検査が盲目になり、しかもレポートに何の痕跡も残らなかった——というのが Issue #34 の内容だという。
さらに続きがある。当初この指摘は情報レベル(info)で出していたが、既定の --fail-on=error では拾われないため、Issue #44 を受けて警告(warning)へ格上げされた。--fail-on warning を使うCIなら、走査できなかったファイルの存在でジョブを落とせる。
COV_OVERSIZE file X skipped: size N B exceeds the per-file scan cap;
patterns/unicode/permissions did not read it
COV_BINARY file Y skipped: binary content; text-pattern detectors did not run
「スキャンした」の範囲を自分で申告するという設計は、セキュリティツールとして信頼できる態度である。前節で指摘した遮断しきい値の既定値とは対照的に、ここは丁寧に作られている。
既定設定では、自前の悪性サンプル7件中3件が通る
問題は検出ではなく、どこで止めるかにある。設定を確認すると、遮断しきい値の既定値はこうなっていた。
$ qvr config get
security.scan_on_install = true
security.block_severity = critical # ← ここ
security.require_scan = false
security.require_signed = false
block_severity = critical は、critical 判定が出たときだけインストールを止めるという意味である。error や warning にとどまる検出は、記録はされるが通過する。
これが実際にどう効くのかを確かめるため、testdata/ の悪性フィクスチャをローカルのgitレジストリに登録し、既定設定のまま qvr add した。結果はこうなった。
| テストフィクスチャ | 検出内容 | 既定設定での結果 |
|---|---|---|
| malicious-skill-injection | critical 0 / medium 8 | ✓ 通過 |
| malicious-skill-data-exfil | critical 0 / error 6 | ✓ 通過 |
| malicious-skill-mcp-poisoning | critical 0 / error 2 | ✓ 通過 |
| malicious-skill-secrets | critical 4 | ✖ 遮断 |
| malicious-skill-unicode | critical 2 | ✖ 遮断 |
| malicious-skill-rogue-agent | critical 1 | ✖ 遮断 |
| malicious-skill-supply-chain | critical 1 | ✖ 遮断 |
block_severity = critical での実測。critical が0件なら、error や medium の検出があってもインストールは止まらない7件のうち3件が通った。しかも通ったのは、READMEが冒頭で脅威として名指ししているプロンプトインジェクション・データ持ち出し・MCPツール汚染の3つである。
画面表示はさらに問題を分かりにくくする。プロンプトインジェクションのフィクスチャを既定設定で追加したときの出力は、これだけだった。
✓ Added malicious-skill-injection@main → claude
警告は表示されない。8件の検出があったことは画面には出ず、qvr.lock を開いて初めて分かる。
スキャンは確かに毎回走り、結果は
qvr.lock に decision = 'allowed', counts = {medium = 8} として正確に記録される。隠蔽はしていない。しかし既定のしきい値では、止めない。「スキャンされている」と「守られている」を同じ意味に受け取ると危険である。
擁護すべき点もある。qvrは判断を隠さない。--no-scan でスキャンを飛ばした場合も、lockには decision = 'skipped', reason = '--no-scan' と記録される。監査証跡としては誠実な設計だ。あとから「このスキルは検査されずに入った」と追跡できる。
対策:1行で塞げる
しきい値を下げれば解決する。実際に効くことを確認した。
# 現在のしきい値を確認する(既定は critical)
qvr config get security.block_severity
# warning まで遮断する
qvr config set security.block_severity warning
変更後に同じ3件を再度インストールしようとしたところ、3件とも exit 1 で遮断されるようになった。
error: add malicious-skill-injection: scan blocked (max warning ≥ threshold)
error: add malicious-skill-data-exfil: scan blocked (max error ≥ threshold)
error: add malicious-skill-mcp-poisoning: scan blocked (max error ≥ threshold)
CI側でも --fail-on で同じ制御ができる。qvr scan --fail-on warning とすれば、パイプラインでプロンプトインジェクションを検出したときにジョブを落とせる。
qvrを導入するなら、この1行を最初に実行しておくのが実質的な必須手順だと考えている。既定値のままでは、スキャナが持つ検出能力の相当部分が遮断に結びつかない。
再現性と監査証跡——lockfileが本体である
qvrの本命はスキャナよりも、むしろlockfileのほうだ。
qvr lock verify --strict # ロックの全エントリがディスクと一致するか検証する
qvr sync --frozen # 差分があれば失敗し、何も変更しない(CIゲート向け)
手元で実行すると ✓ skill-creator: ok と Summary: 1 ok, 0 drift, 0 unverified, 0 missing, 0 link, 0 failed が返り、終了コードは0だった。sync --frozen も ✓ Already in sync で通った。
再現性を敵対的な条件で確かめる
「別のマシンでバイト単位で同じ状態を再現する」というのは、パッケージマネージャが掲げる主張のなかで最も検証しづらいものだ。うまくいく条件で試せば当然うまくいくので、意地の悪い条件を作って確かめた。
作った状況はこうだ。まずローカルのgitリポジトリをレジストリとして登録し、スキルを1つ入れてlockを作る。次にレジストリ側にコミットを追加してHEADを前進させる。lockに記録されたコミットは、もはや最新ではなくなる。そのうえでキャッシュを完全に削除し、プロジェクトのスキルディレクトリも消して、qvr.toml と qvr.lock だけが残った「まっさらな別マシン」の状態を作った。
ここで気になるのは、qvrが登録時に作るクローンが浅い(shallow)という点である。実際に確認すると、既定の registry add で作られるbareクローンはコミット1個分しか持っていなかった。この状態では、lockに記録された過去のコミットはローカルに存在しない。
結果はこうなった。
$ qvr sync
✓ Synced — 1 restored
$ qvr lock verify --strict
✓ clean-skill: ok
Summary: 1 ok, 0 drift, 0 unverified, 0 missing, 0 link, 0 failed
再現できた。中身を確認すると、HEADの最新版ではなくlockに記録されたコミット時点の内容が復元されていた。仕組みを追うと、qvrはlockのコミットが浅いクローンに含まれていない場合、クローンを深く取り直している。同期後のbareクローンはshallowでなくなり、コミット数も増えていた。
必要になったときだけ履歴を取りに行き、普段は浅いまま速く保つ。謳っている再現性は、意地悪な条件でも成立した。パッケージマネージャとしての中核はきちんと動いている。
ただし関連する制約はある。既定の registry add はデフォルトブランチしか取らず、タグや他のブランチは取得しない。したがって qvr add <skill>@v1.2.0 のようにタグを指定したインストールは、そのままではできない。タグで固定したいなら、レジストリ登録時に --full(全ブランチ・全タグ・全履歴)または --depth N を指定して登録し直す必要がある。READMEのクイックスタートは qvr add [email protected] を並べているので、ここは実際に試すと引っかかる箇所だ。
設計として効いていると感じたのは qvr sync の副作用のほうである。lockに載っていないものは、エージェントから見えなくなる。エージェントのスキルディレクトリに手で置かれたフォルダがあっても、syncを走らせるとエージェントの読み取り対象から外れる。「lockfileだけが唯一の真実」という原則を、ディレクトリの状態そのもので強制している。
この性質は、チームで使うときに効く。誰かが自分のマシンにだけスキルを足しても、それはPRに現れないので他のメンバーには伝播しない。逆に、正式に足したいなら qvr.toml を編集してPRを出すことになる——スキルの追加がコードレビューの対象になる。
qvr trust pin によるレジストリ単位のコミット作者許可リスト、qvr provenance <skill> による出所確認、スキャン結果のSARIF出力(コードスキャン基盤への取り込み)も揃っている。エージェントスキルという新しい対象に対して、既存のサプライチェーン管理の道具立てを一通り移植した格好だ。
なお、外部から取り込んだスキルの出所を記録するという発想自体は、ツールを使わなくても実践できる。COG second brainとは|33スキルと検証ハーネスをMarkdownだけで回す第二の脳を実測は、取り込んだスキルのディレクトリに出所・ライセンス・取り込み日・無改変かどうかを書いた SOURCE.md を手作業で置いている。qvrはそれを機械化したもの、と捉えると位置づけが分かりやすい。
手作業やgit submoduleと何が違うか
スキル管理の手段として、qvrを使わない選択肢もある。それぞれの限界を整理しておく。
手でコピーするのが現状の主流だ。手軽さは最強だが、バージョンの記録が残らない。上流が更新されても気づけず、逆に自分で書き換えた箇所があっても上流との差分が分からなくなる。何より、貼る前に中身を検査する工程が存在しない。
git submodule を使うと、コミット単位の固定はできる。ただしsubmoduleが固定するのはリポジトリ全体であって、その中の1スキルだけを取ることはできない。スキル1つのために巨大なリポジトリ全体を抱え込むことになる。エージェントごとに読み込み先ディレクトリが違う問題も解決しない。そして検査は依然として無い。
qvrが足しているものは3つある。ひとつはリポジトリ内の特定サブディレクトリだけを取り出す粒度(sparse worktree)。ふたつめは導入前の検査と、その判定をlockへ記録すること。みっつめは、複数のエージェントへの配置先をプロジェクト設定として共有することだ。
| バージョン固定 | スキル単位の粒度 | 導入前検査 | 配置先の共有 | |
|---|---|---|---|---|
| 手でコピー | ✖ | ✅ | ✖ | ✖ |
| git submodule | ✅ | ✖(リポジトリ単位) | ✖ | ✖ |
| qvr | ✅ | ✅ | ✅ | ✅ |
この表だけ見ればqvrが優位に見えるが、手でコピーする方式には「何も壊れない」という強い利点がある。ツールが1つ増えるということは、そのツールが壊れたときに全員が止まるということでもある。★14のプロジェクトを配布経路に据えるかどうかは、この点を天秤にかける話になる。
出力がエージェント向けに設計されている
細かいが効く設計として、すべてのコマンドが --output json に対応している点を挙げておきたい。構造化データは標準出力へ、診断メッセージは標準エラーへ分けて出し、終了コードにも意味を持たせている。
これは人間のためというより、エージェント自身にqvrを操作させるための配慮である。エージェントがスキルを探し、入れ、検査結果を読んで判断する——という使い方を想定している。qvr docs で導入済みスキルから AGENTS.md を生成するコマンドがあるのも同じ発想だ。エージェントの道具をエージェントが管理する、という循環を最初から見込んでいる。
実験的な位置づけながら qvr audit というコマンドもあり、どのスキルに起因する活動だったかを記録して問い合わせられる。スキルが増えたときに「これは何のために入れたのか」を後から辿るための仕組みで、方向としては正しい。ただし現時点では experimental の表記どおり、本格的に頼るものではない。
導入判断——設計は良い、規模は小さい
ここまで設計の話をしてきたが、採用可否の判断材料は別にある。
| 指標 | 値(2026-08-26時点) |
|---|---|
| GitHub スター | 14 |
| フォーク | 1 |
| オープンIssue / PR | 5 / 0 |
| 最終push | 2026-06-30(約8週間前) |
| 最新バージョン | 0.30.0 |
| 言語 / 必要環境 | Go(go.mod は go 1.25.0) |
★14・fork 1というのは、実質的に個人プロジェクトの規模である。最終pushから約8週間空いているのも、活発とは言いにくい。README自体はよく書かれており、コードもテストフィクスチャも整っているが、社内のスキル配布基盤を全面的にここへ預けるのは時期尚早だと考える。
8週間という空白をどう読むかは、対象領域の速さによる。エージェントのスキル周りは仕様そのものが動いている領域で、この間にもスキルの配布形式やプラグインの標準は変化し続けている。追随を前提にした道具が追随を止めているという状態は、単に更新が遅いという以上の意味を持ちうる。
もっとも、悲観的に読みすぎる材料でもない。0.30.0までの開発履歴を見ると、直近では「skill-run の可観測性」「外部グレーダーによる品質スコア」「スキルの自己改善ループ」といった、基礎機能ではなく応用寄りの追加が並んでいる。土台の部分——解決・ロック・インストール・スキャン・同期——は一通り出来上がったあとで手が止まっている格好で、未完成のまま放置されたものとは様子が違う。実際、本記事で試した範囲では動かない機能に当たらなかった。
判断としては、「今日から配布基盤にする」ではなく「動きを見つつ、良い部分だけ先に借りる」が妥当なところだと思う。
一方で、次のような使い方なら現時点でも価値がある。
・設計の参考にする:qvr.lock のスキーマ(commit / subtreeHash / scan / provenance の4点セット)は、自前で管理する場合にもそのまま真似できる
・スキャナ単体で使う:qvr scan <path> は、qvrでインストールしていないスキルにも掛けられる。他人のスキルを .claude/skills/ へ貼る前の検査に使う
・個人プロジェクトで試す:スキルを複数マシンで揃えたい、という個人用途なら規模のリスクは小さい
とくに2番目は今日から使える。手元にコピペで入れたスキルがあるなら、次のコマンドで一度掛けてみる価値がある。
# 手元のスキルディレクトリを検査する(warning 以上で終了コード1)
qvr scan ~/.claude/skills/<skill-name> --fail-on warning
似た系譜では、gstackとは|Garry Tan流Claude Codeスキル集『AIソフトウェア工場』の全貌やbook-to-skill解説|技術書を Claude Code スキルに変換するOSSの実力と限界のように「スキルを作る・配る」側のOSSが先に増えた。qvrはその後段——増えたスキルをどう管理するか——を担おうとしている。この層自体はこれから必要になるはずで、qvrが定着するかどうかとは別に、問題設定は正しい。
まとめ
スキルを依存関係として扱うという問題設定は正しく、
qvr.lock の設計(解決SHA・実バイト列のハッシュ・スキャン判定・コミット作者)は完成度が高い。15カテゴリの検出分類は実装と完全に一致し、スキャナは同梱フィクスチャに対して正しく反応した。lockに載っていないものをエージェントから隠すという強制のかけ方も筋が良い。ただし既定設定では守れていない。遮断しきい値が
critical のため、qvr自身の悪性サンプル7件のうち、プロンプトインジェクション・データ持ち出し・MCPツール汚染の3件がそのまま通り、画面には ✓ Added としか出ない。導入するなら qvr config set security.block_severity warning を最初に打つべきである。結論:★14・最終push 8週間前という規模を踏まえると、いま全面採用する対象ではない。スキャナ単体を手元のスキル検査に使う、あるいは lockfile の設計を自前運用の参考にする、という使い方から始めるのが現実的である。
参照ソース
・astra-sh/qvr(公式リポジトリ・README) — 設計思想・性能グラフ・「15-category detection taxonomy」等の公称値の出典
・internal/security/rules.go — 検出カテゴリ15種の定義。公称値との一致を確認した一次情報
・internal/security/scanner.go — 登録されている10個の検査モジュール
・testdata/ — 遮断しきい値の検証に用いた悪性スキルのテストフィクスチャ一式