@rivi-software/agentic-seo-check 0.1.1 → 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.
- package/README.md +61 -5
- package/dist/cli.js +69 -26
- package/dist/cli.js.map +1 -1
- package/dist/core/aeo/audit.d.ts +44 -0
- package/dist/core/aeo/audit.js +310 -0
- package/dist/core/aeo/audit.js.map +1 -0
- package/dist/core/aeo/bots.d.ts +30 -0
- package/dist/core/aeo/bots.js +299 -0
- package/dist/core/aeo/bots.js.map +1 -0
- package/dist/core/aeo/llms-txt.d.ts +8 -0
- package/dist/core/aeo/llms-txt.js +88 -0
- package/dist/core/aeo/llms-txt.js.map +1 -0
- package/dist/core/aeo/page.d.ts +53 -0
- package/dist/core/aeo/page.js +312 -0
- package/dist/core/aeo/page.js.map +1 -0
- package/dist/core/aeo/probe.d.ts +10 -0
- package/dist/core/aeo/probe.js +53 -0
- package/dist/core/aeo/probe.js.map +1 -0
- package/dist/core/aeo/site-rules.d.ts +13 -0
- package/dist/core/aeo/site-rules.js +178 -0
- package/dist/core/aeo/site-rules.js.map +1 -0
- package/dist/core/audit.d.ts +23 -2
- package/dist/core/audit.js +47 -22
- package/dist/core/audit.js.map +1 -1
- package/dist/core/crawl.d.ts +5 -1
- package/dist/core/crawl.js +26 -7
- package/dist/core/crawl.js.map +1 -1
- package/dist/core/html/answer.d.ts +4 -0
- package/dist/core/html/answer.js +198 -0
- package/dist/core/html/answer.js.map +1 -0
- package/dist/core/html/dom.d.ts +4 -0
- package/dist/core/html/dom.js +67 -1
- package/dist/core/html/dom.js.map +1 -1
- package/dist/core/html/extract.js +5 -71
- package/dist/core/html/extract.js.map +1 -1
- package/dist/core/report/markdown.d.ts +3 -1
- package/dist/core/report/markdown.js +102 -0
- package/dist/core/report/markdown.js.map +1 -1
- package/dist/core/robots.d.ts +7 -0
- package/dist/core/robots.js +12 -1
- package/dist/core/robots.js.map +1 -1
- package/dist/core/rules/aeo.d.ts +11 -0
- package/dist/core/rules/aeo.js +423 -0
- package/dist/core/rules/aeo.js.map +1 -0
- package/dist/core/rules/index.d.ts +4 -0
- package/dist/core/rules/index.js +7 -1
- package/dist/core/rules/index.js.map +1 -1
- package/dist/core/score.d.ts +4 -2
- package/dist/core/score.js.map +1 -1
- package/dist/core/static.js +1 -0
- package/dist/core/static.js.map +1 -1
- package/dist/core/types.d.ts +177 -2
- package/dist/core/types.js.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +129 -3
- package/dist/mcp.js.map +1 -1
- package/package.json +6 -2
- package/skills/seo-audit/SKILL.md +8 -2
package/dist/core/robots.js
CHANGED
|
@@ -4,6 +4,16 @@ import { fetchPage } from "./fetch.js";
|
|
|
4
4
|
// SAFETY: require() returns the package's module.exports, which is the parser function that its
|
|
5
5
|
// index.d.ts declares as the default export.
|
|
6
6
|
const robotsParser = createRequire(import.meta.url)("robots-parser");
|
|
7
|
+
/** The part of a robots.txt file a crawler must read at least (RFC 9309 section 2.5). */
|
|
8
|
+
const MAX_ROBOTS_BYTES = 512_000;
|
|
9
|
+
/**
|
|
10
|
+
* Whether robots.txt at `url` lets the crawler with product token `agent` fetch `target`. A URL
|
|
11
|
+
* on another origin is allowed: this robots.txt has no say over it.
|
|
12
|
+
*/
|
|
13
|
+
export const agentMatcher = (url, text) => {
|
|
14
|
+
const robot = robotsParser(url, text.slice(0, MAX_ROBOTS_BYTES));
|
|
15
|
+
return (target, agent) => robot.isAllowed(target, agent) ?? true;
|
|
16
|
+
};
|
|
7
17
|
export const buildRobots = (url, status, error, text, userAgent) => {
|
|
8
18
|
const info = {
|
|
9
19
|
url,
|
|
@@ -16,12 +26,13 @@ export const buildRobots = (url, status, error, text, userAgent) => {
|
|
|
16
26
|
info.error = error;
|
|
17
27
|
}
|
|
18
28
|
if (text === null) {
|
|
19
|
-
return { info, allows: () => true };
|
|
29
|
+
return { info, allows: () => true, text: null };
|
|
20
30
|
}
|
|
21
31
|
const robot = robotsParser(url, text);
|
|
22
32
|
info.sitemaps = robot.getSitemaps();
|
|
23
33
|
return {
|
|
24
34
|
info,
|
|
35
|
+
text: text.slice(0, MAX_ROBOTS_BYTES),
|
|
25
36
|
allows: (target) => {
|
|
26
37
|
const allowed = robot.isAllowed(target, userAgent);
|
|
27
38
|
return allowed === undefined ? true : allowed;
|
package/dist/core/robots.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"robots.js","sourceRoot":"","sources":["../../src/core/robots.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAI5C,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAIvC,qFAAqF;AACrF,gGAAgG;AAChG,6CAA6C;AAC7C,MAAM,YAAY,GAAG,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CACjD,eAAe,CAC0B,CAAC;
|
|
1
|
+
{"version":3,"file":"robots.js","sourceRoot":"","sources":["../../src/core/robots.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Answer engines' own documentation for the AEO rules. */
|
|
2
|
+
export declare const AEO_DOCS: {
|
|
3
|
+
readonly googleAi: "https://developers.google.com/search/docs/appearance/ai-features";
|
|
4
|
+
readonly bing: "https://www.bing.com/webmasters/help/webmaster-guidelines-30fba23a";
|
|
5
|
+
readonly httpErrors: "https://developers.google.com/search/docs/crawling-indexing/http-network-errors";
|
|
6
|
+
readonly structuredDataPolicies: "https://developers.google.com/search/docs/appearance/structured-data/sd-policies";
|
|
7
|
+
readonly article: "https://developers.google.com/search/docs/appearance/structured-data/article";
|
|
8
|
+
readonly dates: "https://developers.google.com/search/docs/appearance/publication-dates";
|
|
9
|
+
readonly organization: "https://developers.google.com/search/docs/appearance/structured-data/organization";
|
|
10
|
+
};
|
|
11
|
+
export declare const aeoRules: import("./rule.js").Rule[];
|
|
@@ -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":"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"}
|
|
@@ -3,11 +3,15 @@ import type { PageContext, Rule } from "./rule.js";
|
|
|
3
3
|
export type { PageContext, Rule } from "./rule.js";
|
|
4
4
|
/** Rules evaluated against a single HTML document. */
|
|
5
5
|
export declare const PAGE_RULES: Rule[];
|
|
6
|
+
/** Rules the AEO audit evaluates against each page; the SEO audit never runs them. */
|
|
7
|
+
export declare const AEO_PAGE_RULES: Rule[];
|
|
6
8
|
/** Metadata for every rule the tool can emit, including rules implemented outside the page-rule loop. */
|
|
7
9
|
export declare const ALL_RULES: RuleMeta[];
|
|
8
10
|
export interface RunRulesOptions {
|
|
9
11
|
/** Rule ids to skip. */
|
|
10
12
|
disable?: Set<string>;
|
|
13
|
+
/** The rules to run (default: the SEO page rules). */
|
|
14
|
+
rules?: Rule[];
|
|
11
15
|
}
|
|
12
16
|
export declare const sortFindings: (findings: Finding[]) => Finding[];
|
|
13
17
|
export declare const runPageRules: (ctx: PageContext, options?: RunRulesOptions) => Finding[];
|
package/dist/core/rules/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { AEO_SITE_RULES } from "../aeo/site-rules.js";
|
|
1
2
|
import { PERFORMANCE_RULES } from "../performance-rules.js";
|
|
2
3
|
import { RENDER_RULES } from "../render-rules.js";
|
|
3
4
|
import { SITE_RULES } from "../site-rules.js";
|
|
5
|
+
import { aeoRules } from "./aeo.js";
|
|
4
6
|
import { canonicalRules } from "./canonical.js";
|
|
5
7
|
import { contentRules } from "./content.js";
|
|
6
8
|
import { documentRules } from "./document.js";
|
|
@@ -32,12 +34,16 @@ export const PAGE_RULES = [
|
|
|
32
34
|
...keywordRules,
|
|
33
35
|
...responseRules,
|
|
34
36
|
];
|
|
37
|
+
/** Rules the AEO audit evaluates against each page; the SEO audit never runs them. */
|
|
38
|
+
export const AEO_PAGE_RULES = aeoRules;
|
|
35
39
|
/** Metadata for every rule the tool can emit, including rules implemented outside the page-rule loop. */
|
|
36
40
|
export const ALL_RULES = [
|
|
37
41
|
...PAGE_RULES.map((r) => r.meta),
|
|
38
42
|
...RENDER_RULES,
|
|
39
43
|
...PERFORMANCE_RULES,
|
|
40
44
|
...SITE_RULES,
|
|
45
|
+
...AEO_PAGE_RULES.map((r) => r.meta),
|
|
46
|
+
...AEO_SITE_RULES,
|
|
41
47
|
];
|
|
42
48
|
export const sortFindings = (findings) => {
|
|
43
49
|
const rank = { error: 0, warning: 1, info: 2 };
|
|
@@ -45,7 +51,7 @@ export const sortFindings = (findings) => {
|
|
|
45
51
|
};
|
|
46
52
|
export const runPageRules = (ctx, options = {}) => {
|
|
47
53
|
const findings = [];
|
|
48
|
-
for (const rule of PAGE_RULES) {
|
|
54
|
+
for (const rule of options.rules ?? PAGE_RULES) {
|
|
49
55
|
if (options.disable?.has(rule.meta.id)) {
|
|
50
56
|
continue;
|
|
51
57
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/rules/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE9C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAIxC,sDAAsD;AACtD,MAAM,CAAC,MAAM,UAAU,GAAW;IAChC,GAAG,UAAU;IACb,GAAG,SAAS;IACZ,GAAG,YAAY;IACf,GAAG,cAAc;IACjB,GAAG,aAAa;IAChB,GAAG,aAAa;IAChB,GAAG,WAAW;IACd,GAAG,UAAU;IACb,GAAG,SAAS;IACZ,GAAG,aAAa;IAChB,GAAG,mBAAmB;IACtB,GAAG,YAAY;IACf,GAAG,YAAY;IACf,GAAG,aAAa;CACjB,CAAC;AAEF,yGAAyG;AACzG,MAAM,CAAC,MAAM,SAAS,GAAe;IACnC,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAChC,GAAG,YAAY;IACf,GAAG,iBAAiB;IACpB,GAAG,UAAU;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/rules/index.ts"],"names":[],"mappings":"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"}
|
package/dist/core/score.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
import type { Finding,
|
|
1
|
+
import type { Finding, SeverityCounts } from "./types.js";
|
|
2
2
|
export declare const pageScore: (counts: SeverityCounts) => number;
|
|
3
|
-
export declare const siteScore: (pages:
|
|
3
|
+
export declare const siteScore: (pages: {
|
|
4
|
+
score: number;
|
|
5
|
+
}[], siteFindings: Finding[]) => number;
|
package/dist/core/score.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"score.js","sourceRoot":"","sources":["../../src/core/score.ts"],"names":[],"mappings":"AAEA,MAAM,YAAY,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAW,CAAC;AACjE,MAAM,YAAY,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,GAAG,EAAW,CAAC;AAEnE,MAAM,KAAK,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;AAEnE,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,MAAsB,EAAU,EAAE;IAC1D,MAAM,SAAS,GACb,MAAM,CAAC,KAAK,GAAG,YAAY,CAAC,KAAK;QACjC,MAAM,CAAC,OAAO,GAAG,YAAY,CAAC,OAAO;QACrC,MAAM,CAAC,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC;IAElC,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,SAAS,CAAC,CAAC,CAAC;AAC5C,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,SAAS,GAAG,CACvB,
|
|
1
|
+
{"version":3,"file":"score.js","sourceRoot":"","sources":["../../src/core/score.ts"],"names":[],"mappings":"AAEA,MAAM,YAAY,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAW,CAAC;AACjE,MAAM,YAAY,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,GAAG,EAAW,CAAC;AAEnE,MAAM,KAAK,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;AAEnE,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,MAAsB,EAAU,EAAE;IAC1D,MAAM,SAAS,GACb,MAAM,CAAC,KAAK,GAAG,YAAY,CAAC,KAAK;QACjC,MAAM,CAAC,OAAO,GAAG,YAAY,CAAC,OAAO;QACrC,MAAM,CAAC,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC;IAElC,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,SAAS,CAAC,CAAC,CAAC;AAC5C,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,SAAS,GAAG,CACvB,KAA0B,EAC1B,YAAuB,EACf,EAAE;IACV,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM;QACtB,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM;QAC3D,CAAC,CAAC,GAAG,CAAC;IAER,MAAM,SAAS,GAAG,YAAY,CAAC,MAAM,CACnC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC,EAC1C,CAAC,CACF,CAAC;IAEF,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,SAAS,CAAC,CAAC,CAAC;AAC5C,CAAC,CAAC"}
|
package/dist/core/static.js
CHANGED
|
@@ -259,6 +259,7 @@ export const scanDirectory = async (dir, options) => {
|
|
|
259
259
|
linkChecks,
|
|
260
260
|
linkSources,
|
|
261
261
|
robots: robots.info,
|
|
262
|
+
robotsText: robots.text,
|
|
262
263
|
robotsAllows: robots.info.found ? robots.allows : null,
|
|
263
264
|
blockedByRobots: robots.info.found
|
|
264
265
|
? records.flatMap((r) => (robots.allows(r.url) ? [] : [r.url]))
|