Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/pr-description-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ permissions:

jobs:
check-pr-description:
# Dependabot writes its own body with none of these headings, so its PRs could never pass; the author decides, not the actor.
if: github.event.pull_request.user.login != 'dependabot[bot]'
runs-on: ubuntu-latest
steps:
- name: Check required sections
Expand Down
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,8 @@ test(tools): add registry conflict detection

### PR Description Template

CI's `check-pr-description` job requires `## Why`, `## What Changed` and `## Testing`. It skips a pull request Dependabot opened, since Dependabot writes its own body.

```markdown
## Why

Expand Down
47 changes: 47 additions & 0 deletions scripts/pr-description-check.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {spawnSync} from 'node:child_process';
import {readFileSync} from 'node:fs';
import {join} from 'node:path';
import {parseDocument} from 'yaml';
import {root} from './lib/repo-root.mjs';

type Job = {if?: string; steps: {run: string; if?: string}[]};

const job = (): Job => {
const workflow = parseDocument(readFileSync(join(root, '.github/workflows/pr-description-check.yml'), 'utf8')).toJS();

return (workflow as {jobs: Record<string, Job>}).jobs['check-pr-description'];
};

const checkBody = (body: string) => {
const [step] = job().steps;
const {status, stdout} = spawnSync('bash', ['-c', step.run], {
encoding: 'utf8',
env: {...process.env, PR_BODY: body},
});

return {status, stdout};
};

describe('the pull request description check', () => {
it('skips a pull request Dependabot opened, by its author rather than whoever edits it', () => {
expect({condition: job().if, stepConditions: job().steps.map(step => 'if' in step)}).toEqual({
condition: "github.event.pull_request.user.login != 'dependabot[bot]'",
stepConditions: [false],
});
});

it('still passes a body with all three headings and fails a Dependabot-style one, naming each it lacks', () => {
expect({
complete: checkBody('## Why\n\nA.\n\n## What Changed\n\nB.\n\n## Testing\n\nC.\n'),
dependabotStyle: checkBody('Bumps [@types/node](https://github.com/DefinitelyTyped) from 26.6.5 to 26.6.6.\n'),
}).toEqual({
complete: {status: 0, stdout: 'All required PR description sections are present.\n'},
dependabotStyle: {
status: 1,
stdout:
'::error::PR description is missing required sections: ## Why ## What Changed ## Testing\n' +
'Required sections: ## Why, ## What Changed, ## Testing\n',
},
});
});
});
7 changes: 7 additions & 0 deletions specs/hal-engine-doc-gates/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ Spike documents are covered differently. A spike records what was believed when

Two steps of that job are deliberately outside the script: the spec anchor check and the changelog check both diff against `origin/main`, and neither behaves the same on a checkout as it does against a pull request's base.

## The pull request description check leaves Dependabot alone

`pr-description-check.yml` asks for three headings that Dependabot's body never has, so every Dependabot pull request showed a red check nothing could turn green.

- The check skips a pull request Dependabot opened, decided by its author rather than by whoever edits it ([validated by: skips a pull request Dependabot opened, by its author rather than whoever edits it](../../scripts/pr-description-check.test.ts#L26)).
- It still fails any other body that lacks a heading, naming each one ([validated by: still passes a body with all three headings and fails a Dependabot-style one, naming each it lacks](../../scripts/pr-description-check.test.ts#L33)).

## Paths in prose resolve

`npm run docs:check-paths` resolves every backticked repository path written in a document - `src/`, `scripts/`, `example/`, `smoke/`, `docs/`, `specs/`, `adrs/`, `dist/`, `.github/` and `.specify/`. A path that legitimately does not resolve is named in the script's own allowlist with the reason, because an exception has to be written down to be one; narrowing the pattern instead would hide every other path behind the same prefix. A path in prose is a citation, and a citation nobody resolves rots in silence: the file moves, the sentence naming it stays, and the next reader follows it to nothing.
Expand Down
Loading