"""
Base interface for semantic code searchers.
All searcher implementations must inherit from BaseSearcher and implement
the search() method.
"""
from __future__ import annotations
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import List, Optional
@dataclass
class SearchItem:
"""A single search result item."""
file_path: str
content: str
# Optional metadata for debugging/analysis
line_start: Optional[int] = None
line_end: Optional[int] = None
match_context: Optional[str] = None # e.g., matched pattern
@dataclass
class SearchResult:
"""Result of a semantic search operation."""
items: List[SearchItem] = field(default_factory=list)
# Metadata for debugging/analysis
patterns_used: List[str] = field(default_factory=list)
execution_time_ms: Optional[float] = None # LLM + verification time (excluding grep)
total_time_ms: Optional[float] = None # Total wall-clock time
tool_time_ms: Optional[float] = None # Time spent in tools (grep, etc.)
error: Optional[str] = None
def to_dict(self) -> dict:
"""Convert to dict format expected by MCP tool."""
return {
"items": [
{
"file_path": item.file_path,
"content": item.content,
}
for item in self.items
],
# execution_time_ms = LLM + verification (excludes grep per task spec)
"execution_time_ms": self.execution_time_ms,
"total_time_ms": self.total_time_ms,
"tool_time_ms": self.tool_time_ms,
"error": self.error,
}
def limit(self, max_items: int) -> "SearchResult":
"""Return a new SearchResult with at most max_items."""
return SearchResult(
items=self.items[:max_items],
patterns_used=self.patterns_used,
execution_time_ms=self.execution_time_ms,
total_time_ms=self.total_time_ms,
tool_time_ms=self.tool_time_ms,
error=self.error,
)
class BaseSearcher(ABC):
"""
Abstract base class for semantic code searchers.
Different implementations can use different strategies:
- LLM + ripgrep
- LLM + tree-sitter
- Vector embeddings
- etc.
"""
@property
@abstractmethod
def name(self) -> str:
"""Human-readable name of the searcher."""
pass
@abstractmethod
def search(
self,
query: str,
repo_path: str,
path: Optional[str] = None,
) -> SearchResult:
"""
Perform semantic search on the codebase.
Args:
query: Natural language description of what to find
repo_path: Path to the repository root
path: Optional subdirectory to limit search scope
Returns:
SearchResult with found code snippets
"""
pass
def __repr__(self) -> str:
return f"<{self.__class__.__name__}: {self.name}>"