ax-audit 3.6.0 → 4.1.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/CHANGELOG.md +151 -0
- package/LICENSE +1 -1
- package/README.md +58 -36
- package/dist/baseline.d.ts +2 -0
- package/dist/baseline.d.ts.map +1 -1
- package/dist/baseline.js +42 -4
- package/dist/baseline.js.map +1 -1
- package/dist/check-ids.d.ts +19 -0
- package/dist/check-ids.d.ts.map +1 -0
- package/dist/check-ids.js +53 -0
- package/dist/check-ids.js.map +1 -0
- package/dist/checks/agent-access.d.ts +23 -6
- package/dist/checks/agent-access.d.ts.map +1 -1
- package/dist/checks/agent-access.js +200 -54
- package/dist/checks/agent-access.js.map +1 -1
- package/dist/checks/agent-card.d.ts +37 -0
- package/dist/checks/agent-card.d.ts.map +1 -0
- package/dist/checks/agent-card.js +352 -0
- package/dist/checks/agent-card.js.map +1 -0
- package/dist/checks/agent-operability.d.ts +66 -0
- package/dist/checks/agent-operability.d.ts.map +1 -0
- package/dist/checks/agent-operability.js +383 -0
- package/dist/checks/agent-operability.js.map +1 -0
- package/dist/checks/agent-skills.d.ts +24 -0
- package/dist/checks/agent-skills.d.ts.map +1 -0
- package/dist/checks/agent-skills.js +316 -0
- package/dist/checks/agent-skills.js.map +1 -0
- package/dist/checks/ai-catalog.d.ts +28 -0
- package/dist/checks/ai-catalog.d.ts.map +1 -0
- package/dist/checks/ai-catalog.js +254 -0
- package/dist/checks/ai-catalog.js.map +1 -0
- package/dist/checks/ai-directives.d.ts +57 -0
- package/dist/checks/ai-directives.d.ts.map +1 -0
- package/dist/checks/ai-directives.js +263 -0
- package/dist/checks/ai-directives.js.map +1 -0
- package/dist/checks/api-discovery.d.ts +26 -0
- package/dist/checks/api-discovery.d.ts.map +1 -0
- package/dist/checks/api-discovery.js +432 -0
- package/dist/checks/api-discovery.js.map +1 -0
- package/dist/checks/auth-discovery.d.ts +28 -0
- package/dist/checks/auth-discovery.d.ts.map +1 -0
- package/dist/checks/auth-discovery.js +213 -0
- package/dist/checks/auth-discovery.js.map +1 -0
- package/dist/checks/commerce-discovery.d.ts +40 -0
- package/dist/checks/commerce-discovery.d.ts.map +1 -0
- package/dist/checks/commerce-discovery.js +295 -0
- package/dist/checks/commerce-discovery.js.map +1 -0
- package/dist/checks/content-negotiation.d.ts.map +1 -1
- package/dist/checks/content-negotiation.js +135 -20
- package/dist/checks/content-negotiation.js.map +1 -1
- package/dist/checks/crawl-efficiency.d.ts +13 -1
- package/dist/checks/crawl-efficiency.d.ts.map +1 -1
- package/dist/checks/crawl-efficiency.js +65 -1
- package/dist/checks/crawl-efficiency.js.map +1 -1
- package/dist/checks/frontmatter.d.ts +34 -0
- package/dist/checks/frontmatter.d.ts.map +1 -0
- package/dist/checks/frontmatter.js +100 -0
- package/dist/checks/frontmatter.js.map +1 -0
- package/dist/checks/html-rendering.d.ts.map +1 -1
- package/dist/checks/html-rendering.js +0 -1
- package/dist/checks/html-rendering.js.map +1 -1
- package/dist/checks/html-utils.d.ts +10 -0
- package/dist/checks/html-utils.d.ts.map +1 -1
- package/dist/checks/html-utils.js +19 -0
- package/dist/checks/html-utils.js.map +1 -1
- package/dist/checks/http-headers.d.ts.map +1 -1
- package/dist/checks/http-headers.js +82 -10
- package/dist/checks/http-headers.js.map +1 -1
- package/dist/checks/http-hygiene.d.ts +26 -0
- package/dist/checks/http-hygiene.d.ts.map +1 -0
- package/dist/checks/http-hygiene.js +257 -0
- package/dist/checks/http-hygiene.js.map +1 -0
- package/dist/checks/index.d.ts.map +1 -1
- package/dist/checks/index.js +24 -8
- package/dist/checks/index.js.map +1 -1
- package/dist/checks/llms-txt.d.ts +15 -0
- package/dist/checks/llms-txt.d.ts.map +1 -1
- package/dist/checks/llms-txt.js +162 -2
- package/dist/checks/llms-txt.js.map +1 -1
- package/dist/checks/mcp-discovery.d.ts +30 -0
- package/dist/checks/mcp-discovery.d.ts.map +1 -0
- package/dist/checks/mcp-discovery.js +523 -0
- package/dist/checks/mcp-discovery.js.map +1 -0
- package/dist/checks/meta-tags.d.ts.map +1 -1
- package/dist/checks/meta-tags.js +6 -5
- package/dist/checks/meta-tags.js.map +1 -1
- package/dist/checks/robots-parser.d.ts +110 -0
- package/dist/checks/robots-parser.d.ts.map +1 -0
- package/dist/checks/robots-parser.js +277 -0
- package/dist/checks/robots-parser.js.map +1 -0
- package/dist/checks/robots-txt.d.ts +2 -20
- package/dist/checks/robots-txt.d.ts.map +1 -1
- package/dist/checks/robots-txt.js +219 -120
- package/dist/checks/robots-txt.js.map +1 -1
- package/dist/checks/rsl.d.ts +0 -2
- package/dist/checks/rsl.d.ts.map +1 -1
- package/dist/checks/rsl.js +1 -11
- package/dist/checks/rsl.js.map +1 -1
- package/dist/checks/security-txt.d.ts.map +1 -1
- package/dist/checks/security-txt.js +0 -1
- package/dist/checks/security-txt.js.map +1 -1
- package/dist/checks/seo-basics.d.ts.map +1 -1
- package/dist/checks/seo-basics.js +0 -1
- package/dist/checks/seo-basics.js.map +1 -1
- package/dist/checks/sitemap.d.ts.map +1 -1
- package/dist/checks/sitemap.js +0 -1
- package/dist/checks/sitemap.js.map +1 -1
- package/dist/checks/structured-data.d.ts.map +1 -1
- package/dist/checks/structured-data.js +215 -4
- package/dist/checks/structured-data.js.map +1 -1
- package/dist/checks/structured-fields.d.ts +46 -0
- package/dist/checks/structured-fields.d.ts.map +1 -0
- package/dist/checks/structured-fields.js +112 -0
- package/dist/checks/structured-fields.js.map +1 -0
- package/dist/checks/surface.d.ts +59 -0
- package/dist/checks/surface.d.ts.map +1 -0
- package/dist/checks/surface.js +106 -0
- package/dist/checks/surface.js.map +1 -0
- package/dist/checks/tls-https.d.ts.map +1 -1
- package/dist/checks/tls-https.js +0 -1
- package/dist/checks/tls-https.js.map +1 -1
- package/dist/checks/usage-policy.d.ts +53 -0
- package/dist/checks/usage-policy.d.ts.map +1 -0
- package/dist/checks/usage-policy.js +339 -0
- package/dist/checks/usage-policy.js.map +1 -0
- package/dist/checks/utils.d.ts +25 -1
- package/dist/checks/utils.d.ts.map +1 -1
- package/dist/checks/utils.js +33 -1
- package/dist/checks/utils.js.map +1 -1
- package/dist/checks/waf.d.ts +75 -0
- package/dist/checks/waf.d.ts.map +1 -0
- package/dist/checks/waf.js +203 -0
- package/dist/checks/waf.js.map +1 -0
- package/dist/checks/webmcp.d.ts +55 -0
- package/dist/checks/webmcp.d.ts.map +1 -0
- package/dist/checks/webmcp.js +209 -0
- package/dist/checks/webmcp.js.map +1 -0
- package/dist/checks/well-known.d.ts +38 -0
- package/dist/checks/well-known.d.ts.map +1 -0
- package/dist/checks/well-known.js +202 -0
- package/dist/checks/well-known.js.map +1 -0
- package/dist/cli.d.ts +12 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +110 -4
- package/dist/cli.js.map +1 -1
- package/dist/constants.d.ts +195 -14
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +598 -70
- package/dist/constants.js.map +1 -1
- package/dist/fetcher.d.ts.map +1 -1
- package/dist/fetcher.js +44 -14
- package/dist/fetcher.js.map +1 -1
- package/dist/guide-urls.js +1 -1
- package/dist/guide-urls.js.map +1 -1
- package/dist/index.d.ts +22 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -1
- package/dist/index.js.map +1 -1
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +5 -1
- package/dist/orchestrator.js.map +1 -1
- package/dist/reporter/html.d.ts +9 -0
- package/dist/reporter/html.d.ts.map +1 -1
- package/dist/reporter/html.js +49 -11
- package/dist/reporter/html.js.map +1 -1
- package/dist/reporter/markdown.d.ts.map +1 -1
- package/dist/reporter/markdown.js +36 -6
- package/dist/reporter/markdown.js.map +1 -1
- package/dist/reporter/terminal.d.ts.map +1 -1
- package/dist/reporter/terminal.js +36 -1
- package/dist/reporter/terminal.js.map +1 -1
- package/dist/scorer.d.ts +24 -1
- package/dist/scorer.d.ts.map +1 -1
- package/dist/scorer.js +51 -6
- package/dist/scorer.js.map +1 -1
- package/dist/types.d.ts +90 -3
- package/dist/types.d.ts.map +1 -1
- package/docs/api.md +58 -2
- package/docs/architecture.md +27 -11
- package/docs/checks.md +285 -52
- package/docs/cli.md +36 -0
- package/docs/concepts.md +27 -13
- package/docs/faq.md +18 -6
- package/docs/getting-started.md +20 -13
- package/docs/roadmap.md +367 -0
- package/package.json +13 -5
- package/dist/checks/agent-json.d.ts +0 -14
- package/dist/checks/agent-json.d.ts.map +0 -1
- package/dist/checks/agent-json.js +0 -167
- package/dist/checks/agent-json.js.map +0 -1
- package/dist/checks/mcp.d.ts +0 -4
- package/dist/checks/mcp.d.ts.map +0 -1
- package/dist/checks/mcp.js +0 -162
- package/dist/checks/mcp.js.map +0 -1
- package/dist/checks/openapi.d.ts +0 -4
- package/dist/checks/openapi.d.ts.map +0 -1
- package/dist/checks/openapi.js +0 -121
- package/dist/checks/openapi.js.map +0 -1
- package/dist/checks/well-known-ai.d.ts +0 -17
- package/dist/checks/well-known-ai.d.ts.map +0 -1
- package/dist/checks/well-known-ai.js +0 -123
- package/dist/checks/well-known-ai.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,157 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to ax-audit are documented here.
|
|
4
4
|
|
|
5
|
+
## [4.1.0] - 2026-09-04
|
|
6
|
+
|
|
7
|
+
**The reference tables are importable.** A consumer could always run an audit and never describe one: `CHECK_WEIGHTS`, `CHECK_CATEGORIES` and the crawler lists sat behind an `exports` map that admits only the entry point, so anything printing what a check is worth, grouping results by area, or naming the crawlers worth naming kept a hand-copied table and a test to catch the day it went stale. That is a duplication this package caused.
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **`VERSION`, `CHECK_WEIGHTS`, `CHECK_CATEGORIES`, `GRADES`** exported from the entry point.
|
|
12
|
+
- **`AI_CRAWLERS`, `ALL_AI_CRAWLERS`, `CORE_AI_CRAWLERS`, `LEGACY_AI_CRAWLERS`, `CRAWLER_META`, `CONTENT_SIGNALS`**, with `crawlerInfo`, `crawlerPurpose` and `legacyCrawlerNote` for looking a token up, and the `CrawlerInfo` / `CrawlerPurpose` types.
|
|
13
|
+
- **`categoryScore(results, category)`** exported. It was in `cli.ts`, which meant importing `commander` to reach it; it belongs beside `calculateOverallScore` and now lives there. `--fail-on-category` is one caller and a report grouped by area is another.
|
|
14
|
+
- **`test/public-api.test.js`**, which fails both ways: on a promised export that disappeared, and on a new name that leaked out of a refactor. It also holds the tables against the registered checks — every shipped check is weighed and categorised, nothing retired lingers in either, no crawler token sits in two purposes, and a check declaring its own `meta.category` agrees with the table.
|
|
15
|
+
|
|
16
|
+
Nothing else changed: no check, no weight, no score. The rest of `constants.ts` stays private, because a consumer pinned to a threshold or a required-field list would make every check's internals a breaking change.
|
|
17
|
+
|
|
18
|
+
## [4.0.0] - 2026-09-04
|
|
19
|
+
|
|
20
|
+
**Scores change on every site.** This release redistributes the weights, makes protocol checks conditional, and retires a check whose premise did not survive verification. Baselines written by earlier versions are read normally, but regression gating is suspended across the change — a rescore is not something the site did.
|
|
21
|
+
|
|
22
|
+
### Breaking
|
|
23
|
+
|
|
24
|
+
- **Weights redistributed by what actually stops an agent.** Content leads at 33, because the failure that breaks the most agents is a page with nothing in its HTML: most crawlers do not run JavaScript, so content that appears only after hydration is invisible no matter how many discovery files a site publishes. `html-rendering` is the highest-weighted check at 11. Access is second at 24, because a firewall rule or a `nosnippet` directive silently undoes everything else. Discovery drops to 21, with llms.txt demoted from 11 to 5 on the evidence that most published files are never fetched by an AI search crawler.
|
|
25
|
+
- **Protocol checks are conditional.** A blog has no API to describe and is not an agent; scoring it zero said something false and made the overall number unreadable. Each protocol check now detects whether the site has the corresponding surface, reports **n/a** when it does not, and leaves the denominator. When the surface *is* present, a missing description is a stronger finding than before, and the wording says so: not "no MCP server discovery found" but "MCP server present but not discoverable".
|
|
26
|
+
- **`well-known-ai` retired.** Three of its five scored files had no consumer: `nlweb.json` appears in no NLWeb release, `genai.txt` has no specification, and `ai-plugin.json` described a product shut down in 2024. Its useful probes moved into the checks that own them in 3.8.
|
|
27
|
+
- **The 3.x scoring freezes are gone.** `robots-txt` scores against all twelve current core crawlers rather than the frozen eight, so the configuration that scored 100 in 3.6 now scores 90. Blocking a crawler deducts by purpose: 5 for a search crawler, whose absence removes the site from that assistant's answers, and 2 for a training crawler, where blocking is a defensible policy choice.
|
|
28
|
+
- **`CheckMeta.weight` is now optional** and nothing uses it. Weights live in `CHECK_WEIGHTS` alone. Having them in two places is what let them drift.
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- **`--profile api|mcp|agent|docs|commerce|all`** forces conditional checks applicable, for auditing against what a site intends to build rather than what it already has.
|
|
33
|
+
- **`--category content|discovery|access|policy|protocols`** runs one area. It narrows an explicit `--checks` selection rather than replacing it.
|
|
34
|
+
- **`--fail-on-category access:70,content:80`** gates CI per area. An overall score hides an area that is entirely broken: a site can score 80 while every access check fails because the other four carry it. Areas with nothing applicable are reported as not evaluated rather than failed.
|
|
35
|
+
- **Versioned baselines.** `schemaVersion` records which scoring model produced a baseline; a mismatch shows the deltas but suspends gating and says how to resume it. Baselines also record which checks were N/A, so gaining or losing a surface is not read as a regression or an improvement.
|
|
36
|
+
|
|
37
|
+
### Migration
|
|
38
|
+
|
|
39
|
+
1. Expect a different score. Sites with no API or MCP surface generally rise, because they are no longer marked down for lacking things they do not have. A bare page rose from 20 to 50 in testing; a documentation site with real infrastructure rose from 64 to 77.
|
|
40
|
+
2. Re-save your baseline: `ax-audit <url> --save-baseline .ax-baseline.json`. Until you do, `--fail-on-regression` reports but does not gate.
|
|
41
|
+
3. `--checks agent-json`, `--checks mcp` and `--checks openapi` still work; the ids were renamed in 3.7 and the old names remain aliases.
|
|
42
|
+
4. If you relied on `well-known-ai`, its live probes are now in `usage-policy` (TDMRep) and the checks that own each file.
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
- **JSON-LD was missed when `type` was not the first attribute** on the script tag. Next.js and several other frameworks emit `<script id="…" type="application/ld+json">`, so a large class of well-marked-up sites scored 0 on a 6-weight check and were told to add markup they already had. Three copies of the pattern now share one order-independent extractor.
|
|
47
|
+
|
|
48
|
+
### Tests
|
|
49
|
+
|
|
50
|
+
873 total. New coverage for surface detection, the weight distribution (sums to 100, no check declares its own, content outweighs every area), the CLI end to end via a subprocess, and baseline versioning in both directions.
|
|
51
|
+
|
|
52
|
+
## [3.9.0] - 2026-09-04
|
|
53
|
+
|
|
54
|
+
Depth rather than breadth. One new check, and the existing readability checks extended to cover what agents actually do with a page once they reach it. Everything added is informational; scores are unchanged.
|
|
55
|
+
|
|
56
|
+
### Added
|
|
57
|
+
|
|
58
|
+
- **agent-operability**: whether a browser agent can work the page or only look at it. Every major browser agent reads the accessibility tree, so a `<div onclick>` styled as a button does not appear at all — the agent does not see a button it cannot press, it sees nothing. Checks accessible names on buttons and links, labels on form controls, elements dressed as controls, dead links, table headers, iframe titles, `<time datetime>`, heading skips, unsized media, `<html lang>`, and entry-page obstacles. Reports proportions with a 90% threshold, and states on every run that it reads markup rather than a rendered tree.
|
|
59
|
+
|
|
60
|
+
### Changed
|
|
61
|
+
|
|
62
|
+
- **llms.txt** gained the 2026 specification revision's additions — subpath scoping via `rel="describedby"`, per-page `.md` mirrors — plus link-health sampling. An llms.txt is written once and never revalidated while the site moves underneath it, so an index of dead links is its characteristic failure, and it wastes exactly the budget the file exists to save. Dead links are reported separately from redirecting ones. Every report now states who actually reads this file.
|
|
63
|
+
- **content-negotiation** stopped probing with a header no client sends. A bare `Accept: text/markdown` would pass against an implementation that fails every real request; the probe now sends what Claude Code, Cursor and OpenCode send. Added: origin-reported token counts preferred over a byte ratio, frontmatter validation (stripping the HTML also strips the canonical link, leaving an agent unable to attribute what it quotes), and probes for user-agent negotiation and `.md` suffix URLs before concluding a site has no Markdown.
|
|
64
|
+
- **structured-data** now reads provenance and freshness — `author`, `sameAs`, `publisher`, `dateModified` — and compares `headline` and `name` against the visible text, which is Google's one explicit requirement for structured data and AI features.
|
|
65
|
+
- **crawl-efficiency** reports a page's cost in tokens rather than bytes, naming the markup share an agent pays to receive and then discards, plus response time in agent terms.
|
|
66
|
+
|
|
67
|
+
### Tests
|
|
68
|
+
|
|
69
|
+
828 total, up from 777.
|
|
70
|
+
|
|
71
|
+
## [3.8.0] - 2026-09-04
|
|
72
|
+
|
|
73
|
+
Eight new checks, all weight 0, plus the reporting machinery they need. Where 3.7 corrected what ax-audit was looking for, 3.8 covers the signals it was not looking at.
|
|
74
|
+
|
|
75
|
+
### Added — reporting
|
|
76
|
+
|
|
77
|
+
- **Not-applicable checks.** A commerce-protocol check scoring zero on a personal blog says the blog is badly built; the honest answer is that the question does not arise. `CheckResult.applicable: false` excludes a check from both numerator and denominator, and reports render it as `n/a`. Everything counted against a site is now something the site could have done. A check whose meta exists but produced no result — because it crashed — still counts at full weight, so a broken check cannot inflate a score.
|
|
78
|
+
- **Category grouping.** Terminal, Markdown and HTML output group checks under content, discovery, access, policy and protocols. Twenty-six scores in a flat list do not tell a reader where the problem is.
|
|
79
|
+
- **`renderHtml` / `renderBatchHtml`**, returning the document as a string the way `renderMarkdown` already did. The HTML reporter could previously only print to stdout, which made it untestable and unusable from the programmatic API.
|
|
80
|
+
|
|
81
|
+
### Added — checks
|
|
82
|
+
|
|
83
|
+
- **ai-directives**: the page-level controls Google and Microsoft document that they honor — `nosnippet` and `max-snippet` for AI Overviews and AI Mode, `noarchive` and `nocache` for Copilot grounding, `data-nosnippet` judged by what it wraps. The finding it exists for: a site that disallows `Google-Extended` expecting to leave AI Overviews has done no such thing, because that token governs Gemini training and grounding while AI Overviews follow Googlebot and the snippet directives.
|
|
84
|
+
- **usage-policy**: normalises Content Signals, AIPREF, RSL, TDMRep and `noai` onto three questions — train, ground, index — and reports where they disagree. A site whose robots.txt permits training while its RSL licence prohibits it has published two contradictory positions, and which one applies depends on which file a crawler read. Every report states that only robots.txt access rules are documented as honored by major operators.
|
|
85
|
+
- **http-hygiene**: status-code honesty. An agent cannot see that the 200 it received is a "page not found" screen; it stores the apology as the answer. Also redirect depth, `HEAD` support, `Retry-After` on 429, charset, and `<html lang>` against `Content-Language`.
|
|
86
|
+
- **ai-catalog**: the emerging single index of everything callable on a site, discovered the four ways Lighthouse's own audit accepts. Both specifications are drafts, so absence never scores — but a catalog whose entries point at documents that 404 does, because an agent trusts it before it fetches.
|
|
87
|
+
- **agent-skills**: installable procedures (SKILL.md) across all three competing discovery paths, validating the index and fetching a sample of the documents. Conditional on the site having a developer-facing surface.
|
|
88
|
+
- **webmcp**: forms declared as callable tools. Never asks for WebMCP, which is a Community Group draft in a Chrome origin trial; catches the mistakes static analysis can catch with certainty on pages that adopted it.
|
|
89
|
+
- **commerce-discovery**: the Universal Commerce Protocol profile, the one agentic-commerce specification with published site-side discovery. Validates the profile and resolves the schema URLs it promises. The alternatives define no manifest, and the report says so rather than inventing paths.
|
|
90
|
+
- **auth-discovery**: the RFC 9728 chain from `WWW-Authenticate` to the authorization server's metadata. A human hitting a 401 reads the documentation; an agent cannot.
|
|
91
|
+
|
|
92
|
+
### Added — infrastructure
|
|
93
|
+
|
|
94
|
+
- `checks/frontmatter.ts`: a focused YAML frontmatter reader for SKILL.md and Markdown mirrors. ax-audit has two runtime dependencies and will not grow a YAML parser, so the reader handles the flat `key: value` block these documents use and records anything more elaborate as skipped rather than guessing.
|
|
95
|
+
|
|
96
|
+
### Fixed
|
|
97
|
+
|
|
98
|
+
- **A lone `Offer` is a price, not a storefront.** Found by running commerce-discovery against a real SaaS site, which was told to build a commerce integration because it prices its plans with structured data. A bare `Offer` now counts only alongside a second signal such as a cart link.
|
|
99
|
+
- `buildResult` falls back to `CHECK_CATEGORIES`, so every result carries its category in JSON output too. Previously only checks setting `meta.category` explicitly did, leaving JSON consumers to reimplement the fallback the other reporters already had.
|
|
100
|
+
|
|
101
|
+
### Scoring
|
|
102
|
+
|
|
103
|
+
Unchanged. All eight checks carry weight 0 and gain weight in 4.0.
|
|
104
|
+
|
|
105
|
+
### Tests
|
|
106
|
+
|
|
107
|
+
755 total, up from 553. New suites: ai-directives, usage-policy, http-hygiene, ai-catalog, agent-skills, webmcp, commerce-discovery, auth-discovery, frontmatter, and the N/A arithmetic including the crashed-check case.
|
|
108
|
+
|
|
109
|
+
## [3.7.0] - 2026-09-04
|
|
110
|
+
|
|
111
|
+
A correction release. Three checks were probing paths that are no longer, or never were, the standard, and the crawler catalogue had drifted far enough to contain tokens that do not exist. Everything here was re-verified against vendor documentation, IANA, IETF datatracker and the relevant specification repositories on 2026-09-04.
|
|
112
|
+
|
|
113
|
+
**No score goes down.** Every correction that would have lowered an existing score is frozen behind a constant that is removed in 4.0, and tests assert it.
|
|
114
|
+
|
|
115
|
+
### Fixed — wrong paths
|
|
116
|
+
|
|
117
|
+
- **A2A Agent Card moved.** The check probed `/.well-known/agent.json`. A2A relocated the card to `/.well-known/agent-card.json` in v0.3.0 (2025-07-30), and that path is IANA-registered. Both are probed, the registered one first; a card served only from the old path is validated and flagged. The check also handles both spec generations: A2A 1.0 (2026-03-12) folded `url`, `protocolVersion`, `preferredTransport` and `additionalInterfaces` into `supportedInterfaces[]`, and the generation is detected from the card's own structure rather than a version field. `authentication`, removed in 0.2.x in favour of `securitySchemes`, is now flagged. Renamed `agent-json` → `agent-card`.
|
|
118
|
+
- **`/.well-known/mcp.json` was never an MCP convention.** ax-audit recommended it before the ecosystem settled. Discovery now walks the real chain: `/.well-known/ai-catalog.json`, `/.well-known/mcp/server-card.json`, `<endpoint>/server-card`, then the legacy manifest. Server cards deliberately carry no `tools[]` — tool lists come from a live `tools/list` call. Protocol versions are checked against the five released revisions, with `2026-07-28` current. Renamed `mcp` → `mcp-discovery`.
|
|
119
|
+
- **`/.well-known/openapi.json` is a folk convention**, so a site publishing at `/openapi.json` scored zero. Discovery now runs in order of authority: RFC 9727's registered `/.well-known/api-catalog`, then RFC 8631 `service-desc` relations in headers and HTML, then ten conventional paths. YAML descriptions are recognised and reported as surface-validated only, since ax-audit ships no YAML parser. Renamed `openapi` → `api-discovery`.
|
|
120
|
+
- **`agent-access` was probing with `Google-Extended`.** That string is a robots.txt control token governing how an already-crawled page may be used; no request carries it, so the probe tested nothing. Token-only controls are excluded, leaving 10 real crawlers.
|
|
121
|
+
- **`http-headers` and `meta-tags` penalised correct behaviour**, requiring a discovery link to `agent.json` specifically. Either card path now counts.
|
|
122
|
+
- **An SPA catch-all page is absence, not corruption.** Found by running the new checks against a real site: probing `/mcp/server-card` returned HTTP 200 with the application's index shell, and the check reported a malformed server card on a site with no MCP server. `isHtmlDocument` now gates the speculative probes in `agent-card`, `mcp-discovery` and `api-discovery`.
|
|
123
|
+
|
|
124
|
+
### Fixed — crawler catalogue
|
|
125
|
+
|
|
126
|
+
Verified against vendor documentation on 2026-09-04. Removed tokens that were never real user agents (`Gemini`, `GeminiBot`, `DeepSeek-AI`), whose products were discontinued (`NeevaBot`, `Operator`, `GoogleAgent-Mariner`), whose vendor operates no crawlers (Cohere), or that are not AI crawlers at all (the Awario social-listening family). Reclassified five user-triggered fetchers that were listed as training crawlers. Added `meta-webindexer`, `Amzn-SearchBot`, `Amzn-User`, `MistralAI-Index`, `MistralAI-Training`, `Google-GeminiNotebook`, `Applebot`, `ExaSearchBot`, `TikTokSpider`, `OAI-AdsBot`, `YandexAdditional`.
|
|
127
|
+
|
|
128
|
+
The catalogue is now organised by what a client does with a page — training, search, user-fetch, agent — because that determines the cost of blocking it. `CRAWLER_META` carries per token the vendor, whether robots.txt is honored, the published IP list, whether requests are signed with Web Bot Auth, and one sentence on what blocking costs. Findings quote it: "blocking OAI-SearchBot removes you from ChatGPT search answers", not "1 AI crawler blocked".
|
|
129
|
+
|
|
130
|
+
### Added
|
|
131
|
+
|
|
132
|
+
- **Response classification.** `checks/waf.ts` distinguishes a deliberate block from a JavaScript challenge from anti-spoofing, because the remedies are completely different. Signatures verified 2026-09-04: `cf-mitigated: challenge`, `x-vercel-mitigated`, AWS WAF's 202 with `x-amzn-waf-action`, Cloudflare pay-per-crawl's `crawler-price`, x402's `payment-signature`, Web Bot Auth's `Accept-Signature`, RSL's `WWW-Authenticate: License`, plus thirteen body markers. Outcomes an unsigned probe cannot settle are scored at 0.75 and labelled inconclusive with the header observed — never as "blocks AI crawlers".
|
|
133
|
+
- **IETF AIPREF `Content-Usage:`** in robots.txt (draft-ietf-aipref-attach-05), with an optional path scope and a Structured Fields dictionary. The loudest finding is a vocabulary mix-up: AIPREF spells the training token `train-ai` with `y`/`n` while Content Signals and RSL spell it `ai-train` with `yes`/`no`, so `Content-Usage: ai-train=no` looks correct and does nothing.
|
|
134
|
+
- **Content Signals `use=immediate|reference|full`**, the fourth field Cloudflare added on 2026-07-01 and now serves from managed robots.txt. Cloudflare-managed blocks are detected and named.
|
|
135
|
+
- **Discovery link relations** in `http-headers`: `describedby` (llms.txt v2), `api-catalog` (RFC 9727), `service-desc` and `service-doc` (RFC 8631), `ai-catalog`, `c2pa-manifest`, `license`, markdown alternates, and the `X-Llms-Txt` header.
|
|
136
|
+
- **Content parity** in `agent-access` now fingerprints title, h1 and JSON-LD block count, catching cloaking that preserves word count.
|
|
137
|
+
- **Current well-known files** reported but never scored: the Web Bot Auth key directory, TDMRep, the OpenAI Apps verification token, `AGENTS.md`.
|
|
138
|
+
- **Fetcher transport options**: `method: 'HEAD'` and `redirect: 'manual'`, with `elapsedMs`, `redirected` and `redirectLocation` on every response.
|
|
139
|
+
- **Check categories** (content / discovery / access / policy / protocols) and `CheckResult.applicable` for N/A reporting, both consumed by reporters in 3.8.
|
|
140
|
+
- **`docs/roadmap.md`**: the research behind this release and the plan through 4.0.
|
|
141
|
+
|
|
142
|
+
### Changed
|
|
143
|
+
|
|
144
|
+
- **robots.txt is parsed once.** `checks/robots-parser.ts` replaces three separate parsers that disagreed about what closes a User-agent group. Two parsing bugs fell out: `Disallow:` with an empty value now correctly means "allow all" per RFC 9309 §2.2.2 instead of being ignored, and a bot named in two groups is merged so a later `Disallow: /` is no longer hidden by an earlier `Allow: /`.
|
|
145
|
+
- **`well-known-ai` tells the truth.** Re-verification found three of its five scored files have no consumer: `/.well-known/nlweb.json` **does not exist** in any NLWeb release, document or commit (NLWeb exposes `/ask` and `/mcp`); `genai.txt` has no specification; `/ai-plugin.json` described ChatGPT plugins, shut down 2024-04-09. Omitting a retired format now reads as a pass with the evidence attached. The formula stays frozen for score stability; the check loses its weight and its retired probes in 4.0.
|
|
146
|
+
- **Renamed check ids keep working.** `CheckMeta.aliases` plus `src/check-ids.ts` resolve former ids in `--checks` selection and in baseline diffing, so a rename does not read as one check removed at a full regression and one added at zero.
|
|
147
|
+
|
|
148
|
+
### Scoring
|
|
149
|
+
|
|
150
|
+
Unchanged. `robots-txt` deducts against the frozen eight-token core set and the token list 3.6 recognised; a site whose only MCP document is the legacy manifest is validated with the pre-3.7 rules reproduced exactly; `well-known-ai` keeps its five-file formula; every finding added inside a weighted check is informational. Broadened discovery can only raise a score, never lower one. Both freezes are removed in 4.0.
|
|
151
|
+
|
|
152
|
+
### Tests
|
|
153
|
+
|
|
154
|
+
553 total, up from 301. New suites: robots parser, structured fields, crawler catalogue, WAF classification, check-id aliasing, well-known registry, SPA-shell guard, fetcher transport. Two classes of test exist specifically to hold the no-regression promise: score-stability tests asserting a 3.6 perfect configuration still scores 100, and informational-finding tests asserting new findings leave scores untouched.
|
|
155
|
+
|
|
5
156
|
## [3.6.0] - 2026-06-06
|
|
6
157
|
|
|
7
158
|
### Added
|
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
<h1 align="center">ax-audit</h1>
|
|
6
6
|
|
|
7
|
-
[](https://github.com/duranitech/ax-audit/actions/workflows/ci.yml)
|
|
8
8
|
[](https://www.npmjs.com/package/ax-audit)
|
|
9
|
-
[](https://github.com/
|
|
9
|
+
[](https://github.com/duranitech/ax-audit/blob/main/LICENSE)
|
|
10
10
|
[](https://nodejs.org)
|
|
11
11
|
|
|
12
12
|
**Lighthouse for AI Agents.** Audit any website's AI Agent Experience (AX) readiness in seconds.
|
|
@@ -17,47 +17,62 @@ npx ax-audit https://your-site.com
|
|
|
17
17
|
|
|
18
18
|
```
|
|
19
19
|
AX Audit Report
|
|
20
|
-
https://
|
|
20
|
+
https://example.com
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
█████████████████████████████████░░░░░░░ 83/100 Good
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
PASS /llms.txt exists
|
|
26
|
-
PASS /llms.txt Content-Type OK (text/plain)
|
|
27
|
-
PASS H1 heading: "Lucio Duran — Personal Portfolio"
|
|
24
|
+
── Content — is there substance an agent can read?
|
|
28
25
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
Structured Data (90/100)
|
|
27
|
+
PASS 3 JSON-LD block(s) found
|
|
28
|
+
WARN 1 structured-data value(s) do not appear in the visible text
|
|
29
|
+
💡 Google's one explicit requirement for structured data and AI
|
|
30
|
+
features is that it match what a reader sees.
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
── Access — can an agent actually retrieve it?
|
|
33
|
+
|
|
34
|
+
Agent Access (100/100)
|
|
35
|
+
PASS All 10 core AI crawler user-agents receive the same page as a regular client
|
|
36
|
+
|
|
37
|
+
AI Directives (100/100)
|
|
38
|
+
PASS Homepage is indexable
|
|
39
|
+
PASS No directive restricts how AI assistants may use this page
|
|
40
|
+
|
|
41
|
+
── Protocols — what can an agent call?
|
|
42
|
+
|
|
43
|
+
MCP Discovery (n/a)
|
|
44
|
+
PASS No MCP server — MCP discovery does not apply to this site
|
|
36
45
|
...
|
|
37
46
|
```
|
|
38
47
|
|
|
39
48
|
## Why
|
|
40
49
|
|
|
41
|
-
AI agents
|
|
50
|
+
AI agents crawl, cite and act on websites. Lighthouse audits performance, axe-core audits accessibility, and **ax-audit** tells you how ready your site is for agents — discovery files, crawler policy, usage rights, content negotiation, and the failures that are invisible from the inside:
|
|
51
|
+
|
|
52
|
+
- Your robots.txt allows GPTBot and your firewall returns 403 to it.
|
|
53
|
+
- You blocked `Google-Extended` expecting to leave AI Overviews. It does not do that.
|
|
54
|
+
- Your robots.txt permits AI training while your RSL licence prohibits it, so which terms apply depends on which file a crawler read.
|
|
55
|
+
- Your content only exists after hydration, so the crawlers that do not run JavaScript see an empty page.
|
|
56
|
+
- A missing page answers `200 OK`, so an agent stores the apology as the answer.
|
|
42
57
|
|
|
43
58
|
## What it checks
|
|
44
59
|
|
|
45
|
-
|
|
60
|
+
26 checks across five areas. Full reference: **[docs/checks.md](docs/checks.md)**.
|
|
46
61
|
|
|
47
|
-
|
|
|
48
|
-
|
|
49
|
-
|
|
|
50
|
-
|
|
|
51
|
-
|
|
|
52
|
-
|
|
|
53
|
-
|
|
|
54
|
-
|
|
|
55
|
-
|
|
56
|
-
|
|
62
|
+
| Area | Weight | Checks |
|
|
63
|
+
|---|---|---|
|
|
64
|
+
| **Content** — is there substance an agent can read? | 33% | HTML Rendering · Agent Operability · Structured Data · SEO Basics · Content Negotiation |
|
|
65
|
+
| **Access** — can an agent actually retrieve it? | 24% | Agent Access · AI Directives · HTTP Hygiene · TLS/HTTPS · Crawl Efficiency |
|
|
66
|
+
| **Discovery** — can an agent find your machine-readable files? | 21% | Robots.txt · LLMs.txt · HTTP Headers · Sitemap · Meta Tags |
|
|
67
|
+
| **Protocols** — what can an agent call? | 13% | API Discovery · Agent Card · MCP Discovery · Agent Skills · Auth Discovery |
|
|
68
|
+
| **Policy** — what usage rights do you declare? | 9% | Usage Policy · Security.txt · RSL License |
|
|
69
|
+
| Draft specifications, reported but never scored | 0% | AI Catalog · WebMCP · Commerce Discovery |
|
|
70
|
+
|
|
71
|
+
Content leads because the failure that breaks the most agents is a page with nothing in its HTML. Most crawlers do not run JavaScript, so a site whose content appears only after hydration is invisible to them no matter how many discovery files it publishes.
|
|
57
72
|
|
|
58
|
-
|
|
73
|
+
Protocol checks are **conditional**: a blog has no API to describe, so those report `n/a` and are excluded from the score rather than counted as failures. Use `--profile` to audit against what a site intends to build.
|
|
59
74
|
|
|
60
|
-
Every finding links to a step-by-step **[remediation guide](https://
|
|
75
|
+
Every finding links to a step-by-step **[remediation guide](https://axrush.com/guides)**.
|
|
61
76
|
|
|
62
77
|
## Usage
|
|
63
78
|
|
|
@@ -67,6 +82,9 @@ ax-audit https://a.com https://b.com --concurrency 2 # batch, in parallel
|
|
|
67
82
|
ax-audit https://example.com --output markdown # also: json, html
|
|
68
83
|
ax-audit https://example.com --checks llms-txt,rsl # subset of checks
|
|
69
84
|
ax-audit https://example.com --only-failures # hide passing findings
|
|
85
|
+
ax-audit https://example.com --category access # one area only
|
|
86
|
+
ax-audit https://example.com --profile api # audit as though it had an API
|
|
87
|
+
ax-audit https://example.com --fail-on-category access:70 # per-area CI gate
|
|
70
88
|
ax-audit https://example.com --baseline .ax-baseline.json --fail-on-regression 5
|
|
71
89
|
```
|
|
72
90
|
|
|
@@ -97,15 +115,15 @@ Reference:
|
|
|
97
115
|
|
|
98
116
|
| Document | Contents |
|
|
99
117
|
|---|---|
|
|
100
|
-
| [docs/checks.md](docs/checks.md) | All
|
|
101
|
-
| [docs/cli.md](docs/cli.md) | Every flag,
|
|
118
|
+
| [docs/checks.md](docs/checks.md) | All 26 checks with **exact scoring** per finding, the weight table, and the conditional-check rule |
|
|
119
|
+
| [docs/cli.md](docs/cli.md) | Every flag, profiles, area filters, per-area CI gates, baseline workflow |
|
|
102
120
|
| [docs/api.md](docs/api.md) | `audit`, `batchAudit`, baselines, reporters, types, API-stability policy |
|
|
103
121
|
| [docs/ci.md](docs/ci.md) | GitHub Actions recipes: gates, PR comments, scheduled drift detection |
|
|
104
122
|
| [docs/architecture.md](docs/architecture.md) | Pipeline design, check anatomy, how to add a check, scoring policy |
|
|
105
123
|
| [docs/faq.md](docs/faq.md) | Troubleshooting, false positives, the `agent-access` verified-bots caveat |
|
|
106
|
-
| [Remediation guides](https://
|
|
124
|
+
| [Remediation guides](https://axrush.com/guides) | Step-by-step fixes for every finding |
|
|
107
125
|
|
|
108
|
-
The same documentation is browsable at [
|
|
126
|
+
The same documentation is browsable at [axrush.com/docs](https://axrush.com/docs), rendered from these files. Contributors: see [CONTRIBUTING.md](CONTRIBUTING.md) and [SECURITY.md](SECURITY.md).
|
|
109
127
|
|
|
110
128
|
## Scoring
|
|
111
129
|
|
|
@@ -116,9 +134,13 @@ The same documentation is browsable at [lucioduran.com/projects/ax-audit/docs](h
|
|
|
116
134
|
| Fair | 50–69 | `1` |
|
|
117
135
|
| Poor | 0–49 | `1` |
|
|
118
136
|
|
|
137
|
+
Checks that do not apply to a site report **n/a** and leave the denominator entirely, rather than scoring 0. A blog is not marked down for having no API to describe. Everything counted against a site is something the site could have done — which is what makes a low score worth acting on.
|
|
138
|
+
|
|
139
|
+
`--fail-on-category access:70` gates CI per area, because an overall score can hide an area that is entirely broken while the other four carry it.
|
|
140
|
+
|
|
119
141
|
## Tech
|
|
120
142
|
|
|
121
|
-
TypeScript strict mode · 2 runtime dependencies (`chalk`, `commander`) · Node 18+ built-in `fetch` · parallel checks via `Promise.allSettled` · per-run request cache with `Vary`-aware keys · transient-failure retries with backoff ·
|
|
143
|
+
TypeScript strict mode · 2 runtime dependencies (`chalk`, `commander`) · Node 18+ built-in `fetch` · no HTML, XML or YAML parser dependencies · parallel checks via `Promise.allSettled` · per-run request cache with `Vary`-aware keys · transient-failure retries with backoff · 873 tests on `node:test` with zero test dependencies.
|
|
122
144
|
|
|
123
145
|
## Contributing
|
|
124
146
|
|
|
@@ -126,8 +148,8 @@ Contributions are welcome — see **[docs/architecture.md](docs/architecture.md)
|
|
|
126
148
|
|
|
127
149
|
## Related
|
|
128
150
|
|
|
129
|
-
- **[ax-init](https://github.com/
|
|
130
|
-
- **[ax-cite](https://github.com/
|
|
151
|
+
- **[ax-init](https://github.com/duranitech/ax-init)** — generate the AX files this tool audits
|
|
152
|
+
- **[ax-cite](https://github.com/duranitech/ax-cite)** — embed AI-extractable structured data in your pages
|
|
131
153
|
|
|
132
154
|
## License
|
|
133
155
|
|
|
@@ -135,4 +157,4 @@ Contributions are welcome — see **[docs/architecture.md](docs/architecture.md)
|
|
|
135
157
|
|
|
136
158
|
---
|
|
137
159
|
|
|
138
|
-
|
|
160
|
+
**[AX Rush](https://axrush.com)** — the agent-experience toolkit, by [Durani Technologies](https://github.com/duranitech).
|
package/dist/baseline.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ import type { AuditReport, BaselineData, BaselineDiff } from './types.js';
|
|
|
3
3
|
* Extract a minimal, stable snapshot from an AuditReport suitable for
|
|
4
4
|
* persistence and future comparison.
|
|
5
5
|
*/
|
|
6
|
+
/** Scoring model this build produces. Bumped whenever weights or the scoring rules change. */
|
|
7
|
+
export declare const BASELINE_SCHEMA_VERSION = 2;
|
|
6
8
|
export declare function toBaselineData(report: AuditReport): BaselineData;
|
|
7
9
|
/**
|
|
8
10
|
* Persist a baseline to disk as pretty-printed JSON.
|
package/dist/baseline.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../src/baseline.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../src/baseline.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAa,MAAM,YAAY,CAAC;AAErF;;;GAGG;AACH,8FAA8F;AAC9F,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAEzC,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,YAAY,CAiBhE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,IAAI,CAIpE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAwBvD;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,GAAG,YAAY,CAkEtF"}
|
package/dist/baseline.js
CHANGED
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
2
2
|
import { dirname } from 'node:path';
|
|
3
|
+
import { checks as allChecks } from './checks/index.js';
|
|
4
|
+
import { allIdsFor } from './check-ids.js';
|
|
3
5
|
/**
|
|
4
6
|
* Extract a minimal, stable snapshot from an AuditReport suitable for
|
|
5
7
|
* persistence and future comparison.
|
|
6
8
|
*/
|
|
9
|
+
/** Scoring model this build produces. Bumped whenever weights or the scoring rules change. */
|
|
10
|
+
export const BASELINE_SCHEMA_VERSION = 2;
|
|
7
11
|
export function toBaselineData(report) {
|
|
8
12
|
const checks = {};
|
|
13
|
+
const notApplicable = [];
|
|
9
14
|
for (const r of report.results) {
|
|
10
15
|
checks[r.id] = r.score;
|
|
16
|
+
if (r.applicable === false)
|
|
17
|
+
notApplicable.push(r.id);
|
|
11
18
|
}
|
|
12
19
|
return {
|
|
20
|
+
schemaVersion: BASELINE_SCHEMA_VERSION,
|
|
13
21
|
url: report.url,
|
|
14
22
|
timestamp: report.timestamp,
|
|
15
23
|
overallScore: report.overallScore,
|
|
16
24
|
checks,
|
|
25
|
+
...(notApplicable.length > 0 ? { notApplicable } : {}),
|
|
17
26
|
};
|
|
18
27
|
}
|
|
19
28
|
/**
|
|
@@ -58,19 +67,39 @@ export function loadBaseline(path) {
|
|
|
58
67
|
* per-check deltas and overall regression/improvement lists.
|
|
59
68
|
*/
|
|
60
69
|
export function diffBaseline(baseline, report) {
|
|
70
|
+
// A baseline saved before a check was renamed still holds the former id, so
|
|
71
|
+
// look each score up under every id that check answers to. Without this a
|
|
72
|
+
// rename reads as one check removed (a full regression) and one added at
|
|
73
|
+
// zero, which would fire `--fail-on-regression` on an unchanged site.
|
|
74
|
+
const consumedIds = new Set();
|
|
75
|
+
const previousScoreFor = (id) => {
|
|
76
|
+
for (const candidate of idsForCheck(id)) {
|
|
77
|
+
if (Object.hasOwn(baseline.checks, candidate)) {
|
|
78
|
+
consumedIds.add(candidate);
|
|
79
|
+
return baseline.checks[candidate];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return undefined;
|
|
83
|
+
};
|
|
84
|
+
const wasNotApplicable = new Set(baseline.notApplicable ?? []);
|
|
61
85
|
const checks = report.results.map((r) => {
|
|
62
|
-
const previous =
|
|
86
|
+
const previous = previousScoreFor(r.id) ?? 0;
|
|
87
|
+
const isNotApplicable = r.applicable === false;
|
|
88
|
+
// A check that is N/A on one side has no comparable score, so its delta
|
|
89
|
+
// reflects a change in what the site offers rather than in its quality.
|
|
90
|
+
const applicabilityChanged = isNotApplicable !== wasNotApplicable.has(r.id);
|
|
63
91
|
return {
|
|
64
92
|
id: r.id,
|
|
65
93
|
name: r.name,
|
|
66
94
|
previous,
|
|
67
95
|
current: r.score,
|
|
68
96
|
delta: r.score - previous,
|
|
97
|
+
...(applicabilityChanged ? { applicabilityChanged: true } : {}),
|
|
69
98
|
};
|
|
70
99
|
});
|
|
71
100
|
// Include checks that existed in the baseline but were removed from the current run
|
|
72
101
|
for (const [id, score] of Object.entries(baseline.checks)) {
|
|
73
|
-
if (!checks.some((c) => c.id === id)) {
|
|
102
|
+
if (!consumedIds.has(id) && !checks.some((c) => c.id === id)) {
|
|
74
103
|
checks.push({
|
|
75
104
|
id,
|
|
76
105
|
name: id, // no human-readable name available for removed checks
|
|
@@ -81,7 +110,9 @@ export function diffBaseline(baseline, report) {
|
|
|
81
110
|
}
|
|
82
111
|
}
|
|
83
112
|
const overallDelta = report.overallScore - baseline.overallScore;
|
|
113
|
+
const scoringModelChanged = (baseline.schemaVersion ?? 1) !== BASELINE_SCHEMA_VERSION;
|
|
84
114
|
return {
|
|
115
|
+
...(scoringModelChanged ? { scoringModelChanged: true } : {}),
|
|
85
116
|
url: report.url,
|
|
86
117
|
baselineTimestamp: baseline.timestamp,
|
|
87
118
|
currentTimestamp: report.timestamp,
|
|
@@ -89,11 +120,18 @@ export function diffBaseline(baseline, report) {
|
|
|
89
120
|
overallCurrent: report.overallScore,
|
|
90
121
|
overallDelta,
|
|
91
122
|
checks,
|
|
92
|
-
|
|
93
|
-
|
|
123
|
+
// A regression must be something the site did. A check that changed
|
|
124
|
+
// applicability, or a baseline from a different scoring model, is neither.
|
|
125
|
+
regressions: scoringModelChanged ? [] : checks.filter((c) => c.delta < 0 && !c.applicabilityChanged),
|
|
126
|
+
improvements: checks.filter((c) => c.delta > 0 && !c.applicabilityChanged),
|
|
94
127
|
};
|
|
95
128
|
}
|
|
96
129
|
/* ── Internal helpers ─────────────────────────────────────── */
|
|
130
|
+
/** Current id plus every former id a check answers to, for baseline lookup. */
|
|
131
|
+
function idsForCheck(id) {
|
|
132
|
+
const meta = allChecks.find((c) => c.meta.id === id)?.meta;
|
|
133
|
+
return meta === undefined ? [id] : allIdsFor(meta);
|
|
134
|
+
}
|
|
97
135
|
function isBaselineData(value) {
|
|
98
136
|
if (typeof value !== 'object' || value === null)
|
|
99
137
|
return false;
|
package/dist/baseline.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"baseline.js","sourceRoot":"","sources":["../src/baseline.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACjE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"baseline.js","sourceRoot":"","sources":["../src/baseline.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { CheckMeta } from './types.js';
|
|
2
|
+
/** Map of alias → current id, built from the registered checks. */
|
|
3
|
+
export declare function buildAliasMap(metas?: CheckMeta[]): Map<string, string>;
|
|
4
|
+
/**
|
|
5
|
+
* Resolve a possibly-former check id to its current one. Unknown ids are
|
|
6
|
+
* returned unchanged so callers can report them as invalid with the name the
|
|
7
|
+
* user actually typed.
|
|
8
|
+
*/
|
|
9
|
+
export declare function resolveCheckId(id: string, metas?: CheckMeta[]): string;
|
|
10
|
+
/** Every id a check answers to, current name first. */
|
|
11
|
+
export declare function allIdsFor(meta: CheckMeta): string[];
|
|
12
|
+
/** Every selectable id across all checks, aliases included. */
|
|
13
|
+
export declare function allSelectableIds(metas?: CheckMeta[]): string[];
|
|
14
|
+
/**
|
|
15
|
+
* Whether a check should run given a `--checks` selection, matching on its
|
|
16
|
+
* current id or any alias.
|
|
17
|
+
*/
|
|
18
|
+
export declare function isSelected(meta: CheckMeta, selection: string[]): boolean;
|
|
19
|
+
//# sourceMappingURL=check-ids.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check-ids.d.ts","sourceRoot":"","sources":["../src/check-ids.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,mEAAmE;AACnE,wBAAgB,aAAa,CAAC,KAAK,GAAE,SAAS,EAAiC,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAQpG;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,SAAS,EAAE,GAAG,MAAM,CAKtE;AAED,uDAAuD;AACvD,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,EAAE,CAEnD;AAED,+DAA+D;AAC/D,wBAAgB,gBAAgB,CAAC,KAAK,GAAE,SAAS,EAAiC,GAAG,MAAM,EAAE,CAE5F;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAGxE"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check-id resolution across renames.
|
|
3
|
+
*
|
|
4
|
+
* Check ids are a public interface twice over: they appear in `--checks`
|
|
5
|
+
* invocations that live in CI configs, and as keys in saved baseline files. So
|
|
6
|
+
* a rename cannot be a silent break — `--checks mcp` must keep working, and a
|
|
7
|
+
* baseline recorded when the check was called `mcp` must still compare against
|
|
8
|
+
* the check now called `mcp-discovery` rather than reporting one check removed
|
|
9
|
+
* and another added.
|
|
10
|
+
*
|
|
11
|
+
* A check declares its former names in `meta.aliases`; everything here reads
|
|
12
|
+
* from that, so adding an alias is a one-line change on the check itself.
|
|
13
|
+
*/
|
|
14
|
+
import { checks as allChecks } from './checks/index.js';
|
|
15
|
+
/** Map of alias → current id, built from the registered checks. */
|
|
16
|
+
export function buildAliasMap(metas = allChecks.map((c) => c.meta)) {
|
|
17
|
+
const map = new Map();
|
|
18
|
+
for (const meta of metas) {
|
|
19
|
+
for (const alias of meta.aliases ?? []) {
|
|
20
|
+
map.set(alias.toLowerCase(), meta.id);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return map;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Resolve a possibly-former check id to its current one. Unknown ids are
|
|
27
|
+
* returned unchanged so callers can report them as invalid with the name the
|
|
28
|
+
* user actually typed.
|
|
29
|
+
*/
|
|
30
|
+
export function resolveCheckId(id, metas) {
|
|
31
|
+
const current = metas ?? allChecks.map((c) => c.meta);
|
|
32
|
+
const lower = id.toLowerCase();
|
|
33
|
+
if (current.some((m) => m.id.toLowerCase() === lower))
|
|
34
|
+
return current.find((m) => m.id.toLowerCase() === lower).id;
|
|
35
|
+
return buildAliasMap(current).get(lower) ?? id;
|
|
36
|
+
}
|
|
37
|
+
/** Every id a check answers to, current name first. */
|
|
38
|
+
export function allIdsFor(meta) {
|
|
39
|
+
return [meta.id, ...(meta.aliases ?? [])];
|
|
40
|
+
}
|
|
41
|
+
/** Every selectable id across all checks, aliases included. */
|
|
42
|
+
export function allSelectableIds(metas = allChecks.map((c) => c.meta)) {
|
|
43
|
+
return metas.flatMap(allIdsFor);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Whether a check should run given a `--checks` selection, matching on its
|
|
47
|
+
* current id or any alias.
|
|
48
|
+
*/
|
|
49
|
+
export function isSelected(meta, selection) {
|
|
50
|
+
const wanted = new Set(selection.map((s) => s.toLowerCase()));
|
|
51
|
+
return allIdsFor(meta).some((id) => wanted.has(id.toLowerCase()));
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=check-ids.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check-ids.js","sourceRoot":"","sources":["../src/check-ids.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,MAAM,IAAI,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAGxD,mEAAmE;AACnE,MAAM,UAAU,aAAa,CAAC,QAAqB,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAC7E,MAAM,GAAG,GAAG,IAAI,GAAG,EAAkB,CAAC;IACtC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;YACvC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,EAAU,EAAE,KAAmB;IAC5D,MAAM,OAAO,GAAG,KAAK,IAAI,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACtD,MAAM,KAAK,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC;IAC/B,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,WAAW,EAAE,KAAK,KAAK,CAAC;QAAE,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,WAAW,EAAE,KAAK,KAAK,CAAE,CAAC,EAAE,CAAC;IACpH,OAAO,aAAa,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;AACjD,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,SAAS,CAAC,IAAe;IACvC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC;AAC5C,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,gBAAgB,CAAC,QAAqB,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAChF,OAAO,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,IAAe,EAAE,SAAmB;IAC7D,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IAC9D,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;AACpE,CAAC"}
|
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
import type { CheckContext, CheckResult, CheckMeta } from '../types.js';
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* "agent-access" — the gap between what robots.txt permits and what the server
|
|
4
|
+
* actually does.
|
|
5
|
+
*
|
|
6
|
+
* The failure this catches is invisible from the inside: robots.txt says
|
|
7
|
+
* `Allow: /` for GPTBot, and the WAF returns 403 to anything whose user agent
|
|
8
|
+
* says GPTBot. The operator sees a permissive robots.txt and assumes the site
|
|
9
|
+
* is reachable. Cloudflare's "Block AI Crawlers" toggle produces exactly this,
|
|
10
|
+
* and so does a hand-written rule that outlived its reason.
|
|
11
|
+
*
|
|
12
|
+
* The check probes the homepage once per core crawler and compares the result
|
|
13
|
+
* against the default-user-agent baseline. Since 3.7 it classifies *how* a
|
|
14
|
+
* request was turned away, because the remedies are completely different: a
|
|
15
|
+
* JavaScript challenge needs a bot-management exception, a hard 403 needs a
|
|
16
|
+
* firewall rule change, a 402 is a deliberate price, and a signature demand is
|
|
17
|
+
* the site working as designed.
|
|
18
|
+
*
|
|
19
|
+
* Honesty constraint: this probe is unsigned and comes from the auditor's own
|
|
20
|
+
* network. An edge that verifies crawlers by IP range or Web Bot Auth signature
|
|
21
|
+
* will reject it while admitting the genuine crawler. Those outcomes are scored
|
|
22
|
+
* as inconclusive and labelled as such, never as "blocks AI crawlers".
|
|
23
|
+
*/
|
|
3
24
|
export declare const meta: CheckMeta;
|
|
4
25
|
/**
|
|
5
26
|
* Build a realistic crawler User-Agent for a given bot token. WAF and
|
|
@@ -7,10 +28,6 @@ export declare const meta: CheckMeta;
|
|
|
7
28
|
* to trigger the same code path the real crawler would hit.
|
|
8
29
|
*/
|
|
9
30
|
export declare function crawlerUserAgent(token: string): string;
|
|
10
|
-
|
|
11
|
-
* Whether robots.txt expresses the intent to block this crawler: an explicit
|
|
12
|
-
* full Disallow for it, or a full wildcard Disallow with no explicit entry.
|
|
13
|
-
*/
|
|
14
|
-
export declare function intentBlocked(entries: BotEntry[], crawler: string): boolean;
|
|
31
|
+
export { intentBlocked } from './robots-parser.js';
|
|
15
32
|
export default function check(ctx: CheckContext): Promise<CheckResult>;
|
|
16
33
|
//# sourceMappingURL=agent-access.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-access.d.ts","sourceRoot":"","sources":["../../src/checks/agent-access.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,SAAS,EAAW,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"agent-access.d.ts","sourceRoot":"","sources":["../../src/checks/agent-access.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,SAAS,EAAW,MAAM,aAAa,CAAC;AAMjF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,IAAI,EAAE,SAKlB,CAAC;AA+BF;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEtD;AAGD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAoCnD,wBAA8B,KAAK,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CA8E3E"}
|