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

109 lines
4.6 KiB
TypeScript

/**
* Mette in moto le cinque fonti di `src/lib/audit/sources/` su un URL.
*
* Le fonti sono in produzione dal 2026-08-19 ma INERTI: nessuna route le chiama.
* Questo script e' l'unico modo di vederle funzionare, e serve a rispondere a una
* domanda sola: **cosa e' stato misurato davvero, e cosa no.** Un buco qui e' il
* risultato, non un errore da nascondere — il motore dovra' saperlo dire al cliente.
*
* Nessun LLM, nessun database, nessun browser headless (il VPS non lo regge).
*
* npx tsx scripts/audit-fonti.ts https://esempio.it
* npx tsx scripts/audit-fonti.ts https://esempio.it --profilo=ecommerce
* npx tsx scripts/audit-fonti.ts https://esempio.it --no-psi
*
* PAGESPEED_API_KEY (per PageSpeed e CrUX) viene letta dall'ambiente o da .env.local.
*/
import { readFileSync, writeFileSync, existsSync } from "node:fs";
import { resolve } from "node:path";
import { raccogliPagine } from "../src/lib/audit/sources/fetch";
import { rilevaCrux } from "../src/lib/audit/sources/crux";
import { rilevaPagespeedCompleto } from "../src/lib/audit/sources/pagespeed";
import { rilevaStorico } from "../src/lib/audit/sources/history";
import { rilevaSegnali } from "../src/lib/audit/sources/signals";
function caricaEnv() {
if (process.env.PAGESPEED_API_KEY) return;
const f = resolve(process.cwd(), ".env.local");
if (!existsSync(f)) return;
for (const riga of readFileSync(f, "utf8").split("\n")) {
const m = riga.match(/^\s*(?:export\s+)?([A-Z0-9_]+)\s*=\s*(.*)$/);
if (m && !process.env[m[1]]) process.env[m[1]] = m[2].trim().replace(/^["']|["']$/g, "");
}
}
/** Esegue una fonte senza far cadere le altre: un buco e' un dato, non un crash. */
async function fonte<T>(nome: string, f: () => Promise<T>) {
const t0 = Date.now();
try {
const dati = await f();
return { nome, ok: true as const, ms: Date.now() - t0, dati };
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
return { nome, ok: false as const, ms: Date.now() - t0, errore: msg };
}
}
async function main() {
caricaEnv();
const argv = process.argv.slice(2);
const url = argv.find((a) => !a.startsWith("--"));
if (!url) {
console.error("uso: npx tsx scripts/audit-fonti.ts <url> [--profilo=servizi] [--no-psi]");
process.exit(1);
}
const profilo = argv.find((a) => a.startsWith("--profilo="))?.split("=")[1] ?? "servizi";
const psi = !argv.includes("--no-psi");
const inizio = Date.now();
console.log(`\n ${url} · profilo ${profilo}${psi ? "" : " · PageSpeed saltato"}`);
if (!process.env.PAGESPEED_API_KEY) {
console.log(" ⚠ PAGESPEED_API_KEY assente: PageSpeed e CrUX risponderanno a vuoto.");
}
console.log("");
// La home serve alle altre due fonti: si scarica una volta sola.
const raccolta = await fonte("pagine", () => raccogliPagine(url, profilo));
if (!raccolta.ok) {
console.error(` ✗ pagine — ${raccolta.errore}\n\n Senza la home non parte nient'altro.`);
process.exit(1);
}
const { home, homeHtml, homeHeaders, pagine, errori } = raccolta.dati;
const risultati = [
raccolta,
...(await Promise.all([
fonte("crux", () => rilevaCrux(home)),
...(psi ? [fonte("pagespeed", () => rilevaPagespeedCompleto(home))] : []),
fonte("storico", () => rilevaStorico(home, homeHtml)),
fonte("segnali", () => rilevaSegnali(home, homeHtml, homeHeaders)),
])),
];
console.log(" FONTE ESITO TEMPO");
for (const r of risultati) {
const esito = r.ok ? "ok " : "MUTA ";
console.log(` ${r.nome.padEnd(11)} ${esito} ${(r.ms / 1000).toFixed(1)}s${r.ok ? "" : ` ${r.errore}`}`);
}
console.log(`\n Pagine raccolte: ${pagine.length} (${pagine.map((p) => p.ruolo).join(", ")})`);
if (errori.length) console.log(` Non scaricate: ${errori.join(", ")}`);
if (home !== url) console.log(` Canonico: ${home} (redirect dal valore passato)`);
// Il grezzo e' il posto dove ogni numero del documento dovra' essere rintracciabile.
const dominio = new URL(home).hostname.replace(/^www\./, "");
const file = resolve(process.cwd(), `audit-fonti-${dominio}.json`);
writeFileSync(file, JSON.stringify({ url: home, profilo, quando: new Date().toISOString(), risultati }, null, 2));
console.log(`\n Grezzo: ${file}`);
console.log(` Totale: ${((Date.now() - inizio) / 1000).toFixed(1)}s\n`);
console.log(" Un numero entra in un documento solo se lo ritrovi qui dentro.");
console.log(" Un punteggio PageSpeed va sempre con la sua data: ballano di 12 punti in mezz'ora.\n");
}
main().catch((e) => {
console.error("\nERRORE:", e instanceof Error ? e.message : e);
process.exit(1);
});