SPOT-002 locates within hunks; this ticket is the tree-wide search that feeds it candidate files. The C impl (spot/CAPO.h §"Index entry layout", CAPO.c scan/tip-walk) is the model: an LSM stack of sorted 8-byte postings under .be/*.spot.idx, a per-blob memo that makes indexing incremental, and a worktree scan where the index only ever EARLY-REJECTS clean files.
[off:40|id:20|type:4] postings, SPOTIndexFromTips + BLOBFN memo, CAPOTri40 trigram packing, the capo_class_step dirty-bypass scan.shared/store.js (object reads), shared/classify.js (wt walk + clean/dirty), tok.parse (postings come off the token stream, no manual parsing).
shared/spotidx.js (or views/spot/): trigram index maintained lazily AT SEARCH TIME — search never requires a prior index pass, the index only makes it faster.
[off:40 | path_hl:20 | type:4] (WHIFF: type LS nibble, off MS sorts first); natural u64 sort; sorted runs on disk, merged iteration, compaction (C 1/8 invariant).[tri:40 | path_hl:20 | TRI] "this path has ever contained this trigram" (3 RON64 chars, 18 bits used); BLOB memo [blob_hl:40 | path_hl:20 | BLOB] "this blob@path is already tokenized"; TREE memo [tree_hl:40 | dirpath_hl:20 | TREE] "this subtree@dir is already indexed"; free type nibbles reserved (DEF/MEN symbol postings later, [sym_hash:40 | path_hl:20 | type]).apply_filter iff CLASS_BOTH + known mtime stamp).spot://WT/path#token — the substr locator over the worktree; blocked on THIS ticket, ships with it.window, grepCtx twin), so results page/click/compose as usual.#'pattern'.ext): spot:.c#'pattern' = all files of the .c lexer family; spot:/root/path#'pattern' = subtree scope, each file parsed with its OWN grammar (per-ext needle recompile — SPOT-002 compile(loc, ext) / scan already does this); the fragment is the locator, nothing else..ext form (spot:#'…'.c) — stale under this ruling, needs the hand edit on landing.CAPOTri40 packing verbatim; needles with <3 such chars get no filter (full scan, like C SPOT_SCAN_NO_FILTER).candidates(needle, ref) — opens runs, lazily indexes unseen trees/blobs of ref's tip, returns the path-hash filter set; the caller (spot view / SPOT-002 scan) does the actual matching.tris = trigrams(needle) // a handful
cand = null
for t in tris (rarest range first):
ids = k-way-merge over runs of the contiguous block [t] // sorted path_hls
cand = cand==null ? ids : intersect(cand, ids) // sorted merge
if empty(cand): return NO MATCHES ANYWHERE // early out, no walk
for file in classify(wt):
dirty/untracked -> open+match
path_hl(file) ∈ cand -> open+match // binary search in cand
else -> reject
open(repo) // find + mmap .be/ *.spot.idx runs → idx {runs[], fresh}
close(idx) // flush fresh as ONE new run; compact past the 1/8 ratio
ent(type, off40, hl20) // pack; entType/entOff/entHl unpack — SPOT-002 side
// never touches raw entries
trigrams(needle) // RON64 trigram set, CAPOTri40 packing; null when no
// 3-char run → no filter (full scan)
pathHl(relpath) // the 20-bit full-path hash, ONE fn: postings+memos+gate
ensure(idx, ref) // lazy tip-walk AT SEARCH TIME: TREE hit → prune subtree,
// BLOB hit → skip blob, else store-read + tok.parse →
// TRI postings + BLOB row; TREE row on subtree complete
candidates(idx, tris) // the filtering algo above; tris is CNF — AND-list of
// OR-clauses of trigrams (flat needle → singleton
// clauses; regex fold from [/todo/DOG/DOG-022] → real
// clauses); OR = union of blocks, AND = intersect;
// [] = no match anywhere, null = unfiltered
gate(cand, step) // classify verdict → SCAN | REJECT: dirty/untracked
// always SCAN; clean tracked → binsearch pathHl in cand
open → ensure(ref) → candidates(needle) → classify walk with gate, survivors' hunks go to SPOT-002 scan → close persists what the walk learned (next search starts warm). fresh joins candidates as just another run — results never depend on flush timing; a killed session loses warmth, never correctness.*spot.idx family)candidates() intersection → path-hash set (CNF, early-out)spot://WT/path#token nav; result hunks via window — blocked on SPOT-002 landing (parallel wt), the search.js integration is the join point.ext / subtree scope from the PATH slot — same join point, with SPOT-002tok._rx_lits binding); until it lands, /re/ needles run unfiltered (full scan) — correct, just slow..ext form — hand edit on landing.SPOT-003: shared/spotidx.js lazy trigram reject-filter + golden test