@rivi-software/agentic-seo-check 0.0.0-stage → 0.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.
Files changed (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +160 -2
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +149 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/core/audit.d.ts +72 -0
  7. package/dist/core/audit.js +337 -0
  8. package/dist/core/audit.js.map +1 -0
  9. package/dist/core/concurrency.d.ts +4 -0
  10. package/dist/core/concurrency.js +29 -0
  11. package/dist/core/concurrency.js.map +1 -0
  12. package/dist/core/crawl.d.ts +40 -0
  13. package/dist/core/crawl.js +195 -0
  14. package/dist/core/crawl.js.map +1 -0
  15. package/dist/core/fetch.d.ts +27 -0
  16. package/dist/core/fetch.js +135 -0
  17. package/dist/core/fetch.js.map +1 -0
  18. package/dist/core/html/extract.d.ts +10 -0
  19. package/dist/core/html/extract.js +150 -0
  20. package/dist/core/html/extract.js.map +1 -0
  21. package/dist/core/html/structured-data.d.ts +11 -0
  22. package/dist/core/html/structured-data.js +185 -0
  23. package/dist/core/html/structured-data.js.map +1 -0
  24. package/dist/core/html/text.d.ts +23 -0
  25. package/dist/core/html/text.js +149 -0
  26. package/dist/core/html/text.js.map +1 -0
  27. package/dist/core/lighthouse.d.ts +49 -0
  28. package/dist/core/lighthouse.js +140 -0
  29. package/dist/core/lighthouse.js.map +1 -0
  30. package/dist/core/performance-rules.d.ts +3 -0
  31. package/dist/core/performance-rules.js +53 -0
  32. package/dist/core/performance-rules.js.map +1 -0
  33. package/dist/core/render-rules.d.ts +7 -0
  34. package/dist/core/render-rules.js +77 -0
  35. package/dist/core/render-rules.js.map +1 -0
  36. package/dist/core/render.d.ts +22 -0
  37. package/dist/core/render.js +73 -0
  38. package/dist/core/render.js.map +1 -0
  39. package/dist/core/report/markdown.d.ts +2 -0
  40. package/dist/core/report/markdown.js +152 -0
  41. package/dist/core/report/markdown.js.map +1 -0
  42. package/dist/core/robots.d.ts +11 -0
  43. package/dist/core/robots.js +33 -0
  44. package/dist/core/robots.js.map +1 -0
  45. package/dist/core/rules/canonical.d.ts +2 -0
  46. package/dist/core/rules/canonical.js +105 -0
  47. package/dist/core/rules/canonical.js.map +1 -0
  48. package/dist/core/rules/content.d.ts +1 -0
  49. package/dist/core/rules/content.js +41 -0
  50. package/dist/core/rules/content.js.map +1 -0
  51. package/dist/core/rules/document.d.ts +1 -0
  52. package/dist/core/rules/document.js +60 -0
  53. package/dist/core/rules/document.js.map +1 -0
  54. package/dist/core/rules/headings.d.ts +1 -0
  55. package/dist/core/rules/headings.js +61 -0
  56. package/dist/core/rules/headings.js.map +1 -0
  57. package/dist/core/rules/hreflang.d.ts +1 -0
  58. package/dist/core/rules/hreflang.js +68 -0
  59. package/dist/core/rules/hreflang.js.map +1 -0
  60. package/dist/core/rules/images.d.ts +1 -0
  61. package/dist/core/rules/images.js +59 -0
  62. package/dist/core/rules/images.js.map +1 -0
  63. package/dist/core/rules/index.d.ts +14 -0
  64. package/dist/core/rules/index.js +76 -0
  65. package/dist/core/rules/index.js.map +1 -0
  66. package/dist/core/rules/indexing.d.ts +10 -0
  67. package/dist/core/rules/indexing.js +53 -0
  68. package/dist/core/rules/indexing.js.map +1 -0
  69. package/dist/core/rules/keyword.d.ts +1 -0
  70. package/dist/core/rules/keyword.js +74 -0
  71. package/dist/core/rules/keyword.js.map +1 -0
  72. package/dist/core/rules/links.d.ts +1 -0
  73. package/dist/core/rules/links.js +72 -0
  74. package/dist/core/rules/links.js.map +1 -0
  75. package/dist/core/rules/meta.d.ts +1 -0
  76. package/dist/core/rules/meta.js +60 -0
  77. package/dist/core/rules/meta.js.map +1 -0
  78. package/dist/core/rules/response.d.ts +1 -0
  79. package/dist/core/rules/response.js +59 -0
  80. package/dist/core/rules/response.js.map +1 -0
  81. package/dist/core/rules/rule.d.ts +42 -0
  82. package/dist/core/rules/rule.js +51 -0
  83. package/dist/core/rules/rule.js.map +1 -0
  84. package/dist/core/rules/social.d.ts +1 -0
  85. package/dist/core/rules/social.js +74 -0
  86. package/dist/core/rules/social.js.map +1 -0
  87. package/dist/core/rules/structured-data.d.ts +23 -0
  88. package/dist/core/rules/structured-data.js +169 -0
  89. package/dist/core/rules/structured-data.js.map +1 -0
  90. package/dist/core/rules/title.d.ts +1 -0
  91. package/dist/core/rules/title.js +59 -0
  92. package/dist/core/rules/title.js.map +1 -0
  93. package/dist/core/score.d.ts +3 -0
  94. package/dist/core/score.js +15 -0
  95. package/dist/core/score.js.map +1 -0
  96. package/dist/core/site-rules.d.ts +64 -0
  97. package/dist/core/site-rules.js +251 -0
  98. package/dist/core/site-rules.js.map +1 -0
  99. package/dist/core/sitemap.d.ts +13 -0
  100. package/dist/core/sitemap.js +95 -0
  101. package/dist/core/sitemap.js.map +1 -0
  102. package/dist/core/static.d.ts +9 -0
  103. package/dist/core/static.js +184 -0
  104. package/dist/core/static.js.map +1 -0
  105. package/dist/core/summary.d.ts +2 -0
  106. package/dist/core/summary.js +28 -0
  107. package/dist/core/summary.js.map +1 -0
  108. package/dist/core/types.d.ts +296 -0
  109. package/dist/core/types.js +26 -0
  110. package/dist/core/types.js.map +1 -0
  111. package/dist/core/url.d.ts +20 -0
  112. package/dist/core/url.js +139 -0
  113. package/dist/core/url.js.map +1 -0
  114. package/dist/index.d.ts +11 -0
  115. package/dist/index.js +10 -0
  116. package/dist/index.js.map +1 -0
  117. package/dist/mcp-bin.d.ts +2 -0
  118. package/dist/mcp-bin.js +7 -0
  119. package/dist/mcp-bin.js.map +1 -0
  120. package/dist/mcp.d.ts +5 -0
  121. package/dist/mcp.js +190 -0
  122. package/dist/mcp.js.map +1 -0
  123. package/dist/version.d.ts +2 -0
  124. package/dist/version.js +4 -0
  125. package/dist/version.js.map +1 -0
  126. package/package.json +89 -3
  127. package/skills/seo-audit/SKILL.md +53 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/core/render.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { Audit } from '../types.js';
2
+ export declare function renderMarkdown(audit: Audit): string;
@@ -0,0 +1,152 @@
1
+ export function renderMarkdown(audit) {
2
+ return audit.kind === 'page' ? pageMarkdown(audit) : siteMarkdown(audit);
3
+ }
4
+ function pageMarkdown(audit, heading = '#') {
5
+ const lines = [];
6
+ lines.push(`${heading} SEO audit: ${audit.url}`);
7
+ lines.push('');
8
+ lines.push(`Score **${audit.score}/100** · ${countsLine(audit.counts)}${audit.response?.status ? ` · HTTP ${audit.response.status}` : ''}${audit.response?.timeMs ? ` · ${audit.response.timeMs} ms` : ''}`);
9
+ lines.push('');
10
+ const s = audit.summary;
11
+ lines.push(`${heading}# Summary`);
12
+ lines.push('');
13
+ lines.push('| Field | Value |');
14
+ lines.push('| --- | --- |');
15
+ lines.push(`| Title | ${cell(s.title)} (${s.titleLength} chars) |`);
16
+ lines.push(`| Meta description | ${cell(s.metaDescription)} (${s.metaDescriptionLength} chars) |`);
17
+ lines.push(`| Canonical | ${cell(s.canonical)} |`);
18
+ lines.push(`| Indexable | ${s.indexable ? 'yes' : 'no'}${s.robots.length ? ` (${s.robots.join(', ')})` : ''} |`);
19
+ lines.push(`| Language | ${cell(s.lang)} |`);
20
+ lines.push(`| H1 | ${s.h1.length ? s.h1.map(cell).join(' / ') : '—'} |`);
21
+ lines.push(`| Words | ${s.wordCount} |`);
22
+ lines.push(`| Images | ${s.images.total} (${s.images.missingAlt} without alt) |`);
23
+ lines.push(`| Links | ${s.links.internal} internal, ${s.links.external} external |`);
24
+ lines.push(`| Structured data | ${s.structuredDataTypes.length ? s.structuredDataTypes.join(', ') : '—'} |`);
25
+ lines.push(`| Open Graph | ${Object.keys(s.openGraph).length ? Object.keys(s.openGraph).map((k) => `og:${k}`).join(', ') : '—'} |`);
26
+ lines.push('');
27
+ if (audit.performance) {
28
+ const m = audit.performance.metrics;
29
+ lines.push(`${heading}# Lighthouse (${audit.performance.formFactor})`);
30
+ lines.push('');
31
+ lines.push('| Metric | Value |');
32
+ lines.push('| --- | --- |');
33
+ lines.push(`| Performance score | ${m.performanceScore ?? '—'} |`);
34
+ lines.push(`| SEO score | ${m.seoScore ?? '—'} |`);
35
+ lines.push(`| LCP | ${ms(m.lcpMs)} |`);
36
+ lines.push(`| CLS | ${m.cls === null ? '—' : m.cls.toFixed(3)} |`);
37
+ lines.push(`| TBT | ${ms(m.tbtMs)} |`);
38
+ lines.push(`| FCP | ${ms(m.fcpMs)} |`);
39
+ lines.push(`| Speed Index | ${ms(m.speedIndexMs)} |`);
40
+ lines.push(`| TTFB | ${ms(m.ttfbMs)} |`);
41
+ lines.push('');
42
+ }
43
+ if (audit.rendering) {
44
+ const r = audit.rendering;
45
+ lines.push(`${heading}# Rendered vs server HTML`);
46
+ lines.push('');
47
+ lines.push('| Field | Server HTML | Rendered DOM |');
48
+ lines.push('| --- | --- | --- |');
49
+ lines.push(`| Title | ${cell(r.raw.title)} | ${cell(r.rendered.title)} |`);
50
+ lines.push(`| Meta description | ${cell(r.raw.metaDescription)} | ${cell(r.rendered.metaDescription)} |`);
51
+ lines.push(`| Canonical | ${cell(r.raw.canonical)} | ${cell(r.rendered.canonical)} |`);
52
+ lines.push(`| Robots | ${r.raw.robots.join(', ') || '—'} | ${r.rendered.robots.join(', ') || '—'} |`);
53
+ lines.push(`| H1 count | ${r.raw.h1Count} | ${r.rendered.h1Count} |`);
54
+ lines.push(`| Words | ${r.raw.wordCount} | ${r.rendered.wordCount} |`);
55
+ lines.push(`| Internal links | ${r.raw.internalLinks} | ${r.rendered.internalLinks} |`);
56
+ lines.push(`| Structured data items | ${r.raw.structuredDataCount} | ${r.rendered.structuredDataCount} |`);
57
+ lines.push('');
58
+ }
59
+ lines.push(...findingsSection(audit.findings, heading));
60
+ return lines.join('\n');
61
+ }
62
+ function siteMarkdown(audit) {
63
+ const lines = [];
64
+ lines.push(`# SEO site audit: ${audit.target}`);
65
+ lines.push('');
66
+ lines.push(`Score **${audit.score}/100** · ${countsLine(audit.counts)} · ${audit.crawl.pagesCrawled} pages audited, ${audit.crawl.pagesDiscovered} discovered${audit.crawl.hitPageLimit ? ` (page limit ${audit.crawl.maxPages} reached)` : ''} · ${(audit.crawl.durationMs / 1000).toFixed(1)} s`);
67
+ lines.push('');
68
+ lines.push('| Check | Result |');
69
+ lines.push('| --- | --- |');
70
+ lines.push(`| robots.txt | ${audit.robotsTxt ? (audit.robotsTxt.found ? `found (${audit.robotsTxt.sitemaps.length} sitemap(s) declared)` : `not found (HTTP ${audit.robotsTxt.status ?? 'n/a'})`) : 'not checked'} |`);
71
+ lines.push(`| Sitemaps | ${audit.sitemaps.sources.length ? `${audit.sitemaps.sources.length} file(s), ${audit.sitemaps.urlCount} URLs` : 'none found'}${audit.sitemaps.errors.length ? `, ${audit.sitemaps.errors.length} error(s)` : ''} |`);
72
+ lines.push('');
73
+ if (audit.siteFindings.length) {
74
+ lines.push('## Site-wide findings');
75
+ lines.push('');
76
+ for (const f of audit.siteFindings)
77
+ lines.push(...findingLines(f));
78
+ lines.push('');
79
+ }
80
+ if (audit.issues.length) {
81
+ lines.push('## Page issues by rule');
82
+ lines.push('');
83
+ lines.push('| Severity | Rule | Pages | Example |');
84
+ lines.push('| --- | --- | --- | --- |');
85
+ for (const i of audit.issues)
86
+ lines.push(`| ${i.severity} | \`${i.rule}\` | ${i.pages} | ${i.examples[0] ?? ''} |`);
87
+ lines.push('');
88
+ }
89
+ lines.push('## Pages');
90
+ lines.push('');
91
+ lines.push('| Score | E/W/I | URL | Title |');
92
+ lines.push('| --- | --- | --- | --- |');
93
+ for (const p of audit.pages)
94
+ lines.push(`| ${p.score} | ${p.counts.error}/${p.counts.warning}/${p.counts.info} | ${p.url} | ${cell(p.summary.title)} |`);
95
+ lines.push('');
96
+ const detailed = audit.pages.filter((p) => p.findings.length > 0);
97
+ if (detailed.length) {
98
+ lines.push('## Page details');
99
+ lines.push('');
100
+ for (const p of detailed) {
101
+ lines.push(`### ${p.url}`);
102
+ lines.push('');
103
+ lines.push(`Score ${p.score}/100 · ${countsLine(p.counts)}`);
104
+ lines.push('');
105
+ for (const f of p.findings)
106
+ lines.push(...findingLines(f));
107
+ lines.push('');
108
+ }
109
+ }
110
+ return lines.join('\n');
111
+ }
112
+ function findingsSection(findings, heading) {
113
+ const lines = [];
114
+ lines.push(`${heading}# Findings`);
115
+ lines.push('');
116
+ if (!findings.length) {
117
+ lines.push('No issues found.');
118
+ return lines;
119
+ }
120
+ for (const severity of ['error', 'warning', 'info']) {
121
+ const group = findings.filter((f) => f.severity === severity);
122
+ if (!group.length)
123
+ continue;
124
+ lines.push(`${heading}## ${severity === 'error' ? 'Errors' : severity === 'warning' ? 'Warnings' : 'Info'} (${group.length})`);
125
+ lines.push('');
126
+ for (const f of group)
127
+ lines.push(...findingLines(f));
128
+ lines.push('');
129
+ }
130
+ return lines;
131
+ }
132
+ function findingLines(f) {
133
+ const out = [`- **\`${f.rule}\`** ${f.message}`];
134
+ out.push(` - Fix: ${f.recommendation}`);
135
+ if (f.selector)
136
+ out.push(` - Where: \`${f.selector}\``);
137
+ if (f.evidence && Object.keys(f.evidence).length)
138
+ out.push(` - Evidence: \`${JSON.stringify(f.evidence).slice(0, 400)}\``);
139
+ return out;
140
+ }
141
+ function countsLine(c) {
142
+ return `${c.error} error(s), ${c.warning} warning(s), ${c.info} info`;
143
+ }
144
+ function cell(value) {
145
+ if (!value)
146
+ return '—';
147
+ return value.replace(/\|/g, '\\|').replace(/\n/g, ' ').slice(0, 120);
148
+ }
149
+ function ms(value) {
150
+ return value === null ? '—' : `${Math.round(value)} ms`;
151
+ }
152
+ //# sourceMappingURL=markdown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.js","sourceRoot":"","sources":["../../../src/core/report/markdown.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { RobotsTxtInfo } from './types.js';
2
+ import { type FetchOptions } from './fetch.js';
3
+ export interface RobotsResult {
4
+ info: RobotsTxtInfo;
5
+ /** Returns true when crawling the URL is allowed (unknown/missing robots.txt allows everything). */
6
+ allows: (url: string) => boolean;
7
+ }
8
+ export declare function loadRobotsTxt(origin: string, userAgent: string, options?: FetchOptions): Promise<RobotsResult>;
9
+ export declare function buildRobots(url: string, status: number | null, error: string | undefined, text: string | null, userAgent: string): RobotsResult;
10
+ /** Short bot token used for robots.txt matching, derived from the full UA string. */
11
+ export declare function robotsUserAgentToken(userAgent: string): string;
@@ -0,0 +1,33 @@
1
+ import { createRequire } from 'node:module';
2
+ import { fetchPage } from './fetch.js';
3
+ // robots-parser is CommonJS; require() avoids ESM default-export interop mismatches.
4
+ const robotsParser = createRequire(import.meta.url)('robots-parser');
5
+ export async function loadRobotsTxt(origin, userAgent, options = {}) {
6
+ const url = new URL('/robots.txt', origin).href;
7
+ const res = await fetchPage(url, { ...options, timeoutMs: Math.min(options.timeoutMs ?? 10_000, 10_000) });
8
+ const found = res.status === 200 && typeof res.body === 'string' && !res.error && !/<html/i.test(res.body.slice(0, 500));
9
+ return buildRobots(url, res.status, res.error, found ? res.body : null, userAgent);
10
+ }
11
+ export function buildRobots(url, status, error, text, userAgent) {
12
+ const info = { url, status, found: text !== null, sitemaps: [], text: text ? text.slice(0, 20_000) : null };
13
+ if (error)
14
+ info.error = error;
15
+ if (text === null) {
16
+ return { info, allows: () => true };
17
+ }
18
+ const robot = robotsParser(url, text);
19
+ info.sitemaps = robot.getSitemaps();
20
+ return {
21
+ info,
22
+ allows: (target) => {
23
+ const allowed = robot.isAllowed(target, userAgent);
24
+ return allowed === undefined ? true : allowed;
25
+ },
26
+ };
27
+ }
28
+ /** Short bot token used for robots.txt matching, derived from the full UA string. */
29
+ export function robotsUserAgentToken(userAgent) {
30
+ const match = /agentic-seo-check|Googlebot|bingbot/i.exec(userAgent);
31
+ return match ? match[0] : userAgent;
32
+ }
33
+ //# sourceMappingURL=robots.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"robots.js","sourceRoot":"","sources":["../../src/core/robots.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAE,SAAS,EAAqB,MAAM,YAAY,CAAC;AAI1D,qFAAqF;AACrF,MAAM,YAAY,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,eAAe,CAA8C,CAAC;AAQlH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,MAAc,EAAE,SAAiB,EAAE,UAAwB,EAAE;IAC/F,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC;IAChD,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE,EAAE,GAAG,OAAO,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,IAAI,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;IAC3G,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,KAAK,GAAG,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IACzH,OAAO,WAAW,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;AACrF,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,GAAW,EAAE,MAAqB,EAAE,KAAyB,EAAE,IAAmB,EAAE,SAAiB;IAC/H,MAAM,IAAI,GAAkB,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,KAAK,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC3H,IAAI,KAAK;QAAE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IAC9B,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC;IACtC,CAAC;IACD,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACtC,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;IACpC,OAAO;QACL,IAAI;QACJ,MAAM,EAAE,CAAC,MAAc,EAAE,EAAE;YACzB,MAAM,OAAO,GAAG,KAAK,CAAC,SAAS,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;YACnD,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC;QAChD,CAAC;KACF,CAAC;AACJ,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,oBAAoB,CAAC,SAAiB;IACpD,MAAM,KAAK,GAAG,sCAAsC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACrE,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACtC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function isLocalHost(url: string): boolean;
2
+ export declare const canonicalRules: import("./rule.js").Rule[];
@@ -0,0 +1,105 @@
1
+ import { hostOf, normalizeUrl, samePath } from '../url.js';
2
+ import { cap, defineRule, GOOGLE_DOCS } from './rule.js';
3
+ const SELECTOR = 'head > link[rel="canonical"]';
4
+ const LOCAL_HOSTS = new Set(['localhost', '127.0.0.1', '0.0.0.0', '::1', '[::1]']);
5
+ export function isLocalHost(url) {
6
+ const host = hostOf(url);
7
+ if (!host)
8
+ return false;
9
+ return LOCAL_HOSTS.has(host) || host.endsWith('.local') || host.endsWith('.localhost') || host.endsWith('.test');
10
+ }
11
+ export const canonicalRules = [
12
+ defineRule({
13
+ id: 'canonical.missing',
14
+ category: 'canonical',
15
+ severity: 'warning',
16
+ description: 'No <link rel="canonical"> is present.',
17
+ recommendation: 'Add a self-referencing canonical with the absolute preferred URL (scheme, host, trailing slash) so duplicate URLs (query strings, www/non-www) consolidate to it.',
18
+ docs: GOOGLE_DOCS.canonical,
19
+ }, ({ page }, make) => (page.canonicals.length === 0 ? make('Page has no canonical link.', { selector: SELECTOR }) : null)),
20
+ defineRule({
21
+ id: 'canonical.multiple',
22
+ category: 'canonical',
23
+ severity: 'error',
24
+ description: 'More than one canonical link is present. When they disagree, search engines ignore all of them.',
25
+ recommendation: 'Emit exactly one canonical link per page. Check for a framework default plus a hand-written one.',
26
+ docs: GOOGLE_DOCS.canonical,
27
+ }, ({ page }, make) => {
28
+ if (page.canonicals.length <= 1)
29
+ return null;
30
+ const distinct = new Set(page.canonicals.map((c) => c.trim()));
31
+ return make(`Page has ${page.canonicals.length} canonical links${distinct.size > 1 ? ' with different targets' : ' (identical)'}.`, {
32
+ selector: SELECTOR,
33
+ severity: distinct.size > 1 ? 'error' : 'warning',
34
+ evidence: { canonicals: cap(page.canonicals) },
35
+ });
36
+ }),
37
+ defineRule({
38
+ id: 'canonical.invalid',
39
+ category: 'canonical',
40
+ severity: 'error',
41
+ description: 'The canonical href is empty or cannot be resolved to an http(s) URL.',
42
+ recommendation: 'Set the canonical href to a valid absolute URL such as https://www.example.com/path/.',
43
+ docs: GOOGLE_DOCS.canonical,
44
+ }, ({ page }, make) => {
45
+ const first = page.canonicals[0];
46
+ if (first === undefined)
47
+ return null;
48
+ return page.canonical ? null : make(`Canonical href "${first}" is not a valid URL.`, { selector: SELECTOR, evidence: { href: first } });
49
+ }),
50
+ defineRule({
51
+ id: 'canonical.relative',
52
+ category: 'canonical',
53
+ severity: 'warning',
54
+ description: 'The canonical href is a relative URL.',
55
+ recommendation: 'Use an absolute URL in the canonical link. Relative canonicals are error-prone and some crawlers resolve them unexpectedly.',
56
+ docs: GOOGLE_DOCS.canonical,
57
+ }, ({ page }, make) => {
58
+ const first = page.canonicals[0];
59
+ if (!first || !page.canonical)
60
+ return null;
61
+ return /^https?:\/\//i.test(first) ? null : make(`Canonical href "${first}" is relative.`, { selector: SELECTOR, evidence: { href: first, resolved: page.canonical } });
62
+ }),
63
+ defineRule({
64
+ id: 'canonical.protocol-mismatch',
65
+ category: 'canonical',
66
+ severity: 'warning',
67
+ description: 'The page is served over https but the canonical points to http.',
68
+ recommendation: 'Point the canonical at the https URL.',
69
+ docs: GOOGLE_DOCS.canonical,
70
+ }, ({ page, source }, make) => {
71
+ if (source !== 'url' || !page.canonical)
72
+ return null;
73
+ return page.url.startsWith('https://') && page.canonical.startsWith('http://')
74
+ ? make('Canonical uses http:// while the page is served over https://.', { selector: SELECTOR, evidence: { canonical: page.canonical, page: page.url } })
75
+ : null;
76
+ }),
77
+ defineRule({
78
+ id: 'canonical.other-page',
79
+ category: 'canonical',
80
+ severity: 'info',
81
+ description: 'The canonical points to a different URL, so search engines will treat this page as a duplicate of that URL.',
82
+ recommendation: 'If this page should rank on its own, make the canonical self-referencing. If it is intentionally a duplicate (print view, filtered list, tracking URL), this is correct.',
83
+ docs: GOOGLE_DOCS.canonical,
84
+ }, ({ page, source }, make) => {
85
+ if (!page.canonical)
86
+ return null;
87
+ const pageUrl = normalizeUrl(page.url);
88
+ const canon = normalizeUrl(page.canonical);
89
+ if (!pageUrl || !canon)
90
+ return null;
91
+ // For files and local dev servers the host is synthetic; compare paths only.
92
+ const pathOnly = source !== 'url' || isLocalHost(page.url);
93
+ const same = pathOnly ? samePath(pageUrl, canon) : stripTrailingSlash(pageUrl) === stripTrailingSlash(canon);
94
+ return same
95
+ ? null
96
+ : make(`Canonical points to ${page.canonical}, not to this page.`, {
97
+ selector: SELECTOR,
98
+ evidence: { canonical: page.canonical, page: page.url, comparedBy: pathOnly ? 'path' : 'url' },
99
+ });
100
+ }),
101
+ ];
102
+ function stripTrailingSlash(url) {
103
+ return url.endsWith('/') && new URL(url).pathname !== '/' ? url.slice(0, -1) : url;
104
+ }
105
+ //# sourceMappingURL=canonical.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonical.js","sourceRoot":"","sources":["../../../src/core/rules/canonical.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare const contentRules: import("./rule.js").Rule[];
@@ -0,0 +1,41 @@
1
+ import { fleschReadingEase, hasPlaceholderText } from '../html/text.js';
2
+ import { defineRule, GOOGLE_DOCS } from './rule.js';
3
+ export const contentRules = [
4
+ defineRule({
5
+ id: 'content.empty',
6
+ category: 'content',
7
+ severity: 'warning',
8
+ description: 'The HTML contains no visible text.',
9
+ recommendation: 'If the page is rendered client-side, use server-side rendering or static generation so the HTML carries the content. Re-run with render enabled to compare the rendered DOM.',
10
+ docs: GOOGLE_DOCS.javascript,
11
+ }, ({ page }, make) => (page.wordCount === 0 ? make('Page body has no visible text in the HTML response.') : null)),
12
+ defineRule({
13
+ id: 'content.thin',
14
+ category: 'content',
15
+ severity: 'warning',
16
+ description: 'The page has very little text (default: fewer than 300 words).',
17
+ recommendation: 'Add substantive, useful content, or set noindex if the page is a utility page (login, cart, thank-you). Thin pages rarely rank and can drag down site quality signals.',
18
+ }, ({ page, thresholds }, make) => page.wordCount > 0 && page.wordCount < thresholds.thinContentWords
19
+ ? make(`Page has only ${page.wordCount} words of visible text (threshold ${thresholds.thinContentWords}).`, { evidence: { wordCount: page.wordCount, threshold: thresholds.thinContentWords } })
20
+ : null),
21
+ defineRule({
22
+ id: 'content.placeholder-text',
23
+ category: 'content',
24
+ severity: 'error',
25
+ description: 'Placeholder text ("lorem ipsum") is present in the page.',
26
+ recommendation: 'Replace the placeholder copy with real content before publishing.',
27
+ }, ({ page }, make) => (hasPlaceholderText(page.text) ? make('Page contains "lorem ipsum" placeholder text.') : null)),
28
+ defineRule({
29
+ id: 'content.readability-low',
30
+ category: 'content',
31
+ severity: 'info',
32
+ description: 'Flesch Reading Ease below 30 (very difficult). Only evaluated for English or unspecified language, 100+ words.',
33
+ recommendation: 'Shorten sentences and prefer common words. Aim for a Flesch score of 50-70 for general audiences.',
34
+ }, ({ page }, make) => {
35
+ if (page.lang && !page.lang.toLowerCase().startsWith('en'))
36
+ return null;
37
+ const score = fleschReadingEase(page.text);
38
+ return score !== null && score < 30 ? make(`Flesch Reading Ease is ${score} (very difficult).`, { evidence: { fleschReadingEase: score, wordCount: page.wordCount } }) : null;
39
+ }),
40
+ ];
41
+ //# sourceMappingURL=content.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content.js","sourceRoot":"","sources":["../../../src/core/rules/content.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAEpD,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,UAAU,CACR;QACE,EAAE,EAAE,eAAe;QACnB,QAAQ,EAAE,SAAS;QACnB,QAAQ,EAAE,SAAS;QACnB,WAAW,EAAE,oCAAoC;QACjD,cAAc,EAAE,8KAA8K;QAC9L,IAAI,EAAE,WAAW,CAAC,UAAU;KAC7B,EACD,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,qDAAqD,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAChH;IACD,UAAU,CACR;QACE,EAAE,EAAE,cAAc;QAClB,QAAQ,EAAE,SAAS;QACnB,QAAQ,EAAE,SAAS;QACnB,WAAW,EAAE,gEAAgE;QAC7E,cAAc,EAAE,wKAAwK;KACzL,EACD,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,EAAE,CAC7B,IAAI,CAAC,SAAS,GAAG,CAAC,IAAI,IAAI,CAAC,SAAS,GAAG,UAAU,CAAC,gBAAgB;QAChE,CAAC,CAAC,IAAI,CAAC,iBAAiB,IAAI,CAAC,SAAS,qCAAqC,UAAU,CAAC,gBAAgB,IAAI,EAAE,EAAE,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,SAAS,EAAE,UAAU,CAAC,gBAAgB,EAAE,EAAE,CAAC;QAChM,CAAC,CAAC,IAAI,CACX;IACD,UAAU,CACR;QACE,EAAE,EAAE,0BAA0B;QAC9B,QAAQ,EAAE,SAAS;QACnB,QAAQ,EAAE,OAAO;QACjB,WAAW,EAAE,0DAA0D;QACvE,cAAc,EAAE,mEAAmE;KACpF,EACD,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,+CAA+C,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CACnH;IACD,UAAU,CACR;QACE,EAAE,EAAE,yBAAyB;QAC7B,QAAQ,EAAE,SAAS;QACnB,QAAQ,EAAE,MAAM;QAChB,WAAW,EAAE,gHAAgH;QAC7H,cAAc,EAAE,mGAAmG;KACpH,EACD,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE;QACjB,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;QACxE,MAAM,KAAK,GAAG,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3C,OAAO,KAAK,KAAK,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,0BAA0B,KAAK,oBAAoB,EAAE,EAAE,QAAQ,EAAE,EAAE,iBAAiB,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAChL,CAAC,CACF;CACF,CAAC"}
@@ -0,0 +1 @@
1
+ export declare const documentRules: import("./rule.js").Rule[];
@@ -0,0 +1,60 @@
1
+ import { defineRule, GOOGLE_DOCS } from './rule.js';
2
+ const BCP47 = /^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$/;
3
+ export const documentRules = [
4
+ defineRule({
5
+ id: 'html.lang-missing',
6
+ category: 'document',
7
+ severity: 'warning',
8
+ description: 'The <html> element has no lang attribute.',
9
+ recommendation: 'Set <html lang="xx"> (for example "en", "nl", "en-GB") so search engines and screen readers know the content language.',
10
+ }, ({ page }, make) => (page.lang ? null : make('<html> has no lang attribute.', { selector: 'html' }))),
11
+ defineRule({
12
+ id: 'html.lang-invalid',
13
+ category: 'document',
14
+ severity: 'warning',
15
+ description: 'The <html lang> value is not a valid BCP 47 language tag.',
16
+ recommendation: 'Use a BCP 47 tag such as "en", "en-US" or "pt-BR".',
17
+ }, ({ page }, make) => (page.lang && !BCP47.test(page.lang) ? make(`<html lang="${page.lang}"> is not a valid language tag.`, { selector: 'html', evidence: { lang: page.lang } }) : null)),
18
+ defineRule({
19
+ id: 'viewport.missing',
20
+ category: 'document',
21
+ severity: 'warning',
22
+ description: 'No <meta name="viewport"> is present, so the page is not mobile-friendly.',
23
+ recommendation: 'Add <meta name="viewport" content="width=device-width, initial-scale=1"> to <head>. Google indexes the mobile version of pages first.',
24
+ docs: GOOGLE_DOCS.mobile,
25
+ }, ({ page }, make) => (page.viewport ? null : make('Page has no viewport meta tag.', { selector: 'head > meta[name="viewport"]' }))),
26
+ defineRule({
27
+ id: 'viewport.not-scalable',
28
+ category: 'document',
29
+ severity: 'info',
30
+ description: 'The viewport disables user zoom (user-scalable=no or maximum-scale=1).',
31
+ recommendation: 'Remove user-scalable=no and maximum-scale limits; blocking zoom is an accessibility problem flagged by Lighthouse.',
32
+ }, ({ page }, make) => {
33
+ const v = (page.viewport ?? '').toLowerCase().replace(/\s/g, '');
34
+ return /user-scalable=(no|0)/.test(v) || /maximum-scale=1(\.0*)?(,|$)/.test(v)
35
+ ? make('Viewport prevents users from zooming.', { selector: 'head > meta[name="viewport"]', evidence: { viewport: page.viewport } })
36
+ : null;
37
+ }),
38
+ defineRule({
39
+ id: 'charset.missing',
40
+ category: 'document',
41
+ severity: 'info',
42
+ description: 'No character encoding is declared.',
43
+ recommendation: 'Add <meta charset="utf-8"> as the first element in <head>.',
44
+ }, ({ page }, make) => (page.charset ? null : make('No <meta charset> declaration found.', { selector: 'head > meta[charset]' }))),
45
+ defineRule({
46
+ id: 'doctype.missing',
47
+ category: 'document',
48
+ severity: 'info',
49
+ description: 'The document does not start with <!DOCTYPE html>.',
50
+ recommendation: 'Start the document with <!DOCTYPE html> to avoid quirks-mode rendering.',
51
+ }, ({ page }, make) => (page.hasDoctype ? null : make('Document has no HTML5 doctype.'))),
52
+ defineRule({
53
+ id: 'document.html-too-large',
54
+ category: 'document',
55
+ severity: 'info',
56
+ description: 'The HTML document is larger than 1.5 MB.',
57
+ recommendation: 'Reduce inline scripts/styles/data (large JSON state, inline SVG sprites). Very large documents slow parsing and may be truncated by crawlers.',
58
+ }, ({ page }, make) => page.htmlBytes > 1_500_000 ? make(`HTML is ${(page.htmlBytes / 1_000_000).toFixed(1)} MB.`, { evidence: { bytes: page.htmlBytes } }) : null),
59
+ ];
60
+ //# sourceMappingURL=document.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document.js","sourceRoot":"","sources":["../../../src/core/rules/document.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare const headingRules: import("./rule.js").Rule[];
@@ -0,0 +1,61 @@
1
+ import { cap, defineRule } from './rule.js';
2
+ export const headingRules = [
3
+ defineRule({
4
+ id: 'h1.missing',
5
+ category: 'headings',
6
+ severity: 'warning',
7
+ description: 'The page has no <h1>.',
8
+ recommendation: 'Add one <h1> that states the main topic of the page. It is usually the visible page heading.',
9
+ }, ({ page }, make) => (page.h1s.length === 0 ? make('Page has no <h1> heading.', { selector: 'h1' }) : null)),
10
+ defineRule({
11
+ id: 'h1.multiple',
12
+ category: 'headings',
13
+ severity: 'info',
14
+ description: 'The page has more than one <h1>.',
15
+ recommendation: 'Prefer a single <h1> for the main topic and use <h2>-<h6> for sections. Multiple H1s are valid HTML but make the main topic less clear.',
16
+ }, ({ page }, make) => page.h1s.length > 1 ? make(`Page has ${page.h1s.length} <h1> headings.`, { selector: 'h1', evidence: { h1s: cap(page.h1s) } }) : null),
17
+ defineRule({
18
+ id: 'h1.empty',
19
+ category: 'headings',
20
+ severity: 'warning',
21
+ description: 'An <h1> exists but contains no text.',
22
+ recommendation: 'Put the page heading text inside the <h1>. If it wraps only an image, give the image descriptive alt text.',
23
+ }, ({ page }, make) => (page.h1s.length > 0 && page.h1s.every((h) => !h) ? make('All <h1> elements are empty.', { selector: 'h1' }) : null)),
24
+ defineRule({
25
+ id: 'h1.too-long',
26
+ category: 'headings',
27
+ severity: 'info',
28
+ description: 'The <h1> is longer than the configured maximum (default 70 characters).',
29
+ recommendation: 'Keep the H1 concise; move detail into the intro paragraph.',
30
+ }, ({ page, thresholds }, make) => {
31
+ const long = page.h1s.filter((h) => h.length > thresholds.h1MaxChars);
32
+ return long.length ? make(`${long.length} <h1> heading(s) exceed ${thresholds.h1MaxChars} characters.`, { selector: 'h1', evidence: { h1s: cap(long) } }) : null;
33
+ }),
34
+ defineRule({
35
+ id: 'headings.empty',
36
+ category: 'headings',
37
+ severity: 'warning',
38
+ description: 'Heading elements (h2-h6) with no text content.',
39
+ recommendation: 'Remove empty heading tags or fill them with text. Empty headings confuse outline parsers and screen readers.',
40
+ }, ({ page }, make) => {
41
+ const empty = page.headings.filter((h) => h.level > 1 && !h.text);
42
+ return empty.length ? make(`${empty.length} empty heading element(s) found.`, { evidence: { levels: cap(empty.map((h) => `h${h.level}`)) } }) : null;
43
+ }),
44
+ defineRule({
45
+ id: 'headings.skipped-level',
46
+ category: 'headings',
47
+ severity: 'info',
48
+ description: 'Heading levels are skipped (for example an <h2> followed directly by an <h4>).',
49
+ recommendation: 'Use heading levels in order (h1 > h2 > h3) so the document outline reflects the content structure.',
50
+ }, ({ page }, make) => {
51
+ const skips = [];
52
+ let prev = 0;
53
+ for (const h of page.headings) {
54
+ if (prev > 0 && h.level > prev + 1)
55
+ skips.push(`h${prev} → h${h.level} ("${h.text.slice(0, 40)}")`);
56
+ prev = h.level;
57
+ }
58
+ return skips.length ? make(`${skips.length} heading level skip(s) found.`, { evidence: { skips: cap(skips) } }) : null;
59
+ }),
60
+ ];
61
+ //# sourceMappingURL=headings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headings.js","sourceRoot":"","sources":["../../../src/core/rules/headings.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare const hreflangRules: import("./rule.js").Rule[];