Skip to content
Open
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
25 changes: 25 additions & 0 deletions src/explore.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3931,6 +3931,31 @@ pub const Explorer = struct {
// OOM building the offset table → bail to the full searchContent
// path (caller falls through), which renders the same results.
const n_spans = self.line_offsets.lineSpans(stats.path, content, target_lines[0..target_count], &spans) orelse return false;
// Def-line-first: within a file that defines the query symbol, render the
// definition line(s) before mere mentions — stable-moves matching spans to
// the front (reorders the OUTPUT spans, not lineSpans' sorted input).
if (stats.defines) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Select the definition before capping hits

Because this runs only after target_lines has already been truncated to tier0_per_file_cap, it cannot surface the definition when earlier mentions fill the per-file quota (for example, with multiple result files max_results=20 caps each file at 4 lines, so comments/calls above fn query keep the later definition out of spans). In that case stats.defines is true but there is no definition span to move, so codedb_search still renders a non-definition first; the definition line needs to be selected or injected before applying the per-file cap.

Useful? React with 👍 / 👎.

if (self.outlines.get(stats.path)) |outline| {
var def_w: usize = 0;
var i: usize = 0;
while (i < n_spans) : (i += 1) {
var is_def = false;
for (outline.symbols.items) |sym| {
if (sym.line_start == spans[i].line and asciiEqlIgnoreCase(sym.name, query)) {
is_def = true;
break;
}
}
if (is_def) {
const tmp = spans[i];
var j: usize = i;
while (j > def_w) : (j -= 1) spans[j] = spans[j - 1];
spans[def_w] = tmp;
def_w += 1;
}
}
}
}
for (spans[0..n_spans]) |line_span| {
rendered += 1;

Expand Down
22 changes: 22 additions & 0 deletions src/test_search.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1900,6 +1900,28 @@ test "audit: searchContent tier0 use_line_hits early-return skips rerank basenam

// src/explore.zig renderPlainSearch — the MCP codedb_search fast-path rendered in raw
// hit-count order with no basename prior, so a noise file outranked the canonical match.
test "def-first: renderPlainSearch surfaces the definition line before mentions in the same file" {
var explorer = Explorer.init(testing.allocator, Explorer.DEFAULT_CONTENT_CACHE_CAPACITY);
defer explorer.deinit();

// A comment mentioning `gadget` appears BEFORE its definition; several call
// sites follow. Line-order rendering would show the line-1 comment first.
try explorer.indexFile("src/g.zig",
"// gadget helper\nconst a = gadget;\nconst b = gadget;\npub fn gadget() void {}\ngadget();\ngadget();\n");

var out: std.ArrayList(u8) = .empty;
defer out.deinit(testing.allocator);

const rendered = try explorer.renderPlainSearch("gadget", testing.allocator, &out, 6, false);
try testing.expect(rendered);

// the def line (`pub fn gadget`, line 4) must render before the comment mention (line 1)
const def_i = std.mem.indexOf(u8, out.items, "src/g.zig:4:");
const com_i = std.mem.indexOf(u8, out.items, "src/g.zig:1:");
try testing.expect(def_i != null and com_i != null);
try testing.expect(def_i.? < com_i.?);
}

test "def-first: renderPlainSearch ranks the defining file above higher-count mentions" {
var explorer = Explorer.init(testing.allocator, Explorer.DEFAULT_CONTENT_CACHE_CAPACITY);
defer explorer.deinit();
Expand Down