scripts/review-iterate.mjs
scripts/review-iterate.mjsBrowse 4 files
1,131 tokens
4,270 bytes
Token encoding: o200k_base
Snapshot fac8604
← Back to SKILL.md
1#!/usr/bin/env node2 3import { spawnSync } from 'node:child_process';4import { access, mkdir } from 'node:fs/promises';5import { dirname, resolve } from 'node:path';6import { fileURLToPath, pathToFileURL } from 'node:url';7 8const __filename = fileURLToPath(import.meta.url);9const __dirname = dirname(__filename);10const SKILL_DIR = resolve(__dirname, '..');11const SKILLS_ROOT = resolve(SKILL_DIR, '..');12 13const EXIT_SUCCESS = 0;14const EXIT_OPERATIONAL = 1;15const EXIT_CLI = 2;16const DEFAULT_REVIEWS_ROOT = 'wip/reviews';17 18function parseCliArgs(argv) {19 const args = argv.slice(2);20 const result = { prUrl: null, reviewsRoot: DEFAULT_REVIEWS_ROOT, help: false };21 if (args.includes('--help')) return { ...result, help: true };22 for (let i = 0; i < args.length; i += 2) {23 const flag = args[i];24 const value = args[i + 1];25 if ((flag !== '--pr' && flag !== '--reviews-root') || !value) {26 throw { code: EXIT_CLI, message: `error: invalid args near "${flag ?? ''}"` };27 }28 if (flag === '--pr') result.prUrl = value;29 if (flag === '--reviews-root') result.reviewsRoot = value;30 }31 if (!result.prUrl) throw { code: EXIT_CLI, message: 'error: --pr is required' };32 return result;33}34 35function parsePrUrl(url) {36 const match = url37 .trim()38 .match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)(?:\/)?(?:#.*)?$/i);39 if (!match) return null;40 return {41 owner: match[1],42 repo: match[2].replace(/\.git$/i, ''),43 number: Number.parseInt(match[3], 10),44 };45}46 47function deriveReviewDirectoryName(prUrl) {48 const parsed = parsePrUrl(prUrl);49 if (!parsed) throw new TypeError('error: invalid --pr value');50 return `${parsed.owner.toLowerCase()}_${parsed.repo.toLowerCase()}_pr-${parsed.number}`;51}52 53function runNodeScript(scriptPath, args) {54 const result = spawnSync(process.execPath, [scriptPath, ...args], { stdio: 'inherit' });55 if (result.status !== 0) throw new Error(`error: failed running ${scriptPath}`);56}57 58async function fileExists(path) {59 try {60 await access(path);61 return true;62 } catch {63 return false;64 }65}66 67async function main() {68 const options = parseCliArgs(process.argv);69 if (options.help) {70 process.stdout.write('Usage: review-iterate.mjs --pr <url> [--reviews-root <dir>] [--help]\n');71 process.exit(EXIT_SUCCESS);72 }73 74 if (!parsePrUrl(options.prUrl)) {75 throw { code: EXIT_CLI, message: 'error: --pr must be a GitHub pull request URL' };76 }77 78 const directoryName = deriveReviewDirectoryName(options.prUrl);79 const reviewDir = resolve(options.reviewsRoot, directoryName);80 await mkdir(reviewDir, { recursive: true });81 82 const reviewStateJsonPath = resolve(reviewDir, 'review-state.json');83 const reviewStateMdPath = resolve(reviewDir, 'review-state.md');84 const reviewSummaryPath = resolve(reviewDir, 'summary.txt');85 const reviewActionsJsonPath = resolve(reviewDir, 'review-actions.json');86 const reviewActionsMdPath = resolve(reviewDir, 'review-actions.md');87 await mkdir(dirname(reviewStateJsonPath), { recursive: true });88 89 const fetchPhase = resolve(SKILLS_ROOT, 'review-fetch-phase/scripts');90 const triagePhase = resolve(SKILLS_ROOT, 'review-triage-phase/scripts');91 92 runNodeScript(resolve(fetchPhase, 'fetch-review-state.mjs'), [93 '--pr',94 options.prUrl,95 '--out-json',96 reviewStateJsonPath,97 ]);98 runNodeScript(resolve(fetchPhase, 'render-review-state.mjs'), [99 '--in',100 reviewStateJsonPath,101 '--out',102 reviewStateMdPath,103 ]);104 runNodeScript(resolve(fetchPhase, 'summarize-review-state.mjs'), [105 '--in',106 reviewStateJsonPath,107 '--format',108 'text',109 '--out',110 reviewSummaryPath,111 ]);112 113 if (await fileExists(reviewActionsJsonPath)) {114 runNodeScript(resolve(triagePhase, 'render-review-actions.mjs'), [115 '--in',116 reviewActionsJsonPath,117 '--out',118 reviewActionsMdPath,119 ]);120 }121 122 process.stdout.write(`${reviewDir}\n`);123}124 125const isMain =126 Boolean(process.argv[1]) && pathToFileURL(resolve(process.argv[1])).href === import.meta.url;127if (isMain) {128 main().catch((error) => {129 const code = typeof error?.code === 'number' ? error.code : EXIT_OPERATIONAL;130 process.stderr.write(`${error?.message ? String(error.message) : String(error)}\n`);131 process.exit(code);132 });133}134