teqo-backup/scripts/check-plans-only-pr-closes.mjs
Cursor Agent 1ad8863994
OPS12 — CI bloqueia Closes/Fixes em PR só docs/plans
- Lógica pura em plansOnlyClosesGuard.mjs + CLI check-plans-only-pr-closes
- Job plans-only-closes em ci-pr.yml (rollup checks)
- Unit tests com fixture do incidente #115
- plan-issue, agent-pr-workflow, PR template e GUARDRAILS atualizados

Closes #116

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 11:26:19 +00:00

69 lines
1.9 KiB
JavaScript

#!/usr/bin/env node
/**
* check-plans-only-pr-closes — CI guard: plans-only PRs must not close Issues via body keywords.
* Paths from git diff (merge-base…HEAD); body from PR_BODY env or `gh pr view` when PR_NUMBER is set.
*/
import { execFileSync } from 'node:child_process'
import { assertPlansOnlyPrAllowsBody } from './lib/plansOnlyClosesGuard.mjs'
const base = `origin/${process.env.GITHUB_BASE_REF ?? 'main'}`
function die(message) {
console.error(`[check-plans-only-pr-closes] ${message}`)
process.exit(1)
}
function listChangedPaths() {
let mergeBase
try {
mergeBase = execFileSync('git', ['merge-base', 'HEAD', base], { encoding: 'utf8' }).trim()
} catch {
die(`no merge-base with ${base} (shallow clone?)`)
}
const raw = execFileSync('git', ['diff', '--name-only', `${mergeBase}...HEAD`], {
encoding: 'utf8',
})
return raw
.split('\n')
.map((line) => line.trim())
.filter(Boolean)
}
function readPrBody() {
if (process.env.PR_BODY !== undefined) {
return process.env.PR_BODY
}
const prNumber = process.env.PR_NUMBER ?? process.env.GITHUB_PR_NUMBER
if (!prNumber) {
return ''
}
try {
const json = execFileSync('gh', ['pr', 'view', String(prNumber), '--json', 'body'], {
encoding: 'utf8',
})
const parsed = JSON.parse(json)
return typeof parsed.body === 'string' ? parsed.body : ''
} catch (error) {
const detail = error instanceof Error ? error.message : String(error)
die(`failed to read PR #${prNumber} body via gh: ${detail}`)
}
}
const paths = listChangedPaths()
const body = readPrBody()
const result = assertPlansOnlyPrAllowsBody({ paths, body })
if (!result.ok) {
die(result.message)
}
if (paths.length > 0 && paths.every((path) => path.startsWith('docs/plans/'))) {
console.log('[check-plans-only-pr-closes] ok — plans-only diff without closing keywords')
} else {
console.log('[check-plans-only-pr-closes] ok — not a plans-only diff (skipped)')
}