diff options
| author | soryu <soryu@soryu.co> | 2026-05-18 01:21:30 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-05-18 01:21:30 +0100 |
| commit | f240675da99bc7705e473b8f70a2628812aa4c10 (patch) | |
| tree | 3ee2d24b431ccb8cd1a3013c86b34a5782a3e224 /makima/src/daemon/tui/fuzzy.rs | |
| parent | 0d996cf7590e3e52f424859c7d6f0e68640f119e (diff) | |
| download | soryu-master.tar.gz soryu-master.zip | |
The contracts table, supervisor task type, and all their backing
machinery have been inert for several PRs. The directives system reads
its own active contract body for spec text, and PR #135 removed the
last LLM surface that spawned supervisors.
This PR wipes the dead surface in one shot — the user authorised a DB
wipe, so the migration drops every legacy table with CASCADE rather
than carrying forward stub rows. Net change: −12k LOC across handlers,
repository, state, models, the TUI, and the listen module.
What's gone:
- contracts, contract_chat_*, contract_events, contract_repositories,
contract_type_templates tables.
- supervisor_states, supervisor_heartbeats tables.
- mesh_chat_conversations, mesh_chat_messages tables.
- tasks.contract_id/is_supervisor/supervisor_task_id/supervisor_worktree_task_id columns.
- directive_steps.contract_id/contract_type columns.
- files.contract_id/contract_phase columns.
- history_events.contract_id/phase columns.
- The Contract/Supervisor/MeshChat handler + model + repository
surface, plus the daemon TUI views that read them.
- The standalone listen.rs websocket handler (orphaned with the LLM).
What stays:
- mesh_supervisor handler: trimmed to just the questions + orders
backchannel used by `makima directive ask` / `create-order` (kept
the URL prefix for CLI client compat).
- directive_documents (the user-facing "contracts" surface).
- pending_questions in-memory state for the directive Ask flow.
cargo check, cargo test --lib (68 passed), tsc, and vite build all
clean.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'makima/src/daemon/tui/fuzzy.rs')
| -rw-r--r-- | makima/src/daemon/tui/fuzzy.rs | 217 |
1 files changed, 0 insertions, 217 deletions
diff --git a/makima/src/daemon/tui/fuzzy.rs b/makima/src/daemon/tui/fuzzy.rs deleted file mode 100644 index 44c27ad..0000000 --- a/makima/src/daemon/tui/fuzzy.rs +++ /dev/null @@ -1,217 +0,0 @@ -//! Fuzzy matching wrapper for search functionality. -//! -//! This module provides a wrapper around the `fuzzy-matcher` crate's -//! `SkimMatcherV2` algorithm, offering: -//! -//! - Single-term fuzzy matching with score and matched indices -//! - Multi-term search (space-separated patterns) -//! - Recency-adjusted scoring for time-aware results -//! - Case-insensitive matching by default -//! -//! # Examples -//! -//! ``` -//! use makima::daemon::tui::fuzzy::FuzzyMatcher; -//! -//! let matcher = FuzzyMatcher::new(); -//! -//! // Single pattern matching -//! if let Some((score, indices)) = matcher.fuzzy_match("hello world", "hlo") { -//! println!("Score: {}, Matched positions: {:?}", score, indices); -//! } -//! -//! // Multi-term search -//! if let Some(score) = matcher.fuzzy_match_all("fix authentication bug", "fix bug") { -//! println!("All terms matched with score: {}", score); -//! } -//! ``` - -use fuzzy_matcher::skim::SkimMatcherV2; -use fuzzy_matcher::FuzzyMatcher as FuzzyMatcherTrait; - -/// Fuzzy matcher wrapper providing search functionality. -/// -/// Wraps the `SkimMatcherV2` algorithm which provides: -/// - Smart case matching (case-insensitive unless pattern has uppercase) -/// - Word boundary bonuses -/// - Consecutive character bonuses -pub struct FuzzyMatcher { - matcher: SkimMatcherV2, -} - -impl FuzzyMatcher { - /// Create a new fuzzy matcher with default settings. - pub fn new() -> Self { - Self { - matcher: SkimMatcherV2::default(), - } - } - - /// Match a pattern against a string, returning score and matched indices. - /// - /// Returns `Some((score, indices))` if the pattern matches, where: - /// - `score` is a relevance score (higher is better) - /// - `indices` are the positions of matched characters in the text - /// - /// Returns `None` if the pattern doesn't match the text. - /// - /// # Arguments - /// - /// * `text` - The text to search in - /// * `pattern` - The pattern to search for - pub fn fuzzy_match(&self, text: &str, pattern: &str) -> Option<(i64, Vec<usize>)> { - self.matcher.fuzzy_indices(text, pattern) - } - - /// Match multiple patterns (space-separated) against a string. - /// - /// All patterns must match for the function to return a score. - /// The returned score is the sum of individual pattern scores. - /// - /// # Arguments - /// - /// * `text` - The text to search in - /// * `patterns` - Space-separated patterns (e.g., "fix bug" matches both "fix" and "bug") - /// - /// # Returns - /// - /// `Some(total_score)` if all patterns match, `None` otherwise. - pub fn fuzzy_match_all(&self, text: &str, patterns: &str) -> Option<i64> { - let patterns: Vec<&str> = patterns.split_whitespace().collect(); - - if patterns.is_empty() { - return Some(0); - } - - let mut total_score = 0i64; - - for pattern in patterns { - if let Some((score, _)) = self.matcher.fuzzy_indices(text, pattern) { - total_score += score; - } else { - return None; - } - } - - Some(total_score) - } - - /// Calculate a recency-adjusted score for time-aware sorting. - /// - /// Items with lower indices (more recent) receive a bonus to their score, - /// making them rank higher in search results. - /// - /// # Arguments - /// - /// * `base_score` - The original fuzzy match score - /// * `index` - The item's position in the list (0 = most recent) - /// * `total_items` - Total number of items in the list - /// - /// # Returns - /// - /// An adjusted score that factors in recency. - pub fn recency_adjusted_score(base_score: i64, index: usize, total_items: usize) -> i64 { - if total_items == 0 { - return base_score; - } - - // Recency bonus: items at the beginning get up to 20% bonus - // Formula: bonus = base_score * 0.2 * (1 - index/total_items) - let recency_factor = 1.0 - (index as f64 / total_items as f64); - let bonus = (base_score as f64 * 0.2 * recency_factor) as i64; - - base_score + bonus - } -} - -impl Default for FuzzyMatcher { - fn default() -> Self { - Self::new() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_fuzzy_match_exact() { - let matcher = FuzzyMatcher::new(); - let result = matcher.fuzzy_match("hello world", "hello"); - assert!(result.is_some()); - } - - #[test] - fn test_fuzzy_match_partial() { - let matcher = FuzzyMatcher::new(); - let result = matcher.fuzzy_match("authentication", "auth"); - assert!(result.is_some()); - } - - #[test] - fn test_fuzzy_match_no_match() { - let matcher = FuzzyMatcher::new(); - let result = matcher.fuzzy_match("hello", "xyz"); - assert!(result.is_none()); - } - - #[test] - fn test_multi_term_search() { - let matcher = FuzzyMatcher::new(); - let result = matcher.fuzzy_match_all("fix authentication bug", "fix bug"); - assert!(result.is_some()); - } - - #[test] - fn test_case_insensitive() { - let matcher = FuzzyMatcher::new(); - let result = matcher.fuzzy_match("Hello World", "hello"); - assert!(result.is_some()); - } - - #[test] - fn test_recency_bonus() { - // Earlier items (lower index) should get higher recency bonus - let score1 = FuzzyMatcher::recency_adjusted_score(100, 0, 50); - let score2 = FuzzyMatcher::recency_adjusted_score(100, 10, 50); - assert!(score1 > score2); - } - - #[test] - fn test_fuzzy_match_returns_indices() { - let matcher = FuzzyMatcher::new(); - let result = matcher.fuzzy_match("hello world", "hlo"); - assert!(result.is_some()); - let (_, indices) = result.unwrap(); - // Should have matched 3 characters - assert_eq!(indices.len(), 3); - } - - #[test] - fn test_multi_term_empty_pattern() { - let matcher = FuzzyMatcher::new(); - let result = matcher.fuzzy_match_all("hello world", ""); - assert!(result.is_some()); - assert_eq!(result.unwrap(), 0); - } - - #[test] - fn test_multi_term_partial_match_fails() { - let matcher = FuzzyMatcher::new(); - // "xyz" doesn't match, so the whole search should fail - let result = matcher.fuzzy_match_all("fix authentication bug", "fix xyz"); - assert!(result.is_none()); - } - - #[test] - fn test_recency_bonus_edge_cases() { - // Zero total items should return base score - let score = FuzzyMatcher::recency_adjusted_score(100, 0, 0); - assert_eq!(score, 100); - - // Last item should get minimal bonus - let score_last = FuzzyMatcher::recency_adjusted_score(100, 49, 50); - let score_first = FuzzyMatcher::recency_adjusted_score(100, 0, 50); - assert!(score_first > score_last); - } -} |
