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>
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.md — sparire 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
- Preflight sui dati finti —
./check-profilo.shda questa cartella.profile.tse' uno snapshot: quello che c'e' dentro al momento della generazione finisce inproposals.contente ci resta anche se poi correggi il file. - 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.
- Copy — italiano, registro di
../../../../brand/voce.md, e nessun numero che non stia in../../../../brand/prove.mdmarcato 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. - Stato —
draftnon e' visibile (la pagina risponde «non ancora disponibile»). Pubblicare =publishProposalinsrc/app/admin/preventivi/actions.ts.
Come si prova davvero
npm run builde' 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.localnon autentica piu' contro il DB (dal 2026-08-21, vediSTATUS.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.