feat(ci): add 08-maintenance_code-maturity workflow for analyze_code_maturity.py #1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Code Maturity Maintenance | |
| # Runs .github/scripts/analyze_code_maturity.py in one of two modes: | |
| # | |
| # check-only (default) | |
| # Executes the script with --no-headers so no source files are modified. | |
| # Generates docs/code_maturity_report.md to /tmp and compares it against | |
| # the committed version to surface drift. Badge and version-tracking | |
| # updates are written to the workspace but never committed. | |
| # Triggered by: pull_request on the script / workflow file, schedule, and | |
| # workflow_dispatch with update_headers=false (the default). | |
| # | |
| # rewrite (manual only, opt-in) | |
| # Executes the script without --no-headers, writing auto-generated headers | |
| # into every supported source file and updating docs/code_maturity_report.md, | |
| # .github/version_tracking.json, and .github/badges/*.json. | |
| # All changed artefacts are uploaded as workflow artefacts so maintainers | |
| # can review and commit them manually. | |
| # Triggered by: workflow_dispatch with update_headers=true. | |
| # | |
| # Governance note: | |
| # This workflow is classified as Maintenance (lane 08). It is NOT a required | |
| # PR gate. It must never be widened to trigger on broad src/**, include/**, | |
| # or tests/** path patterns. The script itself is responsible for scanning | |
| # those directories; this workflow only monitors changes to the script or its | |
| # direct outputs. | |
| # | |
| # Vorlage aus .github/no_workflows/: | |
| # 05-quality_build_disabled-stub-policy-ci.yml (Python-Guard-Muster) | |
| # Strukturell nächstverwandter deaktivierter Workflow; nicht reaktiviert, | |
| # sondern als eigenständiger neuer Workflow nach dem 08-Maintenance-Schema | |
| # der aktiven Workflows erstellt. | |
| on: | |
| pull_request: | |
| types: [opened, synchronize, reopened] | |
| branches: | |
| - develop | |
| - main | |
| paths: | |
| - '.github/scripts/analyze_code_maturity.py' | |
| - '.github/workflows/08-maintenance_code-maturity.yml' | |
| - '.github/version_tracking.json' | |
| - '.github/badges/**' | |
| schedule: | |
| - cron: '0 3 * * 1' # Weekly on Monday at 03:00 UTC (check-only) | |
| workflow_dispatch: | |
| inputs: | |
| update_headers: | |
| description: > | |
| Write auto-generated headers into source files (rewrite mode). | |
| When false (default), only the maturity report is generated and | |
| no source files are modified. | |
| type: boolean | |
| default: false | |
| fail_on_drift: | |
| description: > | |
| Fail when docs/code_maturity_report.md differs from the freshly | |
| generated report (check-only mode only; ignored when update_headers | |
| is true). | |
| type: boolean | |
| default: false | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| jobs: | |
| code-maturity: | |
| name: Code Maturity Analysis | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| steps: | |
| # ── 1. Checkout ──────────────────────────────────────────────────────── | |
| # Full history (fetch-depth: 0) is required in rewrite mode so that | |
| # get_file_commit_history() can retrieve per-file git log entries. | |
| # In check-only mode the script skips history extraction, but fetching | |
| # full history here is harmless and avoids conditional checkout logic. | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| # ── 2. Python ────────────────────────────────────────────────────────── | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| # ── 3. Determine run mode ────────────────────────────────────────────── | |
| - name: Determine run mode | |
| id: mode | |
| run: | | |
| UPDATE_HEADERS="${{ inputs.update_headers }}" | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${UPDATE_HEADERS}" = "true" ]; then | |
| echo "mode=rewrite" >> "$GITHUB_OUTPUT" | |
| echo "no_headers_flag=" >> "$GITHUB_OUTPUT" | |
| echo "report_path_flag=" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "mode=check-only" >> "$GITHUB_OUTPUT" | |
| echo "no_headers_flag=--no-headers" >> "$GITHUB_OUTPUT" | |
| echo "report_path_flag=--report-path /tmp/code_maturity_report.md" >> "$GITHUB_OUTPUT" | |
| fi | |
| # ── 4. Run analysis ──────────────────────────────────────────────────── | |
| - name: Run code maturity analysis (${{ steps.mode.outputs.mode }}) | |
| id: analyze | |
| run: | | |
| python .github/scripts/analyze_code_maturity.py \ | |
| --root . \ | |
| ${{ steps.mode.outputs.no_headers_flag }} \ | |
| ${{ steps.mode.outputs.report_path_flag }} \ | |
| 2>&1 | tee /tmp/maturity_run.log | |
| # Surface analyzed file count for the step summary | |
| FILE_COUNT=$(grep -oP 'Analyzed \K[0-9]+' /tmp/maturity_run.log | tail -1 || echo "n/a") | |
| echo "file_count=${FILE_COUNT}" >> "$GITHUB_OUTPUT" | |
| # ── 5. Drift detection (check-only mode) ────────────────────────────── | |
| - name: Detect report drift (check-only) | |
| if: steps.mode.outputs.mode == 'check-only' | |
| run: | | |
| REPORT_COMMITTED="docs/code_maturity_report.md" | |
| REPORT_GENERATED="/tmp/code_maturity_report.md" | |
| FAIL_ON_DRIFT="${{ inputs.fail_on_drift || 'false' }}" | |
| DRIFT=0 | |
| if [ ! -f "${REPORT_COMMITTED}" ]; then | |
| echo "::notice::docs/code_maturity_report.md does not exist in the repository yet." | |
| echo " Run the workflow with update_headers=false (default) via workflow_dispatch" | |
| echo " to generate the initial report, then commit it." | |
| elif ! diff -q "${REPORT_COMMITTED}" "${REPORT_GENERATED}" > /dev/null 2>&1; then | |
| echo "::warning::docs/code_maturity_report.md is out of date." | |
| echo " Diff:" | |
| diff "${REPORT_COMMITTED}" "${REPORT_GENERATED}" || true | |
| DRIFT=1 | |
| else | |
| echo "✅ docs/code_maturity_report.md is up to date." | |
| fi | |
| if [ "${FAIL_ON_DRIFT}" = "true" ] && [ "${DRIFT}" -ne 0 ]; then | |
| echo "::error::Drift detected and fail_on_drift=true — aborting." | |
| exit 1 | |
| fi | |
| # ── 6. Upload report artefact ────────────────────────────────────────── | |
| - name: Upload maturity report | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: code-maturity-report-${{ github.run_number }} | |
| path: | | |
| /tmp/code_maturity_report.md | |
| docs/code_maturity_report.md | |
| retention-days: 30 | |
| if-no-files-found: ignore | |
| # ── 7. Upload tracking + badge artefacts (rewrite mode only) ────────── | |
| - name: Upload version tracking and badges (rewrite) | |
| if: steps.mode.outputs.mode == 'rewrite' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: code-maturity-tracking-${{ github.run_number }} | |
| path: | | |
| .github/version_tracking.json | |
| .github/badges/*.json | |
| retention-days: 30 | |
| if-no-files-found: ignore | |
| # ── 8. Step summary ─────────────────────────────────────────────────── | |
| - name: Write job summary | |
| if: always() | |
| run: | | |
| { | |
| echo "## Code Maturity Maintenance" | |
| echo "" | |
| echo "| Parameter | Value |" | |
| echo "|-----------|-------|" | |
| echo "| **Script** | \`.github/scripts/analyze_code_maturity.py\` |" | |
| echo "| **Mode** | \`${{ steps.mode.outputs.mode }}\` |" | |
| echo "| **Event** | \`${{ github.event_name }}\` |" | |
| echo "| **Branch** | \`${{ github.ref_name }}\` |" | |
| echo "| **Analyzed files** | ${{ steps.analyze.outputs.file_count }} |" | |
| if [ "${{ steps.mode.outputs.mode }}" = "check-only" ]; then | |
| echo "| **Header rewrites** | disabled (\`--no-headers\`) |" | |
| echo "| **Fail on drift** | \`${{ inputs.fail_on_drift || 'false' }}\` |" | |
| else | |
| echo "| **Header rewrites** | enabled |" | |
| fi | |
| echo "" | |
| REPORT_FILE="" | |
| if [ "${{ steps.mode.outputs.mode }}" = "check-only" ] && [ -f /tmp/code_maturity_report.md ]; then | |
| REPORT_FILE=/tmp/code_maturity_report.md | |
| elif [ -f docs/code_maturity_report.md ]; then | |
| REPORT_FILE=docs/code_maturity_report.md | |
| fi | |
| if [ -n "${REPORT_FILE}" ]; then | |
| echo "**Report excerpt:**" | |
| echo "" | |
| echo '```' | |
| head -35 "${REPORT_FILE}" | |
| echo '```' | |
| fi | |
| } >> "$GITHUB_STEP_SUMMARY" |