#!/usr/bin/env bun // verify-lsp.ts [--timeout=ms]: perform a real LSP diagnostics roundtrip // for through the bundled LSP engine and report ok/fail with error text. // The engine source is located by walking up from this script and the cwd, so // run it inside the HolyCodex repository (where packages/lsp-core/src exists). import { existsSync, statSync } from "node:fs"; import { dirname, isAbsolute, join, resolve } from "node:path"; import process from "node:process"; import { fileURLToPath, pathToFileURL } from "node:url"; import { z } from "zod"; const ENGINE_TOOLS = "packages/lsp-core/src/tools.ts"; const ENGINE_CONTEXT = "packages/lsp-core/src/request-context.ts"; const ENGINE_MANAGER = "packages/lsp-core/src/lsp/manager.ts"; const DEFAULT_TIMEOUT_MS = 60_000; interface ToolExecutionResult { readonly content: ReadonlyArray<{ readonly type: "text"; readonly text: string }>; readonly isError?: boolean; readonly details?: unknown; } interface DiagnosticsDetails { readonly mode: "file" | "directory"; readonly totalDiagnostics: number; readonly error?: string; readonly errorKind?: "missing_dependency" | "no_files" | "invalid_path"; } const DiagnosticsDetailsSchema = z.looseObject({ mode: z.enum(["file", "directory"]), totalDiagnostics: z.number().int().nonnegative(), error: z.string().optional(), errorKind: z.enum(["missing_dependency", "no_files", "invalid_path"]).optional(), }); interface ToolsModule { readonly executeLspDiagnostics: ( params: Record, signal?: AbortSignal, ) => Promise; } interface ContextModule { readonly runWithRequestContext: ( context: { cwd?: string; env?: Record }, fn: () => T, ) => T; } interface ManagerModule { readonly disposeDefaultLspManager: () => Promise; } function findUp(relativeTarget: string): string | null { const starts = [dirname(fileURLToPath(import.meta.url)), process.cwd()]; for (const start of starts) { let current = start; while (true) { const candidate = join(current, relativeTarget); if (existsSync(candidate)) return candidate; const parent = dirname(current); if (parent === current) break; current = parent; } } return null; } function buildEnv(): Record { const env: Record = {}; for (const [key, value] of Object.entries(process.env)) { if (value !== undefined) env[key] = value; } return env; } function isDiagnosticsDetails(value: unknown): value is DiagnosticsDetails { return DiagnosticsDetailsSchema.safeParse(value).success; } async function loadModule(relativeTarget: string): Promise { const path = findUp(relativeTarget); if (path === null) { throw new EngineNotFoundError(relativeTarget); } return (await import(pathToFileURL(path).href)) as T; } class EngineNotFoundError extends Error { constructor(public readonly target: string) { super( `LSP engine not found (looked for ${target}). Run verify-lsp.ts inside the HolyCodex repository.`, ); this.name = "EngineNotFoundError"; } } function parseTimeout(args: readonly string[]): number { const flag = args.find((arg) => arg.startsWith("--timeout=")); if (flag === undefined) return DEFAULT_TIMEOUT_MS; const parsed = Number.parseInt(flag.slice("--timeout=".length), 10); return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_TIMEOUT_MS; } async function run(filePath: string, timeoutMs: number): Promise { const tools = await loadModule(ENGINE_TOOLS); const context = await loadModule(ENGINE_CONTEXT); const manager = await loadModule(ENGINE_MANAGER); const absolute = isAbsolute(filePath) ? filePath : resolve(process.cwd(), filePath); try { const signal = AbortSignal.timeout(timeoutMs); const result = await context.runWithRequestContext( { cwd: process.cwd(), env: buildEnv() }, () => tools.executeLspDiagnostics({ filePath: absolute, severity: "all" }, signal), ); const details = isDiagnosticsDetails(result.details) ? result.details : null; const text = result.content.map((part) => part.text).join("\n"); if (details?.errorKind === "missing_dependency") { process.stdout.write(`FAIL ${absolute}: language server not installed\n${text}\n`); return 1; } if ( result.isError === true || details?.errorKind === "invalid_path" || details?.errorKind === "no_files" ) { process.stdout.write(`FAIL ${absolute}: ${details?.error ?? text}\n`); return 1; } const count = details?.totalDiagnostics ?? 0; process.stdout.write( `OK ${absolute}: LSP roundtrip succeeded (${count} diagnostic(s))\n${text}\n`, ); return 0; } finally { await manager.disposeDefaultLspManager(); } } async function main(): Promise { const args = process.argv.slice(2); const filePath = args.find((arg) => !arg.startsWith("--")); if (filePath === undefined) { process.stderr.write("Usage: bun verify-lsp.ts [--timeout=ms]\n"); process.exit(2); } if (!existsSync(filePath) || !statSync(filePath).isFile()) { process.stderr.write(`verify-lsp: not a file: ${filePath}\n`); process.exit(2); } try { const code = await run(filePath, parseTimeout(args)); process.exit(code); } catch (error) { if (error instanceof EngineNotFoundError) { process.stderr.write(`SKIP: ${error.message}\n`); process.exit(3); } process.stderr.write( `FAIL ${filePath}: ${error instanceof Error ? error.message : String(error)}\n`, ); process.exit(1); } } await main();