Skip to content

Commit 1bfa186

Browse files
committed
ci: weekly desktop matrix audit driven by Claude
Adds a scheduled GitHub Actions workflow that audits the desktop YAML matrix for two kinds of staleness and proposes fixes via the Anthropic API. Pieces ------ tools/desktops/audit.py Deterministic auditor. Checks out armbian/build alongside configng (from CI; from local CLI when run by hand) and cross-references three things: - releases declared in armbian/build's config/distributions/ (with their support status — 'supported', 'csc', 'eos', ...) - the 'releases:' blocks declared in every DE YAML - the per-(release, arch, tier) resolved DESKTOP_PACKAGES set from parse_desktop_yaml.py For each DESKTOP_PACKAGES entry, fetches packages.debian.org or packages.ubuntu.com and decides whether the package actually exists in that (release, arch). Outputs a JSON report listing 'missing_releases' (build supports it, no DE YAML covers it, not EOS) and 'package_holes' (DESKTOP_PACKAGES names a package that the upstream archive doesn't ship for that combination). Pure logic, no LLM. tools/desktops/audit_apply.py Reads the JSON report and hands it to Claude via the Anthropic Python SDK. Claude is given file Read/Write tool access scoped strictly to tools/modules/desktops/yaml/ and is told to propose minimal edits to common.yaml's tier_overrides for package holes and to add release blocks to per-DE YAMLs for missing releases. After Claude finishes, post-edit validation re-parses every YAML and spot-checks the parser to ensure nothing was broken. Short-circuits on no findings (so 'no work' runs cost nothing in API tokens). Also has --dry-run mode that prints the prompt without calling the API, useful for local testing. .github/workflows/maintenance-desktop-audit.yml Schedule: Mondays 06:00 UTC. Also workflow_dispatch with optional tier / release filter inputs and a dry_run toggle. Job: checkout both repos, run audit.py, surface findings as a job summary (markdown tables), upload the report as an artifact, run audit_apply.py if there's anything actionable, open or update a draft PR via peter-evans/create-pull-request on a fixed branch (bot/desktop-matrix-audit) so weekly runs update the existing PR rather than spamming new ones. Operational notes ----------------- - The Claude apply step is gated on actionable findings AND on ANTHROPIC_API_KEY being present. Without the secret it logs a warning and exits cleanly, so the workflow can run as a smoke test without any API access. - Sandboxed file access: Claude can only read files inside the configng checkout (path traversal blocked) and can only write files inside tools/modules/desktops/yaml/. No bash, no network. - Token cap: --max-tokens 50000 by default, configurable. - PR is opened as a draft so a human can review before merging. Labels applied: bot, desktops, documentation. Local validation ---------------- Tested locally against the live build + configng checkouts: python3 tools/desktops/audit.py \\ --build-repo /home/igorp/Development/build \\ --configng-repo . \\ --skip-network --output /tmp/r.json Found 5 missing CSC releases (forky, jammy, questing, resolute, sid) and 0 package holes — exactly what's expected given the current YAML matrix.
1 parent 2865fdb commit 1bfa186

File tree

3 files changed

+997
-0
lines changed

3 files changed

