@pseolint/core 0.2.2 → 0.3.1
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 +10 -9
- package/dist/ai/prompt.d.ts +1 -1
- package/dist/ai/prompt.d.ts.map +1 -1
- package/dist/ai/prompt.js +13 -1
- package/dist/ai/prompt.js.map +1 -1
- package/dist/ai/triage.d.ts +15 -1
- package/dist/ai/triage.d.ts.map +1 -1
- package/dist/ai/triage.js +30 -0
- package/dist/ai/triage.js.map +1 -1
- package/dist/analytics-blocklist.d.ts +28 -0
- package/dist/analytics-blocklist.d.ts.map +1 -0
- package/dist/analytics-blocklist.js +129 -0
- package/dist/analytics-blocklist.js.map +1 -0
- package/dist/auditor.d.ts.map +1 -1
- package/dist/auditor.js +130 -46
- package/dist/auditor.js.map +1 -1
- package/dist/formatters/console.d.ts +9 -0
- package/dist/formatters/console.d.ts.map +1 -1
- package/dist/formatters/console.js +53 -0
- package/dist/formatters/console.js.map +1 -1
- package/dist/formatters/html.d.ts.map +1 -1
- package/dist/formatters/html.js +557 -144
- package/dist/formatters/html.js.map +1 -1
- package/dist/index.d.ts +14 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -1
- package/dist/index.js.map +1 -1
- package/dist/renderer.d.ts +14 -0
- package/dist/renderer.d.ts.map +1 -1
- package/dist/renderer.js +130 -4
- package/dist/renderer.js.map +1 -1
- package/dist/rule-references.d.ts.map +1 -1
- package/dist/rule-references.js +9 -0
- package/dist/rule-references.js.map +1 -1
- package/dist/rules/aeo/answer-first.d.ts +18 -0
- package/dist/rules/aeo/answer-first.d.ts.map +1 -0
- package/dist/rules/aeo/answer-first.js +191 -0
- package/dist/rules/aeo/answer-first.js.map +1 -0
- package/dist/rules/aeo/citable-facts.d.ts +9 -0
- package/dist/rules/aeo/citable-facts.d.ts.map +1 -0
- package/dist/rules/aeo/citable-facts.js +90 -0
- package/dist/rules/aeo/citable-facts.js.map +1 -0
- package/dist/rules/aeo/content-modularity.d.ts +11 -0
- package/dist/rules/aeo/content-modularity.d.ts.map +1 -0
- package/dist/rules/aeo/content-modularity.js +107 -0
- package/dist/rules/aeo/content-modularity.js.map +1 -0
- package/dist/rules/aeo/crawler-access.d.ts +25 -0
- package/dist/rules/aeo/crawler-access.d.ts.map +1 -0
- package/dist/rules/aeo/crawler-access.js +116 -0
- package/dist/rules/aeo/crawler-access.js.map +1 -0
- package/dist/rules/aeo/faq-coverage.d.ts +9 -0
- package/dist/rules/aeo/faq-coverage.d.ts.map +1 -0
- package/dist/rules/aeo/faq-coverage.js +71 -0
- package/dist/rules/aeo/faq-coverage.js.map +1 -0
- package/dist/rules/aeo/freshness-signals.d.ts +9 -0
- package/dist/rules/aeo/freshness-signals.d.ts.map +1 -0
- package/dist/rules/aeo/freshness-signals.js +109 -0
- package/dist/rules/aeo/freshness-signals.js.map +1 -0
- package/dist/rules/aeo/llms-txt.d.ts +24 -0
- package/dist/rules/aeo/llms-txt.d.ts.map +1 -0
- package/dist/rules/aeo/llms-txt.js +93 -0
- package/dist/rules/aeo/llms-txt.js.map +1 -0
- package/dist/rules/aeo/non-replicable-value.d.ts +9 -0
- package/dist/rules/aeo/non-replicable-value.d.ts.map +1 -0
- package/dist/rules/aeo/non-replicable-value.js +95 -0
- package/dist/rules/aeo/non-replicable-value.js.map +1 -0
- package/dist/rules/aeo/summary-bait.d.ts +20 -0
- package/dist/rules/aeo/summary-bait.d.ts.map +1 -0
- package/dist/rules/aeo/summary-bait.js +147 -0
- package/dist/rules/aeo/summary-bait.js.map +1 -0
- package/dist/rules/scope.d.ts +12 -0
- package/dist/rules/scope.d.ts.map +1 -0
- package/dist/rules/scope.js +67 -0
- package/dist/rules/scope.js.map +1 -0
- package/dist/types.d.ts +30 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
2
|
+
async function defaultFetch(url, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
3
|
+
const controller = new AbortController();
|
|
4
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
5
|
+
try {
|
|
6
|
+
const response = await fetch(url, { signal: controller.signal });
|
|
7
|
+
if (!response.ok)
|
|
8
|
+
return null;
|
|
9
|
+
return await response.text();
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
finally {
|
|
15
|
+
clearTimeout(timer);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Minimal shape check for the emerging llms.txt convention:
|
|
20
|
+
* - First non-empty line is an `# H1` title
|
|
21
|
+
* - At least one `## ` section heading
|
|
22
|
+
* - At least one markdown link line under a section
|
|
23
|
+
* Deliberately lenient: the spec is still evolving, so only reject obvious garbage.
|
|
24
|
+
*/
|
|
25
|
+
export function validateLlmsTxt(content) {
|
|
26
|
+
const lines = content.split(/\r?\n/);
|
|
27
|
+
let sawTitle = false;
|
|
28
|
+
let sawSection = false;
|
|
29
|
+
let sawLink = false;
|
|
30
|
+
for (const raw of lines) {
|
|
31
|
+
const line = raw.trim();
|
|
32
|
+
if (!line)
|
|
33
|
+
continue;
|
|
34
|
+
if (!sawTitle) {
|
|
35
|
+
if (/^#\s+\S/.test(line)) {
|
|
36
|
+
sawTitle = true;
|
|
37
|
+
}
|
|
38
|
+
else if (line.startsWith("#")) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
return { valid: false, reason: "file does not start with an `# ` H1 title" };
|
|
43
|
+
}
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (/^##\s+\S/.test(line)) {
|
|
47
|
+
sawSection = true;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (/^-\s*\[[^\]]+\]\([^)]+\)/.test(line)) {
|
|
51
|
+
sawLink = true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (!sawTitle)
|
|
55
|
+
return { valid: false, reason: "missing `# ` H1 title" };
|
|
56
|
+
if (!sawSection)
|
|
57
|
+
return { valid: false, reason: "no `## ` section headings found" };
|
|
58
|
+
if (!sawLink)
|
|
59
|
+
return { valid: false, reason: "no markdown link entries found under any section" };
|
|
60
|
+
return { valid: true };
|
|
61
|
+
}
|
|
62
|
+
export async function llmsTxtRule(source, fetcherOrOptions = {}) {
|
|
63
|
+
if (!/^https?:\/\//i.test(source))
|
|
64
|
+
return [];
|
|
65
|
+
const origin = new URL(source).origin;
|
|
66
|
+
const llmsUrl = `${origin}/llms.txt`;
|
|
67
|
+
const fetcher = typeof fetcherOrOptions === "function"
|
|
68
|
+
? fetcherOrOptions
|
|
69
|
+
: (url) => defaultFetch(url, fetcherOrOptions.timeoutMs);
|
|
70
|
+
const text = await fetcher(llmsUrl);
|
|
71
|
+
if (text === null) {
|
|
72
|
+
return [{
|
|
73
|
+
ruleId: "aeo/llms-txt",
|
|
74
|
+
severity: "warning",
|
|
75
|
+
message: `No llms.txt found at ${llmsUrl}.`,
|
|
76
|
+
fix: `Create ${llmsUrl} to guide AI engines toward your most authoritative, citable content. ` +
|
|
77
|
+
`Start with an # H1 title, a blockquote summary, then ## sections listing your key pages as markdown links. ` +
|
|
78
|
+
`See https://llmstxt.org for the emerging convention.`,
|
|
79
|
+
}];
|
|
80
|
+
}
|
|
81
|
+
const check = validateLlmsTxt(text);
|
|
82
|
+
if (!check.valid) {
|
|
83
|
+
return [{
|
|
84
|
+
ruleId: "aeo/llms-txt",
|
|
85
|
+
severity: "warning",
|
|
86
|
+
message: `${llmsUrl} exists but is malformed: ${check.reason}.`,
|
|
87
|
+
fix: `Ensure ${llmsUrl} opens with an "# Site Name" H1, has at least one "## Section" heading, ` +
|
|
88
|
+
`and lists pages as "- [Title](https://...): short description". See https://llmstxt.org.`,
|
|
89
|
+
}];
|
|
90
|
+
}
|
|
91
|
+
return [];
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=llms-txt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llms-txt.js","sourceRoot":"","sources":["../../../src/rules/aeo/llms-txt.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ParsedPage, RuleResult } from "../../types.js";
|
|
2
|
+
export interface NonReplicableValueOptions {
|
|
3
|
+
/** Extra selectors that count as interactive/non-replicable. */
|
|
4
|
+
interactiveSelectors?: string[];
|
|
5
|
+
/** Severity override. Default: "warning". */
|
|
6
|
+
severity?: "info" | "warning" | "error";
|
|
7
|
+
}
|
|
8
|
+
export declare function nonReplicableValueRule(pages: ParsedPage[], options?: NonReplicableValueOptions): RuleResult[];
|
|
9
|
+
//# sourceMappingURL=non-replicable-value.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"non-replicable-value.d.ts","sourceRoot":"","sources":["../../../src/rules/aeo/non-replicable-value.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE7D,MAAM,WAAW,yBAAyB;IACxC,gEAAgE;IAChE,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAChC,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;CACzC;AAqED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,UAAU,EAAE,EACnB,OAAO,CAAC,EAAE,yBAAyB,GAClC,UAAU,EAAE,CA+Bd"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { load } from "cheerio";
|
|
2
|
+
const DEFAULT_INTERACTIVE_SELECTORS = [
|
|
3
|
+
"form",
|
|
4
|
+
"input:not([type=hidden])",
|
|
5
|
+
"select",
|
|
6
|
+
"textarea",
|
|
7
|
+
"iframe",
|
|
8
|
+
"button[type=submit]",
|
|
9
|
+
"canvas",
|
|
10
|
+
"[data-interactive]",
|
|
11
|
+
"[data-calculator]",
|
|
12
|
+
"[data-tool]",
|
|
13
|
+
"[data-widget]",
|
|
14
|
+
"[x-data]",
|
|
15
|
+
".calculator",
|
|
16
|
+
".tool",
|
|
17
|
+
".checker",
|
|
18
|
+
".generator",
|
|
19
|
+
".interactive",
|
|
20
|
+
".widget",
|
|
21
|
+
".comparator",
|
|
22
|
+
];
|
|
23
|
+
const DOWNLOAD_PATTERN = /\.(pdf|docx?|xlsx?|csv|zip|pptx?)(?:$|[?#])/i;
|
|
24
|
+
const GATED_PATTERNS = [
|
|
25
|
+
/\bsign\s*(in|up)\s+to\s+(read|access|view|download|continue)/i,
|
|
26
|
+
/\bpaywall\b/i,
|
|
27
|
+
/\bsubscribe\s+to\s+read\b/i,
|
|
28
|
+
/\blog\s*in\s+to\s+(read|access|view|download|continue)/i,
|
|
29
|
+
];
|
|
30
|
+
function hasInteractiveElement($, html, extraSelectors) {
|
|
31
|
+
const selectors = [...DEFAULT_INTERACTIVE_SELECTORS, ...extraSelectors];
|
|
32
|
+
for (const sel of selectors) {
|
|
33
|
+
try {
|
|
34
|
+
if ($(sel).length > 0)
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// invalid selector — skip
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Framework-generated component markers use hashed attribute names; scan raw HTML for common patterns.
|
|
42
|
+
if (/\bdata-(reactroot|react-[\w-]+|vue-[\w-]+|v-[\w-]+|svelte-[\w-]+)\b/i.test(html))
|
|
43
|
+
return true;
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
function hasDownloadableAsset($) {
|
|
47
|
+
let found = false;
|
|
48
|
+
$("a[href]").each((_, el) => {
|
|
49
|
+
if (found)
|
|
50
|
+
return false;
|
|
51
|
+
const href = $(el).attr("href") ?? "";
|
|
52
|
+
if (DOWNLOAD_PATTERN.test(href) || /[?&]format=(pdf|docx?|xlsx?|csv)\b/i.test(href)) {
|
|
53
|
+
found = true;
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
if ($(el).attr("download") !== undefined) {
|
|
57
|
+
found = true;
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
});
|
|
62
|
+
return found;
|
|
63
|
+
}
|
|
64
|
+
function hasGatedContent(text) {
|
|
65
|
+
return GATED_PATTERNS.some((re) => re.test(text));
|
|
66
|
+
}
|
|
67
|
+
export function nonReplicableValueRule(pages, options) {
|
|
68
|
+
const extra = options?.interactiveSelectors ?? [];
|
|
69
|
+
const severity = options?.severity ?? "warning";
|
|
70
|
+
const findings = [];
|
|
71
|
+
for (const page of pages) {
|
|
72
|
+
const $ = load(page.html);
|
|
73
|
+
const interactive = hasInteractiveElement($, page.html, extra);
|
|
74
|
+
const downloadable = hasDownloadableAsset($);
|
|
75
|
+
const gated = hasGatedContent(page.contentText);
|
|
76
|
+
if (interactive || downloadable || gated)
|
|
77
|
+
continue;
|
|
78
|
+
findings.push({
|
|
79
|
+
ruleId: "aeo/non-replicable-value",
|
|
80
|
+
severity,
|
|
81
|
+
message: `${page.url} contains only text AI can fully summarize — no interactive element, downloadable asset, or gated content detected.`,
|
|
82
|
+
pageUrl: page.url,
|
|
83
|
+
fix: `Add a non-replicable value so users have a reason to click through instead of accepting the AI summary. Options: ` +
|
|
84
|
+
`(1) an interactive tool specific to this page's topic (calculator, checker, comparator, generator), ` +
|
|
85
|
+
`(2) an interactive checklist users can complete, ` +
|
|
86
|
+
`(3) a downloadable asset (PDF, spreadsheet, dataset) visible above the fold, ` +
|
|
87
|
+
`(4) a live preview / try-it-yourself widget, ` +
|
|
88
|
+
`(5) a gated resource (account required). ` +
|
|
89
|
+
`For single-page apps, ensure interactive elements are present in the server-rendered HTML ` +
|
|
90
|
+
`so crawlers see them — client-only widgets are invisible to this audit AND to most AI crawlers.`,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return findings;
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=non-replicable-value.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"non-replicable-value.js","sourceRoot":"","sources":["../../../src/rules/aeo/non-replicable-value.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { EntityMaskPattern, ParsedPage, RuleResult } from "../../types.js";
|
|
2
|
+
export interface SummaryBaitOptions {
|
|
3
|
+
/** Words considered "the opener" for fact-distribution analysis. Default: 150. */
|
|
4
|
+
openerWordCount?: number;
|
|
5
|
+
/** Fraction of citable facts that, when packed into the opener, triggers the rule. Default: 0.7. */
|
|
6
|
+
openerFactConcentrationThreshold?: number;
|
|
7
|
+
/** Minimum distinct facts on the page before the distribution check runs. Default: 3. */
|
|
8
|
+
minFactsToAnalyze?: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Composite rule — fires when a page is "optimized for summarization, not retention":
|
|
12
|
+
* 1. Has a strong answer-first opener (AI Overviews will cite it), AND
|
|
13
|
+
* 2. Has no interactive / downloadable / gated value below the fold, AND
|
|
14
|
+
* 3. Has its citable facts concentrated in the opener (nothing deeper to scroll for).
|
|
15
|
+
*
|
|
16
|
+
* The overlap of these three is the worst-case zero-click page: readers (and AI engines)
|
|
17
|
+
* get everything they need from the summary, and the click-through never happens.
|
|
18
|
+
*/
|
|
19
|
+
export declare function summaryBaitRule(pages: ParsedPage[], entityPatterns: EntityMaskPattern[], options?: SummaryBaitOptions): RuleResult[];
|
|
20
|
+
//# sourceMappingURL=summary-bait.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summary-bait.d.ts","sourceRoot":"","sources":["../../../src/rules/aeo/summary-bait.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAGhF,MAAM,WAAW,kBAAkB;IACjC,kFAAkF;IAClF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oGAAoG;IACpG,gCAAgC,CAAC,EAAE,MAAM,CAAC;IAC1C,yFAAyF;IACzF,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AA8FD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,UAAU,EAAE,EACnB,cAAc,EAAE,iBAAiB,EAAE,EACnC,OAAO,CAAC,EAAE,kBAAkB,GAC3B,UAAU,EAAE,CA2Cd"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { load } from "cheerio";
|
|
2
|
+
import { extractFirstParagraph } from "./answer-first.js";
|
|
3
|
+
// Same fact extractors as aeo/citable-facts — intentionally duplicated so this
|
|
4
|
+
// rule is self-contained and doesn't silently break if citable-facts's regexes
|
|
5
|
+
// drift. Keep in sync when adding new patterns there.
|
|
6
|
+
const FACT_PATTERNS = [
|
|
7
|
+
/\$[\d,]+(\.\d{2})?/g,
|
|
8
|
+
/\b\d+(\.\d+)?\s*%/g,
|
|
9
|
+
/\b\d+(?:-\d+)?\s*(business\s+days?|days?|weeks?|months?|years?|hours?|minutes?)\b/gi,
|
|
10
|
+
/\b(january|february|march|april|may|june|july|august|september|october|november|december)\s+\d{1,2}(?:,\s*\d{4})?\b/gi,
|
|
11
|
+
/\b\d{4}-\d{2}-\d{2}\b/g,
|
|
12
|
+
/\bForm\s+[A-Z0-9][A-Z0-9-]*\b/g,
|
|
13
|
+
];
|
|
14
|
+
const INTERACTIVE_SELECTORS = [
|
|
15
|
+
"form",
|
|
16
|
+
"input:not([type=hidden])",
|
|
17
|
+
"select",
|
|
18
|
+
"textarea",
|
|
19
|
+
"iframe",
|
|
20
|
+
"button[type=submit]",
|
|
21
|
+
"canvas",
|
|
22
|
+
"[data-interactive]",
|
|
23
|
+
"[data-calculator]",
|
|
24
|
+
"[data-tool]",
|
|
25
|
+
"[data-widget]",
|
|
26
|
+
"[x-data]",
|
|
27
|
+
".calculator",
|
|
28
|
+
".tool",
|
|
29
|
+
".checker",
|
|
30
|
+
".generator",
|
|
31
|
+
".interactive",
|
|
32
|
+
".widget",
|
|
33
|
+
".comparator",
|
|
34
|
+
];
|
|
35
|
+
const DOWNLOAD_PATTERN = /\.(pdf|docx?|xlsx?|csv|zip|pptx?)(?:$|[?#])/i;
|
|
36
|
+
function extractFacts(text) {
|
|
37
|
+
const out = new Set();
|
|
38
|
+
for (const re of FACT_PATTERNS) {
|
|
39
|
+
const matches = text.match(re);
|
|
40
|
+
if (!matches)
|
|
41
|
+
continue;
|
|
42
|
+
for (const m of matches)
|
|
43
|
+
out.add(m.trim().toLowerCase());
|
|
44
|
+
}
|
|
45
|
+
return Array.from(out);
|
|
46
|
+
}
|
|
47
|
+
function applyEntityMask(text, patterns) {
|
|
48
|
+
let out = text;
|
|
49
|
+
for (const p of patterns)
|
|
50
|
+
out = out.replace(p.pattern, p.placeholder);
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
function openerHasExtractableAnswer(html) {
|
|
54
|
+
// Echo of aeo/answer-first's minimum bar: an opener exists, it has at least
|
|
55
|
+
// one concrete fact, and it's a complete sentence. If the opener is already
|
|
56
|
+
// broken, aeo/answer-first fires and the user sees that diagnosis; this rule
|
|
57
|
+
// stays silent so we don't produce two overlapping findings per page.
|
|
58
|
+
const opener = extractFirstParagraph(html);
|
|
59
|
+
if (!opener)
|
|
60
|
+
return false;
|
|
61
|
+
const hasFact = FACT_PATTERNS.some((re) => {
|
|
62
|
+
re.lastIndex = 0;
|
|
63
|
+
return re.test(opener);
|
|
64
|
+
});
|
|
65
|
+
const isCompleteSentence = /[.!?]\s*$/.test(opener.trim()) && opener.split(/\s+/).length >= 8;
|
|
66
|
+
return hasFact && isCompleteSentence;
|
|
67
|
+
}
|
|
68
|
+
function pageHasNonReplicableValue(html, contentText) {
|
|
69
|
+
const $ = load(html);
|
|
70
|
+
for (const sel of INTERACTIVE_SELECTORS) {
|
|
71
|
+
try {
|
|
72
|
+
if ($(sel).length > 0)
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
catch { /* invalid selector */ }
|
|
76
|
+
}
|
|
77
|
+
if (/\bdata-(reactroot|react-[\w-]+|vue-[\w-]+|v-[\w-]+|svelte-[\w-]+)\b/i.test(html))
|
|
78
|
+
return true;
|
|
79
|
+
let hasDownload = false;
|
|
80
|
+
$("a[href]").each((_, el) => {
|
|
81
|
+
if (hasDownload)
|
|
82
|
+
return false;
|
|
83
|
+
const href = $(el).attr("href") ?? "";
|
|
84
|
+
if (DOWNLOAD_PATTERN.test(href) || /[?&]format=(pdf|docx?|xlsx?|csv)\b/i.test(href)) {
|
|
85
|
+
hasDownload = true;
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
if ($(el).attr("download") !== undefined) {
|
|
89
|
+
hasDownload = true;
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
return undefined;
|
|
93
|
+
});
|
|
94
|
+
if (hasDownload)
|
|
95
|
+
return true;
|
|
96
|
+
return /\b(sign\s*(in|up)|log\s*in)\s+to\s+(read|access|view|download|continue)/i.test(contentText);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Composite rule — fires when a page is "optimized for summarization, not retention":
|
|
100
|
+
* 1. Has a strong answer-first opener (AI Overviews will cite it), AND
|
|
101
|
+
* 2. Has no interactive / downloadable / gated value below the fold, AND
|
|
102
|
+
* 3. Has its citable facts concentrated in the opener (nothing deeper to scroll for).
|
|
103
|
+
*
|
|
104
|
+
* The overlap of these three is the worst-case zero-click page: readers (and AI engines)
|
|
105
|
+
* get everything they need from the summary, and the click-through never happens.
|
|
106
|
+
*/
|
|
107
|
+
export function summaryBaitRule(pages, entityPatterns, options) {
|
|
108
|
+
const openerWords = options?.openerWordCount ?? 150;
|
|
109
|
+
const threshold = options?.openerFactConcentrationThreshold ?? 0.7;
|
|
110
|
+
const minFacts = options?.minFactsToAnalyze ?? 3;
|
|
111
|
+
const findings = [];
|
|
112
|
+
for (const page of pages) {
|
|
113
|
+
if (!openerHasExtractableAnswer(page.html))
|
|
114
|
+
continue;
|
|
115
|
+
if (pageHasNonReplicableValue(page.html, page.contentText))
|
|
116
|
+
continue;
|
|
117
|
+
const maskedFull = applyEntityMask(page.contentText, entityPatterns);
|
|
118
|
+
const openerText = maskedFull.split(/\s+/).slice(0, openerWords).join(" ");
|
|
119
|
+
const fullFacts = extractFacts(maskedFull);
|
|
120
|
+
if (fullFacts.length < minFacts)
|
|
121
|
+
continue;
|
|
122
|
+
const openerFacts = extractFacts(openerText);
|
|
123
|
+
const concentration = openerFacts.length / fullFacts.length;
|
|
124
|
+
if (concentration < threshold)
|
|
125
|
+
continue;
|
|
126
|
+
findings.push({
|
|
127
|
+
ruleId: "aeo/summary-bait",
|
|
128
|
+
severity: "error",
|
|
129
|
+
message: `${page.url} is optimized for summarization, not retention. ` +
|
|
130
|
+
`${Math.round(concentration * 100)}% of citable facts (${openerFacts.length}/${fullFacts.length}) ` +
|
|
131
|
+
`sit in the first ${openerWords} words, the page has no interactive / downloadable / gated value ` +
|
|
132
|
+
`below the fold, and the opener passes answer-first. AI Overviews will cite this page ` +
|
|
133
|
+
`without sending the click.`,
|
|
134
|
+
pageUrl: page.url,
|
|
135
|
+
fix: `Redistribute value across the page so the full answer requires scrolling: ` +
|
|
136
|
+
`(1) move the interactive element — calculator, checklist, comparison tool, generator, ` +
|
|
137
|
+
`downloadable asset — above the fold, or at least into a prominent section below the opener; ` +
|
|
138
|
+
`(2) push a portion of the citable facts into a "Details" / "Requirements" / "Full breakdown" ` +
|
|
139
|
+
`section deeper on the page; ` +
|
|
140
|
+
`(3) add non-replicable value the summary can't carry (worked examples, user-specific calculations, ` +
|
|
141
|
+
`gated templates). ` +
|
|
142
|
+
`A good opener plus no deeper value guarantees zero-click traffic loss.`,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
return findings;
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=summary-bait.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summary-bait.js","sourceRoot":"","sources":["../../../src/rules/aeo/summary-bait.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declarative scope for each rule ID.
|
|
3
|
+
* - "page": output depends only on a single parsed page.
|
|
4
|
+
* - "corpus": output requires the full set of pages (clustering, cross-page comparisons).
|
|
5
|
+
*
|
|
6
|
+
* Diff-audit dispatch reads this map and skips corpus rules when `state.since` is set.
|
|
7
|
+
*/
|
|
8
|
+
export type RuleScope = "page" | "corpus";
|
|
9
|
+
export declare const RULE_SCOPE: Record<string, RuleScope>;
|
|
10
|
+
/** Returns true when the rule may run in diff (page-scoped) mode. Unknown ids default to corpus (safer). */
|
|
11
|
+
export declare function isRuleAllowedInDiff(ruleId: string): boolean;
|
|
12
|
+
//# sourceMappingURL=scope.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scope.d.ts","sourceRoot":"","sources":["../../src/rules/scope.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAC;AAE1C,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAqEhD,CAAC;AAEF,4GAA4G;AAC5G,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAE3D"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export const RULE_SCOPE = {
|
|
2
|
+
// spam
|
|
3
|
+
"spam/near-duplicate": "corpus",
|
|
4
|
+
"spam/entity-swap": "corpus",
|
|
5
|
+
"spam/thin-content": "page",
|
|
6
|
+
"spam/boilerplate-ratio": "corpus",
|
|
7
|
+
"spam/template-diversity": "corpus",
|
|
8
|
+
"spam/publication-velocity": "corpus",
|
|
9
|
+
"spam/doorway-pattern": "corpus",
|
|
10
|
+
"spam/template-coverage": "corpus",
|
|
11
|
+
// content
|
|
12
|
+
"content/unique-value": "corpus",
|
|
13
|
+
"content/heading-uniqueness": "corpus",
|
|
14
|
+
"content/meta-uniqueness": "corpus",
|
|
15
|
+
"content/missing-author": "page",
|
|
16
|
+
"content/eeat-signals": "page",
|
|
17
|
+
// links (all need the global link graph)
|
|
18
|
+
"links/orphan-pages": "corpus",
|
|
19
|
+
"links/dead-ends": "corpus",
|
|
20
|
+
"links/cluster-connectivity": "corpus",
|
|
21
|
+
"links/hub-pages": "corpus",
|
|
22
|
+
"links/link-depth": "corpus",
|
|
23
|
+
"links/unreachable-from-root": "corpus",
|
|
24
|
+
"links/hub-pages-skipped": "corpus",
|
|
25
|
+
// tech (per-page except sitemap / robots / canonical-consistency which need knownUrls)
|
|
26
|
+
"tech/canonical-consistency": "corpus",
|
|
27
|
+
"tech/canonical-noindex-conflict": "page",
|
|
28
|
+
"tech/robots-noindex-conflict": "corpus",
|
|
29
|
+
"tech/sitemap-completeness": "corpus",
|
|
30
|
+
"tech/redirect-chain": "page",
|
|
31
|
+
"tech/soft-404": "page",
|
|
32
|
+
"tech/og-completeness": "page",
|
|
33
|
+
"tech/hreflang-consistency": "corpus",
|
|
34
|
+
"tech/robots-compliance": "corpus",
|
|
35
|
+
"tech/robots-sitemap-presence": "corpus",
|
|
36
|
+
// schema
|
|
37
|
+
"schema/json-ld-valid": "page",
|
|
38
|
+
"schema/required-fields": "page",
|
|
39
|
+
"schema/consistency": "corpus",
|
|
40
|
+
// cannibal (cross-page by definition)
|
|
41
|
+
"cannibal/title-overlap": "corpus",
|
|
42
|
+
"cannibal/keyword-collision": "corpus",
|
|
43
|
+
"cannibal/url-pattern": "corpus",
|
|
44
|
+
// data binding
|
|
45
|
+
"data/missing-binding": "page",
|
|
46
|
+
"data/identical-across-pages": "corpus",
|
|
47
|
+
// audit-internal
|
|
48
|
+
"audit/duplicate-url": "corpus",
|
|
49
|
+
// AEO (Answer Engine Optimization) — forward-compatible entries for rules
|
|
50
|
+
// shipping from a parallel core-engine change. `llms-txt` and `crawler-access`
|
|
51
|
+
// are site-wide (need host config / robots surface); the rest evaluate a
|
|
52
|
+
// single page's content structure and must run in daily diff-audits.
|
|
53
|
+
"aeo/llms-txt": "corpus",
|
|
54
|
+
"aeo/crawler-access": "corpus",
|
|
55
|
+
"aeo/freshness-signals": "page",
|
|
56
|
+
"aeo/faq-coverage": "page",
|
|
57
|
+
"aeo/answer-first": "page",
|
|
58
|
+
"aeo/citable-facts": "page",
|
|
59
|
+
"aeo/non-replicable-value": "page",
|
|
60
|
+
"aeo/content-modularity": "page",
|
|
61
|
+
"aeo/summary-bait": "page",
|
|
62
|
+
};
|
|
63
|
+
/** Returns true when the rule may run in diff (page-scoped) mode. Unknown ids default to corpus (safer). */
|
|
64
|
+
export function isRuleAllowedInDiff(ruleId) {
|
|
65
|
+
return (RULE_SCOPE[ruleId] ?? "corpus") === "page";
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=scope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scope.js","sourceRoot":"","sources":["../../src/rules/scope.ts"],"names":[],"mappings":"AASA,MAAM,CAAC,MAAM,UAAU,GAA8B;IACnD,OAAO;IACP,qBAAqB,EAAE,QAAQ;IAC/B,kBAAkB,EAAE,QAAQ;IAC5B,mBAAmB,EAAE,MAAM;IAC3B,wBAAwB,EAAE,QAAQ;IAClC,yBAAyB,EAAE,QAAQ;IACnC,2BAA2B,EAAE,QAAQ;IACrC,sBAAsB,EAAE,QAAQ;IAChC,wBAAwB,EAAE,QAAQ;IAElC,UAAU;IACV,sBAAsB,EAAE,QAAQ;IAChC,4BAA4B,EAAE,QAAQ;IACtC,yBAAyB,EAAE,QAAQ;IACnC,wBAAwB,EAAE,MAAM;IAChC,sBAAsB,EAAE,MAAM;IAE9B,yCAAyC;IACzC,oBAAoB,EAAE,QAAQ;IAC9B,iBAAiB,EAAE,QAAQ;IAC3B,4BAA4B,EAAE,QAAQ;IACtC,iBAAiB,EAAE,QAAQ;IAC3B,kBAAkB,EAAE,QAAQ;IAC5B,6BAA6B,EAAE,QAAQ;IACvC,yBAAyB,EAAE,QAAQ;IAEnC,uFAAuF;IACvF,4BAA4B,EAAE,QAAQ;IACtC,iCAAiC,EAAE,MAAM;IACzC,8BAA8B,EAAE,QAAQ;IACxC,2BAA2B,EAAE,QAAQ;IACrC,qBAAqB,EAAE,MAAM;IAC7B,eAAe,EAAE,MAAM;IACvB,sBAAsB,EAAE,MAAM;IAC9B,2BAA2B,EAAE,QAAQ;IACrC,wBAAwB,EAAE,QAAQ;IAClC,8BAA8B,EAAE,QAAQ;IAExC,SAAS;IACT,sBAAsB,EAAE,MAAM;IAC9B,wBAAwB,EAAE,MAAM;IAChC,oBAAoB,EAAE,QAAQ;IAE9B,sCAAsC;IACtC,wBAAwB,EAAE,QAAQ;IAClC,4BAA4B,EAAE,QAAQ;IACtC,sBAAsB,EAAE,QAAQ;IAEhC,eAAe;IACf,sBAAsB,EAAE,MAAM;IAC9B,6BAA6B,EAAE,QAAQ;IAEvC,iBAAiB;IACjB,qBAAqB,EAAE,QAAQ;IAE/B,0EAA0E;IAC1E,+EAA+E;IAC/E,yEAAyE;IACzE,qEAAqE;IACrE,cAAc,EAAE,QAAQ;IACxB,oBAAoB,EAAE,QAAQ;IAC9B,uBAAuB,EAAE,MAAM;IAC/B,kBAAkB,EAAE,MAAM;IAC1B,kBAAkB,EAAE,MAAM;IAC1B,mBAAmB,EAAE,MAAM;IAC3B,0BAA0B,EAAE,MAAM;IAClC,wBAAwB,EAAE,MAAM;IAChC,kBAAkB,EAAE,MAAM;CAC3B,CAAC;AAEF,4GAA4G;AAC5G,MAAM,UAAU,mBAAmB,CAAC,MAAc;IAChD,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,QAAQ,CAAC,KAAK,MAAM,CAAC;AACrD,CAAC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -54,8 +54,10 @@ export interface RuleResult {
|
|
|
54
54
|
export interface CategoryScores {
|
|
55
55
|
spam: number;
|
|
56
56
|
content: number;
|
|
57
|
+
aeo: number;
|
|
57
58
|
links: number;
|
|
58
59
|
tech: number;
|
|
60
|
+
data: number;
|
|
59
61
|
schema: number;
|
|
60
62
|
cannibal: number;
|
|
61
63
|
}
|
|
@@ -143,6 +145,8 @@ export interface PageGroupConfig {
|
|
|
143
145
|
overrides?: Record<string, Record<string, unknown>>;
|
|
144
146
|
}
|
|
145
147
|
export interface AuditOptions {
|
|
148
|
+
/** When "diff", corpus-scoped rules are skipped (used by hosted diff-audits). Default: "full". */
|
|
149
|
+
mode?: "full" | "diff";
|
|
146
150
|
rules?: {
|
|
147
151
|
/** Passed to `normalizeAuditUrl` for crawl URLs and resolved links (default: strip query, keep www). */
|
|
148
152
|
stripUrlQuery?: boolean;
|
|
@@ -163,6 +167,20 @@ export interface AuditOptions {
|
|
|
163
167
|
titleOverlapThreshold?: number;
|
|
164
168
|
keywordCollisionMinShared?: number;
|
|
165
169
|
templateCoverageMinPages?: number;
|
|
170
|
+
/** aeo/answer-first: max words in the first paragraph for extractable answer. */
|
|
171
|
+
answerFirstMaxWords?: number;
|
|
172
|
+
/** aeo/citable-facts: below this count, a page errors. */
|
|
173
|
+
citableFactsMin?: number;
|
|
174
|
+
/** aeo/citable-facts: at or above this count, a page passes. */
|
|
175
|
+
citableFactsTarget?: number;
|
|
176
|
+
/** aeo/freshness-signals: days before a dateModified is considered stale. */
|
|
177
|
+
freshnessMaxStaleDays?: number;
|
|
178
|
+
/** aeo/content-modularity: words beyond which a paragraph is flagged. */
|
|
179
|
+
modularityMaxParagraphWords?: number;
|
|
180
|
+
/** aeo/content-modularity: minimum ratio of self-contained sections (0–1). */
|
|
181
|
+
modularityMinSelfContainedRatio?: number;
|
|
182
|
+
/** aeo/faq-coverage: min question-phrased H2s to trigger the check. */
|
|
183
|
+
faqMinQuestionHeadings?: number;
|
|
166
184
|
};
|
|
167
185
|
/** Max parallel HTTP fetches when auditing a remote sitemap (default: 5). */
|
|
168
186
|
concurrency?: number;
|
|
@@ -190,6 +208,18 @@ export interface AuditOptions {
|
|
|
190
208
|
/** Browser rendering options for client-rendered pages. */
|
|
191
209
|
render?: {
|
|
192
210
|
browserWsEndpoint?: string;
|
|
211
|
+
/**
|
|
212
|
+
* How to handle third-party analytics / session-replay beacons during
|
|
213
|
+
* rendered audits.
|
|
214
|
+
* "block" (default) — abort known analytics hosts so the audit doesn't
|
|
215
|
+
* inject fake pageviews / sessions into the site owner's dashboards.
|
|
216
|
+
* "allow-first-party" — block third-party analytics only; same-origin
|
|
217
|
+
* requests pass through (for sites that self-host analytics).
|
|
218
|
+
* "allow" — don't intercept anything (only for sites you own).
|
|
219
|
+
*/
|
|
220
|
+
analyticsMode?: "block" | "allow" | "allow-first-party";
|
|
221
|
+
/** Extra host tokens to block in addition to the default list. */
|
|
222
|
+
extraBlockedHosts?: readonly string[];
|
|
193
223
|
};
|
|
194
224
|
/** Override template auto-detection. When set, skips heuristic detection. */
|
|
195
225
|
templateGenerated?: boolean;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pseolint/core",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Programmatic SEO audit engine —
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"description": "Programmatic SEO audit engine — 43 rules across 8 categories for SpamBrain penalty risk and AI Overview citability",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ouranos Labs <contact@ouranos-labs.dev>",
|
|
7
7
|
"homepage": "https://pseolint.dev",
|
|
@@ -88,4 +88,4 @@
|
|
|
88
88
|
"@ai-sdk/anthropic": "^2.0.0",
|
|
89
89
|
"ollama-ai-provider-v2": "^1.0.0"
|
|
90
90
|
}
|
|
91
|
-
}
|
|
91
|
+
}
|