diff --git a/.claude/commands/step4-review.md b/.claude/commands/step4-review.md index 486858c..61c5566 100644 --- a/.claude/commands/step4-review.md +++ b/.claude/commands/step4-review.md @@ -6,57 +6,110 @@ argument-hint: Esegui la revisione qualitativa di `step-4/$ARGUMENTS/clean.md`. -**Profilo struttura:** -!`cat step-4/$ARGUMENTS/structure_profile.json 2>/dev/null || echo "ERRORE: step-4/$ARGUMENTS/structure_profile.json non trovato — verifica che lo stem sia corretto"` +**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)"` -**Statistiche file:** -!`wc -l step-4/$ARGUMENTS/clean.md 2>/dev/null && grep -c "^## " step-4/$ARGUMENTS/clean.md 2>/dev/null | xargs -I{} echo "## headers: {}" && grep -c "^### " step-4/$ARGUMENTS/clean.md 2>/dev/null | xargs -I{} echo "### headers: {}"` +**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` seguendo questi passi. Usa Grep e Read per raccogliere i casi concreti prima di riportarli. +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). -## Passo 1 — Righe orfane in corpo testo +## Check 1 — Sillabazione residua -Cerca blocchi di testo standalone (separati da righe vuote, non header `#`) più corti di 80 caratteri che sembrino artefatti da PDF: -- Firme o nomi di persone isolati (tutto maiuscolo o iniziali maiuscole seguite da punto) -- Date o luoghi isolati -- Titoli del documento ripetuti nel corpo -- Numeri di pagina isolati -- Riferimenti bibliografici fuori contesto +Righe che terminano con trattino seguito da testo nella riga successiva (artefatto PDF non risolto): -Per ogni caso trovato: riporta numero di riga e testo. +```bash +grep -n "\-$" step-4/$ARGUMENTS/clean.md | head -20 +``` -## Passo 2 — Paragrafi spezzati residui +Segnala se presenti: numero di riga, testo della riga e della riga successiva. -Cerca paragrafi che finiscono senza punteggiatura di fine frase (`.?!»)`), escludendo quelli che terminano con ` -` (interruzione stilistica intenzionale). +## Check 2 — Righe orfane (artefatti PDF) -Riporta i primi 5 casi con numero di riga e la fine del testo. +Righe standalone (non header `#`, non vuote) di meno di 60 caratteri che sembrano artefatti: -## Passo 3 — Header sospetti +```bash +grep -n "^[^#\-\*\|].\{1,59\}$" step-4/$ARGUMENTS/clean.md | grep -v "^\s*$" | head -30 +``` -Controlla i `##` e `###`: -- `### N.` con solo numero e punto → **corretto**, non segnalare -- `##` o `###` con contenuto ALL-CAPS non convertito in sentence-case → segnalare -- `##` o `###` duplicati (stesso titolo che appare due volte) → segnalare -- `##` con testo molto lungo (> 80 char) → segnalare come sospetto +Valuta ogni riga: è testo normale breve (legittimo) o artefatto (numero di pagina, nome autore isolato, riga di intestazione ripetuta)? -## Passo 4 — Sezioni quasi vuote +## Check 3 — Paragrafi con frase spezzata -Cerca `### ` header seguiti da un blocco di testo inferiore a 60 caratteri (sezione priva di contenuto utile per il RAG). Riporta i casi. +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 la qualità del RAG) +🔴 BLOCCANTI (compromettono il chunking o il retrieval) + [riga N] descrizione precisa del problema ... -🟡 MINORI (artefatti visibili ma non bloccanti) +🟡 MINORI (artefatti visibili, non bloccanti) + [riga N] descrizione ... -🟢 OK - ... +🟢 OK — nessun problema rilevato in questa categoria ``` -Chiedi all'utente: "Vuoi che applichi le correzioni per i problemi 🔴? E per i 🟡?" -Applica solo le correzioni esplicitamente approvate. +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/commands/step6-fix.md b/.claude/commands/step6-fix.md index d4def15..66a88a4 100644 --- a/.claude/commands/step6-fix.md +++ b/.claude/commands/step6-fix.md @@ -1,77 +1,120 @@ --- -description: Legge il report di step 6, mostra le fix pianificate e le applica su approvazione tramite fix_chunks.py. +description: Verifica i chunk di step 5, mostra i problemi, propone e applica le fix tramite fix_chunks.py con ri-verifica automatica finale. allowed-tools: Read Bash Grep argument-hint: --- -Leggi e interpreta `step-6/$ARGUMENTS/report.json`. +## Passo 0 — Verifica fresca (sempre) -**Report:** -!`cat step-6/$ARGUMENTS/report.json 2>/dev/null || echo "ERRORE: report.json non trovato — esegui prima: python step-6/verify_chunks.py --stem $ARGUMENTS"` +Esegui sempre `verify_chunks.py` per avere un report aggiornato (non fidarti di un report.json preesistente): + +```bash +source .venv/bin/activate && python step-6/verify_chunks.py --stem $ARGUMENTS +``` --- -In base al contenuto del report, segui le istruzioni qui sotto. +Leggi il report appena generato: + +!`python3 -c " +import json, sys +try: + r = json.load(open('step-6/$ARGUMENTS/report.json')) + v = r.get('verdict','?') + s = r.get('stats', {}) + t = r.get('thresholds', {}) + print(f'Verdict: {v}') + print(f'Totale chunk: {s.get(\"total\",\"?\")} | OK: {s.get(\"ok\",\"?\")}') + print(f'Min: {s.get(\"min_chars\",\"?\")} char Max: {s.get(\"max_chars\",\"?\")} char Media: {s.get(\"avg_chars\",\"?\")} char') + print(f'Soglie: MIN={t.get(\"min_chars\",200)} MAX={t.get(\"max_chars\",800)}') + bl = r.get('blockers', {}) + wa = r.get('warnings', {}) + for cat, label in [('empty','Vuoti'), ('no_prefix','Senza prefisso'), ('incomplete','Frasi spezzate')]: + items = bl.get(cat, []) + if items: + print(f' 🔴 {label}: {len(items)}') + for c in items[:3]: + print(f' [{c[\"chunk_id\"]}] {c[\"n_chars\"]} char → {c[\"last_text\"][-60:]!r}') + for cat, label in [('too_short','Troppo corti'), ('too_long','Troppo lunghi')]: + items = wa.get(cat, []) + if items: + print(f' 🟡 {label}: {len(items)}') + for c in items[:3]: + print(f' [{c[\"chunk_id\"]}] {c[\"n_chars\"]} char') +except Exception as e: print(f'ERRORE lettura report: {e}') +" 2>/dev/null` + +--- ## Se verdict == "ok" -Non c'è nulla da fare. Comunica all'utente che può procedere: +✅ Nessun problema. Comunica: ``` -✅ Nessun problema — procedi con la vettorizzazione: +✅ Chunk puliti — procedi con la vettorizzazione: python step-8/ingest.py --stem $ARGUMENTS ``` +Fermati qui. Non eseguire nessun altro passo. + +--- + ## Se verdict == "warnings_only" o "blocked" -### Passo 1 — Mostra le fix pianificate (dry-run) - -Esegui il dry-run e mostra l'output all'utente: +### Passo 1 — Dry-run ```bash source .venv/bin/activate && python step-6/fix_chunks.py --stem $ARGUMENTS --dry-run ``` -Spiega brevemente cosa farà ogni operazione: -- **rimuovi chunk vuoti**: elimina chunk senza testo -- **aggiungi prefisso**: inserisce `[sezione > titolo]` dove manca -- **fondi incompleti**: unisce i chunk con frase spezzata al chunk successivo -- **fondi troppo corti**: unisce chunk < 200 char con il successivo -- **spezza troppo lunghi**: divide chunk > 1200 char al confine di paragrafo/frase +Spiega in italiano ogni operazione pianificata: +- **rimuovi chunk vuoti** — chunk privi di testo, non contribuiscono al retrieval +- **aggiungi prefisso** — il prefisso `[sezione > titolo]` fornisce contesto all'embedding; senza, il chunk è semanticamente decontestualizzato +- **fondi incompleti** — frase spezzata a metà: il chunk corrente e il successivo formano una frase unica +- **fondi troppo corti** — chunk sotto MIN_CHARS: troppo brevi per portare informazione semantica utile +- **spezza troppo lunghi** — chunk sopra MAX_CHARS×1.5: troppo densi, degradano la precision del retrieval -### Passo 2 — Chiedi conferma +Se ci sono solo 🟡 (nessun 🔴), informa che si può procedere anche senza fix e chiedi la preferenza. -Chiedi all'utente: **"Applico le correzioni?"** +### Passo 2 — Conferma -Applica solo su approvazione esplicita. +Chiedi: **"Applico le correzioni?"** -### Passo 3 — Applica e ri-verifica +Applica solo su risposta affermativa esplicita. + +### Passo 3 — Applica ```bash -source .venv/bin/activate -python step-6/fix_chunks.py --stem $ARGUMENTS -python step-6/verify_chunks.py --stem $ARGUMENTS +source .venv/bin/activate && python step-6/fix_chunks.py --stem $ARGUMENTS ``` -Leggi il nuovo `step-6/$ARGUMENTS/report.json` e comunica: -- Il nuovo verdict -- Quanti warning/blockers residui rimangono -- Se il verdict è `ok` o `warnings_only` senza blockers: comunica che si può procedere con step-8 +### Passo 4 — Ri-verifica automatica -## Dopo le correzioni +```bash +source .venv/bin/activate && python step-6/verify_chunks.py --stem $ARGUMENTS +``` -Se il verdict finale è `ok`: +Leggi il nuovo `step-6/$ARGUMENTS/report.json` e riporta: +- Nuovo verdict +- Delta chunk (N prima → N dopo) +- Problemi residui se presenti + +### Passo 5 — Conclusione + +Se verdict finale è `ok` o `warnings_only` senza 🔴: ``` -✅ chunks.json pulito salvato in step-6/$ARGUMENTS/ +✅ Chunk pronti in step-6/$ARGUMENTS/chunks.json Procedi con la vettorizzazione: python step-8/ingest.py --stem $ARGUMENTS ``` -Se rimangono warning minori (too_short/too_long non risolvibili perché testo non spezzabile): +Se rimangono 🔴 dopo il fix (raro — testo non spezzabile o struttura anomala): ``` -🟡 X warning residui non risolvibili automaticamente (testo non spezzabile). - Puoi procedere comunque con la vettorizzazione: - python step-8/ingest.py --stem $ARGUMENTS +🔴 X problemi residui non risolvibili automaticamente. + Torna a step-4/$ARGUMENTS/clean.md e correggi manualmente le sezioni indicate, + poi riesegui nell'ordine: + python step-5/chunker.py --stem $ARGUMENTS --force + python step-6/verify_chunks.py --stem $ARGUMENTS ```