MeDiVa
DocsRecipes

Gate PR bodies

Reject pull requests whose description is missing sections or still has placeholder text.

MON MOTHMA: "Many Bothans died to bring us these plans. The least the courier can do is fill in the briefing form."

The Rebel Alliance does not accept a mission briefing that's missing the objective, has no linked intel, and whose pre-flight checklist is half-empty. Neither should your repo. Here's a complete, runnable recipe: require every PR to carry a real Summary, a linked issue, and a checked-off checklist before it can dock.

The contract

.github/pull_request_template.md
<!-- mdv: document title.pattern=/^(feat|fix|docs|chore|refactor|perf|test|build|ci)(\(.+\))?!?:\s.+/ -->

<!-- mdv: block required minWords=20 noPlaceholder -->
## Description

Describe what changed, why, and the impact.
<!-- mdv: endblock -->

<!-- mdv: block required issueKeyword=Fixes,Closes,Resolves -->
## Related Issue

Fixes #
<!-- mdv: endblock -->

<!-- mdv: taskList required allChecked exactLabels -->
## Pre-Merge Checklist

- [ ] I reviewed the diff for unrelated changes.
- [ ] I updated docs, or this PR does not require docs.
<!-- mdv: endtaskList -->

mediva validates the PR body against this template — the same file is both the contributor template and the contract. One form, every courier.

The GitHub Action

This is the sensor net guarding the exhaust port: every PR that arrives gets scanned before it can merge. Two details make it tamper-proof — it checks out the base branch's template (so a PR can't loosen the contract it must pass), and it validates the title as well as the body.

.github/workflows/pr.yml
on:
  pull_request:
    types: [opened, edited, reopened, synchronize]
jobs:
  validate-pr-body:
    runs-on: ubuntu-latest
    steps:
      # Check out the BASE branch's template — the rules come from the branch you're merging
      # INTO, so a PR that edits the template to weaken it is still judged by the old contract.
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.base.sha }}
      - run: npm install -D mediva
      - env:
          TITLE: ${{ github.event.pull_request.title }}
          BODY: ${{ github.event.pull_request.body }}
        run: |
          printf '%s' "$BODY" > /tmp/body.md
          npx mediva check --schema .github/pull_request_template.md --title "$TITLE" /tmp/body.md

A PR with a placeholder Description (Fixes # and nothing else), a misformatted title, or an unchecked box exits 1 and fails the check with the exact rule and line — the proton torpedo finds the gap. Real content passes, and the plans get through.

First adoption: the check validates against the template on the base branch, so land .github/pull_request_template.md there first. Until that file exists on base, mediva check --schema can't read its contract and exits 2 — the step fails red, it does not pass silently. So merge the template to base before you turn the required check on.

Variations

  • Swap allChecked for minChecked=1 if not every box is mandatory.

  • Add oneChecked on a choice block for a single-select "Type of change" (feat / fix / docs — pick one, like choosing your starfighter).

  • Require visual evidence for UI work with a media section. requiredOrNA accepts an image or video, or an explicit N/A when there's genuinely nothing to show — so a backend-only PR isn't forced to fake a screenshot:

    <!-- mdv: media requiredOrNA -->
    ## Screenshot
    
    ![before/after of the settings panel](./docs/pr-shot.png)
    <!-- mdv: endmedia -->

    A section with prose but no image and no N/A note fails with [missing-media] — "tell me what changed on screen, or tell me honestly that nothing did."

On this page