Quartet / Quintet

2026-09-05  ·  設計 · typescript · cli · claudecode · 個人開発

linter を作るとき、何を検査しないかで品質が決まる

設定ファイルの検査ツールを作りました。作る過程で、いちばん時間を使ったのは ルールを足すことではなく、足さないと決めることでした。

検出できる項目は、いくらでも思いつきます。問題はそこではありません。

誤検出が1件あると、ツール全体が捨てられる

検査ツールには非対称性があります。

そして誤検出を1度でも踏むと、次からは全部の指摘を疑って読むようになります。 2度踏んだら、CI から外されます。

つまり、正しい設定を「壊れている」と言うツールは、無いほうがマシです。 検出項目を10個から30個に増やして誤検出が1件混ざるくらいなら、10個のままがいい。

これは頭では分かっていても、実装中は逆に振れます。「ここも検査できるな」と 思いつくたびに、足したくなるからです。

根拠を書けないルールは足さない

そこで、ルールを足す条件を1つに決めました。

公式ドキュメントに「エラーになる」「スキップされる」「無視される」と 書かれているものだけを検査する。

書かれていなければ、どれだけ「たぶん間違い」に見えても足しません。

この条件が効くのは、判断が自分の記憶から切り離される点です。 「たしかこの書き方は駄目だったはず」は、根拠になりません。 自分の記憶は、対象のバージョンが上がった瞬間に古くなります。

実装では、指摘ごとに根拠の文字列を持たせました。

export interface Finding {
  severity: "error" | "warn";
  file: string;
  line?: number;
  /** 何が問題か。1文 */
  message: string;
  /** なぜそう言えるか。出典 URL を含める */
  because: string;
}

because を必須の項目にしたのが要点です。根拠を書けないルールは、 型の上で書けません。 出典を思いつかないものは、そもそも実装できない。

テストにもこれを入れました。

for (const f of findings) {
  if (!f.because.includes("https://")) fail(`出典なし: ${f.message}`);
}

出典 URL が無い指摘が1つでもあれば、テストが落ちます。

検査しないと決めた4つ

実際に「できるけどやらない」と判断したものを書きます。 どれも、検出そのものは簡単です。

1. 未知のキー

設定ファイルの JSON スキーマは公開されています。それと突き合わせれば、 知らないキーを「タイポでは?」と指摘できます。

やりませんでした。公式ドキュメントにこう書いてあったからです。

スキーマは最新の CLI リリースに遅れることがあるため、最近文書化された キーに検証警告が出ても、その設定が無効であることを意味しません

つまり、新機能を使った人ほど誤検出を踏みます。 一番熱心な利用者から 順にツールを捨てていく設計になります。

2. モデル名の値

model に指定できる値を検査したくなります。しかし、有効な値の網羅リストが ドキュメントに存在しませんでした。例としていくつか挙がっているだけです。

例は網羅ではありません。 例に載っていない値を「無効」と判定する根拠には ならない。ここも落としました。

3. ファイルパスのパターン

権限設定のパスは、先頭の記号でアンカーが4通りに変わります。

書き方 意味
//path ファイルシステムのルートから
~/path ホームディレクトリから
/path 設定ファイルの位置から(絶対パスではない)
path カレントディレクトリから

3番目が特に紛らわしく、/Users/alice/file と書いても絶対パスになりません。 これに gitignore 式のグロブが重なります。

正しく実装できる自信が持てなかったので、検査対象から外しました。 自信が持てないものを検査すると、誤検出はそこから出ます。

4. 真偽値を true / false に限ること

frontmatter の真偽値です。truefalse だけを許して、それ以外を エラーにしたくなります。

ドキュメントを読むと、yes / no / on / off / 1 / 0 も有効でした。 YAML の仕様としては当然ですが、確認せずに実装していたら誤検出になっていました。

「たぶん違反」を出さない

判断がつかないものは、警告も出さずに黙って通します。

「念のため情報として出しておく」は、一見親切ですが、利用者から見ると 判断を押しつけられているのと同じです。出力が長くなり、本当に見るべき error が埋もれます。

深刻度も2段階だけにしました。

info を作らなかったのは、作ると「判断がつかないもの」の逃げ場になるからです。 逃げ場があると、迷ったものが全部そこに積まれます。

検証は「壊れた入力」より「正しい入力」を重く見る

テストは2種類の入力で行いました。

入力 期待 結果
わざと壊した設定 検出すること 22件
正しい設定 1件も出さないこと 0件

重要なのは下です。しかも「正しい設定」には、わざと引っかかりそうなものを 混ぜました。

このどれかで1件でも出たら、設計が間違っているという合図です。

テストの失敗メッセージも、この非対称性に合わせました。

FALSE POSITIVES on a valid tree: 1

見逃しは「検出されず」と淡々と出しますが、誤検出は目立つ文言にしています。 どちらを先に直すべきかを、出力の側で決めてしまう。

まとめ

検出力を上げるより、黙って通す範囲を決めるほうが難しいというのが、 作ってみての実感です。

この運用そのものを配っています

4 人格版 Quartet は MIT で無料公開しています。UI 設計人格・レビュー基準・ Issue 単位の並列実行スクリプト・実践ガイド 10 章を足した Quintet は有料版です。

無料版を見る 製品ページ