From ac8b783a23e00883aeed6750e8b33a358000e3a9 Mon Sep 17 00:00:00 2001 From: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com> Date: Wed, 10 Jun 2026 14:29:06 +0000 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20implement=20issue=20#61=20=E2=80=94?= =?UTF-8?q?=20Compliance:=20codeowners-org-leads-not-first?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/dev-lead-lint.sh | 27 ++++++++++ tests/dev-lead/unit/test_dev_lead_lint.bats | 58 +++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/scripts/dev-lead-lint.sh b/scripts/dev-lead-lint.sh index c85b52869..4f8baed15 100755 --- a/scripts/dev-lead-lint.sh +++ b/scripts/dev-lead-lint.sh @@ -91,6 +91,33 @@ if [ "${#agent_files[@]}" -gt 0 ]; then fi fi +# ── 3. CODEOWNERS validation ────────────────────────────────────────────────── +# Checks that every owner line lists @petry-projects/org-leads first. +# Standard: standards/codeowners-standard.md (codeowners-org-leads-not-first check) +codeowners_file="" +for candidate in ".github/CODEOWNERS" "CODEOWNERS" "docs/CODEOWNERS"; do + if [ -f "$candidate" ]; then + codeowners_file="$candidate" + break + fi +done + +if [ -n "$codeowners_file" ]; then + echo " [lint] validating $codeowners_file..." + codeowners_fail=0 + while IFS= read -r line; do + [[ "$line" =~ ^[[:space:]]*$ ]] && continue + [[ "$line" =~ ^[[:space:]]*# ]] && continue + first_owner=$(printf '%s\n' "$line" | awk '{print $2}') + if [ "$first_owner" != "@petry-projects/org-leads" ]; then + echo "FAIL: $codeowners_file — @petry-projects/org-leads must be the first owner on line: $line" + codeowners_fail=1 + fi + done < "$codeowners_file" + [ "$codeowners_fail" -eq 0 ] || fail=1 + [ "$codeowners_fail" -eq 0 ] && echo "OK: $codeowners_file" +fi + if [ "$fail" -eq 0 ]; then echo " [lint] all checks passed" fi diff --git a/tests/dev-lead/unit/test_dev_lead_lint.bats b/tests/dev-lead/unit/test_dev_lead_lint.bats index 958f9f1b7..c1c7dea21 100644 --- a/tests/dev-lead/unit/test_dev_lead_lint.bats +++ b/tests/dev-lead/unit/test_dev_lead_lint.bats @@ -194,3 +194,61 @@ MD run bash "$LINT_SCRIPT" [ "$status" -eq 0 ] } + +# ── CODEOWNERS validation tests ─────────────────────────────────────────────── + +@test "lint: passes when no CODEOWNERS file exists" { + run bash "$LINT_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "lint: passes when .github/CODEOWNERS lists org-leads first" { + mkdir -p "$WORK_DIR/.github" + cat > "$WORK_DIR/.github/CODEOWNERS" <<'CO' +# Default owner +* @petry-projects/org-leads +CO + run bash "$LINT_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "lint: passes when CODEOWNERS lists org-leads first with additional owners" { + mkdir -p "$WORK_DIR/.github" + cat > "$WORK_DIR/.github/CODEOWNERS" <<'CO' +* @petry-projects/org-leads @petry-projects/backend +docs/ @petry-projects/org-leads @petry-projects/docs +CO + run bash "$LINT_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "lint: fails when CODEOWNERS owner line does not list org-leads first" { + mkdir -p "$WORK_DIR/.github" + cat > "$WORK_DIR/.github/CODEOWNERS" <<'CO' +* @petry-projects/backend @petry-projects/org-leads +CO + run bash "$LINT_SCRIPT" + [ "$status" -ne 0 ] + [[ "$output" =~ "org-leads" ]] +} + +@test "lint: fails when CODEOWNERS owner line omits org-leads entirely" { + mkdir -p "$WORK_DIR/.github" + cat > "$WORK_DIR/.github/CODEOWNERS" <<'CO' +* @petry-projects/backend +CO + run bash "$LINT_SCRIPT" + [ "$status" -ne 0 ] + [[ "$output" =~ "org-leads" ]] +} + +@test "lint: ignores comment lines and blank lines in CODEOWNERS" { + mkdir -p "$WORK_DIR/.github" + cat > "$WORK_DIR/.github/CODEOWNERS" <<'CO' +# This is a comment + +* @petry-projects/org-leads +CO + run bash "$LINT_SCRIPT" + [ "$status" -eq 0 ] +} From eab6d7e34ca49c903dc16abab9503bd27925ee1d Mon Sep 17 00:00:00 2001 From: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com> Date: Wed, 10 Jun 2026 14:41:23 +0000 Subject: [PATCH 2/2] chore: apply manual instructions [skip ci-relay] --- scripts/dev-lead-lint.sh | 5 ++++- tests/dev-lead/unit/test_dev_lead_lint.bats | 25 +++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/scripts/dev-lead-lint.sh b/scripts/dev-lead-lint.sh index 4f8baed15..3845d0c4f 100755 --- a/scripts/dev-lead-lint.sh +++ b/scripts/dev-lead-lint.sh @@ -106,9 +106,12 @@ if [ -n "$codeowners_file" ]; then echo " [lint] validating $codeowners_file..." codeowners_fail=0 while IFS= read -r line; do + line="${line%$'\r'}" [[ "$line" =~ ^[[:space:]]*$ ]] && continue [[ "$line" =~ ^[[:space:]]*# ]] && continue - first_owner=$(printf '%s\n' "$line" | awk '{print $2}') + clean_line="${line//\\ /}" + read -r -a parts <<< "$clean_line" + first_owner="${parts[1]:-}" if [ "$first_owner" != "@petry-projects/org-leads" ]; then echo "FAIL: $codeowners_file — @petry-projects/org-leads must be the first owner on line: $line" codeowners_fail=1 diff --git a/tests/dev-lead/unit/test_dev_lead_lint.bats b/tests/dev-lead/unit/test_dev_lead_lint.bats index c1c7dea21..50c776b09 100644 --- a/tests/dev-lead/unit/test_dev_lead_lint.bats +++ b/tests/dev-lead/unit/test_dev_lead_lint.bats @@ -252,3 +252,28 @@ CO run bash "$LINT_SCRIPT" [ "$status" -eq 0 ] } + +@test "lint: handles CRLF line endings in CODEOWNERS" { + mkdir -p "$WORK_DIR/.github" + printf '* @petry-projects/org-leads\r\ndocs/ @petry-projects/org-leads @petry-projects/docs\r\n' \ + > "$WORK_DIR/.github/CODEOWNERS" + run bash "$LINT_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "lint: handles escaped spaces in CODEOWNERS paths" { + mkdir -p "$WORK_DIR/.github" + printf 'path\\ with\\ spaces/ @petry-projects/org-leads @petry-projects/backend\n' \ + > "$WORK_DIR/.github/CODEOWNERS" + run bash "$LINT_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "lint: fails with CRLF when org-leads is not first" { + mkdir -p "$WORK_DIR/.github" + printf '* @petry-projects/backend @petry-projects/org-leads\r\n' \ + > "$WORK_DIR/.github/CODEOWNERS" + run bash "$LINT_SCRIPT" + [ "$status" -ne 0 ] + [[ "$output" =~ "org-leads" ]] +}