mirror of
https://github.com/vinta/awesome-python.git
synced 2026-11-08 02:15:12 -06:00
docs: add priority ranking mode to audit-the-list
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -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()
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user