Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ Bot-authored comments (`sender.login` ending in `[bot]`) and comments shorter th
| Name | Type | Description |
|------|------|-------------|
| `query` | string (optional) | Natural-language query. Omit or empty = scan mode. |
| `repo` | string | Filter by repository — full slug (`owner/repo`), exact match. A bare repository name matches nothing; search mode reports that as `"repo"` in the response's `filters_unmatched`. |
| `repo` | string | Filter by repository — full slug (`owner/repo`), exact match. A bare repository name matches nothing; search mode reports that as `"repo"` in the response's `filters_unmatched`. Pass it whenever the question is about a known repository — without it, a near-duplicate from another repository can rank first; leave it unset only when the question spans repositories. |
| `path_prefix` | string | Filter docs by repository-relative directory prefix. Requires `type: "doc"`, a trailing `/`, and at most 64 UTF-8 bytes. |
| `state` | `"open"` \| `"closed"` \| `"all"` | Filter by state (default `all`). |
| `labels` | string[] | Filter by label names (AND). |
Expand Down
2 changes: 1 addition & 1 deletion docs/0-requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ Returns:
- `same_entity` on results that absorbed other rows of the same entity (see Entity Aggregation); `top_k` counts entities, not rows
- top-level metadata: `fusion`, `dense_candidates`, `sparse_candidates`, `rerank_requested`, `rerank_applied`, `filters_unmatched`

**Unmatched filters (`filters_unmatched`).** `repo` takes the full slug (`owner/repo`) and matches exactly — on the dense side as a Vectorize metadata `$eq`, on the sparse side as `d.repo = ?`. `path_prefix` selects doc rows whose repository-relative path begins with the named directory prefix. A bare repository name or a path prefix that selects no doc row therefore produces a response shaped exactly like a genuine zero-hit search. `filters_unmatched` separates the two: it is always present in search mode, `[]` means every applied filter selected a non-empty population (so `count: 0` really is "no hits"), and a listed name means that filter's population is empty — the value is wrong, not the query.
**Unmatched filters (`filters_unmatched`).** `repo` takes the full slug (`owner/repo`) and matches exactly — on the dense side as a Vectorize metadata `$eq`, on the sparse side as `d.repo = ?`. `path_prefix` selects doc rows whose repository-relative path begins with the named directory prefix. A bare repository name or a path prefix that selects no doc row therefore produces a response shaped exactly like a genuine zero-hit search. `filters_unmatched` separates the two: it is always present in search mode, `[]` means every applied filter selected a non-empty population (so `count: 0` really is "no hits"), and a listed name means that filter's population is empty — the value is wrong, not the query. The tool description tells the caller when to pass `repo`: whenever the question is about a known repository, since without it a near-duplicate from another repository can rank first, and to leave it unset only when the question spans repositories. The default stays unfiltered so cross-repository questions keep working.

The distinction is worth a field because agentic multi-step search inverts the cost of a silent zero. In a single search a zero is a dead end the caller inspects; in a search loop it is a normal intermediate result ("nothing down this angle") that the caller consumes and moves past, so the mis-specified filter never surfaces and one query out of the budget is spent on a false negative.

Expand Down
2 changes: 1 addition & 1 deletion mcp-server/server/search-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"maxLength": 4096
},
"repo": {
"description": "Filter by repository — full slug (owner/repo), exact match. A bare repository name (\"my-repo\") matches nothing and yields an empty result set; search mode flags that case as \"repo\" in the response's filters_unmatched.",
"description": "Filter by repository — full slug (owner/repo), exact match. A bare repository name (\"my-repo\") matches nothing and yields an empty result set; search mode flags that case as \"repo\" in the response's filters_unmatched. Pass it whenever the question is about a known repository: without it, a near-duplicate from another repository can rank first. Leave it unset only when the question spans repositories.",
"type": "string",
"maxLength": 256
},
Expand Down
4 changes: 3 additions & 1 deletion src/search-contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ export const searchInputSchema = z.object({
.describe(
"Filter by repository — full slug (owner/repo), exact match. " +
"A bare repository name (\"my-repo\") matches nothing and yields an empty result set; " +
"search mode flags that case as \"repo\" in the response's filters_unmatched.",
"search mode flags that case as \"repo\" in the response's filters_unmatched. " +
"Pass it whenever the question is about a known repository: without it, a near-duplicate from another repository can rank first. " +
"Leave it unset only when the question spans repositories.",
),
path_prefix: z
.string().max(256)
Expand Down
Loading