Update repo_health.yml

This commit is contained in:
2025-12-30 17:47:19 -06:00
parent 47f26ed3ff
commit f2b3add520

View File

@@ -76,6 +76,9 @@ env:
REPO_DISALLOWED_DIRS: src
REPO_DISALLOWED_FILES: TODO.md,todo.md
# Extended checks toggles
EXTENDED_CHECKS: "true"
# Operational toggles
SFTP_VERBOSE: "false"
@@ -167,7 +170,13 @@ jobs:
esac
if [ "${profile}" = 'scripts' ] || [ "${profile}" = 'repo' ]; then
printf '%s\n' "Profile ${profile} selected. Skipping release configuration checks." >> "${GITHUB_STEP_SUMMARY}"
{
printf '%s\n' '### Release configuration'
printf '%s\n' "Profile: ${profile}"
printf '%s\n' 'Status: SKIPPED'
printf '%s\n' 'Reason: profile excludes release validation'
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
exit 0
fi
@@ -197,12 +206,39 @@ jobs:
[ "${ok}" = false ] && missing+=("FTP_PROTOCOL_INVALID")
fi
target_path="${FTP_PATH:-}"
if [ -n "${FTP_PATH_SUFFIX:-}" ]; then
target_path="${target_path%/}/${FTP_PATH_SUFFIX#/}"
fi
auth_method='none'
[ -n "${FTP_KEY:-}" ] && auth_method='key'
[ -z "${FTP_KEY:-}" ] && [ -n "${FTP_PASSWORD:-}" ] && auth_method='password'
{
printf '%s\n' '### Release configuration'
printf '%s\n' "Profile: ${profile}"
printf '%s\n' '| Control | Value |'
printf '%s\n' '|---|---|'
printf '%s\n' "| Protocol | ${proto} |"
printf '%s\n' "| Port | ${FTP_PORT:-22} |"
printf '%s\n' "| Auth | ${auth_method} |"
printf '%s\n' "| Path (resolved) | ${target_path} |"
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
if [ "${#missing_optional[@]}" -gt 0 ]; then
{
printf '%s\n' '### Missing optional release configuration'
for m in "${missing_optional[@]}"; do printf '%s\n' "- ${m}"; done
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
else
{
printf '%s\n' '### Optional release configuration'
printf '%s\n' 'None missing.'
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
fi
if [ "${#missing[@]}" -gt 0 ]; then
@@ -215,7 +251,8 @@ jobs:
fi
{
printf '%s\n' '### Guardrails release configuration'
printf '%s\n' '### Release configuration result'
printf '%s\n' 'Status: OK'
printf '%s\n' 'All required release variables present.'
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
@@ -243,7 +280,13 @@ jobs:
esac
if [ "${profile}" = 'scripts' ] || [ "${profile}" = 'repo' ]; then
printf '%s\n' "Profile ${profile} selected. Skipping SFTP connectivity check." >> "${GITHUB_STEP_SUMMARY}"
{
printf '%s\n' '### SFTP connectivity'
printf '%s\n' "Profile: ${profile}"
printf '%s\n' 'Status: SKIPPED'
printf '%s\n' 'Reason: profile excludes release validation'
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
exit 0
fi
@@ -258,10 +301,22 @@ jobs:
sftp_v_opt=()
[ "${sftp_verbose}" = 'true' ] && sftp_v_opt=(-vv)
auth_method='none'
if [ -n "${FTP_KEY:-}" ]; then
auth_method='key'
elif [ -n "${FTP_PASSWORD:-}" ]; then
auth_method='password'
fi
{
printf '%s\n' '### SFTP connectivity test'
printf '%s\n' "Target path: ${target_path}"
printf '%s\n' 'Attempting non-destructive SFTP session'
printf '%s\n' '### SFTP connectivity'
printf '%s\n' '| Control | Value |'
printf '%s\n' '|---|---|'
printf '%s\n' "| Host | ${FTP_HOST} |"
printf '%s\n' "| User | ${FTP_USER} |"
printf '%s\n' "| Port | ${port} |"
printf '%s\n' "| Auth | ${auth_method} |"
printf '%s\n' "| Path (resolved) | ${target_path} |"
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
@@ -277,7 +332,12 @@ jobs:
if [ -n "${FTP_PASSWORD:-}" ]; then
first_line="$(head -n 1 "${key_file}" || true)"
if printf '%s\n' "${first_line}" | grep -q '^PuTTY-User-Key-File-'; then
printf '%s\n' 'ERROR: FTP_KEY appears to be a PuTTY PPK. Provide an OpenSSH private key.' >> "${GITHUB_STEP_SUMMARY}"
{
printf '%s\n' '### SFTP connectivity result'
printf '%s\n' 'Status: FAILED'
printf '%s\n' 'Reason: FTP_KEY appears to be a PuTTY PPK. Provide an OpenSSH private key.'
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
exit 1
fi
ssh-keygen -p -P "${FTP_PASSWORD}" -N '' -f "${key_file}" >/dev/null
@@ -290,24 +350,31 @@ jobs:
printf '%s' "${sftp_cmds}" | sshpass -p "${FTP_PASSWORD}" sftp "${sftp_v_opt[@]}" -oBatchMode=no -oStrictHostKeyChecking=no -P "${port}" "${FTP_USER}@${FTP_HOST}" >/tmp/sftp_check.log 2>&1
sftp_rc=$?
else
printf '%s\n' 'ERROR: No FTP_KEY or FTP_PASSWORD provided for SFTP authentication.' >> "${GITHUB_STEP_SUMMARY}"
{
printf '%s\n' '### SFTP connectivity result'
printf '%s\n' 'Status: FAILED'
printf '%s\n' 'Reason: No FTP_KEY or FTP_PASSWORD provided for SFTP authentication.'
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
exit 1
fi
set -e
printf '%s\n' '### SFTP connectivity result' >> "${GITHUB_STEP_SUMMARY}"
if [ "${sftp_rc}" -eq 0 ]; then
printf '%s\n' 'Status: SUCCESS' >> "${GITHUB_STEP_SUMMARY}"
exit 0
fi
{
printf '%s\n' '### SFTP connectivity result'
if [ "${sftp_rc}" -eq 0 ]; then
printf '%s\n' 'Status: SUCCESS'
printf '%s\n' 'Validated host connectivity and remote path access.'
else
printf '%s\n' "Status: FAILED (exit code ${sftp_rc})"
printf '\n'
printf '%s\n' 'Last SFTP output'
tail -n 60 /tmp/sftp_check.log || true
fi
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
exit 1
[ "${sftp_rc}" -eq 0 ] || exit 1
scripts_governance:
name: Scripts governance
@@ -340,14 +407,21 @@ jobs:
esac
if [ "${profile}" = 'release' ] || [ "${profile}" = 'repo' ]; then
printf '%s\n' "Profile ${profile} selected. Skipping scripts governance." >> "${GITHUB_STEP_SUMMARY}"
{
printf '%s\n' '### Scripts governance'
printf '%s\n' "Profile: ${profile}"
printf '%s\n' 'Status: SKIPPED'
printf '%s\n' 'Reason: profile excludes scripts governance'
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
exit 0
fi
if [ ! -d scripts ]; then
{
printf '%s\n' '### Scripts governance'
printf '%s\n' 'Warning: scripts/ directory not present. No scripts governance enforced.'
printf '%s\n' 'Status: OK (advisory)'
printf '%s\n' 'scripts/ directory not present. No scripts governance enforced.'
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
exit 0
@@ -375,34 +449,45 @@ jobs:
{
printf '%s\n' '### Scripts governance'
printf '%s\n' "Profile: ${profile}"
printf '%s\n' '| Area | Status | Notes |'
printf '%s\n' '|---|---|---|'
if [ "${#missing_dirs[@]}" -gt 0 ]; then
printf '%s\n' '| Required directories | Warning | Missing required subfolders |'
else
printf '%s\n' '| Required directories | OK | All required subfolders present |'
fi
if [ "${#unapproved_dirs[@]}" -gt 0 ]; then
printf '%s\n' '| Directory policy | Warning | Unapproved directories detected |'
else
printf '%s\n' '| Directory policy | OK | No unapproved directories |'
fi
printf '%s\n' '| Enforcement mode | Advisory | scripts folder is optional |'
printf '\n'
if [ "${#missing_dirs[@]}" -gt 0 ]; then
printf '%s\n' 'Missing required script directories:'
for m in "${missing_dirs[@]}"; do printf '%s\n' "- ${m}"; done
printf '\n'
else
printf '%s\n' 'Missing required script directories: none.'
printf '\n'
fi
if [ "${#unapproved_dirs[@]}" -gt 0 ]; then
printf '%s\n' 'Unapproved script directories detected:'
for m in "${unapproved_dirs[@]}"; do printf '%s\n' "- ${m}"; done
printf '\n'
else
printf '%s\n' 'Unapproved script directories detected: none.'
printf '\n'
fi
printf '%s\n' '| Area | Status | Notes |'
printf '%s\n' '|------|--------|-------|'
if [ "${#missing_dirs[@]}" -gt 0 ]; then
printf '%s\n' '| Required directories | Warning | Missing required subfolders |'
else
printf '%s\n' '| Required directories | OK | All required subfolders present |'
fi
if [ "${#unapproved_dirs[@]}" -gt 0 ]; then
printf '%s\n' '| Directory policy | Warning | Unapproved directories detected |'
else
printf '%s\n' '| Directory policy | OK | No unapproved directories |'
fi
printf '%s\n' '| Enforcement mode | Advisory | scripts folder is optional |'
printf '\n'
printf '%s\n' 'Scripts governance completed in advisory mode.'
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
repo_health:
@@ -410,7 +495,7 @@ jobs:
needs: access_check
if: ${{ needs.access_check.outputs.allowed == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 15
timeout-minutes: 20
permissions:
contents: read
@@ -436,7 +521,13 @@ jobs:
esac
if [ "${profile}" = 'release' ] || [ "${profile}" = 'scripts' ]; then
printf '%s\n' "Profile ${profile} selected. Skipping repository health checks." >> "${GITHUB_STEP_SUMMARY}"
{
printf '%s\n' '### Repository health'
printf '%s\n' "Profile: ${profile}"
printf '%s\n' 'Status: SKIPPED'
printf '%s\n' 'Reason: profile excludes repository health'
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
exit 0
fi
@@ -498,6 +589,10 @@ jobs:
content_warnings+=("CHANGELOG.md missing '# Changelog' header")
fi
if [ -f 'CHANGELOG.md' ] && grep -Eq '^[# ]*Unreleased' CHANGELOG.md; then
content_warnings+=("CHANGELOG.md contains Unreleased section (review release readiness)")
fi
if [ -f 'LICENSE' ] && ! grep -qiE 'GNU GENERAL PUBLIC LICENSE|GPL' LICENSE; then
content_warnings+=("LICENSE does not look like a GPL text")
fi
@@ -533,8 +628,15 @@ jobs:
)"
{
printf '%s\n' '### Guardrails repository health'
printf '%s\n' '### Repository health'
printf '%s\n' "Profile: ${profile}"
printf '%s\n' '| Metric | Value |'
printf '%s\n' '|---|---|'
printf '%s\n' "| Missing required | ${#missing_required[@]} |"
printf '%s\n' "| Missing optional | ${#missing_optional[@]} |"
printf '%s\n' "| Content warnings | ${#content_warnings[@]} |"
printf '\n'
printf '%s\n' '### Guardrails report (JSON)'
printf '%s\n' '```json'
printf '%s\n' "${report_json}"
@@ -547,6 +649,7 @@ jobs:
printf '%s\n' '### Missing required repo artifacts'
for m in "${missing_required[@]}"; do printf '%s\n' "- ${m}"; done
printf '%s\n' 'ERROR: Guardrails failed. Missing required repository artifacts.'
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
exit 1
fi
@@ -567,6 +670,157 @@ jobs:
} >> "${GITHUB_STEP_SUMMARY}"
fi
printf '%s\n' 'Repository health guardrails passed.' >> "${GITHUB_STEP_SUMMARY}"
extended_enabled="${EXTENDED_CHECKS:-true}"
extended_findings=()
# EOF
if [ "${extended_enabled}" = 'true' ]; then
# CODEOWNERS presence
if [ -f '.github/CODEOWNERS' ] || [ -f 'CODEOWNERS' ] || [ -f 'docs/CODEOWNERS' ]; then
:
else
extended_findings+=("CODEOWNERS not found (.github/CODEOWNERS preferred)")
fi
# Workflow pinning advisory: flag uses @main/@master
if ls .github/workflows/*.yml >/dev/null 2>&1; then
bad_refs="$(grep -RIn --include='*.yml' --include='*.yaml' -E '^[[:space:]]*uses:[[:space:]]*[^#]+@(main|master)\b' .github/workflows 2>/dev/null || true)"
if [ -n "${bad_refs}" ]; then
extended_findings+=("Workflows reference actions @main/@master (pin versions): see log excerpt")
{
printf '%s\n' '### Workflow pinning advisory'
printf '%s\n' 'Found uses: entries pinned to main/master:'
printf '%s\n' '```'
printf '%s\n' "${bad_refs}"
printf '%s\n' '```'
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
fi
fi
# Docs index link integrity (docs/docs-index.md)
if [ -f 'docs/docs-index.md' ]; then
missing_links="$(python3 - <<'PY'
import os
import re
idx = 'docs/docs-index.md'
base = os.getcwd()
bad = []
pat = re.compile(r'\[[^\]]+\]\(([^)]+)\)')
with open(idx, 'r', encoding='utf-8') as f:
for line in f:
for m in pat.findall(line):
link = m.strip()
if link.startswith('http://') or link.startswith('https://') or link.startswith('#') or link.startswith('mailto:'):
continue
if link.startswith('/'):
rel = link.lstrip('/')
else:
rel = os.path.normpath(os.path.join(os.path.dirname(idx), link))
rel = rel.split('#', 1)[0]
rel = rel.split('?', 1)[0]
if not rel:
continue
p = os.path.join(base, rel)
if not os.path.exists(p):
bad.append(rel)
print('\n'.join(sorted(set(bad))))
PY
)"
if [ -n "${missing_links}" ]; then
extended_findings+=("docs/docs-index.md contains broken relative links")
{
printf '%s\n' '### Docs index link integrity'
printf '%s\n' 'Broken relative links:'
while IFS= read -r l; do [ -n "${l}" ] && printf '%s\n' "- ${l}"; done <<< "${missing_links}"
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
fi
fi
# ShellCheck advisory
if [ -d 'scripts' ]; then
if ! command -v shellcheck >/dev/null 2>&1; then
sudo apt-get update -qq
sudo apt-get install -y shellcheck >/dev/null
fi
sc_out="$(shellcheck -S warning -x scripts/**/*.sh 2>/dev/null || true)"
if [ -n "${sc_out}" ]; then
extended_findings+=("ShellCheck warnings detected (advisory)")
{
printf '%s\n' '### ShellCheck (advisory)'
printf '%s\n' '```'
printf '%s\n' "${sc_out}" | head -n 200
printf '%s\n' '```'
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
fi
fi
# SPDX header advisory for common source types
spdx_missing=()
while IFS= read -r f; do
[ -z "${f}" ] && continue
if ! head -n 40 "${f}" | grep -q 'SPDX-License-Identifier:'; then
spdx_missing+=("${f}")
fi
done < <(git ls-files '*.sh' '*.php' '*.js' '*.ts' '*.css' '*.xml' '*.yml' '*.yaml' 2>/dev/null || true)
if [ "${#spdx_missing[@]}" -gt 0 ]; then
extended_findings+=("SPDX header missing in some tracked files (advisory)")
{
printf '%s\n' '### SPDX header advisory'
printf '%s\n' 'Files missing SPDX-License-Identifier (first 40 lines scan):'
for f in "${spdx_missing[@]}"; do printf '%s\n' "- ${f}"; done
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
fi
# Git hygiene advisory: branches older than 180 days (remote)
stale_cutoff_days=180
stale_branches="$(git for-each-ref --format='%(refname:short) %(committerdate:unix)' refs/remotes/origin 2>/dev/null | awk -v now="$(date +%s)" -v days="${stale_cutoff_days}" '{if (now-$2 > days*86400) print $1}' | sed 's#^origin/##' | grep -v '^HEAD$' | head -n 50 || true)"
if [ -n "${stale_branches}" ]; then
extended_findings+=("Stale remote branches detected (advisory)")
{
printf '%s\n' '### Git hygiene advisory'
printf '%s\n' "Branches with last commit older than ${stale_cutoff_days} days (sample up to 50):"
while IFS= read -r b; do [ -n "${b}" ] && printf '%s\n' "- ${b}"; done <<< "${stale_branches}"
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
fi
fi
{
printf '%s\n' '### Guardrails coverage matrix'
printf '%s\n' '| Domain | Status | Notes |'
printf '%s\n' '|---|---|---|'
printf '%s\n' '| Access control | OK | Admin-only execution gate |'
printf '%s\n' '| Release configuration | OK | Variable presence and protocol gate |'
printf '%s\n' '| SFTP connectivity | OK | Connectivity plus remote path resolution |'
printf '%s\n' '| Scripts governance | OK | Directory policy and advisory reporting |'
printf '%s\n' '| Repo required artifacts | OK | Required, optional, disallowed enforcement |'
printf '%s\n' '| Repo content heuristics | OK | Brand, license, changelog structure |'
if [ "${extended_enabled}" = 'true' ]; then
if [ "${#extended_findings[@]}" -gt 0 ]; then
printf '%s\n' '| Extended checks | Warning | See extended findings below |'
else
printf '%s\n' '| Extended checks | OK | No findings |'
fi
else
printf '%s\n' '| Extended checks | SKIPPED | EXTENDED_CHECKS disabled |'
fi
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
if [ "${extended_enabled}" = 'true' ] && [ "${#extended_findings[@]}" -gt 0 ]; then
{
printf '%s\n' '### Extended findings (advisory)'
for f in "${extended_findings[@]}"; do printf '%s\n' "- ${f}"; done
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
fi
printf '%s\n' 'Repository health guardrails passed.' >> "${GITHUB_STEP_SUMMARY}"