ax-audit 2.4.0 → 3.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +39 -17
  3. package/dist/checks/agent-json.d.ts +10 -0
  4. package/dist/checks/agent-json.d.ts.map +1 -1
  5. package/dist/checks/agent-json.js +66 -2
  6. package/dist/checks/agent-json.js.map +1 -1
  7. package/dist/checks/html-rendering.d.ts +21 -0
  8. package/dist/checks/html-rendering.d.ts.map +1 -0
  9. package/dist/checks/html-rendering.js +204 -0
  10. package/dist/checks/html-rendering.js.map +1 -0
  11. package/dist/checks/html-utils.d.ts +37 -0
  12. package/dist/checks/html-utils.d.ts.map +1 -0
  13. package/dist/checks/html-utils.js +93 -0
  14. package/dist/checks/html-utils.js.map +1 -0
  15. package/dist/checks/http-headers.js +1 -1
  16. package/dist/checks/http-headers.js.map +1 -1
  17. package/dist/checks/index.d.ts.map +1 -1
  18. package/dist/checks/index.js +10 -0
  19. package/dist/checks/index.js.map +1 -1
  20. package/dist/checks/llms-txt.d.ts.map +1 -1
  21. package/dist/checks/llms-txt.js +17 -2
  22. package/dist/checks/llms-txt.js.map +1 -1
  23. package/dist/checks/mcp.d.ts.map +1 -1
  24. package/dist/checks/mcp.js +11 -2
  25. package/dist/checks/mcp.js.map +1 -1
  26. package/dist/checks/meta-tags.d.ts +13 -0
  27. package/dist/checks/meta-tags.d.ts.map +1 -1
  28. package/dist/checks/meta-tags.js +104 -24
  29. package/dist/checks/meta-tags.js.map +1 -1
  30. package/dist/checks/openapi.d.ts.map +1 -1
  31. package/dist/checks/openapi.js +11 -2
  32. package/dist/checks/openapi.js.map +1 -1
  33. package/dist/checks/robots-txt.js +1 -1
  34. package/dist/checks/security-txt.js +1 -1
  35. package/dist/checks/seo-basics.d.ts +13 -0
  36. package/dist/checks/seo-basics.d.ts.map +1 -0
  37. package/dist/checks/seo-basics.js +222 -0
  38. package/dist/checks/seo-basics.js.map +1 -0
  39. package/dist/checks/sitemap.d.ts +12 -0
  40. package/dist/checks/sitemap.d.ts.map +1 -0
  41. package/dist/checks/sitemap.js +241 -0
  42. package/dist/checks/sitemap.js.map +1 -0
  43. package/dist/checks/structured-data.js +1 -1
  44. package/dist/checks/structured-data.js.map +1 -1
  45. package/dist/checks/tls-https.d.ts +13 -0
  46. package/dist/checks/tls-https.d.ts.map +1 -0
  47. package/dist/checks/tls-https.js +164 -0
  48. package/dist/checks/tls-https.js.map +1 -0
  49. package/dist/checks/utils.d.ts +13 -1
  50. package/dist/checks/utils.d.ts.map +1 -1
  51. package/dist/checks/utils.js +28 -0
  52. package/dist/checks/utils.js.map +1 -1
  53. package/dist/checks/well-known-ai.d.ts +17 -0
  54. package/dist/checks/well-known-ai.d.ts.map +1 -0
  55. package/dist/checks/well-known-ai.js +123 -0
  56. package/dist/checks/well-known-ai.js.map +1 -0
  57. package/dist/constants.d.ts +19 -0
  58. package/dist/constants.d.ts.map +1 -1
  59. package/dist/constants.js +47 -10
  60. package/dist/constants.js.map +1 -1
  61. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,40 @@
2
2
 
3
3
  All notable changes to ax-audit are documented here.
4
4
 
5
+ ## [3.0.0] - 2026-04-30
6
+
7
+ ### Added — five new checks (full agent-optimization coverage)
8
+
9
+ - **html-rendering** (weight 9%): detects whether the static HTML response actually contains content, since most AI crawlers (GPTBot, ClaudeBot, CCBot, …) do not execute JavaScript. Heuristics: text length, word count, text-to-markup ratio, empty SPA mount points (`#root`, `#__next`, `#__nuxt`, `#app`, `#svelte`, `#gatsby`), semantic landmarks (`<main>`, `<article>`, `<header>`, `<footer>`, `<nav>`), single `<h1>`, `<noscript>` fallback, and `<img alt>` coverage.
10
+ - **sitemap** (weight 4%): locates the sitemap via `robots.txt` `Sitemap:` directive or `/sitemap.xml`, validates XML shape, parses `<urlset>` and `<sitemapindex>`, samples child sitemaps from indexes, scores `<lastmod>` coverage and freshness (>365d → stale), enforces 50k-URL / 50MB limits.
11
+ - **seo-basics** (weight 7%): `<title>` length 20–70, `<meta name="description">` length 70–160, `<link rel="canonical">` (absolute, single), `<html lang>` (BCP 47), `<meta charset="utf-8">`, `<meta name="viewport">`, hreflang completeness with `x-default`. Title/description duplication detection.
12
+ - **tls-https** (weight 5%): site is served over HTTPS, HTTP redirects to HTTPS, HSTS `max-age` >= 6 months (1 year for preload), `includeSubDomains`, `preload` directive eligibility per https://hstspreload.org.
13
+ - **well-known-ai** (weight 3%): emerging AI-specific discovery files — `/.well-known/ai.txt` (Spawning), `/.well-known/genai.txt`, `/ai-plugin.json` (legacy ChatGPT plugin), `/agents.json` (Wildcard / OpenAgents), `/.well-known/nlweb.json` (Microsoft NLWeb). Each present file scores; coverage is bonus rather than baseline.
14
+
15
+ ### Improved — existing checks
16
+
17
+ - **meta-tags**: now validates Open Graph completeness (`og:title`, `og:description`, `og:url`, `og:type`, `og:image`, `og:site_name`) and Twitter Card completeness (`twitter:card`, `twitter:title`, `twitter:description`, `twitter:image`). Reuses shared HTML utilities for tag matching.
18
+ - **agent-json**: validates the `url` field is absolute and matches the audited origin, and that every `skills[]` entry has both `id` and `description`.
19
+ - **llms-txt / agent-json / mcp / openapi**: validate `Content-Type` of the fetched resource (`text/plain` / `text/markdown` for llms.txt; `application/json` for the JSON manifests). Penalty: −5 per mismatch.
20
+ - **robots-txt**: `CORE_AI_CRAWLERS` extended (now 8 entries: GPTBot, ClaudeBot, ChatGPT-User, Claude-SearchBot, Google-Extended, PerplexityBot, OAI-SearchBot, CCBot). `ALL_AI_CRAWLERS` extended with MistralAI-User, KagiBot, GeminiBot, Goose, AwarioBot family, Bingbot, ImagesiftBot, omgili, Webzio-Extended, and others (47 known crawlers total).
21
+
22
+ ### Refactored
23
+
24
+ - New shared module `src/checks/html-utils.ts` with regex-based primitives for HTML inspection (`getMetaContent`, `findLinkTags`, `findMetaTagsByPrefix`, `extractVisibleText`, `countExecutableScripts`, `getTagAttribute`, …). Eliminates duplicated regex code across `meta-tags`, `seo-basics`, `html-rendering`, and `structured-data`.
25
+ - New shared utility `checkContentType` in `src/checks/utils.ts` for consistent Content-Type validation.
26
+
27
+ ### Scoring
28
+
29
+ - Weights redistributed across 14 checks, total still sums to 100. New highest-weight signals are llms-txt and robots-txt (11% each) followed by html-rendering / structured-data / http-headers (9%).
30
+
31
+ ### Tests
32
+
33
+ - 198 tests total (77 new). New suites: html-rendering (14), sitemap (12), seo-basics (19), tls-https (11), well-known-ai (8). Plus expanded meta-tags / agent-json / mcp / openapi / llms-txt suites for the new validations.
34
+
35
+ ### Breaking
36
+
37
+ - Score deltas vs v2.x are expected on the same site because (a) weights were redistributed across 14 checks instead of 9, and (b) Content-Type validation on `/llms.txt` and the `.well-known` JSON manifests now applies a −5 penalty per mismatch. Sites previously scoring 100 may drop a few points until the new signals are addressed. Use `--baseline` to track regressions explicitly.
38
+
5
39
  ## [2.4.0] - 2026-04-16
