121 lines
4.0 KiB
JavaScript
121 lines
4.0 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
|
|
const { findProjectRoot } = require("../lib/project-root");
|
|
const { buildDecisionManifest } = require("../lib/pr-decision");
|
|
|
|
function parseArgs(argv) {
|
|
const args = {
|
|
preflight: null,
|
|
evidence: null,
|
|
output: null,
|
|
semanticReviewState: "unknown",
|
|
writeStepSummary: false,
|
|
writeGithubOutput: false,
|
|
json: false,
|
|
};
|
|
for (let index = 0; index < argv.length; index += 1) {
|
|
const arg = argv[index];
|
|
if (["--preflight", "--evidence", "--output", "--semantic-review-state"].includes(arg)) {
|
|
const value = argv[index + 1];
|
|
if (!value) throw new Error(`${arg} requires a value`);
|
|
const key = {
|
|
"--preflight": "preflight",
|
|
"--evidence": "evidence",
|
|
"--output": "output",
|
|
"--semantic-review-state": "semanticReviewState",
|
|
}[arg];
|
|
args[key] = value;
|
|
index += 1;
|
|
} else if (arg === "--write-step-summary") {
|
|
args.writeStepSummary = true;
|
|
} else if (arg === "--write-github-output") {
|
|
args.writeGithubOutput = true;
|
|
} else if (arg === "--json") {
|
|
args.json = true;
|
|
}
|
|
}
|
|
if (!args.preflight || !args.evidence) {
|
|
throw new Error("--preflight and --evidence are required");
|
|
}
|
|
return args;
|
|
}
|
|
|
|
function safePath(projectRoot, value) {
|
|
const target = path.resolve(projectRoot, value);
|
|
const relative = path.relative(projectRoot, target);
|
|
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
throw new Error(`Path escapes repository root: ${value}`);
|
|
}
|
|
return target;
|
|
}
|
|
|
|
function readJson(filePath) {
|
|
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
}
|
|
|
|
function appendSummary(manifest) {
|
|
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
if (!summaryPath) return;
|
|
const lines = [
|
|
"## PR Decision Manifest (shadow)",
|
|
"",
|
|
`- Route: \`${manifest.route}\``,
|
|
`- Deterministic gate: ${manifest.deterministic_gate.blocking ? "blocking" : "pass"}`,
|
|
`- Semantic review: \`${manifest.semantic_review.state}\``,
|
|
`- Changed skills: ${manifest.change.changed_skills.length}`,
|
|
`- Sensitive paths: ${manifest.change.sensitive_paths.length}`,
|
|
`- Reasons: ${manifest.reason_codes.length ? manifest.reason_codes.map((item) => `\`${item}\``).join(", ") : "none"}`,
|
|
"",
|
|
"> Advisory artifact produced by unprivileged pull_request code. Privileged actions must recompute from trusted main.",
|
|
];
|
|
fs.appendFileSync(summaryPath, `${lines.join("\n")}\n`, "utf8");
|
|
}
|
|
|
|
function appendGithubOutput(manifest) {
|
|
const outputPath = process.env.GITHUB_OUTPUT;
|
|
if (!outputPath) return;
|
|
const lines = [
|
|
`route=${manifest.route}`,
|
|
`blocking=${manifest.deterministic_gate.blocking}`,
|
|
`semantic_review_state=${manifest.semantic_review.state}`,
|
|
`changed_skills_count=${manifest.change.changed_skills.length}`,
|
|
`sensitive_paths_count=${manifest.change.sensitive_paths.length}`,
|
|
];
|
|
fs.appendFileSync(outputPath, `${lines.join("\n")}\n`, "utf8");
|
|
}
|
|
|
|
function main() {
|
|
const args = parseArgs(process.argv.slice(2));
|
|
const projectRoot = findProjectRoot(__dirname);
|
|
const preflight = readJson(safePath(projectRoot, args.preflight));
|
|
const evidence = readJson(safePath(projectRoot, args.evidence));
|
|
const manifest = buildDecisionManifest({
|
|
preflight,
|
|
evidence,
|
|
semanticReviewState: args.semanticReviewState,
|
|
});
|
|
const serialized = `${JSON.stringify(manifest, null, 2)}\n`;
|
|
if (args.output) {
|
|
const outputPath = safePath(projectRoot, args.output);
|
|
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
fs.writeFileSync(outputPath, serialized, { encoding: "utf8", mode: 0o600 });
|
|
}
|
|
if (args.writeStepSummary) appendSummary(manifest);
|
|
if (args.writeGithubOutput) appendGithubOutput(manifest);
|
|
if (args.json || !args.output) process.stdout.write(serialized);
|
|
}
|
|
|
|
if (require.main === module) {
|
|
try {
|
|
main();
|
|
} catch (error) {
|
|
console.error(`[pr-decision] ${error.message}`);
|
|
process.exit(2);
|
|
}
|
|
}
|
|
|
|
module.exports = { appendGithubOutput, appendSummary, parseArgs, safePath };
|