@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.
- package/LICENSE +1 -1
- package/README.md +124 -18
- package/dist/brand.d.ts +11 -0
- package/dist/brand.js +22 -0
- package/dist/brand.js.map +1 -0
- package/dist/cli.js +174 -86
- 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 +37 -16
- package/dist/core/audit.js +389 -249
- package/dist/core/audit.js.map +1 -1
- package/dist/core/concurrency.d.ts +4 -4
- package/dist/core/concurrency.js +33 -23
- package/dist/core/concurrency.js.map +1 -1
- package/dist/core/crawl.d.ts +9 -5
- package/dist/core/crawl.js +271 -123
- package/dist/core/crawl.js.map +1 -1
- package/dist/core/fetch.d.ts +5 -5
- package/dist/core/fetch.js +151 -100
- package/dist/core/fetch.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 +17 -0
- package/dist/core/html/dom.js +83 -0
- package/dist/core/html/dom.js.map +1 -0
- package/dist/core/html/extract.d.ts +2 -2
- package/dist/core/html/extract.js +102 -111
- package/dist/core/html/extract.js.map +1 -1
- package/dist/core/html/structured-data.d.ts +16 -6
- package/dist/core/html/structured-data.js +180 -145
- package/dist/core/html/structured-data.js.map +1 -1
- package/dist/core/html/text.d.ts +14 -14
- package/dist/core/html/text.js +110 -107
- package/dist/core/html/text.js.map +1 -1
- package/dist/core/lighthouse.d.ts +26 -25
- package/dist/core/lighthouse.js +181 -105
- package/dist/core/lighthouse.js.map +1 -1
- package/dist/core/performance-rules.d.ts +2 -2
- package/dist/core/performance-rules.js +105 -33
- package/dist/core/performance-rules.js.map +1 -1
- package/dist/core/render-rules.d.ts +5 -4
- package/dist/core/render-rules.js +129 -62
- package/dist/core/render-rules.js.map +1 -1
- package/dist/core/render.d.ts +2 -2
- package/dist/core/render.js +29 -23
- package/dist/core/render.js.map +1 -1
- package/dist/core/report/markdown.d.ts +4 -2
- package/dist/core/report/markdown.js +210 -129
- package/dist/core/report/markdown.js.map +1 -1
- package/dist/core/robots.d.ts +12 -5
- package/dist/core/robots.js +43 -17
- 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/canonical.d.ts +1 -1
- package/dist/core/rules/canonical.js +93 -57
- package/dist/core/rules/canonical.js.map +1 -1
- package/dist/core/rules/content.js +40 -26
- package/dist/core/rules/content.js.map +1 -1
- package/dist/core/rules/document.js +64 -41
- package/dist/core/rules/document.js.map +1 -1
- package/dist/core/rules/headings.js +58 -38
- package/dist/core/rules/headings.js.map +1 -1
- package/dist/core/rules/hreflang.js +70 -38
- package/dist/core/rules/hreflang.js.map +1 -1
- package/dist/core/rules/images.js +44 -26
- package/dist/core/rules/images.js.map +1 -1
- package/dist/core/rules/index.d.ts +10 -6
- package/dist/core/rules/index.js +43 -36
- package/dist/core/rules/index.js.map +1 -1
- package/dist/core/rules/indexing.d.ts +2 -2
- package/dist/core/rules/indexing.js +48 -26
- package/dist/core/rules/indexing.js.map +1 -1
- package/dist/core/rules/keyword.js +84 -47
- package/dist/core/rules/keyword.js.map +1 -1
- package/dist/core/rules/links.js +72 -37
- package/dist/core/rules/links.js.map +1 -1
- package/dist/core/rules/meta.js +55 -34
- package/dist/core/rules/meta.js.map +1 -1
- package/dist/core/rules/response.js +75 -41
- package/dist/core/rules/response.js.map +1 -1
- package/dist/core/rules/rule.d.ts +6 -6
- package/dist/core/rules/rule.js +36 -36
- package/dist/core/rules/rule.js.map +1 -1
- package/dist/core/rules/social.js +86 -45
- package/dist/core/rules/social.js.map +1 -1
- package/dist/core/rules/structured-data.d.ts +6 -4
- package/dist/core/rules/structured-data.js +314 -102
- package/dist/core/rules/structured-data.js.map +1 -1
- package/dist/core/rules/title.js +48 -33
- package/dist/core/rules/title.js.map +1 -1
- package/dist/core/score.d.ts +5 -3
- package/dist/core/score.js +11 -9
- package/dist/core/score.js.map +1 -1
- package/dist/core/site-rules.d.ts +3 -3
- package/dist/core/site-rules.js +409 -198
- package/dist/core/site-rules.js.map +1 -1
- package/dist/core/sitemap.d.ts +5 -5
- package/dist/core/sitemap.js +109 -64
- package/dist/core/sitemap.js.map +1 -1
- package/dist/core/static.d.ts +2 -2
- package/dist/core/static.js +210 -112
- package/dist/core/static.js.map +1 -1
- package/dist/core/summary.d.ts +2 -2
- package/dist/core/summary.js +11 -6
- package/dist/core/summary.js.map +1 -1
- package/dist/core/types.d.ts +189 -14
- package/dist/core/types.js +15 -8
- package/dist/core/types.js.map +1 -1
- package/dist/core/url.d.ts +11 -11
- package/dist/core/url.js +122 -70
- package/dist/core/url.js.map +1 -1
- package/dist/index.d.ts +15 -11
- package/dist/index.js +11 -9
- package/dist/index.js.map +1 -1
- package/dist/mcp-bin.js +7 -4
- package/dist/mcp-bin.js.map +1 -1
- package/dist/mcp.d.ts +3 -3
- package/dist/mcp.js +437 -121
- package/dist/mcp.js.map +1 -1
- package/dist/version.d.ts +0 -1
- package/dist/version.js +4 -2
- package/dist/version.js.map +1 -1
- package/package.json +32 -10
- package/skills/seo-audit/SKILL.md +8 -2
|
@@ -1,68 +1,100 @@
|
|
|
1
|
-
import { normalizeUrl } from
|
|
2
|
-
import { cap, defineRule, GOOGLE_DOCS } from
|
|
3
|
-
const HREFLANG_CODE = /^(x-default|[a-zA-Z]{2,3}(
|
|
1
|
+
import { normalizeUrl } from "../url.js";
|
|
2
|
+
import { cap, defineRule, GOOGLE_DOCS } from "./rule.js";
|
|
3
|
+
const HREFLANG_CODE = /^(?:x-default|[a-zA-Z]{2,3}(?:-[a-zA-Z]{4})?(?:-(?:[a-zA-Z]{2}|\d{3}))?)$/u;
|
|
4
4
|
export const hreflangRules = [
|
|
5
5
|
defineRule({
|
|
6
|
-
id:
|
|
7
|
-
category:
|
|
8
|
-
severity:
|
|
9
|
-
description:
|
|
6
|
+
id: "hreflang.invalid-code",
|
|
7
|
+
category: "international",
|
|
8
|
+
severity: "warning",
|
|
9
|
+
description: "hreflang values that are not valid ISO 639-1 language codes with an optional ISO 3166-1 region (or x-default).",
|
|
10
10
|
recommendation: 'Use codes like "en", "en-gb", "nl-nl", "zh-hant" or "x-default". Country codes alone ("uk", "eu") are invalid.',
|
|
11
11
|
docs: GOOGLE_DOCS.hreflang,
|
|
12
12
|
}, ({ page }, make) => {
|
|
13
13
|
const bad = page.hreflang.filter((h) => !HREFLANG_CODE.test(h.lang));
|
|
14
|
-
return bad.length
|
|
14
|
+
return bad.length
|
|
15
|
+
? make(`${bad.length} hreflang value(s) are invalid.`, {
|
|
16
|
+
selector: 'link[rel="alternate"][hreflang]',
|
|
17
|
+
evidence: { invalid: cap(bad.map((h) => h.lang)) },
|
|
18
|
+
})
|
|
19
|
+
: null;
|
|
15
20
|
}),
|
|
16
21
|
defineRule({
|
|
17
|
-
id:
|
|
18
|
-
category:
|
|
19
|
-
severity:
|
|
20
|
-
description:
|
|
21
|
-
recommendation:
|
|
22
|
+
id: "hreflang.relative-url",
|
|
23
|
+
category: "international",
|
|
24
|
+
severity: "warning",
|
|
25
|
+
description: "hreflang links must use absolute URLs.",
|
|
26
|
+
recommendation: "Use fully qualified URLs (https://example.com/nl/) in every hreflang link.",
|
|
22
27
|
docs: GOOGLE_DOCS.hreflang,
|
|
23
28
|
}, ({ page }, make) => {
|
|
24
|
-
const rel = page.hreflang.filter((h) => !/^https?:\/\//
|
|
25
|
-
return rel.length
|
|
29
|
+
const rel = page.hreflang.filter((h) => !/^https?:\/\//iu.test(h.href));
|
|
30
|
+
return rel.length
|
|
31
|
+
? make(`${rel.length} hreflang link(s) use relative URLs.`, {
|
|
32
|
+
selector: 'link[rel="alternate"][hreflang]',
|
|
33
|
+
evidence: { examples: cap(rel.map((h) => h.href)) },
|
|
34
|
+
})
|
|
35
|
+
: null;
|
|
26
36
|
}),
|
|
27
37
|
defineRule({
|
|
28
|
-
id:
|
|
29
|
-
category:
|
|
30
|
-
severity:
|
|
31
|
-
description:
|
|
32
|
-
recommendation:
|
|
38
|
+
id: "hreflang.duplicate-lang",
|
|
39
|
+
category: "international",
|
|
40
|
+
severity: "warning",
|
|
41
|
+
description: "The same hreflang value appears more than once with different URLs.",
|
|
42
|
+
recommendation: "Each language/region code may map to exactly one URL per page.",
|
|
33
43
|
docs: GOOGLE_DOCS.hreflang,
|
|
34
44
|
}, ({ page }, make) => {
|
|
35
45
|
const seen = new Map();
|
|
36
46
|
for (const h of page.hreflang) {
|
|
37
47
|
const key = h.lang.toLowerCase();
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
seen.
|
|
48
|
+
const urls = seen.get(key) ?? new Set();
|
|
49
|
+
urls.add(h.url ?? h.href);
|
|
50
|
+
seen.set(key, urls);
|
|
41
51
|
}
|
|
42
|
-
const dupes = [...seen.entries()].
|
|
43
|
-
return dupes.length
|
|
52
|
+
const dupes = [...seen.entries()].flatMap(([lang, urls]) => urls.size > 1 ? [lang] : []);
|
|
53
|
+
return dupes.length
|
|
54
|
+
? make(`hreflang codes with conflicting URLs: ${dupes.join(", ")}.`, {
|
|
55
|
+
selector: 'link[rel="alternate"][hreflang]',
|
|
56
|
+
evidence: { codes: dupes },
|
|
57
|
+
})
|
|
58
|
+
: null;
|
|
44
59
|
}),
|
|
45
60
|
defineRule({
|
|
46
|
-
id:
|
|
47
|
-
category:
|
|
48
|
-
severity:
|
|
49
|
-
description:
|
|
50
|
-
recommendation:
|
|
61
|
+
id: "hreflang.missing-self",
|
|
62
|
+
category: "international",
|
|
63
|
+
severity: "warning",
|
|
64
|
+
description: "The hreflang set does not include this page itself (self-referencing entry).",
|
|
65
|
+
recommendation: "Every page in an hreflang cluster must list itself as well as its alternates, otherwise the annotations may be ignored.",
|
|
51
66
|
docs: GOOGLE_DOCS.hreflang,
|
|
52
67
|
}, ({ page }, make) => {
|
|
53
|
-
if (page.hreflang.length === 0)
|
|
68
|
+
if (page.hreflang.length === 0) {
|
|
54
69
|
return null;
|
|
55
|
-
|
|
70
|
+
}
|
|
71
|
+
const self = new Set([
|
|
72
|
+
normalizeUrl(page.url),
|
|
73
|
+
page.canonical ? normalizeUrl(page.canonical) : null,
|
|
74
|
+
].filter(Boolean));
|
|
56
75
|
const hasSelf = page.hreflang.some((h) => h.url && self.has(normalizeUrl(h.url)));
|
|
57
|
-
return hasSelf
|
|
76
|
+
return hasSelf
|
|
77
|
+
? null
|
|
78
|
+
: make("hreflang set has no entry pointing at this page.", {
|
|
79
|
+
selector: 'link[rel="alternate"][hreflang]',
|
|
80
|
+
evidence: {
|
|
81
|
+
page: page.url,
|
|
82
|
+
alternates: cap(page.hreflang.map((h) => `${h.lang} → ${h.href}`)),
|
|
83
|
+
},
|
|
84
|
+
});
|
|
58
85
|
}),
|
|
59
86
|
defineRule({
|
|
60
|
-
id:
|
|
61
|
-
category:
|
|
62
|
-
severity:
|
|
63
|
-
description:
|
|
87
|
+
id: "hreflang.missing-x-default",
|
|
88
|
+
category: "international",
|
|
89
|
+
severity: "info",
|
|
90
|
+
description: "The hreflang set has no x-default entry.",
|
|
64
91
|
recommendation: 'Add <link rel="alternate" hreflang="x-default" href="..."> pointing at the language-selector or default version.',
|
|
65
92
|
docs: GOOGLE_DOCS.hreflang,
|
|
66
|
-
}, ({ page }, make) =>
|
|
93
|
+
}, ({ page }, make) => page.hreflang.length > 0 &&
|
|
94
|
+
!page.hreflang.some((h) => h.lang.toLowerCase() === "x-default")
|
|
95
|
+
? make("hreflang set has no x-default entry.", {
|
|
96
|
+
selector: 'link[rel="alternate"][hreflang]',
|
|
97
|
+
})
|
|
98
|
+
: null),
|
|
67
99
|
];
|
|
68
100
|
//# sourceMappingURL=hreflang.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hreflang.js","sourceRoot":"","sources":["../../../src/core/rules/hreflang.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAEzD,MAAM,aAAa,
|
|
1
|
+
{"version":3,"file":"hreflang.js","sourceRoot":"","sources":["../../../src/core/rules/hreflang.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,59 +1,77 @@
|
|
|
1
|
-
import { cap, defineRule, GOOGLE_DOCS } from
|
|
2
|
-
const FILENAME_ALT = /^(img|image|dsc|dscn|dcim|photo|screenshot|screen shot|untitled)[-_ ]?\d*|\.(jpe?g|png|gif|webp|avif|svg)$/
|
|
1
|
+
import { cap, defineRule, GOOGLE_DOCS } from "./rule.js";
|
|
2
|
+
const FILENAME_ALT = /^(?:img|image|dsc|dscn|dcim|photo|screenshot|screen shot|untitled)[-_ ]?\d*|\.(?:jpe?g|png|gif|webp|avif|svg)$/iu;
|
|
3
3
|
export const imageRules = [
|
|
4
4
|
defineRule({
|
|
5
|
-
id:
|
|
6
|
-
category:
|
|
7
|
-
severity:
|
|
8
|
-
description:
|
|
5
|
+
id: "img.alt-missing",
|
|
6
|
+
category: "images",
|
|
7
|
+
severity: "warning",
|
|
8
|
+
description: "Images without an alt attribute.",
|
|
9
9
|
recommendation: 'Give every <img> an alt attribute: a short description for meaningful images, alt="" for purely decorative ones. Alt text is used for image search and accessibility.',
|
|
10
10
|
docs: GOOGLE_DOCS.images,
|
|
11
11
|
}, ({ page }, make) => {
|
|
12
12
|
const missing = page.images.filter((i) => i.alt === null);
|
|
13
13
|
return missing.length
|
|
14
14
|
? make(`${missing.length} of ${page.images.length} images have no alt attribute.`, {
|
|
15
|
-
selector:
|
|
16
|
-
evidence: {
|
|
15
|
+
selector: "img:not([alt])",
|
|
16
|
+
evidence: {
|
|
17
|
+
count: missing.length,
|
|
18
|
+
total: page.images.length,
|
|
19
|
+
examples: cap(missing.map((i) => i.src ?? "(no src)")),
|
|
20
|
+
},
|
|
17
21
|
})
|
|
18
22
|
: null;
|
|
19
23
|
}),
|
|
20
24
|
defineRule({
|
|
21
|
-
id:
|
|
22
|
-
category:
|
|
23
|
-
severity:
|
|
24
|
-
description:
|
|
25
|
-
recommendation:
|
|
25
|
+
id: "img.alt-filename",
|
|
26
|
+
category: "images",
|
|
27
|
+
severity: "info",
|
|
28
|
+
description: "Image alt text looks like a filename (IMG_1234.jpg, screenshot.png).",
|
|
29
|
+
recommendation: "Replace filename-style alt text with a description of what the image shows.",
|
|
26
30
|
docs: GOOGLE_DOCS.images,
|
|
27
31
|
}, ({ page }, make) => {
|
|
28
32
|
const bad = page.images.filter((i) => i.alt && FILENAME_ALT.test(i.alt.trim()));
|
|
29
|
-
return bad.length
|
|
33
|
+
return bad.length
|
|
34
|
+
? make(`${bad.length} image(s) have filename-like alt text.`, {
|
|
35
|
+
evidence: {
|
|
36
|
+
examples: cap(bad.map((i) => ({ src: i.src, alt: i.alt }))),
|
|
37
|
+
},
|
|
38
|
+
})
|
|
39
|
+
: null;
|
|
30
40
|
}),
|
|
31
41
|
defineRule({
|
|
32
|
-
id:
|
|
33
|
-
category:
|
|
34
|
-
severity:
|
|
35
|
-
description:
|
|
36
|
-
recommendation:
|
|
42
|
+
id: "img.dimensions-missing",
|
|
43
|
+
category: "images",
|
|
44
|
+
severity: "info",
|
|
45
|
+
description: "Images without explicit width and height attributes, which causes layout shift (CLS) while they load.",
|
|
46
|
+
recommendation: "Add width and height attributes (or CSS aspect-ratio) to <img> elements so the browser can reserve space before the image loads.",
|
|
37
47
|
docs: GOOGLE_DOCS.cwv,
|
|
38
48
|
}, ({ page }, make) => {
|
|
39
49
|
const missing = page.images.filter((i) => i.src && (!i.width || !i.height));
|
|
40
50
|
return missing.length
|
|
41
51
|
? make(`${missing.length} of ${page.images.length} images lack width/height attributes.`, {
|
|
42
|
-
selector:
|
|
43
|
-
evidence: {
|
|
52
|
+
selector: "img:not([width]), img:not([height])",
|
|
53
|
+
evidence: {
|
|
54
|
+
count: missing.length,
|
|
55
|
+
examples: cap(missing.map((i) => i.src)),
|
|
56
|
+
},
|
|
44
57
|
})
|
|
45
58
|
: null;
|
|
46
59
|
}),
|
|
47
60
|
defineRule({
|
|
48
|
-
id:
|
|
49
|
-
category:
|
|
50
|
-
severity:
|
|
51
|
-
description:
|
|
61
|
+
id: "img.src-missing",
|
|
62
|
+
category: "images",
|
|
63
|
+
severity: "info",
|
|
64
|
+
description: "Images without a src attribute (usually lazy-loading via data-src without a fallback).",
|
|
52
65
|
recommendation: 'Use native loading="lazy" with a real src, or ensure the lazy-loader also works without JavaScript, so crawlers can discover the images.',
|
|
53
66
|
docs: GOOGLE_DOCS.images,
|
|
54
67
|
}, ({ page }, make) => {
|
|
55
68
|
const missing = page.images.filter((i) => !i.src);
|
|
56
|
-
return missing.length
|
|
69
|
+
return missing.length
|
|
70
|
+
? make(`${missing.length} image(s) have no src attribute.`, {
|
|
71
|
+
selector: "img:not([src])",
|
|
72
|
+
evidence: { count: missing.length },
|
|
73
|
+
})
|
|
74
|
+
: null;
|
|
57
75
|
}),
|
|
58
76
|
];
|
|
59
77
|
//# sourceMappingURL=images.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"images.js","sourceRoot":"","sources":["../../../src/core/rules/images.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAEzD,MAAM,YAAY,
|
|
1
|
+
{"version":3,"file":"images.js","sourceRoot":"","sources":["../../../src/core/rules/images.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import type { Finding, RuleMeta } from
|
|
2
|
-
import type { PageContext, Rule } from
|
|
3
|
-
export type { PageContext, Rule } from
|
|
1
|
+
import type { Finding, RuleMeta } from "../types.js";
|
|
2
|
+
import type { PageContext, Rule } from "./rule.js";
|
|
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
|
-
export declare
|
|
13
|
-
export declare
|
|
14
|
-
export declare
|
|
16
|
+
export declare const sortFindings: (findings: Finding[]) => Finding[];
|
|
17
|
+
export declare const runPageRules: (ctx: PageContext, options?: RunRulesOptions) => Finding[];
|
|
18
|
+
export declare const findRule: (id: string) => RuleMeta | undefined;
|
package/dist/core/rules/index.js
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
1
|
+
import { AEO_SITE_RULES } from "../aeo/site-rules.js";
|
|
2
|
+
import { PERFORMANCE_RULES } from "../performance-rules.js";
|
|
3
|
+
import { RENDER_RULES } from "../render-rules.js";
|
|
4
|
+
import { SITE_RULES } from "../site-rules.js";
|
|
5
|
+
import { aeoRules } from "./aeo.js";
|
|
6
|
+
import { canonicalRules } from "./canonical.js";
|
|
7
|
+
import { contentRules } from "./content.js";
|
|
8
|
+
import { documentRules } from "./document.js";
|
|
9
|
+
import { headingRules } from "./headings.js";
|
|
10
|
+
import { hreflangRules } from "./hreflang.js";
|
|
11
|
+
import { imageRules } from "./images.js";
|
|
12
|
+
import { indexingRules } from "./indexing.js";
|
|
13
|
+
import { keywordRules } from "./keyword.js";
|
|
14
|
+
import { linkRules } from "./links.js";
|
|
15
|
+
import { metaRules } from "./meta.js";
|
|
16
|
+
import { responseRules } from "./response.js";
|
|
17
|
+
import { socialRules } from "./social.js";
|
|
18
|
+
import { structuredDataRules } from "./structured-data.js";
|
|
19
|
+
import { titleRules } from "./title.js";
|
|
18
20
|
/** Rules evaluated against a single HTML document. */
|
|
19
21
|
export const PAGE_RULES = [
|
|
20
22
|
...titleRules,
|
|
@@ -32,45 +34,50 @@ 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
|
-
export
|
|
48
|
+
export const sortFindings = (findings) => {
|
|
49
|
+
const rank = { error: 0, warning: 1, info: 2 };
|
|
50
|
+
return findings.toSorted((a, b) => rank[a.severity] - rank[b.severity] || a.rule.localeCompare(b.rule));
|
|
51
|
+
};
|
|
52
|
+
export const runPageRules = (ctx, options = {}) => {
|
|
43
53
|
const findings = [];
|
|
44
|
-
for (const rule of PAGE_RULES) {
|
|
45
|
-
if (options.disable?.has(rule.meta.id))
|
|
54
|
+
for (const rule of options.rules ?? PAGE_RULES) {
|
|
55
|
+
if (options.disable?.has(rule.meta.id)) {
|
|
46
56
|
continue;
|
|
47
|
-
|
|
57
|
+
}
|
|
58
|
+
if (rule.meta.requires === "keyword" && !ctx.keyword) {
|
|
48
59
|
continue;
|
|
60
|
+
}
|
|
49
61
|
try {
|
|
50
62
|
for (const f of rule.check(ctx)) {
|
|
51
|
-
if (!f.url)
|
|
63
|
+
if (!f.url) {
|
|
52
64
|
f.url = ctx.response?.finalUrl ?? ctx.page.url;
|
|
65
|
+
}
|
|
53
66
|
findings.push(f);
|
|
54
67
|
}
|
|
55
68
|
}
|
|
56
|
-
catch (
|
|
69
|
+
catch (error) {
|
|
57
70
|
findings.push({
|
|
58
|
-
rule:
|
|
59
|
-
severity:
|
|
60
|
-
category:
|
|
61
|
-
message: `Rule ${rule.meta.id} failed: ${
|
|
62
|
-
recommendation:
|
|
71
|
+
rule: "tool.rule-error",
|
|
72
|
+
severity: "info",
|
|
73
|
+
category: "tool",
|
|
74
|
+
message: `Rule ${rule.meta.id} failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
75
|
+
recommendation: "Report this as a bug at https://github.com/rivi-software/agentic-seo-check/issues; the rest of the audit is unaffected.",
|
|
63
76
|
url: ctx.page.url,
|
|
64
77
|
});
|
|
65
78
|
}
|
|
66
79
|
}
|
|
67
80
|
return sortFindings(findings);
|
|
68
|
-
}
|
|
69
|
-
export
|
|
70
|
-
const rank = { error: 0, warning: 1, info: 2 };
|
|
71
|
-
return [...findings].sort((a, b) => rank[a.severity] - rank[b.severity] || a.rule.localeCompare(b.rule));
|
|
72
|
-
}
|
|
73
|
-
export function findRule(id) {
|
|
74
|
-
return ALL_RULES.find((r) => r.id === id);
|
|
75
|
-
}
|
|
81
|
+
};
|
|
82
|
+
export const findRule = (id) => ALL_RULES.find((r) => r.id === id);
|
|
76
83
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/rules/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/rules/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExtractedPage, ResponseInfo } from
|
|
1
|
+
import type { ExtractedPage, ResponseInfo } from "../types.js";
|
|
2
2
|
export interface RobotsDirectives {
|
|
3
3
|
noindex: boolean;
|
|
4
4
|
nofollow: boolean;
|
|
@@ -6,5 +6,5 @@ export interface RobotsDirectives {
|
|
|
6
6
|
directives: string[];
|
|
7
7
|
}
|
|
8
8
|
/** Combine <meta name="robots"> / googlebot and the X-Robots-Tag header. */
|
|
9
|
-
export declare
|
|
9
|
+
export declare const robotsDirectives: (page: ExtractedPage, response?: ResponseInfo) => RobotsDirectives;
|
|
10
10
|
export declare const indexingRules: import("./rule.js").Rule[];
|
|
@@ -1,53 +1,75 @@
|
|
|
1
|
-
import { defineRule, GOOGLE_DOCS } from
|
|
1
|
+
import { defineRule, GOOGLE_DOCS } from "./rule.js";
|
|
2
2
|
/** Combine <meta name="robots"> / googlebot and the X-Robots-Tag header. */
|
|
3
|
-
export
|
|
3
|
+
export const robotsDirectives = (page, response) => {
|
|
4
4
|
const directives = [];
|
|
5
5
|
const sources = [];
|
|
6
6
|
for (const content of page.metaRobots) {
|
|
7
|
-
for (const d of content
|
|
7
|
+
for (const d of content
|
|
8
|
+
.split(",")
|
|
9
|
+
.map((s) => s.trim())
|
|
10
|
+
.filter(Boolean)) {
|
|
8
11
|
directives.push(d);
|
|
9
|
-
sources.push(
|
|
12
|
+
sources.push("meta");
|
|
10
13
|
}
|
|
11
14
|
}
|
|
12
|
-
const header = response?.headers[
|
|
15
|
+
const header = response?.headers["x-robots-tag"];
|
|
13
16
|
if (header) {
|
|
14
|
-
for (const d of header
|
|
17
|
+
for (const d of header
|
|
18
|
+
.toLowerCase()
|
|
19
|
+
.split(",")
|
|
20
|
+
.map((s) => s.trim())
|
|
21
|
+
.filter(Boolean)) {
|
|
15
22
|
// "googlebot: noindex" form
|
|
16
|
-
directives.push(d.includes(
|
|
17
|
-
sources.push(
|
|
23
|
+
directives.push(d.includes(":") ? (d.split(":").pop() ?? d).trim() : d);
|
|
24
|
+
sources.push("header");
|
|
18
25
|
}
|
|
19
26
|
}
|
|
20
|
-
const noindex = directives.some((d) => d ===
|
|
21
|
-
const nofollow = directives.some((d) => d ===
|
|
22
|
-
return {
|
|
23
|
-
|
|
27
|
+
const noindex = directives.some((d) => d === "noindex" || d === "none");
|
|
28
|
+
const nofollow = directives.some((d) => d === "nofollow" || d === "none");
|
|
29
|
+
return {
|
|
30
|
+
noindex,
|
|
31
|
+
nofollow,
|
|
32
|
+
sources: [...new Set(sources)],
|
|
33
|
+
directives: [...new Set(directives)],
|
|
34
|
+
};
|
|
35
|
+
};
|
|
24
36
|
export const indexingRules = [
|
|
25
37
|
defineRule({
|
|
26
|
-
id:
|
|
27
|
-
category:
|
|
28
|
-
severity:
|
|
29
|
-
description:
|
|
30
|
-
recommendation:
|
|
38
|
+
id: "robots.noindex",
|
|
39
|
+
category: "indexing",
|
|
40
|
+
severity: "warning",
|
|
41
|
+
description: "The page tells search engines not to index it (meta robots or X-Robots-Tag).",
|
|
42
|
+
recommendation: "If this page should appear in search results, remove the noindex directive. Preview/staging hosts often add X-Robots-Tag: noindex at the platform level; verify on production. If the page is intentionally private (login, thank-you, internal search), this is correct.",
|
|
31
43
|
docs: GOOGLE_DOCS.robotsMeta,
|
|
32
44
|
}, ({ page, response }, make) => {
|
|
33
45
|
const r = robotsDirectives(page, response);
|
|
34
46
|
return r.noindex
|
|
35
|
-
? make(`Page is set to noindex via ${r.sources.join(
|
|
36
|
-
selector: r.sources.includes(
|
|
37
|
-
|
|
47
|
+
? make(`Page is set to noindex via ${r.sources.join(" and ")}.`, {
|
|
48
|
+
selector: r.sources.includes("meta")
|
|
49
|
+
? 'head > meta[name="robots"]'
|
|
50
|
+
: undefined,
|
|
51
|
+
evidence: {
|
|
52
|
+
directives: r.directives,
|
|
53
|
+
sources: r.sources,
|
|
54
|
+
xRobotsTag: response?.headers["x-robots-tag"] ?? null,
|
|
55
|
+
},
|
|
38
56
|
})
|
|
39
57
|
: null;
|
|
40
58
|
}),
|
|
41
59
|
defineRule({
|
|
42
|
-
id:
|
|
43
|
-
category:
|
|
44
|
-
severity:
|
|
45
|
-
description:
|
|
46
|
-
recommendation:
|
|
60
|
+
id: "robots.nofollow",
|
|
61
|
+
category: "indexing",
|
|
62
|
+
severity: "info",
|
|
63
|
+
description: "The page tells search engines not to follow its links.",
|
|
64
|
+
recommendation: "Remove nofollow unless you deliberately want link equity to stop at this page; it also hides linked pages from discovery.",
|
|
47
65
|
docs: GOOGLE_DOCS.robotsMeta,
|
|
48
66
|
}, ({ page, response }, make) => {
|
|
49
67
|
const r = robotsDirectives(page, response);
|
|
50
|
-
return r.nofollow
|
|
68
|
+
return r.nofollow
|
|
69
|
+
? make(`Page-level nofollow is set via ${r.sources.join(" and ")}.`, {
|
|
70
|
+
evidence: { directives: r.directives },
|
|
71
|
+
})
|
|
72
|
+
: null;
|
|
51
73
|
}),
|
|
52
74
|
];
|
|
53
75
|
//# sourceMappingURL=indexing.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"indexing.js","sourceRoot":"","sources":["../../../src/core/rules/indexing.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AASpD,4EAA4E;AAC5E,MAAM,
|
|
1
|
+
{"version":3,"file":"indexing.js","sourceRoot":"","sources":["../../../src/core/rules/indexing.ts"],"names":[],"mappings":"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"}
|