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%
3.4 KB · 100 lines typescript
Raw Blame History
1/**2 * KHAELOR3 * File: tests/repository/search.test.ts4 * Description: FileSearch tests — fuzzy ranking tiers (exact > prefix > subsequence) and frecency boosts.5 *6 * Author: Simon-Pierre Boucher7 * Contact: contact@spboucher.ai8 */910import { rmSync } from "node:fs";11import { afterEach, beforeEach, describe, expect, it } from "vitest";12import {13  FileSearch,14  RecentFilesTracker,15  RepositoryMap,16  subsequenceQuality,17} from "../../src/repository/index.js";18import { LocalWorkspace } from "../../src/workspace/index.js";19import { makeTempDir, writeFixtureFile } from "./fixtures.js";2021let dir: string;22let map: RepositoryMap;23let recent: RecentFilesTracker;24let search: FileSearch;2526beforeEach(() => {27  dir = makeTempDir("khaelor-search-");28  for (const rel of [29    "abc.ts",30    "abcdef.ts",31    "axbxcx.ts",32    "src/alpha/agent.ts",33    "src/betaa/agent.ts",34    "notes.md",35  ]) {36    writeFixtureFile(dir, rel, "content\n");37  }38  map = new RepositoryMap(new LocalWorkspace(dir));39  recent = new RecentFilesTracker();40  search = new FileSearch(map, recent);41});4243afterEach(() => {44  rmSync(dir, { recursive: true, force: true });45});4647describe("FileSearch ranking tiers", () => {48  it("ranks exact matches above prefix matches above subsequence matches", async () => {49    const exact = await search.findFiles("abc.ts");50    expect(exact[0]?.path).toBe("abc.ts");5152    const prefix = await search.findFiles("abc");53    expect(prefix.map((r) => r.path)).toEqual(["abc.ts", "abcdef.ts", "axbxcx.ts"]);54    expect(prefix[0]?.score).toBeGreaterThan(prefix[2]?.score ?? 0);55  });5657  it("excludes files that are not even a subsequence match", async () => {58    expect(await search.findFiles("zzz")).toEqual([]);59    expect(subsequenceQuality("notes.md", "zzz")).toBeUndefined();60    expect(subsequenceQuality("axbxcx.ts", "abc")).toBeGreaterThan(0);61  });6263  it("respects the result limit", async () => {64    const results = await search.findFiles("a", 2);65    expect(results).toHaveLength(2);66  });67});6869describe("FileSearch frecency boost", () => {70  it("breaks ties deterministically without access history", async () => {71    const results = await search.findFiles("agent");72    expect(results.map((r) => r.path)).toEqual(["src/alpha/agent.ts", "src/betaa/agent.ts"]);73  });7475  it("boosts recently accessed files within the same match tier", async () => {76    recent.noteAccess("src/betaa/agent.ts");77    const results = await search.findFiles("agent");78    expect(results.map((r) => r.path)).toEqual(["src/betaa/agent.ts", "src/alpha/agent.ts"]);79  });8081  it("never lets frecency promote a weaker match class above a stronger one", async () => {82    // axbxcx.ts is only a subsequence match for "abc" — heavy access must not83    // rank it above the prefix matches.84    for (let i = 0; i < 10; i += 1) recent.noteAccess("axbxcx.ts");85    const results = await search.findFiles("abc");86    expect(results.map((r) => r.path)).toEqual(["abc.ts", "abcdef.ts", "axbxcx.ts"]);87  });88});8990describe("FileSearch empty query", () => {91  it("returns recently accessed files first, then alphabetical fill", async () => {92    recent.noteAccess("notes.md");93    const results = await search.findFiles("", 4);94    expect(results[0]?.path).toBe("notes.md");95    expect(results).toHaveLength(4);96    const rest = results.slice(1).map((r) => r.path);97    expect(rest).toEqual([...rest].sort((a, b) => a.localeCompare(b)));98  });99});100