import { execFileSync } from "node:child_process"; import fs from "node:fs/promises"; import path from "node:path"; import { listRuntimeSourceFiles } from "./repo-scan.js"; export type RuntimeSourceGuardrailFile = { relativePath: string; source: string; }; const DEFAULT_GUARDRAIL_SKIP_PATTERNS = [ /\.test\.tsx?$/, /\.test-helpers\.tsx?$/, /\.test-utils\.tsx?$/, /\.test-harness\.tsx?$/, /\.suite\.tsx?$/, /\.e2e\.tsx?$/, /\.d\.ts$/, /[\\/](?:__tests__|tests|test-utils)[\\/]/, /[\\/][^\\/]*test-helpers(?:\.[^\\/]+)?\.ts$/, /[\\/][^\\/]*test-utils(?:\.[^\\/]+)?\.ts$/, /[\\/][^\\/]*test-harness(?:\.[^\\/]+)?\.ts$/, ]; const runtimeSourceGuardrailCache = new Map>(); const trackedRuntimeSourceListCache = new Map(); const FILE_READ_CONCURRENCY = 24; export function shouldSkipGuardrailRuntimeSource(relativePath: string): boolean { return DEFAULT_GUARDRAIL_SKIP_PATTERNS.some((pattern) => pattern.test(relativePath)); } async function readRuntimeSourceFiles( repoRoot: string, absolutePaths: string[], ): Promise { const output: Array = Array.from({ length: absolutePaths.length, }); let nextIndex = 0; const worker = async () => { for (;;) { const index = nextIndex; nextIndex += 1; if (index >= absolutePaths.length) { return; } const absolutePath = absolutePaths[index]; if (!absolutePath) { continue; } const source = await fs.readFile(absolutePath, "utf8"); output[index] = { relativePath: path.relative(repoRoot, absolutePath), source, }; } }; const workers = Array.from( { length: Math.min(FILE_READ_CONCURRENCY, Math.max(1, absolutePaths.length)) }, () => worker(), ); await Promise.all(workers); return output.filter((entry): entry is RuntimeSourceGuardrailFile => entry !== undefined); } function tryListTrackedRuntimeSourceFiles(repoRoot: string): string[] | null { const cached = trackedRuntimeSourceListCache.get(repoRoot); if (cached) { return cached.slice(); } try { const stdout = execFileSync("git", ["-C", repoRoot, "ls-files", "--", "src", "extensions"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], }); const files = stdout .split(/\r?\n/u) .filter(Boolean) .filter((relativePath) => relativePath.endsWith(".ts") || relativePath.endsWith(".tsx")) .filter((relativePath) => !shouldSkipGuardrailRuntimeSource(relativePath)) .map((relativePath) => path.join(repoRoot, relativePath)); trackedRuntimeSourceListCache.set(repoRoot, files); return files.slice(); } catch { return null; } } export async function loadRuntimeSourceFilesForGuardrails( repoRoot: string, ): Promise { let pending = runtimeSourceGuardrailCache.get(repoRoot); if (!pending) { pending = (async () => { const trackedFiles = tryListTrackedRuntimeSourceFiles(repoRoot); const sourceFiles = trackedFiles ?? ( await listRuntimeSourceFiles(repoRoot, { roots: ["src", "extensions"], extensions: [".ts", ".tsx"], }) ).filter((absolutePath) => { const relativePath = path.relative(repoRoot, absolutePath); return !shouldSkipGuardrailRuntimeSource(relativePath); }); return await readRuntimeSourceFiles(repoRoot, sourceFiles); })(); runtimeSourceGuardrailCache.set(repoRoot, pending); } return await pending; }