SPOT-003: spot.js tree search — lazy trigram index, seen-tree/blob memo, dirty bypass

Now:
OPEN

Input

Context

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.

Goals

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.

Constraints

WIP

Design decisions

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

TODOs

Blockers and bummers

Outcome