GrooveSeek

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

View the Project on GitHub alphabet-h/grooveseek

18. One range is one definition

Context and problem

ADR-0014 bounds how deeply a definition may sit before the chunker stops chunking at definitions: the walk that collects a definition’s scope counts its steps, and the first definition past 64 syntax-tree ancestors ends definition-based chunking for that file. That bound is also what keeps the chunker’s one recursive function from running away. emit_def descends the containment tree, and a containing definition is a syntax-tree ancestor too, so a bound on ancestors was taken to be a bound on recursion.

It is the “too” that fails. Containment is computed from byte ranges rather than from the tree: one definition contains another when its range does not end before the other begins. A range never ends before itself, so two definitions covering the same bytes are recorded as one containing the other. The chain that produces is as long as the number of repeats, and not one of its links adds a syntax-tree ancestor, so the scope bound never sees it.

The compiled-in Rust grammar produces no such shape, which is why this went unnoticed for three releases. The PHP grammar this project publishes does. Its tags query hangs @definition.field on a property_declaration and @name on each property_element beneath it, and tree-sitter-tags keeps one tag per name rather than one per node, so public $a, $b, $c; arrives as three definitions covering one range. A class declaring three thousand properties in a single statement is a 22,923 byte file — two per cent of the 1 MiB raw-byte cap that is meant to be the defence here — and indexing it ended the run with STATUS_STACK_OVERFLOW . A stack overflow is not a panic. The guard that lets one unreadable file be skipped instead of ending the whole run never sees it, nothing is written, and over the MCP transport the server goes down with the process.

The question this answers: what is a definition, when two of them cover the same bytes?

Decision drivers

Options considered

  1. Collapse definitions that cover the same bytes into one.
  2. Add a third bound, on containment depth, beside the scope and chunk bounds, and degrade to line chunking past it.
  3. Both: collapse the repeats, and keep the bound behind them.

Decision

Option 1. A definition whose covered range repeats one already recorded is dropped before the containment forest is built. The leftmost name stands for the declaration.

Why not option 2. A bound on containment depth would stop the crash without naming what is wrong. Definitions covering the same bytes are not deeply nested; they are one definition seen under several names, and recording the repeat as nesting is the defect itself. The bound would also cost a file all of its definition metadata because one declaration in it happened to be wide, and a comma-separated property list is ordinary PHP rather than a pathological input. Degrading is the right answer to a file whose shape defeats definition chunking, and the wrong answer to a file the chunker simply mis-read.

Why not option 3. Once the repeats are gone, containment nests no deeper than the syntax tree does, and the scope bound already refused anything past its own limit. No input reaches a third bound, so shipping one would ship the fourth driver’s failure on purpose. The invariant is written as a debug_assert! instead, which says the same thing without claiming to enforce it where it cannot fire.

The collapse is derived from the file alone, so the same file still chunks the same way anywhere. Where the whole declaration fits the chunk budget, the output does not change at all: the outermost definition of the chain already answered for it, and the outermost definition was already the first name.

Consequences

References