Aggiungi lo script per controllare le statistiche e riordina CLAUDE.md

controlla-statistiche.py confronta stelle, fork, licenza, versione,
installazioni e controlli di sicurezza delle schede con i dati attuali di
GitHub e skills.sh, senza modificare nulla. CLAUDE.md documenta lo script,
le convenzioni delle schede e gli argomenti Ufficio e Second brain.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
davideandClaude Sonnet 5.5 committed 2026-10-30 09:38:54 +01:00
1 parent 5051b50902
commit 71286cf9b8
2 files changed
+188 -2

No files matched your search

+18 -2
View File
@@ -4,12 +4,13 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Scopo del repository ## Scopo del repository
Un dizionario personale di skill per agenti AI, scritto in Markdown. Non è un progetto software: non ci sono comandi di build, lint o test. Le skill vengono aggiunte nel tempo, di solito con una richiesta come "aggiungi la skill X" o con un comando di installazione (`npx skills add ...`). Un dizionario personale di skill per agenti AI, scritto in Markdown. Non è un progetto software: non ci sono build, lint o test; l'unico programma è `scripts/controlla-statistiche.py` (passo 4). Le skill vengono aggiunte nel tempo, di solito con una richiesta come "aggiungi la skill X" o con un comando di installazione (`npx skills add ...`).
## Struttura ## Struttura
- `README.md` è l'indice: una tabella con una riga per skill, con il nome che rimanda alla scheda e una descrizione di una frase. Niente dettagli qui. - `README.md` è l'indice: una tabella con una riga per skill, con il nome che rimanda alla scheda e una descrizione di una frase. Niente dettagli qui.
- `skills/<nome-skill>.md` è la scheda di una skill. Il nome del file è il nome della skill. - `skills/<nome-skill>.md` è la scheda di una skill. Il nome del file è il nome della skill.
- `scripts/controlla-statistiche.py` confronta le statistiche delle schede con quelle attuali (passo 4).
[skills/find-skills.md](skills/find-skills.md) è la scheda di riferimento: ogni nuova scheda deve avere lo stesso livello di dettaglio, struttura e tono. [skills/find-skills.md](skills/find-skills.md) è la scheda di riferimento: ogni nuova scheda deve avere lo stesso livello di dettaglio, struttura e tono.
@@ -44,9 +45,24 @@ Sezioni, in quest'ordine:
Traduci e adatta il contenuto del `SKILL.md` in italiano invece di copiarlo; lascia in originale comandi, nomi e parole chiave di ricerca. Traduci e adatta il contenuto del `SKILL.md` in italiano invece di copiarlo; lascia in originale comandi, nomi e parole chiave di ricerca.
### Convenzioni delle schede
- I numeri di diffusione portano mese e anno (`Diffusione (ottobre 2026)`) e sono arrotondati con "circa".
- Dì sempre cosa non hai verificato: pagina senza controlli di sicurezza, avvisi senza spiegazione, file non letti, comandi non provati.
- Per plugin e raccolte (esempi: [ecc.md](skills/ecc.md), [obsidian-wiki.md](skills/obsidian-wiki.md)) aggiungi in *Cosa fa* una tabella delle skill o degli agenti inclusi, e in *Da sapere* hook, scritture fuori dal progetto e consumo di contesto. In *Installazione* metti per primo il metodo del plugin.
- Se la skill non può funzionare nelle app di chat (servono file locali, sottoagenti, hook), la sezione sulle app desktop lo dice in un avviso.
### 3. Aggiorna l'indice ### 3. Aggiorna l'indice
Aggiungi nel `README.md` la riga della skill con una descrizione di una frase. L'indice è diviso in due gruppi, *Skill singole* e *Plugin, raccolte e strumenti*, e dentro ogni gruppo per argomento (sottosezioni `###`, righe in ordine alfabetico). Metti la skill nel gruppo e nell'argomento giusti, creando un nuovo argomento solo se nessuno calza. Nel secondo gruppo la tabella ha anche la colonna *Tipo*. Aggiungi nel `README.md` la riga della skill con una descrizione di una frase:
- due gruppi, *Skill singole* e *Plugin, raccolte e strumenti*; nel secondo la tabella ha anche la colonna *Tipo*;
- dentro ogni gruppo, sottosezioni `###` per argomento (quelle già presenti), righe in ordine alfabetico; crea un argomento nuovo solo se nessuno calza;
- *Ufficio* (documenti, fogli di calcolo, presentazioni, posta, calendario) e *Second brain* (Obsidian, LLM Wiki e simili) esistono in entrambi i gruppi anche se vuoti: la sezione vuota riporta *Ancora nessuna skill.* al posto della tabella.
### 4. Tieni aggiornate le statistiche
`python3 scripts/controlla-statistiche.py [--tutto] [--soglia <%>] [scheda ...]` confronta stelle, fork, licenza, versione, installazioni e controlli di sicurezza con i dati attuali (serve `gh`) e mostra cosa aggiornare, senza modificare le schede: correggile a mano, mese della rilevazione compreso. Legge le frasi della sezione *Intestazione* (`circa N stelle`, `circa N installazioni`, `licenza X`, `versione X.Y.Z`, `Controlli di sicurezza su skills.sh`): se le cambi, controlla che lo script le legga ancora.
## Come lavorare qui ## Come lavorare qui
+170
View File
@@ -0,0 +1,170 @@
#!/usr/bin/env python3
"""Confronta le statistiche scritte nelle schede con quelle attuali.
Sola lettura: non modifica nessuna scheda, mostra solo che cosa è cambiato.
Per ogni scheda controlla, quando la scheda li riporta:
- stelle, fork, licenza e ultima versione del repository (GitHub, tramite `gh`)
- installazioni e controlli di sicurezza (pagina della skill su skills.sh)
Uso:
python3 scripts/controlla-statistiche.py # tutte le schede, solo le differenze
python3 scripts/controlla-statistiche.py --tutto # mostra anche i valori invariati
python3 scripts/controlla-statistiche.py --soglia 5 pdf # scarto minimo 5%, solo la scheda pdf
Serve `gh` con l'accesso effettuato (`gh auth login`). Uscita 1 se c'è qualcosa da aggiornare.
"""
import argparse
import json
import re
import subprocess
import sys
import urllib.request
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
SCHEDE = Path(__file__).resolve().parent.parent / "skills"
AUDITOR = ["Gen Agent Trust Hub", "Socket", "Snyk"]
MESI = ["gennaio", "febbraio", "marzo", "aprile", "maggio", "giugno", "luglio",
"agosto", "settembre", "ottobre", "novembre", "dicembre"]
def num_it(cifre, milioni=None):
"""'35.200' -> 35200; '3,9' + 'milioni' -> 3900000."""
if milioni:
return round(float(cifre.replace(",", ".")) * 1_000_000)
return int(cifre.replace(".", "").replace(",", ""))
def num_sksh(testo):
"""'219.7K' -> 219700; '1.4M' -> 1400000; '649' -> 649."""
mult = {"K": 1_000, "M": 1_000_000}.get(testo[-1].upper(), 1)
return round(float(testo.rstrip("KkMm")) * mult)
def fmt(n):
return f"{n:,}".replace(",", ".")
def gh(percorso):
r = subprocess.run(["gh", "api", percorso], capture_output=True, text=True)
return json.loads(r.stdout) if r.returncode == 0 else None
def pagina(url):
try:
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
html = urllib.request.urlopen(req, timeout=25).read().decode("utf-8", "replace")
except Exception:
return None
html = re.sub(r"<(script|style).*?</\1>", " ", html, flags=re.S)
return re.sub(r"\s+", " ", re.sub(r"<[^>]+>", " ", html))
def audit_scheda(testo):
"""Esiti dei controlli come li dice la scheda: {'Socket': 'pass' | 'warn'}."""
m = re.search(r"\*\*Controlli di sicurezza su skills\.sh:\*\*(.*)", testo)
if not m:
return {}
riga = re.sub(r"\([^)]*\)", "", m.group(1))
esiti = {}
for clausola in re.split(r"[;.]", riga):
nomi = [a for a in AUDITOR if a in clausola]
if not nomi:
continue
if re.search(r"avviso|segnala|non superat", clausola):
stato = "warn"
elif "supera" in clausola:
stato = "pass"
else:
continue
esiti.update({a: stato for a in nomi})
return esiti
def confronta(righe, nome, scheda, ora, soglia):
"""Aggiunge una riga numerica; segnala se lo scarto supera la soglia."""
scarto = (ora - scheda) / scheda * 100 if scheda else 100.0
esito = "DA AGGIORNARE" if abs(scarto) >= soglia else "ok"
righe.append((nome, fmt(scheda), fmt(ora), f"{scarto:+.1f}%", esito))
def controlla(percorso, soglia):
testo = percorso.read_text(encoding="utf-8")
righe = []
repo = re.search(r"\*\*Repository:\*\*\s*\[[^\]]*\]\(https://github\.com/([^/\s)]+/[^/\s)]+)\)", testo)
riga_repo = re.search(r"\*\*Repository:\*\*(.*)", testo)
riga_repo = riga_repo.group(1) if riga_repo else ""
# GitHub: stelle, fork, licenza, versione
if repo:
info = gh(f"repos/{repo.group(1)}")
if info is None:
righe.append(("repository", repo.group(1), "non raggiungibile", "", "DA CONTROLLARE"))
else:
if m := re.search(r"circa ([\d.,]+)( milioni)? stelle", testo):
confronta(righe, "stelle", num_it(*m.groups()), info["stargazers_count"], soglia)
if m := re.search(r"([\d.,]+)( milioni)? fork", testo):
confronta(righe, "fork", num_it(*m.groups()), info["forks_count"], soglia)
if m := re.search(r"licenza ([A-Za-z0-9.\-]+)", riga_repo):
ora = (info.get("license") or {}).get("spdx_id") or "nessuna"
esito = "ok" if ora.lower() == m.group(1).lower() else "DA AGGIORNARE"
righe.append(("licenza", m.group(1), ora, "", esito))
if m := re.search(r"versione v?(\d+(?:\.\d+)+)", riga_repo):
rel = gh(f"repos/{repo.group(1)}/releases/latest")
if rel:
ora = rel["tag_name"].lstrip("v")
esito = "ok" if ora == m.group(1) else "DA AGGIORNARE"
righe.append(("versione", m.group(1), ora, "", esito))
if info.get("archived"):
righe.append(("repository", "", "archiviato", "", "DA CONTROLLARE"))
# skills.sh: installazioni e controlli di sicurezza
sksh = re.search(r"\*\*Pagina su skills\.sh:\*\*\s*\[[^\]]*\]\((https://skills\.sh/[^)\s]+)\)", testo)
if sksh:
pg = pagina(sksh.group(1))
if pg is None:
righe.append(("skills.sh", sksh.group(1), "non raggiungibile", "", "DA CONTROLLARE"))
else:
m_sc = re.search(r"circa ([\d.,]+)( milioni)? installazioni", testo)
m_pg = re.search(r"([\d.,]+[KM]?) total installs", pg) or re.search(r"\bInstalls ([\d.,]+[KM]?)(?=\s)", pg)
if m_sc and m_pg:
confronta(righe, "installazioni", num_it(*m_sc.groups()), num_sksh(m_pg.group(1)), soglia)
attesi = audit_scheda(testo)
visti = {a: ("pass" if s == "Pass" else "warn")
for a, s in re.findall(r"(Gen Agent Trust Hub|Socket|Snyk) (Pass|Warn|Fail)", pg)}
for a in AUDITOR:
if a in attesi and a in visti:
esito = "ok" if attesi[a] == visti[a] else "DA AGGIORNARE"
righe.append((f"sicurezza: {a}", attesi[a], visti[a], "", esito))
return righe
def main():
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("schede", nargs="*", help="nomi delle schede da controllare (predefinito: tutte)")
ap.add_argument("--soglia", type=float, default=10.0, help="scarto %% oltre il quale segnalare (predefinito 10)")
ap.add_argument("--tutto", action="store_true", help="mostra anche i valori invariati")
args = ap.parse_args()
elenco = [p for p in sorted(SCHEDE.glob("*.md")) if not args.schede or p.stem in args.schede]
if not elenco:
sys.exit("nessuna scheda trovata")
with ThreadPoolExecutor(max_workers=6) as pool:
risultati = list(pool.map(lambda p: controlla(p, args.soglia), elenco))
da_aggiornare = 0
for percorso, righe in zip(elenco, risultati):
mostra = [r for r in righe if args.tutto or r[4] != "ok"]
da_aggiornare += sum(r[4] != "ok" for r in righe)
if not mostra:
continue
rilevazione = re.search(r"Diffusione \(([^)]+)\)", percorso.read_text(encoding="utf-8"))
print(f"{percorso.stem} (rilevazione: {rilevazione.group(1) if rilevazione else 'non indicata'})")
for nome, scheda, ora, scarto, esito in mostra:
print(f" {nome:<24} scheda {scheda:>12} ora {ora:>12} {scarto:>7} {esito}")
print(f"\n{len(elenco)} schede controllate, {da_aggiornare} voci da aggiornare (soglia {args.soglia:g}%)")
sys.exit(1 if da_aggiornare else 0)
if __name__ == "__main__":
main()