@forkpoint/agent-lighthouse-core 0.4.0 → 2.0.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/dist/index.d.mts CHANGED
@@ -6,11 +6,28 @@ interface FetchOptions {
6
6
  timeout?: number;
7
7
  followRedirects?: boolean;
8
8
  acceptHeader?: string;
9
- method?: 'GET' | 'POST' | 'OPTIONS' | 'HEAD';
9
+ method?: 'GET' | 'POST' | 'OPTIONS' | 'HEAD' | 'DELETE';
10
10
  body?: string;
11
11
  contentType?: string;
12
+ /** Override the User-Agent header (e.g. to probe a site as a specific AI bot). */
13
+ userAgent?: string;
14
+ /**
15
+ * Extra request headers (e.g. `MCP-Protocol-Version`). Applied before the
16
+ * fetcher's own headers, so a caller can add headers but cannot clobber the
17
+ * scanner User-Agent, the negotiated Accept, or credentials lifted out of the
18
+ * URL's userinfo.
19
+ */
20
+ headers?: Record<string, string>;
12
21
  /** External abort (e.g. the per-scan deadline). Combined with the per-request timeout. */
13
22
  signal?: AbortSignal;
23
+ /**
24
+ * Read the response as bytes instead of text.
25
+ *
26
+ * `body` is a UTF-8 decoded string, which replaces every invalid sequence
27
+ * with U+FFFD — fatal for an image, whose provenance metadata is binary. With
28
+ * this flag the response arrives in `bytes` and `body` stays empty.
29
+ */
30
+ binary?: boolean;
14
31
  }
15
32
  interface FetchResult {
16
33
  url: string;
@@ -22,6 +39,8 @@ interface FetchResult {
22
39
  totalMs: number;
23
40
  contentType: string;
24
41
  contentLength: number;
42
+ /** Raw response bytes. Present only when the request asked for `binary`. */
43
+ bytes?: Uint8Array;
25
44
  error?: string;
26
45
  }
27
46
  /**
@@ -35,10 +54,20 @@ declare function createFetcher(): {
35
54
 
36
55
  interface WafProtection {
37
56
  isBlocked: boolean;
38
- provider?: 'akamai' | 'cloudflare' | 'datadome' | 'perimeterx' | 'imperva' | 'kasada' | 'aws-waf' | 'generic-waf' | 'connection-drop';
57
+ provider?: 'akamai' | 'cloudflare' | 'datadome' | 'perimeterx' | 'imperva' | 'kasada' | 'aws-waf' | 'generic-waf' | 'connection-drop' | 'rate-limited';
39
58
  name: string;
40
59
  reason: string;
41
60
  statusCode?: number;
61
+ /**
62
+ * The scan was throttled, not refused.
63
+ *
64
+ * An HTTP 429 says "too many requests", which is a statement about this
65
+ * scan's request rate and not about who the site admits. An audit that
66
+ * cannot tell the two apart must not report a bot wall on the strength of
67
+ * one — a storefront that serves GPTBot perfectly well would be told its
68
+ * firewall is blocking AI crawlers.
69
+ */
70
+ isRateLimit?: boolean;
42
71
  }
