aeorank 1.5.0 → 1.6.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.
package/README.md CHANGED
@@ -150,6 +150,55 @@ Score a single HTML page against 14 per-page AEO criteria. Returns `PageScoreRes
150
150
 
151
151
  Batch-score all pages (homepage + blogSample) from a `SiteData` object. Returns `PageScoreResult[]`.
152
152
 
153
+ ### `buildLinkGraph(pages, domain, homepageUrl)`
154
+
155
+ Analyze internal linking structure from crawled pages. Returns `LinkGraph` with:
156
+
157
+ - `nodes` - Map of URL to `PageNode` (in/out degree, depth, pillar/hub/orphan flags)
158
+ - `edges` - Array of `LinkEdge` (from, to, anchor text)
159
+ - `stats` - `LinkGraphStats` (total pages, orphans, pillars, hubs, avg depth, clusters)
160
+ - `clusters` - `TopicCluster[]` (pillar URL, spoke URLs, cohesion score)
161
+
162
+ ```ts
163
+ import { crawlFullSite, prefetchSiteData, buildLinkGraph } from 'aeorank';
164
+
165
+ const siteData = await prefetchSiteData('example.com');
166
+ const crawl = await crawlFullSite(siteData, { maxPages: 200 });
167
+ const graph = buildLinkGraph(crawl.pages, 'example.com', 'https://example.com');
168
+
169
+ console.log(graph.stats.orphanPages); // Pages with no inbound links
170
+ console.log(graph.stats.pillarPages); // High-authority hub pages
171
+ console.log(graph.clusters); // Topic clusters detected
172
+ ```
173
+
174
+ ### `generateFixPlan(domain, score, criteria, pages?, linkGraph?)`
175
+
176
+ Generate a phased fix plan from audit results. Returns `FixPlan` with:
177
+
178
+ - `phases` - 4 phases (Foundation, Content, Authority, Architecture) with prioritized `FixAction[]`
179
+ - `quickWins` - Low-effort, high-impact fixes
180
+ - `projectedScore` - Estimated score after applying all fixes
181
+ - `summary` - Counts by impact level, top opportunity, estimated effort
182
+
183
+ Each `FixAction` includes: title, description, impact/effort levels, step-by-step instructions, code examples, affected pages, and dependency ordering.
184
+
185
+ ```ts
186
+ import { audit, generateFixPlan } from 'aeorank';
187
+
188
+ const result = await audit('example.com');
189
+ const plan = generateFixPlan(
190
+ 'example.com',
191
+ result.overallScore,
192
+ result.criterionResults,
193
+ result.pagesReviewed,
194
+ );
195
+
196
+ console.log(plan.projectedScore); // e.g. 82
197
+ console.log(plan.quickWins[0].title); // e.g. "Add llms.txt file"
198
+ console.log(plan.quickWins[0].impactScore); // e.g. 10
199
+ console.log(plan.phases[0].fixes.length); // Foundation phase fixes
200
+ ```
201
+
153
202
  ### Advanced API
154
203
 
155
204
  For custom pipelines, import individual stages:
@@ -165,6 +214,8 @@ import {
165
214
  generateOpportunities,
166
215
  scorePage,
167
216
  scoreAllPages,
217
+ buildLinkGraph,
218
+ generateFixPlan,
168
219
  isSpaShell,
169
220
  fetchWithHeadless,
170
221
  } from 'aeorank';
@@ -174,6 +225,24 @@ const results = auditSiteFromData(siteData);
174
225
  const score = calculateOverallScore(results);
175
226
  ```
176
227
 
228
+ ### Browser Entry Point
229
+
230
+ For browser environments (Chrome extensions, web apps), import from `aeorank/browser` to avoid Node.js dependencies (Puppeteer, fs):
231
+
232
+ ```ts
233
+ import {
234
+ prefetchSiteData,
235
+ auditSiteFromData,
236
+ calculateOverallScore,
237
+ buildLinkGraph,
238
+ generateFixPlan,
239
+ analyzeAllPages,
240
+ crawlFullSite,
241
+ } from 'aeorank/browser';
242
+ ```
243
+
244
+ The browser entry exports everything except `headless-fetch` (Puppeteer), `html-report` (Node fs), `audit` orchestrator, and CLI.
245
+
177
246
  ## SPA Support
