Documentation
¶
Overview ¶
Package recall ranks notes by relevance to a free-text task context so a session can surface the lessons/gotchas a past session recorded BEFORE it repeats the mistake — the "learn from previous sessions" loop.
Why this exists: nt_search is substring-AND (every term must appear verbatim), which misses paraphrases — an agent about to add "parallel request handling" won't type the exact words of a note titled "goroutine deadlock". recall trades precision for recall in the one place that needs it: it tokenizes the context, drops stopwords, applies light stemming, and expands each token across a small map of dev-concept synonyms, then scores notes by concept overlap. Notes tagged `lesson` are boosted so durable gotchas rise above ordinary reference notes.
It is deliberately dependency-free (no embeddings/index) — a good-enough recall lift that stays instant on a plain-file store. When that ceases to be enough (very large corpora, subtle paraphrase), a vector index is the natural next step.
Index ¶
Constants ¶
const LessonTag = "lesson"
LessonTag marks a note as a durable lesson/gotcha — the class recall surfaces first. Capture with `nt note --lesson` (or tag an existing note `lesson`).
Variables ¶
This section is empty.
Functions ¶
func ExplainProject ¶ added in v0.29.0
func ExplainProject(notes []*note.Note, context string, limit int, project string) ([]Result, *Trace)
ExplainProject is RankProject with a full scoring trace attached: which query terms hit which note at what strength, plus which candidates the precision floor, tail trim, or --limit dropped and why. Ranking itself is identical to RankProject/Rank — see TestExplainMatchesRank.
func LoadUserSynonyms ¶ added in v0.24.0
LoadUserSynonyms reads $dir/synonyms.txt — one synonym group per line, words separated by commas/whitespace, '#' starts a comment, blank lines ignored — and merges it over the built-in table. A line sharing a word with a built-in group extends that group (e.g. add a house term to "concurrency" without knowing the rest of the list); an all-new line mints its own group. Cheap enough to call before every ranking — callers do, so an edited synonyms.txt takes effect on the next recall with no restart. A missing file is not an error — nothing to load; treat any other error as best-effort too (a malformed synonyms file shouldn't break recall).
Types ¶
type NoteTrace ¶ added in v0.29.0
type NoteTrace struct {
ID, Title string
Hits []TermHit
Raw, Final float64 // pre-boost / post-boost score
Lesson, ProjectMatch, Expired bool // which boosts fired
Excluded string // "" | "no-match" | "precision-floor" | "tail-trim" | "limit"
// StrongTerms is the note's strong-bag (title/tags/description) vocabulary
// after stemming — only populated for an ExplainNote target. It's the
// actionable half of "why is my note missing": the words it WOULD need to
// share with the query.
StrongTerms []string
}
NoteTrace is one note's full scoring decomposition: how each query term scored against it, what it summed to before/after boosts, and — for a dropped candidate — why it isn't in the printed results.
type Result ¶
type Result struct {
Note *note.Note
Score int
Lesson bool
ProjectMatch bool // note belongs to the caller's project (soft ranking boost applied)
Expired bool // note.Note.Expired() as of ranking time — valid_until has passed
// Confidence is the PRE-BOOST score over the best score this query could
// possibly award (see fMax below) — comparable across queries and store
// sizes, unlike Score, which is IDF-scaled and therefore query-dependent.
// Boosts (lesson/project/expired) are deliberately excluded: they express
// preference, not evidence, and folding them in would let a thinly-matched
// lesson print as confident — the exact promotion pathology the precision
// floor already fights. Matched/QueryTerms are the raw coverage fraction
// (m/n query words that hit at all), shown alongside the tier because a
// tier alone hides *how much* of the query a note actually covers.
Confidence float64
Matched int
QueryTerms int
}
Result is one ranked note. Lesson notes sort first at equal relevance.
func Rank ¶
Rank scores active notes against the task context and returns the most relevant, lesson notes boosted. A note scores 3 per concept matched in its title/tags/ description (high-signal fields) or 1 in its body; lesson notes get a flat boost so a relevant gotcha outranks a merely-adjacent reference note. Notes with no overlap are dropped. limit<=0 means no cap.
func RankProject ¶ added in v0.22.0
RankProject is Rank with a soft same-project preference: notes tagged (or foldered) as belonging to `project` — typically the caller's NT_WORKSTREAM — rank above equally-relevant notes from other projects. Empty project means no preference (identical to Rank).
type TermHit ¶ added in v0.29.0
type TermHit struct {
Term string // stemmed query word
Concept string // synonym-group id ("g0"…), or the word itself if ungrouped
Where string // "strong-exact" | "strong-syn" | "weak-exact" | "weak-syn" | ""
Base float64 // 4, 2, 2, 1, or 0 — matches Where
IDF float64
}
TermHit is one query term's outcome against one note. Where is empty when the term didn't match anything in the note at all.
type Trace ¶ added in v0.29.0
type Trace struct {
QueryTerms []string // stemmed, sorted — the scorer's actual fixed iteration order
NumNotes int
FloorActive bool
TargetID string // set by ExplainNote; empty for ExplainProject
Notes []NoteTrace
}
Trace is the scoring trace for one recall call: ExplainProject fills Notes with the printed results plus up to explainExcludedCap dropped candidates; ExplainNote fills it with exactly one NoteTrace for the requested note, whether or not that note ever became a candidate.
func ExplainNote ¶ added in v0.29.0
ExplainNote traces ONE note against the query, whether or not it ever became a candidate — a note with zero term overlap never enters RankProject's output at all, so no list-level --explain can surface it. limit is unbounded: the target's own fate (kept, or dropped by the floor/ trim/limit) must not depend on how many rows the caller asked to see.