feat(recipe-report): link recipe names in the lead to their mirror repos; 3-para concise lead

render() auto-links whole-word recipe mentions in the editorial lead to
git.autonomic.zone/recipe-maintainers/<recipe> (single regex pass, longest-name-first,
no href corruption). Skill: lead is ~3 short paragraphs (~150-180 words) incl. an
'anything strange worth looking into' paragraph. example-spec.json lead updated to the
concise target.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
autonomic-bot
2026-06-04 02:17:19 +00:00
parent ea2d8c8210
commit a6efcec720
3 changed files with 27 additions and 139 deletions

View File

@ -29,11 +29,14 @@ Helper: `python3 /srv/cc-ci/cc-ci-plan/recipe-report.py {survey|render|publish}`
of the version bumps) for upgrades that fix **CVEs / security issues**. Anything **critical/high** of the version bumps) for upgrades that fix **CVEs / security issues**. Anything **critical/high**
leads the page → the `security` bulletin (recipe · CVE id(s) + severity · what it fixes · PR link). leads the page → the `security` bulletin (recipe · CVE id(s) + severity · what it fixes · PR link).
This is the most important section; be specific about severity and what's exposed if not merged. This is the most important section; be specific about severity and what's exposed if not merged.
- **Lead / editorial.** Write the `lead`: the **overall state of the recipe fleet** this week (how - **Lead / editorial.** Write the `lead` in opus's voice — useful, concrete, opinionated. **~3 short
healthy, what moved, any worrying trend) and **specific, opinionated suggestions of what to focus paragraphs, ~150180 words:**
on** — opus's voice, useful and concrete. **Keep it TIGHT: 2 short paragraphs, ~120 words total** 1. **Fleet state** in a line or two — how healthy, what moved, any trend.
lead with the single most important thing, then security/focus in a sentence or two. (Trim hard; 2. **What to focus on** — security/critical merges first, then the key failures.
the rest of the page carries the detail.) 3. **Anything strange worth looking into** — odd or unexpected failures, parser snags, PR-state
oddities (e.g. a recipe carrying two open PRs to reconcile), drift from the summary, leftover
artifacts. This is the "editor's eye" paragraph; flag what a careful maintainer would want to notice.
Lead with the single most important thing; the rest of the page carries the detail.
- **Needs attention** — GREEN PRs ready to merge + errors/failures to investigate (RED `!testme`, - **Needs attention** — GREEN PRs ready to merge + errors/failures to investigate (RED `!testme`,
recipe bugs). Short, specific prose + links. Flag cross-cutting issues (e.g. two open PRs to reconcile). recipe bugs). Short, specific prose + links. Flag cross-cutting issues (e.g. two open PRs to reconcile).
- **Routine** — minor/clean bumps, stale-test PRs (need operator `--with-tests`), up-to-date / skipped. - **Routine** — minor/clean bumps, stale-test PRs (need operator `--with-tests`), up-to-date / skipped.

File diff suppressed because one or more lines are too long

View File

@ -21,7 +21,7 @@ SPEC SHAPE (the agent writes this JSON):
"pr":"#4","pr_url":"","notes":""}]} "pr":"#4","pr_url":"","notes":""}]}
PUBLIC PAGE — include only public-safe data (no secrets/tokens/raw logs). PUBLIC PAGE — include only public-safe data (no secrets/tokens/raw logs).
""" """
import base64, html, json, os, subprocess, sys, urllib.request import base64, html, json, os, re, subprocess, sys, urllib.request
from datetime import datetime, timezone from datetime import datetime, timezone
LOGDIR = "/srv/cc-ci/.cc-ci-logs" LOGDIR = "/srv/cc-ci/.cc-ci-logs"
@ -121,6 +121,17 @@ def _esc(s):
return html.escape(str(s or "")) return html.escape(str(s or ""))
def _linkify_recipes(text, repo_url):
"""Linkify whole-word recipe-name mentions to their mirror repo. ONE pass over the (already
HTML-escaped) text, longest names first so 'custom-html-tiny' wins over 'custom-html'; re.sub does
not re-scan inserted hrefs, so URLs that end in a recipe name aren't double-linked."""
if not repo_url:
return text
names = sorted(repo_url, key=len, reverse=True)
pat = re.compile(r"(?<![\w-])(" + "|".join(re.escape(n) for n in names) + r")(?![\w-])")
return pat.sub(lambda m: f'<a href="{repo_url[m.group(1)]}">{m.group(1)}</a>', text)
def _links(links): def _links(links):
if not links: if not links:
return "" return ""
@ -172,8 +183,13 @@ def render(spec_path, out_path):
gen = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC") gen = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
sub = s.get("subtitle", "Week of " + s["date"]) sub = s.get("subtitle", "Week of " + s["date"])
lead = s.get("lead", "") or "" lead = s.get("lead", "") or ""
# Auto-link recipe-name mentions in the lead to their mirror repos.
gitea = _env().get("GITEA_URL", "git.autonomic.zone")
repo_url = {r["recipe"]: f"https://{gitea}/recipe-maintainers/{r['recipe']}"
for r in (s.get("table") or []) if r.get("recipe")}
if lead and "<p>" not in lead: if lead and "<p>" not in lead:
lead = "".join(f"<p>{_esc(p.strip())}</p>" for p in lead.split("\n\n") if p.strip()) lead = "".join(f"<p>{_linkify_recipes(_esc(p.strip()), repo_url)}</p>"
for p in lead.split("\n\n") if p.strip())
body = (_mast() + body = (_mast() +
f'<div class="dateline"><span>{_esc(sub)}</span>' f'<div class="dateline"><span>{_esc(sub)}</span>'
f'<span>report.ci.commoninternet.net</span><span>{gen}</span></div>' f'<span>report.ci.commoninternet.net</span><span>{gen}</span></div>'