Rank a search the same whatever order discovery heard things in
mesh/merge-gate pass: builds mesh-tools, node-tools → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of …
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered

A tie went to whichever tool discovery listed first, so docker logs found docker_secrets_in_logs
before docker_logs on some runs. A name the query says whole wins, then the shorter name.
This commit is contained in:
jochen
2026-10-07 20:27:26 +02:00
parent 49cb06f6c5
commit aa209313c2
5 changed files with 37 additions and 12 deletions
+30 -5
View File
@@ -1,6 +1,6 @@
package console
// Finding a tool by the words an agent already has (novox/hq ADR 0242).
// Finding a tool by the words an agent already has (novox/hq ADR 0245).
//
// An agent that wants a unit's log reaches for `journalctl`, and one that wants a container's for `docker
// logs`; it searches with those words, or with `logs`. Matched against names and descriptions alone, none
@@ -12,10 +12,11 @@ package console
// every word of the replaced command, in order, among the query's words. The most specific command wins.
// 2. **Every word is in the tool's name, its replaced commands or its description**, a word also matching
// through a short table of the words agents use for the mesh's (logs ⇄ journal, ps ⇄ list). A word in
// the name or a replaced command weighs more than one in the description.
// the name or a replaced command weighs more than one in the description, and a name the query says
// whole more than one it says in part.
// 3. Nothing else: a search that matches nothing says so.
//
// Ties keep the listing's order — seats first, the place tools belong — then the modules by name.
// Ties go to a seat — the place tools belong — then to the shorter name, then by address.
import (
"encoding/json"
@@ -149,6 +150,19 @@ func scoreOf(query string, qwords []string, name, description string, replaces [
score += found
}
}
// A name the query says whole — `docker logs` for docker_logs, not docker_secrets_in_logs — first.
own := name[strings.LastIndex(name, ".")+1:]
whole := true
for _, part := range strings.FieldsFunc(strings.ToLower(own), func(r rune) bool { return r == '_' || r == '-' }) {
in := false
for _, w := range qwords {
in = in || w == part
}
whole = whole && in
}
if whole {
score += 2
}
return score, ""
}
@@ -208,11 +222,22 @@ func (x *index) search(query string) []hit {
hits = append(hits, h)
}
}
sort.SliceStable(hits, func(i, j int) bool { return hits[i].score > hits[j].score })
// Best first; on a tie the shorter name — the plainer tool — then the address, so an answer never
// depends on the order discovery heard things in.
sort.SliceStable(hits, func(i, j int) bool {
if hits[i].score != hits[j].score {
return hits[i].score > hits[j].score
}
a, b := hits[i].Address[strings.LastIndex(hits[i].Address, ".")+1:], hits[j].Address[strings.LastIndex(hits[j].Address, ".")+1:]
if len(a) != len(b) {
return len(a) < len(b)
}
return hits[i].Address < hits[j].Address
})
return hits
}
// replacesOf is what every verb and tool replaces, from the controller's records (novox/hq ADR 0242):
// replacesOf is what every verb and tool replaces, from the controller's records (novox/hq ADR 0245):
// a seat's verbs from its `tools` answer, keyed `seat:<seat>.<verb>`, and a module's own tools from
// its `modules` answer, keyed `<module>.<tool>`. A controller older than the field answers neither,
// and search matches names and descriptions as before.