From e12e43ba72fcf141eb9fbd2c09761c59b005696a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Jul 2026 00:20:29 +0000 Subject: [PATCH 1/4] fix: scope markdown quality lint targets --- .github/workflows/markdown-quality.yml | 107 +++++++++++++++++++----- tests/test_markdown_quality_workflow.py | 30 +++++++ 2 files changed, 117 insertions(+), 20 deletions(-) create mode 100644 tests/test_markdown_quality_workflow.py diff --git a/.github/workflows/markdown-quality.yml b/.github/workflows/markdown-quality.yml index ae7729da3..920a3f202 100644 --- a/.github/workflows/markdown-quality.yml +++ b/.github/workflows/markdown-quality.yml @@ -70,29 +70,96 @@ jobs: with: # Avoid leaving credentials on disk for subsequent steps. persist-credentials: false - # Shallow clone is sufficient for linting changed/all markdown files. - fetch-depth: 1 + # Full history is required to diff against PR and push base commits. + fetch-depth: 0 + + - name: Select markdown targets + id: targets + env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + EVENT_NAME: ${{ github.event_name }} + PR_BASE_SHA: ${{ github.event.pull_request.base.sha || '' }} + PR_HEAD_SHA: ${{ github.event.pull_request.head.sha || '' }} + PUSH_BEFORE_SHA: ${{ github.event.before || '' }} + HEAD_SHA: ${{ github.sha }} + run: | + set -euo pipefail + + FILE_LIST="${RUNNER_TEMP}/markdown_targets.txt" + NULL_SHA="0000000000000000000000000000000000000000" + : > "$FILE_LIST" + + is_markdown_target() { + local file="$1" + + case "$file" in + *.md) ;; + *) + return 1 + ;; + esac + + case "$file" in + */node_modules/*|node_modules/*|*/.venv/*|.venv/*|*/venv/*|venv/*|*/.git/*|.git/*|*/dist/*|dist/*|*/build/*|build/*|*/.next/*|.next/*|*/.cache/*|.cache/*|data_out/*|mount/*|*/CHANGELOG.md|CHANGELOG.md) + return 1 + ;; + esac + + return 0 + } + + write_targets_from_diff() { + local base_sha="$1" + local head_sha="$2" + local diff_file="${RUNNER_TEMP}/changed_markdown_files.txt" + + if ! git diff --name-only --diff-filter=ACMR -z "$base_sha" "$head_sha" > "$diff_file"; then + echo "Unable to diff ${base_sha}...${head_sha}; selecting no markdown targets." + return 1 + fi + + while IFS= read -r -d '' file; do + [ -f "$file" ] || continue + is_markdown_target "$file" || continue + printf '%s\0' "$file" >> "$FILE_LIST" + done < "$diff_file" + } + + if [ "$EVENT_NAME" = "pull_request" ] && [ -n "$PR_BASE_SHA" ] && [ -n "$PR_HEAD_SHA" ]; then + write_targets_from_diff "$PR_BASE_SHA" "$PR_HEAD_SHA" || true + elif [ "$EVENT_NAME" = "push" ] && [ -n "$PUSH_BEFORE_SHA" ] && [ "$PUSH_BEFORE_SHA" != "$NULL_SHA" ]; then + write_targets_from_diff "$PUSH_BEFORE_SHA" "$HEAD_SHA" || true + elif [ "$EVENT_NAME" = "workflow_dispatch" ]; then + git fetch --no-tags origin "$DEFAULT_BRANCH" + BASE_SHA="$(git merge-base "origin/$DEFAULT_BRANCH" "$HEAD_SHA")" + write_targets_from_diff "$BASE_SHA" "$HEAD_SHA" || true + fi + + echo "file_list=$FILE_LIST" >> "$GITHUB_OUTPUT" - name: Lint Markdown files id: lint - uses: DavidAnson/markdownlint-cli2-action@6b51ade7a9e4a75a7ad929842dd298a3804ebe8b - with: - # Lint all repo markdown except generated/output and mounted volumes. - # Keep this list in sync with .markdownlintignore if present. - globs: | - **/*.md - !**/node_modules/** - !**/.venv/** - !**/venv/** - !**/.git/** - !**/dist/** - !**/build/** - !**/.next/** - !**/.cache/** - !data_out/** - !mount/** - !**/CHANGELOG.md - fix: ${{ github.event_name == 'workflow_dispatch' && inputs.fix == true }} + run: | + set -euo pipefail + + FILE_LIST="${{ steps.targets.outputs.file_list }}" + + if [ -z "$FILE_LIST" ] || [ ! -f "$FILE_LIST" ] || [ ! -s "$FILE_LIST" ]; then + echo "No markdown files selected for linting." + exit 0 + fi + + mapfile -d '' -t markdown_files < "$FILE_LIST" + + printf 'Linting markdown targets:\n' + printf ' - %s\n' "${markdown_files[@]}" + + args=() + if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ] && [ "${{ inputs.fix }}" = "true" ]; then + args+=(--fix) + fi + + npx --yes markdownlint-cli2 "${args[@]}" "${markdown_files[@]}" - name: Generate auto-fix patch if: ${{ always() && github.event_name == 'workflow_dispatch' && inputs.fix == true }} diff --git a/tests/test_markdown_quality_workflow.py b/tests/test_markdown_quality_workflow.py new file mode 100644 index 000000000..eff16cbcb --- /dev/null +++ b/tests/test_markdown_quality_workflow.py @@ -0,0 +1,30 @@ +from __future__ import annotations + +from pathlib import Path + +import pytest +import yaml + +pytestmark = pytest.mark.unit + + +def _load_workflow() -> dict: + workflow_path = Path(__file__).resolve().parents[1] / ".github" / "workflows" / "markdown-quality.yml" + return yaml.safe_load(workflow_path.read_text(encoding="utf-8")) + + +def test_markdown_quality_workflow_selects_changed_markdown_targets() -> None: + workflow = _load_workflow() + steps = workflow["jobs"]["markdownlint"]["steps"] + + checkout_step = next(step for step in steps if step["name"] == "Checkout") + assert checkout_step["with"]["fetch-depth"] == 0 + + select_step = next(step for step in steps if step["name"] == "Select markdown targets") + lint_step = next(step for step in steps if step["name"] == "Lint Markdown files") + + assert "git diff --name-only --diff-filter=ACMR -z" in select_step["run"] + assert 'git fetch --no-tags origin "$DEFAULT_BRANCH"' in select_step["run"] + assert 'No markdown files selected for linting.' in lint_step["run"] + assert 'markdownlint-cli2 "${args[@]}" "${markdown_files[@]}"' in lint_step["run"] + assert "uses" not in lint_step, "Lint step should run only on selected markdown files, not the entire repo." From b8ec86d8602645584c5b22a3dba179ce0fe19a0d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Jul 2026 00:22:05 +0000 Subject: [PATCH 2/4] refine: improve markdown workflow diagnostics --- .github/workflows/markdown-quality.yml | 4 ++-- tests/test_markdown_quality_workflow.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/markdown-quality.yml b/.github/workflows/markdown-quality.yml index 920a3f202..296169e09 100644 --- a/.github/workflows/markdown-quality.yml +++ b/.github/workflows/markdown-quality.yml @@ -114,7 +114,7 @@ jobs: local diff_file="${RUNNER_TEMP}/changed_markdown_files.txt" if ! git diff --name-only --diff-filter=ACMR -z "$base_sha" "$head_sha" > "$diff_file"; then - echo "Unable to diff ${base_sha}...${head_sha}; selecting no markdown targets." + echo "::warning::Unable to diff ${base_sha}...${head_sha}; verify repository history is available and the base/head commits exist. Selecting no markdown targets." return 1 fi @@ -145,7 +145,7 @@ jobs: FILE_LIST="${{ steps.targets.outputs.file_list }}" if [ -z "$FILE_LIST" ] || [ ! -f "$FILE_LIST" ] || [ ! -s "$FILE_LIST" ]; then - echo "No markdown files selected for linting." + echo "No Markdown files changed for this run, or all changed Markdown files were excluded from linting." exit 0 fi diff --git a/tests/test_markdown_quality_workflow.py b/tests/test_markdown_quality_workflow.py index eff16cbcb..64d38a392 100644 --- a/tests/test_markdown_quality_workflow.py +++ b/tests/test_markdown_quality_workflow.py @@ -18,13 +18,13 @@ def test_markdown_quality_workflow_selects_changed_markdown_targets() -> None: steps = workflow["jobs"]["markdownlint"]["steps"] checkout_step = next(step for step in steps if step["name"] == "Checkout") - assert checkout_step["with"]["fetch-depth"] == 0 + assert int(checkout_step["with"]["fetch-depth"]) == 0 select_step = next(step for step in steps if step["name"] == "Select markdown targets") lint_step = next(step for step in steps if step["name"] == "Lint Markdown files") assert "git diff --name-only --diff-filter=ACMR -z" in select_step["run"] assert 'git fetch --no-tags origin "$DEFAULT_BRANCH"' in select_step["run"] - assert 'No markdown files selected for linting.' in lint_step["run"] + assert "No Markdown files changed for this run" in lint_step["run"] assert 'markdownlint-cli2 "${args[@]}" "${markdown_files[@]}"' in lint_step["run"] assert "uses" not in lint_step, "Lint step should run only on selected markdown files, not the entire repo." From a6739c11a66f80324db4e8e3cb78b333885006fb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Jul 2026 00:23:30 +0000 Subject: [PATCH 3/4] fix: tighten markdown workflow guards --- .github/workflows/markdown-quality.yml | 12 ++++++++++-- tests/test_markdown_quality_workflow.py | 4 +++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/.github/workflows/markdown-quality.yml b/.github/workflows/markdown-quality.yml index 296169e09..baf47e7df 100644 --- a/.github/workflows/markdown-quality.yml +++ b/.github/workflows/markdown-quality.yml @@ -139,23 +139,31 @@ jobs: - name: Lint Markdown files id: lint + env: + EVENT_NAME: ${{ github.event_name }} run: | set -euo pipefail FILE_LIST="${{ steps.targets.outputs.file_list }}" - if [ -z "$FILE_LIST" ] || [ ! -f "$FILE_LIST" ] || [ ! -s "$FILE_LIST" ]; then + if [ -z "$FILE_LIST" ]; then + echo "No Markdown files changed for this run, or all changed Markdown files were excluded from linting." + exit 0 + fi + + if [ ! -f "$FILE_LIST" ] || [ ! -s "$FILE_LIST" ]; then echo "No Markdown files changed for this run, or all changed Markdown files were excluded from linting." exit 0 fi + # Read the NUL-delimited target list written by write_targets_from_diff. mapfile -d '' -t markdown_files < "$FILE_LIST" printf 'Linting markdown targets:\n' printf ' - %s\n' "${markdown_files[@]}" args=() - if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ] && [ "${{ inputs.fix }}" = "true" ]; then + if [ "$EVENT_NAME" = "workflow_dispatch" ] && [ "${{ inputs.fix }}" = "true" ]; then args+=(--fix) fi diff --git a/tests/test_markdown_quality_workflow.py b/tests/test_markdown_quality_workflow.py index 64d38a392..f5966e9a2 100644 --- a/tests/test_markdown_quality_workflow.py +++ b/tests/test_markdown_quality_workflow.py @@ -18,7 +18,8 @@ def test_markdown_quality_workflow_selects_changed_markdown_targets() -> None: steps = workflow["jobs"]["markdownlint"]["steps"] checkout_step = next(step for step in steps if step["name"] == "Checkout") - assert int(checkout_step["with"]["fetch-depth"]) == 0 + fetch_depth = checkout_step["with"]["fetch-depth"] + assert fetch_depth in {0, "0"} select_step = next(step for step in steps if step["name"] == "Select markdown targets") lint_step = next(step for step in steps if step["name"] == "Lint Markdown files") @@ -26,5 +27,6 @@ def test_markdown_quality_workflow_selects_changed_markdown_targets() -> None: assert "git diff --name-only --diff-filter=ACMR -z" in select_step["run"] assert 'git fetch --no-tags origin "$DEFAULT_BRANCH"' in select_step["run"] assert "No Markdown files changed for this run" in lint_step["run"] + assert lint_step["env"]["EVENT_NAME"] == "${{ github.event_name }}" assert 'markdownlint-cli2 "${args[@]}" "${markdown_files[@]}"' in lint_step["run"] assert "uses" not in lint_step, "Lint step should run only on selected markdown files, not the entire repo." From 38ca13af6f75b171b0bd507006e4677bad95da7c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Jul 2026 00:24:48 +0000 Subject: [PATCH 4/4] docs: clarify markdown workflow skip behavior --- .github/workflows/markdown-quality.yml | 3 ++- tests/test_markdown_quality_workflow.py | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/markdown-quality.yml b/.github/workflows/markdown-quality.yml index baf47e7df..1b50a1937 100644 --- a/.github/workflows/markdown-quality.yml +++ b/.github/workflows/markdown-quality.yml @@ -114,11 +114,12 @@ jobs: local diff_file="${RUNNER_TEMP}/changed_markdown_files.txt" if ! git diff --name-only --diff-filter=ACMR -z "$base_sha" "$head_sha" > "$diff_file"; then - echo "::warning::Unable to diff ${base_sha}...${head_sha}; verify repository history is available and the base/head commits exist. Selecting no markdown targets." + echo "::warning::Unable to diff ${base_sha}...${head_sha}; markdown linting will be skipped. Verify repository history is available and the base/head commits exist." return 1 fi while IFS= read -r -d '' file; do + # Skip entries that no longer exist in the working tree (for example, after renames). [ -f "$file" ] || continue is_markdown_target "$file" || continue printf '%s\0' "$file" >> "$FILE_LIST" diff --git a/tests/test_markdown_quality_workflow.py b/tests/test_markdown_quality_workflow.py index f5966e9a2..b7997277f 100644 --- a/tests/test_markdown_quality_workflow.py +++ b/tests/test_markdown_quality_workflow.py @@ -18,8 +18,7 @@ def test_markdown_quality_workflow_selects_changed_markdown_targets() -> None: steps = workflow["jobs"]["markdownlint"]["steps"] checkout_step = next(step for step in steps if step["name"] == "Checkout") - fetch_depth = checkout_step["with"]["fetch-depth"] - assert fetch_depth in {0, "0"} + assert checkout_step["with"]["fetch-depth"] == 0 select_step = next(step for step in steps if step["name"] == "Select markdown targets") lint_step = next(step for step in steps if step["name"] == "Lint Markdown files")