commit bf4e4c2c0f011b7fee2f08e3917770ad2137b3be
parent 7cb1d765993386693547b561922cb8c63b614a66
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Sat, 12 Sep 2026 02:07:59 -0400
common: the five lib/search/ modules S3 fills, claimed as stubs
S2a, S2b, S2c, S3 and S0-pause all branch off this slice's merged tip and run in
parallel. Two of them creating `lib/search/policy.ts` independently is a merge
conflict in a file neither wrote, so S1 claims all five now: `export {}` plus the
intended signatures in a comment block, taken from
`plans/one-core-phase-2.md` §S3 with the line anchors in `mcp/src/search.ts`
they come from.
Each comment carries the thing that must survive the move rather than just the
shape: the three caps are PASSED, not re-derived, so the bench's structural
counts cannot drift; `passesFilters` stays singular because a second "cheap"
predicate for planning is how a pruner starts disagreeing with the scanner; and
collapse never maps a timestamp between mirrors — that needs the per-pair
`aligned` gate this code does not have.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
5 files changed, 124 insertions(+), 0 deletions(-)
diff --git a/common/lib/search/collapse.ts b/common/lib/search/collapse.ts
@@ -0,0 +1,29 @@
+// ONE SEARCH PIPELINE — placeholder. Filled by one-core phase 2 slice S3
+// (`plans/one-core-phase-2.md` §S3); created here by S1 so no two parallel
+// slices race to create the same file.
+//
+// Intended contents: cross-platform mirror collapsing, from
+// `mcp/src/search.ts` (collapseDuplicates) and `components/SearchResults.tsx`.
+//
+// The two rules that make it safe on by default, and that must survive the
+// move verbatim:
+//
+// 1. The kept row is the cluster's canonical member WHEN that member is
+// itself among the matches — otherwise simply the first match. A mirror is
+// frequently the only surviving copy of a deleted upload, and preferring
+// an absent canonical would delete exactly the evidence a "what did the
+// removed videos say" question is asking for.
+// 2. The collapsed copies are NAMED on the row they folded into. Nothing
+// vanishes; the count stops double-counting.
+//
+// And the one it must never break: timestamps are NEVER mapped between copies
+// here. That requires the per-pair `aligned` gate, and this function does not
+// move a single second of anything.
+//
+// export function collapseDuplicates<T extends CollapsibleHit>(
+// hits: T[],
+// dupes: DuplicateIndex,
+// enabled: boolean,
+// ): { collapsed: number; kept: T[] };
+
+export {};
diff --git a/common/lib/search/evalTree.ts b/common/lib/search/evalTree.ts
@@ -0,0 +1,31 @@
+// ONE SEARCH PIPELINE — placeholder. Filled by one-core phase 2 slice S3
+// (`plans/one-core-phase-2.md` §S3); created here by S1 so no two parallel
+// slices race to create the same file.
+//
+// Intended contents: the query-tree evaluator that exists twice today —
+// `mcp/src/search.ts:1189-1338` (evalLeaf / evalNode / passesFilters) and
+// `common/lib/searchEval.ts`'s copy — as one implementation over one record
+// shape. `passesFilters` stays SINGULAR: a second "cheap" predicate for
+// planning is exactly how a pruner starts silently disagreeing with the scanner
+// about what matches.
+//
+// export type LeafMatcher = { scope: LayerScope; test: (text: string) => boolean };
+// export type RecordCtx = {
+// cues: Cue[];
+// title: string;
+// description?: string;
+// tags?: string[];
+// includeSnippets: boolean;
+// snippetsPerVideo: number;
+// };
+// export type LeafOutcome = { matched: boolean; count: number; hits: ScopedSnippet[] };
+//
+// export function evalLeaf(leaf: QueryNode, m: LeafMatcher, ctx: RecordCtx): LeafOutcome;
+// export function evalNode(node: QueryNode, ms: LeafMatcher[], ctx: RecordCtx): LeafOutcome;
+// export function passesFilters(
+// rec: { isLivestream?: boolean; ageRestricted?: boolean; uploadDate: string },
+// f: SearchFilters,
+// avail: VideoAvailability | undefined,
+// ): boolean;
+
+export {};
diff --git a/common/lib/search/policy.ts b/common/lib/search/policy.ts
@@ -0,0 +1,30 @@
+// ONE SEARCH PIPELINE — placeholder. Filled by one-core phase 2 slice S3
+// (`plans/one-core-phase-2.md` §S3); created here by S1 so no two parallel
+// slices race to create the same file.
+//
+// Intended contents: the caps that are today private constants of
+// `mcp/src/search.ts`, named and passed rather than re-derived, so the bench's
+// structural counts are unchanged by construction.
+//
+// export type SearchPolicy = {
+// // A hard ceiling on shard pages fetched per query, so a rare term over a
+// // large (or hub-wide) corpus cannot run away. Reaching it sets
+// // `truncated`. search.ts:259 MAX_PAGES 400
+// maxPages: number;
+// // A ceiling on matched videos collected before counting stops, so
+// // `total` stays bounded for a very common term. Reaching it also sets
+// // `truncated`. search.ts:263 HARD_VIDEO_CAP 2000
+// hardVideoCap: number;
+// // Cap on windowed excerpt lines per video, so a video with hundreds of
+// // matches cannot blow a batch's token budget.
+// // search.ts:268 WINDOW_LINE_CAP 200
+// windowLineCap: number;
+// // Snippet truncation width. search.ts:456 truncate(…, 240)
+// snippetChars: number;
+// };
+//
+// // What the MCP server passes. The viewer passes its own, UNCAPPED: a human
+// // scrolling a page is not spending an agent's token budget.
+// export const MCP_POLICY: SearchPolicy;
+
+export {};
diff --git a/common/lib/search/rank.ts b/common/lib/search/rank.ts
@@ -0,0 +1,16 @@
+// ONE SEARCH PIPELINE — placeholder. Filled by one-core phase 2 slice S3
+// (`plans/one-core-phase-2.md` §S3); created here by S1 so no two parallel
+// slices race to create the same file.
+//
+// Intended contents: result ordering, once. `mcp/src/search.ts:477` and the
+// viewer's ordering in `components/SearchResults.tsx` are the same intent
+// written twice.
+//
+// export type RankMode = "relevance" | "date" | "duration" | …;
+// export function rankHits<T extends RankableHit>(hits: T[], mode: RankMode): T[];
+//
+// Fetchers are INJECTED into this module's callers ({ reader, policy,
+// onProgress }) so react-query stays in `components/`; reuse
+// `lib/concurrency.ts`'s `mapConcurrent` rather than a second limiter.
+
+export {};
diff --git a/common/lib/search/window.ts b/common/lib/search/window.ts
@@ -0,0 +1,18 @@
+// ONE SEARCH PIPELINE — placeholder. Filled by one-core phase 2 slice S3
+// (`plans/one-core-phase-2.md` §S3); created here by S1 so no two parallel
+// slices race to create the same file.
+//
+// Intended contents: the windowing + snippet layer, over the existing
+// `lib/transcriptWindow.ts` primitives (`windowCues`, `cuesToSnippets`,
+// `mergeSnippets`), so the viewer's excerpt and the MCP's excerpt are the same
+// excerpt.
+//
+// export function windowedTranscript(
+// cues: Cue[],
+// match: Matcher,
+// opts: { before: number; after: number; lineCap: number },
+// ): WindowSnippet[];
+//
+// export function truncate(text: string, max: number): string;
+
+export {};