chore(ci): gate formatting and PR titles - #4
Merged
Merged
Conversation
The repository has carried a prettier config and a `format:check` script since the frontend was created, but nothing in CI ever ran them, so nine files kept whichever indentation their author's editor produced. All the changes here are whitespace: four-space indentation collapsed to the two the config asks for. Doing it in its own commit keeps the gate that follows readable, because 285 lines of reindentation would otherwise bury a nine-line workflow change. `test-results/` and `playwright-report/` join .prettierignore for a different reason. Both are gitignored, so CI never sees them, but a local Playwright run leaves them behind and `format:check` then fails on a machine where the gate would have passed — which is the fastest way to teach someone the gate is unreliable and should be skipped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B455UiUbbGqda87ZFc9wjb
Three gates the contributing rules already describe but nothing enforced.
`ruff check` and `ruff format` disagree about different things — the first
about what the code does, the second about how it reads — and only the
first was wired up. The frontend had the same hole for a different reason:
`next lint` is eslint, which says nothing about formatting, so the
`format:check` script sat unused. Both pass on the current tree, so
neither gate arrives red.
The PR title check is new. A squash merge writes the title into history,
which makes it the subject line the repository's 72-character
Conventional Commits rule actually applies to; gating the branch commits
instead would check text that gets discarded. The accepted types are the
six in the contributing convention rather than the wider Conventional
Commits vocabulary, so widening it stays a visible edit here.
The title is read through an environment variable rather than a `${{ }}`
interpolation. On a public repository the title is attacker-controlled,
and substituting it into the script would run whatever a PR author put
after a semicolon.
No secret-scanning job: GitHub's own scanning and push protection are
enabled on this repository, and a second scanner would only add a way for
the two to disagree.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B455UiUbbGqda87ZFc9wjb
2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
Three gates the contributing rules already describe, none of which anything
enforced.
ruff checkandruff formatdisagree about different things — the first aboutwhat the code does, the second about how it reads — and only the first was
wired up. The frontend had the same hole for a different reason:
next lintiseslint, which says nothing about formatting, so the
format:checkscript andthe
.prettierrcbeside it had sat unused since the frontend was created. Ninefiles had drifted to whichever indentation their author's editor produced;
chore(frontend): reformat the files prettier had never been run onfixes themin a separate commit so 285 lines of reindentation do not bury a 49-line
workflow change. Every hunk in it is whitespace.
The PR title check is new. A squash merge writes the title into history, which
makes it the subject line this repository's 72-character Conventional Commits
rule actually applies to — gating the branch commits instead would check text
that gets discarded on merge. The accepted types are the six the convention
lists rather than the wider Conventional Commits vocabulary, so widening the set
stays a visible edit here rather than something that happens by accident.
The title is read through an environment variable rather than a
${{ }}interpolation into the script. On a public repository the title is
attacker-controlled, and substituting it directly would run whatever a PR author
put after a semicolon.
How it was verified
cd backend && ruff check . && python -m compileall -q -x "venv|data" . && pytest && ruff format --check .cd frontend && npm run lint && npm run typecheck && npm test && npm run buildBeyond the checklist:
npm run format:checkandruff format --check .— the two new gates — passon this branch, so neither arrives red.
python scripts/build_agent_skill.py --checkandpython scripts/build_repo_facts.py --checkstill pass; the reformat touchedpackage.jsonbut no generated figure moved.titles: the five valid shapes above pass (including
feat!:and a baredocs:), andUpdate stuff,feature(ui): …, a capitalised description, atrailing period, a 98-character subject and a bare
feat(ui):each fail withthe message naming the specific rule.
block work already in flight.
feat(ui): x; rm -rf /is accepted as awell-formed title and nothing executes, which is the point of the
env:block.Screenshots
Not applicable — no UI change. The nine reformatted files render identically;
the diff is indentation only.
Configuration
Nothing here applies.
Notes for the reviewer
No secret-scanning job, deliberately. GitHub's own secret scanning and push
protection are already enabled on this repository, and
security_and_analysisreports both
enabled. A second scanner would add a way for the two todisagree without adding coverage.
This branch push ran no CI.
ci.ymllistens onpush: mainandpull_request: main, so nothing fired until this PR opened. The gates are greenlocally; this PR is the first time they run on a runner, and
pr-titleisrunning against its own definition.
Left out on purpose, both worth their own PR:
concurrencyon the workflow, to cancel superseded runs. Five jobs on everypush gets expensive as PR volume grows, but it is a different concern from
what is gated.
disabled.The one judgement call I would push back on if you disagree: restricting types
to the six in the convention means a CI-only change has to be
chore, notci.That is what this PR did.
🤖 Generated with Claude Code
https://claude.ai/code/session_01B455UiUbbGqda87ZFc9wjb