Keyword Search
Last updated
How a query string is turned into keywords, matched against stored chunks, ranked, and grouped in the response.
Query Processing
search.Search (internal/search/search.go) validates the request, then prepares keywords:| Step | Rule |
|---|---|
| Database | Must be a loaded database name, otherwise invalid argument: "<name>" not exist |
| Query | q must be non-empty, otherwise invalid argument: q is required |
| Limit | 1 to 100; missing, zero, negative, non-numeric, or above 100 falls back to 10 |
| Target | Empty or keyword; anything else returns invalid argument: unknown target "<value>" |
| Keywords | q is lower-cased, split on whitespace, and de-duplicated |
Matching and Ranking
Each keyword becomes a case-insensitive substring match (
LOWER(content) LIKE '%keyword%') over live rows only (dismiss = FALSE). A chunk matches when it contains any keyword.| Order | Key |
|---|---|
| 1 | Number of distinct keywords the chunk contains, highest first |
| 2 | Row id, oldest first |
Substring matching means
go also matches golang and google. The % and _ characters in a keyword act as SQL LIKE wildcards. Lower-casing follows SQLite LOWER(), which folds ASCII letters only; text in other scripts matches as written.Response Shape
Matching chunks are grouped by source file, in the order the first chunk of each file appears in the ranking:
- {
- "keyword": [
- {
- "source": "/Users/you/.config/kuradb/notes/inbox/a.md",
- "matches": [
- { "chunk": 1, "content": "Alpha Go notes\nline two" },
- { "chunk": 2, "content": "Second paragraph about SQLite" }
- ]
- }
- ]
- }
| Field | Meaning |
|---|---|
source | Absolute path of the file inside the database inbox |
matches[].chunk | 1-based chunk number within that file |
matches[].content | Full chunk text |
Internal ranking values such as row ids and hit counts are not returned. When nothing matches, the HTTP API returns
{"keyword": []} and the MCP tool returns an empty object.