Maintenance: Docs #245
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: "Maintenance: Docs" | |
| # Rechenaufwand-Score: R=2 (K=2, L=2, N=2) | last-calibrated: 2026-08-25 | |
| # Trigger policy: repo framework score calibration for workflow cost controls. | |
| # Consolidated docs-maintenance workflow. | |
| # Replaces: 08-maintenance_ai-context-wiki-sync.yml, | |
| # 08-maintenance_code-maturity.yml, | |
| # 08-maintenance_docs-orphan-check.yml, | |
| # 08-maintenance_root-docs-hygiene.yml, | |
| # 08-maintenance_src-include-docs-align.yml | |
| on: | |
| # push trigger retained for docs/ai_context/ai_working changes merged to develop/community. | |
| # pull_request trigger removed — doc hygiene checks run on schedule and do not need | |
| # to block PRs (they are non-blocking maintenance tasks, not PR gate requirements). | |
| push: | |
| branches: [develop, community] | |
| paths: | |
| - 'ai_context/**' | |
| - 'ai_working/**' | |
| - 'INDEX.md' | |
| - 'LOG.md' | |
| - 'AI_WIKI_INTEGRATION_PLAYBOOK.md' | |
| - 'scripts/ai-context-lint.py' | |
| - 'scripts/ai-dev-llm-wiki-sync.py' | |
| - 'ai_context/developer_llm_wiki/**' | |
| - 'ai_context/DEVELOPER_LLM_WIKI_FRAMEWORK.md' | |
| - 'ROADMAP.md' | |
| - 'FUTURE_ENHANCEMENTS.md' | |
| - 'DOCUMENTATION_GOVERNANCE.md' | |
| - 'RELEASE_STRATEGY.md' | |
| - 'BRANCHING_STRATEGY.md' | |
| - 'VERSIONING.md' | |
| - 'CHANGELOG.md' | |
| - 'docs/de/**' | |
| - 'docs/en/**' | |
| - 'docs/**' | |
| - 'scripts/docs-orphan-check.py' | |
| - 'scripts/root-docs-hygiene.py' | |
| - 'scripts/src-include-docs-align.py' | |
| - '.github/scripts/code_maturity_header_writer.py' | |
| - '.github/workflows/maintenance-docs.yml' | |
| pull_request: | |
| branches: [develop, community] | |
| paths: | |
| - 'ai_context/**' | |
| - 'docs/de/**' | |
| - 'docs/en/**' | |
| - 'docs/**' | |
| - 'INDEX.md' | |
| - 'LOG.md' | |
| - 'AI_WIKI_INTEGRATION_PLAYBOOK.md' | |
| - 'scripts/ai-context-lint.py' | |
| - 'scripts/ai-dev-llm-wiki-sync.py' | |
| - 'ai_context/developer_llm_wiki/**' | |
| - 'ai_context/DEVELOPER_LLM_WIKI_FRAMEWORK.md' | |
| - 'ROADMAP.md' | |
| - 'FUTURE_ENHANCEMENTS.md' | |
| - 'DOCUMENTATION_GOVERNANCE.md' | |
| - 'RELEASE_STRATEGY.md' | |
| - 'BRANCHING_STRATEGY.md' | |
| - 'VERSIONING.md' | |
| - 'CHANGELOG.md' | |
| - 'scripts/docs-orphan-check.py' | |
| - 'scripts/root-docs-hygiene.py' | |
| - 'scripts/src-include-docs-align.py' | |
| - '.github/scripts/code_maturity_header_writer.py' | |
| - '.github/workflows/maintenance-docs.yml' | |
| schedule: | |
| - cron: '0 4 * * *' # Daily 04:00 UTC — all daily doc checks (maturity, orphan, hygiene) | |
| - cron: '0 5 * * 1' # Weekly Monday 05:00 UTC — src/include alignment | |
| workflow_dispatch: | |
| inputs: | |
| fail_on_findings: | |
| description: 'Fail on any findings (orphan check, hygiene, alignment)' | |
| type: boolean | |
| default: false | |
| update_headers: | |
| description: 'Code maturity: rewrite headers (default: check-only)' | |
| type: boolean | |
| default: false | |
| full_repo_scan: | |
| description: 'Code maturity: include full repository scan target (`.`) for supported code extensions' | |
| type: boolean | |
| default: false | |
| include_all_files: | |
| description: 'Code maturity: update/validate all scanned files regardless of score threshold' | |
| type: boolean | |
| default: false | |
| header_mode: | |
| description: 'Code maturity: header richness mode' | |
| type: choice | |
| default: extended | |
| options: | |
| - lean | |
| - extended | |
| - auto | |
| min_score: | |
| description: 'Code maturity: score threshold when include_all_files=false' | |
| type: string | |
| default: '80' | |
| target_paths: | |
| description: 'Code maturity: comma-separated scan roots (ignored when full_repo_scan=true)' | |
| type: string | |
| default: 'src,include,tests,benchmarks' | |
| apply_ai_updates: | |
| description: 'AI context: apply lint updates' | |
| type: boolean | |
| default: false | |
| llm_wiki_apply_updates: | |
| description: 'Developer LLM Wiki: write/update ai_context/developer_llm_wiki artifacts' | |
| type: boolean | |
| default: false | |
| llm_wiki_full_sync: | |
| description: 'Developer LLM Wiki: perform initial/global source migration scan' | |
| type: boolean | |
| default: false | |
| llm_wiki_fail_on_findings: | |
| description: 'Developer LLM Wiki: fail job when validation findings exist' | |
| type: boolean | |
| default: false | |
| concurrency: | |
| group: maintenance-docs-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| # ────────────────────────────────────────────────────────────────────────────── | |
| jobs: | |
| # 1. AI Context Wiki Sync | |
| ai-context-sync: | |
| name: AI Context lint and sync | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Set up Python | |
| uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 | |
| with: | |
| python-version: '3.11' | |
| - name: Run AI context lint | |
| id: lint | |
| run: | | |
| APPLY_FLAG="" | |
| [ "${{ inputs.apply_ai_updates }}" = "true" ] && APPLY_FLAG="--apply-updates" | |
| python3 scripts/ai-context-lint.py \ | |
| --format text \ | |
| --output /tmp/ai-context-lint.txt \ | |
| $APPLY_FLAG | |
| python3 scripts/ai-context-lint.py \ | |
| --format json \ | |
| --output /tmp/ai-context-lint.json || true | |
| - name: Upload lint artifacts | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: ai-context-lint-${{ github.run_number }} | |
| path: | | |
| /tmp/ai-context-lint.txt | |
| /tmp/ai-context-lint.json | |
| retention-days: 30 | |
| if-no-files-found: ignore | |
| - name: Write job summary | |
| if: always() | |
| run: | | |
| { | |
| echo "## AI Context Sync" | |
| if [ -f /tmp/ai-context-lint.txt ]; then | |
| echo '```'; cat /tmp/ai-context-lint.txt; echo '```' | |
| fi | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| # 2. Code Maturity Analysis | |
| developer-llm-wiki: | |
| name: Developer LLM Wiki sync | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up Python | |
| uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 | |
| with: | |
| python-version: '3.11' | |
| - name: Run developer LLM wiki sync/check | |
| run: | | |
| APPLY_FLAG="" | |
| FULL_FLAG="" | |
| FAIL_FLAG="" | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ inputs.llm_wiki_apply_updates }}" = "true" ]; then | |
| APPLY_FLAG="--apply-updates" | |
| fi | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ inputs.llm_wiki_full_sync }}" = "true" ]; then | |
| FULL_FLAG="--full-sync" | |
| fi | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ inputs.llm_wiki_fail_on_findings }}" = "true" ]; then | |
| FAIL_FLAG="--fail-on-findings" | |
| fi | |
| python3 scripts/ai-dev-llm-wiki-sync.py \ | |
| --output-text /tmp/dev-llm-wiki-sync.txt \ | |
| --output-json /tmp/dev-llm-wiki-sync.json \ | |
| $APPLY_FLAG \ | |
| $FULL_FLAG \ | |
| $FAIL_FLAG | |
| - name: Upload developer LLM wiki artifacts | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: developer-llm-wiki-${{ github.run_number }} | |
| path: | | |
| /tmp/dev-llm-wiki-sync.txt | |
| /tmp/dev-llm-wiki-sync.json | |
| ai_context/developer_llm_wiki/ | |
| retention-days: 30 | |
| if-no-files-found: ignore | |
| - name: Write job summary | |
| if: always() | |
| run: | | |
| { | |
| echo "## Developer LLM Wiki Sync" | |
| if [ -f /tmp/dev-llm-wiki-sync.txt ]; then | |
| echo '```'; cat /tmp/dev-llm-wiki-sync.txt; echo '```' | |
| fi | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| # 3. Code Maturity Analysis | |
| code-maturity: | |
| name: Code Maturity Analysis | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up Python | |
| uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 | |
| with: | |
| python-version: '3.11' | |
| - name: Determine run mode | |
| id: mode | |
| run: | | |
| HEADER_MODE="extended" | |
| MIN_SCORE="80" | |
| TARGET_PATHS="src,include,tests,benchmarks" | |
| INCLUDE_ALL_FLAG="" | |
| FAIL_ON_VALIDATION_FLAG="" | |
| REPORT_PATH_FLAG="--report-path /tmp/code_maturity_report.md" | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ inputs.update_headers }}" = "true" ]; then | |
| { | |
| echo "mode=rewrite" | |
| echo "no_headers_flag=" | |
| } >> "$GITHUB_OUTPUT" | |
| else | |
| { | |
| echo "mode=check-only" | |
| echo "no_headers_flag=--no-headers" | |
| } >> "$GITHUB_OUTPUT" | |
| fi | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | |
| HEADER_MODE="${{ inputs.header_mode }}" | |
| MIN_SCORE="${{ inputs.min_score }}" | |
| TARGET_PATHS="${{ inputs.target_paths }}" | |
| if [ "${{ inputs.full_repo_scan }}" = "true" ]; then | |
| TARGET_PATHS="." | |
| fi | |
| if [ "${{ inputs.include_all_files }}" = "true" ] || [ "${{ inputs.full_repo_scan }}" = "true" ]; then | |
| INCLUDE_ALL_FLAG="--include-all-files" | |
| fi | |
| if [ "${{ inputs.fail_on_findings }}" = "true" ]; then | |
| FAIL_ON_VALIDATION_FLAG="--fail-on-validation" | |
| fi | |
| fi | |
| { | |
| echo "header_mode=$HEADER_MODE" | |
| echo "min_score=$MIN_SCORE" | |
| echo "target_paths=$TARGET_PATHS" | |
| echo "include_all_flag=$INCLUDE_ALL_FLAG" | |
| echo "fail_on_validation_flag=$FAIL_ON_VALIDATION_FLAG" | |
| echo "report_path_flag=$REPORT_PATH_FLAG" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Run code maturity analysis (${{ steps.mode.outputs.mode }}) | |
| run: | | |
| python .github/scripts/code_maturity_header_writer.py \ | |
| --root . \ | |
| --header-mode "${{ steps.mode.outputs.header_mode }}" \ | |
| --min-score "${{ steps.mode.outputs.min_score }}" \ | |
| --target-paths "${{ steps.mode.outputs.target_paths }}" \ | |
| ${{ steps.mode.outputs.include_all_flag }} \ | |
| ${{ steps.mode.outputs.no_headers_flag }} \ | |
| ${{ steps.mode.outputs.fail_on_validation_flag }} \ | |
| ${{ steps.mode.outputs.report_path_flag }} \ | |
| 2>&1 | tee /tmp/maturity_run.log | |
| - name: Upload maturity report | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| 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 | |
| - name: Write job summary | |
| if: always() | |
| run: | | |
| { | |
| echo "## Code Maturity — ${{ steps.mode.outputs.mode }}" | |
| echo "| Mode | ${{ steps.mode.outputs.mode }} |" | |
| echo "| Event | \`${{ github.event_name }}\` |" | |
| echo "| Header mode | \`${{ steps.mode.outputs.header_mode }}\` |" | |
| echo "| Min score | \`${{ steps.mode.outputs.min_score }}\` |" | |
| echo "| Target paths | \`${{ steps.mode.outputs.target_paths }}\` |" | |
| echo "| Include all files | \`${{ steps.mode.outputs.include_all_flag != '' }}\` |" | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| # 4. Docs Orphan Check | |
| docs-orphan-check: | |
| name: docs/de + docs/en Orphan Check | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Set up Python | |
| uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 | |
| with: | |
| python-version: '3.11' | |
| - name: Run docs orphan check | |
| run: | | |
| FAIL_FLAG="" | |
| [ "${{ inputs.fail_on_findings }}" = "true" ] && FAIL_FLAG="--fail-on-findings" | |
| python3 scripts/docs-orphan-check.py --format text --output /tmp/docs-orphan-check.txt $FAIL_FLAG | |
| python3 scripts/docs-orphan-check.py --format json --output /tmp/docs-orphan-check.json --quiet || true | |
| - name: Upload orphan-check reports | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: docs-orphan-check-${{ github.run_number }} | |
| path: | | |
| /tmp/docs-orphan-check.txt | |
| /tmp/docs-orphan-check.json | |
| retention-days: 30 | |
| if-no-files-found: ignore | |
| - name: Write job summary | |
| if: always() | |
| run: | | |
| { | |
| echo "## Docs Orphan Check" | |
| if [ -f /tmp/docs-orphan-check.txt ]; then | |
| echo '```'; cat /tmp/docs-orphan-check.txt; echo '```' | |
| fi | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| # 5. Root Docs Hygiene | |
| root-docs-hygiene: | |
| name: Repo root markdown hygiene | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Set up Python | |
| uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 | |
| with: | |
| python-version: '3.11' | |
| - name: Run root docs hygiene check | |
| run: | | |
| FAIL_FLAG="" | |
| [ "${{ inputs.fail_on_findings }}" = "true" ] && FAIL_FLAG="--fail-on-findings" | |
| python3 scripts/root-docs-hygiene.py --format text --output /tmp/root-docs-hygiene.txt $FAIL_FLAG | |
| python3 scripts/root-docs-hygiene.py --format json --output /tmp/root-docs-hygiene.json || true | |
| - name: Upload hygiene reports | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: root-docs-hygiene-${{ github.run_number }} | |
| path: | | |
| /tmp/root-docs-hygiene.txt | |
| /tmp/root-docs-hygiene.json | |
| retention-days: 30 | |
| if-no-files-found: ignore | |
| - name: Write job summary | |
| if: always() | |
| run: | | |
| { | |
| echo "## Root Docs Hygiene" | |
| if [ -f /tmp/root-docs-hygiene.txt ]; then | |
| echo '```'; cat /tmp/root-docs-hygiene.txt; echo '```' | |
| fi | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| # 6. src/include ↔ docs/de Alignment | |
| src-docs-alignment: | |
| name: src/include ↔ docs/de Alignment | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Set up Python | |
| uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 | |
| with: | |
| python-version: '3.11' | |
| - name: Run alignment check | |
| run: | | |
| FAIL_FLAG="" | |
| [ "${{ inputs.fail_on_findings }}" = "true" ] && FAIL_FLAG="--fail-on-gaps" | |
| python3 scripts/src-include-docs-align.py --format text --output /tmp/align-report.txt $FAIL_FLAG | |
| python3 scripts/src-include-docs-align.py --format json --output /tmp/align-report.json --quiet || true | |
| - name: Upload alignment reports | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: src-include-docs-align-${{ github.run_number }} | |
| path: | | |
| /tmp/align-report.txt | |
| /tmp/align-report.json | |
| retention-days: 30 | |
| if-no-files-found: ignore | |
| - name: Write job summary | |
| if: always() | |
| run: | | |
| { | |
| echo "## src/include ↔ docs/de Alignment" | |
| if [ -f /tmp/align-report.txt ]; then | |
| echo '```'; cat /tmp/align-report.txt; echo '```' | |
| fi | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| # ──────────────────────────────────────────────────────────────────────────── | |
| # 5. ai_working + ai_context Hygiene | |
| # Detects stale AI-session working documents in ai_working/ (> STALE_DAYS days, | |
| # not governance-critical) and reports orphaned ai_context/ entries. | |
| # Governance-critical files are explicitly excluded from the stale-file list: | |
| # - ai_working/ENFORCEMENT_WAIVERS.md (read by compliance-governance-gates.yml) | |
| # - ai_working/MERGE_GATE_AUDIT_LOG.jsonl (read by compliance-governance-gates.yml) | |
| # - ai_working/gs3/ (used by maintenance-gs3-gaps.yml; partially .gitignored) | |
| # This job is report-only: it never deletes files and never fails the workflow. | |
| # ──────────────────────────────────────────────────────────────────────────── | |
| ai-working-hygiene: | |
| name: ai_working + ai_context Hygiene | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up Python | |
| uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 | |
| with: | |
| python-version: '3.11' | |
| - name: Scan ai_working/ for stale files and ai_context/ for orphans | |
| id: hygiene | |
| run: | | |
| python3 - <<'PY' | |
| import json | |
| import os | |
| import subprocess | |
| import sys | |
| from datetime import datetime, timezone | |
| from pathlib import Path | |
| STALE_DAYS = 30 | |
| REPO_ROOT = Path( | |
| subprocess.check_output(["git", "rev-parse", "--show-toplevel"], | |
| text=True).strip() | |
| ) | |
| # Governance-critical files that must not be flagged as stale | |
| GOVERNANCE_KEEP = { | |
| "ENFORCEMENT_WAIVERS.md", | |
| "MERGE_GATE_AUDIT_LOG.jsonl", | |
| "MERGE_GATE_AUDIT_LOG.json", | |
| } | |
| # Subdirectories managed by other workflows — skip them | |
| SKIP_SUBDIRS = {"gs3", "archive", "backups", "delta"} | |
| def git_mtime(path: Path) -> datetime: | |
| """Return last-commit timestamp for a file (UTC).""" | |
| try: | |
| ts = subprocess.check_output( | |
| ["git", "log", "-1", "--format=%ct", "--", str(path)], | |
| text=True, | |
| ).strip() | |
| return datetime.fromtimestamp(int(ts), tz=timezone.utc) if ts else datetime.now(tz=timezone.utc) | |
| except Exception: | |
| return datetime.now(tz=timezone.utc) | |
| now = datetime.now(tz=timezone.utc) | |
| stale_files = [] | |
| ai_working = REPO_ROOT / "ai_working" | |
| if ai_working.is_dir(): | |
| for f in sorted(ai_working.iterdir()): | |
| if f.is_dir() and f.name in SKIP_SUBDIRS: | |
| continue | |
| if not f.is_file(): | |
| continue | |
| if f.name in GOVERNANCE_KEEP: | |
| continue | |
| # Only track markdown and plain-text docs (not generated JSON/JSONL) | |
| if f.suffix.lower() not in (".md", ".txt", ".rst"): | |
| continue | |
| age_days = (now - git_mtime(f)).days | |
| if age_days >= STALE_DAYS: | |
| stale_files.append({"file": str(f.relative_to(REPO_ROOT)), | |
| "age_days": age_days}) | |
| # --- ai_context orphan check: files not referenced in INDEX.md or PLAYBOOK --- | |
| referenced = set() | |
| for anchor_file in ["INDEX.md", "AI_WIKI_INTEGRATION_PLAYBOOK.md"]: | |
| p = REPO_ROOT / anchor_file | |
| if p.exists(): | |
| for line in p.read_text(encoding="utf-8", errors="replace").splitlines(): | |
| if "ai_context/" in line: | |
| # extract bare filename | |
| for token in line.split(): | |
| if "ai_context/" in token: | |
| fname = token.strip("()[]()`*\"'#").split("/")[-1].split(")")[0] | |
| referenced.add(fname) | |
| ai_context = REPO_ROOT / "ai_context" | |
| orphan_ctx = [] | |
| if ai_context.is_dir(): | |
| for f in sorted(ai_context.rglob("*.md")): | |
| if f.name not in referenced: | |
| orphan_ctx.append(str(f.relative_to(REPO_ROOT))) | |
| report = { | |
| "stale_ai_working": stale_files, | |
| "orphan_ai_context": orphan_ctx, | |
| "stale_threshold_days": STALE_DAYS, | |
| "scanned_at": now.isoformat(), | |
| } | |
| report_path = Path("/tmp/ai-hygiene-report.json") | |
| report_path.write_text(json.dumps(report, indent=2), encoding="utf-8") | |
| # Write step summary | |
| summary_path = os.environ.get("GITHUB_STEP_SUMMARY") | |
| lines = [] | |
| lines.append("## ai_working + ai_context Hygiene Report\n") | |
| lines.append(f"Stale threshold: **{STALE_DAYS} days**\n") | |
| lines.append(f"\n### Stale ai_working/ files ({len(stale_files)})\n") | |
| if stale_files: | |
| lines.append("| File | Age (days) |\n|---|---|\n") | |
| for entry in stale_files: | |
| lines.append(f"| `{entry['file']}` | {entry['age_days']} |\n") | |
| lines.append("\n> ℹ️ Review and remove stale working documents when no longer needed.\n") | |
| else: | |
| lines.append("✅ No stale ai_working/ documents.\n") | |
| lines.append(f"\n### Unreferenced ai_context/ files ({len(orphan_ctx)})\n") | |
| if orphan_ctx: | |
| lines.append("These files are not linked from INDEX.md or AI_WIKI_INTEGRATION_PLAYBOOK.md:\n") | |
| for f in orphan_ctx[:20]: | |
| lines.append(f"- `{f}`\n") | |
| if len(orphan_ctx) > 20: | |
| lines.append(f"- ... and {len(orphan_ctx) - 20} more\n") | |
| else: | |
| lines.append("✅ All ai_context/ files are referenced.\n") | |
| output = "".join(lines) | |
| if summary_path: | |
| with open(summary_path, "a", encoding="utf-8") as fh: | |
| fh.write(output) | |
| else: | |
| print(output) | |
| # Write human-readable text report | |
| Path("/tmp/ai-hygiene-report.txt").write_text(output, encoding="utf-8") | |
| # Set output for downstream notification use | |
| github_output = os.environ.get("GITHUB_OUTPUT") | |
| if github_output: | |
| with open(github_output, "a") as fh: | |
| fh.write(f"stale_count={len(stale_files)}\n") | |
| fh.write(f"orphan_ctx_count={len(orphan_ctx)}\n") | |
| PY | |
| - name: Upload hygiene report | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: ai-hygiene-report-${{ github.run_number }} | |
| path: | | |
| /tmp/ai-hygiene-report.json | |
| /tmp/ai-hygiene-report.txt | |
| retention-days: 30 | |
| if-no-files-found: ignore |