pagetrace 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/rules/rich-results.ts","../src/audit.ts","../src/diff.ts","../src/extract.ts","../src/rules/guidance.ts","../src/report.ts","../src/snapshot.ts"],"sourcesContent":["export { auditCrossPage, auditHreflang, auditPage, auditSite, auditSnapshot } from './audit.js';\nexport { diffPage, diffSite, diffSnapshots } from './diff.js';\nexport {\n extractJsonLd,\n extractLlmsTxt,\n extractPage,\n extractRobotsTxt,\n extractSitemapUrls,\n} from './extract.js';\nexport { detectPlatform, withGuidance, GUIDANCE } from './rules/guidance.js';\nexport type { Guidance } from './rules/guidance.js';\nexport {\n aggregate,\n applyConfig,\n formatAuditHtml,\n formatAuditMarkdown,\n formatAuditPretty,\n formatGithub,\n formatJson,\n formatMarkdown,\n formatPretty,\n shouldFail,\n summarize,\n} from './report.js';\nexport { RICH_RESULT_RULES, DEFAULT_AI_AGENTS } from './rules/rich-results.js';\nexport {\n routeFromFilePath,\n routeFromUrl,\n shouldIgnore,\n snapshotFromDir,\n snapshotFromOrigin,\n} from './snapshot.js';\nexport type { AuditMeta } from './report.js';\nexport type {\n Aggregate,\n Config,\n Finding,\n JsonLdEntity,\n PageFingerprint,\n Platform,\n Severity,\n SiteFingerprint,\n Snapshot,\n} from './types.js';\n","/**\n * Required and recommended properties for the Schema.org types Google supports\n * as rich results. Sourced from Google Search Central's structured data\n * reference. Deliberately a plain data table so it can be updated without\n * touching the engine, and so consumers can extend it.\n */\nexport interface RichResultRule {\n required: string[];\n recommended: string[];\n /** Groups where at least one member must be present. */\n oneOf?: string[][];\n}\n\nexport const RICH_RESULT_RULES: Record<string, RichResultRule> = {\n Article: {\n required: ['headline'],\n recommended: ['author', 'datePublished', 'dateModified', 'image'],\n },\n NewsArticle: {\n required: ['headline'],\n recommended: ['author', 'datePublished', 'dateModified', 'image'],\n },\n BlogPosting: {\n required: ['headline'],\n recommended: ['author', 'datePublished', 'dateModified', 'image'],\n },\n Product: {\n required: ['name'],\n recommended: ['image', 'description', 'brand'],\n oneOf: [['offers', 'review', 'aggregateRating']],\n },\n Offer: {\n required: ['price', 'priceCurrency'],\n recommended: ['availability', 'url'],\n },\n FAQPage: {\n required: ['mainEntity'],\n recommended: [],\n },\n HowTo: {\n required: ['name', 'step'],\n recommended: ['image', 'totalTime', 'supply', 'tool'],\n },\n Recipe: {\n required: ['name', 'image'],\n recommended: ['author', 'datePublished', 'description', 'recipeIngredient', 'recipeInstructions'],\n },\n Event: {\n required: ['name', 'startDate', 'location'],\n recommended: ['endDate', 'description', 'image', 'offers'],\n },\n JobPosting: {\n required: ['title', 'description', 'datePosted', 'hiringOrganization'],\n recommended: ['jobLocation', 'baseSalary', 'validThrough', 'employmentType'],\n },\n Organization: {\n required: ['name'],\n recommended: ['url', 'logo', 'sameAs', 'contactPoint'],\n },\n LocalBusiness: {\n required: ['name', 'address'],\n recommended: ['telephone', 'openingHoursSpecification', 'geo', 'priceRange', 'image'],\n },\n BreadcrumbList: {\n required: ['itemListElement'],\n recommended: [],\n },\n VideoObject: {\n required: ['name', 'description', 'thumbnailUrl', 'uploadDate'],\n recommended: ['duration', 'contentUrl', 'embedUrl'],\n },\n Review: {\n required: ['itemReviewed', 'reviewRating', 'author'],\n recommended: ['datePublished', 'reviewBody'],\n },\n AggregateRating: {\n required: ['ratingValue'],\n recommended: ['reviewCount', 'ratingCount', 'bestRating'],\n },\n Course: {\n required: ['name', 'description'],\n recommended: ['provider', 'offers', 'hasCourseInstance'],\n },\n SoftwareApplication: {\n required: ['name', 'applicationCategory'],\n recommended: ['operatingSystem', 'offers', 'aggregateRating'],\n },\n WebSite: {\n required: ['name', 'url'],\n recommended: ['potentialAction'],\n },\n Person: {\n required: ['name'],\n recommended: ['url', 'jobTitle', 'sameAs', 'image'],\n },\n};\n\n/** AI crawler user agents checked against robots.txt by default. */\nexport const DEFAULT_AI_AGENTS = [\n 'GPTBot',\n 'OAI-SearchBot',\n 'ChatGPT-User',\n 'ClaudeBot',\n 'Claude-User',\n 'PerplexityBot',\n 'Perplexity-User',\n 'Google-Extended',\n 'Applebot-Extended',\n 'CCBot',\n 'Bytespider',\n 'meta-externalagent',\n];\n","import { RICH_RESULT_RULES } from './rules/rich-results.js';\nimport type { Config, Finding, PageFingerprint, Snapshot } from './types.js';\n\n/**\n * Rules that hold regardless of history. These overlap with what any auditor\n * reports; the diff engine in `diff.ts` is what catches regressions.\n */\nexport function auditPage(page: PageFingerprint, config: Config = {}): Finding[] {\n const findings: Finding[] = [];\n const at = (code: string, severity: Finding['severity'], message: string, extra: Partial<Finding> = {}) =>\n findings.push({ code, severity, route: page.route, message, ...extra });\n\n if (!page.title) at('title.missing', 'error', 'Page has no <title>.');\n else if (page.title.length > 65)\n at('title.long', 'info', 'Title is long enough that it will likely be truncated in results.', {\n after: page.title.length,\n });\n\n if (!page.description) at('description.missing', 'warn', 'Page has no meta description.');\n\n if (!page.canonical) at('canonical.missing', 'error', 'Page has no canonical URL.');\n\n if (page.robots?.includes('noindex')) at('robots.noindex', 'warn', 'Page is marked noindex.');\n\n if (page.h1.length === 0) at('h1.missing', 'error', 'Page has no <h1>.');\n else if (page.h1.length > 1)\n at('h1.multiple', 'warn', 'Page has more than one <h1>.', { after: page.h1 });\n\n if (!page.og['og:title']) at('og.title.missing', 'warn', 'Missing og:title.');\n if (!page.og['og:image']) at('og.image.missing', 'warn', 'Missing og:image.');\n\n if (page.jsonLd.length === 0)\n at('jsonld.missing', 'warn', 'Page has no JSON-LD structured data.');\n\n for (const entity of page.jsonLd) {\n if (entity.type === '__parse_error__') {\n at('jsonld.invalid', 'error', 'A JSON-LD block failed to parse.');\n continue;\n }\n const rule = RICH_RESULT_RULES[entity.type];\n if (!rule) continue;\n\n const present = new Set(entity.properties);\n const missing = rule.required.filter((p) => !present.has(p));\n if (missing.length > 0) {\n at(\n 'jsonld.required.missing',\n 'error',\n `${entity.type} is missing required ${missing.length === 1 ? 'property' : 'properties'}: ${missing.join(', ')}.`,\n { after: entity.properties },\n );\n }\n for (const group of rule.oneOf ?? []) {\n if (!group.some((p) => present.has(p))) {\n at(\n 'jsonld.oneof.missing',\n 'error',\n `${entity.type} needs at least one of: ${group.join(', ')}.`,\n );\n }\n }\n const missingRecommended = rule.recommended.filter((p) => !present.has(p));\n if (missingRecommended.length > 0) {\n at(\n 'jsonld.recommended.missing',\n 'info',\n `${entity.type} is missing recommended: ${missingRecommended.join(', ')}.`,\n );\n }\n }\n\n const minWords = config.minWordCount ?? 150;\n if (page.wordCount < minWords)\n at('content.thin', 'warn', `Page has fewer than ${minWords} words.`, {\n after: page.wordCount,\n });\n\n // AEO: answer engines extract the opening passage. Nothing quotable there is\n // a missed citation, and an overlong lead tends to be chunked badly.\n if (page.leadAnswerWords === 0)\n at('aeo.lead.missing', 'warn', 'No substantive opening paragraph for an answer engine to quote.');\n else if (page.leadAnswerWords > 120)\n at('aeo.lead.long', 'info', 'Opening paragraph is long; under ~80 words extracts better.', {\n after: page.leadAnswerWords,\n });\n\n if (page.images.missingAlt > 0)\n at('images.alt.missing', 'warn', 'Page has images with no alt attribute.', {\n after: { missingAlt: page.images.missingAlt, total: page.images.total },\n });\n\n return findings;\n}\n\nexport function auditSite(snapshot: Snapshot): Finding[] {\n const findings: Finding[] = [];\n const { site } = snapshot;\n\n if (!site.robotsTxt?.present) {\n findings.push({ code: 'robotstxt.missing', severity: 'warn', route: null, message: 'No robots.txt found.' });\n } else {\n const blocked = Object.entries(site.robotsTxt.aiAgents)\n .filter(([, state]) => state === 'disallowed')\n .map(([agent]) => agent);\n if (blocked.length > 0) {\n findings.push({\n code: 'aeo.crawler.blocked',\n severity: 'info',\n route: null,\n message: `robots.txt blocks ${blocked.length} AI crawler(s): ${blocked.join(', ')}.`,\n after: blocked,\n });\n }\n if (site.robotsTxt.sitemaps.length === 0) {\n findings.push({\n code: 'robotstxt.sitemap.missing',\n severity: 'warn',\n route: null,\n message: 'robots.txt does not declare a sitemap.',\n });\n }\n }\n\n if (!site.llmsTxt?.present) {\n findings.push({\n code: 'aeo.llmstxt.missing',\n severity: 'info',\n route: null,\n message: 'No /llms.txt found.',\n });\n }\n\n return findings;\n}\n\n/** Origin of an absolute href. Relative hrefs have no host, so they return null. */\nfunction originOf(href: string): string | null {\n try {\n return new URL(href).origin;\n } catch {\n return null;\n }\n}\n\n/**\n * A paginated archive and an AMP variant both canonicalise to their parent on\n * purpose, and both are ordinary CMS output. Flagging them buries the canonical\n * mistakes that are real.\n */\nconst VARIANT_SUFFIX = /(?:\\/(?:page|p)\\/\\d+|\\/amp)\\/?$/i;\nconst AMP_PREFIX = /^\\/amp(?=\\/)/i;\n\nfunction isVariantOf(route: string, target: string): boolean {\n return [route.replace(VARIANT_SUFFIX, ''), route.replace(AMP_PREFIX, '')]\n .filter((stripped) => stripped !== route)\n .some((stripped) => (stripped === '' ? '/' : stripped) === target);\n}\n\n/** Path portion of a canonical or hreflang href, normalized to match a snapshot route. */\nfunction pathOf(href: string): string | null {\n try {\n const path = new URL(href, 'https://placeholder.invalid').pathname.replace(/\\/+$/, '');\n return path === '' ? '/' : path;\n } catch {\n return null;\n }\n}\n\n/**\n * Rules that only exist when you look at the whole site at once. These are the\n * findings that matter most on a large CMS site, where the defects come from\n * templates rather than individual pages.\n */\nexport function auditCrossPage(snapshot: Snapshot): Finding[] {\n const findings: Finding[] = [];\n const pages = Object.values(snapshot.pages);\n\n const group = <T>(key: (p: (typeof pages)[number]) => T | null) => {\n const map = new Map<T, string[]>();\n for (const page of pages) {\n const value = key(page);\n if (value === null || value === undefined || value === '') continue;\n if (!map.has(value)) map.set(value, []);\n map.get(value)!.push(page.route);\n }\n return map;\n };\n\n for (const [title, routes] of group((p) => p.title)) {\n if (routes.length > 1) {\n findings.push({\n code: 'duplicate.title',\n severity: 'warn',\n route: null,\n message: `${routes.length} pages share the title \"${title}\".`,\n after: routes,\n });\n }\n }\n\n for (const [, routes] of group((p) => p.description)) {\n if (routes.length > 1) {\n findings.push({\n code: 'duplicate.description',\n severity: 'warn',\n route: null,\n message: `${routes.length} pages share the same meta description.`,\n after: routes,\n });\n }\n }\n\n for (const [canonical, routes] of group((p) => p.canonical)) {\n if (routes.length > 1) {\n findings.push({\n code: 'duplicate.canonical',\n severity: 'error',\n route: null,\n message: `${routes.length} pages canonicalise to ${canonical}.`,\n after: routes,\n });\n }\n }\n\n // Only checked when we know what the site's own host is: an origin crawl\n // records it, a --dir crawl needs config.siteUrl. Guessing it from the\n // canonicals themselves would miss the case that matters most, where a\n // staging host has leaked into every canonical on the site.\n const expectedOrigin = snapshot.site.origin ?? null;\n\n for (const page of pages) {\n if (!page.canonical) continue;\n\n if (expectedOrigin !== null) {\n const host = originOf(page.canonical);\n if (host !== null && host !== expectedOrigin) {\n findings.push({\n code: 'canonical.offsite',\n severity: 'error',\n route: page.route,\n message: 'Canonical points at another host.',\n before: expectedOrigin,\n after: page.canonical,\n });\n continue;\n }\n }\n\n const normalized = pathOf(page.canonical);\n if (normalized === null || normalized === page.route) continue;\n if (isVariantOf(page.route, normalized)) continue;\n\n findings.push({\n code: 'canonical.crosspath',\n severity: 'warn',\n route: page.route,\n message: 'Canonical points to a different path.',\n before: page.route,\n after: page.canonical,\n });\n }\n\n return findings;\n}\n\nconst LANG_TAG = /^[a-z]{2,3}(-[a-zA-Z0-9]{2,8})*$/i;\n\n/**\n * hreflang is the rule set most worth automating: Google requires the\n * annotations to be reciprocal, and a one-sided set is silently ignored rather\n * than reported anywhere. You cannot see this from a single page, which is why\n * it lives here rather than in auditPage.\n */\nexport function auditHreflang(snapshot: Snapshot): Finding[] {\n const findings: Finding[] = [];\n const pages = Object.values(snapshot.pages);\n const annotated = pages.filter((p) => Object.keys(p.hreflang).length > 0);\n\n // Only meaningful on a site that uses hreflang somewhere. A monolingual site\n // should not be nagged about it.\n if (annotated.length === 0) return findings;\n\n const byRoute = new Map(pages.map((p) => [p.route, p]));\n\n // Routes another page names as an alternate. Restricting hreflang.missing to\n // these keeps the rule honest on a partial crawl: a page nobody points at\n // proves nothing, exactly as an absent page proves nothing about reciprocity.\n const claimed = new Set<string>();\n for (const page of annotated) {\n for (const href of Object.values(page.hreflang)) {\n const target = pathOf(href);\n if (target !== null && target !== page.route) claimed.add(target);\n }\n }\n\n for (const page of pages) {\n const entries = Object.entries(page.hreflang);\n\n if (entries.length === 0) {\n if (claimed.has(page.route)) {\n findings.push({\n code: 'hreflang.missing',\n severity: 'warn',\n route: page.route,\n message: 'Page is named as an hreflang alternate but declares none of its own.',\n });\n }\n continue;\n }\n\n const invalid = entries\n .map(([lang]) => lang)\n .filter((lang) => lang !== 'x-default' && !LANG_TAG.test(lang));\n if (invalid.length > 0) {\n findings.push({\n code: 'hreflang.invalid',\n severity: 'warn',\n route: page.route,\n message: 'Page has malformed hreflang language codes.',\n after: invalid,\n });\n }\n\n const targets = [\n ...new Set(\n entries.map(([, href]) => pathOf(href)).filter((p): p is string => p !== null),\n ),\n ];\n\n if (!targets.includes(page.route)) {\n findings.push({\n code: 'hreflang.self.missing',\n severity: 'warn',\n route: page.route,\n message: 'Page does not include a self-referencing hreflang.',\n });\n }\n\n if (!entries.some(([lang]) => lang === 'x-default')) {\n findings.push({\n code: 'hreflang.xdefault.missing',\n severity: 'info',\n route: page.route,\n message: 'Page has hreflang alternates but no x-default.',\n });\n }\n\n // Reciprocity. Only checked against pages actually in the snapshot, so a\n // partial crawl does not manufacture findings.\n const broken: string[] = [];\n for (const target of targets) {\n if (target === page.route) continue;\n const other = byRoute.get(target);\n if (!other) continue;\n const returns = Object.values(other.hreflang)\n .map(pathOf)\n .includes(page.route);\n if (!returns) broken.push(target);\n }\n if (broken.length > 0) {\n findings.push({\n code: 'hreflang.nonreciprocal',\n severity: 'error',\n route: page.route,\n message: 'Page points at alternates that do not point back.',\n after: broken,\n });\n }\n\n for (const target of targets) {\n if (target === page.route) continue;\n const other = byRoute.get(target);\n if (other?.robots?.includes('noindex')) {\n findings.push({\n code: 'hreflang.noindex.target',\n severity: 'error',\n route: page.route,\n message: 'Page declares an hreflang alternate that is noindexed.',\n after: target,\n });\n }\n }\n }\n\n return findings;\n}\n\nexport function auditSnapshot(snapshot: Snapshot, config: Config = {}): Finding[] {\n return [\n ...auditSite(snapshot),\n ...auditCrossPage(snapshot),\n ...auditHreflang(snapshot),\n ...Object.values(snapshot.pages).flatMap((page) => auditPage(page, config)),\n ];\n}\n","import type { Finding, JsonLdEntity, PageFingerprint, Snapshot } from './types.js';\n\n/**\n * The point of the diff engine: a field being *reworded* and a field being\n * *removed* are different events. Auditors collapse both into \"current state\".\n * We classify by transition, so CI can fail on regressions while ignoring the\n * ordinary content churn that happens on every deploy.\n */\nfunction transition(\n before: string | null,\n after: string | null,\n): 'unchanged' | 'added' | 'removed' | 'changed' {\n if (before === after) return 'unchanged';\n if (before === null) return 'added';\n if (after === null) return 'removed';\n return 'changed';\n}\n\ninterface FieldRule {\n field: keyof PageFingerprint;\n label: string;\n code: string;\n onRemoved: Finding['severity'];\n onChanged: Finding['severity'];\n onAdded: Finding['severity'];\n}\n\nconst SCALAR_FIELDS: FieldRule[] = [\n { field: 'title', label: 'Title', code: 'title', onRemoved: 'error', onChanged: 'info', onAdded: 'info' },\n { field: 'description', label: 'Meta description', code: 'description', onRemoved: 'warn', onChanged: 'info', onAdded: 'info' },\n { field: 'canonical', label: 'Canonical', code: 'canonical', onRemoved: 'error', onChanged: 'warn', onAdded: 'info' },\n];\n\n/**\n * Keyed by @id where available so a reordered @graph is not reported as a\n * change. Most templates emit no @id at all, and several entities of one type\n * on a page is the norm (a category page of Products, a FAQPage of Questions),\n * so those fall back to a positional key: keying on the bare type would collapse\n * them into one and hide every removal but the last.\n */\nfunction indexEntities(entities: JsonLdEntity[]): Map<string, JsonLdEntity> {\n const map = new Map<string, JsonLdEntity>();\n const seen = new Map<string, number>();\n for (const entity of entities) {\n if (entity.id !== undefined && !map.has(entity.id)) {\n map.set(entity.id, entity);\n continue;\n }\n const nth = (seen.get(entity.type) ?? 0) + 1;\n seen.set(entity.type, nth);\n map.set(`${entity.type}#${nth}`, entity);\n }\n return map;\n}\n\nexport function diffPage(before: PageFingerprint, after: PageFingerprint): Finding[] {\n const findings: Finding[] = [];\n const route = after.route;\n const push = (code: string, severity: Finding['severity'], message: string, extra: Partial<Finding> = {}) =>\n findings.push({ code, severity, route, message, ...extra });\n\n for (const rule of SCALAR_FIELDS) {\n const b = before[rule.field] as string | null;\n const a = after[rule.field] as string | null;\n switch (transition(b, a)) {\n case 'removed':\n push(`${rule.code}.removed`, rule.onRemoved, `${rule.label} was removed.`, { before: b });\n break;\n case 'added':\n push(`${rule.code}.added`, rule.onAdded, `${rule.label} was added.`, { after: a });\n break;\n case 'changed':\n push(`${rule.code}.changed`, rule.onChanged, `${rule.label} changed.`, { before: b, after: a });\n break;\n }\n }\n\n // Indexability transitions are the highest-cost silent regression there is.\n const wasNoindex = before.robots?.includes('noindex') ?? false;\n const isNoindex = after.robots?.includes('noindex') ?? false;\n if (!wasNoindex && isNoindex)\n push('robots.noindex.added', 'error', 'Page became noindex.', { before: before.robots, after: after.robots });\n if (wasNoindex && !isNoindex)\n push('robots.noindex.removed', 'info', 'Page is no longer noindex.', { before: before.robots });\n\n const wasNofollow = before.robots?.includes('nofollow') ?? false;\n const isNofollow = after.robots?.includes('nofollow') ?? false;\n if (!wasNofollow && isNofollow)\n push('robots.nofollow.added', 'warn', 'Page became nofollow.', { after: after.robots });\n\n const beforeEntities = indexEntities(before.jsonLd);\n const afterEntities = indexEntities(after.jsonLd);\n\n for (const [key, entity] of beforeEntities) {\n if (!afterEntities.has(key)) {\n push('jsonld.entity.removed', 'error', `Structured data entity ${entity.type} was removed.`, {\n before: entity,\n });\n }\n }\n for (const [key, entity] of afterEntities) {\n if (!beforeEntities.has(key)) {\n push('jsonld.entity.added', 'info', `Structured data entity ${entity.type} was added.`, { after: entity });\n continue;\n }\n const prev = beforeEntities.get(key)!;\n const dropped = prev.properties.filter((p) => !entity.properties.includes(p));\n if (dropped.length > 0) {\n push('jsonld.property.removed', 'error', `${entity.type} lost structured data properties.`, {\n before: prev.properties,\n after: entity.properties,\n });\n }\n }\n\n // Open Graph / Twitter Card removals break social and some AI previews.\n for (const [group, label] of [\n ['og', 'Open Graph'],\n ['twitter', 'Twitter Card'],\n ] as const) {\n const b = before[group];\n const a = after[group];\n const dropped = Object.keys(b).filter((k) => !(k in a));\n if (dropped.length > 0)\n push(`${group}.removed`, 'warn', `${label} tags were removed.`, { before: dropped });\n }\n\n const droppedHreflang = Object.keys(before.hreflang).filter((k) => !(k in after.hreflang));\n if (droppedHreflang.length > 0)\n push('hreflang.removed', 'warn', 'hreflang alternates were removed.', {\n before: droppedHreflang,\n });\n\n if (before.h1.length > 0 && after.h1.length === 0)\n push('h1.removed', 'error', 'The <h1> was removed.', { before: before.h1 });\n\n if (before.headingOutline.join('>') !== after.headingOutline.join('>'))\n push('headings.changed', 'info', 'Heading outline changed.', {\n before: before.headingOutline.length,\n after: after.headingOutline.length,\n });\n\n // A large content drop usually means a render failure or a template regression,\n // not an edit.\n if (before.wordCount > 0) {\n const ratio = after.wordCount / before.wordCount;\n if (ratio < 0.5)\n push('content.dropped', 'error', 'Word count fell by more than half.', {\n before: before.wordCount,\n after: after.wordCount,\n });\n }\n\n return findings;\n}\n\nexport function diffSite(before: Snapshot['site'], after: Snapshot['site']): Finding[] {\n const findings: Finding[] = [];\n const push = (code: string, severity: Finding['severity'], message: string, extra: Partial<Finding> = {}) =>\n findings.push({ code, severity, route: null, message, ...extra });\n\n if (before.robotsTxt?.present && !after.robotsTxt?.present)\n push('robotstxt.removed', 'error', 'robots.txt disappeared.');\n\n if (before.robotsTxt && after.robotsTxt) {\n for (const [agent, state] of Object.entries(before.robotsTxt.aiAgents)) {\n const next = after.robotsTxt.aiAgents[agent];\n if (state === 'allowed' && next === 'disallowed')\n push('aeo.crawler.newly_blocked', 'error', `robots.txt now blocks ${agent}.`, { after: agent });\n if (state === 'disallowed' && next === 'allowed')\n push('aeo.crawler.unblocked', 'info', `robots.txt now allows ${agent}.`, { after: agent });\n }\n const droppedSitemaps = before.robotsTxt.sitemaps.filter(\n (s) => !after.robotsTxt!.sitemaps.includes(s),\n );\n if (droppedSitemaps.length > 0)\n push('robotstxt.sitemap.removed', 'warn', `Sitemap declaration removed: ${droppedSitemaps.join(', ')}.`);\n }\n\n if (before.llmsTxt?.present && !after.llmsTxt?.present)\n push('aeo.llmstxt.removed', 'error', '/llms.txt disappeared.');\n\n if (before.llmsTxt?.present && after.llmsTxt?.present) {\n const dropped = before.llmsTxt.sections.filter((s) => !after.llmsTxt!.sections.includes(s));\n if (dropped.length > 0)\n push('aeo.llmstxt.sections.removed', 'warn', `llms.txt sections removed: ${dropped.join(', ')}.`);\n if (after.llmsTxt.bytes < before.llmsTxt.bytes * 0.5)\n push('aeo.llmstxt.truncated', 'warn', 'llms.txt shrank by more than half.', {\n before: before.llmsTxt.bytes,\n after: after.llmsTxt.bytes,\n });\n }\n\n return findings;\n}\n\nexport function diffSnapshots(before: Snapshot, after: Snapshot): Finding[] {\n const findings: Finding[] = diffSite(before.site, after.site);\n\n for (const route of Object.keys(before.pages)) {\n if (!(route in after.pages)) {\n findings.push({\n code: 'page.removed',\n severity: 'warn',\n route,\n message: 'Page is no longer present.',\n });\n }\n }\n\n for (const [route, page] of Object.entries(after.pages)) {\n const previous = before.pages[route];\n if (!previous) {\n findings.push({ code: 'page.added', severity: 'info', route, message: 'New page.' });\n continue;\n }\n findings.push(...diffPage(previous, page));\n }\n\n return findings;\n}\n","import { parse, type HTMLElement } from 'node-html-parser';\nimport type { JsonLdEntity, PageFingerprint } from './types.js';\n\nconst HEADING_TAGS = new Set(['H1', 'H2', 'H3', 'H4', 'H5', 'H6']);\nconst NON_CONTENT = new Set(['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEMPLATE', 'SVG']);\n\nfunction text(el: HTMLElement | null): string | null {\n if (!el) return null;\n const value = el.textContent.replace(/\\s+/g, ' ').trim();\n return value.length > 0 ? value : null;\n}\n\n/**\n * HTML keywords are case-insensitive (`<meta NAME=\"Description\">`, `rel=\"Canonical\"`)\n * but CSS attribute selectors are not, so we match on lowercased values rather\n * than through querySelector.\n */\nfunction metaContent(root: HTMLElement, name: string): string | null {\n for (const el of root.querySelectorAll('meta')) {\n if (el.getAttribute('name')?.trim().toLowerCase() !== name) continue;\n const value = el.getAttribute('content')?.trim();\n if (value) return value;\n }\n return null;\n}\n\n/** `rel` is a space-separated token list, e.g. `rel=\"alternate stylesheet\"`. */\nfunction hasRel(el: HTMLElement, rel: string): boolean {\n const value = el.getAttribute('rel');\n if (!value) return false;\n return value.trim().toLowerCase().split(/\\s+/).includes(rel);\n}\n\nfunction linkHref(root: HTMLElement, rel: string): string | null {\n for (const el of root.querySelectorAll('link')) {\n if (!hasRel(el, rel)) continue;\n const href = el.getAttribute('href')?.trim();\n if (href) return href;\n }\n return null;\n}\n\n/**\n * Collect a namespaced meta group (og:*, twitter:*) into a flat record.\n * Open Graph uses `property`, Twitter Cards use `name`; some sites mix them,\n * so we read both.\n */\nfunction metaGroup(root: HTMLElement, prefix: string): Record<string, string> {\n const out: Record<string, string> = {};\n for (const el of root.querySelectorAll('meta')) {\n const key = el.getAttribute('property') ?? el.getAttribute('name');\n if (!key || !key.toLowerCase().startsWith(`${prefix}:`)) continue;\n const content = el.getAttribute('content')?.trim();\n if (!content) continue;\n out[key.toLowerCase()] = content;\n }\n return out;\n}\n\nfunction hreflangMap(root: HTMLElement): Record<string, string> {\n const out: Record<string, string> = {};\n for (const el of root.querySelectorAll('link')) {\n if (!hasRel(el, 'alternate')) continue;\n const lang = el.getAttribute('hreflang');\n const href = el.getAttribute('href');\n if (lang && href) out[lang.toLowerCase()] = href.trim();\n }\n return out;\n}\n\n/** Flatten @graph containers and arrays into a single list of entities. */\nfunction flattenJsonLd(node: unknown, out: JsonLdEntity[]): void {\n if (Array.isArray(node)) {\n for (const item of node) flattenJsonLd(item, out);\n return;\n }\n if (typeof node !== 'object' || node === null) return;\n\n const obj = node as Record<string, unknown>;\n if ('@graph' in obj) {\n flattenJsonLd(obj['@graph'], out);\n // A wrapper carrying only @context/@graph is not itself an entity.\n const rest = Object.keys(obj).filter((k) => k !== '@graph' && k !== '@context');\n if (rest.length === 0) return;\n }\n\n const rawType = obj['@type'];\n const type = Array.isArray(rawType) ? String(rawType[0]) : rawType ? String(rawType) : null;\n if (!type) return;\n\n out.push({\n type,\n ...(typeof obj['@id'] === 'string' ? { id: obj['@id'] } : {}),\n properties: Object.keys(obj)\n .filter((k) => !k.startsWith('@'))\n .sort(),\n });\n}\n\nexport function extractJsonLd(root: HTMLElement): JsonLdEntity[] {\n const entities: JsonLdEntity[] = [];\n for (const script of root.querySelectorAll('script')) {\n if (script.getAttribute('type')?.trim().toLowerCase() !== 'application/ld+json') continue;\n try {\n flattenJsonLd(JSON.parse(script.textContent), entities);\n } catch {\n entities.push({ type: '__parse_error__', properties: [] });\n }\n }\n return entities;\n}\n\n/** Visible word count, excluding scripts, styles and inline SVG. */\nfunction countWords(root: HTMLElement): number {\n const body = root.querySelector('body') ?? root;\n const clone = parse(body.outerHTML);\n for (const tag of NON_CONTENT) {\n for (const el of clone.querySelectorAll(tag.toLowerCase())) el.remove();\n }\n const words = clone.textContent.replace(/\\s+/g, ' ').trim();\n return words.length === 0 ? 0 : words.split(' ').length;\n}\n\n/**\n * Answer engines favour pages that answer the question up front. We measure the\n * length of the first substantive paragraph after the h1 as a proxy: too short\n * and there is nothing to quote, too long and it will not be extracted cleanly.\n */\nfunction leadAnswer(root: HTMLElement): number {\n const scope =\n root.querySelector('main') ??\n root.querySelector('article') ??\n root.querySelector('body') ??\n root;\n\n // Cookie banners, promo strips and breadcrumbs are paragraphs too, and they\n // sit above the h1. Anchor on the h1 so they cannot stand in for the lead.\n const candidates = scope\n .querySelectorAll('h1, p')\n .filter((el) => !el.closest('header, nav, footer, aside'));\n const firstH1 = candidates.findIndex((el) => el.tagName?.toUpperCase() === 'H1');\n\n for (const el of candidates.slice(firstH1 + 1)) {\n if (el.tagName?.toUpperCase() !== 'P') continue;\n const value = text(el);\n if (!value) continue;\n const words = value.split(' ').length;\n if (words >= 8) return words;\n }\n return 0;\n}\n\nexport function extractPage(html: string, route: string): PageFingerprint {\n const root = parse(html, { blockTextElements: { script: true, style: true } });\n\n const headings: string[] = [];\n const h1: string[] = [];\n for (const el of root.querySelectorAll('h1, h2, h3, h4, h5, h6')) {\n const tag = el.tagName?.toUpperCase();\n if (!tag || !HEADING_TAGS.has(tag)) continue;\n headings.push(tag.toLowerCase());\n if (tag === 'H1') {\n const value = text(el);\n if (value) h1.push(value);\n }\n }\n\n const imgs = root.querySelectorAll('img');\n const missingAlt = imgs.filter((img) => {\n const alt = img.getAttribute('alt');\n return alt === undefined || alt === null;\n }).length;\n\n return {\n route,\n title: text(root.querySelector('title')),\n description: metaContent(root, 'description'),\n canonical: linkHref(root, 'canonical'),\n robots: metaContent(root, 'robots')?.toLowerCase() ?? null,\n og: metaGroup(root, 'og'),\n twitter: metaGroup(root, 'twitter'),\n hreflang: hreflangMap(root),\n h1,\n headingOutline: headings,\n jsonLd: extractJsonLd(root).sort((a, b) => a.type.localeCompare(b.type)),\n wordCount: countWords(root),\n images: { total: imgs.length, missingAlt },\n leadAnswerWords: leadAnswer(root),\n generator: metaContent(root, 'generator'),\n };\n}\n\n/** Parse robots.txt into per-agent crawlability of the site root. */\nexport function extractRobotsTxt(body: string, agents: string[]) {\n const sitemaps: string[] = [];\n const groups: { agents: string[]; disallowAll: boolean }[] = [];\n let current: { agents: string[]; disallowAll: boolean } | null = null;\n let lastWasAgent = false;\n\n for (const rawLine of body.split(/\\r?\\n/)) {\n const line = rawLine.split('#')[0].trim();\n if (!line) continue;\n const idx = line.indexOf(':');\n if (idx === -1) continue;\n const field = line.slice(0, idx).trim().toLowerCase();\n const value = line.slice(idx + 1).trim();\n\n if (field === 'sitemap') {\n sitemaps.push(value);\n continue;\n }\n if (field === 'user-agent') {\n if (!current || !lastWasAgent) {\n current = { agents: [], disallowAll: false };\n groups.push(current);\n }\n current.agents.push(value.toLowerCase());\n lastWasAgent = true;\n continue;\n }\n lastWasAgent = false;\n if (field === 'disallow' && current && value === '/') current.disallowAll = true;\n if (field === 'allow' && current && value === '/') current.disallowAll = false;\n }\n\n const aiAgents: Record<string, 'allowed' | 'disallowed'> = {};\n for (const agent of agents) {\n const lower = agent.toLowerCase();\n const specific = groups.find((g) => g.agents.includes(lower));\n const wildcard = groups.find((g) => g.agents.includes('*'));\n const group = specific ?? wildcard;\n aiAgents[agent] = group?.disallowAll ? 'disallowed' : 'allowed';\n }\n\n return { present: true, aiAgents, sitemaps };\n}\n\n/** Parse llms.txt, capturing section headings so truncation is detectable. */\nexport function extractLlmsTxt(body: string) {\n const sections = body\n .split(/\\r?\\n/)\n .filter((line) => line.startsWith('## '))\n .map((line) => line.slice(3).trim());\n return { present: true, sections, bytes: Buffer.byteLength(body, 'utf8') };\n}\n\nconst XML_ENTITIES: Record<string, string> = {\n amp: '&',\n lt: '<',\n gt: '>',\n quot: '\"',\n apos: \"'\",\n};\n\n/** XML requires `&` in a URL to be escaped, so every query string arrives encoded. */\nfunction decodeXml(value: string): string {\n return value.replace(/&(?:#(\\d+)|#x([0-9a-f]+)|([a-z]+));/gi, (match, dec, hex, name) => {\n if (dec) return String.fromCodePoint(Number(dec));\n if (hex) return String.fromCodePoint(parseInt(hex, 16));\n return XML_ENTITIES[String(name).toLowerCase()] ?? match;\n });\n}\n\n/** Pull <loc> entries out of a sitemap or sitemap index. */\nexport function extractSitemapUrls(xml: string): string[] {\n const pattern = /<loc>\\s*(?:<!\\[CDATA\\[([\\s\\S]*?)\\]\\]>|([^<]*?))\\s*<\\/loc>/gi;\n return [...xml.matchAll(pattern)]\n // CDATA is literal by definition; only the escaped form needs decoding.\n .map((m) => (m[1] !== undefined ? m[1] : decodeXml(m[2] ?? '')).trim())\n .filter((url) => url.length > 0);\n}\n","import type { Platform } from '../types.js';\n\nexport interface Guidance {\n /** Why the issue costs you traffic or citations. */\n why: string;\n /** Generic remedy. */\n fix: string;\n /** Platform-specific remedy, used when the platform is detected. */\n byPlatform?: Partial<Record<Platform, string>>;\n}\n\n/**\n * Explanations attached to findings in audit output. Diff output stays terse —\n * you already know what a canonical is when you are reviewing a regression.\n * An audit handed to a client or a content team needs the reasoning.\n */\nexport const GUIDANCE: Record<string, Guidance> = {\n 'title.missing': {\n why: 'The title is the strongest on-page ranking signal and the clickable line in results. Without one, search engines invent a title from page content, usually badly.',\n fix: 'Add a unique <title> of roughly 50-60 characters that leads with the primary term.',\n byPlatform: {\n wordpress: 'Set the SEO title in Yoast or Rank Math for this post, or fix the title template under the plugin\\'s Search Appearance settings.',\n nextjs: 'Export `metadata.title` from the route segment, or set a `title.template` in the root layout.',\n },\n },\n 'title.long': {\n why: 'Titles beyond roughly 60 characters get truncated in results, so the tail of the title does no work.',\n fix: 'Trim to under 60 characters, keeping the distinguishing words at the front.',\n },\n 'description.missing': {\n why: 'Without a meta description the engine writes its own snippet from page text, which is often a nav menu or boilerplate.',\n fix: 'Write a 140-160 character description that states what the page offers.',\n byPlatform: {\n wordpress: 'Fill the meta description field in the Yoast or Rank Math box below the editor, or set a template for this post type.',\n nextjs: 'Add `description` to the route\\'s exported `metadata` object.',\n },\n },\n 'canonical.missing': {\n why: 'Without a canonical, duplicate URLs (query strings, pagination, tracking parameters, trailing-slash variants) compete against each other and split ranking signals.',\n fix: 'Emit a self-referencing canonical link on every indexable page.',\n byPlatform: {\n wordpress: 'Yoast and Rank Math both output canonicals by default — this usually means the SEO plugin is inactive on this template, or a theme is stripping wp_head().',\n nextjs: 'Set `alternates.canonical` in the route\\'s metadata.',\n },\n },\n 'h1.missing': {\n why: 'The h1 tells both crawlers and answer engines what the page is about, and it anchors the document outline used for passage extraction.',\n fix: 'Add exactly one h1 that matches the page topic.',\n byPlatform: {\n wordpress: 'Many themes render the post title as h2 inside archive templates. Check single.php or the block template for this post type.',\n },\n },\n 'h1.multiple': {\n why: 'Multiple h1 elements make the document outline ambiguous, which weakens passage extraction for AI answers.',\n fix: 'Keep one h1 and demote the rest to h2.',\n },\n 'robots.noindex': {\n why: 'This page is explicitly excluded from search results. If that is unintentional it is invisible traffic loss.',\n fix: 'Remove the noindex directive if the page should rank.',\n byPlatform: {\n wordpress: 'Check Settings → Reading for the site-wide discourage option, and the per-post Advanced tab in your SEO plugin.',\n },\n },\n 'og.title.missing': {\n why: 'Without Open Graph tags, shared links render with whatever the platform can scrape, which is usually wrong.',\n fix: 'Add og:title, og:description, og:image and og:url.',\n byPlatform: {\n wordpress: 'Enable social meta in Yoast (Social tab) or Rank Math, and set a site-wide fallback image.',\n },\n },\n 'og.image.missing': {\n why: 'Links without og:image get a plain text card in messaging apps and social feeds, which measurably lowers click-through.',\n fix: 'Add an og:image of at least 1200x630.',\n },\n 'jsonld.missing': {\n why: 'Structured data is how you become eligible for rich results, and it is the most reliable signal answer engines use to identify entities on a page.',\n fix: 'Add JSON-LD appropriate to the page type — Article for posts, Product for products, LocalBusiness and Organization site-wide.',\n byPlatform: {\n wordpress: 'Rank Math and Yoast both emit a schema graph. If it is absent, the plugin is off for this template or the theme is not calling wp_head().',\n },\n },\n 'jsonld.invalid': {\n why: 'A JSON-LD block that fails to parse is ignored entirely, so any valid markup in the same script tag is lost with it.',\n fix: 'Fix the JSON syntax — usually an unescaped quote or a trailing comma injected by a template.',\n },\n 'jsonld.required.missing': {\n why: 'Google will not show a rich result when a required property is absent, even though the rest of the markup is valid.',\n fix: 'Add the named properties. Verify with the Rich Results Test before shipping.',\n },\n 'jsonld.oneof.missing': {\n why: 'Some types need at least one of a group of properties to qualify for a rich result.',\n fix: 'Add one of the listed properties.',\n },\n 'jsonld.recommended.missing': {\n why: 'Recommended properties are not required, but they widen the rich result and give answer engines more to work with.',\n fix: 'Add them where you have the data.',\n },\n 'content.thin': {\n why: 'Short pages rarely rank for competitive terms and are almost never cited by answer engines, which need enough context to quote.',\n fix: 'Either expand the page substantively or consolidate it into a stronger one.',\n byPlatform: {\n wordpress: 'Tag and category archives commonly trip this. Consider noindexing thin archives rather than padding them.',\n },\n },\n 'images.alt.missing': {\n why: 'Missing alt text is both an accessibility failure and lost context — image search and multimodal crawlers rely on it.',\n fix: 'Describe the image in alt, or use alt=\"\" for purely decorative images so it is explicitly marked.',\n byPlatform: {\n wordpress: 'Set alt text in the Media Library so it applies everywhere the image is reused.',\n },\n },\n 'aeo.lead.missing': {\n why: 'Answer engines extract and quote the opening passage. A page that starts with a hero image, a nav block or a one-line teaser gives them nothing to lift.',\n fix: 'Open with a self-contained paragraph of 40-80 words that directly answers the page\\'s implied question.',\n },\n 'aeo.lead.long': {\n why: 'A very long opening block gets chunked awkwardly and the quotable part may be split across chunks.',\n fix: 'Front-load a short direct answer, then expand below it.',\n },\n 'robotstxt.missing': {\n why: 'Without robots.txt you have no control over crawler access and no place to declare your sitemap.',\n fix: 'Add a robots.txt at the site root with a Sitemap line.',\n byPlatform: {\n wordpress: 'WordPress serves a virtual robots.txt; a missing one usually means a plugin or the server is intercepting the request.',\n },\n },\n 'robotstxt.sitemap.missing': {\n why: 'The sitemap declaration in robots.txt is the primary discovery path for crawlers that did not arrive through Search Console.',\n fix: 'Add a Sitemap line pointing at your sitemap index.',\n byPlatform: {\n wordpress: 'WordPress core exposes /wp-sitemap.xml; Yoast and Rank Math replace it with their own. Declare whichever is live.',\n },\n },\n 'aeo.crawler.blocked': {\n why: 'A blocked AI crawler cannot fetch your pages, so your site cannot be cited in that assistant\\'s answers. This is sometimes deliberate — worth confirming it is.',\n fix: 'Remove the Disallow for agents you want citing you, and keep it for the ones you do not.',\n byPlatform: {\n wordpress: 'Some security and SEO plugins add AI crawler blocks by default. Check the plugin that manages your robots.txt.',\n },\n },\n 'aeo.llmstxt.missing': {\n why: 'llms.txt is an emerging convention giving assistants a curated map of your site. Adoption is still early, so treat this as an opportunity rather than a defect.',\n fix: 'Publish /llms.txt with a short site summary and links to your most important pages.',\n },\n 'hreflang.missing': {\n why: 'The rest of the site declares language alternates but this page does not, so search engines treat it as having no localised counterparts and may serve the wrong language version.',\n fix: 'Add the full set of hreflang links, including a self-reference.',\n byPlatform: {\n wordpress: 'Usually a template the translation plugin does not cover. Check that WPML or Polylang is active for this post type.',\n nextjs: 'Set `alternates.languages` in the route\\'s metadata, or generate it in the shared layout.',\n },\n },\n 'hreflang.nonreciprocal': {\n why: 'Google requires hreflang annotations to be reciprocal. If page A points at B but B does not point back at A, the entire annotation is discarded — not just the one link — so the whole language cluster stops working.',\n fix: 'Make every page in a language group list every other page in that group, including itself.',\n },\n 'hreflang.self.missing': {\n why: 'Each page in an hreflang set should reference itself. Without it, some engines will not associate the page with its own language.',\n fix: 'Add an hreflang link pointing at this page\\'s own URL with its own language code.',\n },\n 'hreflang.xdefault.missing': {\n why: 'x-default tells engines which version to serve to users whose language matches none of your alternates. Without it, that choice is made for you.',\n fix: 'Add an x-default link pointing at your default or language-selection page.',\n },\n 'hreflang.invalid': {\n why: 'A malformed language code makes the annotation invalid and it is ignored.',\n fix: 'Use ISO 639-1 language codes, optionally with an ISO 3166-1 Alpha 2 region — `en`, `ml`, `en-IN` — or `x-default`.',\n },\n 'hreflang.noindex.target': {\n why: 'An hreflang alternate that is noindexed cannot be served as a language variant, which invalidates that link in the cluster.',\n fix: 'Either remove the noindex from the target or drop it from the hreflang set.',\n },\n 'duplicate.title': {\n why: 'Identical titles across pages make them compete for the same queries and signal thin or templated content.',\n fix: 'Make each title unique, usually by including the distinguishing attribute of the page.',\n byPlatform: {\n wordpress: 'Almost always a title template problem — check Search Appearance for the affected post type or archive.',\n },\n },\n 'duplicate.description': {\n why: 'Repeated descriptions get discarded by search engines, which then write their own snippet.',\n fix: 'Vary the description per page, or leave it off and let the engine choose rather than repeating boilerplate.',\n },\n 'duplicate.canonical': {\n why: 'Several pages pointing at one canonical means those pages are declaring themselves duplicates and will not rank independently. Correct for pagination and filters, a serious bug elsewhere.',\n fix: 'Confirm each canonical is self-referencing unless consolidation is intended.',\n byPlatform: {\n wordpress: 'A common symptom of a plugin canonicalising every archive page to the parent.',\n },\n },\n 'canonical.offsite': {\n why: 'The canonical points at a different host, which tells search engines to index that host instead of this one. A staging or CDN hostname leaking into canonicals removes the live site from results.',\n fix: 'Point canonicals at the production origin. If the content is deliberately syndicated from another domain, this is correct and the rule can be switched off in config.',\n byPlatform: {\n wordpress: 'Check the Site Address (URL) setting, and any WP_HOME or WP_SITEURL override in wp-config.php, on the environment that built this.',\n nextjs: 'Check `metadataBase` — a wrong or missing value makes every relative canonical resolve against the wrong origin.',\n },\n },\n 'canonical.crosspath': {\n why: 'The canonical points at a different path than the page itself, so this URL is asking not to be indexed in favour of another.',\n fix: 'Verify the target is correct. If this page should rank on its own, make the canonical self-referencing.',\n },\n};\n\n/** Detect the publishing platform from generator meta and URL shape. */\nexport function detectPlatform(generators: (string | null)[], urls: string[] = []): Platform {\n const gen = generators.filter(Boolean).join(' ').toLowerCase();\n if (gen.includes('wordpress')) return 'wordpress';\n if (gen.includes('drupal')) return 'drupal';\n if (gen.includes('wix')) return 'wix';\n if (gen.includes('squarespace')) return 'squarespace';\n if (gen.includes('webflow')) return 'webflow';\n if (gen.includes('shopify')) return 'shopify';\n if (gen.includes('next.js')) return 'nextjs';\n\n const joined = urls.join(' ').toLowerCase();\n if (joined.includes('/wp-content/') || joined.includes('/wp-json/')) return 'wordpress';\n if (joined.includes('/_next/')) return 'nextjs';\n if (joined.includes('cdn.shopify.com')) return 'shopify';\n return 'unknown';\n}\n\n/** Attach why/fix text to a finding, preferring platform-specific advice. */\nexport function withGuidance<T extends { code: string }>(\n finding: T,\n platform: Platform = 'unknown',\n): T & { detail?: string; fix?: string } {\n const guidance = GUIDANCE[finding.code];\n if (!guidance) return finding;\n return {\n ...finding,\n detail: guidance.why,\n fix: guidance.byPlatform?.[platform] ?? guidance.fix,\n };\n}\n","import pc from 'picocolors';\nimport type { Aggregate, Config, Finding, Platform, Severity } from './types.js';\n\nconst ORDER: Record<Severity, number> = { error: 0, warn: 1, info: 2 };\n\n/** Apply user severity overrides and drop anything switched off. */\nexport function applyConfig(findings: Finding[], config: Config = {}): Finding[] {\n const overrides = config.severity ?? {};\n const out: Finding[] = [];\n for (const finding of findings) {\n const override = overrides[finding.code];\n if (override === 'off') continue;\n out.push(override ? { ...finding, severity: override } : finding);\n }\n return out.sort(\n (a, b) => ORDER[a.severity] - ORDER[b.severity] || (a.route ?? '').localeCompare(b.route ?? ''),\n );\n}\n\nexport function summarize(findings: Finding[]) {\n return {\n error: findings.filter((f) => f.severity === 'error').length,\n warn: findings.filter((f) => f.severity === 'warn').length,\n info: findings.filter((f) => f.severity === 'info').length,\n };\n}\n\nexport function shouldFail(findings: Finding[], failOn: Severity): boolean {\n const threshold = ORDER[failOn];\n // An unknown severity would make every comparison false and silently disable\n // the gate, which is the one failure mode a CI check must not have.\n if (threshold === undefined) {\n throw new Error(`Unknown severity \"${failOn}\". Expected one of: error, warn, info.`);\n }\n return findings.some((f) => ORDER[f.severity] <= threshold);\n}\n\nconst BADGE: Record<Severity, (s: string) => string> = {\n error: (s) => pc.red(s),\n warn: (s) => pc.yellow(s),\n info: (s) => pc.dim(s),\n};\n\nexport function formatPretty(findings: Finding[]): string {\n if (findings.length === 0) return pc.green('No SEO/AEO changes or issues found.');\n\n const byRoute = new Map<string, Finding[]>();\n for (const finding of findings) {\n const key = finding.route ?? '(site-wide)';\n if (!byRoute.has(key)) byRoute.set(key, []);\n byRoute.get(key)!.push(finding);\n }\n\n const lines: string[] = [];\n for (const [route, group] of byRoute) {\n lines.push(pc.bold(route));\n for (const f of group) {\n lines.push(` ${BADGE[f.severity](f.severity.padEnd(5))} ${f.message} ${pc.dim(f.code)}`);\n }\n lines.push('');\n }\n\n const s = summarize(findings);\n lines.push(`${s.error} error, ${s.warn} warning, ${s.info} info`);\n return lines.join('\\n');\n}\n\nexport function formatJson(findings: Finding[]): string {\n return JSON.stringify({ schemaVersion: 1, summary: summarize(findings), findings }, null, 2);\n}\n\n/** Titles routinely contain a pipe (\"Buy Widgets | Acme\"), which would split the row. */\nconst escapeCell = (value: string) => value.replace(/\\|/g, '\\\\|');\n\n/** Markdown table, sized for a PR comment. */\nexport function formatMarkdown(findings: Finding[]): string {\n const s = summarize(findings);\n if (findings.length === 0) return '### pagetrace\\n\\nNo SEO/AEO changes or issues found.';\n\n const rows = findings.map(\n (f) => `| ${f.severity} | \\`${escapeCell(f.route ?? '—')}\\` | ${escapeCell(f.message)} | \\`${f.code}\\` |`,\n );\n return [\n '### pagetrace',\n '',\n `${s.error} error · ${s.warn} warning · ${s.info} info`,\n '',\n '| Severity | Route | Finding | Code |',\n '| --- | --- | --- | --- |',\n ...rows,\n ].join('\\n');\n}\n\n/** GitHub Actions workflow-command annotations. */\nexport function formatGithub(findings: Finding[]): string {\n return findings\n .filter((f) => f.severity !== 'info')\n .map((f) => {\n const level = f.severity === 'error' ? 'error' : 'warning';\n return `::${level} title=${f.code}::${f.route ?? 'site'} — ${f.message}`;\n })\n .join('\\n');\n}\n\n/**\n * Roll findings up by issue rather than by page. On a large CMS site the same\n * template defect produces hundreds of identical findings; the useful unit is\n * \"canonical missing on 43 pages\", not 43 separate lines.\n */\nexport function aggregate(findings: Finding[]): Aggregate[] {\n const map = new Map<string, Aggregate>();\n for (const finding of findings) {\n // Keyed by code *and* message: several rules embed type-specific detail in\n // the message, and merging those would attach one type's message to another\n // type's routes. Rules whose message would otherwise vary per page keep the\n // varying number in `after` instead.\n const key = `${finding.code}::${finding.message}`;\n const existing = map.get(key);\n if (existing) {\n existing.count += 1;\n if (finding.route) existing.routes.push(finding.route);\n continue;\n }\n map.set(key, {\n code: finding.code,\n severity: finding.severity,\n count: 1,\n routes: finding.route ? [finding.route] : [],\n message: finding.message,\n detail: finding.detail,\n fix: finding.fix,\n });\n }\n return [...map.values()].sort(\n (a, b) => ORDER[a.severity] - ORDER[b.severity] || b.count - a.count,\n );\n}\n\nexport interface AuditMeta {\n target: string;\n platform: Platform;\n pageCount: number;\n generatedAt: string;\n}\n\nconst PLATFORM_LABEL: Record<Platform, string> = {\n wordpress: 'WordPress',\n nextjs: 'Next.js',\n shopify: 'Shopify',\n webflow: 'Webflow',\n wix: 'Wix',\n squarespace: 'Squarespace',\n drupal: 'Drupal',\n unknown: 'Unknown platform',\n};\n\n/**\n * An issue on nearly every page is one template defect, not N problems.\n * Labelling it keeps the reader from triaging the same fix forty times.\n */\nexport function isTemplateWide(group: Aggregate, pageCount: number): boolean {\n return pageCount >= 5 && group.routes.length >= Math.ceil(pageCount * 0.8);\n}\n\n/** Counts of distinct issues, and of page instances, by severity. */\nexport function countIssues(groups: Aggregate[]) {\n const blank = () => ({ error: 0, warn: 0, info: 0 }) as Record<Severity, number>;\n const issues = blank();\n const instances = blank();\n for (const group of groups) {\n issues[group.severity] += 1;\n instances[group.severity] += group.count;\n }\n return { issues, instances, total: groups.length };\n}\n\nfunction sampleRoutes(routes: string[], limit = 5): string {\n if (routes.length === 0) return 'site-wide';\n const shown = routes.slice(0, limit).join(', ');\n return routes.length > limit ? `${shown} +${routes.length - limit} more` : shown;\n}\n\nexport function formatAuditPretty(groups: Aggregate[], meta: AuditMeta): string {\n const lines: string[] = [\n pc.bold(meta.target),\n pc.dim(`${PLATFORM_LABEL[meta.platform]} · ${meta.pageCount} pages · ${meta.generatedAt}`),\n '',\n ];\n\n if (groups.length === 0) {\n lines.push(pc.green('No issues found.'));\n return lines.join('\\n');\n }\n\n for (const group of groups) {\n const scope = isTemplateWide(group, meta.pageCount)\n ? pc.dim(`(${group.count} pages — one template fix)`)\n : pc.dim(`(${group.count})`);\n lines.push(`${BADGE[group.severity](group.severity.toUpperCase())} ${pc.bold(group.message)} ${scope}`);\n if (group.detail) lines.push(` ${group.detail}`);\n if (group.fix) lines.push(` ${pc.cyan('Fix:')} ${group.fix}`);\n if (group.routes.length > 0) lines.push(` ${pc.dim(sampleRoutes(group.routes))}`);\n lines.push('');\n }\n\n const { issues, instances, total } = countIssues(groups);\n lines.push(\n `${total} issue${total === 1 ? '' : 's'}: ${issues.error} error, ${issues.warn} warning, ${issues.info} info`,\n );\n lines.push(\n pc.dim(\n `across ${instances.error + instances.warn + instances.info} page findings on ${meta.pageCount} pages`,\n ),\n );\n return lines.join('\\n');\n}\n\nexport function formatAuditMarkdown(groups: Aggregate[], meta: AuditMeta): string {\n const lines = [\n `# SEO & AEO audit — ${meta.target}`,\n '',\n `${PLATFORM_LABEL[meta.platform]} · ${meta.pageCount} pages crawled · ${meta.generatedAt}`,\n '',\n ];\n if (groups.length === 0) {\n lines.push('No issues found.');\n return lines.join('\\n');\n }\n for (const group of groups) {\n lines.push(`## ${group.message}`, '');\n const scope = isTemplateWide(group, meta.pageCount)\n ? `affects ${group.count} pages — one template fix`\n : `affects ${group.count} page${group.count === 1 ? '' : 's'}`;\n lines.push(`**${group.severity.toUpperCase()}** · ${scope} · \\`${group.code}\\``, '');\n if (group.detail) lines.push(group.detail, '');\n if (group.fix) lines.push(`**Fix.** ${group.fix}`, '');\n if (group.routes.length > 0) {\n lines.push('<details><summary>Affected pages</summary>', '');\n for (const route of group.routes.slice(0, 50)) lines.push(`- \\`${route}\\``);\n if (group.routes.length > 50) lines.push(`- …and ${group.routes.length - 50} more`);\n lines.push('', '</details>', '');\n }\n }\n return lines.join('\\n');\n}\n\nconst escapeHtml = (value: string) =>\n value.replace(/[&<>\"]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;' })[c]!);\n\n/** Self-contained HTML report, suitable for handing to a client. */\nexport function formatAuditHtml(groups: Aggregate[], meta: AuditMeta): string {\n const { issues } = countIssues(groups);\n\n const cards = groups\n .map((group) => {\n const scope = isTemplateWide(group, meta.pageCount)\n ? `<span class=\"tmpl\">template-wide</span>`\n : '';\n const routes =\n group.routes.length > 0\n ? `<details><summary>${group.routes.length} affected page${group.routes.length === 1 ? '' : 's'}</summary><ul>${group.routes\n .slice(0, 100)\n .map((r) => `<li><code>${escapeHtml(r)}</code></li>`)\n .join('')}</ul></details>`\n : '';\n return `<article class=\"f ${group.severity}\">\n <header><span class=\"sev\">${group.severity}</span><h2>${escapeHtml(group.message)}</h2>${scope}<span class=\"count\">${group.count}</span></header>\n ${group.detail ? `<p>${escapeHtml(group.detail)}</p>` : ''}\n ${group.fix ? `<p class=\"fix\"><strong>Fix.</strong> ${escapeHtml(group.fix)}</p>` : ''}\n ${routes}\n <code class=\"code\">${escapeHtml(group.code)}</code>\n</article>`;\n })\n .join('\\n');\n\n return `<!doctype html>\n<html lang=\"en\"><head><meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<title>SEO &amp; AEO audit — ${escapeHtml(meta.target)}</title>\n<style>\n:root{--fg:#16181d;--muted:#6b7280;--line:#e5e7eb;--err:#b42318;--warn:#b54708;--info:#475467;--bg:#fff}\n*{box-sizing:border-box}\nbody{margin:0;padding:48px 24px;font:16px/1.6 ui-sans-serif,system-ui,-apple-system,\"Segoe UI\",sans-serif;color:var(--fg);background:var(--bg)}\nmain{max-width:820px;margin:0 auto}\nh1{font-size:28px;margin:0 0 6px;letter-spacing:-.02em}\n.meta{color:var(--muted);font-size:14px;margin:0 0 28px}\n.totals{display:flex;gap:12px;margin:0 0 36px;padding:0;list-style:none}\n.totals li{flex:1;border:1px solid var(--line);border-radius:10px;padding:14px 16px}\n.totals b{display:block;font-size:26px;line-height:1.2}\n.totals span{color:var(--muted);font-size:13px;text-transform:uppercase;letter-spacing:.06em}\n.f{border:1px solid var(--line);border-left-width:4px;border-radius:10px;padding:18px 20px;margin:0 0 16px}\n.f.error{border-left-color:var(--err)} .f.warn{border-left-color:var(--warn)} .f.info{border-left-color:var(--info)}\n.f header{display:flex;align-items:baseline;gap:10px;margin-bottom:8px}\n.f h2{font-size:17px;margin:0;flex:1;letter-spacing:-.01em}\n.sev{font-size:11px;text-transform:uppercase;letter-spacing:.08em;font-weight:700}\n.error .sev{color:var(--err)} .warn .sev{color:var(--warn)} .info .sev{color:var(--info)}\n.count{font-variant-numeric:tabular-nums;color:var(--muted);font-size:14px}\n.tmpl{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);border:1px solid var(--line);border-radius:99px;padding:2px 8px}\n.f p{margin:0 0 10px;font-size:15px}\n.fix{color:#065f46}\ndetails{font-size:14px;margin:10px 0}\nsummary{cursor:pointer;color:var(--muted)}\ndetails ul{margin:8px 0 0;padding-left:20px;max-height:260px;overflow:auto}\n.code{font-size:12px;color:var(--muted)}\nfooter{margin-top:40px;color:var(--muted);font-size:13px;border-top:1px solid var(--line);padding-top:16px}\n</style></head>\n<body><main>\n<h1>SEO &amp; AEO audit</h1>\n<p class=\"meta\">${escapeHtml(meta.target)} · ${PLATFORM_LABEL[meta.platform]} · ${meta.pageCount} pages crawled · ${escapeHtml(meta.generatedAt)}</p>\n<ul class=\"totals\">\n <li><b>${issues.error}</b><span>Errors</span></li>\n <li><b>${issues.warn}</b><span>Warnings</span></li>\n <li><b>${issues.info}</b><span>Notes</span></li>\n</ul>\n${cards || '<p>No issues found.</p>'}\n<footer>Generated by pagetrace. Findings are heuristic; verify structured data with Google&rsquo;s Rich Results Test before shipping fixes.</footer>\n</main></body></html>`;\n}\n","import { readdir, readFile } from 'node:fs/promises';\nimport { join, relative, sep } from 'node:path';\nimport {\n extractLlmsTxt,\n extractPage,\n extractRobotsTxt,\n extractSitemapUrls,\n} from './extract.js';\nimport { DEFAULT_AI_AGENTS } from './rules/rich-results.js';\nimport type { Config, PageFingerprint, SiteFingerprint, Snapshot } from './types.js';\n\nexport function routeFromFilePath(root: string, filePath: string): string {\n const rel = relative(root, filePath).split(sep).join('/');\n const withoutExt = rel.replace(/\\.html?$/i, '');\n const route = withoutExt === 'index' ? '/' : `/${withoutExt.replace(/\\/index$/, '')}`;\n return route === '//' ? '/' : route;\n}\n\nexport function routeFromUrl(url: string): string {\n try {\n const parsed = new URL(url);\n const path = parsed.pathname.replace(/\\/+$/, '');\n return path === '' ? '/' : path;\n } catch {\n return url;\n }\n}\n\nexport function shouldIgnore(route: string, patterns: string[] = []): boolean {\n return patterns.some((pattern) =>\n pattern.endsWith('*') ? route.startsWith(pattern.slice(0, -1)) : route === pattern,\n );\n}\n\nasync function walkHtml(dir: string, acc: string[] = []): Promise<string[]> {\n for (const entry of await readdir(dir, { withFileTypes: true })) {\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue;\n await walkHtml(full, acc);\n } else if (/\\.html?$/i.test(entry.name)) {\n acc.push(full);\n }\n }\n return acc;\n}\n\nconst DEFAULT_TIMEOUT_MS = 15_000;\n\n/**\n * `null` means the resource is genuinely absent; anything else throws.\n *\n * The distinction is the whole point: swallowing a 503 or a DNS failure into\n * \"not found\" makes an unreachable site look like a deleted one, and `check`\n * then reports a wall of removals that never happened.\n */\nasync function fetchText(url: string, timeoutMs = DEFAULT_TIMEOUT_MS): Promise<string | null> {\n let response: Response;\n try {\n response = await fetch(url, {\n signal: AbortSignal.timeout(timeoutMs),\n headers: { 'user-agent': 'pagetrace (+https://npmjs.com/package/pagetrace)' },\n });\n } catch (cause) {\n throw new Error(`Could not reach ${url}: ${(cause as Error).message}`, { cause });\n }\n if (response.status === 404 || response.status === 410) return null;\n if (!response.ok) throw new Error(`Could not reach ${url}: HTTP ${response.status}.`);\n return await response.text();\n}\n\n/** For speculative URLs, where a failure is a miss rather than a problem. */\nfunction tryFetchText(url: string, timeoutMs?: number): Promise<string | null> {\n return fetchText(url, timeoutMs).catch(() => null);\n}\n\n/** Build a snapshot from a directory of pre-rendered HTML (next export, dist, out). */\nexport async function snapshotFromDir(dir: string, config: Config = {}): Promise<Snapshot> {\n const files = await walkHtml(dir);\n const pages: Record<string, PageFingerprint> = {};\n\n for (const file of files.sort()) {\n const route = routeFromFilePath(dir, file);\n if (shouldIgnore(route, config.ignoreRoutes)) continue;\n if (route in pages) {\n // e.g. both blog.html and blog/index.html. Silently overwriting makes the\n // survivor depend on readdir order, which flips between runs.\n console.error(`pagetrace: ${file} maps to ${route}, already taken. Skipping.`);\n continue;\n }\n pages[route] = extractPage(await readFile(file, 'utf8'), route);\n }\n\n const agents = config.aiAgents ?? DEFAULT_AI_AGENTS;\n const site: SiteFingerprint = {\n origin: config.siteUrl ? new URL(config.siteUrl).origin : null,\n robotsTxt: null,\n llmsTxt: null,\n };\n\n const robots = await readFile(join(dir, 'robots.txt'), 'utf8').catch(() => null);\n if (robots !== null) site.robotsTxt = extractRobotsTxt(robots, agents);\n\n const llms = await readFile(join(dir, 'llms.txt'), 'utf8').catch(() => null);\n if (llms !== null) site.llmsTxt = extractLlmsTxt(llms);\n\n return { schemaVersion: 1, createdAt: new Date().toISOString(), site, pages };\n}\n\nexport interface CrawlOptions extends Config {\n /** Cap the number of pages fetched. */\n limit?: number;\n /** Parallel requests. */\n concurrency?: number;\n /** Per-request timeout in milliseconds. */\n timeout?: number;\n}\n\n/** A sitemap index can list hundreds of children; we do not need all of them. */\nconst MAX_NESTED_SITEMAPS = 50;\n\n/** Build a snapshot by fetching a live origin, discovering routes via sitemap. */\nexport async function snapshotFromOrigin(\n origin: string,\n options: CrawlOptions = {},\n): Promise<Snapshot> {\n const base = new URL(origin);\n const agents = options.aiAgents ?? DEFAULT_AI_AGENTS;\n const timeout = options.timeout;\n const site: SiteFingerprint = { origin: base.origin, robotsTxt: null, llmsTxt: null };\n const limit = options.limit ?? 200;\n\n const robots = await fetchText(new URL('/robots.txt', base).href, timeout);\n if (robots !== null) site.robotsTxt = extractRobotsTxt(robots, agents);\n\n const llms = await fetchText(new URL('/llms.txt', base).href, timeout);\n if (llms !== null) site.llmsTxt = extractLlmsTxt(llms);\n\n // Fall back through the conventional locations. WordPress core serves\n // /wp-sitemap.xml; Yoast and Rank Math replace it with sitemap_index.xml.\n const sitemapUrls = site.robotsTxt?.sitemaps.length\n ? site.robotsTxt.sitemaps\n : [\n new URL('/sitemap.xml', base).href,\n new URL('/sitemap_index.xml', base).href,\n new URL('/wp-sitemap.xml', base).href,\n ];\n\n const discovered = new Set<string>();\n const fetched = new Set<string>();\n let nestedFetches = 0;\n\n for (const sitemapUrl of sitemapUrls) {\n if (discovered.size >= limit) break;\n if (discovered.size > 0 && !site.robotsTxt?.sitemaps.length) break;\n if (fetched.has(sitemapUrl)) continue;\n fetched.add(sitemapUrl);\n // A guessed location that is not there is a miss, not a failure.\n const xml = site.robotsTxt?.sitemaps.length\n ? await fetchText(sitemapUrl, timeout)\n : await tryFetchText(sitemapUrl, timeout);\n if (!xml) continue;\n\n for (const loc of extractSitemapUrls(xml)) {\n if (discovered.size >= limit) break;\n // A sitemap index points at more sitemaps; follow one level.\n if (!/\\.xml(\\.gz)?($|\\?)/i.test(loc)) {\n discovered.add(loc);\n continue;\n }\n // ponytail: a gzipped child is recognised, so it is not crawled as a page\n // and parsed as HTML, but its URLs are not discovered either. Pipe the\n // body through node:zlib if a real site needs them.\n if (/\\.gz($|\\?)/i.test(loc)) continue;\n if (nestedFetches >= MAX_NESTED_SITEMAPS || fetched.has(loc)) continue;\n fetched.add(loc);\n nestedFetches += 1;\n const nested = await tryFetchText(loc, timeout);\n if (nested) for (const url of extractSitemapUrls(nested)) discovered.add(url);\n }\n }\n if (discovered.size === 0) discovered.add(base.href);\n\n // routeFromUrl drops the host, so an off-origin URL would silently overwrite\n // the same route from this site.\n const targets = [...discovered]\n .filter((url) => {\n try {\n return new URL(url).origin === base.origin;\n } catch {\n return false;\n }\n })\n .filter((url) => !shouldIgnore(routeFromUrl(url), options.ignoreRoutes))\n .slice(0, limit);\n\n const pages: Record<string, PageFingerprint> = {};\n const concurrency = Math.max(1, options.concurrency ?? 5);\n const queue = [...targets];\n\n await Promise.all(\n Array.from({ length: Math.min(concurrency, queue.length) }, async () => {\n while (queue.length > 0) {\n const url = queue.shift()!;\n const html = await fetchText(url, timeout);\n if (html === null) continue;\n const route = routeFromUrl(url);\n pages[route] = extractPage(html, route);\n }\n }),\n );\n\n return { schemaVersion: 1, createdAt: new Date().toISOString(), site, pages };\n}\n"],"mappings":"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","names":["pc"]}
@@ -0,0 +1,232 @@
1
+ import { HTMLElement } from 'node-html-parser';
2
+
3
+ type Severity = 'error' | 'warn' | 'info';
4
+ /** A single structured-data entity found on a page. */
5
+ interface JsonLdEntity {
6
+ type: string;
7
+ id?: string;
8
+ /** Sorted list of top-level property names present on the entity. */
9
+ properties: string[];
10
+ }
11
+ /** The normalized SEO/AEO surface of one page. */
12
+ interface PageFingerprint {
13
+ route: string;
14
+ title: string | null;
15
+ description: string | null;
16
+ canonical: string | null;
17
+ /** Content of <meta name="robots">, lowercased. */
18
+ robots: string | null;
19
+ og: Record<string, string>;
20
+ twitter: Record<string, string>;
21
+ /** hreflang value -> href */
22
+ hreflang: Record<string, string>;
23
+ h1: string[];
24
+ /** Heading tag sequence in document order, e.g. ["h1","h2","h2","h3"]. */
25
+ headingOutline: string[];
26
+ jsonLd: JsonLdEntity[];
27
+ wordCount: number;
28
+ images: {
29
+ total: number;
30
+ missingAlt: number;
31
+ };
32
+ /** Whether the page exposes an answer-shaped opening paragraph (AEO signal). */
33
+ leadAnswerWords: number;
34
+ /** Content of <meta name="generator">, used for platform detection. */
35
+ generator: string | null;
36
+ }
37
+ /** Site-wide signals that live outside any single page. */
38
+ interface SiteFingerprint {
39
+ /**
40
+ * The origin this snapshot was crawled from, e.g. "https://example.com".
41
+ * Absent for a filesystem crawl and for lockfiles written before 0.2.0.
42
+ */
43
+ origin?: string | null;
44
+ robotsTxt: {
45
+ present: boolean;
46
+ /** agent name -> whether the root path is crawlable */
47
+ aiAgents: Record<string, 'allowed' | 'disallowed'>;
48
+ sitemaps: string[];
49
+ } | null;
50
+ llmsTxt: {
51
+ present: boolean;
52
+ /** H2 section titles, used to detect silent truncation. */
53
+ sections: string[];
54
+ bytes: number;
55
+ } | null;
56
+ }
57
+ interface Snapshot {
58
+ schemaVersion: 1;
59
+ createdAt: string;
60
+ site: SiteFingerprint;
61
+ pages: Record<string, PageFingerprint>;
62
+ }
63
+ type Platform = 'wordpress' | 'nextjs' | 'shopify' | 'webflow' | 'wix' | 'squarespace' | 'drupal' | 'unknown';
64
+ interface Finding {
65
+ /** Stable machine code, e.g. "canonical.removed". Integrations key on this. */
66
+ code: string;
67
+ severity: Severity;
68
+ route: string | null;
69
+ message: string;
70
+ before?: unknown;
71
+ after?: unknown;
72
+ /** Why this matters, for audit output. */
73
+ detail?: string;
74
+ /** How to fix it, platform-specific where known. */
75
+ fix?: string;
76
+ }
77
+ /** One issue rolled up across every route it affects. */
78
+ interface Aggregate {
79
+ code: string;
80
+ severity: Severity;
81
+ count: number;
82
+ routes: string[];
83
+ message: string;
84
+ detail?: string;
85
+ fix?: string;
86
+ }
87
+ interface Config {
88
+ /** Per-code severity overrides. Set to "off" to silence a rule. */
89
+ severity?: Record<string, Severity | 'off'>;
90
+ /** Routes to skip entirely (exact match or trailing-* prefix). */
91
+ ignoreRoutes?: string[];
92
+ /** Extra AI user agents to check in robots.txt. */
93
+ aiAgents?: string[];
94
+ /** Minimum word count before a page is flagged as thin. */
95
+ minWordCount?: number;
96
+ /**
97
+ * The site's own origin, e.g. "https://example.com". Lets a --dir crawl detect
98
+ * canonicals pointing at another host; an origin crawl infers it.
99
+ */
100
+ siteUrl?: string;
101
+ }
102
+
103
+ /**
104
+ * Rules that hold regardless of history. These overlap with what any auditor
105
+ * reports; the diff engine in `diff.ts` is what catches regressions.
106
+ */
107
+ declare function auditPage(page: PageFingerprint, config?: Config): Finding[];
108
+ declare function auditSite(snapshot: Snapshot): Finding[];
109
+ /**
110
+ * Rules that only exist when you look at the whole site at once. These are the
111
+ * findings that matter most on a large CMS site, where the defects come from
112
+ * templates rather than individual pages.
113
+ */
114
+ declare function auditCrossPage(snapshot: Snapshot): Finding[];
115
+ /**
116
+ * hreflang is the rule set most worth automating: Google requires the
117
+ * annotations to be reciprocal, and a one-sided set is silently ignored rather
118
+ * than reported anywhere. You cannot see this from a single page, which is why
119
+ * it lives here rather than in auditPage.
120
+ */
121
+ declare function auditHreflang(snapshot: Snapshot): Finding[];
122
+ declare function auditSnapshot(snapshot: Snapshot, config?: Config): Finding[];
123
+
124
+ declare function diffPage(before: PageFingerprint, after: PageFingerprint): Finding[];
125
+ declare function diffSite(before: Snapshot['site'], after: Snapshot['site']): Finding[];
126
+ declare function diffSnapshots(before: Snapshot, after: Snapshot): Finding[];
127
+
128
+ declare function extractJsonLd(root: HTMLElement): JsonLdEntity[];
129
+ declare function extractPage(html: string, route: string): PageFingerprint;
130
+ /** Parse robots.txt into per-agent crawlability of the site root. */
131
+ declare function extractRobotsTxt(body: string, agents: string[]): {
132
+ present: boolean;
133
+ aiAgents: Record<string, "allowed" | "disallowed">;
134
+ sitemaps: string[];
135
+ };
136
+ /** Parse llms.txt, capturing section headings so truncation is detectable. */
137
+ declare function extractLlmsTxt(body: string): {
138
+ present: boolean;
139
+ sections: string[];
140
+ bytes: number;
141
+ };
142
+ /** Pull <loc> entries out of a sitemap or sitemap index. */
143
+ declare function extractSitemapUrls(xml: string): string[];
144
+
145
+ interface Guidance {
146
+ /** Why the issue costs you traffic or citations. */
147
+ why: string;
148
+ /** Generic remedy. */
149
+ fix: string;
150
+ /** Platform-specific remedy, used when the platform is detected. */
151
+ byPlatform?: Partial<Record<Platform, string>>;
152
+ }
153
+ /**
154
+ * Explanations attached to findings in audit output. Diff output stays terse —
155
+ * you already know what a canonical is when you are reviewing a regression.
156
+ * An audit handed to a client or a content team needs the reasoning.
157
+ */
158
+ declare const GUIDANCE: Record<string, Guidance>;
159
+ /** Detect the publishing platform from generator meta and URL shape. */
160
+ declare function detectPlatform(generators: (string | null)[], urls?: string[]): Platform;
161
+ /** Attach why/fix text to a finding, preferring platform-specific advice. */
162
+ declare function withGuidance<T extends {
163
+ code: string;
164
+ }>(finding: T, platform?: Platform): T & {
165
+ detail?: string;
166
+ fix?: string;
167
+ };
168
+
169
+ /** Apply user severity overrides and drop anything switched off. */
170
+ declare function applyConfig(findings: Finding[], config?: Config): Finding[];
171
+ declare function summarize(findings: Finding[]): {
172
+ error: number;
173
+ warn: number;
174
+ info: number;
175
+ };
176
+ declare function shouldFail(findings: Finding[], failOn: Severity): boolean;
177
+ declare function formatPretty(findings: Finding[]): string;
178
+ declare function formatJson(findings: Finding[]): string;
179
+ /** Markdown table, sized for a PR comment. */
180
+ declare function formatMarkdown(findings: Finding[]): string;
181
+ /** GitHub Actions workflow-command annotations. */
182
+ declare function formatGithub(findings: Finding[]): string;
183
+ /**
184
+ * Roll findings up by issue rather than by page. On a large CMS site the same
185
+ * template defect produces hundreds of identical findings; the useful unit is
186
+ * "canonical missing on 43 pages", not 43 separate lines.
187
+ */
188
+ declare function aggregate(findings: Finding[]): Aggregate[];
189
+ interface AuditMeta {
190
+ target: string;
191
+ platform: Platform;
192
+ pageCount: number;
193
+ generatedAt: string;
194
+ }
195
+ declare function formatAuditPretty(groups: Aggregate[], meta: AuditMeta): string;
196
+ declare function formatAuditMarkdown(groups: Aggregate[], meta: AuditMeta): string;
197
+ /** Self-contained HTML report, suitable for handing to a client. */
198
+ declare function formatAuditHtml(groups: Aggregate[], meta: AuditMeta): string;
199
+
200
+ /**
201
+ * Required and recommended properties for the Schema.org types Google supports
202
+ * as rich results. Sourced from Google Search Central's structured data
203
+ * reference. Deliberately a plain data table so it can be updated without
204
+ * touching the engine, and so consumers can extend it.
205
+ */
206
+ interface RichResultRule {
207
+ required: string[];
208
+ recommended: string[];
209
+ /** Groups where at least one member must be present. */
210
+ oneOf?: string[][];
211
+ }
212
+ declare const RICH_RESULT_RULES: Record<string, RichResultRule>;
213
+ /** AI crawler user agents checked against robots.txt by default. */
214
+ declare const DEFAULT_AI_AGENTS: string[];
215
+
216
+ declare function routeFromFilePath(root: string, filePath: string): string;
217
+ declare function routeFromUrl(url: string): string;
218
+ declare function shouldIgnore(route: string, patterns?: string[]): boolean;
219
+ /** Build a snapshot from a directory of pre-rendered HTML (next export, dist, out). */
220
+ declare function snapshotFromDir(dir: string, config?: Config): Promise<Snapshot>;
221
+ interface CrawlOptions extends Config {
222
+ /** Cap the number of pages fetched. */
223
+ limit?: number;
224
+ /** Parallel requests. */
225
+ concurrency?: number;
226
+ /** Per-request timeout in milliseconds. */
227
+ timeout?: number;
228
+ }
229
+ /** Build a snapshot by fetching a live origin, discovering routes via sitemap. */
230
+ declare function snapshotFromOrigin(origin: string, options?: CrawlOptions): Promise<Snapshot>;
231
+
232
+ export { type Aggregate, type AuditMeta, type Config, DEFAULT_AI_AGENTS, type Finding, GUIDANCE, type Guidance, type JsonLdEntity, type PageFingerprint, type Platform, RICH_RESULT_RULES, type Severity, type SiteFingerprint, type Snapshot, aggregate, applyConfig, auditCrossPage, auditHreflang, auditPage, auditSite, auditSnapshot, detectPlatform, diffPage, diffSite, diffSnapshots, extractJsonLd, extractLlmsTxt, extractPage, extractRobotsTxt, extractSitemapUrls, formatAuditHtml, formatAuditMarkdown, formatAuditPretty, formatGithub, formatJson, formatMarkdown, formatPretty, routeFromFilePath, routeFromUrl, shouldFail, shouldIgnore, snapshotFromDir, snapshotFromOrigin, summarize, withGuidance };