Files
pi-map/tests/llm-extract.test.ts
T
alex 2f198ea0d2 feat: remove heuristics, go LLM-only with mocks + restructure
BREAKING: Heuristic fallback removed — LLM client now required

Changes:
- Remove extractFileHeuristic, extractPackageHeuristic, all helpers
- extractFileLLM / extractPackageLLM now throw LLMError when client missing
- Add hybrid binary detection: extension blacklist + content sniffing
- Increase file size limit: 50KB → 500KB (text files only)
- Restructure src/ into subdirectories:
  - src/llm/ — all LLM clients, extract, batch, cache, error
  - src/ast/ — AST extraction
  - src/cli/ — CLI entry point
- Update all imports across codebase and tests
- Add tests/mock-llm.ts helper for deterministic mock clients
- Update all tests to use mock LLM clients (no heuristics dependency)
- All 52 tests passing (including 8 real LLM integration tests)
2026-06-10 17:34:37 +02:00

83 lines
2.6 KiB
TypeScript

import { describe, it, expect } from "vitest";
import { extractFileLLM } from "../src/llm/llm-extract.js";
import { writeFileSync, mkdtempSync } from "fs";
import { join } from "path";
import { tmpdir } from "os";
import type { LLMClient } from "../src/llm/llm-client.js";
function createMockClient(response: string): LLMClient {
return {
async complete() {
return response;
},
};
}
describe("llm-extract with mock client", () => {
it("uses LLM client when provided", async () => {
const dir = mkdtempSync(join(tmpdir(), "pi-map-"));
const file = join(dir, "test.ts");
writeFileSync(file, `export const foo = 1;`);
const mockClient = createMockClient(
"PURPOSE: Test file\nDEPS: none\nCONCEPTS: testing",
);
const result = await extractFileLLM(file, mockClient, tmpdir());
expect(result.purpose).toBe("Test file");
expect(result.deps).toEqual([]);
expect(result.concepts).toContain("testing");
});
it("throws without LLM client", async () => {
const dir = mkdtempSync(join(tmpdir(), "pi-map-"));
const file = join(dir, "test.ts");
writeFileSync(file, `export const foo = 1;`);
await expect(extractFileLLM(file)).rejects.toThrow("No LLM client configured");
});
it("skips large files", async () => {
const dir = mkdtempSync(join(tmpdir(), "pi-map-"));
const file = join(dir, "big.ts");
writeFileSync(file, "x".repeat(600 * 1024));
const mockClient = createMockClient(
"PURPOSE: Should not call\nDEPS: none\nCONCEPTS: none",
);
const result = await extractFileLLM(file, mockClient);
expect(result.purpose).toBe("Large file");
});
it("skips binary files", async () => {
const dir = mkdtempSync(join(tmpdir(), "pi-map-"));
const file = join(dir, "image.png");
// Write some binary-looking content with null bytes
const buf = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
writeFileSync(file, buf);
const mockClient = createMockClient(
"PURPOSE: Should not call\nDEPS: none\nCONCEPTS: none",
);
const result = await extractFileLLM(file, mockClient);
expect(result.purpose).toBe("Binary file");
});
it("parses response with deps and concepts", async () => {
const dir = mkdtempSync(join(tmpdir(), "pi-map-"));
const file = join(dir, "test.ts");
writeFileSync(file, `import { foo } from "bar";\nexport const x = 1;`);
const mockClient = createMockClient(
"PURPOSE: Config module\nDEPS: bar, baz\nCONCEPTS: constants, config",
);
const result = await extractFileLLM(file, mockClient, tmpdir());
expect(result.purpose).toBe("Config module");
expect(result.deps).toEqual(["bar", "baz"]);
expect(result.concepts).toEqual(["constants", "config"]);
});
});