import { describe, expect, it } from "vitest"; import { createCallHierarchyTool, createDefinitionTool, createHoverTool, createImplementationTool, createReferencesTool, createRenameTool, createSymbolsTool, createWorkspaceSymbolTool, } from "./queries.js"; type AnyTool = { execute: (...args: any[]) => Promise; promptGuidelines?: string[] }; function managerWith(result: unknown) { return { request: async () => result, getClients: async () => [] } as any; } function managerWithMethods(methods: Record) { return { request: async () => null, getClients: async () => [], ...methods } as any; } function fakeCtx() { return { cwd: "/tmp" } as any; } describe("prompt guidelines", () => { const cases: Array<[string, () => AnyTool]> = [ ["lsp_hover", () => createHoverTool(() => managerWith(null))], ["lsp_definition", () => createDefinitionTool(() => managerWith(null))], ["lsp_references", () => createReferencesTool(() => managerWith(null))], ["lsp_symbols", () => createSymbolsTool(() => managerWith(null))], ["lsp_rename", () => createRenameTool(() => managerWith(null))], ["lsp_implementation", () => createImplementationTool(() => managerWith(null))], ["lsp_workspace_symbol", () => createWorkspaceSymbolTool(() => managerWith(null))], ["lsp_call_hierarchy", () => createCallHierarchyTool(() => managerWith(null))], ]; it.each(cases)("%s defines promptGuidelines that name the tool", (_name, make) => { const tool = make(); expect(tool.promptGuidelines?.length ?? 0).toBeGreaterThan(0); for (const guideline of tool.promptGuidelines ?? []) { expect(guideline).toContain(_name); } }); it("rename guidelines teach that it previews and never writes", () => { const tool = createRenameTool(() => managerWith(null)); expect(tool.promptGuidelines?.some((g) => g.includes("never writes"))).toBe(true); }); }); describe("OpenCode parity tools", () => { it("implementation tool returns locations", async () => { const manager = managerWithMethods({ implementation: async () => [ { uri: "file:///tmp/impl.ts", range: { start: { line: 2, character: 0 }, end: { line: 2, character: 4 } } }, ], }); const tool = createImplementationTool(() => manager); const result = await tool.execute("c1", { path: "/tmp/a.ts", line: 0, character: 3 }, undefined, undefined, fakeCtx()); const text = (result.content?.[0] as { text?: string } | undefined)?.text ?? ""; expect(text).toContain("impl.ts"); }); it("workspace symbol tool passes the query", async () => { let called: any; const manager = managerWithMethods({ workspaceSymbol: async (query: string) => { called = query; return [{ name: "cannedFn", kind: 12 }]; }, }); const tool = createWorkspaceSymbolTool(() => manager); const result = await tool.execute("c1", { query: "canned" }, undefined, undefined, fakeCtx()); expect(called).toBe("canned"); const text = (result.content?.[0] as { text?: string } | undefined)?.text ?? ""; expect(text).toContain("cannedFn"); }); it("call hierarchy tool routes by direction", async () => { let direction: string | undefined; const manager = managerWithMethods({ prepareCallHierarchy: async () => [{ name: "prepared", kind: 12 }], incomingCalls: async () => { direction = "incomingCalls"; return [{ from: { name: "caller" } }]; }, outgoingCalls: async () => { direction = "outgoingCalls"; return [{ to: { name: "callee" } }]; }, }); const tool = createCallHierarchyTool(() => manager); const result = await tool.execute( "c1", { path: "/tmp/a.ts", line: 0, character: 0, direction: "incoming" }, undefined, undefined, fakeCtx(), ); expect(direction).toBe("incomingCalls"); const text = (result.content?.[0] as { text?: string } | undefined)?.text ?? ""; expect(text).toContain("caller"); }); }); describe("query tools", () => { it("definition tool returns locations from the manager", async () => { const tool = createDefinitionTool(() => managerWith([ { uri: "file:///tmp/def.ts", range: { start: { line: 1, character: 2 }, end: { line: 1, character: 5 } } }, ]), ); const result = await tool.execute( "c1", { path: "/tmp/a.ts", line: 3, character: 4 }, undefined, undefined, fakeCtx(), ); const text = (result.content?.[0] as { text?: string } | undefined)?.text ?? ""; expect(text).toContain("def.ts"); }); it("references tool passes includeDeclaration", async () => { let called: any; const tool = createReferencesTool(() => ({ request: async (file: string, method: string, params: any) => { called = { file, method, params }; return []; }, getClients: async () => [], }) as any, ); await tool.execute("c1", { path: "/tmp/a.ts", line: 0, character: 0 }, undefined, undefined, fakeCtx()); expect(called.method).toBe("textDocument/references"); expect(called.params.context.includeDeclaration).toBe(true); }); });