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%
1/**2 * KHAELOR3 * File: tests/tools/glob.test.ts4 * Description: Unit tests for the glob tool — pattern matching, mtime ordering, ignore behavior, caps.5 *6 * Author: Simon-Pierre Boucher7 * Contact: contact@spboucher.ai8 */910import { readFileSync, rmSync, utimesSync } from "node:fs";11import { afterEach, beforeEach, describe, expect, it } from "vitest";12import { createGlobTool } from "../../src/tools/index.js";13import type { TestHarness } from "./helpers.js";14import { fixture, makeHarness } from "./helpers.js";1516let h: TestHarness;17const glob = createGlobTool();1819beforeEach(() => {20 h = makeHarness();21});2223afterEach(() => {24 rmSync(h.dir, { recursive: true, force: true });25});2627describe("glob tool", () => {28 it("matches nested files and orders newest first", async () => {29 const oldFile = fixture(h, "src/old.ts", "x\n");30 const newFile = fixture(h, "src/deep/new.ts", "x\n");31 fixture(h, "src/skip.py", "x\n");32 const now = Date.now() / 1000;33 utimesSync(oldFile, now - 1000, now - 1000);34 utimesSync(newFile, now, now);3536 const result = await glob.execute({ pattern: "**/*.ts" }, h.ctx);37 expect(result.isError).toBeUndefined();38 const lines = result.content.split("\n");39 expect(lines[0]).toBe('2 files match "**/*.ts" (newest first):');40 expect(lines[1]).toBe("src/deep/new.ts");41 expect(lines[2]).toBe("src/old.ts");42 expect(result.metadata?.files).toBe(2);43 });4445 it("treats a slash-less pattern as a basename match at any depth", async () => {46 fixture(h, "a/b/config.json", "{}\n");47 const result = await glob.execute({ pattern: "config.*" }, h.ctx);48 expect(result.content).toContain("a/b/config.json");49 });5051 it("ignores node_modules, .git-style noise, and .gitignore entries", async () => {52 fixture(h, ".gitignore", "coverage/\n");53 fixture(h, "node_modules/pkg/index.ts", "x\n");54 fixture(h, "dist/out.ts", "x\n");55 fixture(h, "coverage/cov.ts", "x\n");56 fixture(h, "src/keep.ts", "x\n");57 const result = await glob.execute({ pattern: "**/*.ts" }, h.ctx);58 expect(result.content).toContain("src/keep.ts");59 expect(result.content).not.toContain("node_modules");60 expect(result.content).not.toContain("dist/out.ts");61 expect(result.content).not.toContain("coverage/cov.ts");62 expect(result.metadata?.files).toBe(1);63 });6465 it("searches built-in ignored dirs when the pattern targets them explicitly", async () => {66 fixture(h, "dist/out.ts", "x\n");67 const result = await glob.execute({ pattern: "dist/**/*.ts" }, h.ctx);68 expect(result.content).toContain("dist/out.ts");69 });7071 it("reports zero matches with a nearest-extension hint", async () => {72 fixture(h, "src/a.ts", "x\n");73 fixture(h, "src/b.ts", "x\n");74 const result = await glob.execute({ pattern: "src/**/*.tsx" }, h.ctx);75 expect(result.isError).toBeUndefined();76 expect(result.content).toContain(`No files match "src/**/*.tsx" under ${h.dir}.`);77 expect(result.content).toContain("Nearest existing extension: .ts (2 files).");78 });7980 it("caps at 100 paths and spills the full list", async () => {81 for (let i = 0; i < 120; i++) fixture(h, `many/f${String(i).padStart(3, "0")}.ts`, "x\n");82 const result = await glob.execute({ pattern: "many/*.ts" }, h.ctx);83 expect(result.content).toContain('120 files match "many/*.ts" (newest first):');84 expect(result.content).toContain("[Showing 100 of 120. Full list: ");85 expect(result.content).toContain("or narrow the pattern.]");86 const spill = result.metadata?.truncation?.spillPath;87 expect(spill).toBeDefined();88 expect(readFileSync(spill!, "utf8").split("\n")).toHaveLength(120);89 });9091 it("scopes the search with the path parameter", async () => {92 fixture(h, "src/a.ts", "x\n");93 fixture(h, "other/b.ts", "x\n");94 const result = await glob.execute({ pattern: "*.ts", path: "src" }, h.ctx);95 expect(result.content).toContain("a.ts");96 expect(result.content).not.toContain("b.ts");97 });98});99