6
40
 
7
41
  ### Added
package/README.md CHANGED
@@ -19,16 +19,28 @@ npx ax-audit https://your-site.com
19
19
  AX Audit Report
20
20
  https://lucioduran.com
21
21
 
22
- ███████████████████████████████████████░ 98/100 Excellent
22
+ ███████████████████████████████████░░░░░ 88/100 Good
23
23
 
24
24
  LLMs.txt (100/100)
25
25
  PASS /llms.txt exists
26
+ PASS /llms.txt Content-Type OK (text/plain)
26
27
  PASS H1 heading: "Lucio Duran — Personal Portfolio"
27
28
  PASS /llms-full.txt also available (bonus)
28
29
 
29
30
  Robots.txt (100/100)
30
- PASS All 6 core AI crawlers explicitly configured
31
- PASS 31/31 known AI crawlers have explicit rules
31
+ PASS All 8 core AI crawlers explicitly configured
32
+ PASS 32/47 known AI crawlers have explicit rules
33
+
34
+ HTML Rendering (90/100)
35
+ PASS Server-rendered content detected (473 words)
36
+ PASS Semantic landmarks present (main, article, header, footer, nav)
37
+ PASS Single <h1> heading
38
+ PASS 3/3 <img> tags have alt attributes
39
+
40
+ TLS / HTTPS (100/100)
41
+ PASS Site is served over HTTPS
42
+ PASS HTTP requests redirect to HTTPS
43
+ PASS HSTS preload-eligible
32
44
  ...
33
45
  ```
34
46
 
@@ -40,15 +52,20 @@ AI agents and LLMs are increasingly crawling, indexing, and interacting with web
40
52
 
41
53
  | Check | What it audits | Weight |
42
54
  |---|---|---|
43
- | **LLMs.txt** | `/llms.txt` presence and [llmstxt.org](https://llmstxt.org) spec compliance | 15% |
44
- | **Robots.txt** | AI crawler configuration, wildcard detection, partial path restrictions | 15% |
45
- | **Structured Data** | JSON-LD on homepage (schema.org, `@graph`, entity types) | 13% |
46
- | **HTTP Headers** | Security headers + AI discovery `Link` headers + CORS on `.well-known` | 13% |
47
- | **Agent Card** | `/.well-known/agent.json` [A2A protocol](https://a2a-protocol.org) compliance | 10% |
48
- | **MCP** | `/.well-known/mcp.json` [Model Context Protocol](https://modelcontextprotocol.io) server config | 10% |
49
- | **Security.txt** | `/.well-known/security.txt` [RFC 9116](https://www.rfc-editor.org/rfc/rfc9116) compliance | 8% |
50
- | **Meta Tags** | AI meta tags (`ai:*`), `rel="alternate"` to llms.txt, `rel="me"` identity links | 8% |
51
- | **OpenAPI** | `/.well-known/openapi.json` presence and schema validity | 8% |
55
+ | **LLMs.txt** | `/llms.txt` presence, [llmstxt.org](https://llmstxt.org) spec, Content-Type | 11% |
56
+ | **Robots.txt** | AI crawler configuration (40+ known crawlers), wildcard detection, partial path restrictions | 11% |
57
+ | **HTML Rendering** | Server-rendered content, semantic landmarks, SPA-shell detection, alt coverage | 9% |
58
+ | **Structured Data** | JSON-LD on homepage (schema.org, `@graph`, entity types) | 9% |
59
+ | **HTTP Headers** | Security headers + AI discovery `Link` headers + CORS on `.well-known` | 9% |
60
+ | **Agent Card** | `/.well-known/agent.json` [A2A protocol](https://a2a-protocol.org) + same-origin url + skill quality | 7% |
61
+ | **MCP** | `/.well-known/mcp.json` [Model Context Protocol](https://modelcontextprotocol.io) server config | 7% |
62
+ | **SEO Basics** | `<title>`, meta description, canonical, `<html lang>`, charset, viewport, hreflang | 7% |
63
+ | **Security.txt** | `/.well-known/security.txt` [RFC 9116](https://www.rfc-editor.org/rfc/rfc9116) compliance | 6% |
64
+ | **Meta Tags** | AI meta tags (`ai:*`), `rel="alternate"`, `rel="me"`, OpenGraph + Twitter Card completeness | 6% |
65
+ | **OpenAPI** | `/.well-known/openapi.json` presence, schema validity, Content-Type | 6% |
66
+ | **TLS / HTTPS** | HTTPS, HTTP→HTTPS redirect, HSTS with `preload` + `includeSubDomains` | 5% |
67
+ | **Sitemap** | `sitemap.xml` (or `Sitemap:` from robots.txt) — XML validity, `<lastmod>` coverage, freshness, sitemap-index handling | 4% |
68
+ | **AI Well-Known** | Emerging files: `/.well-known/ai.txt`, `genai.txt`, `ai-plugin.json`, `agents.json`, `nlweb.json` | 3% |
52
69
 
53
70
  ## Install
54
71
 
@@ -232,15 +249,20 @@ Fail on regressions using a committed baseline:
232
249
 
233
250
  | Check ID | Use with `--checks` |
234
251
  |---|---|
235
- | `llms-txt` | LLMs.txt spec compliance |
236
- | `robots-txt` | AI crawler configuration |
252
+ | `llms-txt` | LLMs.txt spec + Content-Type |
253
+ | `robots-txt` | AI crawler configuration (40+ crawlers) |
254
+ | `html-rendering` | SSR / SPA-shell detection + semantic HTML |
237
255
  | `structured-data` | JSON-LD structured data |
238
256
  | `http-headers` | Security + AI discovery headers |
239
- | `agent-json` | A2A Agent Card |
257
+ | `agent-json` | A2A Agent Card + same-origin validation |
240
258
  | `mcp` | MCP server configuration |
259
+ | `seo-basics` | title / description / canonical / lang / hreflang |
241
260
  | `security-txt` | RFC 9116 Security.txt |
242
- | `meta-tags` | AI meta tags and identity links |
261
+ | `meta-tags` | AI meta tags + OpenGraph + Twitter Card |
243
262
  | `openapi` | OpenAPI specification |
263
+ | `tls-https` | HTTPS + HTTP→HTTPS redirect + HSTS preload |
264
+ | `sitemap` | sitemap.xml validation + freshness |
265
+ | `well-known-ai` | Emerging AI discovery files |
244
266
 
245
267
  ## Testing
246
268
 
@@ -248,7 +270,7 @@ Fail on regressions using a committed baseline:
248
270
  npm test
249
271
  ```
