import { describe, it, expect, beforeEach, afterEach } from "vitest"; import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from "fs"; import { join } from "path"; import { tmpdir } from "os"; import { execSync } from "child_process"; import { fileURLToPath } from "url"; const projectRoot = fileURLToPath(new URL("..", import.meta.url)); function runCli( args: string, cwd: string, ): { stdout: string; stderr: string; exitCode: number } { try { const stdout = execSync( `npx tsx ${join(projectRoot, "src/cli/cli.ts")} ${args}`, { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], }, ); return { stdout: stdout.trim(), stderr: "", exitCode: 0 }; } catch (err: any) { return { stdout: err.stdout?.toString().trim() || "", stderr: err.stderr?.toString().trim() || "", exitCode: err.status || 1, }; } } describe("cli context", () => { let dir: string; beforeEach(() => { dir = mkdtempSync(join(tmpdir(), "pi-map-cli-")); }); afterEach(() => { rmSync(dir, { recursive: true }); }); it("context command returns a bundle for a matching query", () => { mkdirSync(join(dir, "src")); mkdirSync(join(dir, "src", "auth")); writeFileSync( join(dir, "src", "auth", "tokens.ts"), `export function validateToken() {}\n`, ); writeFileSync( join(dir, "src", "auth", ".pi-map.md"), `# src/auth dir: src/auth index: src/auth/.pi-map.index.md ## role Authentication and token validation. ## files - tokens.ts | Token validation | exp: validateToken | dep: - ## arch Guard pattern. ## tags auth, token, validate ## symbols validateToken ## workflows - validate token files: tokens.ts ## dirty - `, ); writeFileSync( join(dir, "src", "auth", ".pi-map.index.md"), `# src/auth (index) dir: src/auth ## role Auth layer. ## parent index: ./.pi-map.index.md map: ./.pi-map.md ## children - ## files - tokens.ts ## links index: src/auth/.pi-map.index.md map: src/auth/.pi-map.md ## workflows - validate token ## dirty - `, ); const { stdout, exitCode } = runCli('context "token validation"', dir); expect(exitCode).toBe(0); expect(stdout).toContain("# Context bundle: token validation"); expect(stdout).toContain("## relevant indexes"); expect(stdout).toContain("src/auth/.pi-map.index.md"); expect(stdout).toContain("## relevant maps"); expect(stdout).toContain("src/auth/.pi-map.md"); expect(stdout).toContain("## likely files"); expect(stdout).toContain("src/auth/tokens.ts"); expect(stdout).toContain("## instructions"); }); it("context command omits symbols section when no symbols exist", () => { mkdirSync(join(dir, "src")); writeFileSync( join(dir, "src", ".pi-map.md"), `# src dir: src index: src/.pi-map.index.md ## role Core source. ## files - index.ts | Entry | exp: main | dep: - ## arch Entrypoint. ## tags - ## symbols - ## workflows - ## dirty - `, ); writeFileSync( join(dir, "src", ".pi-map.index.md"), `# src (index) dir: src ## role Core source. ## parent - ## children - ## files - index.ts ## links index: src/.pi-map.index.md map: src/.pi-map.md ## workflows - ## dirty - `, ); const { stdout, exitCode } = runCli('context "core source"', dir); expect(exitCode).toBe(0); expect(stdout).toContain("# Context bundle: core source"); expect(stdout).not.toContain("## relevant symbols"); }); it("context command returns no-results bundle when nothing matches", () => { mkdirSync(join(dir, "src")); writeFileSync( join(dir, "src", ".pi-map.md"), `# src dir: src index: src/.pi-map.index.md ## role Core source. ## files - index.ts | Entry | exp: main | dep: - ## arch Entrypoint. ## tags - ## symbols - ## workflows - ## dirty - `, ); writeFileSync( join(dir, "src", ".pi-map.index.md"), `# src (index) dir: src ## role Core source. ## parent - ## children - ## files - index.ts ## links index: src/.pi-map.index.md map: src/.pi-map.md ## workflows - ## dirty - `, ); const { stdout, exitCode } = runCli('context "zzzzzzzz"', dir); expect(exitCode).toBe(0); expect(stdout).toContain("# Context bundle: zzzzzzzz"); expect(stdout).toContain("No relevant directories found"); }); it("context command errors when query is missing", () => { const { stderr, exitCode } = runCli("context", dir); expect(exitCode).toBe(1); expect(stderr).toContain("Missing query"); }); });