ci(content): run the content-style check automatically on copy changes #137

Sammanfogat
supernaut sammanfogade 1 incheckning från ci/content-style-gate in i main 2026-08-01 14:29:41 +00:00
Ägare

Closes #128.

The issue argued that the obvious fix — tests asserting on copy — is the wrong one, and asked for an
automated language gate instead. This is that gate.

What it is

scripts/check-content-style.mjs (pnpm lang-check) encodes the rules from docs/content-style.md
— brand casing, English/Swedish terminology, Forgejo term consistency, British spelling, spelled-out
shorthands — and reports the rule id and the guide section alongside each finding:

src/content/en/example.md:1
  Write 'pull request' out; 'PR' is shorthand.
  rule en-short/pull-request — docs/content-style.md § "General principles"
  > Open a PR to organize your repo.

Wired into both places, not one

  • lefthook pre-commit, globbed to src/content/**/*.md and src/i18n/*.ts, so a copy change is
    checked as it is written.
  • CI, sweeping every surface. Pre-commit only sees staged files, so without the CI sweep a rule
    added later would never be applied to copy that already exists.

Written in Node rather than shell so it behaves identically on macOS and in node:24-bookworm, and so
the rule table is testable — it has 20 tests of its own.

Two changes that make it a gate rather than noise

The first run produced three false positives, which would have failed CI on day one:

  • arkiv is the correct Swedish word for an archive on a line about backups and encryption, so it is
    exempt there instead of being flagged as a stand-in for kodförråd.
  • Whole-line comments in the .ts dictionaries are not checked. Failing a commit over a code comment's
    wording only teaches people to write awkward comments.
  • Added an escape hatch — lang-check-ignore-next-line <rule-id> — because a gate without one is a
    trap. A test caught a real bug here: the directive line matched its own rule id.

What it cannot do

Stated in the script header and in the guide: it is a regex heuristic. It stops the known faults
recurring. It cannot see a call to action that contradicts its own link, an instruction that excludes
half its audience, or tone. Reading the email and legal copy in both languages is still necessary, and
the guide says so rather than implying the gate replaces it.

Verified

pnpm lang-check → ✓ content style: no candidates in 70 file(s). · pnpm test → 169 passed ·
pnpm format:check clean · pnpm mdlint 0 issues. The gate was proven to fail end-to-end against a
deliberate fault probe (exit 1, six findings) and then pass once removed, and the new pre-commit job
ran live while committing.

Follow-up, not in this PR

The private web-language-check skill script now duplicates this rule set and lacks the two
refinements above. It should delegate to pnpm lang-check so there is one source of rules.

Closes #128. The issue argued that the obvious fix — tests asserting on copy — is the wrong one, and asked for an automated language gate instead. This is that gate. ## What it is `scripts/check-content-style.mjs` (`pnpm lang-check`) encodes the rules from `docs/content-style.md` — brand casing, English/Swedish terminology, Forgejo term consistency, British spelling, spelled-out shorthands — and reports the rule id and the guide section alongside each finding: ```text src/content/en/example.md:1 Write 'pull request' out; 'PR' is shorthand. rule en-short/pull-request — docs/content-style.md § "General principles" > Open a PR to organize your repo. ``` ## Wired into both places, not one - **lefthook pre-commit**, globbed to `src/content/**/*.md` and `src/i18n/*.ts`, so a copy change is checked as it is written. - **CI**, sweeping every surface. Pre-commit only sees staged files, so without the CI sweep a rule added later would never be applied to copy that already exists. Written in Node rather than shell so it behaves identically on macOS and in `node:24-bookworm`, and so the rule table is testable — it has 20 tests of its own. ## Two changes that make it a gate rather than noise The first run produced three false positives, which would have failed CI on day one: - `arkiv` is the correct Swedish word for an archive on a line about backups and encryption, so it is exempt there instead of being flagged as a stand-in for *kodförråd*. - Whole-line comments in the `.ts` dictionaries are not checked. Failing a commit over a code comment's wording only teaches people to write awkward comments. - Added an escape hatch — `lang-check-ignore-next-line <rule-id>` — because a gate without one is a trap. A test caught a real bug here: the directive line matched its own rule id. ## What it cannot do Stated in the script header and in the guide: it is a regex heuristic. It stops the known faults recurring. It cannot see a call to action that contradicts its own link, an instruction that excludes half its audience, or tone. Reading the email and legal copy in both languages is still necessary, and the guide says so rather than implying the gate replaces it. ## Verified `pnpm lang-check` → `✓ content style: no candidates in 70 file(s).` · `pnpm test` → 169 passed · `pnpm format:check` clean · `pnpm mdlint` 0 issues. The gate was proven to fail end-to-end against a deliberate fault probe (exit 1, six findings) and then pass once removed, and the new pre-commit job ran live while committing. ## Follow-up, not in this PR The private `web-language-check` skill script now duplicates this rule set and lacks the two refinements above. It should delegate to `pnpm lang-check` so there is one source of rules.
supernaut lade till 1 incheckning 2026-08-01 14:18:24 +00:00
ci(content): run the content-style check automatically on copy changes
Alla kontroller lyckades
ci / ci (pull_request) Successful in 1m17s
2679c3eb03
The content-style rules were enforced only by someone remembering to run them, and
the transactional email copy shipped three separate faults that a human caught
late. Equality or snapshot assertions on those strings would have caught none of
them — they mostly assert a string equals itself, and they turn every intentional
wording change into a test update. So the rules that can be expressed as a pattern
now run on their own instead.

`pnpm lang-check` (scripts/check-content-style.mjs) checks src/content/** and
src/i18n/*.ts. It runs from the lefthook pre-commit path on staged files, next to
the existing prettier/eslint/markdownlint jobs, and over every surface in CI so a
rule added later also reaches copy that already exists. Each finding prints the
rule id, the hint, the docs/content-style.md section behind it and the offending
line, so the output says which rule was broken rather than only what matched.

Two changes to the ported rules, both to keep the gate honest rather than loud:
"arkiv" is exempt on a line about backups, where it is the correct Swedish word for
an archive rather than a stand-in for kodförråd, and whole-line comments in the
TypeScript dictionaries are not checked, because failing a commit over the wording
of a code comment would only teach people to write awkward comments. A legitimate
line is suppressed with a `lang-check-ignore-next-line <rule-id>` directive instead
of weakening a rule for everyone.

The ceiling is documented in the guide and in the script: it is a regular-expression
heuristic. It stops the known faults recurring silently and it cannot see that a
call to action contradicts its link, that an instruction excludes half its audience,
or that the tone is wrong. Email and legal copy still have to be read in both
languages. Tests cover the gate's own logic, not the copy.

Version bumped to 1.4.0 for the branch.

Refs #128
supernaut tvångsskickade ci/content-style-gate från 2679c3eb03
Alla kontroller lyckades
ci / ci (pull_request) Successful in 1m17s
till f9f7e6f789
Alla kontroller lyckades
ci / ci (pull_request) Successful in 1m13s
2026-08-01 14:24:53 +00:00
Jämför
supernaut sammanfogade incheckning b65faeb1fc till main 2026-08-01 14:29:41 +00:00
supernaut tog bort grenen ci/content-style-gate 2026-08-01 14:29:41 +00:00
Logga in för att delta i denna konversation.
Inga granskare
Ingen milstolpe
Inget projekt
Inga tilldelade
1 deltagare
Notiser
Förfallodatum
Förfallodatumet är ogiltigt eller utanför gränserna. Använd formatet "åååå-mm-dd".

Inget förfallodatum satt.

Beroenden

Inga beroenden satta

Referens
bitborg/bitborg-web!137
Ingen beskrivning angiven.