- 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>
69 lines
1.9 KiB
JavaScript
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)')
|
|
}
|