43
72
  /**
44
73
  * Analyzes fetch results (homepage, root files, extra pages) to determine
@@ -89,6 +118,23 @@ interface AuditGuidance {
89
118
  tags?: string[];
90
119
  }
91
120
  type ScoreDisplayMode = 'binary' | 'ternary' | 'informative';
121
+ /** Public sunset notice for a deprecated audit (see docs/evidence/sunset/not-a-factor.md). */
122
+ interface DeprecationNotice {
123
+ /** One sentence: why this signal is not a factor. */
124
+ notice: string;
125
+ /** Public rationale URL (not-a-factor.md anchor). */
126
+ link: string;
127
+ }
128
+ /**
129
+ * Strength of the published evidence backing an audit, assigned in the audit's
130
+ * dossier per docs/evidence/policy.md. Only A and B carry scoring weight.
131
+ */
132
+ type EvidenceGrade = 'A' | 'B' | 'C' | 'D';
133
+ /**
134
+ * How an audit participates in scoring. Derived from its evidence grade
135
+ * (spec §4): only `scored` audits move a category score.
136
+ */
137
+ type AuditTier = 'scored' | 'informative' | 'experimental';
92
138
  interface AuditMeta {
93
139
  id: string;
94
140
  category: string;
@@ -100,6 +146,14 @@ interface AuditMeta {
100
146
  applicablePageTypes?: PageType[];
101
147
  defaultPriority: CheckPriority;
102
148
  guidance?: AuditGuidance;
149
+ /** Present when the audit is sunset: shown as a notice, excluded from scores. */
150
+ deprecated?: DeprecationNotice;
151
+ /** Evidence grade from the audit's dossier (docs/evidence/policy.md). */
152
+ evidenceGrade?: EvidenceGrade;
153
+ /** Scoring tier derived from the grade (spec §4). */
154
+ tier?: AuditTier;
155
+ /** Repo-relative path to the audit's evidence dossier. */
156
+ dossier?: string;
103
157
  }
104
158
  interface AuditResult {
105
159
  status: CheckStatus;
@@ -117,6 +171,13 @@ interface AuditResult {
117
171
  };
118
172
  pageUrl?: string;
119
173
  priority?: CheckPriority;
174
+ /**
175
+ * A fix written from what this scan actually found.
176
+ *
177
+ * Overrides `meta.guidance.fix` in the report, so an audit can name the
178
+ * offending section rather than repeat the generic advice.
179
+ */
180
+ remediation?: string;
120
181
  }
121
182
  type CheckStatus = 'pass' | 'warn' | 'fail' | 'na';
122
183
  type CheckPriority = 'critical' | 'high' | 'medium' | 'low';
@@ -133,6 +194,8 @@ interface CheckResult {
133
194
  description: string;
134
195
  status: CheckStatus;
135
196
  score: number;
197
+ /** Evidence-derived weight copied from AuditMeta.weight (A=1.0, B=0.6, informative=0). */
198
+ weight?: number;
136
199
  scoreDisplayMode: ScoreDisplayMode;
137
200
  displayValue?: string;
138
201
  explanation?: string;
@@ -145,9 +208,17 @@ interface CheckResult {
145
208
  found?: string;
146
209
  code?: string;
147
210
  docsUrl?: string;
211
+ /** The published evidence dossier for this check, derived from its id. */
212
+ evidenceUrl?: string;
148
213
  [key: string]: unknown;
149
214
  };
150
215
  tags?: string[];
216
+ /** Present when the audit is sunset: shown as a notice, excluded from scores. */
217
+ deprecated?: DeprecationNotice;
218
+ /** Evidence grade copied from AuditMeta.evidenceGrade. */
219
+ evidenceGrade?: EvidenceGrade;
220
+ /** Scoring tier copied from AuditMeta.tier. */
221
+ tier?: AuditTier;
151
222
  }
152
223
  interface CategoryResult {
153
224
  id: string;
@@ -198,90 +269,6 @@ interface ReadinessVitals {
198
269
  technical: number;
199
270
  }
200
271
 
201
- type PhaseId = 'fetch-root' | 'fetch-pages' | 'analyze' | 'audits' | 'report';
202
- type ScanEvent = {
203
- type: 'scan:start';
204
- url: string;
205
- fraction: number;
206
- elapsedMs: number;
207
- } | {
208
- type: 'phase:start';
209
- phase: PhaseId;
210
- totalUnits: number;
211
- fraction: number;
212
- elapsedMs: number;
213
- } | {
214
- type: 'unit:done';
215
- phase: PhaseId;
216
- completed: number;
217
- total: number;
218
- label?: string;
219
- fraction: number;
220
- elapsedMs: number;
221
- } | {
222
- type: 'unit:fail';
223
- phase: PhaseId;
224
- label: string;
225
- error: string;
226
- fraction: number;
227
- elapsedMs: number;
228
- } | {
229
- type: 'phase:done';
230
- phase: PhaseId;
231
- durationMs: number;
232
- fraction: number;
233
- elapsedMs: number;
234
- } | {
235
- type: 'scan:done';
236
- durationMs: number;
237
- score: number;
238
- fraction: number;
239
- elapsedMs: number;
240
- };
241
- /**
242
- * How much of the overall scan fraction each phase owns. Sums to 1 so the
243
- * fraction of a finished scan is exactly 1.
244
- */
245
- declare const PHASE_WEIGHTS: Record<PhaseId, number>;
246
- /**
247
- * Turns scan milestones into {@link ScanEvent}s. Owns the fraction math
248
- * (`Σ weights of done phases + current phase weight × completed/total`),
249
- * clamps it to be monotonic non-decreasing, and stamps `fraction`/`elapsedMs`
250
- * onto every event. Emission is synchronous — throttling is a consumer concern.
251
- */
252
- declare class ProgressTracker {
253
- private readonly onEvent;
254
- private readonly startMs;
255
- private doneWeight;
256
- private phase;
257
- private phaseStartMs;
258
- private totalUnits;
259
- private completedUnits;
260
- private lastFraction;
261
- constructor(onEvent: (event: ScanEvent) => void);
262
- /** Fraction of the whole scan that is complete, in [0, 1]. Never decreases. */
263
- get fraction(): number;
264
- /** Stamp an event with the current fraction/elapsed and advance the floor. */
265
- private stamp;
266
- private elapsedMs;
267
- scanStart(url: string): void;
268
- phaseStart(phase: PhaseId, totalUnits: number): void;
269
- /** Correct the current phase's unit total (e.g. discovery finds pages mid-phase). */
270
- setPhaseTotal(totalUnits: number): void;
271
- unitDone(label?: string): void;
272
- /** A failed unit still counts as settled work so the phase can complete. */
273
- unitFail(label: string, error: string): void;
274
- phaseDone(): void;
275
- scanDone(score: number): void;
276
- }
277
-
278
- interface ScanOptions {
279
- onEvent?: (event: ScanEvent) => void;
280
- pages?: PageOverride[] | null;
281
- signal?: AbortSignal;
282
- }
283
- declare function runScan(url: string, options?: ScanOptions): Promise<ScanReport>;
284
-
285
272
  interface A11yNodeFinding {
286
273
  target: string;
287
274
  summary: string;
@@ -332,6 +319,14 @@ interface CheckContext {
332
319
  }
333
320
  type CheckFn = (ctx: CheckContext) => CheckResult | Promise<CheckResult>;
334
321
 
322
+ /**
323
+ * Where an audit's evidence dossier is published. A pure function of the id.
324
+ *
325
+ * Every audit id is `<category>/<slug>`, and the documentation site publishes
326
+ * one page per dossier at that path with a trailing slash (`trailingSlash:
327
+ * 'always'`), so no per-audit field is needed to reach the evidence.
328
+ */
329
+ declare function evidenceUrl(id: string): string;
335
330
  /**
336
331
  * Base class for all audits. Follows the Lighthouse pattern:
337
332
  * each audit is a class with a static `meta` descriptor and
@@ -340,6 +335,16 @@ type CheckFn = (ctx: CheckContext) => CheckResult | Promise<CheckResult>;
340
335
  declare abstract class Audit {
341
336
  static meta: AuditMeta;
342
337
  private static readonly PRIORITY_MAP;
338
+ /**
339
+ * Split the fourth argument of `fail`/`warn` into a priority and a fix.
340
+ *
341
+ * The argument started as a priority token and grew a second use: audits
342
+ * pass a sentence describing the fix for what this scan found. A sentence is
343
+ * not a `CheckPriority`, and putting it there made `toCheckResult` throw on
344
+ * the schema — so a sentence is read as the remediation and the priority
345
+ * falls back to the audit's default.
346
+ */
347
+ private static splitRecommendation;
343
348
  /** Validate an audit result against the schema. */
344
349
  protected validate(result: AuditResult): AuditResult;
345
350
  /** Run the audit and return a result. */
@@ -370,7 +375,11 @@ declare abstract class Audit {
370
375
  interface CategoryConfig {
371
376
  id: string;
372
377
  name: string;
373
- /** Weight in the overall score (all weights should sum to 1.0). */
378
+ /**
379
+ * Evidence mass: the summed weight of the category's registered audits
380
+ * (spec §4). Relative, not a percentage — `calculateOverallScore` normalizes
381
+ * by the total mass, so a category with no scored audits weighs nothing.
382
+ */
374
383
  weight: number;
375
384
  }
376
385
  interface AuditRegistration {
@@ -384,7 +393,76 @@ interface ScanConfig {
384
393
  /** categoryId → list of audit registrations */
385
394
  audits: Record<string, AuditRegistration[]>;
386
395
  }
396
+ /**
397
+ * Evidence mass per category: Σ of the member audits' weights (spec §4).
398
+ *
399
+ * This replaces the old hand-tuned `CATEGORY_WEIGHTS` map. A category earns
400
+ * influence over the overall score by carrying proven audits, so moving an
401
+ * audit between categories moves its mass with it and nothing else changes.
402
+ * Categories made entirely of informative/experimental audits have mass 0 and
403
+ * cannot move the overall score.
404
+ */
405
+ declare const CATEGORY_MASS: Record<string, number>;
387
406
  declare const defaultConfig: ScanConfig;
407
+ /** Every registered category id, in canonical report order. */
408
+ declare const CATEGORY_IDS: readonly string[];
409
+ /**
410
+ * Narrow a scan to a subset of the registry.
411
+ *
412
+ * Two independent filters: `categories` restricts which categories run at all,
413
+ * and `includeExperimental` decides whether experimental-tier audits are part
414
+ * of the run. Experimental audits are excluded unless asked for — they carry
415
+ * weight 0 either way, but running an unvalidated check on every scan is a
416
+ * decision the operator makes, not a default.
417
+ *
418
+ * Returns the same object when there is nothing to filter, so the common path
419
+ * allocates nothing.
420
+ */
421
+ declare function filterConfig(config: ScanConfig, opts: {
422
+ categories?: string[];
423
+ includeExperimental?: boolean;
424
+ }): ScanConfig;
425
+
426
+ /**
427
+ * What one audit did, in a form that can be diffed.
428
+ *
429
+ * A report says what an audit concluded. Tracing bad logic needs more: which
430
+ * audits never ran and why, how long each took, and what evidence each verdict
431
+ * was drawn from. That is the difference between "this site failed
432
+ * `faqpage-schema`" and "`faqpage-schema` ran in 4ms against 0 sampled pages",
433
+ * and only the second one tells you where to look.
434
+ *
435
+ * One record per registered audit, every scan, no exceptions — an audit that
436
+ * crashed or was skipped is exactly the one worth seeing.
437
+ */
438
+ interface AuditTrace {
439
+ id: string;
440
+ category: string;
441
+ /**
442
+ * `ran` — the audit executed and returned.
443
+ * `skipped` — no scanned page matched its `applicablePageTypes`.
444
+ * `error` — it threw, or its result was rejected by the schema.
445
+ */
446
+ outcome: 'ran' | 'skipped' | 'error';
447
+ status: CheckStatus;
448
+ score: number;
449
+ weight: number;
450
+ tier?: string;
451
+ evidenceGrade?: string;
452
+ /** Wall time inside `audit()`. Zero for an audit that never ran. */
453
+ durationMs: number;
454
+ displayValue?: string;
455
+ explanation?: string;
456
+ pageUrl?: string;
457
+ /** The structured evidence behind the verdict, as the report carries it. */
458
+ details?: CheckResult['details'];
459
+ }
460
+ /** Which of the three outcomes a finished check represents. */
461
+ declare function outcomeOf(check: CheckResult): AuditTrace['outcome'];
462
+ /** Build a trace record from a finished check. */
463
+ declare function traceFromCheck(check: CheckResult, durationMs: number): AuditTrace;
464
+ /** One trace as a log line: the shape a human scans, not the full record. */
465
+ declare function formatTrace(trace: AuditTrace): string;
388
466
 
389
467
  interface AuditRunResult {
390
468
  checks: CheckResult[];
@@ -403,6 +481,14 @@ type AuditProgressEvent = {
403
481
  label: string;
404
482
  error: string;
405
483
  };
484
+ /**
485
+ * Called once per registered audit, with what it did.
486
+ *
487
+ * Separate from {@link AuditProgressEvent}, which exists to drive a progress
488
+ * bar and carries only a label. This carries the verdict and its evidence, and
489
+ * fires for skipped and errored audits too — those are the ones worth seeing.
490
+ */
491
+ type AuditTraceHandler = (trace: AuditTrace) => void;
406
492
  interface AuditPlan {
407
493
  runnable: Array<{
408
494
  reg: AuditRegistration;
@@ -421,12 +507,370 @@ declare function planAudits(ctx: CheckContext, config: ScanConfig): AuditPlan;
421
507
  * Returns check results grouped into categories with weighted scores.
422
508
  * Pass a precomputed `plan` (from {@link planAudits}) to avoid recomputing it.
423
509
  */
424
- declare function runAudits(ctx: CheckContext, config: ScanConfig, onEvent?: (event: AuditProgressEvent) => void, plan?: AuditPlan): Promise<AuditRunResult>;
510
+ declare function runAudits(ctx: CheckContext, config: ScanConfig, onEvent?: (event: AuditProgressEvent) => void, plan?: AuditPlan, onTrace?: AuditTraceHandler): Promise<AuditRunResult>;
511
+
512
+ type PhaseId = 'fetch-root' | 'fetch-pages' | 'analyze' | 'audits' | 'report';
513
+ type ScanEvent = {
514
+ type: 'scan:start';
515
+ url: string;
516
+ fraction: number;
517
+ elapsedMs: number;
518
+ } | {
519
+ type: 'phase:start';
520
+ phase: PhaseId;
521
+ totalUnits: number;
522
+ fraction: number;
523
+ elapsedMs: number;
524
+ } | {
525
+ type: 'unit:done';
526
+ phase: PhaseId;
527
+ completed: number;
528
+ total: number;
529
+ label?: string;
530
+ fraction: number;
531
+ elapsedMs: number;
532
+ } | {
533
+ type: 'unit:fail';
534
+ phase: PhaseId;
535
+ label: string;
536
+ error: string;
537
+ fraction: number;
538
+ elapsedMs: number;
539
+ } | {
540
+ type: 'phase:done';
541
+ phase: PhaseId;
542
+ durationMs: number;
543
+ fraction: number;
544
+ elapsedMs: number;
545
+ } | {
546
+ type: 'scan:done';
547
+ durationMs: number;
548
+ score: number;
549
+ fraction: number;
550
+ elapsedMs: number;
551
+ };
552
+ /**
553
+ * How much of the overall scan fraction each phase owns. Sums to 1 so the
554
+ * fraction of a finished scan is exactly 1.
555
+ */
556
+ declare const PHASE_WEIGHTS: Record<PhaseId, number>;
557
+ /**
558
+ * Turns scan milestones into {@link ScanEvent}s. Owns the fraction math
559
+ * (`Σ weights of done phases + current phase weight × completed/total`),
560
+ * clamps it to be monotonic non-decreasing, and stamps `fraction`/`elapsedMs`
561
+ * onto every event. Emission is synchronous — throttling is a consumer concern.
562
+ */
563
+ declare class ProgressTracker {
564
+ private readonly onEvent;
565
+ private readonly startMs;
566
+ private doneWeight;
567
+ private phase;
568
+ private phaseStartMs;
569
+ private totalUnits;
570
+ private completedUnits;
571
+ private lastFraction;
572
+ constructor(onEvent: (event: ScanEvent) => void);
573
+ /** Fraction of the whole scan that is complete, in [0, 1]. Never decreases. */
574
+ get fraction(): number;
575
+ /** Stamp an event with the current fraction/elapsed and advance the floor. */
576
+ private stamp;
577
+ private elapsedMs;
578
+ scanStart(url: string): void;
579
+ phaseStart(phase: PhaseId, totalUnits: number): void;
580
+ /** Correct the current phase's unit total (e.g. discovery finds pages mid-phase). */
581
+ setPhaseTotal(totalUnits: number): void;
582
+ unitDone(label?: string): void;
583
+ /** A failed unit still counts as settled work so the phase can complete. */
584
+ unitFail(label: string, error: string): void;
585
+ phaseDone(): void;
586
+ scanDone(score: number): void;
587
+ }
588
+
589
+ interface ScanOptions {
590
+ onEvent?: (event: ScanEvent) => void;
591
+ pages?: PageOverride[] | null;
592
+ signal?: AbortSignal;
593
+ /**
594
+ * Restrict the scan to these category ids. Unknown ids simply match nothing —
595
+ * validate them at the entry point so the operator hears about a typo.
596
+ */
597
+ categories?: string[];
598
+ /**
599
+ * Include audits whose tier is `experimental`. Off by default, per
600
+ * docs/evidence/policy.md: an experimental check is behind a flag and never
601
+ * scored.
602
+ */
603
+ includeExperimental?: boolean;
604
+ /**
605
+ * Called once per registered audit with what it did — including the ones
606
+ * that were skipped or failed. Use it to trace a verdict back to the
607
+ * evidence it was drawn from; see {@link AuditTrace}.
608
+ */
609
+ onAuditTrace?: AuditTraceHandler;
610
+ }
611
+ declare function runScan(url: string, options?: ScanOptions): Promise<ScanReport>;
612
+
613
+ type FetchClass = 'ok' | 'soft-404' | 'blocked' | 'missing' | 'error';
614
+ type ExpectedKind = 'text' | 'json' | 'xml' | 'html';
615
+ declare function stripBom(text: string): string;
616
+ declare function normalizeNewlines(text: string): string;
617
+ /**
618
+ * Classify a fetched root file honestly. `status === 200` alone is not
619
+ * "the file exists": SPAs and some CDNs return the HTML app shell (200)
620
+ * for any unknown path — a soft 404 that inflated v1 scores.
621
+ *
622
+ * The body is the primary evidence and outranks the Content-Type header. Plenty
623
+ * of real servers ship `/llms.txt` or `/sitemap.xml` as `text/html`; trusting
624
+ * the header there would condemn a genuine file as a soft 404.
625
+ */
626
+ declare function classifyFetch(result: FetchResult | undefined, expected: ExpectedKind): FetchClass;
627
+ declare function isRealFile(result: FetchResult | undefined, expected: ExpectedKind): boolean;
628
+
629
+ interface RobotsRule {
630
+ type: 'allow' | 'disallow';
631
+ path: string;
632
+ }
633
+ interface RobotsGroup {
634
+ userAgent: string;
635
+ rules: RobotsRule[];
636
+ crawlDelay?: number;
637
+ /**
638
+ * Directives inside the group that are neither rules nor crawl-delay, in file
639
+ * order — Content-Signal, Request-rate, vendor extensions. Retained rather
640
+ * than dropped: a directive we do not model today is still evidence about
641
+ * what the operator declared. Absent when the group carries none.
642
+ */
643
+ otherDirectives?: Array<{
644
+ name: string;
645
+ value: string;
646
+ }>;
647
+ }
648
+ /** A parsed robots.txt: its groups plus the file-level directives outside them. */
649
+ interface RobotsFile {
650
+ groups: RobotsGroup[];
651
+ /**
652
+ * Every `Sitemap:` value, in file order. RFC 9309 §2.2.3 makes this directive
653
+ * host-global and user-agent independent: it is not part of any group, which
654
+ * is exactly why a site can advertise a sitemap its own rules forbid.
655
+ */
656
+ sitemaps: string[];
657
+ }
658
+ /**
659
+ * Parse robots.txt into groups plus file-level directives.
660
+ *
661
+ * `parseRobots` returns this function's `groups` and nothing else, so the
662
+ * group-only view every existing audit uses and the whole-file view added for
663
+ * the Plan 5 discovery audits can never disagree about grouping.
664
+ */
665
+ declare function parseRobotsFile(body: string): RobotsFile;
666
+ declare function parseRobots(body: string): RobotsGroup[];
667
+ /** Group UA "GPTBot/1.1" matches bot token "gptbot": compare the product token. */
668
+ declare function matchesUserAgent(groupUserAgent: string, botToken: string): boolean;
669
+ /**
670
+ * Does this bot have a group of its own?
671
+ *
672
+ * Under RFC 9309 §2.2.1 a crawler obeys the most specific matching group and
673
+ * ignores `*` entirely once a named group exists. So "the wildcard allows it"
674
+ * is not an answer for a bot with its own group, and this predicate is what
675
+ * lets an audit report which set of rules actually applied.
676
+ */
677
+ declare function hasNamedGroup(groups: RobotsGroup[], botToken: string): boolean;
678
+ declare function groupsForBot(groups: RobotsGroup[], botToken: string): RobotsGroup[];
679
+ declare function isPathAllowed(groups: RobotsGroup[], botToken: string, path: string): boolean;
680
+ declare function isBlanketBlocked(groups: RobotsGroup[], botToken: string): boolean;
681
+
682
+ /**
683
+ * The AI crawlers whose published User-Agent strings can actually be sent.
684
+ *
685
+ * `Google-Extended` is deliberately absent: it is a robots.txt product token
686
+ * only, with no user agent of its own, so a UA probe for it would compare the
687
+ * site against a string that never appears in real traffic.
688
+ */
689
+ declare const AI_CRAWLER_UAS: ReadonlyArray<{
690
+ token: string;
691
+ ua: string;
692
+ label: string;
693
+ }>;
694
+ /** A mainstream browser UA. The control half of every pair. */
695
+ declare const BASELINE_UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36";
696
+ /**
697
+ * How a site answered a crawler UA, relative to the same request as a browser.
698
+ *
699
+ * The classes are deliberately distinct rather than one "blocked" verdict:
700
+ * a Cloudflare interstitial, a pay-per-crawl price, a proof-of-work wall and a
701
+ * rate limit each need a different remedy from the operator, and an opaque 403
702
+ * may even be correct impersonation defence.
703
+ */
704
+ type BlockClass = 'ok' | 'cf-challenge' | 'pay-per-crawl' | 'anubis-pow' | 'rate-limited' | 'opaque-403' | 'soft-block' | 'transport-error';
705
+ interface UaProbe {
706
+ url: string;
707
+ token: string;
708
+ baselineStatus: number;
709
+ probeStatus: number;
710
+ blockClass: BlockClass;
711
+ /** Probe main-content length over baseline main-content length. */
712
+ textRatio: number;
713
+ /** The shortest decisive fact — a header line, a status, a body marker. */
714
+ evidence: string;
715
+ /** Main-content text of each side, kept so a caller can diff them word by word. */
716
+ baselineText: string;
717
+ probeText: string;
718
+ /** The raw baseline body, so a caller can resolve declared markup against the DOM a browser got. */
719
+ baselineBody: string;
720
+ /** The raw probe body, so a caller can look for challenge fingerprints the classifier does not model. */
721
+ probeBody: string;
722
+ /** Baseline response headers, so a caller can read a price or a payment challenge. */
723
+ baselineHeaders: Record<string, string>;
724
+ /** Probe response headers, for the same reason. */
725
+ probeHeaders: Record<string, string>;
726
+ }
727
+ /**
728
+ * Compare one crawler-UA response against the browser baseline for the same URL.
729
+ *
730
+ * A non-2xx baseline yields `ok` for every probe: the scanner could not read the
731
+ * page either, so nothing observed is bot-specific and reporting it would be a
732
+ * finding about our own fetch.
733
+ */
734
+ declare function classifyResponse(baseline: FetchResult, probe: FetchResult): {
735
+ blockClass: BlockClass;
736
+ textRatio: number;
737
+ evidence: string;
738
+ baselineText: string;
739
+ probeText: string;
740
+ baselineBody: string;
741
+ probeBody: string;
742
+ baselineHeaders: Record<string, string>;
743
+ probeHeaders: Record<string, string>;
744
+ };
745
+ /**
746
+ * Fetch each URL once as a browser, then once per crawler token, and classify
747
+ * the difference.
748
+ *
749
+ * The baseline is fetched once per URL and reused across tokens, so a six-token
750
+ * sweep costs seven requests per URL rather than twelve. URLs are `isSafeUrl`-
751
+ * gated because callers pass in addresses read out of site-controlled content.
752
+ */
753
+ declare function probeUaParity(fetch: (options: FetchOptions) => Promise<FetchResult>, urls: string[], tokens: string[], opts?: {
754
+ signal?: AbortSignal;
755
+ }): Promise<UaProbe[]>;
756
+
757
+ /** One `<url>` row of a sitemap. */
758
+ interface SitemapEntry {
759
+ loc: string;
760
+ lastmod?: string;
761
+ }
762
+ /** The result of walking a site's advertised sitemap roots. */
763
+ interface SitemapTree {
764
+ entries: SitemapEntry[];
765
+ /** Child sitemaps actually fetched, in order. Empty for a flat sitemap. */
766
+ childSitemaps: string[];
767
+ /** How many `<lastmod>` values failed the W3C Datetime shape. */
768
+ malformedLastmod: number;
769
+ /** True when a cap stopped the walk, so `entries` is not the whole tree. */
770
+ truncated: boolean;
771
+ }
772
+ /**
773
+ * Does this value parse as a W3C Datetime, the format the sitemap protocol
774
+ * requires of `<lastmod>`?
775
+ *
776
+ * Accepts the date-only `YYYY-MM-DD` form and the full RFC 3339 timestamp.
777
+ * Deliberately strict about the shape before handing anything to `Date`, which
778
+ * would happily accept `1755859200` (epoch seconds, a common CMS bug) and
779
+ * every prose string a locale parser recognises.
780
+ */
781
+ declare function isW3CDateTime(value: string): boolean;
782
+ /**
783
+ * Walk a site's sitemap roots and collect their `<url>` rows.
784
+ *
785
+ * Recursion stops after one level of `<sitemapindex>`: the depth of a nested
786
+ * index is chosen by the site being scanned, so following it arbitrarily is an
787
+ * unbounded walk driven by untrusted input. Every child URL is `isSafeUrl`- and
788
+ * same-host-gated before it is fetched, for the same reason.
789
+ */
790
+ declare function collectSitemapEntries(fetch: (options: FetchOptions) => Promise<FetchResult>, roots: string[], opts?: {
791
+ maxChildren?: number;
792
+ maxEntries?: number;
793
+ signal?: AbortSignal;
794
+ }): Promise<SitemapTree>;
795
+ /**
796
+ * Take an even-strided sample of `n` entries.
797
+ *
798
+ * Deterministic rather than random: two audits sampling the same tree must
799
+ * probe the same URLs, or a finding from one cannot be lined up against a
800
+ * finding from the other.
801
+ */
802
+ declare function sampleEntries(entries: SitemapEntry[], n: number): SitemapEntry[];
803
+
804
+ /** One `selector { declarations }` rule, with the at-rule it sits inside. */
805
+ interface CssRule {
806
+ /** The full selector list, verbatim, so a human can adjudicate a match. */
807
+ selector: string;
808
+ /** The declaration block, lowercased and whitespace-collapsed. */
809
+ declarations: string;
810
+ /** The enclosing at-rule prelude (`media print`, `supports (...)`), if any. */
811
+ atRule?: string;
812
+ /** Where the rule came from, for evidence. */
813
+ origin: 'inline' | string;
814
+ }
815
+ /**
816
+ * Scan a stylesheet into flat selector/declaration pairs.
817
+ *
818
+ * Deliberately a scanner, not a parser: it performs no cascade, no specificity
819
+ * ordering and no media-query evaluation. It exists so an audit can ask "does
820
+ * any rule that could apply to this element declare display:none", and it
821
+ * reports the matched selector text so a human can check the answer. A full CSS
822
+ * engine would need a dependency the core package does not carry.
823
+ */
824
+ declare function parseCssRules(source: string, origin?: string): CssRule[];
825
+ interface PageCss {
826
+ rules: CssRule[];
827
+ /** Stylesheet URLs on another origin, which are never fetched. */
828
+ skippedCrossOrigin: string[];
829
+ /** Stylesheet URLs fetched and scanned. */
830
+ fetched: string[];
831
+ }
832
+ /**
833
+ * Collect every CSS rule a page's own markup and same-origin stylesheets carry.
834
+ *
835
+ * Cross-origin sheets are not fetched: a scan must not pull bytes from a third
836
+ * party on the scanned site's behalf. They are reported instead, so a result
837
+ * built on partial CSS says so rather than reading as complete.
838
+ */
839
+ declare function collectPageCss(ctx: CheckContext, page: PageContext): Promise<PageCss>;
840
+
841
+ declare function pagesOfType(ctx: CheckContext, ...types: PageType[]): PageContext[];
842
+ interface PageJudgement {
843
+ page: PageContext;
844
+ ok: boolean;
845
+ detail?: string;
846
+ }
847
+ /**
848
+ * Run one judgement over a set of pages. v1 audits frequently judged only
849
+ * pages[0] and generalized to the whole site; this makes per-page judgement
850
+ * the path of least resistance. Callers MUST return notApplicable when
851
+ * `judged.length === 0` — an empty set proves nothing.
852
+ */
853
+ declare function judgePages(pages: PageContext[], judge: (page: PageContext) => {
854
+ ok: boolean;
855
+ detail?: string;
856
+ }): {
857
+ judged: PageJudgement[];
858
+ passRate: number;
859
+ failures: PageJudgement[];
860
+ };
425
861
 
426
862
  declare function parseHtml(html: string): CheerioAPI;
427
863
  declare function extractJsonLd($: CheerioAPI): object[];
428
864
  /**
429
- * Flatten parsed JSON-LD into a flat list of schema.org nodes.
865
+ * Top-level JSON-LD entities only: expands arrays and `@graph` members,
866
+ * propagating `@context` downward. Nested property objects (an Article's
867
+ * `publisher`, a Product's `offers`) are NOT hoisted — v1's deep flatten
868
+ * made audits "find" entities the page never declared at top level.
869
+ */
870
+ declare function topLevelJsonLd(blocks: object[]): object[];
871
+ /**
872
+ * Every JSON-LD node including nested ones — for audits that legitimately
873
+ * search deep (e.g. AggregateRating nested under Product).
430
874
  *
431
875
  * Real-world JSON-LD nests the interesting types arbitrarily: a top-level
432
876
  * `[{...}]` array (common on Shopify), an `@graph` container, and properties
@@ -436,7 +880,9 @@ declare function extractJsonLd($: CheerioAPI): object[];
436
880
  * invisible — producing false "schema not found" verdicts. This walks every
437
881
  * object and array value so type/property lookups see the whole graph.
438
882
  */
439
- declare function flattenJsonLd(blocks: object[]): object[];
883
+ declare function allJsonLdNodes(blocks: object[]): object[];
884
+ /** @deprecated v1 name for the deep walk. Use topLevelJsonLd (structure) or allJsonLdNodes (deep search). Removed in v2. */
885
+ declare const flattenJsonLd: typeof allJsonLdNodes;
440
886
  /**
441
887
  * Extract links from Markdown-ish text (llms.txt, READMEs).
442
888
  *
@@ -475,6 +921,9 @@ declare function extractImages($: CheerioAPI): Array<{
475
921
  loading?: string;
476
922
  role?: string;
477
923
  ariaHidden?: string;
924
+ ariaLabel?: string;
925
+ ariaLabelledby?: string;
926
+ title?: string;
478
927
  }>;
479
928
  declare function extractForms($: CheerioAPI): Array<{
480
929
  action: string;
@@ -514,7 +963,7 @@ declare const MAX_CONCURRENT_REQUESTS = 10;
514
963
  declare const SCANNER_USER_AGENT = "AgentLighthouse/1.0 (+https://github.com/ForkPoint/agent-lighthouse)";
515
964
  declare const TAG_SKIPPED_PAGE_TYPE = "skipped:page-type";
516
965
  declare const TAG_SCAN_ERROR = "scan-error";
517
- declare const CATEGORY_WEIGHTS: Record<string, number>;
966
+ /** Display names for the 8 v2 categories. */
518
967
  declare const CATEGORY_NAMES: Record<string, string>;
519
968
  declare const READINESS_WEIGHTS: {
520
969
  readonly commerce: 0.4;
@@ -527,8 +976,31 @@ declare const SCORE_TIER_LABELS: Record<ScoreTier, string>;
527
976
  declare function getTierLabel(tier: string | null): string;
528
977
  declare function getTierColor(tier: string | null): string;
529
978
 
979
+ /**
980
+ * Spec §4 weight law: an audit's scoring weight is a pure function of its
981
+ * evidence grade and tier. Only the `scored` tier carries weight, and only
982
+ * grades A and B are proven enough to move a score.
983
+ */
984
+ declare function weightForGrade(grade: EvidenceGrade, tier: AuditTier): number;
985
+ /**
986
+ * Single source of truth for "this check is advisory only".
987
+ *
988
+ * Informative checks (deprecated audits / no proven consumer) are still shown
989
+ * to the user, but they must never influence scores, recommendations, top
990
+ * fails/passes or readiness vitals. Every surface that ranks or scores checks
991
+ * filters through this predicate so the rule cannot drift per package.
992
+ */
993
+ declare function isInformative(check: Pick<CheckResult, 'scoreDisplayMode'>): boolean;
530
994
  declare function calculateCategoryScore(checks: CheckResult[]): number;
531
- declare function buildCategoryResult(id: string, checks: CheckResult[]): CategoryResult;
995
+ /**
996
+ * Assemble a category result.
997
+ *
998
+ * `mass` is the category's evidence mass — the summed weight of its registered
999
+ * audits (`CATEGORY_MASS` in audit-config). It is passed in rather than looked
1000
+ * up so this module stays free of a dependency on the registry, which the
1001
+ * audits themselves import (for `weightForGrade`).
1002
+ */
1003
+ declare function buildCategoryResult(id: string, checks: CheckResult[], mass?: number): CategoryResult;
532
1004
  declare function calculateOverallScore(categories: CategoryResult[]): number;
533
1005
 
534
1006
  /**
@@ -552,17 +1024,19 @@ declare const AuditResultSchema: z.ZodObject<{
552
1024
  expected: z.ZodOptional<z.ZodString>;
553
1025
  found: z.ZodOptional<z.ZodString>;
554
1026
  code: z.ZodOptional<z.ZodString>;
555
- }, "strip", z.ZodTypeAny, {
556
- code?: string | undefined;
557
- found?: string | undefined;
558
- expected?: string | undefined;
559
- }, {
560
- code?: string | undefined;
561
- found?: string | undefined;
562
- expected?: string | undefined;
563
- }>>;
1027
+ }, "strip", z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString, "many">]>, z.objectOutputType<{
1028
+ expected: z.ZodOptional<z.ZodString>;
1029
+ found: z.ZodOptional<z.ZodString>;
1030
+ code: z.ZodOptional<z.ZodString>;
1031
+ }, z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString, "many">]>, "strip">, z.objectInputType<{
1032
+ expected: z.ZodOptional<z.ZodString>;
1033
+ found: z.ZodOptional<z.ZodString>;
1034
+ code: z.ZodOptional<z.ZodString>;
1035
+ }, z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString, "many">]>, "strip">>>;
564
1036
  pageUrl: z.ZodUnion<[z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodString]>, z.ZodString]>;
565
1037
  priority: z.ZodOptional<z.ZodEnum<["critical", "high", "medium", "low"]>>;
1038
+ /** A fix written from what this scan found, which beats the generic one in meta.guidance. */
1039
+ remediation: z.ZodOptional<z.ZodString>;
566
1040
  code: z.ZodOptional<z.ZodString>;
567
1041
  }, "strip", z.ZodTypeAny, {
568
1042
  status: "warn" | "pass" | "fail" | "na";
@@ -570,32 +1044,34 @@ declare const AuditResultSchema: z.ZodObject<{
570
1044
  code?: string | undefined;
571
1045
  found?: string | undefined;
572
1046
  expected?: string | undefined;
573
- details?: {
574
- code?: string | undefined;
575
- found?: string | undefined;
576
- expected?: string | undefined;
577
- } | undefined;
1047
+ details?: z.objectOutputType<{
1048
+ expected: z.ZodOptional<z.ZodString>;
1049
+ found: z.ZodOptional<z.ZodString>;
1050
+ code: z.ZodOptional<z.ZodString>;
1051
+ }, z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString, "many">]>, "strip"> | undefined;
578
1052
  message?: string | undefined;
579
1053
  displayValue?: string | undefined;
580
1054
  explanation?: string | undefined;
581
1055
  pageUrl?: string | undefined;
582
1056
  priority?: "critical" | "high" | "medium" | "low" | undefined;
1057
+ remediation?: string | undefined;
583
1058
  }, {
584
1059
  status: "warn" | "pass" | "fail" | "na";
585
1060
  score: number;
586
1061
  code?: string | undefined;
587
1062
  found?: string | undefined;
588
1063
  expected?: string | undefined;
589
- details?: {
590
- code?: string | undefined;
591
- found?: string | undefined;
592
- expected?: string | undefined;
593
- } | undefined;
1064
+ details?: z.objectInputType<{
1065
+ expected: z.ZodOptional<z.ZodString>;
1066
+ found: z.ZodOptional<z.ZodString>;
1067
+ code: z.ZodOptional<z.ZodString>;
1068
+ }, z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString, "many">]>, "strip"> | undefined;
594
1069
  message?: string | undefined;
595
1070
  displayValue?: string | undefined;
596
1071
  explanation?: string | undefined;
597
1072
  pageUrl?: string | undefined;
598
1073
  priority?: "critical" | "high" | "medium" | "low" | undefined;
1074
+ remediation?: string | undefined;
599
1075
  }>;
600
1076
  declare const FixEffortSchema: z.ZodEnum<["trivial", "easy", "moderate", "complex"]>;
601
1077
  declare const AuditGuidanceSchema: z.ZodObject<{
@@ -621,6 +1097,22 @@ declare const AuditGuidanceSchema: z.ZodObject<{
621
1097
  tags?: string[] | undefined;
622
1098
  }>;
623
1099
  declare const ScoreDisplayModeSchema: z.ZodEnum<["binary", "ternary", "informative"]>;
1100
+ declare const DeprecationNoticeSchema: z.ZodObject<{
1101
+ notice: z.ZodString;
1102
+ link: z.ZodString;
1103
+ }, "strip", z.ZodTypeAny, {
1104
+ link: string;
1105
+ notice: string;
1106
+ }, {
1107
+ link: string;
1108
+ notice: string;
1109
+ }>;
1110
+ /** Evidence strength from the audit's dossier (docs/evidence/policy.md). */
1111
+ declare const EvidenceGradeSchema: z.ZodEnum<["A", "B", "C", "D"]>;
1112
+ /** Scoring participation tier (spec §4). */
1113
+ declare const AuditTierSchema: z.ZodEnum<["scored", "informative", "experimental"]>;
1114
+ /** v2 audit identity: `category/slug`, stable across releases (spec §6). */
1115
+ declare const AUDIT_ID_PATTERN: RegExp;
624
1116
  declare const AuditMetaSchema: z.ZodObject<{
625
1117
  id: z.ZodString;
626
1118
  category: z.ZodString;
@@ -653,6 +1145,19 @@ declare const AuditMetaSchema: z.ZodObject<{
653
1145
  docsUrl?: string | undefined;
654
1146
  tags?: string[] | undefined;
655
1147
  }>>;
1148
+ deprecated: z.ZodOptional<z.ZodObject<{
1149
+ notice: z.ZodString;
1150
+ link: z.ZodString;
1151
+ }, "strip", z.ZodTypeAny, {
1152
+ link: string;
1153
+ notice: string;
1154
+ }, {
1155
+ link: string;
1156
+ notice: string;
1157
+ }>>;
1158
+ evidenceGrade: z.ZodEnum<["A", "B", "C", "D"]>;
1159
+ tier: z.ZodEnum<["scored", "informative", "experimental"]>;
1160
+ dossier: z.ZodString;
656
1161
  }, "strip", z.ZodTypeAny, {
657
1162
  category: string;
658
1163
  title: string;
@@ -662,6 +1167,9 @@ declare const AuditMetaSchema: z.ZodObject<{
662
1167
  scoreDisplayMode: "binary" | "ternary" | "informative";
663
1168
  weight: number;
664
1169
  defaultPriority: "critical" | "high" | "medium" | "low";
1170
+ evidenceGrade: "A" | "B" | "C" | "D";
1171
+ tier: "informative" | "scored" | "experimental";
1172
+ dossier: string;
665
1173
  applicablePageTypes?: string[] | undefined;
666
1174
  guidance?: {
667
1175
  impact: string;
@@ -671,6 +1179,10 @@ declare const AuditMetaSchema: z.ZodObject<{
671
1179
  docsUrl?: string | undefined;
672
1180
  tags?: string[] | undefined;
673
1181
  } | undefined;
1182
+ deprecated?: {
1183
+ link: string;
1184
+ notice: string;
1185
+ } | undefined;
674
1186
  }, {
675
1187
  category: string;
676
1188
  title: string;
@@ -680,6 +1192,9 @@ declare const AuditMetaSchema: z.ZodObject<{
680
1192
  scoreDisplayMode: "binary" | "ternary" | "informative";
681
1193
  weight: number;
682
1194
  defaultPriority: "critical" | "high" | "medium" | "low";
1195
+ evidenceGrade: "A" | "B" | "C" | "D";
1196
+ tier: "informative" | "scored" | "experimental";
1197
+ dossier: string;
683
1198
  applicablePageTypes?: string[] | undefined;
684
1199
  guidance?: {
685
1200
  impact: string;
@@ -689,6 +1204,10 @@ declare const AuditMetaSchema: z.ZodObject<{
689
1204
  docsUrl?: string | undefined;
690
1205
  tags?: string[] | undefined;
691
1206
  } | undefined;
1207
+ deprecated?: {
1208
+ link: string;
1209
+ notice: string;
1210
+ } | undefined;
692
1211
  }>;
693
1212
  declare const CheckResultSchema: z.ZodObject<{
694
1213
  id: z.ZodString;
@@ -709,18 +1228,33 @@ declare const CheckResultSchema: z.ZodObject<{
709
1228
  found: z.ZodOptional<z.ZodString>;
710
1229
  code: z.ZodOptional<z.ZodString>;
711
1230
  docsUrl: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodString]>;
1231
+ evidenceUrl: z.ZodOptional<z.ZodString>;
1232
+ }, "strip", z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString, "many">]>, z.objectOutputType<{
1233
+ expected: z.ZodOptional<z.ZodString>;
1234
+ found: z.ZodOptional<z.ZodString>;
1235
+ code: z.ZodOptional<z.ZodString>;
1236
+ docsUrl: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodString]>;
1237
+ evidenceUrl: z.ZodOptional<z.ZodString>;
1238
+ }, z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString, "many">]>, "strip">, z.objectInputType<{
1239
+ expected: z.ZodOptional<z.ZodString>;
1240
+ found: z.ZodOptional<z.ZodString>;
1241
+ code: z.ZodOptional<z.ZodString>;
1242
+ docsUrl: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodString]>;
1243
+ evidenceUrl: z.ZodOptional<z.ZodString>;
1244
+ }, z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString, "many">]>, "strip">>>;
1245
+ tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
1246
+ deprecated: z.ZodOptional<z.ZodObject<{
1247
+ notice: z.ZodString;
1248
+ link: z.ZodString;
712
1249
  }, "strip", z.ZodTypeAny, {
713
- code?: string | undefined;
714
- found?: string | undefined;
715
- expected?: string | undefined;
716
- docsUrl?: string | undefined;
1250
+ link: string;
1251
+ notice: string;
717
1252
  }, {
718
- code?: string | undefined;
719
- found?: string | undefined;
720
- expected?: string | undefined;
721
- docsUrl?: string | undefined;
1253
+ link: string;
1254
+ notice: string;
722
1255
  }>>;
723
- tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
1256
+ evidenceGrade: z.ZodOptional<z.ZodEnum<["A", "B", "C", "D"]>>;
1257
+ tier: z.ZodOptional<z.ZodEnum<["scored", "informative", "experimental"]>>;
724
1258
  }, "strip", z.ZodTypeAny, {
725
1259
  status: "warn" | "pass" | "fail" | "na";
726
1260
  category: string;
@@ -732,16 +1266,23 @@ declare const CheckResultSchema: z.ZodObject<{
732
1266
  fix: string;
733
1267
  description: string;
734
1268
  scoreDisplayMode: "binary" | "ternary" | "informative";
735
- details?: {
736
- code?: string | undefined;
737
- found?: string | undefined;
738
- expected?: string | undefined;
739
- docsUrl?: string | undefined;
740
- } | undefined;
1269
+ details?: z.objectOutputType<{
1270
+ expected: z.ZodOptional<z.ZodString>;
1271
+ found: z.ZodOptional<z.ZodString>;
1272
+ code: z.ZodOptional<z.ZodString>;
1273
+ docsUrl: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodString]>;
1274
+ evidenceUrl: z.ZodOptional<z.ZodString>;
1275
+ }, z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString, "many">]>, "strip"> | undefined;
741
1276
  displayValue?: string | undefined;
742
1277
  explanation?: string | undefined;
743
1278
  pageUrl?: string | undefined;
744
1279
  tags?: string[] | undefined;
1280
+ deprecated?: {
1281
+ link: string;
1282
+ notice: string;
1283
+ } | undefined;
1284
+ evidenceGrade?: "A" | "B" | "C" | "D" | undefined;
1285
+ tier?: "informative" | "scored" | "experimental" | undefined;
745
1286
  }, {
746
1287
  status: "warn" | "pass" | "fail" | "na";
747
1288
  category: string;
@@ -753,16 +1294,23 @@ declare const CheckResultSchema: z.ZodObject<{
753
1294
  fix: string;
754
1295
  description: string;
755
1296
  scoreDisplayMode: "binary" | "ternary" | "informative";
756
- details?: {
757
- code?: string | undefined;
758
- found?: string | undefined;
759
- expected?: string | undefined;
760
- docsUrl?: string | undefined;
761
- } | undefined;
1297
+ details?: z.objectInputType<{
1298
+ expected: z.ZodOptional<z.ZodString>;
1299
+ found: z.ZodOptional<z.ZodString>;
1300
+ code: z.ZodOptional<z.ZodString>;
1301
+ docsUrl: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodString]>;
1302
+ evidenceUrl: z.ZodOptional<z.ZodString>;
1303
+ }, z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString, "many">]>, "strip"> | undefined;
762
1304
  displayValue?: string | undefined;
763
1305
  explanation?: string | undefined;
764
1306
  pageUrl?: string | undefined;
765
1307
  tags?: string[] | undefined;
1308
+ deprecated?: {
1309
+ link: string;
1310
+ notice: string;
1311
+ } | undefined;
1312
+ evidenceGrade?: "A" | "B" | "C" | "D" | undefined;
1313
+ tier?: "informative" | "scored" | "experimental" | undefined;
766
1314
  }>;
767
1315
 
768
1316
  declare function normalizeUrl(input: string): string;
@@ -817,4 +1365,15 @@ declare class Logger {
817
1365
  }
818
1366
  declare const logger: Logger;
819
1367
 
820
- export { type AgentLighthouseConfig, Audit, type AuditGuidance, AuditGuidanceSchema, type AuditMeta, AuditMetaSchema, type AuditPlan, type AuditProgressEvent, type AuditRegistration, type AuditResult, AuditResultSchema, type AuditRunResult, CATEGORY_NAMES, CATEGORY_WEIGHTS, type CategoryConfig, type CategoryResult, type CheckContext, type CheckFn, type CheckPriority, CheckPrioritySchema, type CheckRecommendation, type CheckResult, CheckResultSchema, type CheckStatus, CheckStatusSchema, DEFAULT_SCAN_LIMIT, type FetchOptions, type FetchResult, type FieldStatus, type FixEffort, FixEffortSchema, MAX_CONCURRENT_REQUESTS, MAX_PAGES_PER_SCAN, MAX_RESPONSE_BODY_BYTES, PAGE_TYPE_LABELS, PHASE_WEIGHTS, PRESETS, type PageContext, type PageOverride, type PageType, type PhaseId, type PresetName, type PresetOptions, type ProductFieldVerification, ProgressTracker, READINESS_WEIGHTS, REQUEST_TIMEOUT_MS, type ReadinessVitals, SCANNER_USER_AGENT, SCAN_TIMEOUT_MS, SCORE_TIER_LABELS, type ScanConfig, type ScanEvent, type ScanOptions, type ScanReport, type ScoreDisplayMode, ScoreDisplayModeSchema, type ScoreTier, TAG_SCAN_ERROR, TAG_SKIPPED_PAGE_TYPE, type WafProtection, buildCategoryResult, calculateCategoryScore, calculateOverallScore, createFetcher, defaultConfig, defineConfig, detectPageType, detectWafProtection, extractDomain, extractForms, extractHeadLinks, extractHeadings, extractImages, extractInternalLinks, extractJsonLd, extractMarkdownLinks, extractMetaTags, extractNavLinks, extractProductFieldVerification, extractScripts, extractStylesheetUrls, flattenJsonLd, getMainContentText, getPreset, getScoreTier, getTierColor, getTierLabel, getWordCount, isPrivateIp, isSafeUrl, joinUrl, loadConfigFile, logger, normalizeUrl, parseHtml, planAudits, runAudits, runScan };
1368
+ /** The ACP CheckoutSession `links` enum, in spec order. */
1369
+ declare const ACP_LINK_TYPES: readonly ["terms_of_use", "privacy_policy", "return_policy", "shipping_policy", "contact_us", "about_us", "faq", "support"];
1370
+ type AcpLinkType = (typeof ACP_LINK_TYPES)[number];
1371
+ /**
1372
+ * The first same-page candidate URL for each ACP link type, in document order.
1373
+ *
1374
+ * Classification is by URL path first, anchor text second, so a link whose path
1375
+ * is opaque (`/p/12345`) is still classified when its label is not.
1376
+ */
1377
+ declare function resolvePolicyLinks(ctx: CheckContext): Map<AcpLinkType, string>;
1378
+
1379
+ export { ACP_LINK_TYPES, AI_CRAWLER_UAS, AUDIT_ID_PATTERN, type AcpLinkType, type AgentLighthouseConfig, Audit, type AuditGuidance, AuditGuidanceSchema, type AuditMeta, AuditMetaSchema, type AuditPlan, type AuditProgressEvent, type AuditRegistration, type AuditResult, AuditResultSchema, type AuditRunResult, type AuditTier, AuditTierSchema, type AuditTrace, type AuditTraceHandler, BASELINE_UA, type BlockClass, CATEGORY_IDS, CATEGORY_MASS, CATEGORY_NAMES, type CategoryConfig, type CategoryResult, type CheckContext, type CheckFn, type CheckPriority, CheckPrioritySchema, type CheckRecommendation, type CheckResult, CheckResultSchema, type CheckStatus, CheckStatusSchema, type CssRule, DEFAULT_SCAN_LIMIT, type DeprecationNotice, DeprecationNoticeSchema, type EvidenceGrade, EvidenceGradeSchema, type ExpectedKind, type FetchClass, type FetchOptions, type FetchResult, type FieldStatus, type FixEffort, FixEffortSchema, MAX_CONCURRENT_REQUESTS, MAX_PAGES_PER_SCAN, MAX_RESPONSE_BODY_BYTES, PAGE_TYPE_LABELS, PHASE_WEIGHTS, PRESETS, type PageContext, type PageCss, type PageJudgement, type PageOverride, type PageType, type PhaseId, type PresetName, type PresetOptions, type ProductFieldVerification, ProgressTracker, READINESS_WEIGHTS, REQUEST_TIMEOUT_MS, type ReadinessVitals, type RobotsFile, type RobotsGroup, type RobotsRule, SCANNER_USER_AGENT, SCAN_TIMEOUT_MS, SCORE_TIER_LABELS, type ScanConfig, type ScanEvent, type ScanOptions, type ScanReport, type ScoreDisplayMode, ScoreDisplayModeSchema, type ScoreTier, type SitemapEntry, type SitemapTree, TAG_SCAN_ERROR, TAG_SKIPPED_PAGE_TYPE, type UaProbe, type WafProtection, allJsonLdNodes, buildCategoryResult, calculateCategoryScore, calculateOverallScore, classifyFetch, classifyResponse, collectPageCss, collectSitemapEntries, createFetcher, defaultConfig, defineConfig, detectPageType, detectWafProtection, evidenceUrl, extractDomain, extractForms, extractHeadLinks, extractHeadings, extractImages, extractInternalLinks, extractJsonLd, extractMarkdownLinks, extractMetaTags, extractNavLinks, extractProductFieldVerification, extractScripts, extractStylesheetUrls, filterConfig, flattenJsonLd, formatTrace, getMainContentText, getPreset, getScoreTier, getTierColor, getTierLabel, getWordCount, groupsForBot, hasNamedGroup, isBlanketBlocked, isInformative, isPathAllowed, isPrivateIp, isRealFile, isSafeUrl, isW3CDateTime, joinUrl, judgePages, loadConfigFile, logger, matchesUserAgent, normalizeNewlines, normalizeUrl, outcomeOf, pagesOfType, parseCssRules, parseHtml, parseRobots, parseRobotsFile, planAudits, probeUaParity, resolvePolicyLinks, runAudits, runScan, sampleEntries, stripBom, topLevelJsonLd, traceFromCheck, weightForGrade };