Skill
Redigi i Dev Report
Definisce struttura, tono, vincoli di periodo e regole anti-troncamento per i Dev Report di CH Labs.
File
skills/dev-report-editorial.md
Prompt
name: dev-report-editorial description: Regole editoriali per i Dev Report di CH Labs (report settimanali/periodici basati sui commit). Spiega struttura, tono, regole di periodo, anti-troncamento e dove vivono nel codice.
Skill: Editorial dei Dev Report
A cosa serve
I Dev Report di CH Labs sono articoli giornalistici autogenerati dai commit GitHub di ogni progetto attivo. Vengono generati settimanalmente da un cron Vercel (lunedì 9:00 UTC) o on-demand via API. Questa skill è il riferimento unico per le regole editoriali: tono, struttura, lingua, vincoli di periodo, lunghezza, anti-troncamento.
Regola d'oro: se devi cambiare una regola editoriale, modifica lib/editorial/rules.ts. Mai sparpagliare regex o frasi vietate in altri file.
Dove vive il codice
`` lib/editorial/ rules.ts # SINGLE SOURCE OF TRUTH: sezioni, frasi proibite, tono, lunghezze types.ts # tipi (ReportSection, EditorialRules, PeriodCopy, ...) dates.ts # period copy, formattazione date italiane, calcolo giorni inclusivi prompt.ts # buildSystemPrompt, buildContinuationPrompt, getMaxOutputTokens parse.ts # estrae title/summary/content dal raw markdown dell'AI normalize.ts # applica le forbiddenPhrases dopo l'AI validate.ts # isReportComplete, isSummaryComplete, detectTruncation render.ts # markdownToHtml + helper di formattazione fallback.ts # report deterministico quando l'AI fallisce index.ts # composeReport(): orchestrazione completa con recovery ``
Orchestrazione end-to-end: lib/dev-reports.ts (fetch progetto + commit, costruisce input, chiama composeReport, salva su Supabase).
Pipeline di generazione
- Cron o API →
generateWeeklyReport(projectId)inlib/dev-reports.ts. - Recupera config progetto da
project_github_config(Supabase). - Calcola
weekStart/weekEndcongetWeekBoundaries+ costruiscePeriodCopycongetReportPeriod. - Fetcha commit GitHub via
fetchRecentCommits+ README viabuildRepoContext. - Costruisce
ReportComposeInput+FallbackInput+aiCall(provider Anthropic Haiku,claude-haiku-4-5, viaANTHROPIC_API_KEY; il caller Gemini resta disponibile come alternativa). - Chiama
composeReport({input, period, aiCall, fallback})dalib/editorial:
buildSystemPrompt→ primo tentativo AIparseAiOutput+detectTruncation- se troncato:
buildContinuationPrompt+ secondo tentativo + merge normalizeReport(forbidden phrases)isReportComplete+isSummaryComplete- se ancora incompleto:
generateFallbackReport(deterministico)
- Salva su
dev_reports(Supabase).
Regole editoriali consolidate
Lingua e tono
- Lingua: italiano.
- Target: investitori SaaS-oriented, comprensione business ma non necessariamente tech.
- Tono: editoriale, scorrevole, concreto. Ogni frase supportata da fatti nei commit o nelle metriche.
- Vietati: hype, claim non dimostrabili, aggettivi promozionali, frasi vaghe ("migliorata la stabilità", "abbiamo lavorato duramente", "rivoluzionario", "best-in-class", "game-changer"). Lista in
EDITORIAL_RULES.tone.banned.
Struttura obbligatoria (4 sezioni in quest'ordine)
- Cosa abbiamo fatto questa settimana (se periodo = 7 giorni) / Cosa abbiamo fatto nel periodo (altrimenti) — sezione dinamica, gestita da
getActivityHeading(period.isWeek). - Highlights tecnici — lista di 2-8 bullet, ognuno una capability o milestone reale.
- Progressi — paragrafo con numeri concreti che interpreta le metriche.
- Prossimi passi — indicazioni operative coerenti, non auspici generici.
Le sezioni sono dichiarate in EDITORIAL_RULES.sections. Il prompt builder le itera per generare il blocco "STRUTTURA OBBLIGATORIA". Il validatore le itera per verificare presenza.
Regole di periodo (caso "settimanale")
Background: in passato l'AI generava report con frasi tipo "ultima settimana" o "settimanale" anche per intervalli di 30 giorni, perché il prompt diceva "settimana" indipendentemente dalla durata reale.
Soluzione attuale (2 strati):
- Prompt-side: se
period.isWeek === false, il prompt esplicita "Vietato usare 'settimanale', 'settimanali', 'questa settimana', 'ultima settimana'. Usa '${period.label}' (es. 'periodo di 30 giorni')." - Post-AI normalization:
normalizeReportapplicaEDITORIAL_RULES.forbiddenPhrasescon condizionedays_neq_7. Anche se l'AI sbaglia, le regex correggono.
Aggiungere una nuova frase vietata: modifica solo EDITORIAL_RULES.forbiddenPhrases in rules.ts. Nessun altro file.
Lunghezza dinamica
- Contenuto: 1.200-3.500 caratteri. Banda stretta per forzare concisione: l'AI resta vicino al minimo se il volume di lavoro è basso.
- Summary: 80-240 caratteri, una o due frasi chiuse.
- Bullet per sezione: 2-6, ognuno su una sola riga.
- Le soglie vivono in
EDITORIAL_RULES.lengths. Non duplicare numeri altrove. getMaxOutputTokens()calcola il cap dell'AI a partire dalla soglia massima (≈ 0.4 token/char per italiano + margine).
Voce: conciso, tecnico, diretto
L'obiettivo è prosa densa e scorrevole, leggibile dal profano ma asciutta. Le direttive vivono in EDITORIAL_RULES.tone.defaults (iterate nel prompt come "REGOLE DI INTERPRETAZIONE") e nel blocco "REGOLE PER IL CONTENUTO" di prompt.ts: una idea per frase, voce attiva, niente preamboli o chiusure retoriche, gergo chiarito in mezza riga, titolo breve (max ~70 caratteri) senza sottotitoli con due punti.
Trattino lungo (— / –) vietato, come da house style. Doppio strato: il prompt lo proibisce e forbiddenPhrases con when: "always" lo sostituisce con virgola anche se l'AI sbaglia.
Filtro di stile linguistico
Sopra le regole di tono vale il filtro di stile della Prompt Library. La fonte unica è il file content/prompt-library/chat-preferences/filtra-stile-linguistico.md: il suo body viene caricato a runtime e iniettato nel system prompt come blocco "FILTRO DI STILE LINGUISTICO". Impone scrittura diretta e affermativa, vieta le parole "tradizione/tradizionale/rituale/simbolico" e gli equivalenti inglesi, "davvero" come intensificatore e "crucial", limita i due punti ai casi indispensabili nella prosa, esclude la virgola di Oxford in italiano e proibisce la negazione contrastiva ("non X ma Y"), da riscrivere in forma affermativa.
Catena di caricamento: getStyleFilterPrompt() in lib/prompt-library.server.ts legge il body del file → lib/dev-reports.ts lo passa a composeReport({ ..., styleFilter }) → buildSystemPrompt lo rende verbatim sotto l'header del blocco. Non esiste una copia delle direttive nel codice: per cambiare il filtro si edita solo il file .md, e la modifica si propaga ai Dev Report e a ogni altra skill del sito CH che chiama getStyleFilterPrompt(). Se il file non si carica, il blocco viene omesso e parte un warning; il trattino lungo resta comunque coperto dal layer post-AI in forbiddenPhrases.
Anti-troncamento
Regola: un report non deve mai uscire troncato. Se accade, è un bug, non un comportamento accettabile.
detectTruncation segna il report come troncato se almeno una di queste è vera:
finishReason === "MAX_TOKENS"dalla risposta API- ultima frase non chiusa (no
.,!,?,:,",») - manca una sezione obbligatoria
- l'ultima riga è un marker di lista vuoto (
-da solo)
Recovery (composeReport):
- Primo tentativo AI normale.
- Se troncato → continuation: si passa il markdown parziale e si chiede "completa da dove ti sei fermato, non ripetere, chiudi le sezioni mancanti".
- Se ancora troncato →
generateFallbackReportdeterministico (per costruzione completo).
Mai troncare a posteriori con slice() per rispettare un limite. Se serve accorciare, è l'AI a riscrivere.
Markdown e rendering
L'AI produce Markdown semplice. parseAiOutput lo estrae, normalizeReportContent (in render.ts) lo converte in HTML che viene salvato su Supabase. Il client lo mostra con dangerouslySetInnerHTML dentro un div.dev-report-content (stile in app/globals.css).
Il parser gestisce: H1 (saltato, il titolo è separato), H2/H3, liste - o , paragrafi separati da riga vuota, bold, italic*, riconoscimento euristico di righe corte dopo : come list items.
Un bullet -/* può essere hard-wrappato su più righe sorgente (singolo a-capo, senza riga vuota): le righe di continuazione vengono accodate allo stesso <li> via il buffer listItem in markdownToHtml. Senza questo, un bold spezzato a metà riga lasciava gli asterischi orfani e buttava la coda fuori dalla lista. Se ricompare un'impaginazione rotta nei bullet, il sospetto è una continuazione di list item non gestita lì.
Date in italiano
Un solo helper: formatItalianDate(dateStr) in lib/editorial/dates.ts. Restituisce "28 aprile 2026". Non duplicare toLocaleDateString("it-IT", ...) altrove.
Cosa fare quando...
...l'utente segnala una frase generica/vaga/banale che ricorre nei report:
- Aggiungila a
EDITORIAL_RULES.tone.bannedper renderla esplicita nel prompt. - Se vuoi una sostituzione automatica, aggiungi anche una pattern in
forbiddenPhrasesconwhen: "always".
...l'utente vuole una nuova sezione obbligatoria:
- Aggiungi un elemento a
EDITORIAL_RULES.sections. Il prompt builder e il validatore la prenderanno automaticamente.
...l'utente vuole cambiare la banda di lunghezza:
- Modifica
EDITORIAL_RULES.lengths.getMaxOutputTokenssi ricalibra da solo.
...l'utente vuole cambiare provider AI:
- I dev report sono fissati su Anthropic Haiku in
getDevReportsAIConfig()(lib/ai-config.ts), indipendente dal globalAI_PROVIDER. Per cambiare modello/endpoint editare lì. - I caller vivono in
lib/dev-reports.ts:makeAnthropicCalleremakeGeminiCaller, selezionati peraiConfig.provider. Per un nuovo provider aggiungi un caller che rispetti l'interfacciaAiCallerda@/lib/editoriale mappa il suostop_reasonsu"MAX_TOKENS"per far funzionaredetectTruncation. La pipeline editoriale resta invariata.
...l'utente vuole testare manualmente:
POST /api/ch-labs/generate-weekly-reportcon body{projectId, force: true, referenceDate?}.- Per testare il caso periodo lungo, passa una
referenceDateche produca un range > 7 giorni e verifica che il report non contenga "settimana"/"settimanale".
Anti-pattern da rifiutare
- Aggiungere una
replace(/.../, "...")indev-reports.tso nei componenti UI. Sempre inrules.ts. - Hardcodare un nome di sezione in un controllo. Sempre via
getRequiredSectionHeadings(period.isWeek). - Tagliare il contenuto con
.slice()per "fixare" un troncamento. Sempre via continuation o fallback. - Aggiungere una nuova costante di lunghezza fuori da
EDITORIAL_RULES.lengths. - Duplicare la formattazione date italiane.