scripts/upsert-recap-block.mjs
scripts/upsert-recap-block.mjsBrowse 6 files
942 tokens
3,485 bytes
Token encoding: o200k_base
Snapshot 8beb6cd
← Back to SKILL.md
1#!/usr/bin/env node2// Upsert a system-recap block into a PR description without touching any3// text outside the markers. Usage:4// node upsert-recap-block.mjs <pr-number> <block-file>5// The block file must start with the start marker and end with the end6// marker. Requires the `gh` CLI to be authenticated.7//8// Cloud Agents cannot `gh pr edit` (GraphQL updatePullRequest is forbidden9// for the integration token). On that failure the script still validates10// mermaid, prints the merged body, and tells the agent to apply it with11// Cursor ManagePullRequest.12import { execFileSync, spawnSync } from 'node:child_process'13import { readFileSync } from 'node:fs'14import path from 'node:path'15import { fileURLToPath } from 'node:url'16 17const startMarker = '<!-- system-recap:start -->'18const endMarker = '<!-- system-recap:end -->'19const repoRoot = fileURLToPath(new URL('../../../..', import.meta.url))20 21export function isGhPrEditForbidden(error) {22 const text =23 error instanceof Error24 ? `${error.message}${25 'stderr' in error && typeof error.stderr === 'string'26 ? error.stderr27 : ''28 }`29 : String(error)30 return (31 text.includes('Resource not accessible by integration') ||32 text.includes('updatePullRequest')33 )34}35 36export function mergeRecapBody(body, block) {37 const startIndex = body.indexOf(startMarker)38 const endIndex = body.indexOf(endMarker)39 const hasExistingBlock =40 startIndex !== -1 && endIndex !== -1 && endIndex > startIndex41 const nextBody = hasExistingBlock42 ? body.slice(0, startIndex) +43 block +44 body.slice(endIndex + endMarker.length)45 : `${body.trimEnd()}\n\n${block}\n`46 return { hasExistingBlock, nextBody }47}48 49function main() {50 const [prNumber, blockFile] = process.argv.slice(2)51 if (!prNumber || !blockFile) {52 console.error('usage: upsert-recap-block.mjs <pr-number> <block-file>')53 process.exit(1)54 }55 56 const block = readFileSync(blockFile, 'utf8').trim()57 if (!block.startsWith(startMarker) || !block.endsWith(endMarker)) {58 console.error(59 `block file must start with "${startMarker}" and end with "${endMarker}"`,60 )61 process.exit(1)62 }63 64 const mermaidCheck = spawnSync(65 process.execPath,66 [67 path.join(repoRoot, 'tools/check-mermaid-syntax.ts'),68 '--stdin',69 '--label',70 blockFile,71 ],72 { input: block, encoding: 'utf8', cwd: repoRoot },73 )74 if (mermaidCheck.status !== 0) {75 process.stderr.write(mermaidCheck.stdout)76 process.stderr.write(mermaidCheck.stderr)77 process.exit(mermaidCheck.status === null ? 1 : mermaidCheck.status)78 }79 80 const body = execFileSync(81 'gh',82 ['pr', 'view', prNumber, '--json', 'body', '--jq', '.body'],83 { encoding: 'utf8' },84 )85 86 const { hasExistingBlock, nextBody } = mergeRecapBody(body, block)87 88 try {89 execFileSync('gh', ['pr', 'edit', prNumber, '--body-file', '-'], {90 input: nextBody,91 })92 } catch (error) {93 if (isGhPrEditForbidden(error)) {94 console.error(95 `gh pr edit cannot update PR #${prNumber} (Resource not accessible by integration). Cloud Agents must apply this recap with Cursor ManagePullRequest. Merged PR body follows.`,96 )97 process.stdout.write(nextBody)98 if (!nextBody.endsWith('\n')) process.stdout.write('\n')99 process.exitCode = 2100 return101 }102 throw error103 }104 105 console.log(106 hasExistingBlock107 ? `updated system-recap block on PR #${prNumber}`108 : `added system-recap block to PR #${prNumber}`,109 )110}111 112if (113 process.argv[1] &&114 fileURLToPath(import.meta.url) === path.resolve(process.argv[1])115) {116 main()117}118