/** * Shared types for the SEO extension. * * All types are plain data structures (no class hierarchies) so they can be * serialised to JSON and returned as tool `details` without transformation. */ /** A single HTTP redirect hop in a fetch redirect chain. */ export interface RedirectHop { url: string; status: number; } /** Result of fetching a URL. */ export interface FetchResult { url: string; finalUrl: string; statusCode: number; statusText: string; headers: Record; content: string; redirectChain: RedirectHop[]; contentLengthBytes: number; fetchedAt: string; error?: string; protocolHint?: "http1" | "http2" | "http3" | "unknown"; } /** A parsed heading element. */ export interface HeadingInfo { level: 1 | 2 | 3 | 4 | 5 | 6; text: string; /** True if the heading looks auto-generated or suspicious (e.g. empty, "Click here"). */ suspicious?: boolean; } /** A parsed image element. */ export interface ImageInfo { src: string; alt: string; width?: string; height?: string; loading?: string; /** Detected lazy-loading method: "native", "dataSrc", "dataLazy", "none". */ lazyMethod: "native" | "dataSrc" | "dataLazy" | "none"; fetchpriority?: string; srcset?: string; sizes?: string; decoding?: string; isSvg?: boolean; isDecorative?: boolean; isInPicture?: boolean; } /** A parsed link element. */ export interface LinkInfo { href: string; text: string; rel: string; /** "internal" or "external" relative to baseUrl. */ type: "internal" | "external"; nofollow: boolean; } /** A detected JSON-LD schema block. */ export interface SchemaBlock { type: string; format: "json-ld" | "microdata" | "rdfa"; raw: string; parsed?: unknown; issues: string[]; } /** Open Graph tag. */ export interface OpenGraphTag { property: string; content: string; } /** Twitter Card tag. */ export interface TwitterTag { name: string; content: string; } /** Hreflang entry. */ export interface HreflangEntry { href: string; hreflang: string; } /** Parsed on-page SEO data extracted from HTML. */ export interface PageData { url: string; baseUrl: string; title: string | null; titleLength: number; metaDescription: string | null; metaDescriptionLength: number; metaRobots: string | null; canonical: string | null; ogTags: OpenGraphTag[]; twitterTags: TwitterTag[]; headings: HeadingInfo[]; h1Count: number; h2Count: number; h3Count: number; images: ImageInfo[]; links: LinkInfo[]; internalLinkCount: number; externalLinkCount: number; schemaBlocks: SchemaBlock[]; hreflang: HreflangEntry[]; wordCount: number; language: string | null; viewport: string | null; charset: string | null; pictureCount?: number; svgCount?: number; svgMissingTitle?: number; } /** A single audit issue with severity. */ export interface AuditIssue { category: string; severity: "critical" | "high" | "medium" | "low"; title: string; description: string; recommendation: string; /** Stable machine-readable issue ID for tracking across audits. */ issueId?: string; } /** Result of a technical SEO audit. */ export interface TechnicalAuditResult { url: string; score: number; issues: AuditIssue[]; categoryScores: Record; robotsTxt: { exists: boolean; sitemapDeclared: boolean; aiCrawlerRules: string[]; content?: string; disallowRules: string[]; sitemapUrls: string[]; }; securityHeaders: Record; isHttps: boolean; mobileOptimized: boolean; hreflang: { entries: number; issues: string[]; }; contentType: string | null; compression: string | null; cacheHeaders: { cacheControl?: string; etag?: string; lastModified?: string; }; redirectChainQuality: { hops: number; permanentRedirects: number; temporaryRedirects: number; issues: string[]; }; soft404Risk: "none" | "low" | "high"; pagination: { detected: boolean; hasNext: boolean; hasPrev: boolean; }; } /** Result of content quality analysis. */ export interface ContentQualityResult { url?: string; wordCount: number; tokens: number; uniqueTokens: number; fillerScore: number; aiPatternScore: number; informationDensity: number; repetitionScore: number; overallQuality: number; flags: string[]; fillerMatches: string[]; aiPatternMatches: string[]; sentenceCount?: number; avgSentenceLength?: number; avgWordLength?: number; readabilityFleschKincaid?: number; readability?: ReadabilityResult; entities?: string[]; headingAlignment?: { score: number; missingKeywords: string[]; }; semanticDepth?: { score: number; hasH1: boolean; hasH2: boolean; hasH3: boolean; headingCount: number; }; language?: string; disclaimer?: string; } /** Result of schema audit. */ export interface SchemaAuditResult { url?: string; score: number; detectedSchemas: SchemaBlock[]; deprecatedTypes: string[]; missingContext: boolean; relativeUrls: string[]; invalidDates: string[]; recommendations: string[]; } /** Result of sitemap analysis. */ export interface SitemapAnalysisResult { url: string; urlCount: number; urls: string[]; lastmodCoverage: number; invalidEntries: number; sizeBytes: number; isSitemapIndex: boolean; childSitemaps: string[]; warnings: string[]; } /** Result of image audit. */ export interface ImageAuditResult { url: string; score: number; totalImages: number; missingAlt: number; missingDimensions: number; lazyLoading: { native: number; dataSrc: number; dataLazy: number; none: number; }; modernFormats: number; oversized: string[]; images: ImageInfo[]; pictureCount?: number; svgCount?: number; svgMissingTitle?: number; responsiveImages?: number; responsiveCoverage?: number; fetchpriorityHighCount?: number; fetchpriorityIssues?: string[]; decorativeCount?: number; ratioIssues?: string[]; decodingStats?: { async: number; sync: number; auto: number; none: number; }; svgImgMissingAlt?: number; dimensionHintIssues?: string[]; decorativeWithAltIssues?: number; } /** Result of preload / speculation rules audit. */ export interface PreloadAuditResult { url: string; speculationRules: { inlineBlocks: number; headerPresent: boolean; actions: string[]; }; preloadHints: number; prerenderLinks: number; bfcacheSignals: { cacheControlNoStore: boolean; unloadListener: boolean; beforeunloadListener: boolean; }; lcpResourceHints: { preloadLcpCandidate: boolean; fetchpriorityHigh: number; }; score: number; recommendations: string[]; } /** Result of agent UX audit. */ export interface AgentUxAuditResult { url: string; agentUxScore: number; html: { realButtons: number; realAnchors: number; divOnclickWidgets: number; semanticLandmarks: number; inputsTotal: number; inputsMissingLabel: number; }; accessibilityTree: { totalNodes: number; interactiveNodes: number; unnamedInteractive: number; roleGenericRatio: number; }; opportunities: string[]; } /** A single page's parasite-SEO audit data. */ export interface ParasitePageAudit { url: string; thirdPartyHits: number; commerceHits: number; affiliateLinkHits: number; } /** Result of parasite risk scan. */ export interface ParasiteRiskResult { host: string; totalPages: number; sections: Array<{ section: string; pageCount: number; thirdPartyHitsPerPage: number; commerceHitsPerPage: number; affiliateLinkHitsPerPage: number; flags: string[]; risk: "high" | "medium" | "low"; }>; } /** A drift baseline snapshot. */ export interface DriftBaseline { id: number; url: string; urlHash: string; timestamp: string; expiresAt: string; title: string | null; metaDescription: string | null; canonical: string | null; robots: string | null; h1: string | null; h2Json: string; h3Json: string; schemaJson: string; ogJson: string; htmlHash: string; schemaHash: string; statusCode: number; headersJson: string; } /** Result of comparing current state against a baseline. */ export interface DriftCompareResult { url: string; baselineId: number; baselineTimestamp: string; comparedAt: string; changedFields: string[]; details: Record; fieldSeverity: Record; severity: "none" | "low" | "medium" | "high"; } /** Full audit result combining all sub-audits. */ export interface FullAuditResult { url: string; auditedAt: string; overallScore: number; coverage: number; confidence: "high" | "medium" | "low"; skippedCategories: string[]; rendererAvailable: boolean; /** Schema version of this result format. */ version: number; /** Non-blocking warnings about the audit process itself. */ warnings: string[]; renderComparison?: { staticByteLength: number; renderedByteLength: number; contentGrew: boolean; isSpa: boolean; textDelta: number; headingDelta: number; linkDelta: number; imageDelta: number; schemaDelta: number; notes: string[]; }; technical?: TechnicalAuditResult; content?: ContentQualityResult; schema?: SchemaAuditResult; images?: ImageAuditResult; preload?: PreloadAuditResult; agentUx?: AgentUxAuditResult; performance?: PerformanceResult; pageWeight?: PageWeightResult; sitemap?: SitemapAnalysisResult; allIssues: AuditIssue[]; summary: string; } export interface CrawlConfig { maxPages: number; maxDepth: number; concurrency: number; respectRobotsTxt: boolean; crawlDelayMs: number; includeExternal: boolean; } export interface CrawledPage { url: string; depth: number; statusCode: number; fetchResult: FetchResult; pageData: PageData; linksFound: string[]; crawlDuration: number; error?: string; } export interface BrokenLink { sourcePage: string; targetUrl: string; statusCode: number; linkText: string; } export interface DuplicateGroup { type: "title" | "description" | "h1"; value: string; pageUrls: string[]; } export interface LinkGraphNode { url: string; internalInlinks: number; internalOutlinks: number; externalOutlinks: number; pageRank?: number; depth: number; statusCode: number; } export interface CrawlResult { baseUrl: string; pagesCrawled: number; totalPagesFound: number; maxDepthReached: number; brokenLinks: BrokenLink[]; duplicateGroups: DuplicateGroup[]; orphanPages: string[]; pages: CrawledPage[]; linkGraph: LinkGraphNode[]; errors: string[]; /** Canonical URLs declared via across crawled pages. */ canonicalUrls?: string[]; /** Count of links with rel="nofollow" (tracked but not added to crawl queue). */ nofollowLinks: number; /** Count of links with rel="sponsored". */ sponsoredLinks: number; /** Count of links with rel="ugc". */ ugcLinks: number; /** Count of detected pagination links (rel="next" / rel="prev"). */ paginationLinks: number; /** Internal links that returned a 3xx redirect to another internal URL. */ redirectingInternalLinks: Array<{ source: string; target: string; }>; keywordCannibalization?: Array<{ keyword: string; pages: string[]; }>; } export interface KeywordInfo { keyword: string; count: number; density: number; tfidf: number; variants: string[]; } export interface KeywordAnalysisResult { url: string; totalWords: number; keywords: KeywordInfo[]; topNgrams: Array<{ ngram: string; count: number; }>; searchIntent: "informational" | "commercial" | "transactional" | "mixed"; intentConfidence: number; keywordStuffingFlags: string[]; } export interface ReadabilityResult { url: string; fleschKincaid: number; fleschReadingEase: number; smog: number; ari: number; colemanLiau: number; gradeAverage: number; sentenceCount: number; syllableCount: number; complexWordCount: number; } export interface ContentOptimizationResult { url: string; titleScore: number; descriptionScore: number; headingStructureScore: number; contentLengthScore: number; overallScore: number; issues: Array<{ category: string; severity: "critical" | "high" | "medium" | "low"; issue: string; recommendation: string; }>; } export type PerformanceSource = "heuristic" | "lab"; export interface PerformanceResult { url: string; lcp: { value: number; rating: "good" | "needs-improvement" | "poor"; element?: string | LcpElementInfo; }; cls: { value: number; rating: "good" | "needs-improvement" | "poor"; }; tbt: { value: number; rating: "good" | "needs-improvement" | "poor"; }; fcp: { value: number; rating: "good" | "needs-improvement" | "poor"; }; ttfb: { value: number; rating: "good" | "needs-improvement" | "poor"; }; score: number; source?: PerformanceSource; navigationTiming?: { dnsMs: number; tcpMs: number; sslMs: number; ttfbMs: number; downloadMs: number; }; longTasks?: { count: number; totalDurationMs: number; details?: Array<{ startTime: number; duration: number; attribution?: string; }>; }; failedRequests?: { url: string; status: number; type: string; }[]; desktop?: PerformanceResult; disclaimer?: string; layoutShiftCount?: number; layoutShiftSources?: Array<{ value: number; nodeCount: number; }>; navigationTimingRatings?: { dns: { value: number; rating: "good" | "needs-improvement" | "poor"; }; tcp: { value: number; rating: "good" | "needs-improvement" | "poor"; }; ssl: { value: number; rating: "good" | "needs-improvement" | "poor"; }; ttfb: { value: number; rating: "good" | "needs-improvement" | "poor"; }; download: { value: number; rating: "good" | "needs-improvement" | "poor"; }; }; } export interface PageWeightResult { url: string; totalKb: number; htmlKb: number; cssKb: number; jsKb: number; imageKb: number; fontKb: number; otherKb: number; resourceCount: number; topHeavyResources: Array<{ url: string; sizeKb: number; type: string; }>; } export interface LcpElementInfo { tagName: string; id?: string; src?: string; className?: string; renderTime?: number; size?: string; } export interface MeasuredPerformance { lcp?: { value: number; rating: "good" | "needs-improvement" | "poor"; element?: string | LcpElementInfo; }; cls?: { value: number; rating: "good" | "needs-improvement" | "poor"; }; tbt?: { value: number; rating: "good" | "needs-improvement" | "poor"; }; fcp?: { value: number; rating: "good" | "needs-improvement" | "poor"; }; ttfb?: { value: number; rating: "good" | "needs-improvement" | "poor"; }; navigationTiming?: { dnsMs: number; tcpMs: number; sslMs: number; ttfbMs: number; downloadMs: number; }; longTasks?: { count: number; totalDurationMs: number; details?: Array<{ startTime: number; duration: number; attribution?: string; }>; }; resourceCount?: number; totalTransferKb?: number; failedRequests?: { url: string; status: number; type: string; }[]; topHeavyResources?: { url: string; type: string; transferSize: number; encodedSize: number; }[]; layoutShiftCount?: number; layoutShiftSources?: Array<{ value: number; nodeCount: number; }>; } export interface RenderBlockingResult { url: string; blockingCss: Array<{ href: string; sizeEstimate: number; }>; blockingJs: Array<{ src: string; sizeEstimate: number; }>; totalBlockingUrls: number; recommendations: string[]; } export interface FontAuditResult { url: string; fontsFound: Array<{ family: string; source: string; hasDisplay: boolean; isSubset: boolean; format: string; }>; hasFontDisplay: boolean; recommendations: string[]; score: number; } export interface SarifReport { $schema: string; version: "2.1.0"; runs: SarifRun[]; } export interface SarifRun { tool: { driver: { name: string; version: string; semanticVersion?: string; informationUri?: string; rules?: SarifRule[]; }; }; invocation?: SarifInvocation; results: SarifResult[]; } export interface SarifRule { id: string; name: string; shortDescription: { text: string; }; } export interface SarifInvocation { executionSuccessful: boolean; startTimeUtc: string; endTimeUtc: string; arguments?: string[]; } export interface SarifResult { ruleId: string; ruleIndex?: number; level: "error" | "warning" | "note" | "none"; message: { text: string; }; locations?: SarifLocation[]; properties?: Record; partialFingerprints?: Record; } export interface SarifLocation { physicalLocation: { artifactLocation: { uri: string; }; }; } //# sourceMappingURL=types.d.ts.map