#!/usr/bin/env node import { execFileSync } from "node:child_process"; import { existsSync, readFileSync, readdirSync, statSync } from "node:fs"; import { relative, resolve } from "node:path"; const root = resolve(import.meta.dirname, ".."); const testFilePattern = /(?:^|\/)(?:test|tests|__tests__)(?:\/|$)|\.(?:test|spec)\.[cm]?[jt]sx?$|(?:^|\/)vitest\.config\.[cm]?[jt]s$|^prime-agent-runtime\/test\/.*\.py$/; function git(args, allowFailure = false) { try { return execFileSync("git", args, { cwd: root, encoding: "utf8", maxBuffer: 64 * 1024 * 1024, stdio: ["ignore", "pipe", allowFailure ? "ignore" : "inherit"], }).trim(); } catch (error) { if (allowFailure) return ""; throw error; } } function resolveBase() { if (process.env.TEST_POLICY_BASE && git(["rev-parse", "--verify", process.env.TEST_POLICY_BASE], true)) { return process.env.TEST_POLICY_BASE; } if (process.env.GITHUB_BASE_REF) { const remote = `origin/${process.env.GITHUB_BASE_REF}`; if (git(["rev-parse", "--verify", remote], true)) return git(["merge-base", "HEAD", remote], true) || remote; } const originMain = git(["rev-parse", "--verify", "origin/main"], true); const head = git(["rev-parse", "HEAD"]); if (originMain && originMain !== head) return git(["merge-base", "HEAD", "origin/main"], true) || "origin/main"; return git(["rev-parse", "--verify", "HEAD^"], true) ? "HEAD^" : undefined; } function walkFiles(dir, out = []) { for (const entry of readdirSync(dir)) { if (["node_modules", ".git", "dist"].includes(entry)) continue; const path = resolve(dir, entry); const stat = statSync(path); if (stat.isDirectory()) walkFiles(path, out); else { const rel = relative(root, path).replaceAll("\\", "/"); if (testFilePattern.test(rel)) out.push(rel); } } return out; } function maskJsSyntax(content) { let quote; let escaped = false; let lineComment = false; let blockComment = false; let regex = false; let regexEscaped = false; let regexClass = false; let lastCodeChar = ""; const templateExpressions = []; let masked = ""; for (let index = 0; index < content.length; index += 1) { const char = content[index]; const next = content[index + 1]; if (lineComment) { if (char === "\n") { lineComment = false; masked += "\n"; } else masked += " "; continue; } if (blockComment) { if (char === "*" && next === "/") { masked += " "; blockComment = false; index += 1; } else masked += char === "\n" ? "\n" : " "; continue; } if (regex) { masked += char === "\n" ? "\n" : " "; if (regexEscaped) regexEscaped = false; else if (char === "\\") regexEscaped = true; else if (char === "[") regexClass = true; else if (char === "]") regexClass = false; else if (char === "/" && !regexClass) { regex = false; lastCodeChar = "/"; } continue; } if (quote) { if (quote === "`" && !escaped && char === "$" && next === "{") { masked += " "; templateExpressions.push(1); lastCodeChar = ""; quote = undefined; index += 1; continue; } masked += char === "\n" ? "\n" : " "; if (escaped) escaped = false; else if (char === "\\") escaped = true; else if (char === quote) quote = undefined; continue; } if (templateExpressions.length > 0) { if (char === "{") templateExpressions[templateExpressions.length - 1] += 1; else if (char === "}") { templateExpressions[templateExpressions.length - 1] -= 1; if (templateExpressions[templateExpressions.length - 1] === 0) { templateExpressions.pop(); lastCodeChar = ""; quote = "`"; masked += " "; continue; } } } if (char === "/" && next === "/") { masked += " "; lineComment = true; index += 1; } else if (char === "/" && next === "*") { masked += " "; blockComment = true; index += 1; } else if ( char === "/" && !/(?:\+\+|--)\s*$/.test(masked) && (lastCodeChar === "" || /[({[=,:;!?&|>~+*%^\-]/.test(lastCodeChar) || /\b(?:return|throw|case|delete|void|typeof|yield|await|in|instanceof)\s*$/.test(masked.slice(masked.lastIndexOf("\n") + 1))) ) { masked += " "; regex = true; regexEscaped = false; regexClass = false; } else if (char === '"' || char === "'" || char === "`") { masked += " "; quote = char; lastCodeChar = "string"; } else { masked += char; if (!/\s/.test(char)) lastCodeChar = char; } } return masked; } function maskPythonSyntax(content) { let quote; let escaped = false; let fString = false; let comment = false; const interpolations = []; let masked = ""; for (let index = 0; index < content.length; index += 1) { const char = content[index]; const next = content[index + 1]; if (comment) { if (char === "\n") { comment = false; masked += "\n"; } else masked += " "; continue; } if (quote) { if (fString && !escaped && char === "{" && next === "{") { masked += " "; index += 1; continue; } if (fString && !escaped && char === "{") { masked += " "; interpolations.push({ depth: 1, quote }); quote = undefined; fString = false; continue; } if (!escaped && content.startsWith(quote, index)) { masked += " ".repeat(quote.length); index += quote.length - 1; quote = undefined; fString = false; continue; } masked += char === "\n" ? "\n" : " "; if (escaped) escaped = false; else if (char === "\\") escaped = true; continue; } if (interpolations.length > 0) { if (char === "{") interpolations[interpolations.length - 1].depth += 1; else if (char === "}") { interpolations[interpolations.length - 1].depth -= 1; if (interpolations[interpolations.length - 1].depth === 0) { const interpolation = interpolations.pop(); quote = interpolation.quote; fString = true; masked += " "; continue; } } } if (char === "#") { masked += " "; comment = true; continue; } if (char === '"' || char === "'") { quote = content.startsWith(char.repeat(3), index) ? char.repeat(3) : char; let prefix = index - 1; while (prefix >= 0 && /[A-Za-z]/.test(content[prefix])) prefix -= 1; fString = /^(?:f|fr|rf)$/i.test(content.slice(prefix + 1, index)); masked += " ".repeat(quote.length); index += quote.length - 1; continue; } masked += char; } return masked; } function directObjectProperties(argument) { const names = new Set(); const numeric = new Set(); if (!argument.trimStart().startsWith("{")) return { names, numeric }; const record = (name, separator) => { names.add(name); if (argument[separator] !== ":") return; let value = separator + 1; while (/\s/.test(argument[value] ?? "")) value += 1; if (/^[1-9][0-9_]*/.test(argument.slice(value))) numeric.add(name); }; let depth = 0; let cursor = 0; let previous = ""; while (cursor < argument.length) { const char = argument[cursor]; const next = argument[cursor + 1]; if (char === "/" && next === "/") { cursor = argument.indexOf("\n", cursor + 2); if (cursor < 0) break; continue; } if (char === "/" && next === "*") { const closing = argument.indexOf("*/", cursor + 2); cursor = closing < 0 ? argument.length : closing + 2; continue; } if (char === '"' || char === "'" || char === "`") { const quote = char; let name = ""; let escaped = false; cursor += 1; while (cursor < argument.length) { const quoted = argument[cursor]; if (escaped) escaped = false; else if (quoted === "\\") escaped = true; else if (quoted === quote) break; else name += quoted; cursor += 1; } let lookahead = cursor + 1; while (/\s/.test(argument[lookahead] ?? "")) lookahead += 1; if (depth === 1 && (previous === "{" || previous === ",") && argument[lookahead] === ":") record(name, lookahead); previous = "string"; cursor += 1; continue; } if (char === "{") depth += 1; else if (char === "}") depth -= 1; else if (depth === 1 && /[A-Za-z_$]/.test(char) && (previous === "{" || previous === ",")) { const name = argument.slice(cursor).match(/^[A-Za-z_$][\w$]*/)?.[0]; if (name) { let lookahead = cursor + name.length; while (/\s/.test(argument[lookahead] ?? "")) lookahead += 1; if ([":", ",", "}"].includes(argument[lookahead])) record(name, lookahead); cursor += name.length; previous = "identifier"; continue; } } if (!/\s/.test(char)) previous = char; cursor += 1; } return { names, numeric }; } function changedTestFiles(base) { if (!base) return [...walkFiles(resolve(root, "packages")), ...walkFiles(resolve(root, "prime-agent-runtime", "test"))]; const roots = ["packages", "prime-agent-runtime/test", "scripts"]; const tracked = git(["diff", "--name-only", "--diff-filter=ACMR", base, "--", ...roots]); const untracked = git(["ls-files", "--others", "--exclude-standard", "--", ...roots], true); return [...new Set(`${tracked}\n${untracked}`.split("\n"))].filter( (path) => path && testFilePattern.test(path) && existsSync(resolve(root, path)), ); } function scan(content, path = "") { const lines = content.split("\n"); const maskedContent = path.endsWith(".py") ? maskPythonSyntax(content) : maskJsSyntax(content); const maskedLines = maskedContent.split("\n"); const violations = []; const isVitestConfig = /(?:^|\/)vitest\.config\.[cm]?[jt]s$/.test(path); let title = ""; const add = (category, line, detail, explicitTitle = title) => { const previous = lines[line - 2]?.trim() ?? ""; const suppression = previous.match(/^(?:\/\/|#) test-policy: allow ([a-z-]+) -- (.+)$/); if (suppression?.[1] === category && suppression[2].trim().length >= 12) return; violations.push({ category, detail, identity: `${category}\0${explicitTitle}\0${detail}`, line, title: explicitTitle }); }; for (let index = 0; index < lines.length; index += 1) { const line = lines[index]; const maskedLine = maskedLines[index]; const matchOutsideSyntax = (pattern) => { const flags = pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`; for (const match of line.matchAll(new RegExp(pattern.source, flags))) { if (/\S/.test(maskedLine.slice(match.index ?? 0, (match.index ?? 0) + match[0].length))) return match; } return undefined; }; const titleMatch = line.match(/(?= 0) header = tagged.slice(callStart, callStart + 10_000); break; } } const maskedHeader = maskJsSyntax(header); const opening = maskedHeader.indexOf("("); let parentheses = 0; let braces = 0; let brackets = 0; let lastArgumentComma = opening; let callbackIndex = -1; let callEnd = maskedHeader.length; for (let cursor = opening; cursor >= 0 && cursor < maskedHeader.length; cursor += 1) { const char = maskedHeader[cursor]; if (char === "(") parentheses += 1; else if (char === ")") { parentheses -= 1; if (parentheses === 0) { callEnd = cursor; break; } } else if (char === "{") braces += 1; else if (char === "}") braces -= 1; else if (char === "[") brackets += 1; else if (char === "]") brackets -= 1; else if (parentheses === 1 && braces === 0 && brackets === 0) { if (char === ",") lastArgumentComma = cursor; const functionToken = maskedHeader.slice(cursor).match(/^function\b/); if (maskedHeader.startsWith("=>", cursor) || functionToken) { callbackIndex = lastArgumentComma + 1; break; } } } const optionsEnd = callbackIndex >= 0 ? callbackIndex : callEnd; const optionsRegion = header.slice(0, optionsEnd); const argumentsBeforeCallback = []; let argumentStart = opening + 1; parentheses = 1; braces = 0; brackets = 0; for (let cursor = opening + 1; cursor <= optionsEnd; cursor += 1) { const char = maskedHeader[cursor]; if (char === "(") parentheses += 1; else if (char === ")") parentheses -= 1; else if (char === "{") braces += 1; else if (char === "}") braces -= 1; else if (char === "[") brackets += 1; else if (char === "]") brackets -= 1; if ((char === "," && parentheses === 1 && braces === 0 && brackets === 0) || cursor === optionsEnd) { argumentsBeforeCallback.push(header.slice(argumentStart, cursor)); argumentStart = cursor + 1; } } const optionNames = new Set(); for (const argument of argumentsBeforeCallback) { for (const name of directObjectProperties(argument).names) optionNames.add(name); } const hasOption = (name) => optionNames.has(name); const ownTitle = optionsRegion.match(/["'`]([^"'`]+)["'`]/)?.[1] ?? title; if (hasOption("retry")) add("test-retry", index + 1, "retry option", ownTitle); if (hasOption("timeout")) add("explicit-test-timeout", index + 1, "per-test timeout option", ownTitle); const disabledOption = ["skip", "todo", "only", "fails"].find((name) => hasOption(name)); if (disabledOption) add("conditional-or-disabled-test", index + 1, `${disabledOption} option`, ownTitle); if (/,\s*\d[\d_]*\s*\)\s*;?\s*$/.test(line)) add("explicit-test-timeout", index + 1, "numeric timeout argument", ownTitle); if (!/\)\s*;?\s*$/.test(line)) for (let cursor = index; cursor < lines.length; cursor += 1) { const candidate = lines[cursor]; const candidateTail = candidate.startsWith(indent) ? candidate.slice(indent.length) : undefined; if (candidateTail && /^},\s*\d[\d_]*\s*\)\s*;?\s*$/.test(candidateTail)) { add("explicit-test-timeout", cursor + 1, "numeric timeout argument", ownTitle); break; } const next = lines[cursor + 1] ?? ""; const nextTail = next.startsWith(indent) ? next.slice(indent.length) : undefined; if (candidateTail && /^\s*\d[\d_]*\s*,?\s*$/.test(candidateTail) && next === `${indent});`) { add("explicit-test-timeout", cursor + 1, "multiline numeric timeout argument", ownTitle); break; } if (candidate === `${indent}},` && nextTail && /^\d[\d_]*\s*,?\s*$/.test(nextTail) && (lines[cursor + 2] ?? "").trim() === ");") { add("explicit-test-timeout", cursor + 1, "multiline numeric timeout argument", ownTitle); break; } if (cursor > index && (candidate === `${indent}});` || candidate === `${indent});`)) break; } } if (!isVitestConfig && /\b(?:testTimeout|hookTimeout)\s*:/.test(maskedLines[index])) { add("explicit-test-timeout", index + 1, "timeout option", ""); } if (matchOutsideSyntax(/\b(?:expect\.poll|vi\.waitFor|waitForTimeout)\s*\(/)) { add("wall-clock-poll", index + 1, line.match(/(?:expect\.poll|vi\.waitFor|waitForTimeout)/)?.[0] ?? "poll"); } const sleepCall = matchOutsideSyntax(/\b(?:sleep|delay)\s*\(/); if ( sleepCall && !/(?:\bdef|\bfunction)\s*$/.test(maskedLine.slice(0, sleepCall.index ?? 0)) && !/\b(?:sleep|delay)\s*[:=]/.test(maskedLine) ) { add("wall-clock-sleep", index + 1, "sleep/delay call"); } if (matchOutsideSyntax(/\b(?:setTimeout|setInterval)\s*\(/)) add("wall-clock-timer", index + 1, "setTimeout/setInterval"); if (matchOutsideSyntax(/\bAtomics\.wait\s*\(/)) add("wall-clock-timer", index + 1, "Atomics.wait"); const controlWindow = maskedLines.slice(index, index + 4).join(" "); if ( /\bif\s*\([^)]*(?:process\.env|apiKey|credential|token|process\.platform|os\.(?:environ|getenv)|sys\.platform)/i.test(maskedLine) && /\b(?:return|continue)\b/.test(controlWindow) ) { add("environment-gated-path", index + 1, "conditional early exit"); } if (matchOutsideSyntax(/\b[A-Za-z_$][\w$.[\]]*\s*&&\s*expect\s*\(/)) { add("optional-assertion", index + 1, "short-circuited assertion"); } if (matchOutsideSyntax(/\bexpect\s*\(\s*(?:true|false|[-+]?\d+(?:\.\d+)?|["'][^"']*["'])\s*\)/)) { add("vacuous-assertion", index + 1, "literal expect"); } } for (const match of maskedContent.matchAll(/\.(?:listen|bind)\s*\(/g)) { let argument = (match.index ?? 0) + match[0].length; while (/\s/.test(maskedContent[argument] ?? "")) argument += 1; let fixed = /^[1-9][0-9_]*/.test(maskedContent.slice(argument)); if (!fixed && maskedContent[argument] === "{") { let depth = 0; let closing = -1; for (let cursor = argument; cursor < maskedContent.length; cursor += 1) { if (maskedContent[cursor] === "{") depth += 1; else if (maskedContent[cursor] === "}" && --depth === 0) { closing = cursor; break; } } if (closing >= 0) fixed = directObjectProperties(content.slice(argument, closing + 1)).numeric.has("port"); } if (fixed) { const line = maskedContent.slice(0, match.index ?? 0).split("\n").length; add("fixed-resource", line, "fixed bind/listen port", ""); } } if (isVitestConfig) { const findClosingBrace = (opening) => { let depth = 0; for (let cursor = opening; cursor >= 0 && cursor < maskedContent.length; cursor += 1) { if (maskedContent[cursor] === "{") depth += 1; else if (maskedContent[cursor] === "}" && --depth === 0) return cursor; } return -1; }; const directSurface = (opening, closing) => { let depth = 0; let surface = " ".repeat(content.length); const chars = surface.split(""); for (let cursor = opening; cursor <= closing; cursor += 1) { const depthBefore = depth; if (maskedContent[cursor] === "{") depth += 1; if (depthBefore <= 1) chars[cursor] = content[cursor]; if (maskedContent[cursor] === "}") depth -= 1; } return chars.join(""); }; const defineConfig = maskedContent.search(/\bdefineConfig\s*\(/); const exportDefault = maskedContent.search(/\bexport\s+default\b/); const rootStart = defineConfig >= 0 ? defineConfig : exportDefault; const rootOpening = rootStart >= 0 ? maskedContent.indexOf("{", rootStart) : -1; const rootClosing = findClosingBrace(rootOpening); if (rootOpening >= 0 && rootClosing >= 0) { const rootSurface = directSurface(rootOpening, rootClosing); const testProperty = rootSurface.slice(rootOpening, rootClosing + 1).match(/(?:\btest\b|["']test["'])\s*:\s*\{/); if (testProperty?.index !== undefined) { const testPropertyStart = rootOpening + testProperty.index; const testOpening = content.indexOf("{", testPropertyStart); const testClosing = findClosingBrace(testOpening); const configNames = directObjectProperties(content.slice(testOpening, testClosing + 1)).names; const line = content.slice(0, testOpening).split("\n").length; if (configNames.has("retry")) add("test-retry", line, "config retry option", ""); if (configNames.has("testTimeout") || configNames.has("hookTimeout")) { add("explicit-test-timeout", line, "config timeout option", ""); } } } } return violations; } function counts(violations) { const result = new Map(); for (const violation of violations) result.set(violation.identity, (result.get(violation.identity) ?? 0) + 1); return result; } function changedLineStats(base) { if (!base) return undefined; const stats = { sourceAdded: 0, testAdded: 0, testDeleted: 0 }; const tracked = new Set(); const isSource = (path) => ((!path.includes("/") && /\.(?:[cm]?[jt]sx?|py|sh)$/.test(path)) || /(?:^|\/)(?:src|scripts)\//.test(path)) && !testFilePattern.test(path); const meaningfulSourceFlags = (lines, path) => { const normalized = path.endsWith(".sh") ? lines : (path.endsWith(".py") ? maskPythonSyntax(lines.join("\n")) : maskJsSyntax(lines.join("\n"))).split("\n"); return normalized .map((line) => line.trim()) .map((line) => line.length > 0 && !/^#/.test(line) && !/^[{}()[\],;]+$/.test(line)); }; const meaningfulSourceCount = (lines, path) => meaningfulSourceFlags(lines, path).filter(Boolean).length; const record = (path, added, deleted) => { tracked.add(path); if (testFilePattern.test(path)) { stats.testAdded += added; stats.testDeleted += deleted; } }; const roots = ["."]; for (const row of git(["diff", "--numstat", "--no-renames", base, "--", ...roots]).split("\n")) { if (!row) continue; const [added, deleted, path] = row.split("\t"); if (/^\d+$/.test(added) && /^\d+$/.test(deleted)) record(path, Number(added), Number(deleted)); } let sourcePath; let addedSourceLines = []; let newLine = 0; const flushSource = () => { if (sourcePath && existsSync(resolve(root, sourcePath))) { const flags = meaningfulSourceFlags(readFileSync(resolve(root, sourcePath), "utf8").split("\n"), sourcePath); for (const line of addedSourceLines) if (flags[line - 1]) stats.sourceAdded += 1; } addedSourceLines = []; }; for (const line of git(["diff", "--unified=0", "--no-renames", base, "--", ...roots]).split("\n")) { if (line.startsWith("+++ ")) { flushSource(); const path = line.slice(4).replace(/^b\//, ""); sourcePath = isSource(path) ? path : undefined; continue; } const hunk = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/); if (hunk) { newLine = Number(hunk[1]); continue; } if (!sourcePath || !line || line.startsWith("diff --git") || line.startsWith("--- ")) continue; if (line.startsWith("+")) { addedSourceLines.push(newLine); newLine += 1; } else if (!line.startsWith("-") && !line.startsWith("\\")) newLine += 1; } flushSource(); const untracked = git(["ls-files", "--others", "--exclude-standard", "--", ...roots], true); for (const path of untracked.split("\n")) { if (!path || tracked.has(path) || !existsSync(resolve(root, path))) continue; const content = readFileSync(resolve(root, path), "utf8"); const added = content.length === 0 ? 0 : content.split("\n").length - (content.endsWith("\n") ? 1 : 0); record(path, added, 0); if (isSource(path)) stats.sourceAdded += meaningfulSourceCount(content.split("\n"), path); } return stats; } const base = resolveBase(); const failures = []; const lineStats = changedLineStats(base); if (lineStats && lineStats.testAdded - lineStats.testDeleted > lineStats.sourceAdded) { failures.push({ path: "", line: 0, category: "excess-test-lines", title: "changed test LOC", detail: `net test additions ${lineStats.testAdded - lineStats.testDeleted} exceed source additions ${lineStats.sourceAdded}`, }); } for (const path of changedTestFiles(base)) { const current = scan(readFileSync(resolve(root, path), "utf8"), path); const oldContent = base ? git(["show", `${base}:${path}`], true) : ""; const allowed = counts(oldContent ? scan(oldContent, path) : []); const seen = new Map(); for (const violation of current) { const count = (seen.get(violation.identity) ?? 0) + 1; seen.set(violation.identity, count); if (count > (allowed.get(violation.identity) ?? 0)) failures.push({ path, ...violation }); } } if (failures.length > 0) { console.error("New test-policy violations:\n"); for (const failure of failures) console.error(`${failure.path}:${failure.line} [${failure.category}] ${failure.title}: ${failure.detail}`); console.error("\nUse a deterministic signal, deferred promise, fake timer, or unconditional local fixture instead."); process.exit(1); } console.log(`Test policy check passed${base ? ` against ${base}` : ""}.`);