@rivi-software/agentic-seo-check 0.1.0 → 0.2.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 (144) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +124 -18
  3. package/dist/brand.d.ts +11 -0
  4. package/dist/brand.js +22 -0
  5. package/dist/brand.js.map +1 -0
  6. package/dist/cli.js +174 -86
  7. package/dist/cli.js.map +1 -1
  8. package/dist/core/aeo/audit.d.ts +44 -0
  9. package/dist/core/aeo/audit.js +310 -0
  10. package/dist/core/aeo/audit.js.map +1 -0
  11. package/dist/core/aeo/bots.d.ts +30 -0
  12. package/dist/core/aeo/bots.js +299 -0
  13. package/dist/core/aeo/bots.js.map +1 -0
  14. package/dist/core/aeo/llms-txt.d.ts +8 -0
  15. package/dist/core/aeo/llms-txt.js +88 -0
  16. package/dist/core/aeo/llms-txt.js.map +1 -0
  17. package/dist/core/aeo/page.d.ts +53 -0
  18. package/dist/core/aeo/page.js +312 -0
  19. package/dist/core/aeo/page.js.map +1 -0
  20. package/dist/core/aeo/probe.d.ts +10 -0
  21. package/dist/core/aeo/probe.js +53 -0
  22. package/dist/core/aeo/probe.js.map +1 -0
  23. package/dist/core/aeo/site-rules.d.ts +13 -0
  24. package/dist/core/aeo/site-rules.js +178 -0
  25. package/dist/core/aeo/site-rules.js.map +1 -0
  26. package/dist/core/audit.d.ts +37 -16
  27. package/dist/core/audit.js +389 -249
  28. package/dist/core/audit.js.map +1 -1
  29. package/dist/core/concurrency.d.ts +4 -4
  30. package/dist/core/concurrency.js +33 -23
  31. package/dist/core/concurrency.js.map +1 -1
  32. package/dist/core/crawl.d.ts +9 -5
  33. package/dist/core/crawl.js +271 -123
  34. package/dist/core/crawl.js.map +1 -1
  35. package/dist/core/fetch.d.ts +5 -5
  36. package/dist/core/fetch.js +151 -100
  37. package/dist/core/fetch.js.map +1 -1
  38. package/dist/core/html/answer.d.ts +4 -0
  39. package/dist/core/html/answer.js +198 -0
  40. package/dist/core/html/answer.js.map +1 -0
  41. package/dist/core/html/dom.d.ts +17 -0
  42. package/dist/core/html/dom.js +83 -0
  43. package/dist/core/html/dom.js.map +1 -0
  44. package/dist/core/html/extract.d.ts +2 -2
  45. package/dist/core/html/extract.js +102 -111
  46. package/dist/core/html/extract.js.map +1 -1
  47. package/dist/core/html/structured-data.d.ts +16 -6
  48. package/dist/core/html/structured-data.js +180 -145
  49. package/dist/core/html/structured-data.js.map +1 -1
  50. package/dist/core/html/text.d.ts +14 -14
  51. package/dist/core/html/text.js +110 -107
  52. package/dist/core/html/text.js.map +1 -1
  53. package/dist/core/lighthouse.d.ts +26 -25
  54. package/dist/core/lighthouse.js +181 -105
  55. package/dist/core/lighthouse.js.map +1 -1
  56. package/dist/core/performance-rules.d.ts +2 -2
  57. package/dist/core/performance-rules.js +105 -33
  58. package/dist/core/performance-rules.js.map +1 -1
  59. package/dist/core/render-rules.d.ts +5 -4
  60. package/dist/core/render-rules.js +129 -62
  61. package/dist/core/render-rules.js.map +1 -1
  62. package/dist/core/render.d.ts +2 -2
  63. package/dist/core/render.js +29 -23
  64. package/dist/core/render.js.map +1 -1
  65. package/dist/core/report/markdown.d.ts +4 -2
  66. package/dist/core/report/markdown.js +210 -129
  67. package/dist/core/report/markdown.js.map +1 -1
  68. package/dist/core/robots.d.ts +12 -5
  69. package/dist/core/robots.js +43 -17
  70. package/dist/core/robots.js.map +1 -1
  71. package/dist/core/rules/aeo.d.ts +11 -0
  72. package/dist/core/rules/aeo.js +423 -0
  73. package/dist/core/rules/aeo.js.map +1 -0
  74. package/dist/core/rules/canonical.d.ts +1 -1
  75. package/dist/core/rules/canonical.js +93 -57
  76. package/dist/core/rules/canonical.js.map +1 -1
  77. package/dist/core/rules/content.js +40 -26
  78. package/dist/core/rules/content.js.map +1 -1
  79. package/dist/core/rules/document.js +64 -41
  80. package/dist/core/rules/document.js.map +1 -1
  81. package/dist/core/rules/headings.js +58 -38
  82. package/dist/core/rules/headings.js.map +1 -1
  83. package/dist/core/rules/hreflang.js +70 -38
  84. package/dist/core/rules/hreflang.js.map +1 -1
  85. package/dist/core/rules/images.js +44 -26
  86. package/dist/core/rules/images.js.map +1 -1
  87. package/dist/core/rules/index.d.ts +10 -6
  88. package/dist/core/rules/index.js +43 -36
  89. package/dist/core/rules/index.js.map +1 -1
  90. package/dist/core/rules/indexing.d.ts +2 -2
  91. package/dist/core/rules/indexing.js +48 -26
  92. package/dist/core/rules/indexing.js.map +1 -1
  93. package/dist/core/rules/keyword.js +84 -47
  94. package/dist/core/rules/keyword.js.map +1 -1
  95. package/dist/core/rules/links.js +72 -37
  96. package/dist/core/rules/links.js.map +1 -1
  97. package/dist/core/rules/meta.js +55 -34
  98. package/dist/core/rules/meta.js.map +1 -1
  99. package/dist/core/rules/response.js +75 -41
  100. package/dist/core/rules/response.js.map +1 -1
  101. package/dist/core/rules/rule.d.ts +6 -6
  102. package/dist/core/rules/rule.js +36 -36
  103. package/dist/core/rules/rule.js.map +1 -1
  104. package/dist/core/rules/social.js +86 -45
  105. package/dist/core/rules/social.js.map +1 -1
  106. package/dist/core/rules/structured-data.d.ts +6 -4
  107. package/dist/core/rules/structured-data.js +314 -102
  108. package/dist/core/rules/structured-data.js.map +1 -1
  109. package/dist/core/rules/title.js +48 -33
  110. package/dist/core/rules/title.js.map +1 -1
  111. package/dist/core/score.d.ts +5 -3
  112. package/dist/core/score.js +11 -9
  113. package/dist/core/score.js.map +1 -1
  114. package/dist/core/site-rules.d.ts +3 -3
  115. package/dist/core/site-rules.js +409 -198
  116. package/dist/core/site-rules.js.map +1 -1
  117. package/dist/core/sitemap.d.ts +5 -5
  118. package/dist/core/sitemap.js +109 -64
  119. package/dist/core/sitemap.js.map +1 -1
  120. package/dist/core/static.d.ts +2 -2
  121. package/dist/core/static.js +210 -112
  122. package/dist/core/static.js.map +1 -1
  123. package/dist/core/summary.d.ts +2 -2
  124. package/dist/core/summary.js +11 -6
  125. package/dist/core/summary.js.map +1 -1
  126. package/dist/core/types.d.ts +189 -14
  127. package/dist/core/types.js +15 -8
  128. package/dist/core/types.js.map +1 -1
  129. package/dist/core/url.d.ts +11 -11
  130. package/dist/core/url.js +122 -70
  131. package/dist/core/url.js.map +1 -1
  132. package/dist/index.d.ts +15 -11
  133. package/dist/index.js +11 -9
  134. package/dist/index.js.map +1 -1
  135. package/dist/mcp-bin.js +7 -4
  136. package/dist/mcp-bin.js.map +1 -1
  137. package/dist/mcp.d.ts +3 -3
  138. package/dist/mcp.js +437 -121
  139. package/dist/mcp.js.map +1 -1
  140. package/dist/version.d.ts +0 -1
  141. package/dist/version.js +4 -2
  142. package/dist/version.js.map +1 -1
  143. package/package.json +32 -10
  144. package/skills/seo-audit/SKILL.md +8 -2
