mirror of
https://github.com/vinta/awesome-python.git
synced 2026-11-08 02:15:12 -06:00
feat: add scan-stars skill and Rejected Additions register
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -27,7 +27,7 @@ Done when every entry in scope has downloads (or a stated no-signal reason), rep
|
||||
|
||||
## 3. Verdicts
|
||||
|
||||
Draft a verdict and reason for every entry, restructure before cap: decide Splits, mints, and re-homes first — cutting before restructuring destroys entries — then apply the cap tier by tier, including promotions and demotions between obvious choice and challenger. Ground every reason in the fetched evidence; label anything unverifiable as a judgment call. Where fresh evidence contradicts the standing verdict, say so in the reason instead of silently keeping the seed.
|
||||
Draft a verdict and reason for every entry, restructure before cap: decide Splits, mints, and re-homes first — cutting before restructuring destroys entries — then apply the cap tier by tier, including promotions and demotions between obvious choice and challenger. Ground every reason in the fetched evidence; label anything unverifiable as a judgment call. Where fresh evidence contradicts the standing verdict, say so in the reason instead of silently keeping the seed. A proposed addition under `docs/audit-logs.md` "Rejected Additions" seeds as drop from that line.
|
||||
|
||||
## 4. Review and go
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ Sort the batch: entry additions continue; anything else (typo fixes, website cha
|
||||
|
||||
## 2. Screen
|
||||
|
||||
Apply the Automatic Rejection rules that the diff and PR metadata answer without judgment. The recently-closed-duplicate rule needs a lookup: `gh pr list --repo vinta/awesome-python --state closed --search "<project name>" --limit 10`. A screened-out PR goes straight to step 4 as a close, with the rule it broke as its reason.
|
||||
Apply the Automatic Rejection rules that the diff and PR metadata answer without judgment. The recently-closed-duplicate rule needs a lookup: `gh pr list --repo vinta/awesome-python --state closed --search "<project name>" --limit 10`; a project under `docs/audit-logs.md` "Rejected Additions" also counts as previously rejected unless the PR brings evidence newer than its line. A screened-out PR goes straight to step 4 as a close, with the rule it broke as its reason.
|
||||
|
||||
Resolve each survivor's target use case against the current README — a diff's context lines show the base the PR was written on, which may have changed since. A merge conflict rides to the Merge arm, which absorbs it. Done when every surviving PR names its target use case.
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
name: scan-stars
|
||||
description: Scan the maintainer's GitHub stars for README.md addition candidates; incremental by default, `full` rejudges stars judged before.
|
||||
argument-hint: [full]
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
# Scan stars
|
||||
|
||||
A star scan turns the maintainer's GitHub stars into proposed additions. Every star the scan sees ends in one place: listed in README.md, screened out in `local/starred-scan.tsv`, or rejected under `docs/audit-logs.md` "Rejected Additions". Judgment belongs to `audit-the-list` and the rules to CONTRIBUTING.md; this skill only sources candidates and records outcomes.
|
||||
|
||||
## 1. Scan
|
||||
|
||||
Run `uv run python .claude/skills/scan-stars/scripts/scan_stars.py`, adding `--full` when the argument is `full`. With no ledger yet, a plain run already covers every star. Print the drop counts from stderr. Done when the candidate TSV is in hand.
|
||||
|
||||
## 2. Screen
|
||||
|
||||
Judge each candidate from its name, description, topics, and language against CONTRIBUTING Quality Requirement 1 (Serves Python Developers), and name the current use case it would compete in. The screen is loose: drop only what plainly fails, and pass anything uncertain on, since `audit-the-list` is the real judge. In a full scan, read the `prior` column as context and overturn a rejection only on new evidence.
|
||||
|
||||
Append each screen-out to `local/starred-scan.tsv` as it is decided, one `repo<TAB>yyyy-mm-dd<TAB>reason` row (create the file with header `repo date reason`), so an interrupted run keeps its work. The file is append-only; the script reads the last row per repo. A survivor no current use case fits is a structure question: carry it to review with its evidence. Done when every candidate is a ledger row, holds a target use case, or is a structure question.
|
||||
|
||||
## 3. Judge
|
||||
|
||||
Group survivors by target use case. Per group, invoke `audit-the-list` with: "Judge proposed entries <names, each with its GitHub repo> for the <section — subcategory> use case. Evidence and Verdicts steps only; report the verdicts back. No preview page, no README changes, no commits." Done when every survivor holds an add or drop verdict with a reason, and each add at cap names the entry it displaces.
|
||||
|
||||
## 4. Review
|
||||
|
||||
Run `preview-verdicts` over all groups: survivors as `add` or `drop` rows, displaced entries as `drop`, structure questions noted on their group, and any `prior` verdict quoted in the row's reason. Done when the maintainer's feedback is processed and they gave the go.
|
||||
|
||||
## 5. Record
|
||||
|
||||
- **Added**: follow `audit-the-list` step 5 for the README change, one commit per entry.
|
||||
- **Rejected**: a candidate that reached review and was not added gets one line under "Rejected Additions": `- owner/repo -- yyyy-mm-dd, <downloads>/month (<source>): <reason>.`, replacing its earlier line.
|
||||
- **Parked**: a structure question the maintainer leaves open gets no record, so it resurfaces next run.
|
||||
|
||||
Done when every candidate from step 1 is listed in README.md, a ledger row, a Rejected Additions line, or parked by the maintainer.
|
||||
@@ -0,0 +1,117 @@
|
||||
"""List the maintainer's GitHub stars that could be README.md candidates.
|
||||
|
||||
Drops stars that fail a check needing no judgment: already listed, own repo,
|
||||
archived, no push in 12 months, or created under a month ago. These drops are
|
||||
recomputed every run, so a repo that becomes active again resurfaces. An
|
||||
incremental run also drops stars already judged: model screen-outs in
|
||||
local/starred-scan.tsv and maintainer rejections under docs/audit-logs.md
|
||||
"Rejected Additions". `--full` keeps them and shows the earlier verdict in the
|
||||
`prior` column. Prints survivors as TSV on stdout, drop counts on stderr.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from collections import Counter
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from pathlib import Path
|
||||
from typing import TypedDict
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
sys.path.insert(0, str(ROOT / "website"))
|
||||
|
||||
from readme_parser import parse_readme
|
||||
|
||||
LEDGER = ROOT / "local" / "starred-scan.tsv"
|
||||
REGISTER = ROOT / "docs" / "audit-logs.md"
|
||||
GITHUB_REPO_RE = re.compile(r"^https://github\.com/([^/#?]+/[^/#?]+)")
|
||||
REJECTED_LINE_RE = re.compile(r"^- (\S+) -- (.+)$")
|
||||
|
||||
|
||||
class Owner(TypedDict):
|
||||
login: str
|
||||
|
||||
|
||||
class Star(TypedDict):
|
||||
full_name: str
|
||||
owner: Owner
|
||||
archived: bool
|
||||
pushed_at: str
|
||||
created_at: str
|
||||
language: str | None
|
||||
stargazers_count: int
|
||||
topics: list[str]
|
||||
description: str | None
|
||||
|
||||
|
||||
def gh(*args: str) -> str:
|
||||
return subprocess.run(["gh", *args], capture_output=True, text=True, check=True).stdout
|
||||
|
||||
|
||||
def listed_repos() -> set[str]:
|
||||
repos = set()
|
||||
for group in parse_readme((ROOT / "README.md").read_text()):
|
||||
for category in group["categories"]:
|
||||
for link in [*category["entries"], *(sub for entry in category["entries"] for sub in entry["also_see"])]:
|
||||
if match := GITHUB_REPO_RE.match(link["url"]):
|
||||
repos.add(match.group(1).removesuffix(".git").lower())
|
||||
return repos
|
||||
|
||||
|
||||
def prior_verdicts() -> dict[str, str]:
|
||||
"""Repo -> earlier verdict. The ledger is append-only, so its last row per repo wins; a maintainer rejection outranks both."""
|
||||
prior = {}
|
||||
if LEDGER.exists():
|
||||
for line in LEDGER.read_text().splitlines()[1:]:
|
||||
repo, date, reason = line.split("\t", 2)
|
||||
prior[repo.lower()] = f"screened out {date}: {reason}"
|
||||
register = REGISTER.read_text().splitlines()
|
||||
start = register.index("## Rejected Additions")
|
||||
for line in register[start + 1 :]:
|
||||
if line.startswith("## "):
|
||||
break
|
||||
if match := REJECTED_LINE_RE.match(line):
|
||||
prior[match.group(1).lower()] = f"rejected {match.group(2)}"
|
||||
return prior
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
parser.add_argument("--full", action="store_true", help="rejudge stars already screened out or rejected")
|
||||
args = parser.parse_args()
|
||||
|
||||
now = datetime.now(UTC)
|
||||
login = gh("api", "user", "--jq", ".login").strip().lower()
|
||||
listed = listed_repos()
|
||||
prior = prior_verdicts()
|
||||
|
||||
stars: list[Star] = [json.loads(line) for line in gh("api", "--paginate", "user/starred?per_page=100", "--jq", ".[]").splitlines()]
|
||||
dropped = Counter()
|
||||
print("repo\tlanguage\tstars\tpushed\ttopics\tdescription\tprior")
|
||||
for star in stars:
|
||||
repo = star["full_name"].lower()
|
||||
if repo in listed:
|
||||
dropped["already listed"] += 1
|
||||
elif star["owner"]["login"].lower() == login:
|
||||
dropped["own repo"] += 1
|
||||
elif star["archived"]:
|
||||
dropped["archived"] += 1
|
||||
elif datetime.fromisoformat(star["pushed_at"]) < now - timedelta(days=365):
|
||||
dropped["no push in 12 months"] += 1
|
||||
elif datetime.fromisoformat(star["created_at"]) > now - timedelta(days=30):
|
||||
dropped["created under a month ago"] += 1
|
||||
elif repo in prior and not args.full:
|
||||
dropped["judged before"] += 1
|
||||
else:
|
||||
description = " ".join((star["description"] or "").split())
|
||||
print("\t".join([star["full_name"], star["language"] or "-", str(star["stargazers_count"]), star["pushed_at"][:10], ",".join(star["topics"]), description, prior.get(repo, "")]))
|
||||
|
||||
print(f"{len(stars)} stars; {len(stars) - dropped.total()} candidates", file=sys.stderr)
|
||||
for reason, count in dropped.most_common():
|
||||
print(f" dropped {count}: {reason}", file=sys.stderr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -67,6 +67,10 @@ These entries sit past the 12-month activity requirement without an override. Ea
|
||||
- schedule -- kept 2026-08-16, recorded 2026-10-02. Last commit 2024-05-25.
|
||||
- sortedcontainers
|
||||
|
||||
## Rejected Additions
|
||||
|
||||
A proposed entry the maintainer turned down leaves no commit, so it is recorded here with the evidence at decision time, keyed by its GitHub `owner/repo`. Rejudge one only on new evidence.
|
||||
|
||||
## 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