SPB Git

spb/khaelor Public

KHAELOR — a terminal-native autonomous engineering agent powered by Anthropic.

TypeScript 82.9% HTML 14.9% CSS 1.1% JavaScript 0.7%
2.3 KB · 81 lines typescript
Raw Blame History
1/**2 * KHAELOR3 * File: src/tui/composer/fuzzy.ts4 * Description: Fuzzy subsequence matcher — exact-prefix bonus, boundary/consecutive scoring, matched-index output.5 *6 * Author: Simon-Pierre Boucher7 * Contact: contact@spboucher.ai8 */910export interface FuzzyResult {11  score: number;12  /** Indices of matched characters in the target (for underlining — §12: never color-only). */13  indices: number[];14}1516const BOUNDARY_CHARS = new Set(["/", "-", "_", ".", " ", ":"]);1718/**19 * Match `query` as a case-insensitive subsequence of `target`.20 * Scoring (fuzzysort-style): +1 per matched char, +3 consecutive, +2 at a21 * word boundary, large exact-prefix bonus, small penalty per unmatched22 * target character so shorter targets win ties. Returns null on no match.23 */24export function fuzzyMatch(query: string, target: string): FuzzyResult | null {25  if (query === "") return { score: 0, indices: [] };26  const q = query.toLowerCase();27  const t = target.toLowerCase();2829  const indices: number[] = [];30  let score = 0;31  let ti = 0;32  let prevMatch = -2;3334  for (let qi = 0; qi < q.length; qi++) {35    const ch = q[qi] as string;36    let found = -1;37    while (ti < t.length) {38      if (t[ti] === ch) {39        found = ti;40        break;41      }42      ti += 1;43    }44    if (found === -1) return null;45    indices.push(found);46    score += 1;47    if (found === prevMatch + 1) score += 3;48    if (found === 0 || BOUNDARY_CHARS.has(t[found - 1] as string)) score += 2;49    prevMatch = found;50    ti = found + 1;51  }5253  if (t.startsWith(q)) score += 100;54  score -= (t.length - q.length) * 0.05;55  return { score, indices };56}5758export interface FuzzyRanked<T> {59  item: T;60  score: number;61  indices: number[];62}6364/** Filter and rank `items` by fuzzy score against `key(item)`; stable for equal scores. */65export function fuzzyFilter<T>(66  query: string,67  items: readonly T[],68  key: (item: T) => string,69): FuzzyRanked<T>[] {70  const results: FuzzyRanked<T>[] = [];71  for (const item of items) {72    const match = fuzzyMatch(query, key(item));73    if (match !== null) results.push({ item, score: match.score, indices: match.indices });74  }75  // Stable sort: equal scores keep provider order.76  return results77    .map((r, i) => ({ r, i }))78    .sort((a, b) => b.r.score - a.r.score || a.i - b.i)79    .map((x) => x.r);80}81