@@ -0,0 +1,423 @@
1
+ import { articles, comparable, directivesFor, hasNoindex, hasNosnippet, isEntity, isHomePage, markedUpTexts, maxSnippet, nameOf, parseRobotsHeader, stripTags, typedNodes, } from "../aeo/page.js";
2
+ import { isJsonObject, isString } from "../html/structured-data.js";
3
+ import { robotsDirectives } from "./indexing.js";
4
+ import { cap, defineRule, GOOGLE_DOCS } from "./rule.js";
5
+ /** Answer engines' own documentation for the AEO rules. */
6
+ export const AEO_DOCS = {
7
+ googleAi: "https://developers.google.com/search/docs/appearance/ai-features",
8
+ bing: "https://www.bing.com/webmasters/help/webmaster-guidelines-30fba23a",
9
+ httpErrors: "https://developers.google.com/search/docs/crawling-indexing/http-network-errors",
10
+ structuredDataPolicies: "https://developers.google.com/search/docs/appearance/structured-data/sd-policies",
11
+ article: "https://developers.google.com/search/docs/appearance/structured-data/article",
12
+ dates: "https://developers.google.com/search/docs/appearance/publication-dates",
13
+ organization: "https://developers.google.com/search/docs/appearance/structured-data/organization",
14
+ };
15
+ /** A question answered in more words than this does not start with a direct answer. */
16
+ const LONG_ANSWER_WORDS = 100;
17
+ const sourcesOf = (meta, header, directive) => {
18
+ const sources = [];
19
+ for (const m of meta) {
20
+ if (m.content.split(",").some((d) => directive(d.replaceAll(/\s+/gu, "")))) {
21
+ sources.push(`<meta name="${m.name}">`);
22
+ }
23
+ }
24
+ if (header && parseRobotsHeader(header).some((d) => directive(d.directive))) {
25
+ sources.push("X-Robots-Tag");
26
+ }
27
+ return [...new Set(sources)];
28
+ };
29
+ const metaSelector = (sources) => {
30
+ const meta = sources.find((s) => s.startsWith("<meta"));
31
+ return meta
32
+ ? `head > meta[name="${meta.slice('<meta name="'.length, -2)}"]`
33
+ : undefined;
34
+ };
35
+ const isNoai = (d) => d === "noai" || d === "noimageai";
36
+ const yearOf = (value) => isString(value) ? (/^\d{4}/u.exec(value.trim())?.[0] ?? null) : null;
37
+ export const aeoRules = [
38
+ defineRule({
39
+ id: "aeo.page-error",
40
+ category: "aeo",
41
+ severity: "error",
42
+ description: "The page answers with an error status or could not be fetched, so no crawler can read it.",
43
+ recommendation: "Make the URL answer 200 with the page's HTML. Answer engines, like search engines, drop pages that fail.",
44
+ docs: AEO_DOCS.httpErrors,
45
+ }, ({ response }, make) => {
46
+ if (!response) {
47
+ return null;
48
+ }
49
+ if (response.error) {
50
+ return make(`The page could not be fetched: ${response.error}.`, {
51
+ evidence: { error: response.error },
52
+ });
53
+ }
54
+ return response.status !== null && response.status >= 400
55
+ ? make(`The page answers HTTP ${response.status}.`, {
56
+ evidence: { status: response.status },
57
+ })
58
+ : null;
59
+ }),
60
+ defineRule({
61
+ id: "aeo.noindex",
62
+ category: "aeo",
63
+ severity: "warning",
64
+ description: "The page is set to noindex, so answer engines that draw on a search index cannot cite it.",
65
+ recommendation: "Remove noindex if answer engines should quote this page. Keep it on pages that should stay private (login, cart, internal search); preview hosts often add it with X-Robots-Tag, so check production first.",
66
+ docs: AEO_DOCS.googleAi,
67
+ }, ({ page, response }, make) => {
68
+ const r = robotsDirectives(page, response);
69
+ return r.noindex
70
+ ? make(`Page is set to noindex via ${r.sources.join(" and ")}, so Google AI Overviews and AI Mode, Copilot and other answer engines built on a search index cannot cite it.`, {
71
+ selector: r.sources.includes("meta")
72
+ ? 'head > meta[name="robots"]'
73
+ : undefined,
74
+ evidence: { directives: r.directives, sources: r.sources },
75
+ })
76
+ : null;
77
+ }),
78
+ defineRule({
79
+ id: "aeo.nosnippet",
80
+ category: "aeo",
81
+ severity: "warning",
82
+ description: "nosnippet (or max-snippet:0) keeps the page's text out of Google's AI Overviews and AI Mode and limits what Copilot can cite.",
83
+ recommendation: "Remove nosnippet or max-snippet:0 if answer engines should quote this page. To hide one part only, wrap that part in an element with data-nosnippet.",
84
+ docs: AEO_DOCS.googleAi,
85
+ }, ({ page, response }, make) => {
86
+ const google = directivesFor("google", page, response);
87
+ const bing = directivesFor("bing", page, response);
88
+ const engines = [
89
+ ...(hasNosnippet(google) && !hasNoindex(google) ? ["Google"] : []),
90
+ ...(hasNosnippet(bing) && !hasNoindex(bing) ? ["Bing"] : []),
91
+ ];
92
+ if (!engines.length) {
93
+ return null;
94
+ }
95
+ const sources = sourcesOf(page.answer.robotsMeta, response?.headers["x-robots-tag"], (d) => d === "nosnippet" || d === "max-snippet:0");
96
+ return make(`Page sets nosnippet for ${engines.join(" and ")} via ${sources.join(" and ")}, so its text cannot be quoted in AI answers.`, {
97
+ selector: metaSelector(sources),
98
+ evidence: { engines, sources },
99
+ });
100
+ }),
101
+ defineRule({
102
+ id: "aeo.max-snippet-short",
103
+ category: "aeo",
104
+ severity: "info",
105
+ description: "max-snippet is below 100 characters, which also caps what Google quotes in AI Overviews and AI Mode.",
106
+ recommendation: "Raise max-snippet, or set max-snippet:-1 for no limit, so answer engines can quote whole sentences.",
107
+ docs: GOOGLE_DOCS.robotsMeta,
108
+ }, ({ page, response }, make) => {
109
+ const directives = directivesFor("google", page, response);
110
+ const limit = maxSnippet(directives);
111
+ return limit !== null &&
112
+ limit > 0 &&
113
+ limit < 100 &&
114
+ !hasNoindex(directives)
115
+ ? make(`max-snippet:${limit} lets Google quote at most ${limit} characters of this page.`, { evidence: { maxSnippet: limit } })
116
+ : null;
117
+ }),
118
+ defineRule({
119
+ id: "aeo.noarchive",
120
+ category: "aeo",
121
+ severity: "warning",
122
+ description: "noarchive keeps the page out of Microsoft Copilot answers; Bing still lists it in search.",
123
+ recommendation: "Remove noarchive if the page should appear in Copilot answers. Paywalled pages can use nocache instead, which lets Copilot cite the URL, title and snippet. Google ignores noarchive.",
124
+ docs: AEO_DOCS.bing,
125
+ }, ({ page, response }, make) => {
126
+ const bing = directivesFor("bing", page, response);
127
+ // Bing treats a page with both noarchive and nocache as nocache.
128
+ if (!bing.includes("noarchive") ||
129
+ bing.includes("nocache") ||
130
+ hasNoindex(bing)) {
131
+ return null;
132
+ }
133
+ const sources = sourcesOf(page.answer.robotsMeta.filter((m) => m.name !== "googlebot"), response?.headers["x-robots-tag"], (d) => d === "noarchive");
134
+ return make(`Page sets noarchive via ${sources.join(" and ")}, so Copilot leaves it out of its answers.`, { selector: metaSelector(sources), evidence: { sources } });
135
+ }),
136
+ defineRule({
137
+ id: "aeo.nocache",
138
+ category: "aeo",
139
+ severity: "info",
140
+ description: "nocache limits Microsoft Copilot to the page's URL, title and snippet.",
141
+ recommendation: "Remove nocache if Copilot should quote the page's content, not only link to it.",
142
+ docs: AEO_DOCS.bing,
143
+ }, ({ page, response }, make) => {
144
+ const bing = directivesFor("bing", page, response);
145
+ return bing.includes("nocache") && !hasNoindex(bing)
146
+ ? make("Page sets nocache, so Copilot may use only its URL, title and snippet.")
147
+ : null;
148
+ }),
149
+ defineRule({
150
+ id: "aeo.noai",
151
+ category: "aeo",
152
+ severity: "info",
153
+ description: "The page sets noai or noimageai, directives no major answer engine documents support for.",
154
+ recommendation: "To opt out of AI training, say so in robots.txt (a `User-Agent: __usage_ai-training` group with `Disallow: /`, or groups for GPTBot, ClaudeBot and Google-Extended). If the page should appear in AI answers, remove noai to avoid mixed signals.",
155
+ }, ({ page, response }, make) => {
156
+ const sources = sourcesOf(page.answer.robotsMeta, response?.headers["x-robots-tag"], isNoai);
157
+ return sources.length
158
+ ? make(`Page sets noai/noimageai via ${sources.join(" and ")}; Google, Bing, OpenAI and Anthropic do not document support for it.`, { selector: metaSelector(sources), evidence: { sources } })
159
+ : null;
160
+ }),
161
+ defineRule({
162
+ id: "aeo.data-nosnippet",
163
+ category: "aeo",
164
+ severity: "info",
165
+ description: "data-nosnippet hides a large share of the page's text (20% or more) from snippets and AI Overviews.",
166
+ recommendation: "Keep data-nosnippet on boilerplate or paywalled parts only. The text answer engines should quote must be outside it.",
167
+ docs: GOOGLE_DOCS.robotsMeta,
168
+ }, ({ page }, make) => {
169
+ const { nosnippetWords, nosnippetElements, nosnippetSelectors } = page.answer;
170
+ if (!nosnippetWords || page.wordCount < 50) {
171
+ return null;
172
+ }
173
+ const share = nosnippetWords / page.wordCount;
174
+ if (share < 0.2) {
175
+ return null;
176
+ }
177
+ return make(`data-nosnippet hides ${nosnippetWords} of ${page.wordCount} words (${Math.round(share * 100)}%) from snippets and AI Overviews.`, {
178
+ severity: share >= 0.5 ? "warning" : "info",
179
+ selector: nosnippetSelectors[0],
180
+ evidence: {
181
+ elements: nosnippetElements,
182
+ words: nosnippetWords,
183
+ selectors: cap(nosnippetSelectors),
184
+ },
185
+ });
186
+ }),
187
+ defineRule({
188
+ id: "aeo.text-needs-javascript",
189
+ category: "aeo",
190
+ severity: "warning",
191
+ description: "The HTML is an empty JavaScript app shell; most AI crawlers do not run JavaScript, so they see no content.",
192
+ recommendation: "Render the content on the server (SSR or static generation) so the HTML carries it. `agentic-seo-check audit <url> --render` compares the HTML with the rendered page.",
193
+ docs: GOOGLE_DOCS.javascript,
194
+ }, ({ page }, make) => {
195
+ const { emptyAppRoot, noscriptAsksForJavaScript } = page.answer;
196
+ if (page.wordCount >= 50 ||
197
+ (!emptyAppRoot && !noscriptAsksForJavaScript)) {
198
+ return null;
199
+ }
200
+ const shell = emptyAppRoot
201
+ ? `an empty app root (${emptyAppRoot})`
202
+ : "a <noscript> message asking for JavaScript";
203
+ return make(`The HTML has ${page.wordCount} words of text and ${shell}. GPTBot, OAI-SearchBot, ClaudeBot and PerplexityBot do not run JavaScript, so they see an empty page.`, {
204
+ selector: emptyAppRoot ?? "noscript",
205
+ evidence: {
206
+ wordCount: page.wordCount,
207
+ emptyAppRoot,
208
+ noscriptAsksForJavaScript,
209
+ },
210
+ });
211
+ }),
212
+ defineRule({
213
+ id: "aeo.question-not-answered-first",
214
+ category: "aeo",
215
+ severity: "info",
216
+ description: "A heading asks a question but no short, direct answer follows it.",
217
+ recommendation: "Answer each question in the first sentence or two under its heading (under about 60 words), then add detail. A direct answer right under the question is the passage answer engines find easiest to quote.",
218
+ }, ({ page }, make) => {
219
+ const weak = page.answer.questions.filter((q) => q.sectionWords === 0 ||
220
+ (q.firstBlockWords ?? q.sectionWords) > LONG_ANSWER_WORDS);
221
+ return weak.length
222
+ ? make(`${weak.length} of ${page.answer.questions.length} question heading(s) are not followed by a short direct answer.`, {
223
+ evidence: {
224
+ questions: cap(weak.map((q) => ({
225
+ question: q.text,
226
+ firstBlockWords: q.firstBlockWords,
227
+ sectionWords: q.sectionWords,
228
+ }))),
229
+ },
230
+ })
231
+ : null;
232
+ }),
233
+ defineRule({
234
+ id: "aeo.unstructured-text",
235
+ category: "aeo",
236
+ severity: "info",
237
+ description: "A long page (600+ words) has no subheadings, lists or tables.",
238
+ recommendation: "Break the text into sections under descriptive subheadings, and use lists or tables for steps, options and comparisons, so answer engines can lift one self-contained passage.",
239
+ }, ({ page }, make) => page.wordCount >= 600 &&
240
+ !page.headings.some((h) => h.level >= 2) &&
241
+ page.answer.lists === 0 &&
242
+ page.answer.tables === 0
243
+ ? make(`Page has ${page.wordCount} words without subheadings, lists or tables.`, { evidence: { wordCount: page.wordCount } })
244
+ : null),
245
+ defineRule({
246
+ id: "aeo.schema-text-not-visible",
247
+ category: "aeo",
248
+ severity: "warning",
249
+ description: "FAQPage, QAPage or HowTo structured data holds questions, answers or steps that are not in the page's HTML text.",
250
+ recommendation: "Mark up only what the page shows: render every question, answer and step from the same data as the JSON-LD, or remove the items that are not on the page. Google's structured data guidelines forbid marking up content readers cannot see. If the page adds them with JavaScript, render them on the server too: most AI crawlers do not run JavaScript.",
251
+ docs: AEO_DOCS.structuredDataPolicies,
252
+ }, ({ page }, make) => {
253
+ if (page.wordCount === 0) {
254
+ return null;
255
+ }
256
+ const texts = markedUpTexts(page);
257
+ if (!texts.length) {
258
+ return null;
259
+ }
260
+ const visible = ` ${comparable(page.text)} `;
261
+ // Compare the first words only: answers and steps are often longer, or formatted, on the page.
262
+ const hidden = texts.filter((t) => {
263
+ const words = comparable(stripTags(t.text))
264
+ .split(" ")
265
+ .slice(0, 12)
266
+ .join(" ");
267
+ return words && !visible.includes(` ${words} `);
268
+ });
269
+ return hidden.length
270
+ ? make(`${hidden.length} of ${texts.length} marked-up question(s), answer(s) or step(s) do not appear in the page's HTML text.`, {
271
+ selector: 'script[type="application/ld+json"]',
272
+ evidence: {
273
+ missing: cap(hidden.map((t) => ({
274
+ type: t.type,
275
+ text: t.text.slice(0, 120),
276
+ }))),
277
+ },
278
+ })
279
+ : null;
280
+ }),
281
+ defineRule({
282
+ id: "aeo.article-author-missing",
283
+ category: "aeo",
284
+ severity: "warning",
285
+ description: "Article structured data names no author.",
286
+ recommendation: "Add author as a Person (or Organization) with name and url, the url pointing to an author page. Answer engines weigh who stands behind the content.",
287
+ docs: AEO_DOCS.article,
288
+ }, ({ page }, make) => {
289
+ const missing = articles(page).filter(({ node }) => {
290
+ const { author } = node;
291
+ return (author === undefined ||
292
+ author === null ||
293
+ (Array.isArray(author) && author.length === 0) ||
294
+ (isString(author) && !author.trim()));
295
+ });
296
+ return missing.length
297
+ ? make(`${missing[0]?.types[0] ?? "Article"} structured data has no author.`, {
298
+ selector: 'script[type="application/ld+json"]',
299
+ evidence: { types: cap(missing.flatMap((m) => m.types)) },
300
+ })
301
+ : null;
302
+ }),
303
+ defineRule({
304
+ id: "aeo.article-author-incomplete",
305
+ category: "aeo",
306
+ severity: "info",
307
+ description: "The article's author is a bare name, without a url or sameAs that identifies the person.",
308
+ recommendation: 'Write the author as {"@type": "Person", "name": ..., "url": ...}, with url pointing to an author page, and sameAs listing the person\'s profiles where they exist.',
309
+ docs: AEO_DOCS.article,
310
+ }, ({ page }, make) => {
311
+ const bare = articles(page).flatMap(({ node }) => {
312
+ const authors = Array.isArray(node["author"])
313
+ ? node["author"]
314
+ : [node["author"]];
315
+ return authors.flatMap((a) => {
316
+ if (isString(a) && a.trim()) {
317
+ return [a.trim()];
318
+ }
319
+ if (isJsonObject(a) &&
320
+ a["url"] === undefined &&
321
+ a["sameAs"] === undefined) {
322
+ return [nameOf(a) ?? "(unnamed author)"];
323
+ }
324
+ return [];
325
+ });
326
+ });
327
+ return bare.length
328
+ ? make(`Article author ${bare.map((n) => `"${n}"`).join(", ")} has no url or sameAs.`, {
329
+ selector: 'script[type="application/ld+json"]',
330
+ evidence: { authors: cap(bare) },
331
+ })
332
+ : null;
333
+ }),
334
+ defineRule({
335
+ id: "aeo.article-dates-missing",
336
+ category: "aeo",
337
+ severity: "warning",
338
+ description: "Article structured data lacks datePublished (warning) or dateModified (info), which tell answer engines how fresh the content is.",
339
+ recommendation: "Add datePublished and dateModified in ISO 8601 (2026-03-14T09:30:00+01:00), and update dateModified when the content changes materially.",
340
+ docs: AEO_DOCS.article,
341
+ }, ({ page }, make) => {
342
+ const [article] = articles(page);
343
+ if (!article) {
344
+ return null;
345
+ }
346
+ const missing = ["datePublished", "dateModified"].filter((p) => {
347
+ const value = article.node[p];
348
+ return !(isString(value) && value.trim());
349
+ });
350
+ if (!missing.length) {
351
+ return null;
352
+ }
353
+ return make(`${article.types[0] ?? "Article"} structured data has no ${missing.join(" or ")}.`, {
354
+ severity: missing.includes("datePublished") ? "warning" : "info",
355
+ selector: 'script[type="application/ld+json"]',
356
+ evidence: { missing },
357
+ });
358
+ }),
359
+ defineRule({
360
+ id: "aeo.article-date-not-visible",
361
+ category: "aeo",
362
+ severity: "info",
363
+ description: "The article's structured data has a date, but the page shows none.",
364
+ recommendation: "Show the publish or update date near the title, matching the structured data; a <time datetime> element works well. Readers and answer engines use it to judge freshness.",
365
+ docs: AEO_DOCS.dates,
366
+ }, ({ page }, make) => {
367
+ const [article] = articles(page);
368
+ if (!article || page.wordCount === 0) {
369
+ return null;
370
+ }
371
+ const years = [
372
+ yearOf(article.node["datePublished"]),
373
+ yearOf(article.node["dateModified"]),
374
+ ].filter((y) => y !== null);
375
+ if (!years.length ||
376
+ page.answer.timeElements > 0 ||
377
+ page.answer.mentionsDate ||
378
+ years.some((y) => page.text.includes(y))) {
379
+ return null;
380
+ }
381
+ return make("Article has a date in its structured data, but none on the page.", {
382
+ evidence: {
383
+ datePublished: article.node["datePublished"] ?? null,
384
+ dateModified: article.node["dateModified"] ?? null,
385
+ },
386
+ });
387
+ }),
388
+ defineRule({
389
+ id: "aeo.organization-missing",
390
+ category: "aeo",
391
+ severity: "info",
392
+ description: "The home page has no Organization (or Person) structured data.",
393
+ recommendation: "Add Organization JSON-LD to the home page (name, url, logo, and sameAs links to official profiles) so answer engines can tie the site to a known entity. Use Person for a personal site.",
394
+ docs: AEO_DOCS.organization,
395
+ }, ({ page, source }, make) => source !== "html" &&
396
+ isHomePage(page.url) &&
397
+ page.wordCount > 0 &&
398
+ !typedNodes(page.structuredData).some(isEntity)
399
+ ? make("Home page has no Organization or Person structured data.")
400
+ : null),
401
+ defineRule({
402
+ id: "aeo.entity-same-as-missing",
403
+ category: "aeo",
404
+ severity: "info",
405
+ description: "Organization or Person structured data has no sameAs links to the entity's profiles elsewhere.",
406
+ recommendation: "Add sameAs with the URLs of the entity's official profiles (Wikipedia, Wikidata, LinkedIn, social accounts) so answer engines can match it to the entity they know.",
407
+ docs: AEO_DOCS.organization,
408
+ }, ({ page }, make) => {
409
+ const bare = typedNodes(page.structuredData).filter((n) => n.topLevel && isEntity(n) && n.node["sameAs"] === undefined);
410
+ return bare.length
411
+ ? make(`${bare[0]?.types[0] ?? "Organization"} "${nameOf(bare[0]?.node["name"]) ?? "unnamed"}" has no sameAs links.`, {
412
+ selector: 'script[type="application/ld+json"]',
413
+ evidence: {
414
+ entities: cap(bare.map((n) => ({
415
+ type: n.types[0],
416
+ name: nameOf(n.node["name"]),
417
+ }))),
418
+ },
419
+ })
420
+ : null;
421
+ }),
422
+ ];
423
+ //# sourceMappingURL=aeo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aeo.js","sourceRoot":"","sources":["../../../src/core/rules/aeo.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EACR,UAAU,EACV,aAAa,EACb,UAAU,EACV,YAAY,EACZ,QAAQ,EACR,UAAU,EACV,aAAa,EACb,UAAU,EACV,MAAM,EACN,iBAAiB,EACjB,SAAS,EACT,UAAU,GACX,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAEpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAEzD,2DAA2D;AAC3D,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,QAAQ,EAAE,kEAAkE;IAC5E,IAAI,EAAE,oEAAoE;IAC1E,UAAU,EACR,iFAAiF;IACnF,sBAAsB,EACpB,kFAAkF;IACpF,OAAO,EACL,8EAA8E;IAChF,KAAK,EACH,wEAAwE;IAC1E,YAAY,EACV,mFAAmF;CAC7E,CAAC;AAEX,uFAAuF;AACvF,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAE9B,MAAM,SAAS,GAAG,CAChB,IAAyC,EACzC,MAA0B,EAC1B,SAAiC,EACvB,EAAE;IACZ,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,IACE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,EACtE,CAAC;YACD,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC;IAED,IAAI,MAAM,IAAI,iBAAiB,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC;QAC5E,OAAO,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC/B,CAAC;IAED,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;AAC/B,CAAC,CAAC;AAEF,MAAM,YAAY,GAAG,CAAC,OAAiB,EAAsB,EAAE;IAC7D,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC;IAExD,OAAO,IAAI;QACT,CAAC,CAAC,qBAAqB,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI;QAChE,CAAC,CAAC,SAAS,CAAC;AAChB,CAAC,CAAC;AAEF,MAAM,MAAM,GAAG,CAAC,CAAS,EAAW,EAAE,CAAC,CAAC,KAAK,MAAM,IAAI,CAAC,KAAK,WAAW,CAAC;AAEzE,MAAM,MAAM,GAAG,CAAC,KAA4B,EAAiB,EAAE,CAC7D,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAEvE,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,UAAU,CACR;QACE,EAAE,EAAE,gBAAgB;QACpB,QAAQ,EAAE,KAAK;QACf,QAAQ,EAAE,OAAO;QACjB,WAAW,EACT,2FAA2F;QAC7F,cAAc,EACZ,0GAA0G;QAC5G,IAAI,EAAE,QAAQ,CAAC,UAAU;KAC1B,EACD,CAAC,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,EAAE;QACrB,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;YACnB,OAAO,IAAI,CAAC,kCAAkC,QAAQ,CAAC,KAAK,GAAG,EAAE;gBAC/D,QAAQ,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE;aACpC,CAAC,CAAC;QACL,CAAC;QAED,OAAO,QAAQ,CAAC,MAAM,KAAK,IAAI,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG;YACvD,CAAC,CAAC,IAAI,CAAC,yBAAyB,QAAQ,CAAC,MAAM,GAAG,EAAE;gBAChD,QAAQ,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE;aACtC,CAAC;YACJ,CAAC,CAAC,IAAI,CAAC;IACX,CAAC,CACF;IACD,UAAU,CACR;QACE,EAAE,EAAE,aAAa;QACjB,QAAQ,EAAE,KAAK;QACf,QAAQ,EAAE,SAAS;QACnB,WAAW,EACT,2FAA2F;QAC7F,cAAc,EACZ,6MAA6M;QAC/M,IAAI,EAAE,QAAQ,CAAC,QAAQ;KACxB,EACD,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,EAAE;QAC3B,MAAM,CAAC,GAAG,gBAAgB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAE3C,OAAO,CAAC,CAAC,OAAO;YACd,CAAC,CAAC,IAAI,CACF,8BAA8B,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,gHAAgH,EACrK;gBACE,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;oBAClC,CAAC,CAAC,4BAA4B;oBAC9B,CAAC,CAAC,SAAS;gBACb,QAAQ,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE;aAC3D,CACF;YACH,CAAC,CAAC,IAAI,CAAC;IACX,CAAC,CACF;IACD,UAAU,CACR;QACE,EAAE,EAAE,eAAe;QACnB,QAAQ,EAAE,KAAK;QACf,QAAQ,EAAE,SAAS;QACnB,WAAW,EACT,+HAA+H;QACjI,cAAc,EACZ,sJAAsJ;QACxJ,IAAI,EAAE,QAAQ,CAAC,QAAQ;KACxB,EACD,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,EAAE;QAC3B,MAAM,MAAM,GAAG,aAAa,CAAC,QAAQ,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;QACvD,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;QAEnD,MAAM,OAAO,GAAG;YACd,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAClE,GAAG,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;SAC7D,CAAC;QAEF,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;YACpB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,OAAO,GAAG,SAAS,CACvB,IAAI,CAAC,MAAM,CAAC,UAAU,EACtB,QAAQ,EAAE,OAAO,CAAC,cAAc,CAAC,EACjC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,WAAW,IAAI,CAAC,KAAK,eAAe,CAClD,CAAC;QAEF,OAAO,IAAI,CACT,2BAA2B,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,+CAA+C,EAC5H;YACE,QAAQ,EAAE,YAAY,CAAC,OAAO,CAAC;YAC/B,QAAQ,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE;SAC/B,CACF,CAAC;IACJ,CAAC,CACF;IACD,UAAU,CACR;QACE,EAAE,EAAE,uBAAuB;QAC3B,QAAQ,EAAE,KAAK;QACf,QAAQ,EAAE,MAAM;QAChB,WAAW,EACT,sGAAsG;QACxG,cAAc,EACZ,qGAAqG;QACvG,IAAI,EAAE,WAAW,CAAC,UAAU;KAC7B,EACD,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,EAAE;QAC3B,MAAM,UAAU,GAAG,aAAa,CAAC,QAAQ,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;QAC3D,MAAM,KAAK,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;QAErC,OAAO,KAAK,KAAK,IAAI;YACnB,KAAK,GAAG,CAAC;YACT,KAAK,GAAG,GAAG;YACX,CAAC,UAAU,CAAC,UAAU,CAAC;YACvB,CAAC,CAAC,IAAI,CACF,eAAe,KAAK,8BAA8B,KAAK,2BAA2B,EAClF,EAAE,QAAQ,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,EAAE,CACpC;YACH,CAAC,CAAC,IAAI,CAAC;IACX,CAAC,CACF;IACD,UAAU,CACR;QACE,EAAE,EAAE,eAAe;QACnB,QAAQ,EAAE,KAAK;QACf,QAAQ,EAAE,SAAS;QACnB,WAAW,EACT,2FAA2F;QAC7F,cAAc,EACZ,uLAAuL;QACzL,IAAI,EAAE,QAAQ,CAAC,IAAI;KACpB,EACD,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,EAAE;QAC3B,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;QAEnD,iEAAiE;QACjE,IACE,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC;YAC3B,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC;YACxB,UAAU,CAAC,IAAI,CAAC,EAChB,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,OAAO,GAAG,SAAS,CACvB,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,EAC5D,QAAQ,EAAE,OAAO,CAAC,cAAc,CAAC,EACjC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,WAAW,CACzB,CAAC;QAEF,OAAO,IAAI,CACT,2BAA2B,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,4CAA4C,EAC5F,EAAE,QAAQ,EAAE,YAAY,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,EAAE,OAAO,EAAE,EAAE,CAC3D,CAAC;IACJ,CAAC,CACF;IACD,UAAU,CACR;QACE,EAAE,EAAE,aAAa;QACjB,QAAQ,EAAE,KAAK;QACf,QAAQ,EAAE,MAAM;QAChB,WAAW,EACT,wEAAwE;QAC1E,cAAc,EACZ,iFAAiF;QACnF,IAAI,EAAE,QAAQ,CAAC,IAAI;KACpB,EACD,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,EAAE;QAC3B,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;QAEnD,OAAO,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YAClD,CAAC,CAAC,IAAI,CACF,wEAAwE,CACzE;YACH,CAAC,CAAC,IAAI,CAAC;IACX,CAAC,CACF;IACD,UAAU,CACR;QACE,EAAE,EAAE,UAAU;QACd,QAAQ,EAAE,KAAK;QACf,QAAQ,EAAE,MAAM;QAChB,WAAW,EACT,2FAA2F;QAC7F,cAAc,EACZ,mPAAmP;KACtP,EACD,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,EAAE;QAC3B,MAAM,OAAO,GAAG,SAAS,CACvB,IAAI,CAAC,MAAM,CAAC,UAAU,EACtB,QAAQ,EAAE,OAAO,CAAC,cAAc,CAAC,EACjC,MAAM,CACP,CAAC;QAEF,OAAO,OAAO,CAAC,MAAM;YACnB,CAAC,CAAC,IAAI,CACF,gCAAgC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,sEAAsE,EAC3H,EAAE,QAAQ,EAAE,YAAY,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,EAAE,OAAO,EAAE,EAAE,CAC3D;YACH,CAAC,CAAC,IAAI,CAAC;IACX,CAAC,CACF;IACD,UAAU,CACR;QACE,EAAE,EAAE,oBAAoB;QACxB,QAAQ,EAAE,KAAK;QACf,QAAQ,EAAE,MAAM;QAChB,WAAW,EACT,qGAAqG;QACvG,cAAc,EACZ,sHAAsH;QACxH,IAAI,EAAE,WAAW,CAAC,UAAU;KAC7B,EACD,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE;QACjB,MAAM,EAAE,cAAc,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,GAC7D,IAAI,CAAC,MAAM,CAAC;QAEd,IAAI,CAAC,cAAc,IAAI,IAAI,CAAC,SAAS,GAAG,EAAE,EAAE,CAAC;YAC3C,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,KAAK,GAAG,cAAc,GAAG,IAAI,CAAC,SAAS,CAAC;QAE9C,IAAI,KAAK,GAAG,GAAG,EAAE,CAAC;YAChB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,IAAI,CACT,wBAAwB,cAAc,OAAO,IAAI,CAAC,SAAS,WAAW,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,CAAC,oCAAoC,EACjI;YACE,QAAQ,EAAE,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM;YAC3C,QAAQ,EAAE,kBAAkB,CAAC,CAAC,CAAC;YAC/B,QAAQ,EAAE;gBACR,QAAQ,EAAE,iBAAiB;gBAC3B,KAAK,EAAE,cAAc;gBACrB,SAAS,EAAE,GAAG,CAAC,kBAAkB,CAAC;aACnC;SACF,CACF,CAAC;IACJ,CAAC,CACF;IACD,UAAU,CACR;QACE,EAAE,EAAE,2BAA2B;QAC/B,QAAQ,EAAE,KAAK;QACf,QAAQ,EAAE,SAAS;QACnB,WAAW,EACT,4GAA4G;QAC9G,cAAc,EACZ,wKAAwK;QAC1K,IAAI,EAAE,WAAW,CAAC,UAAU;KAC7B,EACD,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE;QACjB,MAAM,EAAE,YAAY,EAAE,yBAAyB,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC;QAEhE,IACE,IAAI,CAAC,SAAS,IAAI,EAAE;YACpB,CAAC,CAAC,YAAY,IAAI,CAAC,yBAAyB,CAAC,EAC7C,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,KAAK,GAAG,YAAY;YACxB,CAAC,CAAC,sBAAsB,YAAY,GAAG;YACvC,CAAC,CAAC,4CAA4C,CAAC;QAEjD,OAAO,IAAI,CACT,gBAAgB,IAAI,CAAC,SAAS,sBAAsB,KAAK,wGAAwG,EACjK;YACE,QAAQ,EAAE,YAAY,IAAI,UAAU;YACpC,QAAQ,EAAE;gBACR,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,YAAY;gBACZ,yBAAyB;aAC1B;SACF,CACF,CAAC;IACJ,CAAC,CACF;IACD,UAAU,CACR;QACE,EAAE,EAAE,iCAAiC;QACrC,QAAQ,EAAE,KAAK;QACf,QAAQ,EAAE,MAAM;QAChB,WAAW,EACT,mEAAmE;QACrE,cAAc,EACZ,4MAA4M;KAC/M,EACD,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE;QACjB,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CACvC,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,CAAC,YAAY,KAAK,CAAC;YACpB,CAAC,CAAC,CAAC,eAAe,IAAI,CAAC,CAAC,YAAY,CAAC,GAAG,iBAAiB,CAC5D,CAAC;QAEF,OAAO,IAAI,CAAC,MAAM;YAChB,CAAC,CAAC,IAAI,CACF,GAAG,IAAI,CAAC,MAAM,OAAO,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,iEAAiE,EAClH;gBACE,QAAQ,EAAE;oBACR,SAAS,EAAE,GAAG,CACZ,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;wBACf,QAAQ,EAAE,CAAC,CAAC,IAAI;wBAChB,eAAe,EAAE,CAAC,CAAC,eAAe;wBAClC,YAAY,EAAE,CAAC,CAAC,YAAY;qBAC7B,CAAC,CAAC,CACJ;iBACF;aACF,CACF;YACH,CAAC,CAAC,IAAI,CAAC;IACX,CAAC,CACF;IACD,UAAU,CACR;QACE,EAAE,EAAE,uBAAuB;QAC3B,QAAQ,EAAE,KAAK;QACf,QAAQ,EAAE,MAAM;QAChB,WAAW,EACT,+DAA+D;QACjE,cAAc,EACZ,gLAAgL;KACnL,EACD,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,CACjB,IAAI,CAAC,SAAS,IAAI,GAAG;QACrB,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;QACxC,IAAI,CAAC,MAAM,CAAC,KAAK,KAAK,CAAC;QACvB,IAAI,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;QACtB,CAAC,CAAC,IAAI,CACF,YAAY,IAAI,CAAC,SAAS,8CAA8C,EACxE,EAAE,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,EAAE,CAC5C;QACH,CAAC,CAAC,IAAI,CACX;IACD,UAAU,CACR;QACE,EAAE,EAAE,6BAA6B;QACjC,QAAQ,EAAE,KAAK;QACf,QAAQ,EAAE,SAAS;QACnB,WAAW,EACT,kHAAkH;QACpH,cAAc,EACZ,2VAA2V;QAC7V,IAAI,EAAE,QAAQ,CAAC,sBAAsB;KACtC,EACD,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE;QACjB,IAAI,IAAI,CAAC,SAAS,KAAK,CAAC,EAAE,CAAC;YACzB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;QAElC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;YAClB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;QAE7C,+FAA+F;QAC/F,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;YAChC,MAAM,KAAK,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;iBACxC,KAAK,CAAC,GAAG,CAAC;iBACV,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;iBACZ,IAAI,CAAC,GAAG,CAAC,CAAC;YAEb,OAAO,KAAK,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC;QAClD,CAAC,CAAC,CAAC;QAEH,OAAO,MAAM,CAAC,MAAM;YAClB,CAAC,CAAC,IAAI,CACF,GAAG,MAAM,CAAC,MAAM,OAAO,KAAK,CAAC,MAAM,qFAAqF,EACxH;gBACE,QAAQ,EAAE,oCAAoC;gBAC9C,QAAQ,EAAE;oBACR,OAAO,EAAE,GAAG,CACV,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;wBACjB,IAAI,EAAE,CAAC,CAAC,IAAI;wBACZ,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;qBAC3B,CAAC,CAAC,CACJ;iBACF;aACF,CACF;YACH,CAAC,CAAC,IAAI,CAAC;IACX,CAAC,CACF;IACD,UAAU,CACR;QACE,EAAE,EAAE,4BAA4B;QAChC,QAAQ,EAAE,KAAK;QACf,QAAQ,EAAE,SAAS;QACnB,WAAW,EAAE,0CAA0C;QACvD,cAAc,EACZ,qJAAqJ;QACvJ,IAAI,EAAE,QAAQ,CAAC,OAAO;KACvB,EACD,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE;QACjB,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE;YACjD,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;YAExB,OAAO,CACL,MAAM,KAAK,SAAS;gBACpB,MAAM,KAAK,IAAI;gBACf,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC;gBAC9C,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CACrC,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,OAAO,OAAO,CAAC,MAAM;YACnB,CAAC,CAAC,IAAI,CACF,GAAG,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,SAAS,iCAAiC,EACrE;gBACE,QAAQ,EAAE,oCAAoC;gBAC9C,QAAQ,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE;aAC1D,CACF;YACH,CAAC,CAAC,IAAI,CAAC;IACX,CAAC,CACF;IACD,UAAU,CACR;QACE,EAAE,EAAE,+BAA+B;QACnC,QAAQ,EAAE,KAAK;QACf,QAAQ,EAAE,MAAM;QAChB,WAAW,EACT,0FAA0F;QAC5F,cAAc,EACZ,oKAAoK;QACtK,IAAI,EAAE,QAAQ,CAAC,OAAO;KACvB,EACD,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE;QACjB,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE;YAC/C,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;gBAC3C,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAChB,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;YAErB,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;gBAC3B,IAAI,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;oBAC5B,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;gBACpB,CAAC;gBAED,IACE,YAAY,CAAC,CAAC,CAAC;oBACf,CAAC,CAAC,KAAK,CAAC,KAAK,SAAS;oBACtB,CAAC,CAAC,QAAQ,CAAC,KAAK,SAAS,EACzB,CAAC;oBACD,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,kBAAkB,CAAC,CAAC;gBAC3C,CAAC;gBAED,OAAO,EAAE,CAAC;YACZ,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,OAAO,IAAI,CAAC,MAAM;YAChB,CAAC,CAAC,IAAI,CACF,kBAAkB,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,wBAAwB,EAC9E;gBACE,QAAQ,EAAE,oCAAoC;gBAC9C,QAAQ,EAAE,EAAE,OAAO,EAAE,GAAG,CAAC,IAAI,CAAC,EAAE;aACjC,CACF;YACH,CAAC,CAAC,IAAI,CAAC;IACX,CAAC,CACF;IACD,UAAU,CACR;QACE,EAAE,EAAE,2BAA2B;QAC/B,QAAQ,EAAE,KAAK;QACf,QAAQ,EAAE,SAAS;QACnB,WAAW,EACT,mIAAmI;QACrI,cAAc,EACZ,0IAA0I;QAC5I,IAAI,EAAE,QAAQ,CAAC,OAAO;KACvB,EACD,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE;QACjB,MAAM,CAAC,OAAO,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;QAEjC,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,OAAO,GAAG,CAAC,eAAe,EAAE,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;YAC7D,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAE9B,OAAO,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;YACpB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,IAAI,CACT,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,SAAS,2BAA2B,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,EAClF;YACE,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM;YAChE,QAAQ,EAAE,oCAAoC;YAC9C,QAAQ,EAAE,EAAE,OAAO,EAAE;SACtB,CACF,CAAC;IACJ,CAAC,CACF;IACD,UAAU,CACR;QACE,EAAE,EAAE,8BAA8B;QAClC,QAAQ,EAAE,KAAK;QACf,QAAQ,EAAE,MAAM;QAChB,WAAW,EACT,oEAAoE;QACtE,cAAc,EACZ,2KAA2K;QAC7K,IAAI,EAAE,QAAQ,CAAC,KAAK;KACrB,EACD,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE;QACjB,MAAM,CAAC,OAAO,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;QAEjC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,SAAS,KAAK,CAAC,EAAE,CAAC;YACrC,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,KAAK,GAAG;YACZ,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;YACrC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;SACrC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;QAEzC,IACE,CAAC,KAAK,CAAC,MAAM;YACb,IAAI,CAAC,MAAM,CAAC,YAAY,GAAG,CAAC;YAC5B,IAAI,CAAC,MAAM,CAAC,YAAY;YACxB,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EACxC,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,IAAI,CACT,kEAAkE,EAClE;YACE,QAAQ,EAAE;gBACR,aAAa,EAAE,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,IAAI,IAAI;gBACpD,YAAY,EAAE,OAAO,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,IAAI;aACnD;SACF,CACF,CAAC;IACJ,CAAC,CACF;IACD,UAAU,CACR;QACE,EAAE,EAAE,0BAA0B;QAC9B,QAAQ,EAAE,KAAK;QACf,QAAQ,EAAE,MAAM;QAChB,WAAW,EACT,gEAAgE;QAClE,cAAc,EACZ,0LAA0L;QAC5L,IAAI,EAAE,QAAQ,CAAC,YAAY;KAC5B,EACD,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,EAAE,CACzB,MAAM,KAAK,MAAM;QACjB,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC;QACpB,IAAI,CAAC,SAAS,GAAG,CAAC;QAClB,CAAC,UAAU,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC;QAC7C,CAAC,CAAC,IAAI,CAAC,0DAA0D,CAAC;QAClE,CAAC,CAAC,IAAI,CACX;IACD,UAAU,CACR;QACE,EAAE,EAAE,4BAA4B;QAChC,QAAQ,EAAE,KAAK;QACf,QAAQ,EAAE,MAAM;QAChB,WAAW,EACT,gGAAgG;QAClG,cAAc,EACZ,qKAAqK;QACvK,IAAI,EAAE,QAAQ,CAAC,YAAY;KAC5B,EACD,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE;QACjB,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,MAAM,CACjD,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,IAAI,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,SAAS,CACnE,CAAC;QAEF,OAAO,IAAI,CAAC,MAAM;YAChB,CAAC,CAAC,IAAI,CACF,GAAG,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,cAAc,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,SAAS,wBAAwB,EAC7G;gBACE,QAAQ,EAAE,oCAAoC;gBAC9C,QAAQ,EAAE;oBACR,QAAQ,EAAE,GAAG,CACX,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;wBACf,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;wBAChB,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;qBAC7B,CAAC,CAAC,CACJ;iBACF;aACF,CACF;YACH,CAAC,CAAC,IAAI,CAAC;IACX,CAAC,CACF;CACF,CAAC"}
@@ -1,2 +1,2 @@
1
- export declare function isLocalHost(url: string): boolean;
1
+ export declare const isLocalHost: (url: string) => boolean;
2
2
  export declare const canonicalRules: import("./rule.js").Rule[];