+997
-0
lines changed
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
name: "Maintenance: Desktop matrix audit"
2+
3+
# Periodic audit of the desktop YAML matrix against:
4+
# 1. armbian/build's config/distributions/ — flag releases the build
5+
# supports but no DE YAML covers
6+
# 2. packages.debian.org / packages.ubuntu.com — flag DESKTOP_PACKAGES
7+
# entries that don't exist in the upstream archive for the
8+
# requested (release, arch)
9+
#
10+
# When the audit finds work to do, hand the report to Claude (via the
11+
# Anthropic API) and let it propose YAML edits. Then open a PR with
12+
# whatever Claude wrote, using peter-evans/create-pull-request.
13+
#
14+
# The audit_apply.py script short-circuits when there's nothing to do,
15+
# so a "no changes" run never burns API tokens or opens an empty PR.
16+
17+
on:
18+
schedule:
19+
# Mondays at 06:00 UTC. Weekly is enough — release / package
20+
# availability changes slowly.
21+
- cron: "0 6 * * 1"
22+
workflow_dispatch:
23+
inputs:
24+
tier:
25+
description: "Tier to audit (minimal, mid, full, or empty for all)"
26+
required: false
27+
default: ""
28+
release:
29+
description: "Release codename to audit (empty for all)"
30+
required: false
31+
default: ""
32+
dry_run:
33+
description: "Dry run — do not call Claude or open a PR"
34+
required: false
35+
default: "false"
36+
type: choice
37+
options:
38+
- "false"
39+
- "true"
40+
41+
permissions:
42+
contents: write
43+
pull-requests: write
44+
45+
concurrency:
46+
group: desktop-audit
47+
cancel-in-progress: false
48+
49+
jobs:
50+
audit:
51+
name: "Desktop matrix audit"
52+
runs-on: ubuntu-latest
53+
steps:
54+
- name: "Checkout configng"
55+
uses: actions/checkout@v4
56+
with:
57+
path: configng
58+
59+
- name: "Checkout armbian/build"
60+
uses: actions/checkout@v4
61+
with:
62+
repository: armbian/build
63+
path: build
64+
# Only the config/distributions/ directory matters for the
65+
# audit, but a sparse checkout would complicate things —
66+
# the build repo is small enough to clone shallowly.
67+
fetch-depth: 1
68+
69+
- name: "Set up Python"
70+
uses: actions/setup-python@v5
71+
with:
72+
python-version: "3.12"
73+
74+
- name: "Install Python dependencies"
75+
run: |
76+
pip install --quiet pyyaml anthropic
77+
78+
- name: "Run deterministic audit"
79+
id: audit
80+
working-directory: configng
81+
run: |
82+
set -euo pipefail
83+
args=(
84+
--build-repo "${{ github.workspace }}/build"
85+
--configng-repo "${{ github.workspace }}/configng"
86+
--output "${{ github.workspace }}/audit-report.json"
87+
)
88+
if [[ -n "${{ github.event.inputs.tier }}" ]]; then
89+
args+=(--tier "${{ github.event.inputs.tier }}")
90+
fi
91+
if [[ -n "${{ github.event.inputs.release }}" ]]; then
92+
args+=(--release "${{ github.event.inputs.release }}")
93+
fi
94+
python3 tools/desktops/audit.py "${args[@]}"
95+
96+
# Surface a summary in the workflow's job summary so reviewers
97+
# can read it without downloading artifacts.
98+
{
99+
echo "## Desktop matrix audit"
100+
echo
101+
python3 - <<'PY'
102+
import json
103+
r = json.load(open("${{ github.workspace }}/audit-report.json"))
104+
print(f"- **Desktops scanned:** {r['stats']['desktops']}")
105+
print(f"- **(release, arch) combinations in scope:** {r['stats']['scope']}")
106+
print(f"- **Package availability checks:** {r['stats']['package_lookups']}")
107+
print(f"- **Holes found:** {r['stats']['holes']}")
108+
print(f"- **Releases not covered:** {len(r['missing_releases'])}")
109+
print()
110+
if r['missing_releases']:
111+
print("### Missing releases")
112+
print()
113+
print("| release | status | name | architectures |")
114+
print("|---|---|---|---|")
115+
for m in r['missing_releases']:
116+
print(f"| `{m['release']}` | {m['support_status']} | {m['name']} | `{','.join(m['architectures'])}` |")
117+
print()
118+
if r['package_holes']:
119+
print("### Package holes")
120+
print()
121+
print("| de | release | arch | tier | missing |")
122+
print("|---|---|---|---|---|")
123+
for h in r['package_holes']:
124+
pkgs = ", ".join(f"`{p}`" for p in h['missing'])
125+
print(f"| `{h['de']}` | `{h['release']}` | `{h['arch']}` | `{h['tier']}` | {pkgs} |")
126+
PY
127+
} >> "$GITHUB_STEP_SUMMARY"
128+
129+
# Set step output: did we find anything actionable?
130+
python3 - <<'PY' >> "$GITHUB_OUTPUT"
131+
import json
132+
r = json.load(open("${{ github.workspace }}/audit-report.json"))
133+
actionable = bool(r['package_holes'] or r['missing_releases'])
134+
print(f"actionable={'true' if actionable else 'false'}")
135+
PY
136+
137+
- name: "Upload audit report"
138+
if: always()
139+
uses: actions/upload-artifact@v4
140+
with:
141+
name: audit-report
142+
path: ${{ github.workspace }}/audit-report.json
143+
retention-days: 30
144+
145+
- name: "Run Claude apply step"
146+
id: apply
147+
if: steps.audit.outputs.actionable == 'true' && github.event.inputs.dry_run != 'true'
148+
working-directory: configng
149+
env:
150+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
151+
run: |
152+
set -euo pipefail
153+
if [[ -z "${ANTHROPIC_API_KEY:-}" ]]; then
154+
echo "::warning::ANTHROPIC_API_KEY secret not set; skipping Claude apply step"
155+
echo "skipped=true" >> "$GITHUB_OUTPUT"
156+
exit 0
157+
fi
158+
python3 tools/desktops/audit_apply.py \
159+
--report "${{ github.workspace }}/audit-report.json" \
160+
--configng-repo "${{ github.workspace }}/configng" \
161+
--summary-output "${{ github.workspace }}/audit-summary.md"
162+
echo "skipped=false" >> "$GITHUB_OUTPUT"
163+
164+
- name: "Open / update pull request"
165+
if: |
166+
steps.audit.outputs.actionable == 'true' &&
167+
github.event.inputs.dry_run != 'true' &&
168+
steps.apply.outputs.skipped != 'true'
169+
uses: peter-evans/create-pull-request@v6
170+
with:
171+
path: configng
172+
branch: bot/desktop-matrix-audit
173+
delete-branch: true
174+
base: main
175+
commit-message: |
176+
desktops: weekly matrix audit fixes (bot)
177+
178+
Generated by .github/workflows/maintenance-desktop-audit.yml
179+
via Claude API based on the deterministic findings of
180+
tools/desktops/audit.py.
181+
title: "desktops: weekly matrix audit fixes (bot)"
182+
body-path: ${{ github.workspace }}/audit-summary.md
183+
labels: |
184+
bot
185+
desktops
186+
documentation
187+
draft: true

0 commit comments

Comments
 (0)