paperclip/packages/paperclip-runner/scripts/check-package-boundaries.mjs

60 lines
2.6 KiB
JavaScript

import { readFile } from "node:fs/promises";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const evalKernelRoot = resolve(packageRoot, "../paperclip-eval-kernel");
const runnerManifest = JSON.parse(await readFile(resolve(packageRoot, "package.json"), "utf8"));
const evalKernelManifest = JSON.parse(await readFile(resolve(evalKernelRoot, "package.json"), "utf8"));
const runtimeIndex = await readFile(resolve(packageRoot, "src/index.ts"), "utf8");
const violations = [];
if (runnerManifest.exports?.["./testing"] === undefined) {
violations.push("runner package must declare the ./testing export");
}
if (runnerManifest.exports?.["./evals"] === undefined) {
violations.push("runner package must declare the ./evals integration export");
}
if (evalKernelManifest.private !== true) {
violations.push("generic eval kernel must remain a workspace-private development package");
}
for (const privatePath of ["mock-core", "conformance", "scenarios", "src/tools", "./tools/"]) {
if (runtimeIndex.includes(privatePath)) {
violations.push(`runtime root must not export test/eval path: ${privatePath}`);
}
}
const runtimeDependencyGroups = [
runnerManifest.dependencies ?? {},
runnerManifest.optionalDependencies ?? {},
runnerManifest.peerDependencies ?? {},
];
for (const dependency of runtimeDependencyGroups.flatMap((group) => Object.keys(group))) {
if (dependency.includes("eval")) {
violations.push(`runner runtime must not depend on eval package: ${dependency}`);
}
}
if (runnerManifest.dependencies?.ajv === undefined || runnerManifest.devDependencies?.ajv !== undefined) {
violations.push("ajv must be declared as a runtime dependency because the public dispatcher imports it");
}
for (const dependency of Object.keys({
...(evalKernelManifest.dependencies ?? {}),
...(evalKernelManifest.optionalDependencies ?? {}),
...(evalKernelManifest.peerDependencies ?? {}),
})) {
if (dependency === "@paperclipai/paperclip-runner") {
violations.push("generic eval kernel must use structural callbacks, not a runner runtime dependency");
}
}
if (evalKernelManifest.files?.some((entry) => /scenario|fixture|corpus/.test(entry))) {
violations.push("generic eval kernel package inventory must not include scenario content");
}
if (violations.length > 0) {
process.stderr.write(`Package boundary check failed:\n${violations.map((item) => `- ${item}`).join("\n")}\n`);
process.exitCode = 1;
} else {
process.stdout.write("Package boundary check passed: runtime, testing, and eval ownership is acyclic.\n");
}