import { afterEach, beforeEach, describe, expect, it } from "bun:test"; import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { createTools, type ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; import { resolveToCwd } from "@oh-my-pi/pi-coding-agent/tools/path-utils"; import { PendingActionStore } from "@oh-my-pi/pi-coding-agent/tools/pending-action"; function createTestSession(cwd: string, overrides: Partial = {}): ToolSession { return { cwd, hasUI: false, getSessionFile: () => null, getSessionSpawns: () => "*", settings: Settings.isolated(), ...overrides, }; } function getText(result: { content: Array<{ type: string; text?: string }> }): string { return result.content .filter(entry => entry.type === "text") .map(entry => entry.text ?? "") .join("\n"); } describe("tool path root alias", () => { let tempDir: string; beforeEach(async () => { tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "tool-root-alias-")); await Bun.write(path.join(tempDir, "search.txt"), "root-alias-needle\n"); await Bun.write( path.join(tempDir, "sample.ts"), "const rootAliasSymbol = 1;\nlegacyWrap(rootAliasSymbol, anotherValue);\n", ); }); afterEach(async () => { await fs.rm(tempDir, { recursive: true, force: true }); }); it("resolves a bare slash to the session cwd", () => { expect(resolveToCwd("/", tempDir)).toBe(tempDir); expect(resolveToCwd("///", tempDir)).toBe(tempDir); }); it("greps from cwd when path is slash", async () => { const tools = await createTools(createTestSession(tempDir)); const tool = tools.find(entry => entry.name === "grep"); expect(tool).toBeDefined(); if (!tool) throw new Error("Missing grep tool"); const result = await tool.execute("grep-root-alias", { pattern: "root-alias-needle", path: "/", }); const details = result.details as { scopePath?: string } | undefined; expect(getText(result)).toContain("search.txt"); expect(details?.scopePath).toBe("."); }); it("reads cwd when path is slash", async () => { const tools = await createTools(createTestSession(tempDir)); const tool = tools.find(entry => entry.name === "read"); expect(tool).toBeDefined(); if (!tool) throw new Error("Missing read tool"); const result = await tool.execute("read-root-alias", { path: "/", }); const text = getText(result); expect(text).toContain("search.txt"); expect(text).toContain("sample.ts"); }); it("finds from cwd when pattern is slash", async () => { const tools = await createTools(createTestSession(tempDir)); const tool = tools.find(entry => entry.name === "find"); expect(tool).toBeDefined(); if (!tool) throw new Error("Missing find tool"); const result = await tool.execute("find-root-alias", { pattern: "/", }); const details = result.details as { scopePath?: string } | undefined; const text = getText(result); expect(details?.scopePath).toBe("."); expect(text).toContain("search.txt"); expect(text).toContain("sample.ts"); }); it("ast_grep searches cwd when path is slash", async () => { const tools = await createTools(createTestSession(tempDir)); const tool = tools.find(entry => entry.name === "ast_grep"); expect(tool).toBeDefined(); if (!tool) throw new Error("Missing ast_grep tool"); const result = await tool.execute("ast-grep-root-alias", { pat: ["rootAliasSymbol"], sel: "identifier", lang: "typescript", path: "/", glob: "**/*.ts", }); const details = result.details as { scopePath?: string } | undefined; expect(getText(result)).toContain("sample.ts"); expect(details?.scopePath).toBe("."); }); it("ast_edit rewrites within cwd when path is slash", async () => { const pendingActionStore = new PendingActionStore(); const tools = await createTools(createTestSession(tempDir, { pendingActionStore })); const tool = tools.find(entry => entry.name === "ast_edit"); expect(tool).toBeDefined(); if (!tool) throw new Error("Missing ast_edit tool"); const preview = await tool.execute("ast-edit-root-alias", { ops: [{ pat: "legacyWrap($A, $B)", out: "modernWrap($A, $B)" }], lang: "typescript", path: "/", glob: "**/*.ts", }); const details = preview.details as { scopePath?: string; totalReplacements?: number } | undefined; expect(getText(preview)).toContain("sample.ts"); expect(details?.scopePath).toBe("."); expect(details?.totalReplacements).toBe(1); const pending = pendingActionStore.peek(); expect(pending).not.toBeNull(); if (!pending) throw new Error("Expected pending action"); await pending.apply("apply root alias rewrite"); expect(await Bun.file(path.join(tempDir, "sample.ts")).text()).toContain( "modernWrap(rootAliasSymbol, anotherValue)", ); }); });