@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.
Files changed (77) hide show
  1. package/README.md +10 -9
  2. package/dist/ai/prompt.d.ts +1 -1
  3. package/dist/ai/prompt.d.ts.map +1 -1
  4. package/dist/ai/prompt.js +13 -1
  5. package/dist/ai/prompt.js.map +1 -1
  6. package/dist/ai/triage.d.ts +15 -1
  7. package/dist/ai/triage.d.ts.map +1 -1
  8. package/dist/ai/triage.js +30 -0
  9. package/dist/ai/triage.js.map +1 -1
  10. package/dist/analytics-blocklist.d.ts +28 -0
  11. package/dist/analytics-blocklist.d.ts.map +1 -0
  12. package/dist/analytics-blocklist.js +129 -0
  13. package/dist/analytics-blocklist.js.map +1 -0
  14. package/dist/auditor.d.ts.map +1 -1
  15. package/dist/auditor.js +130 -46
  16. package/dist/auditor.js.map +1 -1
  17. package/dist/formatters/console.d.ts +9 -0
  18. package/dist/formatters/console.d.ts.map +1 -1
  19. package/dist/formatters/console.js +53 -0
  20. package/dist/formatters/console.js.map +1 -1
  21. package/dist/formatters/html.d.ts.map +1 -1
  22. package/dist/formatters/html.js +557 -144
  23. package/dist/formatters/html.js.map +1 -1
  24. package/dist/index.d.ts +14 -1
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +12 -1
  27. package/dist/index.js.map +1 -1
  28. package/dist/renderer.d.ts +14 -0
  29. package/dist/renderer.d.ts.map +1 -1
  30. package/dist/renderer.js +130 -4
  31. package/dist/renderer.js.map +1 -1
  32. package/dist/rule-references.d.ts.map +1 -1
  33. package/dist/rule-references.js +9 -0
  34. package/dist/rule-references.js.map +1 -1
  35. package/dist/rules/aeo/answer-first.d.ts +18 -0
  36. package/dist/rules/aeo/answer-first.d.ts.map +1 -0
  37. package/dist/rules/aeo/answer-first.js +191 -0
  38. package/dist/rules/aeo/answer-first.js.map +1 -0
  39. package/dist/rules/aeo/citable-facts.d.ts +9 -0
  40. package/dist/rules/aeo/citable-facts.d.ts.map +1 -0
  41. package/dist/rules/aeo/citable-facts.js +90 -0
  42. package/dist/rules/aeo/citable-facts.js.map +1 -0
  43. package/dist/rules/aeo/content-modularity.d.ts +11 -0
  44. package/dist/rules/aeo/content-modularity.d.ts.map +1 -0
  45. package/dist/rules/aeo/content-modularity.js +107 -0
  46. package/dist/rules/aeo/content-modularity.js.map +1 -0
  47. package/dist/rules/aeo/crawler-access.d.ts +25 -0
  48. package/dist/rules/aeo/crawler-access.d.ts.map +1 -0
  49. package/dist/rules/aeo/crawler-access.js +116 -0
  50. package/dist/rules/aeo/crawler-access.js.map +1 -0
  51. package/dist/rules/aeo/faq-coverage.d.ts +9 -0
  52. package/dist/rules/aeo/faq-coverage.d.ts.map +1 -0
  53. package/dist/rules/aeo/faq-coverage.js +71 -0
  54. package/dist/rules/aeo/faq-coverage.js.map +1 -0
  55. package/dist/rules/aeo/freshness-signals.d.ts +9 -0
  56. package/dist/rules/aeo/freshness-signals.d.ts.map +1 -0
  57. package/dist/rules/aeo/freshness-signals.js +109 -0
  58. package/dist/rules/aeo/freshness-signals.js.map +1 -0
  59. package/dist/rules/aeo/llms-txt.d.ts +24 -0
  60. package/dist/rules/aeo/llms-txt.d.ts.map +1 -0
  61. package/dist/rules/aeo/llms-txt.js +93 -0
  62. package/dist/rules/aeo/llms-txt.js.map +1 -0
  63. package/dist/rules/aeo/non-replicable-value.d.ts +9 -0
  64. package/dist/rules/aeo/non-replicable-value.d.ts.map +1 -0
  65. package/dist/rules/aeo/non-replicable-value.js +95 -0
  66. package/dist/rules/aeo/non-replicable-value.js.map +1 -0
  67. package/dist/rules/aeo/summary-bait.d.ts +20 -0
  68. package/dist/rules/aeo/summary-bait.d.ts.map +1 -0
  69. package/dist/rules/aeo/summary-bait.js +147 -0
  70. package/dist/rules/aeo/summary-bait.js.map +1 -0
  71. package/dist/rules/scope.d.ts +12 -0
  72. package/dist/rules/scope.d.ts.map +1 -0
  73. package/dist/rules/scope.js +67 -0
  74. package/dist/rules/scope.js.map +1 -0
  75. package/dist/types.d.ts +30 -0
  76. package/dist/types.d.ts.map +1 -1
  77. 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;
@@ -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.2.2",
4
- "description": "Programmatic SEO audit engine — 34 rules across 6 categories for SpamBrain risk detection",
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
+ }