250
272
 
251
- 121 tests covering all 9 checks, the scorer, baseline comparison, and edge cases. Uses Node.js built-in test runner (`node:test`), no extra test dependencies.
273
+ 198 tests covering all 14 checks, the scorer, baseline comparison, HTML parsing utilities, and edge cases. Uses Node.js built-in test runner (`node:test`), no extra test dependencies.
252
274
 
253
275
  ## Tech Stack
254
276
 
@@ -1,4 +1,14 @@
1
1
  import type { CheckContext, CheckResult, CheckMeta } from '../types.js';
2
+ /**
3
+ * "agent-json" — `/.well-known/agent.json` per the A2A (Agent-to-Agent) protocol.
4
+ *
5
+ * Validates the document on three axes:
6
+ * 1. JSON well-formedness
7
+ * 2. Required fields per the A2A spec (`name`, `description`, `url`, `skills`)
8
+ * 3. Field semantics: `url` should resolve to the same origin as the audited site,
9
+ * `skills[]` should each declare an `id` and `description`, and protocol/optional
10
+ * fields are present where expected.
11
+ */
2
12
  export declare const meta: CheckMeta;
3
13
  export default function check(ctx: CheckContext): Promise<CheckResult>;
4
14
  //# sourceMappingURL=agent-json.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"agent-json.d.ts","sourceRoot":"","sources":["../../src/checks/agent-json.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,SAAS,EAAW,MAAM,aAAa,CAAC;AAGjF,eAAO,MAAM,IAAI,EAAE,SAKlB,CAAC;AAEF,wBAA8B,KAAK,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CA+F3E"}
