diff --git a/public/agents.html b/public/agents.html index a399e895..d838ab80 100644 --- a/public/agents.html +++ b/public/agents.html @@ -3,6 +3,7 @@ + TinyStudio — The Desk diff --git a/public/audit.html b/public/audit.html index ecf2c437..504a425c 100644 --- a/public/audit.html +++ b/public/audit.html @@ -3,6 +3,7 @@ + TinyStudio — The Website Appraisal diff --git a/public/index.html b/public/index.html index 4f3f153d..25bd282a 100644 --- a/public/index.html +++ b/public/index.html @@ -3,6 +3,7 @@ + TinyStudio — The Website Appraisal diff --git a/public/pricing.html b/public/pricing.html index 60b057a9..6a4c7594 100644 --- a/public/pricing.html +++ b/public/pricing.html @@ -3,6 +3,7 @@ + Pricing & terms — The Tiny Studio diff --git a/public/specimen.html b/public/specimen.html index cdf3d93d..d4817f27 100644 --- a/public/specimen.html +++ b/public/specimen.html @@ -3,6 +3,7 @@ + TinyStudio — The Website Appraisal — specimen diff --git a/scripts/check-site.mjs b/scripts/check-site.mjs index 2f72b51a..a4c148e8 100644 --- a/scripts/check-site.mjs +++ b/scripts/check-site.mjs @@ -866,6 +866,49 @@ for (const [pageName, pageHtml] of ownedPages) { } } +// ---- Meta descriptions (dogfood) ------------------------------------------- +// The leak audit this site sells flags a homepage whose served HTML carries no +// description, so the site's own five public pages must not carry the same +// fault. Each page keeps exactly one valid, non-empty description meta tag in +// its head, within a practical search-snippet length, distinct per page, and +// free of the offer promises the repo refuses to make. +const metaDescriptionPages = [ + ["homepage", siteHome], + ["audit page", siteAudit], + ["desk page", read("public/agents.html")], + ["pricing page", read("public/pricing.html")], + ["specimen page", read("public/specimen.html")] +]; + +const seenDescriptions = new Map(); +for (const [pageName, pageHtml] of metaDescriptionPages) { + const tags = [...pageHtml.matchAll(/]*\bname="description"[^>]*>/gi)].map((match) => match[0]); + if (tags.length !== 1) { + failures.push(`Meta description must appear exactly once in the head of ${pageName} (found ${tags.length}).`); + continue; + } + const content = tags[0].match(/\bcontent="([^"]*)"/i)?.[1] ?? ""; + const trimmed = content.trim(); + if (!trimmed) { + failures.push(`Meta description on ${pageName} must not be empty.`); + continue; + } + if (trimmed.length > 160) { + failures.push(`Meta description on ${pageName} must fit a search snippet (${trimmed.length} > 160 chars).`); + } + const prior = seenDescriptions.get(trimmed); + if (prior) { + failures.push(`Meta description on ${pageName} must be unique; it duplicates ${prior}.`); + } else { + seenDescriptions.set(trimmed, pageName); + } + for (const claim of forbiddenClaims) { + if (trimmed.toLowerCase().includes(claim.toLowerCase())) { + failures.push(`Meta description on ${pageName} must not promise: ${claim}`); + } + } +} + for (const migration of ["migrations/0002_agent_runs.sql", "migrations/0003_agent_usage_limits.sql"]) { if (!existsSync(new URL(`../${migration}`, import.meta.url))) { failures.push(`Missing migration: ${migration}`); continue;