bee sym narrows a query through the SYM rows, but SYM keys whole identifiers only: a comment word, a string fragment, a partial name (Feed) or a regex literal keys no row, so a substring grep over the registry has no index at all. This ticket adds a TRIGRAM lane of u32 postings in its own <gitdir>/be/*.tri.idx family on the u32 lane of abc.index (QJAB-013). Ruled 2026-09-17 (gritzko): a posting names a PATH PREFIX at a depth, tri:18 | depth:2 | prefix_hl12:12, so the tip-tree descent prunes at the top directory levels and no level of the tree is ever listed whole; test/http/stall.js carrying a trigram mints three rows, for test (depth 0), test/http (depth 1) and the file itself (depth 2). A DIRECTORY at depth 0, 1 or 2 is recorded at its depth and a dir at depth 3 or more has NO record; a FILE at depth below 3 is recorded at its depth, a file at depth 3 or more at depth 3. The lane's one door is paths(words) -> [path], the files that MAY contain every word; the descent is concealed behind it. Trigrams are RON64, case-sensitive, C's CAPOTri40 twin. Suspects only, never delete, a rebuild once a year is fine. Main-tree work permitted by gritzko for this ticket (2026-09-17); no commits, no push.
index/lindex.js:220:PS scan is the one pass over the moved tip blobs, two families out of it (LINK, SYM), each round no-oping off its own MARK row (markKey, LINDEX_REF/SYMDEX_REF) in the wh128 .lite3.idx lane; the diff base is the intersection of the marks. A third round rides the same loop, the same changed list and the same bytes.index/lindex.js:514:PS nameSyms is the pruned descent SYM does with segment hashlets: a dir is entered only when some row names it. This lane makes that exact at the top levels: a dir is entered only when it carries EVERY trigram of the query.index/lindex.js:561:PS symIn / :542 confirm / :532 suspectBytes are the open-and-confirm half, view/sym.js the hunk half (windows, markToks, fileHunks): a record { root, path, full, bytes, toks, hits: [{lo, hi}] } becomes merged ±1-line hunks with the hit re-tagged E. symFilter (:425) reads the .ext / dir/ scope words, pathFits applies them.index/index.js hlOfText is the text hashlet every family keys on; index/index.js sweep unlinks any file in <gitdir>/be whose ext is not a live format — a new family must join IDX_EXTS or its runs are swept.spot/CAPO.h CAPOTriChar (which bytes count: the RON64 alphabet, a run breaks on anything else), CAPOTri40 (three 6-bit RON64 codes, 18 bits); SPOT-003 chose byte-level runs over the token stream, superset-safe for files with no lexer.u32 lane of abc.index is QJAB-013, landed first in the quickjab main tree and built in build-unbundled; bee tests run with LITEJAB=<quickjab>/build-unbundled/bin/quickjab, ~/bin/quickjab is NOT replaced.index/tridex.js, the entry: (tri << 14) | (depth << 12) | hl12. tri is the CAPOTri40 code of three consecutive RON64 bytes inside one run (a run shorter than 3 mints nothing); hl12 is the top 12 bits of hlOfText(prefix), the repo-relative prefix text with no trailing slash; depth counts from 0 at the top segment. Rows per file: its dir prefixes at depths 0, 1, 2 (none for deeper dirs) and the file itself at min(depth, 3). a/b/c/d/e.c mints four rows per trigram, test/http/stall.js three, main.js one (depth 0); the dir rows are shared by every file under that dir and dedupe.scan's loop as the third round, off the BYTES (every blob the loop reads, lexer or not): per blob one Set of trigram codes, then one row per code per slot, deduped through a per-scan Set of entries so a dir carrying a trigram in twenty files puts it once. Family .tri.idx, abc.index("u32", { dir, ext }), joined to IDX_EXTS. Own mark hlOfText("tridex") in the lite3 lane like symdex; the diff base intersects all three marks; a rerun over an unmoved tip writes nothing; bee index's summary line counts the tri rows.tridex.paths(ctx, ix, words, scope) -> [repo-relative path], the tip files that MAY contain every word of words, ascending. Inside: the trigram set of each word (RON64 runs only), ANDed; one range per code over its 14-bit block into a Set of (depth, hl12); a descent of the tip tree that enters a dir at depth 0..2 only when its (depth, hl12) sits in EVERY set, enters every dir at depth 3 or more, and keeps a file only when (min(depth, 3), hl12) sits in every set; an empty set answers [] before any tree is read. Words with no 3-byte RON64 run in any of them mean no filter: every file of the scope, since all of them MAY. scope is symFilter's object, applied by pathFits on the way out.bee grep <word>... [.ext|<dir>]...: view/grep.js + door.js row + README usage line. The words are the needles, every one a plain byte substring (utf8), case-sensitive, comments and strings included; a file answers when it carries ALL of them; bytes are suspectBytes's (worktree over tip blob); hunks as view/sym.js fileHunks builds them over the spans of every word, kind: "grep", --paths for scripting; the BEE-063 cap wording past SYM_CAP candidates; registry fan-out and bring-up as symForeign does.INDEXES.mkd: a "The TRI lane" section (the entry, the depth rule for dirs and files, RON64, the mark, the door, what it is NOT — no positions, no path text, no delete) and the sweep note; README usage line.test/tridex/run.sh + rows.js: the packing (RON64 code, the three rows off test/http/stall.js, the four off a/b/c/d/e.c, the one off main.js, hl12 off hlOfText), a comment word and a string fragment found (what SYM cannot), a two-word query ANDing, a needle absent everywhere answering with NO tree read, a needle present only under src/ never entering test/ (a counter on readTree), a file at depth 5 found through its depth-3 row, a 2-char needle answered unfiltered, rerun writes nothing, a false suspect (word gone at tip) dropping out, .ext and dir/ scope, --paths, the cross-repo fan-out, the stale-format sweep leaving .tri.idx alone.tridex MARK row and the IDX_EXTS entry.paths() is the surface, and grep is its first caller, spot (SPOT-002's structural matcher) the next.dog/ submodule is not touched; no commit in either tree.tri | file posting answers "which files" only by listing the tree to name them; a prefix row lets the descent refuse test/ at the root when no file under it carries the trigram, so a query touches the dirs that MAY answer and nothing else.U8BFeed and u8bFeed are different blocks, as in C.rm -rf <gitdir>/be rebuilds.paths()scan, summary count; index.js IDX_EXTStest/cts's warm rerun, since a fixture with no trigram leaves an honestly empty lane that looks swept. A layout change bumps TRI_EXT and TRIDEX_REF together, the rule the lite3 lane follows; the runs are never deleted alone.bee grep over the registry hang on linux for hours; the lane now opens bulk (1M rows, no fsync until the final commit), git's 4807 files in 31 s, linux about 9 minutes — the reason for BEE-072.