Add markdownlint and link-checking CI - #3
Merged
Merged
Conversation
The repository had no CI of any kind. It is a public marketing surface carrying four external links and time-sensitive factual claims, with nothing verifying either — the gap behind the testing grade in the 2026-08-18 sweep. ──────────────────────── The workflow ──────────────────────── `.github/workflows/checks.yml` runs two independent jobs: - **markdownlint** (markdownlint-cli2) — structure and style across every tracked markdown file. - **link check** (lychee) — every external and relative link actually resolves. Accepts 200/206/301/302/403, retries three times, and times out at 20s so a slow upstream is not read as a broken link. Triggers on pull_request and on push to main, plus a weekly cron and manual dispatch. The schedule is the point of the job as much as the PR trigger: external link rot happens with no commit attached to it, so a check that only runs on change would never catch a dead upstream until someone happened to edit the file. `permissions: contents: read` — the workflow needs nothing else. Concurrency cancels superseded runs per ref. Actions are pinned to full commit SHAs with the version in a trailing comment, resolved from the registry at authoring time rather than written from memory: actions/checkout v7.0.1, markdownlint-cli2-action v24.2.0, lychee-action v2.9.0. A mutable tag on a third-party action is a supply chain hole; a SHA is not. ────────────────────── The lint config ────────────────────── `.markdownlint-cli2.yaml` relaxes three rules deliberately, each with the reason recorded inline: - MD013 at 100 columns, off for tables and code blocks. Prose is wrapped by hand; tables and links cannot always honour a limit. - MD033 allows only the block elements the org profile actually needs — div, img, sub, b, a, br, picture, source. Raw HTML is how a GitHub profile centres a brand lockup and how it serves a theme-aware image. - MD024 siblings_only, so separate files may repeat a heading. LICENSE is excluded from the glob; it is legal text, not prose to lint. Co-authored-by: stxkxsbot <275011021+stxkxsbot@users.noreply.github.com>
profile/README.md carries a scoped MD041 disable because the brand lockup has to precede the heading. The comment there claims the property MD041 protects is still satisfied — a top-level heading naming the document — and that claim is true today. But a suppression outlives the reason it was granted. Delete that h1 and MD041 is off on the file, so nothing fails and the accessibility defect returns silently. The disable would then be asserting something false. Adds a two-line assertion to the markdown job: profile/README.md must contain exactly one top-level heading. This pins the property rather than trusting the comment, and converts the suppression from a promise into a checked invariant. Verified: the fixed README returns 1 and passes; main's current README returns 0 and fails, which is the correct behaviour until the profile fix lands ahead of this workflow. Co-authored-by: stxkxsbot <275011021+stxkxsbot@users.noreply.github.com>
stxkxs
force-pushed
the
ci/markdown-and-link-checks
branch
from
August 19, 2026 06:41
533c90c to
19f5fc4
Compare
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.
Closes the testing gap from the 2026-08-18 sweep: the repo had no CI of any kind, while being a public surface with four external links and time-sensitive factual claims.
This PR will be red until #2 merges, and that is expected, not a fault. The markdownlint job fails on
main's currentprofile/README.md— those failures are the a11y and line-length defects that #2 fixes:Order: #2 → this →
required_status_checks→ #1.What runs
markdownlintlink checkTriggers:
pull_request, push tomain, a weekly cron, and manual dispatch.The cron matters as much as the PR trigger. Link rot happens with no commit attached to it — a check that only runs on change would never notice a dead upstream until someone happened to edit that file. The four links I verified by hand during the sweep are now verified continuously.
Supply-chain note
Actions are pinned to full commit SHAs, with the version in a trailing comment, resolved from the registry at authoring time rather than written from memory:
actions/checkout→3d3c42e5…(v7.0.1)DavidAnson/markdownlint-cli2-action→21c1be1b…(v24.2.0)lycheeverse/lychee-action→e7477775…(v2.9.0)A mutable tag on a third-party action is a supply-chain hole.
permissions: contents: read— the workflow needs nothing more.On the relaxed lint rules
Three rules are relaxed, each with its reason recorded inline in the config rather than left as a bare number:
div, img, sub, b, a, br, picture, source) — raw HTML is how GitHub centres a brand lockup and serves a theme-aware image.siblings_only, so separate files may repeat a heading.LICENSEis excluded from the glob — legal text, not prose to lint.I did not relax MD041 here. The one file that can't satisfy it carries a scoped inline exception in #2 with its reason; the rule stays enforced for every other file.