scripts/extract-review-targets.mjs
scripts/extract-review-targets.mjsBrowse 11 files
864 tokens
3,219 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, formatCanonicalJson } from './review-artifacts.mjs';9 10const EXIT_SUCCESS = 0;11const EXIT_OPERATIONAL = 1;12const EXIT_CLI = 2;13 14function parseCliArgs(argv) {15 const args = argv.slice(2);16 const result = { inPath: null, outPath: null, help: false };17 if (args.includes('--help')) {18 result.help = true;19 return result;20 }21 22 const knownFlags = new Set(['--in', '--out']);23 let index = 0;24 while (index < args.length) {25 const arg = args[index];26 if (!arg.startsWith('--') || !knownFlags.has(arg)) {27 throw { code: EXIT_CLI, message: `error: unknown flag "${arg}"` };28 }29 index += 1;30 if (index >= args.length) {31 throw { code: EXIT_CLI, message: `error: ${arg} requires a value` };32 }33 const value = args[index];34 if (arg === '--in') {35 result.inPath = value;36 } else if (arg === '--out') {37 result.outPath = value;38 }39 index += 1;40 }41 42 if (!result.inPath) {43 throw { code: EXIT_CLI, message: 'error: --in is required' };44 }45 if (!result.outPath) {46 throw { code: EXIT_CLI, message: 'error: --out is required' };47 }48 if (!result.inPath.endsWith('.json') || !result.outPath.endsWith('.json')) {49 throw { code: EXIT_CLI, message: 'error: --in and --out must be .json paths' };50 }51 return result;52}53 54function getHelpText() {55 return [56 'Usage:',57 ' extract-review-targets.mjs --in <review-state.json> --out <review-targets.json>',58 '',59 'Purpose:',60 ' Build deterministic target index for triage bootstrapping.',61 ].join('\n');62}63 64function buildTargetsPayload(reviewState, inPath) {65 return {66 version: 1,67 reviewState: {68 path: inPath,69 fetchedAt: reviewState.fetchedAt,70 prUrl: reviewState.pr.url,71 prNodeId: reviewState.pr.nodeId,72 },73 targets: reviewState.targets.map((target, index) => ({74 order: index + 1,75 ...target,76 })),77 };78}79 80async function main() {81 const args = parseCliArgs(process.argv);82 if (args.help) {83 process.stdout.write(`${getHelpText()}\n`);84 process.exit(EXIT_SUCCESS);85 }86 87 const raw = await readFile(args.inPath, 'utf8');88 const reviewState = JSON.parse(raw);89 assertReviewStateV2(reviewState);90 const payload = buildTargetsPayload(reviewState, args.inPath);91 await mkdir(dirname(args.outPath), { recursive: true });92 await writeFile(args.outPath, formatCanonicalJson(payload), 'utf8');93}94 95const isMain = (() => {96 try {97 const invokedScriptPath = process.argv[1] ? realpathSync(resolve(process.argv[1])) : null;98 const currentModulePath = realpathSync(fileURLToPath(import.meta.url));99 return invokedScriptPath !== null && invokedScriptPath === currentModulePath;100 } catch {101 return false;102 }103})();104 105if (isMain) {106 main().catch((error) => {107 const code = typeof error?.code === 'number' ? error.code : EXIT_OPERATIONAL;108 const message = error?.message ? String(error.message) : String(error);109 process.stderr.write(`${message}\n`);110 process.exit(code);111 });112}113