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
433 changes: 129 additions & 304 deletions .github/linters/package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion .github/linters/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "Pinned linter dependencies for CI supply chain security",
"private": true,
"dependencies": {
"markdownlint-cli2": "0.23.3"
"markdownlint-cli": "0.49.1"
},
"overrides": {
"js-yaml": "4.3.2",
Expand Down
54 changes: 32 additions & 22 deletions .github/workflows/markdown-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@ on:
pull_request:
paths:
- '**/*.md'
- '.markdownlint-cli2.yaml'
- '.markdownlint.jsonc'
- '.markdownlintignore'
- '.markdown-link-check.json'
- '.github/linters/package*.json'
push:
branches:
- main
paths:
- '**/*.md'
- '.markdownlint-cli2.yaml'
- '.markdownlint.jsonc'
- '.markdownlintignore'
- '.markdown-link-check.json'
- '.github/linters/package*.json'

Expand Down Expand Up @@ -46,31 +48,39 @@ jobs:
npm ci
npm audit --audit-level=high

# No glob arguments: file selection comes from .markdownlint-cli2.yaml, the
# same source the pre-commit hook and `npm run lint:md` read, so none of the
# three can select a different set. The version comes from
# .github/linters/package-lock.json (installed above), so there is no second
# version pin to drift.
# A markdownlint-cli2 run that matched no files still exits 0, so assert the
# file count is non-zero from the same output instead of running the linter
# a second time.
# File selection comes from the same command used by `npm run lint:md` and
# pre-commit, so none of the three can select a different set. The version
# comes from .github/linters/package-lock.json (installed above), so there
# is no second version pin to drift.
#
# --dot is required: markdownlint's globber skips dot-directories by
# default, which silently excluded .github/**/*.md (e.g.
# .github/pull_request_template.md) from every invocation.
- name: Run markdownlint
shell: bash
run: |
set -euo pipefail
output="$(mktemp)"
set +e
npx --prefix .github/linters markdownlint-cli2 2>&1 | tee "$output"
lint_rc=${PIPESTATUS[0]}
set -e
.github/linters/node_modules/.bin/markdownlint --config .markdownlint.jsonc --ignore-path .markdownlintignore --dot "**/*.md"

count=$(sed -n 's/^Linting: \([0-9]\{1,\}\) file.*/\1/p' "$output")
echo "markdownlint linted ${count:-0} file(s)"
if [ "$lint_rc" -ne 0 ]; then
exit "$lint_rc"
fi
if [ "${count:-0}" -lt 1 ]; then
echo "::error::markdownlint matched no files - check globs in .markdownlint-cli2.yaml" >&2
# A markdownlint run that matched no files can still exit 0, so assert the
# repository has tracked markdown files after applying .markdownlintignore.
#
# The ignore set is evaluated by GIT's gitignore engine via --exclude-from,
# not by hand-translating .markdownlintignore into `:!` pathspecs. Those are
# different matchers: a bare `test/vendor` pathspec matches only that exact
# path, so a naive translation left vendored files in the count and the
# assertion drifted above the set markdownlint actually linted. Reusing the
# same gitignore semantics markdownlint applies keeps them in agreement.
- name: Assert markdownlint actually linted files
shell: bash
run: |
set -euo pipefail
total=$(git ls-files -- '*.md' | wc -l | tr -d ' ')
ignored=$(git ls-files -c -i --exclude-from=.markdownlintignore -- '*.md' | wc -l | tr -d ' ')
count=$((total - ignored))
echo "markdownlint linted ${count} file(s) (${total} tracked, ${ignored} ignored)"
if [ "${count}" -lt 1 ]; then
echo "::error::markdownlint matched no files - check .markdownlintignore" >&2
exit 1
fi

Expand Down
23 changes: 8 additions & 15 deletions .markdownlint-cli2.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# markdownlint-cli2 configuration
# Aligned with playbook standards for consistency
# Legacy markdownlint-cli2 configuration kept temporarily for branch compatibility.
# Active linting uses .markdownlint.jsonc plus .markdownlintignore.
# See: https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md

config:
Expand Down Expand Up @@ -42,28 +42,21 @@ config:
# MD060 - Table column style spacing
MD060: false

# Scan these files
# Scan these files. This is the SINGLE source of truth for file selection: both
# the pre-commit hook and markdown-quality.yml invoke markdownlint-cli2 with no
# glob arguments, so neither can select a different set. Previously CI passed
# "**/*.md" plus exclusions on the command line (49 files) while these globs
# alone selected 9, so the commit-time hook checked 18% of what CI checked.
# Legacy file selection retained only for consumers still invoking
# markdownlint-cli2 directly. Active file selection is the explicit
# markdownlint command in package.json, pre-commit, and markdown-quality.yml plus
# .markdownlintignore.
globs:
- "**/*.md"

# Ignore files
# Legacy ignore files. Keep this aligned with .markdownlintignore until this file
# is deleted.
ignores:
- "node_modules"
- ".github/linters/node_modules"
- "deleteme"
- "tmp"
- "CHANGELOG.md"
# Vendored, SHA-pinned bats submodules (ADR-0025) -- third-party markdown we do
# not own and must not lint to our rules. markdown-quality.yml checks out
# WITHOUT submodules so it never saw these, but pre-commit.yml uses
# `submodules: true` for the bats hook, so the pre-commit run WOULD: measured
# 21 vendored .md files producing 235 issues in 13 files. Excluding them here
# preserves the existing effective coverage rather than widening it.
- "test/vendor"

# Support YAML frontmatter
Expand Down
22 changes: 22 additions & 0 deletions .markdownlint.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"MD003": false,
"MD004": false,
"MD007": false,
"MD013": false,
"MD022": false,
"MD024": false,
"MD025": false,
"MD026": false,
"MD031": false,
"MD032": false,
"MD033": false,
"MD034": false,
"MD036": false,
"MD040": false,
"MD041": false,
"MD051": false,
"MD056": false,
"MD058": false,
"MD060": false,
"frontMatter": "^---\\s*$[\\s\\S]*?^---\\s*$"
}
6 changes: 6 additions & 0 deletions .markdownlintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CHANGELOG.md
node_modules
.github/linters/node_modules
deleteme
tmp
test/vendor
18 changes: 9 additions & 9 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,26 +34,26 @@ repos:
# .github/linters/package.json (+ package-lock.json, which also carries the
# transitive overrides and is what `npm audit` gates in markdown-quality.yml).
# This hook delegates to that install instead of pinning a second time via a
# remote rev, so the two can never drift. Configuration AND file selection both
# come from .markdownlint-cli2.yaml. Mirrors agentic-coding-patterns#451.
# remote rev, so the two can never drift. Configuration and file exclusions
# come from .markdownlint.jsonc and .markdownlintignore.
#
# Unlike patterns/playbook, this repo runs `pre-commit run --all-files` in CI
# (.github/workflows/pre-commit.yml), so the DEFAULT hook must not rewrite
# files: `--fix` would mutate the checkout and CI would fail on "files were
# modified by this hook" instead of reporting the lint error. The default id is
# therefore check-only, and the fixing form is opt-in via
# `pre-commit run markdownlint-cli2-fix --hook-stage manual`.
# `pre-commit run markdownlint-fix --hook-stage manual`.
- repo: local
hooks:
- id: markdownlint-cli2
name: markdownlint-cli2 (npm-pinned, check only)
entry: npx --prefix .github/linters markdownlint-cli2
- id: markdownlint
name: markdownlint (npm-pinned, check only)
entry: bash -c 'npm ci --prefix .github/linters --prefer-offline --no-audit --no-fund >/dev/null && npm --prefix .github/linters exec -- markdownlint --config .markdownlint.jsonc --ignore-path .markdownlintignore --dot "**/*.md"' --
language: system
types: [markdown]
pass_filenames: false
- id: markdownlint-cli2-fix
name: markdownlint-cli2 (npm-pinned, --fix)
entry: npx --prefix .github/linters markdownlint-cli2 --fix
- id: markdownlint-fix
name: markdownlint (npm-pinned, --fix)
entry: bash -c 'npm ci --prefix .github/linters --prefer-offline --no-audit --no-fund >/dev/null && npm --prefix .github/linters exec -- markdownlint --config .markdownlint.jsonc --ignore-path .markdownlintignore --dot --fix "**/*.md"' --
language: system
types: [markdown]
pass_filenames: false
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"acq.backends/"
],
"scripts": {
"lint:md": "npx --prefix .github/linters markdownlint-cli2",
"lint:md": ".github/linters/node_modules/.bin/markdownlint --config .markdownlint.jsonc --ignore-path .markdownlintignore --dot \"**/*.md\"",
"lint:secrets": "pre-commit run gitleaks --all-files",
"lint": "npm run lint:md",
"check": "pre-commit run --all-files"
Expand Down
Loading