1
+ {"version":3,"file":"agent-json.d.ts","sourceRoot":"","sources":["../../src/checks/agent-json.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,SAAS,EAAW,MAAM,aAAa,CAAC;AAGjF;;;;;;;;;GASG;AACH,eAAO,MAAM,IAAI,EAAE,SAKlB,CAAC;AAEF,wBAA8B,KAAK,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CA0I3E"}
@@ -1,11 +1,21 @@
1
1
  import { AGENT_JSON_REQUIRED_FIELDS } from '../constants.js';
2
2
  import { guideUrl } from '../guide-urls.js';
3
- import { buildResult } from './utils.js';
3
+ import { buildResult, checkContentType } from './utils.js';
4
+ /**
5
+ * "agent-json" — `/.well-known/agent.json` per the A2A (Agent-to-Agent) protocol.
6
+ *
7
+ * Validates the document on three axes:
8
+ * 1. JSON well-formedness
9
+ * 2. Required fields per the A2A spec (`name`, `description`, `url`, `skills`)
10
+ * 3. Field semantics: `url` should resolve to the same origin as the audited site,
11
+ * `skills[]` should each declare an `id` and `description`, and protocol/optional
12
+ * fields are present where expected.
13
+ */
4
14
  export const meta = {
5
15
  id: 'agent-json',
6
16
  name: 'Agent Card (A2A)',
7
17
  description: 'Checks /.well-known/agent.json A2A protocol compliance',
8
- weight: 10,
18
+ weight: 7,
9
19
  };
10
20
  export default async function check(ctx) {
11
21
  const start = performance.now();
@@ -23,6 +33,15 @@ export default async function check(ctx) {
23
33
  return buildResult(meta, 0, findings, start);
24
34
  }
25
35
  findings.push({ status: 'pass', message: '/.well-known/agent.json exists' });
36
+ const ctFinding = checkContentType(res, ['application/json'], {
37
+ checkId: meta.id,
38
+ resourceLabel: '/.well-known/agent.json',
39
+ anchor: 'wrong-content-type',
40
+ });
41
+ if (ctFinding) {
42
+ findings.push(ctFinding);
43
+ score -= 5;
44
+ }
26
45
  let data;
27
46
  try {
28
47
  data = JSON.parse(res.body);
@@ -51,8 +70,42 @@ export default async function check(ctx) {
51
70
  score -= 15;
52
71
  }
53
72
  }
73
+ if (typeof data.url === 'string' && data.url.length > 0) {
74
+ const sameOrigin = sameHost(data.url, ctx.url);
75
+ if (sameOrigin === false) {
76
+ findings.push({
77
+ status: 'warn',
78
+ message: `agent.json "url" points to a different origin: ${data.url}`,
79
+ hint: 'The url field should match the audited site origin. Pointing it elsewhere can confuse agents about the canonical agent endpoint.',
80
+ learnMoreUrl: guideUrl(meta.id, 'url-mismatch'),
81
+ });
82
+ score -= 5;
83
+ }
84
+ else if (sameOrigin === null) {
85
+ findings.push({
86
+ status: 'warn',
87
+ message: `agent.json "url" is not a valid absolute URL: ${data.url}`,
88
+ hint: 'Provide an absolute https:// URL for the url field.',
89
+ learnMoreUrl: guideUrl(meta.id, 'url-invalid'),
90
+ });
91
+ score -= 5;
92
+ }
93
+ }
54
94
  if (Array.isArray(data.skills) && data.skills.length > 0) {
55
95
  findings.push({ status: 'pass', message: `${data.skills.length} skill(s) defined` });
96
+ const incomplete = data.skills.filter((s) => !s.id || !s.description);
97
+ if (incomplete.length === 0) {
98
+ findings.push({ status: 'pass', message: 'All skills have id + description' });
99
+ }
100
+ else {
101
+ findings.push({
102
+ status: 'warn',
103
+ message: `${incomplete.length}/${data.skills.length} skill(s) missing id or description`,
104
+ hint: 'Each entry in skills[] should include both an id and a description so agents can address it and reason about its purpose.',
105
+ learnMoreUrl: guideUrl(meta.id, 'incomplete-skills'),
106
+ });
107
+ score -= 5;
108
+ }
56
109
  }
57
110
  else if (Array.isArray(data.skills)) {
58
111
  findings.push({
@@ -100,4 +153,15 @@ export default async function check(ctx) {
100
153
  }
101
154
  return buildResult(meta, score, findings, start);
102
155
  }
156
+ /** Returns `true` when the two URLs share host (case-insensitive), `false` if hosts differ, `null` on parse error. */
157
+ function sameHost(a, b) {
158
+ try {
159
+ const ua = new URL(a);
160
+ const ub = new URL(b);
161
+ return ua.host.toLowerCase() === ub.host.toLowerCase();
162
+ }
163
+ catch {
164
+ return null;
165
+ }
166
+ }
103
167
  //# sourceMappingURL=agent-json.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"agent-json.js","sourceRoot":"","sources":["../../src/checks/agent-json.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"agent-json.js","sourceRoot":"","sources":["../../src/checks/agent-json.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE3D;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,IAAI,GAAc;IAC7B,EAAE,EAAE,YAAY;IAChB,IAAI,EAAE,kBAAkB;IACxB,WAAW,EAAE,wDAAwD;IACrE,MAAM,EAAE,CAAC;CACV,CAAC;AAEF,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,KAAK,CAAC,GAAiB;IACnD,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;IAChC,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,IAAI,KAAK,GAAG,GAAG,CAAC;IAEhB,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,GAAG,yBAAyB,CAAC,CAAC;IAEjE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,QAAQ,CAAC,IAAI,CAAC;YACZ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,mCAAmC;YAC5C,MAAM,EAAE,QAAQ,GAAG,CAAC,MAAM,IAAI,eAAe,EAAE;YAC/C,IAAI,EAAE,qLAAqL;YAC3L,YAAY,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,WAAW,CAAC;SAC7C,CAAC,CAAC;QACH,OAAO,WAAW,CAAC,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC/C,CAAC;IAED,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,gCAAgC,EAAE,CAAC,CAAC;IAE7E,MAAM,SAAS,GAAG,gBAAgB,CAAC,GAAG,EAAE,CAAC,kBAAkB,CAAC,EAAE;QAC5D,OAAO,EAAE,IAAI,CAAC,EAAE;QAChB,aAAa,EAAE,yBAAyB;QACxC,MAAM,EAAE,oBAAoB;KAC7B,CAAC,CAAC;IACH,IAAI,SAAS,EAAE,CAAC;QACd,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACzB,KAAK,IAAI,CAAC,CAAC;IACb,CAAC;IAED,IAAI,IAA6B,CAAC;IAClC,IAAI,CAAC;QACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,QAAQ,CAAC,IAAI,CAAC;YACZ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,cAAc;YACvB,IAAI,EAAE,8EAA8E;YACpF,YAAY,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,cAAc,CAAC;SAChD,CAAC,CAAC;QACH,OAAO,WAAW,CAAC,IAAI,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;IAChD,CAAC;IACD,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC,CAAC;IAEzD,KAAK,MAAM,KAAK,IAAI,0BAA0B,EAAE,CAAC;QAC/C,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,SAAS,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC;YACtD,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,KAAK,WAAW,EAAE,CAAC,CAAC;QAClF,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,IAAI,CAAC;gBACZ,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,mBAAmB,KAAK,WAAW;gBAC5C,IAAI,EAAE,YAAY,KAAK,iFAAiF;gBACxG,YAAY,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,eAAe,CAAC;aACjD,CAAC,CAAC;YACH,KAAK,IAAI,EAAE,CAAC;QACd,CAAC;IACH,CAAC;IAED,IAAI,OAAO,IAAI,CAAC,GAAG,KAAK,QAAQ,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxD,MAAM,UAAU,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;QAC/C,IAAI,UAAU,KAAK,KAAK,EAAE,CAAC;YACzB,QAAQ,CAAC,IAAI,CAAC;gBACZ,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,kDAAkD,IAAI,CAAC,GAAG,EAAE;gBACrE,IAAI,EAAE,kIAAkI;gBACxI,YAAY,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,cAAc,CAAC;aAChD,CAAC,CAAC;YACH,KAAK,IAAI,CAAC,CAAC;QACb,CAAC;aAAM,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;YAC/B,QAAQ,CAAC,IAAI,CAAC;gBACZ,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,iDAAiD,IAAI,CAAC,GAAG,EAAE;gBACpE,IAAI,EAAE,qDAAqD;gBAC3D,YAAY,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,aAAa,CAAC;aAC/C,CAAC,CAAC;YACH,KAAK,IAAI,CAAC,CAAC;QACb,CAAC;IACH,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzD,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,mBAAmB,EAAE,CAAC,CAAC;QACrF,MAAM,UAAU,GAAI,IAAI,CAAC,MAAoC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;QACrG,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,kCAAkC,EAAE,CAAC,CAAC;QACjF,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,IAAI,CAAC;gBACZ,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,GAAG,UAAU,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,qCAAqC;gBACxF,IAAI,EAAE,2HAA2H;gBACjI,YAAY,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,mBAAmB,CAAC;aACrD,CAAC,CAAC;YACH,KAAK,IAAI,CAAC,CAAC;QACb,CAAC;IACH,CAAC;SAAM,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACtC,QAAQ,CAAC,IAAI,CAAC;YACZ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,uBAAuB;YAChC,IAAI,EAAE,oFAAoF;YAC1F,YAAY,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,cAAc,CAAC;SAChD,CAAC,CAAC;QACH,KAAK,IAAI,EAAE,CAAC;IACd,CAAC;IAED,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;QACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,qBAAqB,IAAI,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC;IAC1F,CAAC;SAAM,CAAC;QACN,QAAQ,CAAC,IAAI,CAAC;YACZ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,0BAA0B;YACnC,IAAI,EAAE,0FAA0F;YAChG,YAAY,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,qBAAqB,CAAC;SACvD,CAAC,CAAC;QACH,KAAK,IAAI,CAAC,CAAC;IACb,CAAC;IAED,MAAM,cAAc,GAAG,CAAC,cAAc,EAAE,gBAAgB,EAAE,kBAAkB,CAAC,CAAC;IAC9E,MAAM,eAAe,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC;IAC5E,IAAI,eAAe,CAAC,MAAM,KAAK,cAAc,CAAC,MAAM,EAAE,CAAC;QACrD,QAAQ,CAAC,IAAI,CAAC;YACZ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,8EAA8E;SACxF,CAAC,CAAC;IACL,CAAC;SAAM,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtC,QAAQ,CAAC,IAAI,CAAC;YACZ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,GAAG,eAAe,CAAC,MAAM,IAAI,cAAc,CAAC,MAAM,0BAA0B;SACtF,CAAC,CAAC;IACL,CAAC;SAAM,CAAC;QACN,QAAQ,CAAC,IAAI,CAAC;YACZ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,qEAAqE;YAC9E,IAAI,EAAE,yIAAyI;YAC/I,YAAY,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,kBAAkB,CAAC;SACpD,CAAC,CAAC;QACH,KAAK,IAAI,CAAC,CAAC;IACb,CAAC;IAED,OAAO,WAAW,CAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;AACnD,CAAC;AAED,sHAAsH;AACtH,SAAS,QAAQ,CAAC,CAAS,EAAE,CAAS;IACpC,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC;QACtB,MAAM,EAAE,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC;QACtB,OAAO,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;IACzD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,21 @@
1
+ import type { CheckContext, CheckResult, CheckMeta } from '../types.js';
2
+ /**
3
+ * "html-rendering" — does the HTML delivered to a non-JS agent actually contain content?
4
+ *
5
+ * Most AI crawlers (GPTBot, ClaudeBot, CCBot, etc.) do **not** execute JavaScript. A site
6
+ * built as a client-rendered SPA returns an empty `<div id="root"></div>` shell to those
7
+ * agents, and is effectively invisible regardless of how good its `llms.txt` and structured
8
+ * data are. This check estimates server-side rendering by inspecting the static HTML body.
9
+ *
10
+ * Signals (each contributes to the score):
11
+ * - Visible text length and word count (low → likely JS-rendered shell)
12
+ * - Text-to-markup ratio (high JS, no text → SPA)
13
+ * - Presence of semantic landmarks (`<main>`, `<article>`, `<header>`, `<footer>`, `<nav>`)
14
+ * - Single, meaningful `<h1>`
15
+ * - `<noscript>` fallback for JS-only frameworks
16
+ * - Non-empty SPA root containers (`#root`, `#app`, `#__next`)
17
+ * - Image `alt` attribute coverage
18
+ */
19
+ export declare const meta: CheckMeta;
20
+ export default function check(ctx: CheckContext): Promise<CheckResult>;
21
+ //# sourceMappingURL=html-rendering.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html-rendering.d.ts","sourceRoot":"","sources":["../../src/checks/html-rendering.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,SAAS,EAAW,MAAM,aAAa,CAAC;AAIjF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,IAAI,EAAE,SAKlB,CAAC;AAOF,wBAA8B,KAAK,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CA2K3E"}
@@ -0,0 +1,204 @@
1
+ import { guideUrl } from '../guide-urls.js';
2
+ import { countExecutableScripts, extractVisibleText, findOpeningTag, getMetaContent } from './html-utils.js';
3
+ import { buildResult } from './utils.js';
4
+ /**
5
+ * "html-rendering" — does the HTML delivered to a non-JS agent actually contain content?
6
+ *
7
+ * Most AI crawlers (GPTBot, ClaudeBot, CCBot, etc.) do **not** execute JavaScript. A site
8
+ * built as a client-rendered SPA returns an empty `<div id="root"></div>` shell to those
9
+ * agents, and is effectively invisible regardless of how good its `llms.txt` and structured
10
+ * data are. This check estimates server-side rendering by inspecting the static HTML body.
11
+ *
12
+ * Signals (each contributes to the score):
13
+ * - Visible text length and word count (low → likely JS-rendered shell)
14
+ * - Text-to-markup ratio (high JS, no text → SPA)
15
+ * - Presence of semantic landmarks (`<main>`, `<article>`, `<header>`, `<footer>`, `<nav>`)
16
+ * - Single, meaningful `<h1>`
17
+ * - `<noscript>` fallback for JS-only frameworks
18
+ * - Non-empty SPA root containers (`#root`, `#app`, `#__next`)
19
+ * - Image `alt` attribute coverage
20
+ */
21
+ export const meta = {
22
+ id: 'html-rendering',
23
+ name: 'HTML Rendering',
24
+ description: 'Checks server-rendered content, semantic landmarks, and SPA-shell heuristics',
25
+ weight: 9,
26
+ };
27
+ const MIN_TEXT_LENGTH = 500;
28
+ const MIN_WORD_COUNT = 80;
29
+ const MIN_TEXT_TO_HTML_RATIO = 0.05;
30
+ const SPA_ROOT_IDS = ['root', 'app', '__next', '__nuxt', 'svelte', 'gatsby', 'gatsby-focus-wrapper'];
31
+ export default async function check(ctx) {
32
+ const start = performance.now();
33
+ const findings = [];
34
+ let score = 100;
35
+ const html = ctx.html;
36
+ if (!html) {
37
+ findings.push({
38
+ status: 'fail',
39
+ message: 'Could not fetch homepage HTML',
40
+ hint: 'The homepage did not return any HTML body. Verify your server returns 200 OK with a non-empty response on the root URL.',
41
+ learnMoreUrl: guideUrl(meta.id, 'no-html'),
42
+ });
43
+ return buildResult(meta, 0, findings, start);
44
+ }
45
+ const visibleText = extractVisibleText(html);
46
+ const wordCount = visibleText ? visibleText.split(/\s+/).filter(Boolean).length : 0;
47
+ const ratio = html.length > 0 ? visibleText.length / html.length : 0;
48
+ if (visibleText.length >= MIN_TEXT_LENGTH && wordCount >= MIN_WORD_COUNT) {
49
+ findings.push({
50
+ status: 'pass',
51
+ message: `Server-rendered content detected (${wordCount} words, ${visibleText.length} chars of visible text)`,
52
+ });
53
+ }
54
+ else if (visibleText.length > 0) {
55
+ findings.push({
56
+ status: 'warn',
57
+ message: `Sparse server-rendered content (${wordCount} words, ${visibleText.length} chars)`,
58
+ detail: `Below thresholds: ${MIN_WORD_COUNT} words, ${MIN_TEXT_LENGTH} chars`,
59
+ hint: 'Render at least the main page content server-side. Many AI crawlers (GPTBot, ClaudeBot, CCBot) do not execute JavaScript and will see only the static HTML.',
60
+ learnMoreUrl: guideUrl(meta.id, 'sparse-content'),
61
+ });
62
+ score -= 25;
63
+ }
64
+ else {
65
+ findings.push({
66
+ status: 'fail',
67
+ message: 'No visible text content in static HTML',
68
+ hint: 'Your homepage appears to be a JavaScript-only shell. Add server-side rendering (Next.js SSR/SSG, Astro, Remix, or a static prerender) so AI agents that do not execute JS can read your content.',
69
+ learnMoreUrl: guideUrl(meta.id, 'js-shell'),
70
+ });
71
+ score -= 50;
72
+ }
73
+ if (ratio >= MIN_TEXT_TO_HTML_RATIO) {
74
+ findings.push({
75
+ status: 'pass',
76
+ message: `Text-to-markup ratio is healthy (${(ratio * 100).toFixed(1)}%)`,
77
+ });
78
+ }
79
+ else {
80
+ findings.push({
81
+ status: 'warn',
82
+ message: `Low text-to-markup ratio (${(ratio * 100).toFixed(1)}%)`,
83
+ detail: `Recommended minimum: ${(MIN_TEXT_TO_HTML_RATIO * 100).toFixed(0)}%`,
84
+ hint: 'A very low text-to-markup ratio is a typical SPA-shell symptom. Inline more content directly into the HTML response.',
85
+ learnMoreUrl: guideUrl(meta.id, 'low-ratio'),
86
+ });
87
+ score -= 10;
88
+ }
89
+ const emptyShell = SPA_ROOT_IDS.find((id) => {
90
+ const re = new RegExp(`<[^>]+\\bid\\s*=\\s*["']${id}["'][^>]*>\\s*</[^>]+>`, 'i');
91
+ return re.test(html);
92
+ });
93
+ if (emptyShell) {
94
+ findings.push({
95
+ status: 'fail',
96
+ message: `Empty SPA mount point detected: #${emptyShell}`,
97
+ hint: 'The static HTML contains an empty SPA mount node, which means content is rendered only client-side. Enable SSR/SSG so the initial HTML response includes meaningful content.',
98
+ learnMoreUrl: guideUrl(meta.id, 'empty-spa'),
99
+ });
100
+ score -= 20;
101
+ }
102
+ const semanticLandmarks = ['main', 'article', 'section', 'header', 'footer', 'nav'];
103
+ const presentLandmarks = semanticLandmarks.filter((tag) => findOpeningTag(html, tag) !== null);
104
+ if (presentLandmarks.length >= 3) {
105
+ findings.push({
106
+ status: 'pass',
107
+ message: `Semantic landmarks present (${presentLandmarks.join(', ')})`,
108
+ });
109
+ }
110
+ else if (presentLandmarks.length > 0) {
111
+ findings.push({
112
+ status: 'warn',
113
+ message: `Only ${presentLandmarks.length} semantic landmark(s) found`,
114
+ detail: `Found: ${presentLandmarks.join(', ')}. Missing: ${semanticLandmarks.filter((t) => !presentLandmarks.includes(t)).join(', ')}`,
115
+ hint: 'Use semantic HTML tags so AI agents can understand page structure: <header>, <nav>, <main>, <article>, <section>, <footer>.',
116
+ learnMoreUrl: guideUrl(meta.id, 'few-landmarks'),
117
+ });
118
+ score -= 10;
119
+ }
120
+ else {
121
+ findings.push({
122
+ status: 'warn',
123
+ message: 'No semantic HTML landmarks found',
124
+ hint: 'Replace generic <div> structures with semantic tags: <main>, <article>, <header>, <nav>, <footer>. Agents use these to identify the primary content region.',
125
+ learnMoreUrl: guideUrl(meta.id, 'no-landmarks'),
126
+ });
127
+ score -= 15;
128
+ }
129
+ const h1Matches = [...html.matchAll(/<h1\b[^>]*>([\s\S]*?)<\/h1>/gi)];
130
+ if (h1Matches.length === 1) {
131
+ const h1Text = extractVisibleText(h1Matches[0][1]);
132
+ if (h1Text.length > 0) {
133
+ findings.push({ status: 'pass', message: `Single <h1> heading: "${truncate(h1Text, 60)}"` });
134
+ }
135
+ else {
136
+ findings.push({
137
+ status: 'warn',
138
+ message: '<h1> is empty',
139
+ hint: 'Add meaningful text inside your <h1> element so agents can identify the page topic.',
140
+ learnMoreUrl: guideUrl(meta.id, 'empty-h1'),
141
+ });
142
+ score -= 5;
143
+ }
144
+ }
145
+ else if (h1Matches.length > 1) {
146
+ findings.push({
147
+ status: 'warn',
148
+ message: `${h1Matches.length} <h1> headings found (recommend exactly 1)`,
149
+ hint: 'Use a single <h1> per page to clearly mark the primary topic. Demote secondary headings to <h2>+.',
150
+ learnMoreUrl: guideUrl(meta.id, 'multiple-h1'),
151
+ });
152
+ score -= 5;
153
+ }
154
+ else {
155
+ findings.push({
156
+ status: 'warn',
157
+ message: 'No <h1> heading found',
158
+ hint: 'Add a single <h1> describing the page. Agents and search engines treat the H1 as the primary topic indicator.',
159
+ learnMoreUrl: guideUrl(meta.id, 'no-h1'),
160
+ });
161
+ score -= 10;
162
+ }
163
+ const heavyJs = countExecutableScripts(html) > 15;
164
+ const hasNoscript = /<noscript\b[^>]*>[\s\S]*?<\/noscript>/i.test(html);
165
+ if (heavyJs && !hasNoscript) {
166
+ findings.push({
167
+ status: 'warn',
168
+ message: 'Heavy JavaScript with no <noscript> fallback',
169
+ detail: `${countExecutableScripts(html)} executable <script> tags`,
170
+ hint: 'Provide a <noscript> fallback that explains the site or links to a non-JS variant. This helps agents that disable JS.',
171
+ learnMoreUrl: guideUrl(meta.id, 'no-noscript'),
172
+ });
173
+ score -= 5;
174
+ }
175
+ const imgs = [...html.matchAll(/<img\b([^>]*)>/gi)];
176
+ if (imgs.length > 0) {
177
+ const withAlt = imgs.filter((m) => /\balt\s*=/i.test(m[1])).length;
178
+ const coverage = withAlt / imgs.length;
179
+ if (coverage >= 0.9) {
180
+ findings.push({
181
+ status: 'pass',
182
+ message: `${withAlt}/${imgs.length} <img> tags have alt attributes`,
183
+ });
184
+ }
185
+ else {
186
+ findings.push({
187
+ status: 'warn',
188
+ message: `Only ${withAlt}/${imgs.length} <img> tags have alt attributes`,
189
+ hint: 'Add descriptive alt="" to every <img>. Agents use alt text to understand images they cannot process visually.',
190
+ learnMoreUrl: guideUrl(meta.id, 'missing-alt'),
191
+ });
192
+ score -= 5;
193
+ }
194
+ }
195
+ const generator = getMetaContent(html, 'generator');
196
+ if (generator) {
197
+ findings.push({ status: 'pass', message: `Generator: ${generator}` });
198
+ }
199
+ return buildResult(meta, score, findings, start);
200
+ }
201
+ function truncate(value, max) {
202
+ return value.length > max ? `${value.slice(0, max - 1)}…` : value;
203
+ }
204
+ //# sourceMappingURL=html-rendering.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html-rendering.js","sourceRoot":"","sources":["../../src/checks/html-rendering.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,OAAO,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC7G,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,IAAI,GAAc;IAC7B,EAAE,EAAE,gBAAgB;IACpB,IAAI,EAAE,gBAAgB;IACtB,WAAW,EAAE,8EAA8E;IAC3F,MAAM,EAAE,CAAC;CACV,CAAC;AAEF,MAAM,eAAe,GAAG,GAAG,CAAC;AAC5B,MAAM,cAAc,GAAG,EAAE,CAAC;AAC1B,MAAM,sBAAsB,GAAG,IAAI,CAAC;AACpC,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,sBAAsB,CAAC,CAAC;AAErG,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,KAAK,CAAC,GAAiB;IACnD,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;IAChC,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,IAAI,KAAK,GAAG,GAAG,CAAC;IAEhB,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC;IACtB,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,QAAQ,CAAC,IAAI,CAAC;YACZ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,+BAA+B;YACxC,IAAI,EAAE,yHAAyH;YAC/H,YAAY,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,CAAC;SAC3C,CAAC,CAAC;QACH,OAAO,WAAW,CAAC,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC/C,CAAC;IAED,MAAM,WAAW,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;IAC7C,MAAM,SAAS,GAAG,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IACpF,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IAErE,IAAI,WAAW,CAAC,MAAM,IAAI,eAAe,IAAI,SAAS,IAAI,cAAc,EAAE,CAAC;QACzE,QAAQ,CAAC,IAAI,CAAC;YACZ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,qCAAqC,SAAS,WAAW,WAAW,CAAC,MAAM,yBAAyB;SAC9G,CAAC,CAAC;IACL,CAAC;SAAM,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClC,QAAQ,CAAC,IAAI,CAAC;YACZ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,mCAAmC,SAAS,WAAW,WAAW,CAAC,MAAM,SAAS;YAC3F,MAAM,EAAE,qBAAqB,cAAc,WAAW,eAAe,QAAQ;YAC7E,IAAI,EAAE,6JAA6J;YACnK,YAAY,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,gBAAgB,CAAC;SAClD,CAAC,CAAC;QACH,KAAK,IAAI,EAAE,CAAC;IACd,CAAC;SAAM,CAAC;QACN,QAAQ,CAAC,IAAI,CAAC;YACZ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,wCAAwC;YACjD,IAAI,EAAE,kMAAkM;YACxM,YAAY,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,UAAU,CAAC;SAC5C,CAAC,CAAC;QACH,KAAK,IAAI,EAAE,CAAC;IACd,CAAC;IAED,IAAI,KAAK,IAAI,sBAAsB,EAAE,CAAC;QACpC,QAAQ,CAAC,IAAI,CAAC;YACZ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,oCAAoC,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI;SAC1E,CAAC,CAAC;IACL,CAAC;SAAM,CAAC;QACN,QAAQ,CAAC,IAAI,CAAC;YACZ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,6BAA6B,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI;YAClE,MAAM,EAAE,wBAAwB,CAAC,sBAAsB,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG;YAC5E,IAAI,EAAE,sHAAsH;YAC5H,YAAY,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,WAAW,CAAC;SAC7C,CAAC,CAAC;QACH,KAAK,IAAI,EAAE,CAAC;IACd,CAAC;IAED,MAAM,UAAU,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE;QAC1C,MAAM,EAAE,GAAG,IAAI,MAAM,CAAC,2BAA2B,EAAE,wBAAwB,EAAE,GAAG,CAAC,CAAC;QAClF,OAAO,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACvB,CAAC,CAAC,CAAC;IACH,IAAI,UAAU,EAAE,CAAC;QACf,QAAQ,CAAC,IAAI,CAAC;YACZ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,oCAAoC,UAAU,EAAE;YACzD,IAAI,EAAE,8KAA8K;YACpL,YAAY,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,WAAW,CAAC;SAC7C,CAAC,CAAC;QACH,KAAK,IAAI,EAAE,CAAC;IACd,CAAC;IAED,MAAM,iBAAiB,GAAG,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;IACpF,MAAM,gBAAgB,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,KAAK,IAAI,CAAC,CAAC;IAC/F,IAAI,gBAAgB,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;QACjC,QAAQ,CAAC,IAAI,CAAC;YACZ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,+BAA+B,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;SACvE,CAAC,CAAC;IACL,CAAC;SAAM,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvC,QAAQ,CAAC,IAAI,CAAC;YACZ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,QAAQ,gBAAgB,CAAC,MAAM,6BAA6B;YACrE,MAAM,EAAE,UAAU,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;YACtI,IAAI,EAAE,6HAA6H;YACnI,YAAY,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,eAAe,CAAC;SACjD,CAAC,CAAC;QACH,KAAK,IAAI,EAAE,CAAC;IACd,CAAC;SAAM,CAAC;QACN,QAAQ,CAAC,IAAI,CAAC;YACZ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,kCAAkC;YAC3C,IAAI,EAAE,6JAA6J;YACnK,YAAY,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,cAAc,CAAC;SAChD,CAAC,CAAC;QACH,KAAK,IAAI,EAAE,CAAC;IACd,CAAC;IAED,MAAM,SAAS,GAAG,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,+BAA+B,CAAC,CAAC,CAAC;IACtE,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG,kBAAkB,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACnD,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,yBAAyB,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;QAC/F,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,IAAI,CAAC;gBACZ,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,eAAe;gBACxB,IAAI,EAAE,qFAAqF;gBAC3F,YAAY,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,UAAU,CAAC;aAC5C,CAAC,CAAC;YACH,KAAK,IAAI,CAAC,CAAC;QACb,CAAC;IACH,CAAC;SAAM,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAChC,QAAQ,CAAC,IAAI,CAAC;YACZ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,GAAG,SAAS,CAAC,MAAM,4CAA4C;YACxE,IAAI,EAAE,mGAAmG;YACzG,YAAY,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,aAAa,CAAC;SAC/C,CAAC,CAAC;QACH,KAAK,IAAI,CAAC,CAAC;IACb,CAAC;SAAM,CAAC;QACN,QAAQ,CAAC,IAAI,CAAC;YACZ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,uBAAuB;YAChC,IAAI,EAAE,+GAA+G;YACrH,YAAY,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,OAAO,CAAC;SACzC,CAAC,CAAC;QACH,KAAK,IAAI,EAAE,CAAC;IACd,CAAC;IAED,MAAM,OAAO,GAAG,sBAAsB,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;IAClD,MAAM,WAAW,GAAG,wCAAwC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxE,IAAI,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC;QAC5B,QAAQ,CAAC,IAAI,CAAC;YACZ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,8CAA8C;YACvD,MAAM,EAAE,GAAG,sBAAsB,CAAC,IAAI,CAAC,2BAA2B;YAClE,IAAI,EAAE,uHAAuH;YAC7H,YAAY,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,aAAa,CAAC;SAC/C,CAAC,CAAC;QACH,KAAK,IAAI,CAAC,CAAC;IACb,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC,CAAC;IACpD,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpB,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;QACnE,MAAM,QAAQ,GAAG,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC;QACvC,IAAI,QAAQ,IAAI,GAAG,EAAE,CAAC;YACpB,QAAQ,CAAC,IAAI,CAAC;gBACZ,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,GAAG,OAAO,IAAI,IAAI,CAAC,MAAM,iCAAiC;aACpE,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,IAAI,CAAC;gBACZ,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,QAAQ,OAAO,IAAI,IAAI,CAAC,MAAM,iCAAiC;gBACxE,IAAI,EAAE,+GAA+G;gBACrH,YAAY,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,aAAa,CAAC;aAC/C,CAAC,CAAC;YACH,KAAK,IAAI,CAAC,CAAC;QACb,CAAC;IACH,CAAC;IAED,MAAM,SAAS,GAAG,cAAc,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IACpD,IAAI,SAAS,EAAE,CAAC;QACd,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,SAAS,EAAE,EAAE,CAAC,CAAC;IACxE,CAAC;IAED,OAAO,WAAW,CAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;AACnD,CAAC;AAED,SAAS,QAAQ,CAAC,KAAa,EAAE,GAAW;IAC1C,OAAO,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC;AACpE,CAAC"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Shared HTML parsing primitives used by checks that need to inspect homepage markup.
3
+ *
4
+ * These are deliberately regex-based (no DOM dependency) so the audit stays free of
5
+ * heavyweight HTML parsers. They are designed for resilience over completeness — every
6
+ * helper returns `null` / empty values on malformed input rather than throwing.
7
+ */
8
+ /** Escape a string for safe inclusion inside a `RegExp` source. */
9
+ export declare function escapeRegex(value: string): string;
10
+ /** Decode the small set of HTML entities that show up most often inside attribute values. */
11
+ export declare function unescapeHtml(value: string): string;
12
+ /**
13
+ * Match the first opening tag (`<tag ...>`) and return its raw attribute string.
14
+ * Returns `null` if the tag is absent.
15
+ */
16
+ export declare function findOpeningTag(html: string, tagName: string): string | null;
17
+ /** Pull a single attribute out of a raw attribute fragment (the `...` in `<tag ...>`). */
18
+ export declare function getAttribute(attrFragment: string, attrName: string): string | null;
19
+ /** Convenience: read an attribute from the first matching opening tag. */
20
+ export declare function getTagAttribute(html: string, tagName: string, attrName: string): string | null;
21
+ /**
22
+ * Extract the `content` value of the first `<meta name="..."|property="..."|http-equiv="...">`
23
+ * matching `key`. Searches `name`, `property`, and `http-equiv` (case-insensitive).
24
+ */
25
+ export declare function getMetaContent(html: string, key: string): string | null;
26
+ /** Match every `<meta>` tag whose name/property begins with `prefix` (e.g. `og:`, `twitter:`). */
27
+ export declare function findMetaTagsByPrefix(html: string, prefix: string): string[];
28
+ /** Match every `<link rel="...">` tag with the requested `rel` value. */
29
+ export declare function findLinkTags(html: string, rel: string): string[];
30
+ /**
31
+ * Strip `<script>` / `<style>` blocks and HTML tags to produce a rough text-content estimate.
32
+ * Returns the trimmed plain text — used for content-density / SSR heuristics.
33
+ */
34
+ export declare function extractVisibleText(html: string): string;
35
+ /** Count `<script>` tags (excluding `type="application/ld+json"` data blocks). */
36
+ export declare function countExecutableScripts(html: string): number;
37
+ //# sourceMappingURL=html-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html-utils.d.ts","sourceRoot":"","sources":["../../src/checks/html-utils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,mEAAmE;AACnE,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,6FAA6F;AAC7F,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CASlD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI3E;AAED,0FAA0F;AAC1F,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAKlF;AAED,0EAA0E;AAC1E,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG9F;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMvE;AAED,kGAAkG;AAClG,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAG3E;AAED,yEAAyE;AACzE,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAGhE;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CASvD;AAED,kFAAkF;AAClF,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAS3D"}