use fuzzy_matcher::FuzzyMatcher;
use fuzzy_matcher::skim::SkimMatcherV2;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RegistryEntry {
pub name: String,
pub slug: String,
pub aliases: Vec<String>,
pub description: String,
pub llms_url: String,
}
impl RegistryEntry {
#[must_use]
pub fn new(name: &str, slug: &str, description: &str, llms_url: &str) -> Self {
Self {
name: name.to_string(),
slug: slug.to_string(),
aliases: vec![slug.to_string()],
description: description.to_string(),
llms_url: llms_url.to_string(),
}
}
#[must_use]
pub fn with_aliases(mut self, aliases: &[&str]) -> Self {
self.aliases = aliases.iter().map(|s| (*s).to_string()).collect();
self
}
}
impl std::fmt::Display for RegistryEntry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} ({})\n {}", self.name, self.slug, self.description)
}
}
pub struct Registry {
entries: Vec<RegistryEntry>,
}
impl Registry {
#[must_use]
pub fn new() -> Self {
let entries = vec![
RegistryEntry::new(
"Bun",
"bun",
"Fast all-in-one JavaScript runtime and package manager",
"https://bun.sh/docs/llms.txt",
)
.with_aliases(&["bun", "bunjs"]),
RegistryEntry::new(
"Node.js",
"node",
"JavaScript runtime built on Chrome's V8 JavaScript engine",
"https://nodejs.org/docs/llms.txt",
)
.with_aliases(&["node", "nodejs", "js"]),
RegistryEntry::new(
"Deno",
"deno",
"Modern runtime for JavaScript and TypeScript",
"https://docs.deno.com/llms.txt",
)
.with_aliases(&["deno"]),
RegistryEntry::new(
"React",
"react",
"JavaScript library for building user interfaces",
"https://react.dev/llms.txt",
)
.with_aliases(&["react", "reactjs"]),
RegistryEntry::new(
"Vue.js",
"vue",
"Progressive JavaScript framework for building UIs",
"https://vuejs.org/llms.txt",
)
.with_aliases(&["vue", "vuejs"]),
RegistryEntry::new(
"Next.js",
"nextjs",
"React framework for production with hybrid static & server rendering",
"https://nextjs.org/docs/llms.txt",
)
.with_aliases(&["nextjs", "next"]),
RegistryEntry::new(
"Claude Code",
"claude-code",
"Anthropic's AI coding assistant documentation",
"https://docs.anthropic.com/claude-code/llms.txt",
)
.with_aliases(&["claude-code", "claude"]),
RegistryEntry::new(
"Pydantic",
"pydantic",
"Data validation library using Python type hints",
"https://docs.pydantic.dev/llms.txt",
)
.with_aliases(&["pydantic"]),
RegistryEntry::new(
"Anthropic Claude API",
"anthropic",
"Claude API documentation and guides",
"https://docs.anthropic.com/llms.txt",
)
.with_aliases(&["anthropic", "claude-api"]),
RegistryEntry::new(
"OpenAI API",
"openai",
"OpenAI API documentation and guides",
"https://platform.openai.com/docs/llms.txt",
)
.with_aliases(&["openai", "gpt"]),
];
Self { entries }
}
#[must_use]
pub const fn from_entries(entries: Vec<RegistryEntry>) -> Self {
Self { entries }
}
#[must_use]
pub fn search(&self, query: &str) -> Vec<RegistrySearchResult> {
let matcher = SkimMatcherV2::default();
let query = query.trim().to_lowercase();
let mut results = Vec::new();
for entry in &self.entries {
let mut max_score = 0;
let mut best_match_field = "name";
if let Some(score) = matcher.fuzzy_match(&entry.name.to_lowercase(), &query) {
if score > max_score {
max_score = score;
best_match_field = "name";
}
}
if let Some(score) = matcher.fuzzy_match(&entry.slug.to_lowercase(), &query) {
if score > max_score {
max_score = score;
best_match_field = "slug";
}
}
for alias in &entry.aliases {
if let Some(score) = matcher.fuzzy_match(&alias.to_lowercase(), &query) {
if score > max_score {
max_score = score;
best_match_field = "alias";
}
}
}
if let Some(score) = matcher.fuzzy_match(&entry.description.to_lowercase(), &query) {
let description_score = score / 2; if description_score > max_score {
max_score = description_score;
best_match_field = "description";
}
}
if max_score > 0 {
results.push(RegistrySearchResult {
entry: entry.clone(),
score: max_score,
match_field: best_match_field.to_string(),
});
}
}
results.sort_by(|a, b| b.score.cmp(&a.score));
results
}
#[must_use]
pub fn all_entries(&self) -> &[RegistryEntry] {
&self.entries
}
}
impl Default for Registry {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct RegistrySearchResult {
pub entry: RegistryEntry,
pub score: i64,
pub match_field: String,
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
use super::*;
#[test]
fn test_registry_entry_creation() {
let entry = RegistryEntry::new(
"React",
"react",
"JavaScript library for building user interfaces",
"https://react.dev/llms.txt",
);
assert_eq!(entry.name, "React");
assert_eq!(entry.slug, "react");
assert_eq!(entry.aliases, vec!["react"]);
assert!(entry.description.contains("JavaScript library"));
assert_eq!(entry.llms_url, "https://react.dev/llms.txt");
}
#[test]
fn test_registry_entry_with_aliases() {
let entry = RegistryEntry::new(
"Node.js",
"node",
"JavaScript runtime",
"https://nodejs.org/llms.txt",
)
.with_aliases(&["node", "nodejs", "js"]);
assert_eq!(entry.aliases, vec!["node", "nodejs", "js"]);
}
#[test]
fn test_registry_creation() {
let registry = Registry::new();
let entries = registry.all_entries();
assert!(!entries.is_empty());
let react_entry = entries.iter().find(|e| e.slug == "react");
assert!(react_entry.is_some());
let node_entry = entries.iter().find(|e| e.slug == "node");
assert!(node_entry.is_some());
let claude_entry = entries.iter().find(|e| e.slug == "claude-code");
assert!(claude_entry.is_some());
}
#[test]
fn test_registry_search_exact_match() {
let registry = Registry::new();
let results = registry.search("react");
assert!(!results.is_empty());
let top_result = &results[0];
assert_eq!(top_result.entry.slug, "react");
}
#[test]
fn test_registry_search_fuzzy_match() {
let registry = Registry::new();
let results = registry.search("reactjs");
assert!(!results.is_empty());
let react_result = results.iter().find(|r| r.entry.slug == "react");
assert!(react_result.is_some());
}
#[test]
fn test_registry_search_partial_match() {
let registry = Registry::new();
let results = registry.search("claude");
assert!(!results.is_empty());
let has_claude = results.iter().any(|r| r.entry.slug.contains("claude"));
assert!(has_claude);
}
#[test]
fn test_registry_search_description_match() {
let registry = Registry::new();
let results = registry.search("javascript runtime");
assert!(!results.is_empty());
let has_js_runtime = results.iter().any(|r| {
r.entry.description.to_lowercase().contains("javascript")
&& r.entry.description.to_lowercase().contains("runtime")
});
assert!(has_js_runtime);
}
#[test]
fn test_registry_search_no_match() {
let registry = Registry::new();
let results = registry.search("nonexistentframework");
assert!(results.is_empty() || results[0].score < 50);
}
#[test]
fn test_registry_search_case_insensitive() {
let registry = Registry::new();
let results_lower = registry.search("react");
let results_upper = registry.search("REACT");
let results_mixed = registry.search("React");
assert!(!results_lower.is_empty());
assert!(!results_upper.is_empty());
assert!(!results_mixed.is_empty());
assert_eq!(results_lower[0].entry.slug, "react");
assert_eq!(results_upper[0].entry.slug, "react");
assert_eq!(results_mixed[0].entry.slug, "react");
}
#[test]
fn test_registry_display_format() {
let entry = RegistryEntry::new(
"React",
"react",
"JavaScript library for building user interfaces",
"https://react.dev/llms.txt",
);
let display = entry.to_string();
assert!(display.contains("React"));
assert!(display.contains("(react)"));
assert!(display.contains("JavaScript library"));
}
#[test]
fn test_all_registry_entries_have_valid_urls() {
let registry = Registry::new();
for entry in registry.all_entries() {
assert!(
entry.llms_url.starts_with("http://") || entry.llms_url.starts_with("https://")
);
assert!(
std::path::Path::new(&entry.llms_url)
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("txt"))
);
assert!(!entry.slug.contains(' '));
assert!(!entry.slug.chars().any(char::is_uppercase));
}
}
#[test]
fn test_registry_entries_have_unique_slugs() {
let registry = Registry::new();
let entries = registry.all_entries();
let mut slugs = std::collections::HashSet::new();
for entry in entries {
assert!(
slugs.insert(&entry.slug),
"Duplicate slug found: {}",
entry.slug
);
}
}
#[test]
fn test_registry_search_unicode_queries() {
let registry = Registry::new();
let results = registry.search("日本語");
assert!(results.is_empty() || results.iter().all(|r| r.score < 100));
let results = registry.search("العربية");
assert!(results.is_empty() || results.iter().all(|r| r.score < 100));
let results = registry.search("русский");
assert!(results.is_empty() || results.iter().all(|r| r.score < 100));
let results = registry.search("🚀");
assert!(results.is_empty() || results.iter().all(|r| r.score < 100));
let results = registry.search("react 日本語");
assert!(results.len() <= registry.all_entries().len());
}
#[test]
fn test_registry_search_very_long_queries() {
let registry = Registry::new();
let long_query = "javascript".repeat(1000);
let results = registry.search(&long_query);
assert!(results.len() <= registry.all_entries().len());
}
#[test]
fn test_registry_search_empty_and_whitespace() {
let registry = Registry::new();
let results = registry.search("");
assert!(results.is_empty());
let whitespace_queries = vec![" ", "\t", "\n", "\r\n", " \t \n "];
for query in whitespace_queries {
let results = registry.search(query);
assert!(
results.is_empty(),
"Whitespace query '{}' should return empty",
query.escape_debug()
);
}
}
#[test]
fn test_registry_search_special_characters() {
let registry = Registry::new();
let special_chars = vec![
"!@#$%^&*()",
"[]{}|\\;':\",./<>?",
"~`",
"react!",
"node.js",
"vue-js",
"next/js",
"c++",
"c#",
".net",
"node@18",
];
for query in special_chars {
let results = registry.search(query);
assert!(results.len() <= registry.all_entries().len());
}
}
#[test]
fn test_registry_search_multiple_spaces() {
let registry = Registry::new();
let spaced_queries = vec![
"javascript runtime",
"javascript runtime",
" javascript runtime ",
"javascript\truntime",
"javascript\n\nruntime",
];
for query in spaced_queries {
let results = registry.search(query);
assert!(results.len() <= registry.all_entries().len());
}
}
#[test]
fn test_registry_search_leading_trailing_whitespace() {
let registry = Registry::new();
let query_variants = vec![
"react",
" react",
"react ",
" react ",
"\treact\t",
"\nreact\n",
" \t react \n ",
];
for query in query_variants {
let results = registry.search(query);
assert!(
!results.is_empty(),
"Query '{}' should find results",
query.escape_debug()
);
assert_eq!(results[0].entry.slug, "react");
}
}
#[test]
fn test_registry_search_fuzzy_matching_edge_cases() {
let registry = Registry::new();
let fuzzy_cases = vec![
("react", "react"), ("nodejs", "node"), ("nextjs", "nextjs"), ("vue", "vue"), ];
for (query, expected_slug) in fuzzy_cases {
let results = registry.search(query);
assert!(
!results.is_empty(),
"Query '{query}' should find results for '{expected_slug}'"
);
let found_expected = results.iter().any(|r| r.entry.slug == expected_slug);
assert!(
found_expected,
"Query '{query}' should find entry '{expected_slug}'"
);
}
let typo_queries = vec!["reactt", "reac", "raect", "nxtjs", "vue.js"];
for query in typo_queries {
let results = registry.search(query);
assert!(results.len() <= registry.all_entries().len());
}
}
#[test]
fn test_registry_search_score_ranking() {
let registry = Registry::new();
let results = registry.search("react");
assert!(!results.is_empty());
assert_eq!(results[0].entry.slug, "react");
let results = registry.search("node");
assert!(!results.is_empty());
let node_result = results.iter().find(|r| r.entry.slug == "node");
assert!(node_result.is_some());
let node_score = node_result.unwrap().score;
assert!(
node_score > 50,
"Node.js should have high score for 'node' query"
);
}
#[test]
fn test_registry_search_alias_matching() {
let registry = Registry::new();
let alias_tests = vec![
("reactjs", "react"),
("nodejs", "node"),
("js", "node"),
("bunjs", "bun"),
("claude", "claude-code"),
("claude-api", "anthropic"),
("gpt", "openai"),
];
for (query, expected_slug) in alias_tests {
let results = registry.search(query);
assert!(!results.is_empty(), "Query '{query}' should find results");
let found_entry = results.iter().find(|r| r.entry.slug == expected_slug);
assert!(
found_entry.is_some(),
"Query '{query}' should find entry '{expected_slug}'"
);
let found = found_entry.unwrap();
assert!(
found.match_field == "alias"
|| found.match_field == "slug"
|| found.match_field == "name",
"Match field should indicate alias/slug/name match for '{}' -> '{}', got '{}'",
query,
expected_slug,
found.match_field
);
}
}
#[test]
fn test_registry_search_case_variations() {
let registry = Registry::new();
let test_cases = vec!["REACT", "React", "rEaCt", "react"];
let mut all_scores = Vec::new();
for query in &test_cases {
let results = registry.search(query);
assert!(!results.is_empty(), "Query '{query}' should find results");
assert_eq!(results[0].entry.slug, "react");
all_scores.push(results[0].score);
}
let min_score = *all_scores.iter().min().unwrap();
let max_score = *all_scores.iter().max().unwrap();
assert!(
(max_score - min_score) <= 50,
"Case variations should have similar scores"
);
}
#[test]
fn test_registry_search_performance() {
let registry = Registry::new();
let queries = vec![
"react",
"node",
"vue",
"angular",
"javascript",
"typescript",
"python",
"rust",
"go",
"java",
"c++",
"c#",
"nonexistent",
"blahblahblah",
"qwerty",
"asdfgh",
];
let start_time = std::time::Instant::now();
for query in &queries {
let results = registry.search(query);
assert!(results.len() <= registry.all_entries().len());
}
let elapsed = start_time.elapsed();
assert!(
elapsed < std::time::Duration::from_millis(100),
"Registry search should be fast, took {elapsed:?}"
);
}
#[test]
fn test_registry_search_boundary_conditions() {
let registry = Registry::new();
let single_chars = vec!["a", "j", "r", "n", "v"];
for char_query in single_chars {
let results = registry.search(char_query);
assert!(results.len() <= registry.all_entries().len());
}
let max_query = "a".repeat(1000);
let results = registry.search(&max_query);
assert!(results.len() <= registry.all_entries().len());
let punct_results = registry.search("!@#$%^&*()");
assert!(punct_results.is_empty() || punct_results.iter().all(|r| r.score < 50));
}
#[test]
fn test_registry_search_description_weighting() {
let registry = Registry::new();
let results = registry.search("documentation");
if !results.is_empty() {
for i in 1..results.len() {
assert!(
results[i - 1].score >= results[i].score,
"Results should be sorted by score descending"
);
}
let desc_matches = results
.iter()
.filter(|r| r.match_field == "description")
.collect::<Vec<_>>();
let name_matches = results
.iter()
.filter(|r| r.match_field == "name" || r.match_field == "slug")
.collect::<Vec<_>>();
if !desc_matches.is_empty() && !name_matches.is_empty() {
let max_desc_score = desc_matches.iter().map(|r| r.score).max().unwrap();
let min_name_score = name_matches.iter().map(|r| r.score).min().unwrap();
if max_desc_score > min_name_score {
} else {
assert!(
max_desc_score <= min_name_score * 2,
"Description match scores should be weighted appropriately"
);
}
}
}
}
}