Files
simone 817a8cd5d1 chore(claude): architettura base .claude — skill preventivo e audit, hook di guardia, piani nel repo
La cartella aveva dentro solo rules/ e i settings: nessun posto dove mettere una
skill, un hook o un piano. Ora ha lo scheletro completo e un .claude/CLAUDE.md che
spiega cosa va dove — non duplica CLAUDE.md di progetto, che resta quello che comanda.

Due skill locali (le altre restano globali in ~/.claude/skills/):
- /preventivo — la catena agent.ts → schema.ts → assemble.ts → ProposalDeck e i tre
  modi di romperla, di cui uno solo fa rumore. Nessun prompt di generazione qui
  dentro: quello vive in agent.ts ed e' l'unico. Porta check-profilo.sh.
- /audit — guida scripts/audit-fonti.ts, nuovo, che mette in moto le cinque fonti di
  src/lib/audit/sources/, in prod dal 2026-08-19 ma mai chiamate da nessuno. Provate
  su giojello.com: 5 su 5, 42,7 s, PageSpeed mobile 58 / desktop 93.

Due hook, provati a mano (6 casi il primo, 5 il secondo):
- guardia-migration.sh BLOCCA l'SQL distruttivo sulle entita' protette — il vincolo
  Data Safety (LOCKED) fatto rispettare dalla macchina invece che dalla memoria.
- guardia-token.sh AVVISA sulle classi Tailwind grezze. Non blocca: con ~450
  occorrenze di debito, bloccare lo renderebbe un ostacolo da disattivare.

I tre piani di v2.5 entrano nel repo: stavano solo in ~/.claude/plans/ e STATE.md
avvertiva che senza quelli la milestone non era ricostruibile. Passati al setaccio
per credenziali prima di committarli.

Corretta in rules/memory-discipline.md la chiave della memoria persistente: e'
…-Vault-IAMCAVALLI-hub, non quella del workspace. Sedici file stavano nella prima,
la regola indicava la seconda.

Impeccable resta abilitato solo a livello globale: fuori da settings.json locale.

Nessun tocco al prodotto. Build e lint verdi, lint identico al baseline.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 16:09:14 +02:00

5.2 KiB

name, description
name description
preventivo Lavorare sulla pipeline che genera i preventivi di ClientHub (src/lib/proposal/) senza romperla. Da usare quando si modifica il prompt, lo schema Zod, il montaggio o la resa di un preventivo, quando una generazione fallisce con "Contenuto AI non valido", quando una sezione del documento esce vuota, o prima di pubblicare una proposta a un cliente.

Preventivo — la catena e come non spezzarla

Il preventivo lo genera l'app, da /admin/preventivi/genera. Questa skill non contiene un prompt di generazione e non ne va aggiunto uno: il prompt vive in agent.ts ed e' l'unico. Un secondo prompt qui resterebbe indietro rispetto a quello vero senza che nessuno se ne accorga.

La catena: quattro file che devono dire la stessa cosa

src/lib/proposal/agent.ts          il prompt chiede N campi          ─┐
src/lib/proposal/schema.ts         lo Zod pretende quegli N campi     │  se non
src/lib/proposal/assemble.ts       li impacchetta con prezzi+profilo  │  coincidono,
src/components/public/proposal/    ProposalDeck + sections/ li resa  ─┘  si rompe

Si tocca il prompt, si riapre lo schema. Sempre. I due fallimenti hanno forma diversa e solo uno si vede subito:

Cosa hai fatto Cosa succede
Campo aggiunto allo schema, non al prompt ProposalContentSchema.safeParse fallisce → ogni generazione muore con «Contenuto AI non valido». Rumoroso, si scopre subito
Campo aggiunto al prompt, non allo schema Zod lo scarta in silenzio. Il documento esce senza quella parte, e te ne accorgi davanti al cliente
Campo nello schema, nessuna <Section> che lo legge Generazione verde, sezione assente. Il piu' subdolo: niente segnala l'errore

Quando aggiungi un campo, il giro completo e' quattro file: prompt in buildUserPrompt, schema Zod, eventuale passaggio in assembleProposal, e la sezione in src/components/public/proposal/sections/ piu' la riga in ProposalDeck.tsx che la monta. I vincoli di cardinalita' stanno nello schema (.min(3).max(5) sui problemi, .length(5) sui nodi del diagramma, .min(4).max(10) sulla matrice): se li cambi li' e non nel prompt, il modello continua a produrre il numero vecchio e Zod lo rifiuta.

Prezzi: cosa puo' vedere il cliente

Vincolo LOCKED #2 (../../CLAUDE.md): al cliente vanno i totali, mai le righe di prezzo.

PricingSection.tsx renderizza tier.publicPrice ?? tier.servicesTotal e il nome dei servizi — corretto. Ma assembleProposal mette in content.offer.tiers[].services[].unitPrice anche il prezzo unitario, e ProposalDeck e' "use client": riceve l'intero proposal come prop da un server component, quindi tutto l'oggetto finisce serializzato nel payload RSC della pagina, renderizzato o no.

E' la stessa trappola del gate OTP annotata in STATUS.mdsparire a schermo non e' sparire. Prima di toccare la pagina pubblica, e prima di mandare un preventivo a un cliente che potrebbe aprire il sorgente:

curl -s https://<host>/preventivo/<slug> > /tmp/p.html
LC_ALL=C grep -c unitPrice /tmp/p.html      # atteso a regime: 0

Se e' > 0, la correzione non e' nascondere la sezione: e' una proiezione client-safe in assemble.ts o al confine del componente, come gia' fa src/lib/client-view.ts per il portale.

Prima di pubblicare

  1. Preflight sui dati finti./check-profilo.sh da questa cartella. profile.ts e' uno snapshot: quello che c'e' dentro al momento della generazione finisce in proposals.content e ci resta anche se poi correggi il file.
  2. Le citazioni sono verbatim o non sono. Il prompt lo impone; verificarlo a campione contro il transcript e' il controllo che nessuna macchina fa al posto tuo. Una citazione inventata in un preventivo e' peggio di un preventivo senza citazioni.
  3. Copy — italiano, registro di ../../../../brand/voce.md, e nessun numero che non stia in ../../../../brand/prove.md marcato divulgabile (../../../../.claude/rules/lingua-e-tono.md). ⚠️ brand/ sta fuori dal repo hub: su un clone senza il workspace questo passo si salta dichiarandolo, non si finge di averlo fatto.
  4. Statodraft non e' visibile (la pagina risponde «non ancora disponibile»). Pubblicare = publishProposal in src/app/admin/preventivi/actions.ts.

Come si prova davvero

  • npm run build e' la verifica di riferimento: non esiste test suite, e il build fa il typecheck. Verde qui significa «i tipi tornano», non «il documento e' giusto».
  • Per vedere l'output serve una generazione vera dall'admin, con un'offerta e almeno un transcript. In locale oggi non si puo': .env.local non autentica piu' contro il DB (dal 2026-08-21, vedi STATUS.md). Si guarda in produzione.
  • Un preventivo si legge a schermo prima di mandarlo. «Buildato» non e' «funziona», e «generato» non e' «verificato».

Note sulla chiamata al modello

agent.ts usa max_tokens: 8192 per uno schema che chiede fino a 5 problemi + 5 soluzioni + matrice. Se la risposta viene troncata, il fallimento non dice «troncata»: dice «L'AI ha prodotto JSON non valido», e manda a cercare nel posto sbagliato. Se capita, controllare message.stop_reason === "max_tokens" prima di dare la colpa al parsing.