817a8cd5d1
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>
90 lines
5.2 KiB
Markdown
90 lines
5.2 KiB
Markdown
---
|
|
name: preventivo
|
|
description: 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:
|
|
|
|
```bash
|
|
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. **Stato** — `draft` 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.
|