From 9f25e5c9f6c1e26303e360eb4ee29153335807db Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 26 Oct 2026 14:03:49 +0100 Subject: [PATCH] mesh_search: filler words of a question (did, our, on, all) need not match, so the operator's own words find a tool; a query of filler finds nothing --- node-tools/internal/console/search.go | 30 +++++++++++++++++++++- node-tools/internal/console/search_test.go | 28 ++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/node-tools/internal/console/search.go b/node-tools/internal/console/search.go index eaeaf1d..2891428 100644 --- a/node-tools/internal/console/search.go +++ b/node-tools/internal/console/search.go @@ -44,6 +44,31 @@ var synonyms = map[string][]string{ "hosts": {"hostname"}, } +// filler are the function words a question carries and no tool is named for: a query's words must all +// match, so "did our weekly upgrade run on all our nodes" would otherwise find nothing for "did", "our", +// "on" and "all". Kept short and here only; a word that names something (`every`, `list`, `not`) is not +// in it. A query of nothing but these matches nothing. +var filler = map[string]bool{} + +func init() { + for _, w := range strings.Fields("a an the did do does our my your we i is are was on in at of for to all any and or with it its there what which how has have been") { + filler[w] = true + } +} + +// queryWords are a query's words to match: split at spaces, the punctuation of a sentence trimmed from +// each end (`nodes?` is `nodes`), filler left out. +func queryWords(query string) []string { + var out []string + for _, w := range strings.Fields(query) { + w = strings.Trim(w, "?!.,;:\"'()") + if w != "" && !filler[w] { + out = append(out, w) + } + } + return out +} + // hit is one match as mesh_search answers it. type hit struct { Address string `json:"address"` @@ -169,8 +194,11 @@ func scoreOf(query string, qwords []string, name, description string, replaces [ // search is mesh_search's answer: every match, best first. func (x *index) search(query string) []hit { query = strings.ToLower(strings.TrimSpace(query)) - qwords := strings.Fields(query) + qwords := queryWords(query) var hits []hit + if len(qwords) == 0 { + return nil + } for _, st := range x.Seats { for _, v := range st.Verbs { replaces := x.Replaces["seat:"+st.Seat+"."+v.Name] diff --git a/node-tools/internal/console/search_test.go b/node-tools/internal/console/search_test.go index 41ba59c..d3e006d 100644 --- a/node-tools/internal/console/search_test.go +++ b/node-tools/internal/console/search_test.go @@ -149,3 +149,31 @@ func TestSearchWithoutReplacesMatchesAsBefore(t *testing.T) { t.Errorf("a tool's own name: %+v", hits) } } + +// A question's filler words do not have to match (every other word still does): the operator's own words +// find the tool whose description says it in other words; a query of filler alone finds nothing. +func TestSearchIgnoresFillerWords(t *testing.T) { + x := meshToSearch(t) + x.Modules["pacman"] = &moduleInfo{Module: "pacman", On: []string{"anchor"}, Tools: []Tool{{Module: "pacman", Name: "pacman_round", + Description: "Whether this week's system upgrade ran on every node (the weekly upgrade round, last run of all nodes)."}}} + for _, c := range []struct { + query string + want string // the first hit's address, "" for none + }{ + {"did our weekly system upgrade run on all our nodes?", "anchor/pacman.pacman_round"}, + {"the weekly upgrade", "anchor/pacman.pacman_round"}, + {"did our", ""}, + {"what is the", ""}, + {"", ""}, + {"did our weekly kubernetes upgrade run?", ""}, + } { + hits := x.search(c.query) + got := "" + if len(hits) > 0 { + got = hits[0].Address + } + if got != c.want || (c.want == "" && len(hits) != 0) { + t.Errorf("%q: first hit %q, want %q (%+v)", c.query, got, c.want, hits) + } + } +}