178
247
 
179
248
  Sites that use client-side rendering (React, Vue, Angular) return empty HTML shells to regular HTTP requests. AEORank detects these automatically and re-renders them with Puppeteer if available.
@@ -258,6 +327,70 @@ console.log(result.criterionScores); // 14 per-criterion scores
258
327
  const allScores = scoreAllPages(siteData);
259
328
  ```
260
329
 
330
+ ## Link Graph Analysis
331
+
332
+ Analyze your site's internal linking structure to find orphan pages, identify pillar content, and detect topic clusters:
333
+
334
+ ```bash
335
+ npx aeorank example.com --full-crawl --json | jq '.linkGraph.stats'
336
+ ```
337
+
338
+ ```ts
339
+ import { crawlFullSite, prefetchSiteData, buildLinkGraph, serializeLinkGraph } from 'aeorank';
340
+
341
+ const siteData = await prefetchSiteData('example.com');
342
+ const crawl = await crawlFullSite(siteData, { maxPages: 200 });
343
+ const graph = buildLinkGraph(crawl.pages, 'example.com', 'https://example.com');
344
+
345
+ // Orphan pages (no inbound links - invisible to crawlers)
346
+ const orphans = [...graph.nodes.values()].filter(n => n.isOrphan);
347
+
348
+ // Pillar pages (high authority, many inbound links)
349
+ const pillars = [...graph.nodes.values()].filter(n => n.isPillar);
350
+
351
+ // Topic clusters (pillar + spoke pages with high cohesion)
352
+ graph.clusters.forEach(c => {
353
+ console.log(`${c.pillarTitle}: ${c.spokes.length} spokes, cohesion ${c.cohesion}`);
354
+ });
355
+
356
+ // Serialize for storage/transport (Map -> plain object)
357
+ const json = serializeLinkGraph(graph);
358
+ ```
359
+
360
+ ## Fix Plan Engine
361
+
362
+ Generate actionable, phased fix plans from audit results. Each fix includes step-by-step instructions, code examples, effort/impact ratings, and dependency ordering:
363
+
364
+ ```bash
365
+ npx aeorank example.com --full-crawl --json | jq '.fixPlan'
366
+ ```
367
+
368
+ ```ts
369
+ import { audit, generateFixPlan } from 'aeorank';
370
+
371
+ const result = await audit('example.com', { fullCrawl: true });
372
+ const plan = generateFixPlan(
373
+ 'example.com',
374
+ result.overallScore,
375
+ result.criterionResults,
376
+ result.pagesReviewed,
377
+ result.linkGraph, // optional - enables link-aware fixes
378
+ );
379
+
380
+ // 4 phases: Foundation -> Content -> Authority -> Architecture
381
+ plan.phases.forEach(phase => {
382
+ console.log(`${phase.title}: ${phase.fixes.length} fixes`);
383
+ });
384
+
385
+ // Quick wins: low effort + high impact
386
+ plan.quickWins.forEach(qw => {
387
+ console.log(`${qw.title} (+${qw.impactScore} pts) - ${qw.effort} effort`);
388
+ qw.steps.forEach(s => console.log(` - ${s}`));
389
+ });
390
+
391
+ console.log(`Current: ${plan.overallScore} -> Projected: ${plan.projectedScore}`);
392
+ ```
393
+
261
394
  ## Scoring
262
395
 
263
396
  Each criterion is scored 0-10 by deterministic checks (regex, HTML parsing, HTTP headers). The overall score is a weighted average normalized to 0-100.
@@ -0,0 +1,524 @@
1
+ /**
2
+ * Link graph analysis for full-site AEO audits.
3
+ * Builds an internal link graph, detects orphan pages, pillar pages,
4
+ * hub pages, and topic clusters from crawled page data.
5
+ */
6
+
7
+ interface LinkEdge {
8
+ source: string;
9
+ target: string;
10
+ anchorText: string;
11
+ }
12
+ interface PageNode {
13
+ url: string;
14
+ title: string;
15
+ wordCount: number;
16
+ category: PageCategory;
17
+ inDegree: number;
18
+ outDegree: number;
19
+ depth: number;
20
+ isPillar: boolean;
21
+ isHub: boolean;
22
+ isOrphan: boolean;
23
+ }
24
+ interface TopicCluster {
25
+ pillarUrl: string;
26
+ pillarTitle: string;
27
+ spokes: string[];
28
+ cohesion: number;
29
+ }
30
+ interface LinkGraphStats {
31
+ totalPages: number;
32
+ totalEdges: number;
33
+ orphanPages: number;
34
+ pillarPages: number;
35
+ hubPages: number;
36
+ avgDepth: number;
37
+ maxDepth: number;
38
+ clusters: number;
39
+ }
40
+ interface LinkGraph {
41
+ nodes: Map<string, PageNode>;
42
+ edges: LinkEdge[];
43
+ stats: LinkGraphStats;
44
+ clusters: TopicCluster[];
45
+ }
46
+ interface SerializedLinkGraph {
47
+ nodes: PageNode[];
48
+ stats: LinkGraphStats;
49
+ clusters: TopicCluster[];
50
+ }
51
+ declare function serializeLinkGraph(graph: LinkGraph): SerializedLinkGraph;
52
+ /**
53
+ * Extract internal links from HTML with their anchor text.
54
+ * Similar to extractInternalLinks in full-site-crawler.ts but returns LinkEdge[].
55
+ */
56
+ declare function extractLinksWithAnchors(html: string, sourceUrl: string, domain: string): LinkEdge[];
57
+ /**
58
+ * Build a link graph from crawled pages.
59
+ * @param pages - Array of FetchResult from full-site crawl
60
+ * @param domain - The site domain
61
+ * @param homepageUrl - Full URL of the homepage (e.g. https://example.com)
62
+ */
63
+ declare function buildLinkGraph(pages: FetchResult[], domain: string, homepageUrl: string): LinkGraph;
64
+
65
+ /**
66
+ * AEORank type definitions.
67
+ * Inlined from @aeo/shared to keep the package zero-dependency.
68
+ */
69
+ type Status = 'MISSING' | 'NEARLY EMPTY' | 'POOR' | 'WEAK' | 'PARTIAL' | 'MODERATE' | 'GOOD' | 'STRONG';
70
+ type Severity = 'WORKING' | 'GOOD' | 'GOOD PATTERN' | 'PARTIAL' | 'MISSING' | 'ADD' | 'FIX' | 'FIX IMMEDIATELY' | 'REWRITE' | 'CONFUSING' | 'INCONSISTENT' | 'SPARSE' | 'PERFORMANCE' | 'CLUTTER' | 'PLATFORM LIMIT' | 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'BIG OPPORTUNITY' | 'AEO GOLDMINE' | 'AEO CORE' | 'CORE AEO' | 'AEO deliverable' | 'QUICK WIN' | 'MEASUREMENT';
71
+ type FindingType = 'Test' | 'Good' | 'Bad' | 'Missing' | 'Critical' | 'Issue' | 'Fix' | 'Exists' | 'Calc' | 'Present' | 'Note' | 'Current' | 'Volume' | 'Bonus' | 'Impact';
72
+ type ImpactLevel = 'QUICK WIN' | 'CRITICAL' | 'HIGH' | 'CORE AEO' | 'MEDIUM' | 'LOW' | 'MEASUREMENT' | 'BIG OPPORTUNITY';
73
+ interface ScoreCardItem {
74
+ id: number;
75
+ criterion: string;
76
+ score: number;
77
+ status: Status;
78
+ keyFindings: string;
79
+ }
80
+ interface DetailedFinding {
81
+ type: FindingType;
82
+ description: string;
83
+ severity: Severity;
84
+ }
85
+ interface CriterionDetail {
86
+ id: number;
87
+ name: string;
88
+ findings: DetailedFinding[];
89
+ }
90
+ interface Deliverable {
91
+ id: number;
92
+ name: string;
93
+ description: string;
94
+ effort: string;
95
+ impact: ImpactLevel;
96
+ }
97
+ interface PitchMetric {
98
+ metric: string;
99
+ value: string;
100
+ significance: string;
101
+ }
102
+ type PageCategory$1 = 'homepage' | 'blog' | 'about' | 'pricing' | 'services' | 'contact' | 'team' | 'resources' | 'docs' | 'cases' | 'faq' | 'content';
103
+ interface PageCriterionScore$1 {
104
+ criterion: string;
105
+ criterion_label: string;
106
+ score: number;
107
+ weight: number;
108
+ }
109
+ interface PageIssue {
110
+ check: string;
111
+ label: string;
112
+ severity: 'error' | 'warning' | 'info';
113
+ }
114
+ interface PageReview {
115
+ url: string;
116
+ title: string;
117
+ category: PageCategory$1;
118
+ wordCount: number;
119
+ issues: PageIssue[];
120
+ strengths: PageIssue[];
121
+ aeoScore?: number;
122
+ criterionScores?: PageCriterionScore$1[];
123
+ }
124
+ interface AuditData {
125
+ site: string;
126
+ auditDate: string;
127
+ auditor: string;
128
+ engine?: string;
129
+ overallScore: number;
130
+ verdict: string;
131
+ scorecard: ScoreCardItem[];
132
+ detailedFindings: CriterionDetail[];
133
+ opportunities: Deliverable[];
134
+ pitchNumbers: PitchMetric[];
135
+ bottomLine: string;
136
+ pagesReviewed?: PageReview[];
137
+ }
138
+ type AuditStatus = 'pass' | 'fail' | 'partial' | 'not_found';
139
+ type Priority = 'P0' | 'P1' | 'P2' | 'P3';
140
+ type FindingSeverity = 'critical' | 'high' | 'medium' | 'low' | 'info';
141
+ interface AuditFinding {
142
+ severity: FindingSeverity;
143
+ detail: string;
144
+ fix?: string;
145
+ }
146
+
147
+ interface CriterionResult {
148
+ criterion: string;
149
+ criterion_label: string;
150
+ score: number;
151
+ status: AuditStatus;
152
+ findings: AuditFinding[];
153
+ fix_priority: Priority;
154
+ }
155
+ type PageCategory = 'homepage' | 'blog' | 'about' | 'pricing' | 'services' | 'contact' | 'team' | 'resources' | 'docs' | 'cases' | 'faq' | 'content';
156
+ interface FetchResult {
157
+ text: string;
158
+ status: number;
159
+ finalUrl?: string;
160
+ category?: PageCategory;
161
+ }
162
+ interface SiteData {
163
+ domain: string;
164
+ protocol: 'https' | 'http' | null;
165
+ homepage: FetchResult | null;
166
+ llmsTxt: FetchResult | null;
167
+ robotsTxt: FetchResult | null;
168
+ faqPage: FetchResult | null;
169
+ sitemapXml: FetchResult | null;
170
+ rssFeed: FetchResult | null;
171
+ aiTxt: FetchResult | null;
172
+ /** Set when homepage redirects to a different (non-brand) domain */
173
+ redirectedTo: string | null;
174
+ /** Set when homepage is a parked/for-sale/lost domain */
175
+ parkedReason: string | null;
176
+ /** Sampled blog/content pages from sitemap (up to 5) */
177
+ blogSample?: FetchResult[];
178
+ /** Full-crawl statistics (set when --full-crawl is used) */
179
+ crawlStats?: {
180
+ discovered: number;
181
+ fetched: number;
182
+ skipped: number;
183
+ elapsed: number;
184
+ };
185
+ /** Link graph from full-site crawl */
186
+ linkGraph?: LinkGraph;
187
+ }
188
+ interface RawDataSummary {
189
+ domain: string;
190
+ protocol: 'https' | 'http' | null;
191
+ homepage_length: number;
192
+ homepage_text_length: number;
193
+ has_https: boolean;
194
+ llms_txt_status: number | null;
195
+ llms_txt_length: number;
196
+ robots_txt_status: number | null;
197
+ robots_txt_snippet: string;
198
+ robots_txt_ai_crawlers: string[];
199
+ robots_txt_blocked_crawlers: string[];
200
+ schema_types_found: string[];
201
+ schema_block_count: number;
202
+ faq_page_status: number | null;
203
+ faq_page_length: number;
204
+ sitemap_status: number | null;
205
+ internal_link_count: number;
206
+ external_link_count: number;
207
+ question_headings_count: number;
208
+ h1_count: number;
209
+ has_meta_description: boolean;
210
+ has_title: boolean;
211
+ has_phone: boolean;
212
+ has_address: boolean;
213
+ has_org_schema: boolean;
214
+ has_social_links: boolean;
215
+ semantic_elements_found: string[];
216
+ img_count: number;
217
+ img_with_alt_count: number;
218
+ has_lang_attr: boolean;
219
+ has_aria: boolean;
220
+ has_breadcrumbs: boolean;
221
+ has_nav: boolean;
222
+ has_footer: boolean;
223
+ has_case_studies: boolean;
224
+ has_statistics: boolean;
225
+ has_expert_attribution: boolean;
226
+ has_blog_section: boolean;
227
+ has_date_modified_schema: boolean;
228
+ time_element_count: number;
229
+ sitemap_url_count: number;
230
+ has_rss_feed: boolean;
231
+ table_count: number;
232
+ ordered_list_count: number;
233
+ unordered_list_count: number;
234
+ definition_pattern_count: number;
235
+ has_ai_txt: boolean;
236
+ has_person_schema: boolean;
237
+ fact_data_point_count: number;
238
+ has_canonical: boolean;
239
+ has_license_schema: boolean;
240
+ sitemap_recent_lastmod_count: number;
241
+ rendered_with_headless?: boolean;
242
+ has_speakable_schema: boolean;
243
+ speakable_selector_count: number;
244
+ blog_sample_count: number;
245
+ blog_sample_urls: string[];
246
+ blog_sample_schema_types: string[];
247
+ blog_sample_question_headings: number;
248
+ blog_sample_faq_schema_found: boolean;
249
+ question_heading_answer_rate: number;
250
+ question_heading_total: number;
251
+ cannibalizing_pairs_count: number;
252
+ page_titles_sampled: number;
253
+ has_visible_date: boolean;
254
+ has_schema_date_in_ld: boolean;
255
+ date_modified_recency_days: number | null;
256
+ crawl_discovered: number;
257
+ crawl_fetched: number;
258
+ crawl_skipped: number;
259
+ }
260
+ /**
261
+ * Fetches all site data in parallel with HTTPS/HTTP fallback.
262
+ * Single entry point for all HTTP requests - no redundant fetches.
263
+ */
264
+ declare function prefetchSiteData(domain: string): Promise<SiteData>;
265
+ declare function extractRawDataSummary(data: SiteData): RawDataSummary;
266
+ /**
267
+ * Run all 26 criteria checks using pre-fetched site data.
268
+ * All functions are synchronous (no HTTP calls) - data was already fetched.
269
+ */
270
+ declare function auditSiteFromData(data: SiteData): CriterionResult[];
271
+
272
+ declare function calculateOverallScore(criteria: CriterionResult[]): number;
273
+
274
+ /**
275
+ * Shared scorecard building functions.
276
+ * Extracted from cli/pre-crawl.ts for reuse in instant-audit and other consumers.
277
+ */
278
+
279
+ declare const CRITERION_LABELS: Record<string, string>;
280
+ declare function scoreToStatus(score: number): Status;
281
+ declare function buildScorecard(results: CriterionResult[]): ScoreCardItem[];
282
+ declare function buildDetailedFindings(results: CriterionResult[]): CriterionDetail[];
283
+
284
+ /**
285
+ * Deterministic narrative generation from scorecard data.
286
+ * Produces verdict, opportunities, pitchNumbers, and bottomLine
287
+ * without any LLM calls - pure template-based generation.
288
+ */
289
+
290
+ declare function generateVerdict(score: number, scorecard: ScoreCardItem[], rawData: RawDataSummary, domain: string): string;
291
+ declare function generateOpportunities(scorecard: ScoreCardItem[], criterionResults: CriterionResult[]): Deliverable[];
292
+ declare function generatePitchNumbers(score: number, rawData: RawDataSummary, scorecard: ScoreCardItem[]): PitchMetric[];
293
+ declare function generateBottomLine(score: number, opportunities: Deliverable[], scorecard: ScoreCardItem[], domain: string): string;
294
+
295
+ /**
296
+ * Extended page discovery for instant audit.
297
+ * Fetches additional pages beyond what prefetchSiteData provides,
298
+ * including nav-linked pages, common paths, and content pages from sitemap.
299
+ */
300
+
301
+ /**
302
+ * Extract internal page paths from <nav> elements in homepage HTML.
303
+ * Returns deduplicated absolute paths (e.g. ['/about', '/pricing']).
304
+ */
305
+ declare function extractNavLinks(html: string, domain: string): string[];
306
+ /**
307
+ * Extract non-blog deep content pages from sitemap XML.
308
+ * Targets service pages, product pages, etc. (not blog/article posts).
309
+ */
310
+ declare function extractContentPagesFromSitemap(sitemapText: string, domain: string, limit?: number): string[];
311
+ interface MultiPageOptions {
312
+ timeoutMs?: number;
313
+ }
314
+ /**
315
+ * Fetch additional pages beyond what prefetchSiteData provides.
316
+ * Discovers pages from nav links + common path variants + sitemap content pages.
317
+ * All fetched pages are appended to siteData.blogSample so existing
318
+ * getCombinedHtml() and criteria checks pick them up automatically.
319
+ *
320
+ * Mutates siteData in place and returns the count of new pages added.
321
+ */
322
+ declare function fetchMultiPageData(siteData: SiteData, options?: MultiPageOptions): Promise<number>;
323
+
324
+ /**
325
+ * Full-site crawler for deep AEO audits.
326
+ * BFS crawl that discovers all internal pages up to a configurable limit.
327
+ */
328
+
329
+ interface CrawlOptions {
330
+ /** Maximum pages to fetch (default 200) */
331
+ maxPages?: number;
332
+ /** Per-page fetch timeout in ms (default 10000) */
333
+ timeoutMs?: number;
334
+ /** Parallel fetches (default 5) */
335
+ concurrency?: number;
336
+ /** Honor robots.txt Disallow rules (default true) */
337
+ respectRobots?: boolean;
338
+ /** Include asset files — skipped by default */
339
+ includeAssets?: boolean;
340
+ }
341
+ interface CrawlResult {
342
+ pages: FetchResult[];
343
+ discoveredUrls: string[];
344
+ fetchedUrls: string[];
345
+ skippedUrls: string[];
346
+ elapsed: number;
347
+ }
348
+ /**
349
+ * Extract all page URLs from sitemap XML (handles sitemapindex with sub-sitemaps).
350
+ * Filters to same domain only, skips resource files.
351
+ */
352
+ declare function extractAllUrlsFromSitemap(sitemapText: string, domain: string, timeoutMs?: number): Promise<string[]>;
353
+ /**
354
+ * Extract ALL internal links from HTML (not just nav).
355
+ * Returns deduplicated full URLs for the same domain.
356
+ */
357
+ declare function extractInternalLinks(html: string, domain: string): string[];
358
+ /**
359
+ * Infer PageCategory from URL path patterns.
360
+ */
361
+ declare function inferCategory(url: string): PageCategory;
362
+ /**
363
+ * BFS crawl of a site, discovering all internal pages up to maxPages.
364
+ * Seeds from sitemap URLs + homepage internal links.
365
+ * Skips URLs already in siteData.blogSample and homepage.
366
+ */
367
+ declare function crawlFullSite(siteData: SiteData, options?: CrawlOptions): Promise<CrawlResult>;
368
+
369
+ /**
370
+ * Per-page analysis for instant audit.
371
+ * Runs 12 deterministic checks on each crawled page (no LLM).
372
+ */
373
+
374
+ declare function analyzePage(html: string, url: string, category: PageCategory): PageReview;
375
+ declare function analyzeAllPages(siteData: SiteData): PageReview[];
376
+
377
+ /**
378
+ * Per-page AEO scoring.
379
+ * Evaluates 14 of 26 criteria that apply at individual page level.
380
+ * Produces a 0-100 AEO score per page.
381
+ */
382
+
383
+ interface PageCriterionScore {
384
+ criterion: string;
385
+ criterion_label: string;
386
+ score: number;
387
+ weight: number;
388
+ }
389
+ interface PageScoreResult {
390
+ aeoScore: number;
391
+ criterionScores: PageCriterionScore[];
392
+ }
393
+ /**
394
+ * Score a single page against 14 AEO criteria.
395
+ * Returns a 0-100 AEO score and individual criterion scores.
396
+ */
397
+ declare function scorePage(html: string, url?: string): PageScoreResult;
398
+ /**
399
+ * Score all crawled pages (homepage + blogSample).
400
+ */
401
+ declare function scoreAllPages(siteData: SiteData): PageScoreResult[];
402
+
403
+ /**
404
+ * Fix Plan Engine - generates actionable, phased fix plans from audit scores.
405
+ * Runs alongside the existing opportunities system (no breaking changes).
406
+ * Uses criterion scores, optional per-page data, and optional link graph
407
+ * to produce structured fix plans with code examples and dependency ordering.
408
+ */
409
+
410
+ interface FixAction {
411
+ id: string;
412
+ criterion: string;
413
+ criterionId: string;
414
+ title: string;
415
+ description: string;
416
+ impact: 'critical' | 'high' | 'medium' | 'low';
417
+ effort: 'trivial' | 'low' | 'medium' | 'high';
418
+ impactScore: number;
419
+ category: 'content' | 'structure' | 'discovery' | 'trust';
420
+ steps: string[];
421
+ codeExample?: string;
422
+ successCriteria: string;
423
+ dependsOn?: string[];
424
+ affectedPages?: string[];
425
+ pageCount?: number;
426
+ }
427
+ interface FixPhase {
428
+ phase: number;
429
+ title: string;
430
+ description: string;
431
+ fixes: FixAction[];
432
+ estimatedImpact: number;
433
+ }
434
+ interface FixPlanSummary {
435
+ criticalCount: number;
436
+ highCount: number;
437
+ mediumCount: number;
438
+ lowCount: number;
439
+ quickWinCount: number;
440
+ topOpportunity: string;
441
+ estimatedTotalEffort: string;
442
+ }
443
+ interface FixPlan {
444
+ domain: string;
445
+ generatedAt: string;
446
+ overallScore: number;
447
+ projectedScore: number;
448
+ totalFixes: number;
449
+ phases: FixPhase[];
450
+ quickWins: FixAction[];
451
+ summary: FixPlanSummary;
452
+ }
453
+ declare function generateFixPlan(domain: string, overallScore: number, criteria: CriterionResult[], pagesReviewed?: PageReview[], linkGraph?: LinkGraph): FixPlan;
454
+
455
+ /**
456
+ * Parked domain detection.
457
+ * Vendored from @aeo/queue/redirect-check (pure functions, no network).
458
+ */
459
+ interface ParkedDomainResult {
460
+ isParked: boolean;
461
+ reason?: string;
462
+ }
463
+ /**
464
+ * Detect if a page is a parked/lost/for-sale domain.
465
+ * Pure function - no network calls.
466
+ */
467
+ declare function detectParkedDomain(bodySnippet: string): ParkedDomainResult;
468
+
469
+ /**
470
+ * Programmatic audit API.
471
+ * Runs the full 7-phase AEO audit pipeline and returns structured results.
472
+ */
473
+
474
+ interface AuditOptions {
475
+ /** Skip Puppeteer SPA rendering (default: false) */
476
+ noHeadless?: boolean;
477
+ /** Homepage + blog only, skip extra page discovery (default: false) */
478
+ noMultiPage?: boolean;
479
+ /** Fetch timeout in ms (default: 15000) */
480
+ timeout?: number;
481
+ /** Enable full-site BFS crawl (default: false) */
482
+ fullCrawl?: boolean;
483
+ /** Max pages for full crawl (default: 200) */
484
+ maxPages?: number;
485
+ /** Parallel fetch concurrency for full crawl (default: 5) */
486
+ concurrency?: number;
487
+ }
488
+ interface AuditResult extends AuditData {
489
+ /** True if headless browser was used for SPA rendering */
490
+ renderedWithHeadless?: boolean;
491
+ /** Wall-clock seconds */
492
+ elapsed: number;
493
+ }
494
+
495
+ /**
496
+ * Comparison mode - run two audits in parallel and compute per-criterion deltas.
497
+ */
498
+
499
+ interface CriterionComparison {
500
+ id: number;
501
+ criterion: string;
502
+ scoreA: number;
503
+ scoreB: number;
504
+ delta: number;
505
+ statusA: string;
506
+ statusB: string;
507
+ }
508
+ interface ComparisonResult {
509
+ siteA: AuditResult;
510
+ siteB: AuditResult;
511
+ comparison: {
512
+ scoreDelta: number;
513
+ criteria: CriterionComparison[];
514
+ siteAAdvantages: string[];
515
+ siteBAdvantages: string[];
516
+ tied: string[];
517
+ };
518
+ }
519
+ /**
520
+ * Audit two domains in parallel and build a per-criterion comparison.
521
+ */
522
+ declare function compare(domainA: string, domainB: string, options?: AuditOptions): Promise<ComparisonResult>;
523
+
524
+ export { type AuditData, type AuditFinding, type AuditStatus, CRITERION_LABELS, type ComparisonResult, type CrawlOptions, type CrawlResult, type CriterionComparison, type CriterionDetail, type CriterionResult, type Deliverable, type DetailedFinding, type FetchResult, type FindingSeverity, type FindingType, type FixAction, type FixPhase, type FixPlan, type FixPlanSummary, type ImpactLevel, type LinkEdge, type LinkGraph, type LinkGraphStats, type PageCategory$1 as PageCategory, type PageCriterionScore$1 as PageCriterionScore, type PageIssue, type PageNode, type PageReview, type PageScoreResult, type ParkedDomainResult, type PitchMetric, type Priority, type RawDataSummary, type ScoreCardItem, type SerializedLinkGraph, type Severity, type SiteData, type Status, type TopicCluster, analyzeAllPages, analyzePage, auditSiteFromData, buildDetailedFindings, buildLinkGraph, buildScorecard, calculateOverallScore, compare, crawlFullSite, detectParkedDomain, extractAllUrlsFromSitemap, extractContentPagesFromSitemap, extractInternalLinks, extractLinksWithAnchors, extractNavLinks, extractRawDataSummary, fetchMultiPageData, generateBottomLine, generateFixPlan, generateOpportunities, generatePitchNumbers, generateVerdict, inferCategory, prefetchSiteData, scoreAllPages, scorePage, scoreToStatus, serializeLinkGraph };