Update repo_health.yml

This commit is contained in:
2025-12-30 14:11:17 -06:00
parent 003efe8a4c
commit 1de2707db6

View File

@@ -1,730 +0,0 @@
# ============================================================================
# Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
#
# This file is part of a Moko Consulting project.
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
# FILE INFORMATION
# DEFGROUP: GitHub.Workflow
# INGROUP: MokoStandards.Validation
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /.github/workflows/repo_health.yml
# VERSION: 03.05.00
# BRIEF: Enforces Joomla repository guardrails by validating release configuration, required validation scripts, tooling availability, and core repository health artifacts.
# ============================================================================
name: Joomla Repo Health
concurrency:
group: repo-health-${{ github.repository }}-${{ github.ref }}
cancel-in-progress: true
defaults:
run:
shell: bash
on:
workflow_dispatch:
inputs:
profile:
description: "Which configuration profile to validate. release checks SFTP variables used by release_pipeline. scripts checks baseline script prerequisites. repo runs repository health only. all runs release, scripts, and repo health."
required: true
default: all
type: choice
options:
- all
- release
- scripts
- repo
pull_request:
paths:
- ".github/workflows/**"
- "scripts/**"
- "docs/**"
- "dev/**"
permissions:
contents: read
jobs:
access_check:
timeout-minutes: 10
name: Access control
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
allowed: ${{ steps.perm.outputs.allowed }}
permission: ${{ steps.perm.outputs.permission }}
steps:
- name: Check actor permission (admin only)
id: perm
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const username = context.actor;
const res = await github.rest.repos.getCollaboratorPermissionLevel({
owner,
repo,
username,
});
const permission = (res?.data?.permission || "unknown").toLowerCase();
const allowed = (permission === "admin");
core.setOutput("permission", permission);
core.setOutput("allowed", allowed ? "true" : "false");
const lines = [];
lines.push("### Access control");
lines.push("");
lines.push(`Actor: ${username}`);
lines.push(`Permission: ${permission}`);
lines.push(`Allowed: ${allowed}`);
lines.push("");
lines.push("Policy: This workflow runs only for users with admin permission on the repository.");
await core.summary.addRaw(lines.join("\n")).write();
- name: Deny execution when not permitted
if: ${{ steps.perm.outputs.allowed != 'true' }}
run: |
set -euo pipefail
echo "ERROR: Access denied. Actor must have admin permission to run this workflow." >> "${GITHUB_STEP_SUMMARY}"
exit 1
release_config:
timeout-minutes: 20
name: Release configuration
runs-on: ubuntu-latest
needs: [access_check]
if: ${{ needs.access_check.outputs.allowed == 'true' }}
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Guardrails - release secrets and vars
env:
PROFILE_RAW: "${{ github.event.inputs.profile }}"
FTP_HOST: "${{ secrets.FTP_HOST }}"
FTP_USER: "${{ secrets.FTP_USER }}"
FTP_KEY: "${{ secrets.FTP_KEY }}"
FTP_PASSWORD: "${{ secrets.FTP_PASSWORD }}"
FTP_PATH: "${{ secrets.FTP_PATH }}"
FTP_PROTOCOL: "${{ secrets.FTP_PROTOCOL }}"
FTP_PORT: "${{ secrets.FTP_PORT }}"
FTP_PATH_SUFFIX: "${{ vars.FTP_PATH_SUFFIX }}"
run: |
set -euo pipefail
profile="${PROFILE_RAW:-all}"
case "${profile}" in
all|release|scripts|repo) ;;
*)
echo "ERROR: Unknown profile: ${profile}" >> "${GITHUB_STEP_SUMMARY}"
exit 1
;;
esac
if [ "${profile}" = "scripts" ] || [ "${profile}" = "repo" ]; then
echo "Profile ${profile} selected. Skipping release configuration checks." >> "${GITHUB_STEP_SUMMARY}"
exit 0
fi
required=("FTP_HOST" "FTP_USER" "FTP_KEY" "FTP_PATH")
optional=("FTP_PASSWORD" "FTP_PROTOCOL" "FTP_PORT" "FTP_PATH_SUFFIX")
missing=()
missing_optional=()
for k in "${required[@]}"; do
v="${!k:-}"
[ -z "${v}" ] && missing+=("${k}")
done
for k in "${optional[@]}"; do
v="${!k:-}"
[ -z "${v}" ] && missing_optional+=("${k}")
done
proto="${FTP_PROTOCOL:-sftp}"
if [ -n "${FTP_PROTOCOL:-}" ] && [ "${proto}" != "sftp" ]; then
missing+=("FTP_PROTOCOL_INVALID")
fi
if [ "${#missing_optional[@]}" -gt 0 ]; then
echo "### Missing optional release configuration" >> "${GITHUB_STEP_SUMMARY}"
for m in "${missing_optional[@]}"; do echo "- ${m}" >> "${GITHUB_STEP_SUMMARY}"; done
fi
if [ "${#missing[@]}" -gt 0 ]; then
echo "### Missing required release configuration" >> "${GITHUB_STEP_SUMMARY}"
for m in "${missing[@]}"; do echo "- ${m}" >> "${GITHUB_STEP_SUMMARY}"; done
echo "ERROR: Guardrails failed. Missing required release configuration." >> "${GITHUB_STEP_SUMMARY}"
exit 1
fi
echo "### Guardrails: release configuration" >> "${GITHUB_STEP_SUMMARY}"
echo "All required release variables present." >> "${GITHUB_STEP_SUMMARY}"
- name: Guardrails - SFTP connectivity
env:
PROFILE_RAW: "${{ github.event.inputs.profile }}"
FTP_HOST: "${{ secrets.FTP_HOST }}"
FTP_USER: "${{ secrets.FTP_USER }}"
FTP_KEY: "${{ secrets.FTP_KEY }}"
FTP_PASSWORD: "${{ secrets.FTP_PASSWORD }}"
FTP_PORT: "${{ secrets.FTP_PORT }}"
run: |
set -euo pipefail
profile="${PROFILE_RAW:-all}"
case "${profile}" in
all|release|scripts|repo) ;;
*)
echo "ERROR: Unknown profile: ${profile}" >> "${GITHUB_STEP_SUMMARY}"
exit 1
;;
esac
if [ "${profile}" = "scripts" ] || [ "${profile}" = "repo" ]; then
echo "Profile ${profile} selected. Skipping SFTP connectivity check." >> "${GITHUB_STEP_SUMMARY}"
exit 0
fi
mkdir -p "$HOME/.ssh"
key_file="$HOME/.ssh/ci_sftp_key"
printf '%s\n' "${FTP_KEY}" > "${key_file}"
chmod 600 "${key_file}"
# If FTP_PASSWORD is present, treat it as the private key passphrase and decrypt the key in place.
# If FTP_PASSWORD is empty, the key must already be unencrypted.
if [ -n "${FTP_PASSWORD:-}" ]; then
first_line="$(head -n 1 "${key_file}" || true)"
if printf '%s' "${first_line}" | grep -q '^PuTTY-User-Key-File-'; then
echo "ERROR: FTP_KEY appears to be a PuTTY PPK. Provide an OpenSSH private key to use FTP_PASSWORD decryption." >> "${GITHUB_STEP_SUMMARY}"
exit 1
fi
ssh-keygen -p -P "${FTP_PASSWORD}" -N "" -f "${key_file}" >/dev/null
fi
port="${FTP_PORT:-22}"
echo "### SFTP connectivity test" >> "${GITHUB_STEP_SUMMARY}"
echo "Attempting non-destructive SFTP session (pwd only)." >> "${GITHUB_STEP_SUMMARY}"
printf 'pwd\nbye\n' | sftp -oBatchMode=yes -oStrictHostKeyChecking=no -P "${port}" -i "${key_file}" "${FTP_USER}@${FTP_HOST}" >/tmp/sftp_check.log 2>&1
sftp_rc=$?
if [ "${sftp_rc}" -eq 0 ]; then
echo "### SFTP connectivity result" >> "${GITHUB_STEP_SUMMARY}"
echo "Status: SUCCESS" >> "${GITHUB_STEP_SUMMARY}"
else
echo "### SFTP connectivity result" >> "${GITHUB_STEP_SUMMARY}"
echo "Status: FAILED (exit code ${sftp_rc})" >> "${GITHUB_STEP_SUMMARY}"
echo "" >> "${GITHUB_STEP_SUMMARY}"
echo "Last SFTP output:" >> "${GITHUB_STEP_SUMMARY}"
tail -n 10 /tmp/sftp_check.log >> "${GITHUB_STEP_SUMMARY}"
exit 1
fi
scripts_config:
name: Scripts and tooling
runs-on: ubuntu-latest
timeout-minutes: 15
needs: [access_check]
if: ${{ needs.access_check.outputs.allowed == 'true' }}
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Guardrails - scripts folder governance
env:
PROFILE_RAW: "${{ github.event.inputs.profile }}"
run: |
set -euo pipefail
profile="${PROFILE_RAW:-all}"
case "${profile}" in
all|release|scripts|repo) ;;
*)
echo "ERROR: Unknown profile: ${profile}" >> "${GITHUB_STEP_SUMMARY}"
exit 1
;;
esac
if [ "${profile}" = "release" ] || [ "${profile}" = "repo" ]; then
echo "Profile ${profile} selected. Skipping scripts checks." >> "${GITHUB_STEP_SUMMARY}"
exit 0
fi
required_script_dirs=(
"scripts/fix"
"scripts/lib"
"scripts/release"
"scripts/run"
"scripts/validate"
)
required_script_files=(
"scripts/validate/manifest.sh"
"scripts/validate/xml_wellformed.sh"
"scripts/validate/changelog.sh"
"scripts/validate/tabs.sh"
"scripts/validate/paths.sh"
"scripts/validate/version_alignment.sh"
"scripts/validate/language_structure.sh"
"scripts/validate/php_syntax.sh"
"scripts/validate/no_secrets.sh"
"scripts/validate/license_headers.sh"
)
missing_dirs=()
missing_files=()
for d in "${required_script_dirs[@]}"; do
[ ! -d "${d}" ] && missing_dirs+=("${d}/")
done
unapproved_dirs=()
while IFS= read -r d; do
case "${d}" in
scripts|scripts/fix|scripts/lib|scripts/release|scripts/run|scripts/validate) ;;
*) unapproved_dirs+=("${d}/") ;;
esac
done < <(find scripts -maxdepth 1 -mindepth 1 -type d 2>/dev/null | sed 's#^\./##')
for f in "${required_script_files[@]}"; do
[ ! -f "${f}" ] && missing_files+=("${f}")
done
legacy_glob_found=()
while IFS= read -r f; do
[ -n "${f}" ] && legacy_glob_found+=("${f}")
done < <(find scripts -maxdepth 1 -type f -name 'validate_*.sh' 2>/dev/null || true)
tools_to_install=()
command -v php >/dev/null 2>&1 || tools_to_install+=("php-cli")
command -v xmllint >/dev/null 2>&1 || tools_to_install+=("libxml2-utils")
command -v shellcheck >/dev/null 2>&1 || tools_to_install+=("shellcheck")
if [ "${#tools_to_install[@]}" -gt 0 ]; then
echo "Installing missing tools: ${tools_to_install[*]}" >> "${GITHUB_STEP_SUMMARY}"
sudo apt-get update -y
sudo apt-get install -y ${tools_to_install[*]}
fi
tool_status=()
command -v php >/dev/null 2>&1 && tool_status+=("php") || true
command -v xmllint >/dev/null 2>&1 && tool_status+=("xmllint") || true
command -v shellcheck >/dev/null 2>&1 && tool_status+=("shellcheck") || true
export MISSING_DIRS="$(printf '%s\\n' "${missing_dirs[@]:-}")"
export MISSING_FILES="$(printf '%s\\n' "${missing_files[@]:-}")"
export TOOLS="${tool_status[*]:-}"
report_json="$(python3 - <<'PY'
import json
import os
profile = os.environ.get('PROFILE_RAW') or 'all'
required_script_dirs = [
"scripts/fix",
"scripts/lib",
"scripts/release",
"scripts/run",
"scripts/validate",
]
required_script_files = [
"scripts/validate/manifest.sh",
"scripts/validate/xml_wellformed.sh",
"scripts/validate/changelog.sh",
"scripts/validate/tabs.sh",
"scripts/validate/paths.sh",
"scripts/validate/version_alignment.sh",
"scripts/validate/language_structure.sh",
"scripts/validate/php_syntax.sh",
"scripts/validate/no_secrets.sh",
"scripts/validate/license_headers.sh",
]
missing_dirs = os.environ.get('MISSING_DIRS','').split('
') if os.environ.get('MISSING_DIRS') else []
missing_files = os.environ.get('MISSING_FILES','').split('
') if os.environ.get('MISSING_FILES') else []
tools = os.environ.get('TOOLS','').split() if os.environ.get('TOOLS') else []
out = {
"profile": profile,
"checked": {
"required_script_dirs": required_script_dirs,
"required_script_files": required_script_files,
},
"missing_dirs": [x for x in missing_dirs if x],
"missing_files": [x for x in missing_files if x],
"tools_available": tools,
}
print(json.dumps(out, indent=2))
PY
)"
{
echo "### Guardrails: scripts and tooling"
echo "Tools available: ${tool_status[*]:-none}"
echo ""
echo "### Guardrails report (JSON)"
echo "```json"
echo "${report_json}"
echo "```"
} >> "${GITHUB_STEP_SUMMARY}"
if [ "${#missing_dirs[@]}" -gt 0 ]; then
echo "### Missing required script directories" >> "${GITHUB_STEP_SUMMARY}"
for m in "${missing_dirs[@]}"; do echo "- ${m}" >> "${GITHUB_STEP_SUMMARY}"; done
echo "ERROR: Guardrails failed. Missing required script directories." >> "${GITHUB_STEP_SUMMARY}"
exit 1
fi
if [ "${#unapproved_dirs[@]}" -gt 0 ]; then
echo "### Unapproved script directories detected" >> "${GITHUB_STEP_SUMMARY}"
for m in "${unapproved_dirs[@]}"; do echo "- ${m}" >> "${GITHUB_STEP_SUMMARY}"; done
echo "ERROR: Guardrails failed. Only fix, lib, release, run, validate directories are allowed under scripts/." >> "${GITHUB_STEP_SUMMARY}"
exit 1
fi
if [ "${#missing_files[@]}" -gt 0 ]; then
echo "### Missing script files" >> "${GITHUB_STEP_SUMMARY}"
for m in "${missing_files[@]}"; do echo "- ${m}" >> "${GITHUB_STEP_SUMMARY}"; done
echo "ERROR: Guardrails failed. Missing required script files." >> "${GITHUB_STEP_SUMMARY}"
exit 1
fi
if [ "${#legacy_glob_found[@]}" -gt 0 ]; then
echo "### Legacy validate_* scripts detected at scripts/ root (disallowed)" >> "${GITHUB_STEP_SUMMARY}"
for m in "${legacy_glob_found[@]}"; do echo "- ${m}" >> "${GITHUB_STEP_SUMMARY}"; done
echo "ERROR: Guardrails failed. Move scripts into scripts/validate/ with approved filenames." >> "${GITHUB_STEP_SUMMARY}"
exit 1
fi
non_exec=()
while IFS= read -r f; do
[ -n "${f}" ] && non_exec+=("${f}")
done < <(find scripts -type f -name '*.sh' ! -perm -u=x 2>/dev/null || true)
if [ "${#non_exec[@]}" -gt 0 ]; then
echo "### Non-executable shell scripts detected" >> "${GITHUB_STEP_SUMMARY}"
for m in "${non_exec[@]}"; do echo "- ${m}" >> "${GITHUB_STEP_SUMMARY}"; done
echo "ERROR: Guardrails failed. All scripts/**/*.sh must be executable." >> "${GITHUB_STEP_SUMMARY}"
exit 1
fi
sh_files="$(find scripts -type f -name '*.sh' 2>/dev/null || true)"
if [ -z "${sh_files}" ]; then
echo "No shell scripts found under scripts/." >> "${GITHUB_STEP_SUMMARY}"
echo "Shell quality gate skipped." >> "${GITHUB_STEP_SUMMARY}"
exit 0
fi
while IFS= read -r f; do
[ -z "${f}" ] && continue
bash -n "${f}"
done <<< "${sh_files}"
shellcheck -x ${sh_files}
echo "Shell quality gate passed." >> "${GITHUB_STEP_SUMMARY}"
{
echo "### Guardrails: scripts and tooling"
echo "Tools available: ${tool_status[*]:-none}"
echo ""
echo "### Guardrails report (JSON)"
echo "```json"
echo "${report_json}"
echo "```"
} >> "${GITHUB_STEP_SUMMARY}"
if [ "${#missing_dirs[@]}" -gt 0 ]; then
echo "### Missing required script directories" >> "${GITHUB_STEP_SUMMARY}"
for m in "${missing_dirs[@]}"; do echo "- ${m}" >> "${GITHUB_STEP_SUMMARY}"; done
echo "ERROR: Guardrails failed. Missing required script directories." >> "${GITHUB_STEP_SUMMARY}"
exit 1
fi
if [ "${#unapproved_dirs[@]}" -gt 0 ]; then
echo "### Unapproved script directories detected" >> "${GITHUB_STEP_SUMMARY}"
for m in "${unapproved_dirs[@]}"; do echo "- ${m}" >> "${GITHUB_STEP_SUMMARY}"; done
echo "ERROR: Guardrails failed. Only fix, lib, release, run, validate directories are allowed under scripts/." >> "${GITHUB_STEP_SUMMARY}"
exit 1
fi
if [ "${#missing_files[@]}" -gt 0 ]; then
echo "### Missing script files" >> "${GITHUB_STEP_SUMMARY}"
for m in "${missing_files[@]}"; do echo "- ${m}" >> "${GITHUB_STEP_SUMMARY}"; done
echo "ERROR: Guardrails failed. Missing required script files." >> "${GITHUB_STEP_SUMMARY}"
exit 1
fi
if [ "${#legacy_glob_found[@]}" -gt 0 ]; then
echo "### Legacy validate_* scripts detected at scripts/ root (disallowed)" >> "${GITHUB_STEP_SUMMARY}"
for m in "${legacy_glob_found[@]}"; do echo "- ${m}" >> "${GITHUB_STEP_SUMMARY}"; done
echo "ERROR: Guardrails failed. Move scripts into scripts/validate/ with approved filenames." >> "${GITHUB_STEP_SUMMARY}"
exit 1
fi
non_exec=()
while IFS= read -r f; do
[ -n "${f}" ] && non_exec+=("${f}")
done < <(find scripts -type f -name '*.sh' ! -perm -u=x 2>/dev/null || true)
if [ "${#non_exec[@]}" -gt 0 ]; then
echo "### Non-executable shell scripts detected" >> "${GITHUB_STEP_SUMMARY}"
for m in "${non_exec[@]}"; do echo "- ${m}" >> "${GITHUB_STEP_SUMMARY}"; done
echo "ERROR: Guardrails failed. All scripts/**/*.sh must be executable." >> "${GITHUB_STEP_SUMMARY}"
exit 1
fi
sh_files="$(find scripts -type f -name '*.sh' 2>/dev/null || true)"
if [ -z "${sh_files}" ]; then
echo "No shell scripts found under scripts/." >> "${GITHUB_STEP_SUMMARY}"
echo "Shell quality gate skipped." >> "${GITHUB_STEP_SUMMARY}"
exit 0
fi
while IFS= read -r f; do
[ -z "${f}" ] && continue
bash -n "${f}"
done <<< "${sh_files}"
shellcheck -x ${sh_files}
echo "Shell quality gate passed." >> "${GITHUB_STEP_SUMMARY}"
repo_health:
name: Repository health
runs-on: ubuntu-latest
timeout-minutes: 15
needs: [access_check]
if: ${{ needs.access_check.outputs.allowed == 'true' }}
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Repo health checks
env:
PROFILE_RAW: "${{ github.event.inputs.profile }}"
run: |
set -euo pipefail
profile="${PROFILE_RAW:-all}"
case "${profile}" in
all|release|scripts|repo) ;;
*)
echo "ERROR: Unknown profile: ${profile}" >> "${GITHUB_STEP_SUMMARY}"
exit 1
;;
esac
required_files=(
"README.md"
"LICENSE"
"CHANGELOG.md"
"CONTRIBUTING.md"
"CODE_OF_CONDUCT.md"
"TODO.md"
"docs/docs-index.md"
)
optional_files=(
"SECURITY.md"
"GOVERNANCE.md"
".editorconfig"
".gitattributes"
".gitignore"
)
required_paths=(
".github/workflows"
"scripts"
"docs"
"dev"
)
missing_required=()
missing_optional=()
for f in "${required_files[@]}"; do
[ ! -f "${f}" ] && missing_required+=("${f}")
done
for f in "${optional_files[@]}"; do
[ ! -f "${f}" ] && missing_optional+=("${f}")
done
for p in "${required_paths[@]}"; do
[ ! -d "${p}" ] && missing_required+=("${p}/")
done
if [ -d "src" ]; then
missing_required+=("src/ (disallowed, use dev/ only)")
fi
git fetch origin --prune
dev_paths=()
dev_branches=()
while IFS= read -r b; do
name="${b#origin/}"
if [ "${name}" = "dev" ]; then
dev_branches+=("${name}")
else
dev_paths+=("${name}")
fi
done < <(git branch -r --list "origin/dev*" | sed 's/^ *//')
if [ "${#dev_paths[@]}" -eq 0 ]; then
missing_required+=("dev/* branch (e.g. dev/01.00.00)")
fi
if [ "${#dev_branches[@]}" -gt 0 ]; then
missing_required+=("invalid branch 'dev' (must be dev/<version>)")
fi
content_warnings=()
if [ -f "CHANGELOG.md" ] && ! grep -Eq '^# Changelog' CHANGELOG.md; then
content_warnings+=("CHANGELOG.md missing '# Changelog' header")
fi
if [ -f "LICENSE" ] && ! grep -qiE 'GNU GENERAL PUBLIC LICENSE|GPL' LICENSE; then
content_warnings+=("LICENSE does not look like a GPL text")
fi
if [ -f "README.md" ] && ! grep -qiE 'moko|Moko' README.md; then
content_warnings+=("README.md missing expected brand keyword")
fi
export MISSING_REQUIRED="$(printf '%s\\n' "${missing_required[@]:-}")"
export MISSING_OPTIONAL="$(printf '%s\\n' "${missing_optional[@]:-}")"
export CONTENT_WARNINGS="$(printf '%s\\n' "${content_warnings[@]:-}")"
report_json="$(python3 - <<'PY'
import json
import os
profile = os.environ.get('PROFILE_RAW') or 'all'
required_files = ["README.md","LICENSE","CHANGELOG.md","CONTRIBUTING.md","CODE_OF_CONDUCT.md","TODO.md","docs/docs-index.md"]
optional_files = ["SECURITY.md","GOVERNANCE.md",".editorconfig",".gitattributes",".gitignore"]
required_paths = [".github/workflows","scripts","docs","dev"]
missing_required = os.environ.get('MISSING_REQUIRED','').split('
') if os.environ.get('MISSING_REQUIRED') else []
missing_optional = os.environ.get('MISSING_OPTIONAL','').split('
') if os.environ.get('MISSING_OPTIONAL') else []
content_warnings = os.environ.get('CONTENT_WARNINGS','').split('
') if os.environ.get('CONTENT_WARNINGS') else []
out = {
"profile": profile,
"checked": {
"required_files": required_files,
"optional_files": optional_files,
"required_paths": required_paths,
},
"missing_required": [x for x in missing_required if x],
"missing_optional": [x for x in missing_optional if x],
"content_warnings": [x for x in content_warnings if x],
}
print(json.dumps(out, indent=2))
PY
)"
{
echo "### Guardrails: repository health"
echo ""
echo "### Guardrails report (JSON)"
echo "```json"
echo "${report_json}"
echo "```"
} >> "${GITHUB_STEP_SUMMARY}"
if [ "${#missing_required[@]}" -gt 0 ]; then
echo "### Missing required repo artifacts" >> "${GITHUB_STEP_SUMMARY}"
for m in "${missing_required[@]}"; do echo "- ${m}" >> "${GITHUB_STEP_SUMMARY}"; done
echo "ERROR: Guardrails failed. Missing required repository artifacts." >> "${GITHUB_STEP_SUMMARY}"
exit 1
fi
if [ "${#missing_optional[@]}" -gt 0 ]; then
echo "### Missing optional repo artifacts" >> "${GITHUB_STEP_SUMMARY}"
for m in "${missing_optional[@]}"; do echo "- ${m}" >> "${GITHUB_STEP_SUMMARY}"; done
fi
if [ "${#content_warnings[@]}" -gt 0 ]; then
echo "### Repo content warnings" >> "${GITHUB_STEP_SUMMARY}"
for m in "${content_warnings[@]}"; do echo "- ${m}" >> "${GITHUB_STEP_SUMMARY}"; done
fi
{
echo "### Guardrails: repository health"
echo ""
echo "### Guardrails report (JSON)"
echo "```json"
echo "${report_json}"
echo "```"
} >> "${GITHUB_STEP_SUMMARY}"
if [ "${#missing_required[@]}" -gt 0 ]; then
echo "### Missing required repo artifacts" >> "${GITHUB_STEP_SUMMARY}"
for m in "${missing_required[@]}"; do echo "- ${m}" >> "${GITHUB_STEP_SUMMARY}"; done
echo "ERROR: Guardrails failed. Missing required repository artifacts." >> "${GITHUB_STEP_SUMMARY}"
exit 1
fi
if [ "${#missing_optional[@]}" -gt 0 ]; then
echo "### Missing optional repo artifacts" >> "${GITHUB_STEP_SUMMARY}"
for m in "${missing_optional[@]}"; do echo "- ${m}" >> "${GITHUB_STEP_SUMMARY}"; done
fi
if [ "${#content_warnings[@]}" -gt 0 ]; then
echo "### Repo content warnings" >> "${GITHUB_STEP_SUMMARY}"
for m in "${content_warnings[@]}"; do echo "- ${m}" >> "${GITHUB_STEP_SUMMARY}"; done
fi