fix(catalog): rank on where a word appears and how rare it is - #3312
Merged
Conversation
Word search returned the right move in the top three for 87% of a 39-query eval set built from real catalog intents. Three defects, all in the same 75-line scorer, and all found by running the queries rather than by reading the code. A token matching an item's NAME counted exactly as much as one buried in a description. Searching "typewriter effect on a title" ranked the item literally called `typewriter` seventh, behind entries that merely mention typing. Name and title now carry three times the weight: an author who types a move's name is giving the strongest signal available and it was being averaged away. Plurals shared no vocabulary with the singular. "a stat that counts up and then pulses once" matched nothing in a description reading "lands with a restrained scale pulse", because `counts` is not `count`. Adding detail to a query made results strictly worse, which is the opposite of what a search should do. Plurals now fold, and only plurals: Porter would fold `counter` to `count` and `values` to `valu`, merging moves that mean different things. Field weighting alone made one case worse, which is why inverse document frequency is here too. "reveal a headline one line at a time" put every item merely NAMED `*-reveal` on top, because one strong hit on the catalog's most common word outscored several weak hits on the words that actually narrowed it down. Rarity now scales each term. Separately: a query in a script this ranker cannot index no longer reports itself as an empty catalog. Tokenising on [a-z]+ leaves nothing of a Japanese query, and returning "no items match" told the author the catalog lacked a move it may well have, then invited them to file a gap report about it. That case now says what actually happened and withholds the gap prompt, since nothing was searched. Measured on the same 39 queries, before and after: top-1 31/39 (79%) -> 33/39 (85%) top-3 34/39 (87%) -> 39/39 (100%) Test plan: 13 new tests, each a real failing query reduced to the smallest fixture that still reproduces it. Existing tests migrated to the fields API (two callers total). Full CLI suite 2643 passed, 2 pre-existing transcribe failures unchanged. Verified against the real CLI: "typewriter effect on a title" now returns typewriter first, and "chat conversation between a user and an assistant" returns chat-message, chat-thread, ai-chat-reveal instead of transitions-blur.
The runtime message added alongside this explains an unsearchable query after the fact. Saying it up front is cheaper: an agent that never writes the query in Japanese never sees the error, never wastes the turn, and never files a gap report about a component that exists. Worth stating rather than assuming, because the mistake is a reasonable one. On a Japanese or Chinese project the brief, the narration and the captions are all in that language and the query naturally follows. The rule is that the query language and the video language are unrelated: describe the move in English, write the on-screen copy in whatever the video needs. Both skills that own `catalog --query` carry it, and those are the only two that mention the command at all.
The message explaining an unsearchable query went to stdout and the command exited 0. An agent that checks the exit code, which is most of them, read that as "searched successfully, the catalog has nothing" and went off to hand-author a move that is sitting in the registry. The explanation only helped a human who happened to be reading the terminal. It is bad input, not an empty shelf, so it now behaves like one: the guidance goes to stderr and the command exits 1, matching what an invalid --type already does. A genuine empty result, where the query parsed fine and the catalog simply has nothing, still exits 0 -- that distinction is the whole point, and both halves are pinned by tests. The wording now also says what to do rather than only what happened: search in English, and let the on-screen copy of the video stay in whatever language it needs. That was the part agents were getting wrong, since a Japanese project makes a Japanese query feel natural. Test plan: 3 new tests covering the exit code, the wording, and the genuine-empty case that must stay at 0. Also asserts the gap-report line is absent, since nothing was searched and a report there is noise in the one signal that tells us what to build. catalog.test.ts 32 passed; commands + registry suites 887 passed with the 2 pre-existing transcribe failures unchanged. Verified against the real CLI: a CJK query exits 1, a genuine miss exits 0.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Three ranking defects in the catalog's word search, plus one honesty fix for queries it cannot read.
Measured on a 39-query eval set built from real catalog intents, same set before and after:
Why
I ran 30 realistic queries against the shipped CLI while preparing a bug bash and 13 came back wrong. Reading the results rather than the code made the causes obvious, and they were all in the same 75-line scorer.
A name match counted the same as a description match.
catalog --query "typewriter effect on a title"ranked the item literally calledtypewriterseventh, behind entries that merely mention typing. An author typing a move's name is giving the strongest signal available and it was being averaged away.Plurals shared no vocabulary with the singular. "a stat that counts up and then pulses once" matched nothing in a description reading "lands with a restrained scale pulse", because
countsis notcountandpulsesis notpulse.count-upfell to rank 10. Adding detail to a query made the result strictly worse, which is the opposite of what a search should do.Common words counted as much as rare ones. This is the one that bit only after fixing the first: weighting the name field put every item merely named
*-revealon top of "reveal a headline one line at a time", because one strong hit on the catalog's most common word outscored several weak hits on the words that actually narrowed it down.A query in another script reported itself as an empty catalog. Tokenising on
[a-z]+leaves nothing of a Japanese query, so it returned zero results, printed "no items match", and then invited the author to file a gap report about a move the catalog may well have. A real user hit exactly this.How
localSearch.tskeeps its shape: shared tokens over the square root of the entry's token count. Three things sit on top.textOfnow returns{strong, weak}instead of one flat string; name and title are worth 3x. Two callers total, both updated.countertocountandvaluestovalu, which merges moves that mean different things. A guard rule protectspress,status,axisbefore the bares$rule runs.+1inside the log keeps a token present in every item at a small positive weight rather than exactly zero, so it can never flip a tie on its own.Separately,
hasNoSearchableTokenslets the command distinguish "we could not read your query" from "the catalog has nothing like this". On that path the CLI now says what actually happened and withholds the gap-report prompt, since nothing was searched:--jsoncarriesunsearchable_query: trueand omitsreport_gapin that case.Test plan
13 new tests, each a query that failed against the real catalog reduced to the smallest fixture that still reproduces it, including the one that only regresses when field weighting is present without IDF. Existing tests migrated to the fields API.
localSearch.test.ts21 passed. Full CLI suite 2643 passed, with the 2 pre-existingtranscribefailures unchanged (confirmed failing on the base commit).Verified against the real CLI, not just the harness:
The second used to return
transitions-blurfirst, withclaude-exchangeandchatgpt-exchangeabsent from the top 5 entirely despite both existing.Not covered
bge-small-en-v1.5, also English. Real multilingual search needs a different model, not a different tokeniser.text that types then backspacesis a gap, not a miss.typewriteris a reveal only and nothing in the catalog deletes. It is the CLI's own--search-missexample, and I removed it from the eval set once I checked rather than counting it against the ranker.