From d8d0ebf601628373a20c2dfb60c6e5bc747c02f1 Mon Sep 17 00:00:00 2001 From: Claude Code Bot Date: Wed, 21 Oct 2026 21:33:20 -0700 Subject: [PATCH] test(standards): fail when markdownlint.json drifts from dotfiles standards/markdownlint.json and dotfiles' canonical markdownlint-cli/.markdownlint.json are kept in step by hand, and nothing checked them. The commit gate lints with the dotfiles copy and standards-check with this one, so drift means local and CI disagree. tests/test-markdownlint-drift.sh compares the two with jq -S. It fetches the dotfiles copy from raw.githubusercontent.com (main), or reads MARKDOWNLINT_DOTFILES_CONFIG. If it cannot read either, it fails rather than skips. A built-in control proves a flipped rule is caught. tests/ has no CI job here, so self-review.yml gains a markdownlint-drift job that runs only this file (curl and jq ship on the runner). It is not a required check. dotfiles carries the mirror test. --- .github/workflows/self-review.yml | 15 ++++++++++ tests/test-markdownlint-drift.sh | 48 +++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100755 tests/test-markdownlint-drift.sh diff --git a/.github/workflows/self-review.yml b/.github/workflows/self-review.yml index 16fb75c..29b84f1 100644 --- a/.github/workflows/self-review.yml +++ b/.github/workflows/self-review.yml @@ -45,3 +45,18 @@ jobs: fi echo "No actions/checkout found in ${file} — guardrail passed." done + + markdownlint-drift: + # standards/markdownlint.json vs dotfiles' canonical copy. Not required. + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 1 + persist-credentials: false + + - name: Compare with dotfiles markdownlint-cli/.markdownlint.json + run: bash tests/test-markdownlint-drift.sh diff --git a/tests/test-markdownlint-drift.sh b/tests/test-markdownlint-drift.sh new file mode 100755 index 0000000..ee4c1e7 --- /dev/null +++ b/tests/test-markdownlint-drift.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# standards/markdownlint.json must equal dotfiles' canonical config (main). +# Unreadable copy: FAIL, never skip. +set -euo pipefail +here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ours="${here}/../standards/markdownlint.json" +url="https://raw.githubusercontent.com/smartwatermelon/dotfiles/main/markdownlint-cli/.markdownlint.json" +tmp="$(mktemp -d)" +trap 'rm -rf "${tmp}"' EXIT +pass=0; fail=0 +_ok() { echo " ok $1"; pass=$((pass + 1)); } +_bad() { echo " FAIL $1"; fail=$((fail + 1)); } + +command -v jq >/dev/null || { echo " FAIL jq not on PATH; cannot compare"; exit 1; } + +theirs="${tmp}/dotfiles.json" +if [[ -n "${MARKDOWNLINT_DOTFILES_CONFIG:-}" ]]; then + cp "${MARKDOWNLINT_DOTFILES_CONFIG}" "${theirs}" || { echo " FAIL cannot read ${MARKDOWNLINT_DOTFILES_CONFIG}"; exit 1; } +elif ! curl -fsSL --max-time 30 -o "${theirs}" "${url}"; then + echo " FAIL cannot fetch ${url}; set MARKDOWNLINT_DOTFILES_CONFIG to a local copy" + exit 1 +fi + +# _same a b: both parse, and jq -S renders them identically. +_same() { + local a b + a="$(jq -S . "$1" 2>/dev/null)" || return 2 + b="$(jq -S . "$2" 2>/dev/null)" || return 2 + [[ -n "${a}" && "${a}" == "${b}" ]] +} + +# Known-bad control: a flipped rule must not compare equal. +jq '.MD013 = (.MD013 | not)' "${ours}" >"${tmp}/flipped.json" +if _same "${ours}" "${tmp}/flipped.json"; then _bad "known-bad: a flipped MD013 compared equal"; else _ok "known-bad: a flipped MD013 is caught"; fi + +rc=0 +_same "${ours}" "${theirs}" || rc=$? +if ((rc == 0)); then + _ok "standards/markdownlint.json == dotfiles markdownlint-cli/.markdownlint.json" +elif ((rc == 2)); then + _bad "could not parse one of the configs; that is not a match" +else + _bad "standards/markdownlint.json has drifted from dotfiles (${MARKDOWNLINT_DOTFILES_CONFIG:-${url}})" + diff -u <(jq -S . "${theirs}") <(jq -S . "${ours}") || true +fi + +echo "markdownlint drift: ${pass} ok, ${fail} failed" +[[ "${fail}" -eq 0 ]]