scripts/bootstrap-review-actions.mjs
scripts/bootstrap-review-actions.mjsBrowse 8 files
1,174 tokens
4,608 bytes
Token encoding: o200k_base
Snapshot fac8604
← Back to SKILL.md
1#!/usr/bin/env node2 3import { realpathSync } from 'node:fs';4import { mkdir, readFile, writeFile } from 'node:fs/promises';5import { dirname, resolve } from 'node:path';6import { fileURLToPath } from 'node:url';7 8import { assertReviewStateV2 } from '../../review-fetch-phase/scripts/review-artifacts.mjs';9import { assertReviewActionsV1 } from './review-artifacts.mjs';10 11const EXIT_SUCCESS = 0;12const EXIT_OPERATIONAL = 1;13const EXIT_CLI = 2;14 15function parseCliArgs(argv) {16 const args = argv.slice(2);17 const result = { inPath: null, outPath: null, help: false };18 if (args.includes('--help')) {19 result.help = true;20 return result;21 }22 23 const knownFlags = new Set(['--in', '--out']);24 let index = 0;25 while (index < args.length) {26 const arg = args[index];27 if (!arg.startsWith('--') || !knownFlags.has(arg)) {28 throw { code: EXIT_CLI, message: `error: unknown flag "${arg}"` };29 }30 index += 1;31 if (index >= args.length) {32 throw { code: EXIT_CLI, message: `error: ${arg} requires a value` };33 }34 const value = args[index];35 if (arg === '--in') {36 result.inPath = value;37 } else if (arg === '--out') {38 result.outPath = value;39 }40 index += 1;41 }42 43 if (!result.inPath || !result.outPath) {44 throw { code: EXIT_CLI, message: 'error: --in and --out are required' };45 }46 if (!result.inPath.endsWith('.json') || !result.outPath.endsWith('.json')) {47 throw { code: EXIT_CLI, message: 'error: --in and --out must be .json paths' };48 }49 return result;50}51 52function getHelpText() {53 return [54 'Usage:',55 ' bootstrap-review-actions.mjs --in <review-state.json> --out <review-actions.json>',56 '',57 'Purpose:',58 ' Generate deterministic triage scaffolding with one action per review target.',59 ].join('\n');60}61 62function formatCanonicalJson(value) {63 return `${JSON.stringify(value, null, 2)}\n`;64}65 66function toActionFromTarget(target, order) {67 return {68 actionId: `A${String(order).padStart(2, '0')}_${target.nodeId}`,69 target: {70 kind: target.kind,71 nodeId: target.nodeId,72 url: target.url ?? null,73 },74 source: {75 targetKey: target.targetKey,76 path: target.path ?? null,77 startLine: target.startLine ?? null,78 endLine: target.endLine ?? null,79 isOutdated: Boolean(target.isOutdated),80 isActionableCandidate: Boolean(target.isActionableCandidate),81 primaryCommentNodeId: target.primaryCommentNodeId ?? null,82 primaryCommentAuthorLogin: target.primaryCommentAuthorLogin ?? null,83 primaryCommentCreatedAt: target.primaryCommentCreatedAt ?? null,84 },85 decision: 'triage_pending',86 summary: null,87 rationale: null,88 targetFiles: target.path ? [target.path] : [],89 acceptance: null,90 status: 'pending',91 done: null,92 };93}94 95const IMPLEMENT_PHASE_SUPPORTED_TARGET_KINDS = new Set(['review_thread', 'pull_request_review']);96 97function buildReviewActions(reviewState, reviewStatePath) {98 const supportedTargets = reviewState.targets.filter((target) =>99 IMPLEMENT_PHASE_SUPPORTED_TARGET_KINDS.has(target.kind),100 );101 const actions = supportedTargets.map((target, index) => toActionFromTarget(target, index + 1));102 103 return {104 version: 2,105 pr: {106 url: reviewState.pr.url,107 nodeId: reviewState.pr.nodeId,108 },109 reviewState: {110 path: reviewStatePath,111 fetchedAt: reviewState.fetchedAt,112 version: reviewState.version,113 },114 actions,115 };116}117 118async function main() {119 const args = parseCliArgs(process.argv);120 if (args.help) {121 process.stdout.write(`${getHelpText()}\n`);122 process.exit(EXIT_SUCCESS);123 }124 125 const raw = await readFile(args.inPath, 'utf8');126 const reviewState = JSON.parse(raw);127 assertReviewStateV2(reviewState);128 129 const reviewActions = buildReviewActions(reviewState, args.inPath);130 assertReviewActionsV1(reviewActions);131 132 await mkdir(dirname(args.outPath), { recursive: true });133 await writeFile(args.outPath, formatCanonicalJson(reviewActions), 'utf8');134}135 136const isMain = (() => {137 try {138 const invokedScriptPath = process.argv[1] ? realpathSync(resolve(process.argv[1])) : null;139 const currentModulePath = realpathSync(fileURLToPath(import.meta.url));140 return invokedScriptPath !== null && invokedScriptPath === currentModulePath;141 } catch {142 return false;143 }144})();145 146if (isMain) {147 main().catch((error) => {148 const code = typeof error?.code === 'number' ? error.code : EXIT_OPERATIONAL;149 const message = error?.message ? String(error.message) : String(error);150 process.stderr.write(`${message}\n`);151 process.exit(code);152 });153}154