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%
4.4 KB · 108 lines typescript
Raw Blame History
1/**2 * KHAELOR3 * File: tests/repograph/repograph.test.ts4 * Description: RepoGraph tests — TS/Python extraction, symbol queries, refs directions, skeletons (v2 §3).5 *6 * Author: Simon-Pierre Boucher7 * Contact: contact@spboucher.ai8 */910import { mkdtempSync, mkdirSync, writeFileSync } from "node:fs";11import { tmpdir } from "node:os";12import { join } from "node:path";13import { describe, expect, it } from "vitest";14import { extractFile, RepoGraphService } from "../../src/repograph/index.js";15import { LocalWorkspace } from "../../src/workspace/index.js";1617const TS_SAMPLE = `/**18 * Handles authentication.19 */20export async function handleAuth(token: string): Promise<boolean> {21  return verifyToken(token);22}2324export class AuthController {25  /** Login entrypoint. */26  async login(user: string): Promise<void> {27    await handleAuth(user);28  }29}3031export interface Session { id: string }32export type AuthResult = boolean;33const secret = "x";34import { verifyToken } from "./token.js";35`;3637describe("extractFile (TS)", () => {38  it("extracts functions, classes, methods, types, and imports", () => {39    const result = extractFile("src/auth.ts", TS_SAMPLE);40    const names = result.symbols.map((symbol) => `${symbol.kind}:${symbol.name}`);41    expect(names).toContain("function:handleAuth");42    expect(names).toContain("class:AuthController");43    expect(names).toContain("method:login");44    expect(names).toContain("type:Session");45    expect(names).toContain("type:AuthResult");46    expect(names).toContain("variable:secret");47    const auth = result.symbols.find((symbol) => symbol.name === "handleAuth");48    expect(auth?.docComment).toContain("Handles authentication");49    expect(auth?.exported).toBe(true);50    expect(result.imports[0]?.spec).toBe("./token.js");51    expect(result.imports[0]?.names).toContain("verifyToken");52  });53});5455describe("extractFile (Python)", () => {56  it("extracts defs, classes, and imports", () => {57    const result = extractFile("app.py", "import os\nfrom flask import Flask\n\nclass App:\n    def run(self):\n        pass\n\ndef main():\n    pass\n");58    const names = result.symbols.map((symbol) => `${symbol.kind}:${symbol.name}`);59    expect(names).toContain("class:App");60    expect(names).toContain("method:run");61    expect(names).toContain("function:main");62    expect(result.imports.map((imp) => imp.spec)).toEqual(["os", "flask"]);63  });64});6566describe("RepoGraphService", () => {67  function makeRepo(): string {68    const dir = mkdtempSync(join(tmpdir(), "khaelor-repograph-"));69    mkdirSync(join(dir, "src"), { recursive: true });70    writeFileSync(join(dir, "src", "auth.ts"), TS_SAMPLE);71    writeFileSync(72      join(dir, "src", "caller.ts"),73      `import { handleAuth } from "./auth.js";\nexport function guard(): void {\n  void handleAuth("t");\n}\n`,74    );75    return dir;76  }7778  it("answers symbol queries with wildcards, kind, and scope filters", async () => {79    const service = new RepoGraphService({ workspace: new LocalWorkspace(makeRepo()) });80    const exact = await service.querySymbols("handleAuth");81    expect(exact).toHaveLength(1);82    expect(exact[0]?.file).toBe("src/auth.ts");83    const wildcard = await service.querySymbols("*Controller", "class");84    expect(wildcard.map((hit) => hit.symbol)).toEqual(["AuthController"]);85    const shorthand = await service.querySymbols("class:*Controller");86    expect(shorthand).toHaveLength(1);87    const scoped = await service.querySymbols("handleAuth", undefined, "docs/**");88    expect(scoped).toHaveLength(0);89  });9091  it("finds importers and callers of a symbol", async () => {92    const service = new RepoGraphService({ workspace: new LocalWorkspace(makeRepo()) });93    const importers = await service.queryRefs("handleAuth", "importers");94    expect(importers.some((hit) => hit.file === "src/caller.ts")).toBe(true);95    const callers = await service.queryRefs("handleAuth", "callers");96    expect(callers.some((hit) => hit.file === "src/caller.ts" && hit.context.includes("handleAuth"))).toBe(true);97  });9899  it("produces a token-cheap skeleton with line anchors", async () => {100    const service = new RepoGraphService({ workspace: new LocalWorkspace(makeRepo()) });101    const skeleton = await service.skeleton("src/auth.ts");102    expect(skeleton).not.toBeNull();103    expect(skeleton).toContain("export async function handleAuth");104    expect(skeleton).toContain("re-read the file if you need bodies");105    expect((skeleton as string).length).toBeLessThan(TS_SAMPLE.length * 2);106  });107});108