スキルが増えると、静かに事故る
Claude の Agent Skills(SKILL.md)を書き始めたころ、私は快適だった。困ったのはスキルが 5 個、10 個と増えてからだ。二つの不安が出てきた。
一つは参照の腐り。SKILL.md に「前処理は scripts/extract.py を使う」と書いたのに、あとでそのスクリプトをリネームした。SKILL.md は古いパスを指したまま。誰も気づかない。エージェントはそれを信じて動く。
もう一つがもっと厄介で、スキル同士の「発火の被り」だ。スキルは description のトリガ文で呼ばれる。似た説明のスキルを二つ置くと、同じ入力でどちらが起動するか分からなくなる。しかも静かに、間違ったほうが動く。
この二つを PR の時点で機械的に落とすツール、skills-lint を作った。
まず既存ツールを探した。そして、被っていた
正直に書く。作る前に「どうせ誰か作ってるだろう」と思って探した。案の定、トリガの衝突検出は pulser のような先行ツールが既にやっていた。有料の SkillCheck にも同種の機能がある。ここで私は一度がっかりした。
ただ、よく見ると隙間があった。衝突を見るツールは参照整合を見ない。参照整合を見るツールは npm 依存だったり有料だったりする。「参照整合 + 衝突検出 + 依存ゼロ + 無料で毎 PR 走る」を一本に束ねたものは、無かった。
だから束ねた。それが skills-lint だ。
何を落とすか
検出するのは三つ。
-
参照整合: SKILL.md 本文の
`scripts/run.py`や[doc](references/guide.md)が実在するか。言語は問わない。 -
frontmatter:
nameが小文字ハイフン形式か、description(発火トリガ)があるか。 -
衝突: 二つのスキルで
nameが重複していないか(インストール衝突)、descriptionが近すぎないか。
三つ目の「近すぎ」の判定には、文字バイグラムの類似度(Jaccard)を使った。形態素解析のような重い依存を持ち込まずに済むし、日本語のトリガ文でも効く。しきい値は 0.7 と高めにして、誤検出を潰した。
わざと似せた二つのスキルを食わせると、こうなる。
✗ examples/bad/summarizer-a/SKILL.md — 1 件
:4 参照 `scripts/missing.py` が存在しません
examples/bad/summarizer-b/SKILL.md:1 description が summarizer-a と高類似 (0.94) — 同じ入力で取り違える恐れ
exit code: 1
腐った参照と、類似 0.94 の衝突を同時に捕まえて、CI を落とす。これだけだ。
使い方
ローカルなら 1 行。
npx @hyuga/skills-lint # .claude/skills / skills を自動探索
CI に置くなら GitHub Action で。
- uses: hyuga611/skills-lint@v1
with:
paths: .claude/skills
指摘は PR にインライン注釈で出て、ジョブが落ちる。人が意識しなくても毎 PR 走る。これが定着の本体だと私は思っている。
姉妹ツール reflint も同じ日に更新した
skills-lint は、以前作った reflint(AGENTS.md / llms.txt の参照整合リンタ)と同じ型で作っている。依存ゼロ、純粋関数の scan と CLI を分離、GitHub Action、PR 注釈。
その reflint も同じ日に v0.2.0 を出した。llms.txt の中の markdown リンク先が、リポジトリ内に実在するかを検証する機能だ。既存の llms.txt ツールはフォーマット検査か外部リンクの死活まで。リポ内の参照整合を CI で見るものは、見当たらなかった。ここは競合がいない。
- reflint: https://github.com/hyuga611/reflint
持ち帰り
スキルが増える時代、一番静かな事故は「発火の被り」と「腐った参照」だ。目視では気づけない。だから CI で落とす。
- リポジトリ: https://github.com/hyuga611/skills-lint
-
npx @hyuga/skills-lintで今すぐ試せる
同じ痛みを持っている人がいたら、ぜひ。