@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,312 @@
1
+ import { isJsonObject, isString, typesOf } from "../html/structured-data.js";
2
+ import { robotsDirectives } from "../rules/indexing.js";
3
+ import { normalizePath } from "../url.js";
4
+ /** Meta tags each engine reads: the generic one and its own. */
5
+ const META_NAMES = {
6
+ google: ["robots", "googlebot"],
7
+ bing: ["robots", "bingbot"],
8
+ };
9
+ const HEADER_AGENT = {
10
+ google: "googlebot",
11
+ bing: "bingbot",
12
+ };
13
+ /** Directives that carry a value after a colon, which is not a user agent prefix. */
14
+ const VALUE_DIRECTIVES = new Set([
15
+ "max-snippet",
16
+ "max-image-preview",
17
+ "max-video-preview",
18
+ "unavailable_after",
19
+ ]);
20
+ /**
21
+ * X-Robots-Tag values. "googlebot: nosnippet, noarchive" scopes every directive after the prefix
22
+ * to googlebot, up to the next prefix.
23
+ */
24
+ export const parseRobotsHeader = (value) => {
25
+ const out = [];
26
+ let agent = null;
27
+ for (const piece of value.toLowerCase().split(",")) {
28
+ let directive = piece.trim();
29
+ const match = /^(?<name>[a-z0-9_-]+)\s*:\s*(?<rest>.*)$/u.exec(directive);
30
+ const name = match?.groups?.name;
31
+ if (name && !VALUE_DIRECTIVES.has(name)) {
32
+ agent = name;
33
+ directive = match.groups?.rest?.trim() ?? "";
34
+ }
35
+ if (directive) {
36
+ out.push({ agent, directive: directive.replaceAll(/\s+/gu, "") });
37
+ }
38
+ }
39
+ return out;
40
+ };
41
+ /** The robots directives one engine applies to the page, from its meta tags and X-Robots-Tag. */
42
+ export const directivesFor = (engine, page, response) => {
43
+ const out = [];
44
+ for (const meta of page.answer.robotsMeta) {
45
+ if (META_NAMES[engine].includes(meta.name)) {
46
+ for (const d of meta.content.split(",")) {
47
+ const directive = d.replaceAll(/\s+/gu, "");
48
+ if (directive) {
49
+ out.push(directive);
50
+ }
51
+ }
52
+ }
53
+ }
54
+ const header = response?.headers["x-robots-tag"];
55
+ if (header) {
56
+ for (const d of parseRobotsHeader(header)) {
57
+ if (d.agent === null || d.agent === HEADER_AGENT[engine]) {
58
+ out.push(d.directive);
59
+ }
60
+ }
61
+ }
62
+ return [...new Set(out)];
63
+ };
64
+ /** The `max-snippet` limit, or null when none is set (-1 means no limit). */
65
+ export const maxSnippet = (directives) => {
66
+ for (const d of directives) {
67
+ if (d.startsWith("max-snippet:")) {
68
+ const n = Number(d.slice("max-snippet:".length));
69
+ if (Number.isInteger(n)) {
70
+ return n;
71
+ }
72
+ }
73
+ }
74
+ return null;
75
+ };
76
+ export const hasNoindex = (directives) => directives.includes("noindex") || directives.includes("none");
77
+ export const hasNosnippet = (directives) => directives.includes("nosnippet") || maxSnippet(directives) === 0;
78
+ /** Every typed object in the page's structured data, nested ones included. */
79
+ export const typedNodes = (items) => {
80
+ const out = [];
81
+ const seen = new Set();
82
+ const walk = (value, depth, top) => {
83
+ if (depth > 6 || out.length >= 500 || value === undefined) {
84
+ return;
85
+ }
86
+ if (Array.isArray(value)) {
87
+ for (const v of value) {
88
+ walk(v, depth, top);
89
+ }
90
+ return;
91
+ }
92
+ if (!isJsonObject(value) || seen.has(value)) {
93
+ return;
94
+ }
95
+ seen.add(value);
96
+ const types = typesOf(value["@type"]);
97
+ if (types.length) {
98
+ out.push({ types, node: value, topLevel: top });
99
+ }
100
+ for (const [key, v] of Object.entries(value)) {
101
+ if (!key.startsWith("@")) {
102
+ walk(v, depth + 1, false);
103
+ }
104
+ }
105
+ };
106
+ for (const item of items) {
107
+ // SAFETY: extractStructuredData builds every item's data from JSON.parse or from microdata, and both hold JSON values alone.
108
+ walk(item.data, 0, true);
109
+ }
110
+ return out;
111
+ };
112
+ export const isArticleType = (type) => type.endsWith("Article") ||
113
+ type === "BlogPosting" ||
114
+ type === "LiveBlogPosting" ||
115
+ type === "Report";
116
+ /** schema.org types for an organization or a person, beyond the many that end in Organization or Business. */
117
+ const ENTITY_TYPES = new Set([
118
+ "Organization",
119
+ "Corporation",
120
+ "NGO",
121
+ "Airline",
122
+ "Consortium",
123
+ "Project",
124
+ "WorkersUnion",
125
+ "LibrarySystem",
126
+ "FundingScheme",
127
+ "LocalBusiness",
128
+ "Store",
129
+ "OnlineStore",
130
+ "Restaurant",
131
+ "Hotel",
132
+ "PerformingGroup",
133
+ "Person",
134
+ ]);
135
+ /**
136
+ * Whether a typed object describes an organization or a person. LocalBusiness alone has over a
137
+ * hundred subtypes (Dentist, Bakery, ...), so an object with a name and a logo, address or phone
138
+ * number counts too.
139
+ */
140
+ export const isEntity = ({ types, node }) => types.some((t) => ENTITY_TYPES.has(t) || /(?:Organization|Business|Store)$/u.test(t)) ||
141
+ (isString(node["name"]) &&
142
+ (node["logo"] !== undefined ||
143
+ node["address"] !== undefined ||
144
+ node["telephone"] !== undefined));
145
+ /** The text of a name-like value: a string, or the `name` of an object, or of the first in a list. */
146
+ export const nameOf = (value) => {
147
+ if (isString(value)) {
148
+ return value.trim() || null;
149
+ }
150
+ if (Array.isArray(value)) {
151
+ for (const v of value) {
152
+ const name = nameOf(v);
153
+ if (name) {
154
+ return name;
155
+ }
156
+ }
157
+ return null;
158
+ }
159
+ if (isJsonObject(value)) {
160
+ const { name } = value;
161
+ return isString(name) ? name.trim() || null : null;
162
+ }
163
+ return null;
164
+ };
165
+ const stringOf = (value) => isString(value) && value.trim() ? value.trim() : null;
166
+ export const articles = (page) => typedNodes(page.structuredData).filter((n) => n.types.some(isArticleType));
167
+ /** Lower-cased letters and digits only, so punctuation and spacing differences do not count. */
168
+ export const comparable = (value) => value
169
+ .toLowerCase()
170
+ .replaceAll(/[^\p{L}\p{N}]+/gu, " ")
171
+ .trim();
172
+ const NAMED_ENTITIES = new Map([
173
+ ["amp", "&"],
174
+ ["lt", "<"],
175
+ ["gt", ">"],
176
+ ["quot", '"'],
177
+ ["apos", "'"],
178
+ ["nbsp", " "],
179
+ ["rsquo", "’"],
180
+ ["lsquo", "‘"],
181
+ ["rdquo", "”"],
182
+ ["ldquo", "“"],
183
+ ["ndash", "–"],
184
+ ["mdash", "—"],
185
+ ]);
186
+ const decodeEntity = (entity, body) => {
187
+ if (body.startsWith("#")) {
188
+ const code = body[1] === "x" || body[1] === "X"
189
+ ? Number.parseInt(body.slice(2), 16)
190
+ : Number(body.slice(1));
191
+ return Number.isInteger(code) && code > 0 && code <= 0x10_ff_ff
192
+ ? String.fromCodePoint(code)
193
+ : entity;
194
+ }
195
+ return NAMED_ENTITIES.get(body.toLowerCase()) ?? entity;
196
+ };
197
+ /**
198
+ * Structured-data text may hold HTML: tags, and entities such as &amp;. `[^<>]` stops each attempt
199
+ * at the next bracket, so a run of "<" without ">" costs one pass, not one pass per bracket.
200
+ */
201
+ export const stripTags = (value) => value
202
+ .replaceAll(/<[^<>]*>/gu, " ")
203
+ .replaceAll(/&(?<body>#\d{1,7}|#[xX][\da-fA-F]{1,6}|[a-zA-Z]{2,8});/gu, (entity, body) => decodeEntity(entity, body));
204
+ const textsOf = (value) => {
205
+ if (value === undefined) {
206
+ return [];
207
+ }
208
+ return Array.isArray(value) ? value : [value];
209
+ };
210
+ const pushStep = (out, step) => {
211
+ if (isString(step)) {
212
+ out.push({ type: "HowToStep", text: step });
213
+ return;
214
+ }
215
+ if (!isJsonObject(step)) {
216
+ return;
217
+ }
218
+ // A HowToSection holds its steps in itemListElement.
219
+ const nested = step["itemListElement"];
220
+ if (nested !== undefined) {
221
+ for (const s of textsOf(nested)) {
222
+ pushStep(out, s);
223
+ }
224
+ return;
225
+ }
226
+ const text = stringOf(step["text"]) ?? stringOf(step["name"]);
227
+ if (text) {
228
+ out.push({ type: "HowToStep", text });
229
+ }
230
+ };
231
+ /** The questions, answers and steps FAQPage, QAPage and HowTo markup claims the page shows. */
232
+ export const markedUpTexts = (page) => {
233
+ const out = [];
234
+ for (const { types, node } of typedNodes(page.structuredData)) {
235
+ if (types.includes("FAQPage") || types.includes("QAPage")) {
236
+ for (const q of textsOf(node["mainEntity"])) {
237
+ if (!isJsonObject(q)) {
238
+ continue;
239
+ }
240
+ const question = stringOf(q["name"]) ?? stringOf(q["text"]);
241
+ if (question) {
242
+ out.push({ type: "Question", text: question });
243
+ }
244
+ for (const a of [
245
+ ...textsOf(q["acceptedAnswer"]),
246
+ ...textsOf(q["suggestedAnswer"]),
247
+ ]) {
248
+ const answer = isJsonObject(a) ? stringOf(a["text"]) : null;
249
+ if (answer) {
250
+ out.push({ type: "Answer", text: answer });
251
+ }
252
+ }
253
+ }
254
+ }
255
+ if (types.includes("HowTo")) {
256
+ for (const step of textsOf(node["step"])) {
257
+ pushStep(out, step);
258
+ }
259
+ }
260
+ }
261
+ return out.slice(0, 100);
262
+ };
263
+ /** Whether the URL is a site's home page (also /index.html in build output). */
264
+ export const isHomePage = (url) => {
265
+ try {
266
+ return normalizePath(new URL(url).pathname) === "/";
267
+ }
268
+ catch {
269
+ return false;
270
+ }
271
+ };
272
+ /* ---------------------------------------------------------------------------
273
+ * Summary
274
+ * ------------------------------------------------------------------------- */
275
+ const googleSnippets = (page, directives) => {
276
+ if (hasNosnippet(directives)) {
277
+ return "blocked";
278
+ }
279
+ const limit = maxSnippet(directives);
280
+ if ((limit !== null && limit > 0 && limit < 100) ||
281
+ page.answer.nosnippetWords > 0) {
282
+ return "limited";
283
+ }
284
+ return "allowed";
285
+ };
286
+ const copilotAnswers = (directives) => {
287
+ // Bing: NOCACHE wins when a page has both tags.
288
+ if (directives.includes("nocache")) {
289
+ return hasNoindex(directives) ? "excluded" : "snippet-only";
290
+ }
291
+ return hasNoindex(directives) || directives.includes("noarchive")
292
+ ? "excluded"
293
+ : "allowed";
294
+ };
295
+ export const aeoPageSummary = (page, response) => {
296
+ const article = articles(page)[0]?.node;
297
+ return {
298
+ indexable: !robotsDirectives(page, response).noindex &&
299
+ (response?.status ?? 200) < 400,
300
+ googleSnippets: googleSnippets(page, directivesFor("google", page, response)),
301
+ copilotAnswers: copilotAnswers(directivesFor("bing", page, response)),
302
+ wordCount: page.wordCount,
303
+ questions: page.answer.questions.length,
304
+ structuredDataTypes: [
305
+ ...new Set(page.structuredData.flatMap((i) => i.types)),
306
+ ],
307
+ author: article ? nameOf(article["author"]) : null,
308
+ datePublished: article ? stringOf(article["datePublished"]) : null,
309
+ dateModified: article ? stringOf(article["dateModified"]) : null,
310
+ };
311
+ };
312
+ //# sourceMappingURL=page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page.js","sourceRoot":"","sources":["../../../src/core/aeo/page.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { FetchOptions, FetchResult } from "../fetch.js";
2
+ import type { BotProbe } from "../types.js";
3
+ import type { AiBot } from "./bots.js";
4
+ /** What one bot's request says about access, given that the audit's own request got the page. */
5
+ export declare const classifyProbe: (res: FetchResult) => BotProbe;
6
+ /**
7
+ * Requests `url` once per bot with the bot's User-Agent, to catch a CDN or firewall that refuses
8
+ * AI bots while robots.txt allows them. The requests come from this machine, not the bots' IPs.
9
+ */
10
+ export declare const probeBots: (url: string, bots: AiBot[], options?: FetchOptions) => Promise<Map<string, BotProbe>>;
@@ -0,0 +1,53 @@
1
+ import { mapConcurrent } from "../concurrency.js";
2
+ import { fetchPage } from "../fetch.js";
3
+ /** Titles of the interstitials CDNs and bot protection show instead of the page. */
4
+ const CHALLENGE_TITLE = /^(?:just a moment|attention required|access denied|request blocked|verifying you are human|pardon our interruption)/iu;
5
+ /**
6
+ * The page title, enough to recognise a challenge page. `[^<>]` stops each attempt at the next
7
+ * bracket, which keeps the scan linear on a body full of "<title" without ">".
8
+ */
9
+ const TITLE = /<title[^<>]*>(?<title>[^<]{0,200})/iu;
10
+ /** What one bot's request says about access, given that the audit's own request got the page. */
11
+ export const classifyProbe = (res) => {
12
+ if (res.error) {
13
+ return {
14
+ status: res.status,
15
+ blocked: true,
16
+ reason: `connection failed: ${res.error}`,
17
+ };
18
+ }
19
+ if (res.status !== null && res.status >= 400) {
20
+ return { status: res.status, blocked: true, reason: `HTTP ${res.status}` };
21
+ }
22
+ const title = TITLE.exec(res.body ?? "")?.groups?.title?.trim() ?? "";
23
+ if (CHALLENGE_TITLE.test(title)) {
24
+ return {
25
+ status: res.status,
26
+ blocked: true,
27
+ reason: `challenge page ("${title}")`,
28
+ };
29
+ }
30
+ return { status: res.status, blocked: false, reason: null };
31
+ };
32
+ /**
33
+ * Requests `url` once per bot with the bot's User-Agent, to catch a CDN or firewall that refuses
34
+ * AI bots while robots.txt allows them. The requests come from this machine, not the bots' IPs.
35
+ */
36
+ export const probeBots = async (url, bots, options = {}) => {
37
+ const probes = new Map();
38
+ await mapConcurrent(bots, 3, async (bot) => {
39
+ if (!bot.userAgent) {
40
+ return;
41
+ }
42
+ const res = await fetchPage(url, {
43
+ ...options,
44
+ userAgent: bot.userAgent,
45
+ timeoutMs: Math.min(options.timeoutMs ?? 15_000, 15_000),
46
+ // Enough for the <title> of a challenge page.
47
+ maxBodyBytes: 100_000,
48
+ });
49
+ probes.set(bot.name, classifyProbe(res));
50
+ });
51
+ return probes;
52
+ };
53
+ //# sourceMappingURL=probe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"probe.js","sourceRoot":"","sources":["../../../src/core/aeo/probe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAKxC,oFAAoF;AACpF,MAAM,eAAe,GACnB,uHAAuH,CAAC;AAE1H;;;GAGG;AACH,MAAM,KAAK,GAAG,sCAAsC,CAAC;AAErD,iGAAiG;AACjG,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,GAAgB,EAAY,EAAE;IAC1D,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;QACd,OAAO;YACL,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,OAAO,EAAE,IAAI;YACb,MAAM,EAAE,sBAAsB,GAAG,CAAC,KAAK,EAAE;SAC1C,CAAC;IACJ,CAAC;IAED,IAAI,GAAG,CAAC,MAAM,KAAK,IAAI,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;QAC7C,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC;IAC7E,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAEtE,IAAI,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAChC,OAAO;YACL,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,OAAO,EAAE,IAAI;YACb,MAAM,EAAE,oBAAoB,KAAK,IAAI;SACtC,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AAC9D,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,KAAK,EAC5B,GAAW,EACX,IAAa,EACb,OAAO,GAAiB,EAAE,EACM,EAAE;IAClC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAoB,CAAC;IAE3C,MAAM,aAAa,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;QACzC,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC;YACnB,OAAO;QACT,CAAC;QAED,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE;YAC/B,GAAG,OAAO;YACV,SAAS,EAAE,GAAG,CAAC,SAAS;YACxB,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,IAAI,MAAM,EAAE,MAAM,CAAC;YACxD,8CAA8C;YAC9C,YAAY,EAAE,OAAO;SACtB,CAAC,CAAC;QAEH,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { AiCrawlerReport, Finding, LlmsTxtInfo, RuleMeta } from "../types.js";
2
+ /** Rules about the site as a whole, which the AEO audit checks once per origin. */
3
+ export declare const AEO_SITE_RULES: RuleMeta[];
4
+ export interface AeoSiteContext {
5
+ crawlers: AiCrawlerReport | null;
6
+ llmsTxt: LlmsTxtInfo | null;
7
+ /** Pages audited, for "blocked from N of M pages". */
8
+ pageCount: number;
9
+ /** Set when the caller asked for the bot probe but it could not run. */
10
+ probeSkipped?: string;
11
+ }
12
+ /** Findings about robots.txt, llms.txt and the bot probe, for the site the audited pages belong to. */
13
+ export declare const aeoSiteFindings: (ctx: AeoSiteContext) => Finding[];
@@ -0,0 +1,178 @@
1
+ import { cap, finding, GOOGLE_DOCS } from "../rules/rule.js";
2
+ import { AI_BOTS, invalidSignals } from "./bots.js";
3
+ const LLMS_TXT_DOCS = "https://llmstxt.org/";
4
+ const CONTENT_SIGNALS_DOCS = "https://contentsignals.org/";
5
+ const meta = (id, severity, description, recommendation, docs, requires) => {
6
+ const rule = {
7
+ id,
8
+ category: "aeo",
9
+ severity,
10
+ description,
11
+ recommendation,
12
+ };
13
+ if (docs) {
14
+ rule.docs = docs;
15
+ }
16
+ if (requires) {
17
+ rule.requires = requires;
18
+ }
19
+ return rule;
20
+ };
21
+ /** Rules about the site as a whole, which the AEO audit checks once per origin. */
22
+ export const AEO_SITE_RULES = [
23
+ meta("aeo.robots-txt-unreachable", "warning", 'robots.txt answers a server error or does not answer, and crawlers treat that as "crawl nothing" (RFC 9309).', "Make /robots.txt answer 200, or 404 when there is none. A 5xx or a timeout stops well-behaved crawlers, AI crawlers included.", GOOGLE_DOCS.robotsTxt),
24
+ meta("aeo.robots-blocks-search-crawler", "error", "robots.txt blocks a search crawler whose index AI answers draw on: Googlebot (AI Overviews, AI Mode), Bingbot (Copilot) or Applebot (Siri, Spotlight).", "Allow these crawlers in robots.txt: remove the Disallow lines that match the audited pages in their groups, or in `User-agent: *` when they have no group of their own. Applebot follows the Googlebot group when no group names Applebot.", "https://developers.google.com/search/docs/appearance/ai-features"),
25
+ meta("aeo.robots-blocks-answer-bot", "warning", "robots.txt blocks a bot that fetches pages for AI answers or an AI search index (OAI-SearchBot, ChatGPT-User, Claude-SearchBot, Claude-User, PerplexityBot, Perplexity-User, Google-Extended, DuckAssistBot, MistralAI-User).", "Allow the bots listed in evidence if the site should be cited in those answer engines: add a group for each with `Allow: /`, or remove the Disallow lines that match them. Keep a block that is deliberate, and disable this rule.", GOOGLE_DOCS.robotsTxt),
26
+ meta("aeo.robots-blocks-training-bot", "info", "robots.txt blocks a crawler that collects text to train AI models (GPTBot, ClaudeBot, Applebot-Extended, CCBot, meta-externalagent).", "Nothing to change if you mean to opt out of model training: these bots do not decide whether answer engines cite the site. Allow them if you want the content in future models.", GOOGLE_DOCS.robotsTxt),
27
+ meta("aeo.content-signal-blocks-answers", "warning", "A robots.txt Content-Signal line sets ai-input=no or search=no, asking answer engines not to use the pages in AI answers or search.", "Set ai-input=yes and search=yes in the Content-Signal line if answer engines should use the content; ai-train=no can stay to opt out of training. Example: `Content-Signal: search=yes, ai-input=yes, ai-train=no`.", CONTENT_SIGNALS_DOCS),
28
+ meta("aeo.content-signal-invalid", "info", "A robots.txt Content-Signal line has a signal other than search, ai-input or ai-train, or a value other than yes or no.", "Write Content-Signal as comma-separated pairs of search, ai-input or ai-train with yes or no, e.g. `Content-Signal: search=yes, ai-input=yes, ai-train=no`.", CONTENT_SIGNALS_DOCS),
29
+ meta("aeo.llms-txt-missing", "info", "The site has no /llms.txt.", "Optional: publish /llms.txt, a Markdown index of the pages that matter, for AI tools and coding agents that look for one. No major answer engine has said it reads llms.txt, so this is low priority.", LLMS_TXT_DOCS),
30
+ meta("aeo.llms-txt-invalid", "warning", "/llms.txt exists but does not follow the llms.txt format, which requires an H1 title first.", "Start the file with `# Site name`, then an optional `> one-line summary`, then H2 sections with links written as `- [Title](https://...): note`.", LLMS_TXT_DOCS),
31
+ meta("aeo.llms-txt-no-links", "info", "/llms.txt has no Markdown links, so it points AI tools to no pages.", "List the pages that matter under H2 sections, one per line: `- [Title](https://...): what it covers`.", LLMS_TXT_DOCS),
32
+ meta("aeo.server-blocks-answer-bot", "warning", "The server refuses or challenges an AI answer bot's User-Agent (403, 429, a challenge page) while it serves the audit's own request.", "Allow these bots at the CDN, firewall or bot-protection layer (for example the CDN's AI crawler settings or a WAF rule). The probe sent the bots' User-Agent from this machine, not from the bots' own IP ranges, and a CDN that verifies bots may let the real ones through: confirm in the CDN's bot logs before changing rules.", undefined, "ai-bots"),
33
+ meta("aeo.server-blocks-training-bot", "info", "The server refuses or challenges an AI training bot's User-Agent while it serves the audit's own request.", "Nothing to change if you mean to keep training bots out; this does not affect AI answers. The probe came from this machine, not the bots' own IP ranges.", undefined, "ai-bots"),
34
+ meta("aeo.bot-probe-inconclusive", "info", "The AI bot probe could not run: the target is not a URL, or the audit's own request did not get the page.", "Probe a live URL, and make the audit's own request succeed first (status, auth headers, --user-agent); otherwise blocked bots cannot be told apart from a page that fails for everyone.", undefined, "ai-bots"),
35
+ ];
36
+ const byId = new Map(AEO_SITE_RULES.map((r) => [r.id, r]));
37
+ const m = (id) => {
38
+ const rule = byId.get(id);
39
+ if (!rule) {
40
+ throw new Error(`Unknown AEO site rule: ${id}`);
41
+ }
42
+ return rule;
43
+ };
44
+ const ROBOTS_RULES = {
45
+ search: "aeo.robots-blocks-search-crawler",
46
+ answer: "aeo.robots-blocks-answer-bot",
47
+ training: "aeo.robots-blocks-training-bot",
48
+ };
49
+ const SERVER_RULES = {
50
+ answer: "aeo.server-blocks-answer-bot",
51
+ training: "aeo.server-blocks-training-bot",
52
+ };
53
+ const docsFor = (name) => AI_BOTS.find((b) => b.name === name)?.docs;
54
+ const robotsFindings = (crawlers, pageCount) => {
55
+ if (crawlers.status === "unreachable") {
56
+ return [
57
+ finding(m("aeo.robots-txt-unreachable"), `robots.txt at ${crawlers.robotsTxt} ${crawlers.httpStatus === null ? "did not answer" : `answers HTTP ${crawlers.httpStatus}`}, so crawlers that follow RFC 9309, AI crawlers included, treat the whole site as off limits.`, { evidence: { url: crawlers.robotsTxt, status: crawlers.httpStatus } }),
58
+ ];
59
+ }
60
+ const out = [];
61
+ for (const purpose of ["search", "answer", "training"]) {
62
+ const blocked = crawlers.bots.filter((b) => b.purpose === purpose && (!b.allowed || b.blockedPages > 0));
63
+ if (!blocked.length) {
64
+ continue;
65
+ }
66
+ const where = (b) => b.allowed
67
+ ? `${b.blockedPages} of ${pageCount} audited pages, which other crawlers may fetch`
68
+ : "the audited page";
69
+ out.push({
70
+ ...finding(m(ROBOTS_RULES[purpose]), `robots.txt blocks ${blocked.map((b) => b.name).join(", ")}.`, {
71
+ evidence: {
72
+ robotsTxt: crawlers.robotsTxt,
73
+ bots: cap(blocked.map((b) => ({
74
+ name: b.name,
75
+ operator: b.operator,
76
+ usedFor: b.usedFor,
77
+ blocked: pageCount > 1 ? where(b) : "the audited page",
78
+ group: b.robotsGroup,
79
+ docs: docsFor(b.name),
80
+ })), 20),
81
+ },
82
+ }),
83
+ url: crawlers.robotsTxt,
84
+ });
85
+ }
86
+ return out;
87
+ };
88
+ const signalFindings = (crawlers) => {
89
+ const out = [];
90
+ const answerAgents = new Set(AI_BOTS.filter((b) => b.purpose !== "training").map((b) => b.name.toLowerCase()));
91
+ const optOuts = crawlers.contentSignals.filter((s) => (s.signals["ai-input"] === "no" || s.signals["search"] === "no") &&
92
+ s.userAgents.some((a) => a === "*" || answerAgents.has(a.split("/")[0]?.toLowerCase() ?? "")));
93
+ if (optOuts.length) {
94
+ out.push({
95
+ ...finding(m("aeo.content-signal-blocks-answers"), `robots.txt Content-Signal asks ${optOuts
96
+ .map((s) => `${s.userAgents.join(", ") || "all crawlers"} for ${Object.entries(s.signals)
97
+ .map(([k, v]) => `${k}=${v}`)
98
+ .join(", ")}${s.path ? ` on ${s.path}` : ""}`)
99
+ .join("; ")}.`, { evidence: { signals: cap(optOuts) } }),
100
+ url: crawlers.robotsTxt,
101
+ });
102
+ }
103
+ const invalid = crawlers.contentSignals.flatMap(invalidSignals);
104
+ if (invalid.length) {
105
+ out.push({
106
+ ...finding(m("aeo.content-signal-invalid"), `robots.txt Content-Signal has values the policy does not define: ${cap(invalid, 5).join(", ")}.`, { evidence: { values: cap(invalid) } }),
107
+ url: crawlers.robotsTxt,
108
+ });
109
+ }
110
+ return out;
111
+ };
112
+ const llmsTxtFindings = (llms) => {
113
+ const at = { url: llms.url };
114
+ if (llms.status === "missing") {
115
+ return [
116
+ {
117
+ ...finding(m("aeo.llms-txt-missing"), `No llms.txt at ${llms.url}${llms.httpStatus ? ` (HTTP ${llms.httpStatus})` : ""}.`),
118
+ ...at,
119
+ },
120
+ ];
121
+ }
122
+ if (llms.status === "invalid") {
123
+ return [
124
+ {
125
+ ...finding(m("aeo.llms-txt-invalid"), `llms.txt does not follow the format: ${llms.problems.join("; ")}.`, { evidence: { problems: llms.problems, bytes: llms.bytes } }),
126
+ ...at,
127
+ },
128
+ ];
129
+ }
130
+ return llms.links === 0
131
+ ? [
132
+ {
133
+ ...finding(m("aeo.llms-txt-no-links"), `llms.txt "${llms.title ?? ""}" has no Markdown links.`),
134
+ ...at,
135
+ },
136
+ ]
137
+ : [];
138
+ };
139
+ const probeFindings = (crawlers) => {
140
+ const baseline = crawlers.probeBaseline;
141
+ if (!baseline) {
142
+ return [];
143
+ }
144
+ const out = [];
145
+ for (const purpose of ["answer", "training"]) {
146
+ const rule = SERVER_RULES[purpose];
147
+ const blocked = crawlers.bots.filter((b) => b.purpose === purpose && b.probe?.blocked);
148
+ if (!rule || !blocked.length) {
149
+ continue;
150
+ }
151
+ out.push(finding(m(rule), `The server refused or challenged ${blocked.map((b) => `${b.name} (${b.probe?.reason ?? "blocked"})`).join(", ")}, while the audit's own request got HTTP ${baseline.status}. The requests came from this machine with each bot's User-Agent, not from the bots' own IP ranges.`, {
152
+ evidence: {
153
+ baseline,
154
+ bots: cap(blocked.map((b) => ({
155
+ name: b.name,
156
+ status: b.probe?.status ?? null,
157
+ reason: b.probe?.reason ?? null,
158
+ })), 20),
159
+ },
160
+ }));
161
+ }
162
+ return out;
163
+ };
164
+ /** Findings about robots.txt, llms.txt and the bot probe, for the site the audited pages belong to. */
165
+ export const aeoSiteFindings = (ctx) => {
166
+ const out = [];
167
+ if (ctx.crawlers) {
168
+ out.push(...robotsFindings(ctx.crawlers, ctx.pageCount), ...signalFindings(ctx.crawlers), ...probeFindings(ctx.crawlers));
169
+ }
170
+ if (ctx.llmsTxt) {
171
+ out.push(...llmsTxtFindings(ctx.llmsTxt));
172
+ }
173
+ if (ctx.probeSkipped) {
174
+ out.push(finding(m("aeo.bot-probe-inconclusive"), `The AI bot probe did not run: ${ctx.probeSkipped}.`));
175
+ }
176
+ return out;
177
+ };
178
+ //# sourceMappingURL=site-rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"site-rules.js","sourceRoot":"","sources":["../../../src/core/aeo/site-rules.ts"],"names":[],"mappings":"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"}