Semantic search over a Markdown knowledge base, served over MCP.
ADR-0012 は、索引する
すべてのソースファイルについて 1 つの約束をしている: parse の成否によらず、持っている
バイトを全部差し出す。定義の外にあることを理由に落としてはならず、grammar が読めなかった
領域だからという理由でも落としてはならない。
ADR-0014 は定義が入れ子で
居てよい深さの上限を足したが、その約束は意図的に守った — 上限を超えたファイルは拒否ではなく
行単位で chunk 化し、その判断が見えるように parse:too-deep を付ける。
3 つ目の上限は両方より前からあり、この約束を守っていなかった。MAX_CHUNKS_PER_FILE は
「1 ファイルが出せる chunk は 512 個まで」と言うが、その適用は先頭 512 個を残して残りを
捨てる形だった。piece は位置順にソートされているので、捨てられるのは常にファイルの
末尾になる。定義が 600 個あるファイルは、先頭からどこか途中までが索引され、残りは
documents にはあって (コードパーサはソースを逐語で保持するので get_document は全文を
返す) どの chunk にも無い — つまりどの検索も届かない状態になっていた。索引時の
tracing::warn! 1 行がそう言うだけで、他には何も無い — document にタグは付かず、
groove doctor が読み戻せるものも無く、ドキュメントにも書かれていなかった。
しかもこの切り捨ては fallback を選ぶ分岐の外にあったので、スコープ側の fallback の 出力も切っていた。ファイルを丸ごと保つためにわざと degrade したファイルが、その直後に 別の理由で末尾を切られていたことになる。
本決定が答える問い: ファイルが生き残らなければならないとき、chunk 数の上限とは何を意味 するのか。
[parsers.code].max_chunk_chars は知識ベース側が決める値で、下限が無い。既定値でしか
安全でない規則は、安全ではないdoctor の検査を足し、末尾は
落としたままにする選択肢 3。 定義が上限より多い chunk を要求するファイルは行単位で chunk 化し
(スコープ上限が既に使っていたのと同じ fallback)、parse:too-many-chunks を付ける。
戻ってくるものが上限に収まるよう、行の budget は必要なら広げる。
選択肢 1 を採らない理由。 get_document が返している末尾に検索が届かない状態は、
書き留めるべき制限ではなく ADR-0012 がまさに防ぐために存在する失敗であり、書き留めた
ところで誰にも逃げ道を与えない。MAX_CHUNKS_PER_FILE は設定ではなく定数なので、検査が
名指しできる直し方は「ファイルを小さくしろ」しかない。
選択肢 2 を採らない理由。 行 fallback を縛るのは budget であり、その budget は下限の
無い設定キーである。max_chunk_chars を小さくした知識ベースでは、1 MiB のファイル 1 本が
何十万もの chunk を生み、そのすべてが rebuild_index が embedder と DB を保持したまま
embed される — ADR-0014 が防ぐために存在する可用性の形に、反対側から到達する。上限は
助言に成り下がる。
選択肢 4 を採らない理由。 バイト上限が縛るのはバイトであって chunk 数ではなく、2 つは
別の問いである。split_by_lines は次の行が budget を超えるときにだけ piece を切るので、
隣り合う 2 つの piece は合計で必ず budget を超える。したがって非空白 W 文字のテキスト
から出る piece は 2W / budget + 1 未満になる。既定 budget 3500 と 1 MiB 上限を入れると、
空白を持たないファイルでおよそ 600 — 既に上限超過。この 600 は計測値ではなく上の式から
出した上界で、式の根拠は grooveseek/src/parser/code/mod.rs の split_by_lines の切り方に
あり、the_widened_budget_keeps_the_split_under_the_bound テストが式を言い直すのではなく
その関数自身に突き合わせている。上限を撤廃すると「1 ファイルが出せる chunk はいくつまでか」に
答えが 1 つも無くなる。
選択肢 3 が使うのは同じ算術である。piece を limit 未満にしたければ budget は
2W / (limit - 1) でよく、fallback はそれと設定値の広い方を採る。広げ幅はファイルから
導かれるので、同じファイルはどのマシンでも同じ chunk になり、広げる必要が無かったファイルに
対しては何もしない。
上限の値そのものは 512 のまま。本リポジトリ自身のソース (.rs 62 本) を scratch の知識ベースへ
索引して数えたところ、最も広いファイルで 297 chunk だった
。
通常のコードは上限に届くまで 4 分の 3 ほどの余地がある。変えたのは「届いたときに何が
起きるか」の方である。
symbol_kind も見出しもスコープも無い)、
バイトは全部保つ — スコープ上限を超えたファイルとまったく同じ。
parse:too-many-chunks を parse:too-deep と共通の「諦めた」タグ 1 つにせず別々にしたのは、
直すべき入力が違うから: ファイルを分割するのか、入れ子を平らにするのかgroove doctor が、どちらかのタグを持つ document を chunked-without-definitions として
報告する。このタグが読み戻されるのはこれが初めて — parse:too-deep は v1.3.0 から
書かれていて、どこにも出ていなかったsplit_by_lines は意図的に行の途中で切らないので、生成コードや minify されたファイルは
1 個の巨大な chunk になり得る。全バイトは索引にあり keyword では届くが、埋め込みモデルの
窓より遥かに大きい chunk はベクトルとしてはうまく表現されない。これは本決定の帰結ではなく
既存の規則だが、budget を広げることでこの規則に当たるファイルは増える[parsers.code].max_chunk_chars が「1 ファイルが出せる chunk 数」を決めることは無くなったので、
可用性の問題ではなくなった — 決めるのは「上限に収まるファイルをどれだけ細かく切るか」
だけになる。決定時点でこの key に下限は無かった。2026-09-10 から config の読み込みが 30 未満を
reject する。30 は chunker が単独で残す最小の断片で、この下限は品質の境界であって、本決定が
不要にした可用性の境界ではない。chunk 数は引き続き上の budget 拡張だけで上限に収まる本リリース以前に作られた索引は、内容が変わっていないファイルの chunk をそのまま保つ
(そういうファイルは二度と parser に届かない)。それらは旧実装が切り落とした末尾を欠いた
ままかもしれず、しかも document 側にそれを示すものが無い — タグを書くのは parser で、
その経路が飛ばすのが parser だから。そこで索引は「どの chunk 化ポリシーで作られたか」を
記録する — --force で作り直す時と、まだソースファイルを 1 本も持っていない時は現行の
ポリシーを、それ以外は「別のもので作られた」印を書く。groove doctor は現行ポリシー以外を
持つ索引について「異常なし」と答えるのではなく、見えていないことを報告する。
groove index --force で作り直せる。
「空かどうか」は文書数ではなくソースファイル数で測る (散文だけの知識ベースが code
パースを有効にした場面で、問われていないノートを理由に legacy 扱いしないため)。記録は
すべての挿入経路が合流する場所で書く — serve は索引が無くても起動するので、知識ベースが
最初のソースファイルを watcher 経由で受け取り、full rebuild が一度も走らないことがあり得る。
そして legacy 状態はキーの不在ではなく明示的に書く: この問いは答えるのにコストがかかり、
記録できない状態は索引するファイルごとに導出し直すことになるから。
代案は推測することだった — 切り捨てられた document は chunk 数がちょうど上限になる。 退けたのは、本決定によって「定義がちょうど上限ぶんの chunk を生むファイル」はそのまま 残されるので、2 つが区別できないから。推測は一部の document を永久に誤報する。 世代は真であることだけを言う: この索引は別の答えの下で書かれたので、問いは未解決である
grooveseek/src/parser/code/。上限と widening は mod.rs にある