GrooveSeek

Semantic search over a Markdown knowledge base, served over MCP.

View the Project on GitHub alphabet-h/grooveseek

10. ADR-0008 が残した command line の 3 つの未決事項を決める

Date: 2026-08-21

English: 0010-settle-what-the-1-0-command-line-freezes.md

Status

Accepted

Context

ADR-0008 は 1.0.0 が何を凍結するかを 書き下した。だが全部を決めたわけではない。3 つの問いを名指しして、明文で保留したdocs/stability.ja.md はそのうち 1 つについて「未決であり、この段落はそれを 凍結しない … その天秤を 1.0.0 までに決める (1.0.0 が決めてしまうのではなく)」 と書いている。

保留は忘却ではなく、期限がある。開いたまま 1.0.0 のタグを切っても問いは 開いたままにはならない — たまたまコードがそうなっている方向で 3 つとも 答えたことになり、しかもその答えを後から覆すのが高くつく。

3 つとは:

  1. groove validate --strict が受理され、そのまま捨てられている。main.rsstrict: _strict で束ね、docs/usage.md は “currently a no-op” と文書化して いた
  2. status / service status / service list が出力を stderr に書くので、 groove status | grep Documents には何も流れない。
  3. 同じ値を設定するフラグと違う語を使っている設定キーが 3 つある: [quality_filter].threshold--min-quality / MCP の min_quality[eval].k_values--k[transport].kind--transport

Decision Drivers

1.0.0 の前は、自由が片方向にしかない。 どれも今なら無料で変えられ、後からは major でしか変えられない。それは 3 つとも変える理由ではなく、1 つずつ 「変えた後にどちらの道が残るか」を問う理由である:

  1.0.0 の後
--strict を削除する 無料 major
--strict に意味を与える 無料 major
--strict を足し直す minor
結果を stdout に移す 無料 major
設定キーの名前を消す 無料 major
設定キーの名前を足す minor (stability.ja.md)

3 つのうち 2 つは 1.0.0 で永久に閉じる。3 つ目は閉じない。その非対称が 答えを決める。

Decision

--strict を削除する

受理され、文書化され、捨てられるフラグは、無いより悪い。 --strict を付けた CI ジョブは「厳しく検査するよう頼んだ」と信じていたが、実際には頼めておらず、 そのことを誰も教えなかった。実装するのは feature — [options].allow_unknown_fields とその裏のスキーマ作業 — であって「1.0 が何を 約束するか」の判断ではない。no-op のまま凍らせれば、後から名前どおりの意味を 与えるのが major になる。

今削除すれば、安い方の道が残る: feature が入った時点でフラグを足し直すのは minor で済む。付けているスクリプトは parse に失敗するようになるが、それは 今まで黙って起きていたことが目に見える形になっただけである。

status / service status / service list は結果を stdout に出す

この 3 つが印字しているのは、問われたことへの答えである。 答えが人間向けで あることは、それを進捗にしない。docs/stability.ja.md は他の全コマンドについて 既に「結果は stdout」を凍結している。

答えでないものはすべて stderr に残る: index の進捗、 service install / uninstall / tray-install / tray-uninstall の確認 (行った動作の報告であって、問われたことへの答えではない)、そして status の “No index found” (答えられないことの報告なので stdout は空)。

凍結するのは出力先だけ。これらの行の文面は非安定のままで、status が最初に 出す 2 つの数を機械可読に取る経路は groove doctor --format json のままである。

設定キーの名前は変えない

実測すると、3 つの名前に当ててみた瞬間に改名の根拠が消える:

キー フラグ 改名先
[transport].kind --transport 無いtransport.transport は名前ではない。kind は TOML の section が自分の variant を呼ぶ語
[eval].k_values --k [eval].k悪化。数か月後に読むファイルの中の裸の k は何も言わない。--k が読めるのは、command line が書きながら読まれるからにすぎない
[quality_filter].threshold --min-quality quality_filter.min_quality は語がだぶる。section 込みで読めば現行名は既に「それが何か」を言っている

設定キーは section 込みで読まれ、フラグは単独で読まれる。 だから同じものを 指していても綴りは揃わない。揃えることを要求すると、少なくとも 2 つは名前が悪くなる。

そしてこれは1.0.0 で閉じない唯一の問いでもある。docs/stability.ja.mdキーの追加を minor としているので、より良い名前は 1.x の途中でも導入できる。 閉じるのは「古い名前を消す」道の方で、そちらはいずれにせよ選ばない

改名の代償は仮定ではない: 未知キーは reject されるので、改名した瞬間に既存の groove.toml が起動しなくなるexamples/deployments/ 配下の 4 本のレシピと groove.toml.example はすべて [quality_filter] を設定しており、これを設定して きた全ユーザのファイルが、バイナリが動く前に編集を要求される。

Consequences

groove status | … に件数が流れるようになり、stderr だけを捕まえていた側には 何も届かなくなる。2>&1 で受けている側は影響を受けない。3 つのうち、既に 壊れていなかった人の挙動が変わるのはこれだけである。

groove validate --strict は parse に失敗するようになる。 documented_flags のテストが両方向で削除を担保する — バイナリが受け付けるフラグは散文に現れねば ならず、散文はバイナリが持たないフラグを名指ししてはならない。

3 つのキー名は現行の綴りで 1.0.0 に凍結される。 quality_filter.threshold が 間違った名前だったと分かったときの 1.x での直し方は、より良い名前を足すことで あって、この名前を動かすことではない。

docs/stability.md は command line について何も保留しなくなる。 出力先の問いを 未決と書いていた段落が答えを述べるようになるので、次の読者が現在の挙動を 「まだ天秤にかけている途中の偶然」と取り違えることがない

Alternatives considered

--strict を削除せず実装する。 ドキュメントを読んだユーザが期待する結末では ある。答えが間違っているのではなく scope の誤りとして却下: [options].allow_ unknown_fields とその裏のスキーマ作業が要り、それは独自の設計を持つ feature で、 そのために 1.0.0 を止めるのは期限のある約束を期限の無い約束に取り替えることに なる。

--strict を documented な no-op として凍結する。 バイナリの実態には正直で、 何も壊さない。却下 — フラグが永久に無用になるから。1.0.0 のタグが切られた後は、 名前どおりの意味を与えるのが major になるので、それを正当化する feature が 1.x では二度と入れられない

status を stderr に残し、それを凍結する。 何も壊れず、テスト 4 ファイルは 今読んでいるストリームを読み続けられる。却下 — 1.x シリーズの全期間にわたって 「動くように見えて動かないパイプ」を凍結することになる。しかも一般的な方の ケース (2>&1) は移しても影響を受けない。

出力先を動かす代わりに status--format json を足す。 stderr に触らずに 件数を機械可読にできる。却下groove doctor --format json が既に解いている 問題のために新しい凍結面を増やすことになり、しかもその下で出力先の問いは 未回答のまま残る。

[quality_filter].threshold を改名し、旧名を alias として残す。 両方の綴りが 通り、何も壊れず、名前も揃う。却下 — 1 つで足りるところに名前を 2 つ凍結 することになり、しかも必要を感じた時点で 1.x のどこででも minor でできる。 今やるのは、片方向の自由を、それを必要としない唯一の問いに使うことになる。