From 368530bc2510ae9f5efc195d576c13ffa6c4c2da Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Fri, 17 Apr 2026 13:44:41 +0200 Subject: [PATCH] refactor(docs): skill prepare-md sostituisce step4-review, CLAUDE.md senza step-X --- .claude/commands/prepare-md.md | 199 +++++++++++++++++++++++++++++++ .claude/commands/step4-review.md | 115 ------------------ CLAUDE.md | 73 +++++------- 3 files changed, 232 insertions(+), 155 deletions(-) create mode 100644 .claude/commands/prepare-md.md delete mode 100644 .claude/commands/step4-review.md diff --git a/.claude/commands/prepare-md.md b/.claude/commands/prepare-md.md new file mode 100644 index 0000000..0ed1f30 --- /dev/null +++ b/.claude/commands/prepare-md.md @@ -0,0 +1,199 @@ +--- +description: Legge un file Markdown, individua tutti i problemi che compromettono il chunking (artefatti, sillabazione, header malformati, paragrafi spezzati, gerarchia incoerente, sezioni vuote) e applica le correzioni direttamente sul file senza chiedere conferma per i casi chiari. +allowed-tools: Read Bash Grep Edit +argument-hint: +--- + +Risolvi il percorso del file da preparare: + +!`python3 -c " +import sys, json, re +from pathlib import Path + +arg = '$ARGUMENTS'.strip() +root = Path('.') + +candidates = [ + Path(arg), + root / arg, + root / 'conversione' / arg / 'clean.md', + root / 'step-4' / arg / 'clean.md', +] + +md_path = None +for p in candidates: + if p.exists() and p.suffix == '.md': + md_path = p + break + +if not md_path: + print('ERRORE: file non trovato per:', arg) + sys.exit(1) + +print('MD_PATH=' + str(md_path)) + +# Cerca profilo strutturale (report.json o structure_profile.json) +stem = md_path.parent.name +profile_candidates = [ + md_path.parent / 'report.json', + md_path.parent / 'structure_profile.json', + root / 'step-4' / stem / 'structure_profile.json', + root / 'conversione' / stem / 'report.json', +] +for sp in profile_candidates: + if sp.exists(): + try: + d = json.load(open(sp)) + st = d.get('structure', d) + print(f'STRATEGIA={st.get(\"strategia_chunking\",\"?\")}') + print(f'LINGUA={st.get(\"lingua_rilevata\",\"?\")}') + print(f'H1={st.get(\"n_h1\",0)} H2={st.get(\"n_h2\",0)} H3={st.get(\"n_h3\",0)}') + for a in st.get('avvertenze', []): + print(f'AVVISO: {a}') + except Exception: + pass + break + +# Statistiche file +text = md_path.read_text(encoding='utf-8') +lines = text.split('\n') +pua = len(re.findall(r'[\ue000-\uf8ff]', text)) +print(f'RIGHE={len(lines)} CHARS={len(text)}') +if pua: + print(f'PUA_RESIDUI={pua}') +" 2>/dev/null` + +Se l'output contiene `ERRORE`, comunica il percorso non trovato e fermati. + +--- + +Leggi il file completo identificato da `MD_PATH` nell'output sopra. Poi esegui **tutti** i controlli e applica le correzioni nell'ordine indicato. + +I parametri di riferimento per il chunking sono: **MIN_CHARS=200, MAX_CHARS=800**. + +--- + +## Controllo 1 — Sillabazione residua + +Cerca blocchi di testo (non header) dove una riga termina con `-` e la successiva inizia con lettera minuscola: è un'interruzione di parola non risolta da PDF. + +Esempio da correggere: +``` +...il meccanismo di decen- +tralizzazione permette... +``` +→ `...il meccanismo di decentralizzazione permette...` + +**Applica** ogni fusione con Edit. Se la parola ricomposta sembra errata, segnala invece di correggere. + +--- + +## Controllo 2 — Artefatti di pagina + +Righe standalone che sono esclusivamente: +- Un numero intero isolato (numero di pagina) +- Titolo del libro / nome autore che si ripete identico 3+ volte nel documento +- Intestazioni di capitolo che si ripetono (es. `## 3. Termodinamica` appare sia come header legittimo che come riga di testo duplicata) + +**Applica** la rimozione con Edit per le ripetizioni chiaramente decorative. Segnala i casi ambigui. + +--- + +## Controllo 3 — Numeri di pagina in header + +Header che terminano con ` | N` o ` N` dove N è un numero isolato (residuo di indice non rimosso): +- `### 16. Link vari | 109` → `### 16. Link vari` +- `## Capitolo 3 42` → `## Capitolo 3` + +**Applica** con Edit. + +--- + +## Controllo 4 — Header malformati + +Per ogni header (`#`, `##`, `###`): + +**a) ALL-CAPS non convertito:** +`## TERMODINAMICA DEI PROCESSI` → `## Termodinamica dei processi` +Usa sentence case (prima lettera maiuscola, resto minuscolo salvo nomi propri evidenti). +**Applica**. + +**b) Livello h4/h5/h6:** +`#### Sottosezione` → `### Sottosezione` +**Applica**. + +**c) Testo troppo lungo (> 120 char):** +Probabilmente non è un header ma testo estratto erroneamente. Rimuovi i `#` iniziali lasciando il testo come paragrafo normale. +**Applica** se chiaramente non è un titolo. Segnala se ambiguo. + +**d) Header duplicati:** +Se lo stesso header appare due volte, rimuovi la seconda occorrenza (o la prima se è quella fuori contesto). +**Applica**. + +--- + +## Controllo 5 — Paragrafi spezzati + +Blocchi di testo (non header, non liste) che terminano senza punteggiatura finale (`.?!»)`). + +Se il blocco successivo non inizia con lettera maiuscola e non è un header/lista, i due blocchi sono parte della stessa frase spezzata da un salto pagina PDF. + +**Applica** la fusione solo quando sei certo (la congiunzione è evidente: inizia con congiunzione, continua la frase in modo inequivocabile). Segnala i casi dubbi invece di correggere. + +--- + +## Controllo 6 — Sezioni quasi-vuote o vuote + +Sezione (header + corpo) con corpo < 100 caratteri: +- Se il contenuto è evidentemente una sottosezione o introduzione di ciò che segue (e non ha senso da solo), rimuovi l'header e unisci il testo alla sezione precedente o successiva. +- Se è un header di capitolo che introduce legittime sottosezioni (`##` seguito da `###`), lascia invariato. + +**Applica** le fusioni sicure. Segnala quelle ambigue. + +--- + +## Controllo 7 — Gerarchia heading + +Verifica che la gerarchia sia coerente. Problemi da correggere: + +- Più di un `# ` (h1) nel documento → il secondo e successivi diventano `## ` salvo che siano chiaramente titoli di parti distinte +- `### ` prima del primo `## ` → abbassa il `###` a `## ` o aggiungi un `## ` genitore appropriato +- `## ` prima del primo `# ` in documenti con h1 → lascia invariato (alcuni documenti non hanno h1) + +**Applica** solo le correzioni di livello sicure. Segnala le ristrutturazioni che richiedono giudizio. + +--- + +## Controllo 8 — Sezioni troppo lunghe senza struttura + +Sezione (## o ###) con corpo > 3000 caratteri e nessun header figlio al suo interno: il chunker la spezzerà su frasi in modo meccanico, perdendo coerenza semantica. + +Se il testo contiene chiari cambio-argomento (paragrafi separati da riga vuota, con transizioni come "Inoltre...", "In secondo luogo...", "Un altro aspetto..."), considera di aggiungere un `### ` per suddividere semanticamente. + +**Non aggiungere header inventati.** Segnala le sezioni candidate e proponi i titoli: applica solo su risposta affermativa. + +--- + +## Report finale + +Dopo aver applicato tutte le correzioni automatiche, mostra: + +``` +File: +Correzioni applicate: N totali + + Sillabazione risolta: N + Artefatti pagina rimossi: N + Numeri pagina in header: N + Header normalizzati: N (ALL-CAPS, livello, lunghezza, duplicati) + Paragrafi fusi: N + Sezioni quasi-vuote risolte:N + Gerarchia corretta: N + +Problemi aperti (richiedono giudizio manuale): + [riga N] + ... +``` + +Se non ci sono problemi aperti: **"Markdown pronto per il chunking."** +Se ci sono problemi aperti: elencali e chiedi quali applicare. diff --git a/.claude/commands/step4-review.md b/.claude/commands/step4-review.md deleted file mode 100644 index 61c5566..0000000 --- a/.claude/commands/step4-review.md +++ /dev/null @@ -1,115 +0,0 @@ ---- -description: Revisione qualitativa del clean.md dopo il pre-processing automatico (step 4). Trova artefatti residui, paragrafi spezzati e header errati, poi propone le correzioni. -allowed-tools: Read Bash Grep Edit -argument-hint: ---- - -Esegui la revisione qualitativa di `step-4/$ARGUMENTS/clean.md`. - -**Cosa è già stato fatto automaticamente (revision_log):** -!`grep -A 12 "^## $ARGUMENTS" step-4/revision_log.md 2>/dev/null || echo "(nessun log trovato per questo stem)"` - -**Profilo strutturale attuale:** -!`python3 -c " -import json, sys -try: - d = json.load(open('step-4/$ARGUMENTS/structure_profile.json')) - print(f'Livello: {d[\"livello_struttura\"]} Strategia: {d[\"strategia_chunking\"]}') - print(f'h1={d[\"n_h1\"]} h2={d[\"n_h2\"]} h3={d[\"n_h3\"]} paragrafi={d[\"n_paragrafi\"]}') - print(f'Lunghezza media sezione: {d[\"lunghezza_media_sezione\"]} char') - for a in d.get('avvertenze', []): print(f' ⚠️ {a}') -except Exception as e: print(f'ERRORE: {e}') -" 2>/dev/null` - ---- - -Analizza `step-4/$ARGUMENTS/clean.md` eseguendo i grep seguenti e ragionando sui risultati. Per ogni check: esegui il grep, conta i risultati, riporta i casi concreti (max 5 esempi con numero di riga). - -## Check 1 — Sillabazione residua - -Righe che terminano con trattino seguito da testo nella riga successiva (artefatto PDF non risolto): - -```bash -grep -n "\-$" step-4/$ARGUMENTS/clean.md | head -20 -``` - -Segnala se presenti: numero di riga, testo della riga e della riga successiva. - -## Check 2 — Righe orfane (artefatti PDF) - -Righe standalone (non header `#`, non vuote) di meno di 60 caratteri che sembrano artefatti: - -```bash -grep -n "^[^#\-\*\|].\{1,59\}$" step-4/$ARGUMENTS/clean.md | grep -v "^\s*$" | head -30 -``` - -Valuta ogni riga: è testo normale breve (legittimo) o artefatto (numero di pagina, nome autore isolato, riga di intestazione ripetuta)? - -## Check 3 — Paragrafi con frase spezzata - -Blocchi di testo che terminano senza punteggiatura di fine frase (`.?!»)`): - -```bash -grep -n "[^.!?»)\]\'\"]$" step-4/$ARGUMENTS/clean.md | grep -v "^[0-9]*:#" | grep -v "^[0-9]*:\s*$" | grep -v "^\s*[-\*]" | head -20 -``` - -Riporta i casi più sospetti (righe brevi che finiscono a metà concetto). - -## Check 4 — Header sospetti - -```bash -grep -n "^##\? " step-4/$ARGUMENTS/clean.md | head -40 -``` - -Verifica: -- `##` o `###` con contenuto interamente MAIUSCOLO non convertito → segnala -- Header duplicati (stesso testo che appare due volte) → segnala -- `##` con testo > 80 caratteri (probabile testo che non è un header) → segnala -- Salti di livello anomali (es. `###` senza un `##` padre) → segnala - -## Check 5 — Sezioni quasi vuote - -```bash -python3 -c " -import re, sys -text = open('step-4/$ARGUMENTS/clean.md').read() -sections = re.split(r'^(#{1,3} .+)$', text, flags=re.MULTILINE) -for i in range(1, len(sections)-1, 2): - header = sections[i].strip() - body = sections[i+1].strip() if i+1 < len(sections) else '' - if len(body) < 80 and body: - print(f'{header!r} → {len(body)} char: {body[:60]!r}') - elif not body: - print(f'{header!r} → VUOTA') -" 2>/dev/null | head -20 -``` - -Sezioni con body < 80 char o vuote compromettono il chunking. Segnala quelle che non hanno senso come sezione autonoma. - -## Check 6 — Gerarchia strutturale - -```bash -grep -n "^#\{1,3\} " step-4/$ARGUMENTS/clean.md | head -50 -``` - -Verifica che la gerarchia sia coerente: `# → ## → ###`. Segnala se ci sono `###` prima del primo `##`, o `##` prima del primo `#`, o `#` multipli (più di un h1). - ---- - -## Report finale - -``` -🔴 BLOCCANTI (compromettono il chunking o il retrieval) - [riga N] descrizione precisa del problema - ... - -🟡 MINORI (artefatti visibili, non bloccanti) - [riga N] descrizione - ... - -🟢 OK — nessun problema rilevato in questa categoria -``` - -Poi chiedi: **"Applico le correzioni per i 🔴? E per i 🟡?"** - -Applica solo ciò che viene esplicitamente approvato. Usa Edit per ogni modifica — mai riscrivere l'intero file. diff --git a/CLAUDE.md b/CLAUDE.md index 4b25071..698d0e4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,83 +4,76 @@ - **Lingua:** Rispondi sempre in italiano. - **Venv obbligatorio:** Usa `.venv/bin/python` o attiva con `source .venv/bin/activate`. Mai `pip`/`python` di sistema. -- **Non modificare `raw.md`:** `step-2//raw.md` è immutabile. La copia di lavoro è `step-4//clean.md`. +- **Non modificare `raw.md`:** Il file `raw.md` di ogni stem è immutabile. La copia di lavoro è sempre `clean.md`. --- -## Pipeline (ordine obbligatorio) +## Pipeline (operazioni in ordine) ``` -PDF (sources/) → step-0 → step-1 → step-2 → step-3 - → step-4 (CRITICO: revisione manuale clean.md) - → step-5 → step-6 → step-7 (Ollama) → step-8 → step-9 +PDF (sources/) + → conversione (PDF → clean.md + structure_profile.json) + → chunking (clean.md → chunks.json) + → verifica (chunks.json → report + fix automatici) + → vettorizzazione (chunks.json → ChromaDB) + → retrieval (query → risposta via Ollama) ``` Il parametro `--stem` identifica il documento (nome PDF senza `.pdf`). Lo stem è anche il nome della collection ChromaDB. -Comandi tipici: -```bash -source .venv/bin/activate -python step-4/revise.py --stem -python step-5/chunker.py --stem -python step-6/verify_chunks.py --stem -python step-8/ingest.py --stem -python step-9/rag.py --stem -``` - --- ## File critici | File | Ruolo | |---|---| -| `step-9/config.py` | Fonte di verità: `EMBED_MODEL`, `OLLAMA_MODEL`, `TOP_K`, `TEMPERATURE`, `SYSTEM_PROMPT` | -| `step-5/chunker.py` | Chunking adattivo — `MIN_CHARS=200`, `MAX_CHARS=800`, `OVERLAP_S=2` | -| `step-6/verify_chunks.py` | Verifica chunk — stesse soglie di `chunker.py` | -| `step-6/fix_chunks.py` | Fix automatici su chunk anomali | -| `step-4/revise.py` | Pre-processing MD automatico (8 trasformazioni euristiche) | -| `step-8/ingest.py` | Vettorizzazione ChromaDB — legge `EMBED_MODEL` da `config.py` | -| `step-9/rag.py` | Pipeline RAG interattiva | +| `config.py` | Fonte di verità: `EMBED_MODEL`, `OLLAMA_MODEL`, `TOP_K`, `TEMPERATURE`, `SYSTEM_PROMPT` | +| `chunker.py` | Chunking adattivo — `MIN_CHARS=200`, `MAX_CHARS=800`, `OVERLAP_S=2` | +| `verify_chunks.py` | Verifica chunk — stesse soglie di `chunker.py` | +| `fix_chunks.py` | Fix automatici su chunk anomali | +| `ingest.py` | Vettorizzazione ChromaDB — legge `EMBED_MODEL` da `config.py` | +| `rag.py` | Pipeline RAG interattiva | +| `conversione/pipeline.py` | Conversione PDF → clean Markdown strutturato | --- ## Regole di assistenza -**Modifica `EMBED_MODEL` in `step-9/config.py`:** +**Modifica `EMBED_MODEL` in `config.py`:** Avvisa sempre che serve rieseguire la vettorizzazione: ```bash -python step-8/ingest.py --stem --force +python ingest.py --stem --force ``` `ingest.py` importa `EMBED_MODEL` direttamente da `config.py` — la coerenza è critica: se violata non produce errori ma restituisce risultati insensati. **Modifica soglie chunking (`MIN_CHARS`, `MAX_CHARS`, `OVERLAP_S`):** -I valori compaiono in tre file che vanno sincronizzati manualmente: -1. `step-5/chunker.py` -2. `step-6/verify_chunks.py` -3. `step-6/fix_chunks.py` +I valori compaiono in più file che vanno sincronizzati manualmente: +- `chunker.py` +- `verify_chunks.py` +- `fix_chunks.py` -**Step 4 — revisione clean.md:** -`revise.py` applica trasformazioni automatiche, ma il risultato va sempre revisionato a mano. La qualità del RAG dipende da `clean.md` più di qualsiasi parametro tecnico. Suggerisci sempre `/step4-review ` dopo `revise.py`. +**Conversione PDF → Markdown:** +`conversione/pipeline.py` produce `raw.md` e `clean.md`. Il `clean.md` va sempre revisionato dopo la conversione automatica — la qualità del RAG dipende da esso più di qualsiasi parametro tecnico. Suggerisci sempre `/prepare-md conversione//clean.md` dopo la conversione. -**Step 6 — verifica chunk:** -Dopo `verify_chunks.py`, usa `/step6-fix ` prima di passare a step-8. +**Verifica chunk:** +Dopo `verify_chunks.py`, usa `/step6-fix ` prima di procedere con la vettorizzazione. --- ## Skills custom -- `/step4-review ` — Revisione qualitativa `clean.md`: artefatti, paragrafi spezzati, header errati. +- `/prepare-md ` — Revisione e correzione automatica di qualsiasi `clean.md`: sillabazione, artefatti, header malformati, paragrafi spezzati, gerarchia, sezioni vuote. Accetta path completo (`conversione/bitcoin/clean.md`) o stem (`bitcoin`). - `/step6-fix ` — Dry-run e applicazione fix chunk tramite `fix_chunks.py`. --- -## Struttura directory per stem +## Output per stem ``` -step-2//raw.md ← immutabile -step-4//clean.md ← copia di lavoro -step-4//structure_profile.json -step-5//chunks.json -step-6//report.json -chroma_db// ← collection ChromaDB +conversione//raw.md ← immutabile +conversione//clean.md ← copia di lavoro +conversione//structure_profile.json +/chunks.json +/report.json +chroma_db// ← collection ChromaDB ```