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>
109 lines
4.6 KiB
TypeScript
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);
|
|
});
|