use std::collections::HashMap;
use std::path::{Component, Path, PathBuf};
use std::time::SystemTime;
use crate::types::{is_test_file, Match};
const VENDOR_DIRS: &[&str] = &[
"node_modules",
"vendor",
"dist",
"build",
".git",
"target",
"__pycache__",
".venv",
"venv",
"pkg",
"out",
];
pub fn sort(matches: &mut [Match], query: &str, scope: &Path, context: Option<&Path>) {
let ctx_parent = context.and_then(|c| c.parent());
let ctx_pkg_root = context
.and_then(package_root)
.map(std::path::Path::to_path_buf);
let mut pkg_cache: HashMap<PathBuf, Option<PathBuf>> = HashMap::new();
let now = SystemTime::now();
matches.sort_by(|a, b| {
let sa = score(
a,
query,
scope,
ctx_parent,
ctx_pkg_root.as_ref(),
&mut pkg_cache,
now,
);
let sb = score(
b,
query,
scope,
ctx_parent,
ctx_pkg_root.as_ref(),
&mut pkg_cache,
now,
);
sb.cmp(&sa)
.then_with(|| a.path.cmp(&b.path))
.then_with(|| a.line.cmp(&b.line))
});
}
fn score(
m: &Match,
query: &str,
scope: &Path,
ctx_parent: Option<&Path>,
ctx_pkg_root: Option<&PathBuf>,
pkg_cache: &mut HashMap<PathBuf, Option<PathBuf>>,
now: SystemTime,
) -> i32 {
let mut s = 0i32;
if m.is_definition {
s += i32::from(m.def_weight) * 10;
s += definition_name_boost(m, query);
}
if m.exact {
s += 500;
}
s += query_intent_boost(m, query);
s += multi_word_boost(m, query);
s += scope_proximity(&m.path, scope) as i32;
s += recency(m.mtime, now) as i32;
if m.file_lines > 0 && m.file_lines < 200 {
s += 50;
}
if ctx_parent.is_some() || ctx_pkg_root.is_some() {
s += context_proximity(&m.path, ctx_parent, ctx_pkg_root, pkg_cache);
}
s += basename_boost(&m.path, query);
s += exported_api_boost(m);
s -= non_code_penalty(&m.path);
s -= incidental_text_penalty(m, query);
if is_test_file(&m.path) && !looks_like_test_query(query) {
s -= 120;
}
s -= fixture_penalty(m);
if is_vendor_path(&m.path) {
s -= 200;
}
s
}
fn basename_boost(path: &Path, query: &str) -> i32 {
if query.is_empty() {
return 0;
}
let Some(stem) = path.file_stem().and_then(|s| s.to_str()) else {
return 0;
};
let stem_lower = stem.to_ascii_lowercase();
let query_lower = query.to_ascii_lowercase();
if stem_lower == query_lower {
return 500;
}
if stem_lower.starts_with(&query_lower)
&& stem_lower
.as_bytes()
.get(query_lower.len())
.is_some_and(|&b| b == b'_' || b == b'.' || b == b'-')
{
return 350;
}
if stem_lower.ends_with(&query_lower) {
return 250;
}
if stem_lower.contains(&query_lower) {
return 180;
}
let parent_name = path
.parent()
.and_then(|p| p.file_name())
.and_then(|s| s.to_str())
.unwrap_or("");
if parent_name.eq_ignore_ascii_case(query) {
return 200;
}
0
}
fn scope_proximity(path: &Path, scope: &Path) -> u32 {
let rel = path.strip_prefix(scope).unwrap_or(path);
let depth = rel.components().count();
200u32.saturating_sub(depth as u32 * 20)
}
fn context_proximity(
match_path: &Path,
ctx_parent: Option<&Path>,
ctx_pkg_root: Option<&PathBuf>,
pkg_cache: &mut HashMap<PathBuf, Option<PathBuf>>,
) -> i32 {
let mut score = 0;
if let Some(cp) = ctx_parent {
if match_path.parent() == Some(cp) {
score += 100;
} else if shared_prefix_depth(cp, match_path.parent().unwrap_or(match_path)) >= 2 {
score += 40;
}
}
if let Some(cp_root) = ctx_pkg_root {
let match_dir = match match_path.parent() {
Some(d) => d.to_path_buf(),
None => return score,
};
let match_root = pkg_cache
.entry(match_dir)
.or_insert_with_key(|dir| package_root(dir).map(std::path::Path::to_path_buf));
if let Some(ref mr) = match_root {
if mr == cp_root {
score += 75;
}
}
}
score
}
fn definition_name_boost(m: &Match, query: &str) -> i32 {
let Some(name) = m.def_name.as_deref() else {
return 0;
};
let query_lower = query.to_ascii_lowercase();
let name_lower = name.to_ascii_lowercase();
if name == query {
220
} else if name_lower == query_lower {
180
} else if m.impl_target.as_deref() == Some(query) {
120
} else if name_lower.starts_with(&query_lower) {
80
} else if name_lower.contains(&query_lower) {
40
} else {
0
}
}
fn query_intent_boost(m: &Match, query: &str) -> i32 {
if query.is_empty() {
return 0;
}
let looks_type = query.chars().next().is_some_and(char::is_uppercase);
let looks_fn = query.chars().next().is_some_and(char::is_lowercase);
let text = m.text.trim_start();
if looks_type
&& (text.starts_with("struct ")
|| text.starts_with("pub struct ")
|| text.starts_with("enum ")
|| text.starts_with("pub enum ")
|| text.starts_with("trait ")
|| text.starts_with("pub trait ")
|| text.starts_with("interface ")
|| text.starts_with("export interface ")
|| text.starts_with("type ")
|| text.starts_with("export type ")
|| text.starts_with("class ")
|| text.starts_with("export class ")
|| text.starts_with("impl "))
{
return 90;
}
if looks_fn
&& (text.starts_with("fn ")
|| text.starts_with("pub fn ")
|| text.starts_with("pub(crate) fn ")
|| text.starts_with("async fn ")
|| text.starts_with("pub async fn ")
|| text.starts_with("function ")
|| text.starts_with("export function ")
|| text.starts_with("export default function ")
|| text.starts_with("export async function "))
{
return 70;
}
0
}
fn exported_api_boost(m: &Match) -> i32 {
let text = m.text.trim_start();
if text.starts_with("export default ") {
90
} else if text.starts_with("export ") {
75
} else if text.starts_with("pub ") {
60
} else {
0
}
}
fn fixture_penalty(m: &Match) -> i32 {
let path = m.path.to_string_lossy().to_ascii_lowercase();
let text = m.text.to_ascii_lowercase();
let mut score = 0;
for needle in ["mock", "fixture", "stub", "fake", "example"] {
if path.contains(needle) {
score += 90;
}
if text.contains(needle) {
score += 40;
}
}
score.min(200)
}
fn incidental_text_penalty(m: &Match, query: &str) -> i32 {
if m.is_definition {
return 0;
}
let text = m.text.trim();
let q_lower = query.to_ascii_lowercase();
let is_comment = (text.starts_with("//") && !text.starts_with("///"))
|| text.starts_with("/*")
|| text.starts_with("<!--");
let is_hash_comment = text.starts_with('#') && {
let ext = m.path.extension().and_then(|e| e.to_str()).unwrap_or("");
matches!(
ext,
"py" | "rb" | "sh" | "bash" | "zsh" | "yaml" | "yml" | "toml" | "pl" | "r" | "R"
)
};
if is_comment || is_hash_comment {
return 150;
}
let t_lower = text.to_ascii_lowercase();
if let Some(slash_pos) = t_lower.find("//") {
let is_url = slash_pos > 0 && t_lower.as_bytes()[slash_pos - 1] == b':';
if slash_pos > 0
&& !is_url
&& t_lower[slash_pos..].contains(&q_lower)
&& !t_lower[..slash_pos].contains(&q_lower)
{
return 100;
}
}
0
}
fn multi_word_boost(m: &Match, query: &str) -> i32 {
if !query.contains(' ') {
return 0;
}
let words: Vec<&str> = query.split_whitespace().collect();
if words.len() < 2 {
return 0;
}
let path_lower = m.path.to_string_lossy().to_ascii_lowercase();
let text_lower = m.text.to_ascii_lowercase();
let haystack = format!("{path_lower} {text_lower}");
let matched = words
.iter()
.filter(|w| haystack.contains(&w.to_ascii_lowercase()))
.count();
if matched == words.len() {
300
} else if matched >= words.len() - 1 {
120
} else {
0
}
}
fn non_code_penalty(path: &Path) -> i32 {
let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
let has_docs_component = path.components().any(|c| {
c.as_os_str()
.to_str()
.is_some_and(|s| s == "docs" || s == "doc")
});
let is_docs = ext == "md" || ext == "mdx" || ext == "txt" || ext == "rst" || has_docs_component;
let path_str = path.to_string_lossy();
let is_config_example = (path_str.contains("example")
|| path_str.contains("sample")
|| path_str.contains("template"))
&& (ext == "md" || ext == "txt" || ext == "rst");
let is_generated = path_str.contains("generated");
let mut penalty = 0;
if is_docs {
penalty += 250;
}
if is_config_example {
penalty += 80;
}
if is_generated {
penalty += 150;
}
penalty
}
fn looks_like_test_query(query: &str) -> bool {
let q = query.to_ascii_lowercase();
q.contains("test") || q.contains("spec") || q.starts_with("should")
}
fn shared_prefix_depth(a: &Path, b: &Path) -> usize {
a.components()
.filter(|c| matches!(c, Component::Normal(_)))
.zip(b.components().filter(|c| matches!(c, Component::Normal(_))))
.take_while(|(l, r)| l == r)
.count()
}
fn package_root(path: &Path) -> Option<&Path> {
crate::lang::package_root(path)
}
fn is_vendor_path(path: &Path) -> bool {
path.components().any(|c| {
c.as_os_str()
.to_str()
.is_some_and(|s| VENDOR_DIRS.contains(&s))
})
}
fn recency(mtime: SystemTime, now: SystemTime) -> u32 {
let age = now.duration_since(mtime).unwrap_or_default().as_secs();
match age {
0..=3_600 => 100, 3_601..=86_400 => 80, 86_401..=604_800 => 50, 604_801..=2_592_000 => 20, _ => 0,
}
}
#[cfg(test)]
mod tests {
use super::sort;
use crate::types::Match;
use std::path::PathBuf;
use std::time::SystemTime;
fn make_match(path: &str, text: &str, is_definition: bool, def_name: Option<&str>) -> Match {
Match {
path: PathBuf::from(path),
line: 1,
text: text.to_string(),
is_definition,
exact: true,
file_lines: 40,
mtime: SystemTime::now(),
def_range: None,
def_name: def_name.map(ToString::to_string),
def_weight: if is_definition { 80 } else { 0 },
impl_target: None,
base_target: None,
in_comment: false,
}
}
#[test]
fn prefers_exact_definition_name_over_usage() {
let scope = PathBuf::from("/repo/src");
let mut matches = vec![
make_match("/repo/src/auth.rs", "handleAuth(user)", false, None),
make_match(
"/repo/src/auth.rs",
"pub fn handleAuth(req: Request) -> Response {",
true,
Some("handleAuth"),
),
];
sort(&mut matches, "handleAuth", &scope, None);
assert!(matches[0].is_definition);
assert_eq!(matches[0].def_name.as_deref(), Some("handleAuth"));
}
#[test]
fn prefers_non_test_match_for_non_test_query() {
let scope = PathBuf::from("/repo/src");
let mut matches = vec![
make_match(
"/repo/src/__tests__/auth.test.ts",
"export function handleAuth() {",
true,
Some("handleAuth"),
),
make_match(
"/repo/src/auth.ts",
"export function handleAuth() {",
true,
Some("handleAuth"),
),
];
sort(&mut matches, "handleAuth", &scope, None);
assert_eq!(matches[0].path, PathBuf::from("/repo/src/auth.ts"));
}
#[test]
fn prefers_same_subtree_as_context() {
let scope = PathBuf::from("/repo/src");
let context = PathBuf::from("/repo/src/auth/controller.rs");
let mut matches = vec![
make_match(
"/repo/src/payments/service.rs",
"pub fn handleAuth() {",
true,
Some("handleAuth"),
),
make_match(
"/repo/src/auth/service.rs",
"pub fn handleAuth() {",
true,
Some("handleAuth"),
),
];
sort(&mut matches, "handleAuth", &scope, Some(&context));
assert_eq!(matches[0].path, PathBuf::from("/repo/src/auth/service.rs"));
}
#[test]
fn prefers_exported_api_over_local_definition() {
let scope = PathBuf::from("/repo/src");
let mut matches = vec![
make_match(
"/repo/src/internal/auth.ts",
"function handleAuth() {",
true,
Some("handleAuth"),
),
make_match(
"/repo/src/public/auth.ts",
"export function handleAuth() {",
true,
Some("handleAuth"),
),
];
sort(&mut matches, "handleAuth", &scope, None);
assert_eq!(matches[0].path, PathBuf::from("/repo/src/public/auth.ts"));
}
#[test]
fn prefers_real_definition_over_fixture_match() {
let scope = PathBuf::from("/repo/src");
let mut matches = vec![
make_match(
"/repo/src/fixtures/auth-fixture.ts",
"export function handleAuth() {",
true,
Some("handleAuth"),
),
make_match(
"/repo/src/auth.ts",
"export function handleAuth() {",
true,
Some("handleAuth"),
),
];
sort(&mut matches, "handleAuth", &scope, None);
assert_eq!(matches[0].path, PathBuf::from("/repo/src/auth.ts"));
}
#[test]
fn prefers_thinking_logic_over_schema_for_concept_query() {
let scope = PathBuf::from("/repo/src");
let mut matches = vec![
make_match(
"/repo/src/internal/interfaces/client_models.go",
"ThinkingConfig *GenerationConfigThinkingConfig `json:\"thinkingConfig,omitempty\"`",
false,
None,
),
make_match(
"/repo/src/internal/util/thinking.go",
"func NormalizeThinkingBudget(model string, requested int) int {",
true,
Some("NormalizeThinkingBudget"),
),
];
sort(&mut matches, "thinking", &scope, None);
assert!(
matches[0].path.to_string_lossy().contains("thinking.go"),
"expected thinking.go first, got {:?}",
matches[0].path,
);
}
#[test]
fn prefers_model_mapping_logic_over_docs_for_alias_query() {
let scope = PathBuf::from("/repo/src");
let mut matches = vec![
make_match(
"/repo/src/docs/FORCE_HANDLER_GUIDE.md",
"Alias routing example",
false,
None,
),
make_match(
"/repo/src/internal/api/modules/amp/model_mapping.go",
"func (m *DefaultModelMapper) MapModel(requestedModel string) string {",
true,
Some("MapModel"),
),
];
sort(&mut matches, "alias", &scope, None);
assert!(
matches[0].path.to_string_lossy().contains("model_mapping"),
"expected model_mapping.go first, got {:?}",
matches[0].path,
);
}
#[test]
fn non_code_penalty_docs_positive() {
let path = PathBuf::from("/repo/docs/guide.md");
assert!(super::non_code_penalty(&path) > 0);
}
#[test]
fn non_code_penalty_no_double_penalty_for_dist() {
let path = PathBuf::from("/repo/dist/bundle.js");
assert_eq!(super::non_code_penalty(&path), 0);
}
#[test]
fn non_code_penalty_no_double_penalty_for_build() {
let path = PathBuf::from("/repo/build/output.js");
assert_eq!(super::non_code_penalty(&path), 0);
}
#[test]
fn non_code_penalty_generated_without_dist() {
let path = PathBuf::from("/repo/src/generated/types.ts");
assert!(super::non_code_penalty(&path) > 0);
}
#[test]
fn non_code_penalty_normal_code_zero() {
let path = PathBuf::from("/repo/src/auth.rs");
assert_eq!(super::non_code_penalty(&path), 0);
}
#[test]
fn fixture_penalty_capped_at_200() {
let m = make_match(
"/repo/src/fixtures/mock_stub_fake.ts",
"example fixture mock stub fake",
false,
None,
);
let penalty = super::fixture_penalty(&m);
assert!(
penalty <= 200,
"fixture_penalty was {penalty}, expected <= 200"
);
assert!(penalty > 0);
}
#[test]
fn fixture_penalty_zero_for_normal_code() {
let m = make_match(
"/repo/src/auth.ts",
"export function handleAuth() {",
true,
Some("handleAuth"),
);
assert_eq!(super::fixture_penalty(&m), 0);
}
#[test]
fn incidental_text_penalty_comment_line() {
let m = make_match(
"/repo/src/lib.rs",
"// handleAuth is deprecated",
false,
None,
);
assert_eq!(super::incidental_text_penalty(&m, "handleAuth"), 150);
}
#[test]
fn incidental_text_penalty_no_hash_false_positive() {
let m = make_match("/repo/src/main.c", "#include <stdio.h>", false, None);
assert_eq!(super::incidental_text_penalty(&m, "stdio"), 0);
}
#[test]
fn incidental_text_penalty_hash_comment_in_python() {
let m = make_match(
"/repo/src/main.py",
"# handle_auth is deprecated",
false,
None,
);
assert_eq!(super::incidental_text_penalty(&m, "handle_auth"), 150);
}
#[test]
fn incidental_text_penalty_no_star_false_positive() {
let m = make_match("/repo/src/main.c", "*ptr = value;", false, None);
assert_eq!(super::incidental_text_penalty(&m, "ptr"), 0);
}
#[test]
fn incidental_text_penalty_no_string_literal_heuristic() {
let m = make_match(
"/repo/src/lib.rs",
r#"let msg = "handleAuth error";"#,
false,
None,
);
assert_eq!(super::incidental_text_penalty(&m, "handleAuth"), 0);
}
#[test]
fn incidental_text_penalty_trailing_comment() {
let m = make_match(
"/repo/src/lib.rs",
"let x = 1; // handleAuth workaround",
false,
None,
);
assert_eq!(super::incidental_text_penalty(&m, "handleAuth"), 100);
}
#[test]
fn incidental_text_penalty_url_not_comment() {
let m = make_match(
"/repo/src/lib.rs",
r#"let url = "https://handleAuth.example.com";"#,
false,
None,
);
assert_eq!(super::incidental_text_penalty(&m, "handleAuth"), 0);
}
#[test]
fn incidental_text_penalty_skip_definitions() {
let m = make_match(
"/repo/src/lib.rs",
"// handleAuth docs",
true,
Some("handleAuth"),
);
assert_eq!(super::incidental_text_penalty(&m, "handleAuth"), 0);
}
#[test]
fn incidental_text_penalty_doc_comment_exempt() {
let m = make_match(
"/repo/src/lib.rs",
"/// Handles auth validation for incoming requests",
false,
None,
);
assert_eq!(super::incidental_text_penalty(&m, "auth"), 0);
}
#[test]
fn sign_convention_all_penalties_positive() {
let doc_path = PathBuf::from("/repo/docs/guide.md");
assert!(super::non_code_penalty(&doc_path) >= 0);
let fixture = make_match("/repo/fixtures/mock.ts", "mock data", false, None);
assert!(super::fixture_penalty(&fixture) >= 0);
let comment = make_match("/repo/src/lib.rs", "// TODO fix auth", false, None);
assert!(super::incidental_text_penalty(&comment, "auth") >= 0);
}
#[test]
fn vendor_path_detects_dist_and_build() {
assert!(super::is_vendor_path(&PathBuf::from(
"/repo/dist/bundle.js"
)));
assert!(super::is_vendor_path(&PathBuf::from(
"/repo/build/output.js"
)));
assert!(!super::is_vendor_path(&PathBuf::from("/repo/src/auth.rs")));
}
}