docs: add priority ranking mode to audit-the-list

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Vinta Chen
2026-10-14 21:35:45 +08:00
co-authored by Claude
parent ba49ec7245
commit d5f917d063
3 changed files with 124 additions and 3 deletions
+5 -3
View File
@@ -1,7 +1,7 @@
---
name: audit-the-list
description: Audit README.md sections against the shortlist rules — re-verify every entry's verdict with live data, promote or demote challengers, restructure oversized use cases, prune, and evaluate proposed additions. Fire when the maintainer asks to audit, sweep, prune, re-check, or maintain sections, or asks whether an entry (or a proposed one) deserves its slot.
argument-hint: [all or specific sections]
argument-hint: [all, specific sections, or none for a recommendation]
---
# Audit the list
@@ -10,7 +10,9 @@ An Audit re-runs the admission test over live sections of README.md: every entry
## 1. Scope
Resolve the scope from the arguments. Named sections mean exactly those, whether or not they were audited before (a re-run is how a past verdict gets rechecked). `all` is ambiguous — `AskUserQuestion` whether it means every section or only never-audited ones; prior audits are recorded in git history (`git log --oneline --grep="sweep\|audit"`). With no arguments, AskUserQuestion listing the never-audited Thematic Groups. Batch the work one Thematic Group per sitting. Done when the section list is settled.
Resolve the scope from the arguments. Named sections mean exactly those, whether or not they were audited before (a re-run is how a past verdict gets rechecked). `all` is ambiguous — `AskUserQuestion` whether it means every section or only never-audited ones. Batch the work one Thematic Group per sitting. Done when the section list is settled.
With no arguments, or to see which sections were never audited, run `uv run python .claude/skills/audit-the-list/scripts/audit_priority.py`. It dates each section's last audit from `audit:`/`prune: sweep` and `style: audit` commit subjects plus the Last Audited lines in `docs/audit-logs.md`, then ranks sections: entries added or removed since that audit first, most changes first, then unchanged sections by oldest audit, never-audited first. Print its ranking, then AskUserQuestion with the top four sections as options, the first marked Recommended, each description carrying its changed entries or its last-audit date. A renamed section loses its audit history and ranks as never audited; the script lists the orphaned subjects, so say so when that happens.
## 2. Evidence
@@ -33,7 +35,7 @@ Run the `preview-verdicts` skill: it generates the interactive review page and d
## 5. Execute
One commit per section: body lists each removal with its reason and downloads figure, naming the figure's source and fetch date so a later audit can tell whether a trend compares like with like (ClickPy counts mirror traffic, pypistats does not); restructures, tier moves, and reorders ride the same commit. A restructure that renames or dissolves a category or subcategory adds `"old path": "new path"` to `website/data/redirects.json` in that commit, so the old URL keeps its search ranking; the build fails when a redirect target no longer exists, so a later rename also updates the entries pointing at the renamed path. Format-only outcomes (no removals) are a single style commit. `make test` before every commit. After the last add or rename, re-run the ClickPy sweep, since new names have no cache row and the build would count them without downloads, then `make build`. Generic commit helpers tend to split a section audit into structural and per-subcategory commits — if that happens, squash back to one commit per section. Done when the tree is clean, tests passed before each commit, and the build count reconciles with the adjudicated changes.
One commit per section: body lists each removal with its reason and downloads figure, naming the figure's source and fetch date so a later audit can tell whether a trend compares like with like (ClickPy counts mirror traffic, pypistats does not); restructures, tier moves, and reorders ride the same commit. A restructure that renames or dissolves a category or subcategory adds `"old path": "new path"` to `website/data/redirects.json` in that commit, so the old URL keeps its search ranking; the build fails when a redirect target no longer exists, so a later rename also updates the entries pointing at the renamed path. Format-only outcomes (no removals) are a single style commit whose subject starts `style: audit <Section>`. A section whose audit changes nothing gets a line under Last Audited in `docs/audit-logs.md` instead (`- <Section> -- <yyyy-mm-dd>, no changes.`, replacing its earlier line), committed, since the ranking script reads an audit only from a commit or that line. `make test` before every commit. After the last add or rename, re-run the ClickPy sweep, since new names have no cache row and the build would count them without downloads, then `make build`. Generic commit helpers tend to split a section audit into structural and per-subcategory commits — if that happens, squash back to one commit per section. Done when the tree is clean, tests passed before each commit, and the build count reconciles with the adjudicated changes.
## 6. Record
@@ -0,0 +1,115 @@
"""Rank README.md sections for the next audit.
A section's last audit is the newest of: a commit whose subject starts with
`audit: sweep`, `prune: sweep`, or `style: audit` and names the section or its
Thematic Group, and its line under docs/audit-logs.md "Last Audited". Sections
whose entries changed since then come first, most changes first; the rest
follow by oldest audit, never-audited first.
"""
import re
import subprocess
import sys
from functools import cache
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
sys.path.insert(0, str(ROOT / "website"))
from readme_parser import parse_readme
# The shortlist reform; audits start after it, and never-audited sections diff against it.
REFORM = "553bf88"
AUDIT_SUBJECT_RE = re.compile(r"^(?:(?:audit|prune): sweep|style: audit) (.+?)(?: —|,| to the shortlist cap|$)")
REGISTER_LINE_RE = re.compile(r"^- (.+?) -- (\d{4}-\d{2}-\d{2})")
def git(*args: str) -> str:
return subprocess.run(["git", "-C", str(ROOT), *args], capture_output=True, text=True, check=True).stdout
@cache
def entries_at(rev: str) -> dict[str, dict[str, str]]:
"""Section -> {normalized entry name: display name}, so a recased or re-punctuated name is no change."""
sections = {}
for group in parse_readme(git("show", f"{rev}:README.md")):
for category in group["categories"]:
names = [entry["name"] for entry in category["entries"]]
names += [sub["name"] for entry in category["entries"] for sub in entry["also_see"]]
sections[category["name"]] = {re.sub(r"[-_.]+", "-", name).lower(): name for name in names}
return sections
def resolve(text: str, groups: dict[str, list[str]]) -> list[str]:
"""Sections named in text, longest name first so "Database Drivers" never counts as "Database"."""
found = []
for name in sorted(groups, key=len, reverse=True):
if re.search(rf"(?<!\w){re.escape(name)}(?!\w)", text):
found.extend(groups[name])
text = text.replace(name, "")
return found
def main() -> None:
current = parse_readme(git("show", "HEAD:README.md"))
group_of = {c["name"]: g["name"] for g in current for c in g["categories"]}
# Section names map to themselves, Thematic Group names to all their sections.
names = {s: [s] for s in group_of} | {g["name"]: [c["name"] for c in g["categories"]] for g in current}
last: dict[str, tuple[int, str, str]] = {} # section -> (timestamp, date, rev)
def stamp(section: str, timestamp: int, date: str, rev: str) -> None:
if section not in last or timestamp > last[section][0]:
last[section] = (timestamp, date, rev)
unmatched = []
for line in git("log", "--format=%H%x09%ct%x09%cs%x09%s", f"{REFORM}..HEAD").splitlines():
rev, timestamp, date, subject = line.split("\t", 3)
if match := AUDIT_SUBJECT_RE.match(subject):
sections = resolve(match.group(1), names)
for section in sections:
stamp(section, int(timestamp), date, rev)
if not sections:
unmatched.append(subject)
register = (ROOT / "docs" / "audit-logs.md").read_text().splitlines()
start = register.index("## Last Audited")
for lineno, line in enumerate(register[start:], start + 1):
if match := REGISTER_LINE_RE.match(line):
blame = git("blame", "--porcelain", "-L", f"{lineno},{lineno}", "--", "docs/audit-logs.md").splitlines()
rev = blame[0].split()[0]
if set(rev) == {"0"}: # uncommitted line
rev = "HEAD"
timestamp = int(next(b.split()[1] for b in blame if b.startswith("committer-time ")))
for section in resolve(match.group(1), names):
stamp(section, timestamp, match.group(2), rev)
head = entries_at("HEAD")
changed, unchanged = [], []
for section in group_of:
_, date, rev = last.get(section, (0, "never", REFORM))
before = entries_at(rev).get(section, {})
added = [head[section][n] for n in head[section].keys() - before.keys()]
removed = [before[n] for n in before.keys() - head[section].keys()]
row = (section, date, rev[:7] if section in last else "", added, removed)
(changed if added or removed else unchanged).append(row)
# "never" sorts after digits, so map it to "" to put never-audited first.
changed.sort(key=lambda r: (-(len(r[3]) + len(r[4])), "" if r[1] == "never" else r[1]))
unchanged.sort(key=lambda r: "" if r[1] == "never" else r[1])
print("Entries added or removed since last audit:")
for section, date, rev, added, removed in changed:
diff = " ".join([*(f"+{n}" for n in sorted(added)), *(f"-{n}" for n in sorted(removed))])
print(f" {section} [{group_of[section]}] last {date} {rev} {diff}")
print("\nNo entry changes, oldest audit first:")
for section, date, rev, _, _ in unchanged:
print(f" {section} [{group_of[section]}] last {date} {rev}")
if unmatched:
print("\nAudit subjects naming no current section or group (renamed or dissolved since):")
for subject in unmatched:
print(f" {subject}")
if __name__ == "__main__":
main()
+4
View File
@@ -65,3 +65,7 @@ These entries sit past the 12-month activity requirement without an override. Ea
- python-pptx -- kept 2026-10-02, last commit 2024-08-06, last release 1.0.2 on 2024-08-07. No successor for generating .pptx files.
- schedule -- kept 2026-08-16, recorded 2026-10-02. Last commit 2024-05-25.
- sortedcontainers
## Last Audited
An audit that changes nothing in a section leaves no commit, so its date is recorded here, one line per section, replaced on each re-audit.