@pseolint/core 0.4.3 → 0.5.4

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 (270) hide show
  1. package/README.md +264 -169
  2. package/dist/ai/manifest/diff.d.ts +78 -0
  3. package/dist/ai/manifest/diff.d.ts.map +1 -0
  4. package/dist/ai/manifest/diff.js +139 -0
  5. package/dist/ai/manifest/diff.js.map +1 -0
  6. package/dist/ai/manifest/index.d.ts +18 -0
  7. package/dist/ai/manifest/index.d.ts.map +1 -0
  8. package/dist/ai/manifest/index.js +15 -0
  9. package/dist/ai/manifest/index.js.map +1 -0
  10. package/dist/ai/manifest/validate-manifest.d.ts +37 -0
  11. package/dist/ai/manifest/validate-manifest.d.ts.map +1 -0
  12. package/dist/ai/manifest/validate-manifest.js +67 -0
  13. package/dist/ai/manifest/validate-manifest.js.map +1 -0
  14. package/dist/ai/manifest/validators/domain-patches.d.ts +15 -0
  15. package/dist/ai/manifest/validators/domain-patches.d.ts.map +1 -0
  16. package/dist/ai/manifest/validators/domain-patches.js +110 -0
  17. package/dist/ai/manifest/validators/domain-patches.js.map +1 -0
  18. package/dist/ai/manifest/validators/index.d.ts +5 -0
  19. package/dist/ai/manifest/validators/index.d.ts.map +1 -0
  20. package/dist/ai/manifest/validators/index.js +4 -0
  21. package/dist/ai/manifest/validators/index.js.map +1 -0
  22. package/dist/ai/manifest/validators/page-changes.d.ts +36 -0
  23. package/dist/ai/manifest/validators/page-changes.d.ts.map +1 -0
  24. package/dist/ai/manifest/validators/page-changes.js +221 -0
  25. package/dist/ai/manifest/validators/page-changes.js.map +1 -0
  26. package/dist/ai/manifest/validators/types.d.ts +17 -0
  27. package/dist/ai/manifest/validators/types.d.ts.map +1 -0
  28. package/dist/ai/manifest/validators/types.js +5 -0
  29. package/dist/ai/manifest/validators/types.js.map +1 -0
  30. package/dist/ai/orchestrate.d.ts +74 -0
  31. package/dist/ai/orchestrate.d.ts.map +1 -0
  32. package/dist/ai/orchestrate.js +54 -0
  33. package/dist/ai/orchestrate.js.map +1 -0
  34. package/dist/ai/orchestrator/budget.d.ts +57 -0
  35. package/dist/ai/orchestrator/budget.d.ts.map +1 -0
  36. package/dist/ai/orchestrator/budget.js +114 -0
  37. package/dist/ai/orchestrator/budget.js.map +1 -0
  38. package/dist/ai/orchestrator/finish-tool.d.ts +568 -0
  39. package/dist/ai/orchestrator/finish-tool.d.ts.map +1 -0
  40. package/dist/ai/orchestrator/finish-tool.js +114 -0
  41. package/dist/ai/orchestrator/finish-tool.js.map +1 -0
  42. package/dist/ai/orchestrator/index.d.ts +25 -0
  43. package/dist/ai/orchestrator/index.d.ts.map +1 -0
  44. package/dist/ai/orchestrator/index.js +21 -0
  45. package/dist/ai/orchestrator/index.js.map +1 -0
  46. package/dist/ai/orchestrator/log.d.ts +24 -0
  47. package/dist/ai/orchestrator/log.d.ts.map +1 -0
  48. package/dist/ai/orchestrator/log.js +48 -0
  49. package/dist/ai/orchestrator/log.js.map +1 -0
  50. package/dist/ai/orchestrator/page-cache.d.ts +64 -0
  51. package/dist/ai/orchestrator/page-cache.d.ts.map +1 -0
  52. package/dist/ai/orchestrator/page-cache.js +127 -0
  53. package/dist/ai/orchestrator/page-cache.js.map +1 -0
  54. package/dist/ai/orchestrator/prompt.d.ts +16 -0
  55. package/dist/ai/orchestrator/prompt.d.ts.map +1 -0
  56. package/dist/ai/orchestrator/prompt.js +52 -0
  57. package/dist/ai/orchestrator/prompt.js.map +1 -0
  58. package/dist/ai/orchestrator/runner.d.ts +65 -0
  59. package/dist/ai/orchestrator/runner.d.ts.map +1 -0
  60. package/dist/ai/orchestrator/runner.js +223 -0
  61. package/dist/ai/orchestrator/runner.js.map +1 -0
  62. package/dist/ai/orchestrator/session.d.ts +44 -0
  63. package/dist/ai/orchestrator/session.d.ts.map +1 -0
  64. package/dist/ai/orchestrator/session.js +64 -0
  65. package/dist/ai/orchestrator/session.js.map +1 -0
  66. package/dist/ai/orchestrator/types.d.ts +99 -0
  67. package/dist/ai/orchestrator/types.d.ts.map +1 -0
  68. package/dist/ai/orchestrator/types.js +8 -0
  69. package/dist/ai/orchestrator/types.js.map +1 -0
  70. package/dist/ai/probes/cache.d.ts +12 -0
  71. package/dist/ai/probes/cache.d.ts.map +1 -0
  72. package/dist/ai/probes/cache.js +46 -0
  73. package/dist/ai/probes/cache.js.map +1 -0
  74. package/dist/ai/tools/ask-ai-engine.d.ts +77 -0
  75. package/dist/ai/tools/ask-ai-engine.d.ts.map +1 -0
  76. package/dist/ai/tools/ask-ai-engine.js +253 -0
  77. package/dist/ai/tools/ask-ai-engine.js.map +1 -0
  78. package/dist/ai/tools/check-domain-crawler-access.d.ts +71 -0
  79. package/dist/ai/tools/check-domain-crawler-access.d.ts.map +1 -0
  80. package/dist/ai/tools/check-domain-crawler-access.js +76 -0
  81. package/dist/ai/tools/check-domain-crawler-access.js.map +1 -0
  82. package/dist/ai/tools/check-domain-llms-txt.d.ts +70 -0
  83. package/dist/ai/tools/check-domain-llms-txt.d.ts.map +1 -0
  84. package/dist/ai/tools/check-domain-llms-txt.js +75 -0
  85. package/dist/ai/tools/check-domain-llms-txt.js.map +1 -0
  86. package/dist/ai/tools/check-indexability.d.ts +58 -0
  87. package/dist/ai/tools/check-indexability.d.ts.map +1 -0
  88. package/dist/ai/tools/check-indexability.js +64 -0
  89. package/dist/ai/tools/check-indexability.js.map +1 -0
  90. package/dist/ai/tools/check-robots.d.ts +68 -0
  91. package/dist/ai/tools/check-robots.d.ts.map +1 -0
  92. package/dist/ai/tools/check-robots.js +90 -0
  93. package/dist/ai/tools/check-robots.js.map +1 -0
  94. package/dist/ai/tools/check-rule-answer-first.d.ts +54 -0
  95. package/dist/ai/tools/check-rule-answer-first.d.ts.map +1 -0
  96. package/dist/ai/tools/check-rule-answer-first.js +50 -0
  97. package/dist/ai/tools/check-rule-answer-first.js.map +1 -0
  98. package/dist/ai/tools/check-rule-canonical-consistency.d.ts +66 -0
  99. package/dist/ai/tools/check-rule-canonical-consistency.d.ts.map +1 -0
  100. package/dist/ai/tools/check-rule-canonical-consistency.js +51 -0
  101. package/dist/ai/tools/check-rule-canonical-consistency.js.map +1 -0
  102. package/dist/ai/tools/check-rule-citable-facts.d.ts +58 -0
  103. package/dist/ai/tools/check-rule-citable-facts.d.ts.map +1 -0
  104. package/dist/ai/tools/check-rule-citable-facts.js +41 -0
  105. package/dist/ai/tools/check-rule-citable-facts.js.map +1 -0
  106. package/dist/ai/tools/check-rule-content-modularity.d.ts +58 -0
  107. package/dist/ai/tools/check-rule-content-modularity.d.ts.map +1 -0
  108. package/dist/ai/tools/check-rule-content-modularity.js +45 -0
  109. package/dist/ai/tools/check-rule-content-modularity.js.map +1 -0
  110. package/dist/ai/tools/check-rule-faq-coverage.d.ts +54 -0
  111. package/dist/ai/tools/check-rule-faq-coverage.d.ts.map +1 -0
  112. package/dist/ai/tools/check-rule-faq-coverage.js +39 -0
  113. package/dist/ai/tools/check-rule-faq-coverage.js.map +1 -0
  114. package/dist/ai/tools/check-rule-freshness-signals.d.ts +54 -0
  115. package/dist/ai/tools/check-rule-freshness-signals.d.ts.map +1 -0
  116. package/dist/ai/tools/check-rule-freshness-signals.js +45 -0
  117. package/dist/ai/tools/check-rule-freshness-signals.js.map +1 -0
  118. package/dist/ai/tools/check-rule-json-ld-valid.d.ts +54 -0
  119. package/dist/ai/tools/check-rule-json-ld-valid.d.ts.map +1 -0
  120. package/dist/ai/tools/check-rule-json-ld-valid.js +44 -0
  121. package/dist/ai/tools/check-rule-json-ld-valid.js.map +1 -0
  122. package/dist/ai/tools/check-rule-missing-author.d.ts +54 -0
  123. package/dist/ai/tools/check-rule-missing-author.d.ts.map +1 -0
  124. package/dist/ai/tools/check-rule-missing-author.js +45 -0
  125. package/dist/ai/tools/check-rule-missing-author.js.map +1 -0
  126. package/dist/ai/tools/check-rule-near-duplicate.d.ts +82 -0
  127. package/dist/ai/tools/check-rule-near-duplicate.d.ts.map +1 -0
  128. package/dist/ai/tools/check-rule-near-duplicate.js +63 -0
  129. package/dist/ai/tools/check-rule-near-duplicate.js.map +1 -0
  130. package/dist/ai/tools/check-rule-required-fields.d.ts +50 -0
  131. package/dist/ai/tools/check-rule-required-fields.d.ts.map +1 -0
  132. package/dist/ai/tools/check-rule-required-fields.js +38 -0
  133. package/dist/ai/tools/check-rule-required-fields.js.map +1 -0
  134. package/dist/ai/tools/check-rule-schema-consistency.d.ts +54 -0
  135. package/dist/ai/tools/check-rule-schema-consistency.d.ts.map +1 -0
  136. package/dist/ai/tools/check-rule-schema-consistency.js +44 -0
  137. package/dist/ai/tools/check-rule-schema-consistency.js.map +1 -0
  138. package/dist/ai/tools/check-rule-summary-bait.d.ts +54 -0
  139. package/dist/ai/tools/check-rule-summary-bait.d.ts.map +1 -0
  140. package/dist/ai/tools/check-rule-summary-bait.js +39 -0
  141. package/dist/ai/tools/check-rule-summary-bait.js.map +1 -0
  142. package/dist/ai/tools/check-rule-thin-content.d.ts +66 -0
  143. package/dist/ai/tools/check-rule-thin-content.d.ts.map +1 -0
  144. package/dist/ai/tools/check-rule-thin-content.js +58 -0
  145. package/dist/ai/tools/check-rule-thin-content.js.map +1 -0
  146. package/dist/ai/tools/detect-templates.d.ts +60 -0
  147. package/dist/ai/tools/detect-templates.d.ts.map +1 -0
  148. package/dist/ai/tools/detect-templates.js +43 -0
  149. package/dist/ai/tools/detect-templates.js.map +1 -0
  150. package/dist/ai/tools/fetch-page.d.ts +70 -0
  151. package/dist/ai/tools/fetch-page.d.ts.map +1 -0
  152. package/dist/ai/tools/fetch-page.js +93 -0
  153. package/dist/ai/tools/fetch-page.js.map +1 -0
  154. package/dist/ai/tools/fetch-sitemap.d.ts +60 -0
  155. package/dist/ai/tools/fetch-sitemap.d.ts.map +1 -0
  156. package/dist/ai/tools/fetch-sitemap.js +116 -0
  157. package/dist/ai/tools/fetch-sitemap.js.map +1 -0
  158. package/dist/ai/tools/index.d.ts +1555 -0
  159. package/dist/ai/tools/index.d.ts.map +1 -0
  160. package/dist/ai/tools/index.js +119 -0
  161. package/dist/ai/tools/index.js.map +1 -0
  162. package/dist/ai/tools/parse-page.d.ts +94 -0
  163. package/dist/ai/tools/parse-page.d.ts.map +1 -0
  164. package/dist/ai/tools/parse-page.js +108 -0
  165. package/dist/ai/tools/parse-page.js.map +1 -0
  166. package/dist/ai/tools/query-serp.d.ts +113 -0
  167. package/dist/ai/tools/query-serp.d.ts.map +1 -0
  168. package/dist/ai/tools/query-serp.js +131 -0
  169. package/dist/ai/tools/query-serp.js.map +1 -0
  170. package/dist/ai/tools/sample-template.d.ts +67 -0
  171. package/dist/ai/tools/sample-template.d.ts.map +1 -0
  172. package/dist/ai/tools/sample-template.js +75 -0
  173. package/dist/ai/tools/sample-template.js.map +1 -0
  174. package/dist/ai/tools/types.d.ts +73 -0
  175. package/dist/ai/tools/types.d.ts.map +1 -0
  176. package/dist/ai/tools/types.js +64 -0
  177. package/dist/ai/tools/types.js.map +1 -0
  178. package/dist/ai/tools/validate-jsonld.d.ts +62 -0
  179. package/dist/ai/tools/validate-jsonld.d.ts.map +1 -0
  180. package/dist/ai/tools/validate-jsonld.js +84 -0
  181. package/dist/ai/tools/validate-jsonld.js.map +1 -0
  182. package/dist/auditor.d.ts +4 -0
  183. package/dist/auditor.d.ts.map +1 -1
  184. package/dist/auditor.js +629 -64
  185. package/dist/auditor.js.map +1 -1
  186. package/dist/backpressure.d.ts.map +1 -1
  187. package/dist/backpressure.js +10 -3
  188. package/dist/backpressure.js.map +1 -1
  189. package/dist/enrich-findings.d.ts.map +1 -1
  190. package/dist/enrich-findings.js +15 -1
  191. package/dist/enrich-findings.js.map +1 -1
  192. package/dist/formatters/console.d.ts.map +1 -1
  193. package/dist/formatters/console.js +13 -0
  194. package/dist/formatters/console.js.map +1 -1
  195. package/dist/formatters/markdown.d.ts.map +1 -1
  196. package/dist/formatters/markdown.js +20 -2
  197. package/dist/formatters/markdown.js.map +1 -1
  198. package/dist/index.d.ts +12 -1
  199. package/dist/index.d.ts.map +1 -1
  200. package/dist/index.js +8 -0
  201. package/dist/index.js.map +1 -1
  202. package/dist/rule-references.d.ts.map +1 -1
  203. package/dist/rule-references.js +5 -0
  204. package/dist/rule-references.js.map +1 -1
  205. package/dist/rules/content/heading-structure.d.ts +21 -0
  206. package/dist/rules/content/heading-structure.d.ts.map +1 -0
  207. package/dist/rules/content/heading-structure.js +56 -0
  208. package/dist/rules/content/heading-structure.js.map +1 -0
  209. package/dist/rules/content/image-alt-text.d.ts +18 -0
  210. package/dist/rules/content/image-alt-text.d.ts.map +1 -0
  211. package/dist/rules/content/image-alt-text.js +77 -0
  212. package/dist/rules/content/image-alt-text.js.map +1 -0
  213. package/dist/rules/content/title-uniqueness.d.ts +18 -0
  214. package/dist/rules/content/title-uniqueness.d.ts.map +1 -0
  215. package/dist/rules/content/title-uniqueness.js +70 -0
  216. package/dist/rules/content/title-uniqueness.js.map +1 -0
  217. package/dist/rules/links/host-section-divergence.d.ts +3 -0
  218. package/dist/rules/links/host-section-divergence.d.ts.map +1 -0
  219. package/dist/rules/links/host-section-divergence.js +158 -0
  220. package/dist/rules/links/host-section-divergence.js.map +1 -0
  221. package/dist/rules/links/link-depth.d.ts +12 -1
  222. package/dist/rules/links/link-depth.d.ts.map +1 -1
  223. package/dist/rules/links/link-depth.js +25 -12
  224. package/dist/rules/links/link-depth.js.map +1 -1
  225. package/dist/rules/scope.d.ts.map +1 -1
  226. package/dist/rules/scope.js +5 -0
  227. package/dist/rules/scope.js.map +1 -1
  228. package/dist/rules/spam/doorway-pattern.d.ts.map +1 -1
  229. package/dist/rules/spam/doorway-pattern.js +27 -4
  230. package/dist/rules/spam/doorway-pattern.js.map +1 -1
  231. package/dist/rules/spam/publication-velocity.d.ts +1 -1
  232. package/dist/rules/spam/publication-velocity.d.ts.map +1 -1
  233. package/dist/rules/spam/publication-velocity.js +9 -4
  234. package/dist/rules/spam/publication-velocity.js.map +1 -1
  235. package/dist/rules/spam/template-coverage.js +1 -1
  236. package/dist/rules/spam/template-coverage.js.map +1 -1
  237. package/dist/rules/spam/template-diversity.js +1 -1
  238. package/dist/rules/spam/template-diversity.js.map +1 -1
  239. package/dist/rules/tech/hreflang-consistency.d.ts.map +1 -1
  240. package/dist/rules/tech/hreflang-consistency.js +33 -4
  241. package/dist/rules/tech/hreflang-consistency.js.map +1 -1
  242. package/dist/rules/tech/og-completeness.d.ts +11 -0
  243. package/dist/rules/tech/og-completeness.d.ts.map +1 -1
  244. package/dist/rules/tech/og-completeness.js +22 -23
  245. package/dist/rules/tech/og-completeness.js.map +1 -1
  246. package/dist/ruleset-version.d.ts +8 -0
  247. package/dist/ruleset-version.d.ts.map +1 -0
  248. package/dist/ruleset-version.js +8 -0
  249. package/dist/ruleset-version.js.map +1 -0
  250. package/dist/scrape-strategy.d.ts +42 -0
  251. package/dist/scrape-strategy.d.ts.map +1 -0
  252. package/dist/scrape-strategy.js +101 -0
  253. package/dist/scrape-strategy.js.map +1 -0
  254. package/dist/site-classifier.d.ts.map +1 -1
  255. package/dist/site-classifier.js +1 -0
  256. package/dist/site-classifier.js.map +1 -1
  257. package/dist/state.d.ts +36 -1
  258. package/dist/state.d.ts.map +1 -1
  259. package/dist/state.js +3 -1
  260. package/dist/state.js.map +1 -1
  261. package/dist/stratified-sample.d.ts +9 -1
  262. package/dist/stratified-sample.d.ts.map +1 -1
  263. package/dist/stratified-sample.js +23 -6
  264. package/dist/stratified-sample.js.map +1 -1
  265. package/dist/types.d.ts +135 -2
  266. package/dist/types.d.ts.map +1 -1
  267. package/dist/url-normalize.d.ts.map +1 -1
  268. package/dist/url-normalize.js +13 -1
  269. package/dist/url-normalize.js.map +1 -1
  270. package/package.json +90 -90
@@ -0,0 +1,158 @@
1
+ import { extractKeywords } from "../../algorithms/tf-idf.js";
2
+ const MIN_SECTION_PAGES = 10;
3
+ const ESCALATE_TO_ERROR_AT = 50;
4
+ const TOPIC_TOP_N = 100;
5
+ const SAMPLE_URLS = 20;
6
+ const TRIP = {
7
+ inboundExternalRatio: 0.20,
8
+ topicJaccardDistance: 0.75,
9
+ templateOverlap: 0.10,
10
+ authorshipDelta: 0.40,
11
+ authorshipLowMax: 0.30,
12
+ authorshipHighMin: 0.70,
13
+ };
14
+ function sectionPrefixFor(url) {
15
+ let pathname;
16
+ try {
17
+ pathname = new URL(url).pathname;
18
+ }
19
+ catch {
20
+ return null;
21
+ }
22
+ const trimmed = pathname.replace(/^\/+/, "");
23
+ if (trimmed.length === 0)
24
+ return null;
25
+ const first = trimmed.split("/")[0];
26
+ if (!first)
27
+ return null;
28
+ return `/${first}/`;
29
+ }
30
+ function authorCoverage(pages) {
31
+ if (pages.length === 0)
32
+ return 0;
33
+ let withAuthor = 0;
34
+ for (const page of pages) {
35
+ const a = page.authorSignals;
36
+ if (a.metaAuthor || a.schemaAuthor || a.bylineElement || a.relAuthorLink) {
37
+ withAuthor += 1;
38
+ }
39
+ }
40
+ return withAuthor / pages.length;
41
+ }
42
+ function jaccardDistance(a, b) {
43
+ if (a.size === 0 && b.size === 0)
44
+ return 0;
45
+ let intersection = 0;
46
+ for (const term of a) {
47
+ if (b.has(term))
48
+ intersection += 1;
49
+ }
50
+ const union = a.size + b.size - intersection;
51
+ if (union === 0)
52
+ return 0;
53
+ return 1 - intersection / union;
54
+ }
55
+ export function hostSectionDivergenceRule(pages, adjacency) {
56
+ if (pages.length < MIN_SECTION_PAGES * 2)
57
+ return [];
58
+ const sections = new Map();
59
+ for (const page of pages) {
60
+ const prefix = sectionPrefixFor(page.url);
61
+ if (!prefix)
62
+ continue;
63
+ const group = sections.get(prefix) ?? [];
64
+ group.push(page);
65
+ sections.set(prefix, group);
66
+ }
67
+ // Candidates: sections that meet the size floor, leave a "rest of host" pool
68
+ // that's also at least as large as MIN_SECTION_PAGES, AND are a strict
69
+ // minority of the corpus (< 50%). The minority gate is what keeps the rule
70
+ // from emitting symmetric findings on both halves of a binary section split:
71
+ // reputation abuse is, by definition, a small parasite section riding a
72
+ // larger host. A 50/50 split is a multi-topic site, not abuse.
73
+ const halfCorpus = pages.length / 2;
74
+ const candidates = [];
75
+ for (const [prefix, sectionPages] of sections.entries()) {
76
+ if (sectionPages.length < MIN_SECTION_PAGES)
77
+ continue;
78
+ if (pages.length - sectionPages.length < MIN_SECTION_PAGES)
79
+ continue;
80
+ if (sectionPages.length >= halfCorpus)
81
+ continue;
82
+ candidates.push({ prefix, pages: sectionPages });
83
+ }
84
+ if (candidates.length === 0)
85
+ return [];
86
+ const allTexts = pages.map((p) => p.contentText);
87
+ const findings = [];
88
+ for (const { prefix, pages: sectionPages } of candidates) {
89
+ const sectionUrls = new Set(sectionPages.map((p) => p.url));
90
+ const restPages = pages.filter((p) => !sectionUrls.has(p.url));
91
+ // 1. inboundExternalRatio: pages in section with ≥1 inbound link from outside the section.
92
+ let pagesWithExternalInbound = 0;
93
+ for (const page of sectionPages) {
94
+ let hasExternal = false;
95
+ for (const [sourceUrl, targets] of adjacency.entries()) {
96
+ if (sectionUrls.has(sourceUrl))
97
+ continue;
98
+ if (targets.has(page.url)) {
99
+ hasExternal = true;
100
+ break;
101
+ }
102
+ }
103
+ if (hasExternal)
104
+ pagesWithExternalInbound += 1;
105
+ }
106
+ const inboundExternalRatio = pagesWithExternalInbound / sectionPages.length;
107
+ // 2. topicJaccardDistance: top-N TF-IDF terms per pool.
108
+ const sectionText = sectionPages.map((p) => p.contentText).join("\n");
109
+ const restText = restPages.map((p) => p.contentText).join("\n");
110
+ const sectionTerms = new Set(extractKeywords(sectionText, allTexts, TOPIC_TOP_N));
111
+ const restTerms = new Set(extractKeywords(restText, allTexts, TOPIC_TOP_N));
112
+ const topicJaccardDistance = jaccardDistance(sectionTerms, restTerms);
113
+ // 3. templateOverlap: section structureSignatures that also appear in rest.
114
+ const restSignatures = new Set(restPages.map((p) => p.structureSignature));
115
+ let sectionPagesWithRestSignature = 0;
116
+ for (const page of sectionPages) {
117
+ if (restSignatures.has(page.structureSignature))
118
+ sectionPagesWithRestSignature += 1;
119
+ }
120
+ const templateOverlap = sectionPagesWithRestSignature / sectionPages.length;
121
+ // 4. authorshipMismatch: gap between section and rest author coverage.
122
+ const sectionAuthorCoverage = authorCoverage(sectionPages);
123
+ const restAuthorCoverage = authorCoverage(restPages);
124
+ const authorshipDelta = Math.abs(sectionAuthorCoverage - restAuthorCoverage);
125
+ const authorshipMismatch = authorshipDelta >= TRIP.authorshipDelta &&
126
+ ((sectionAuthorCoverage <= TRIP.authorshipLowMax && restAuthorCoverage >= TRIP.authorshipHighMin) ||
127
+ (restAuthorCoverage <= TRIP.authorshipLowMax && sectionAuthorCoverage >= TRIP.authorshipHighMin));
128
+ const tripped = [];
129
+ if (inboundExternalRatio < TRIP.inboundExternalRatio) {
130
+ tripped.push(`inboundExternalRatio=${inboundExternalRatio.toFixed(2)}`);
131
+ }
132
+ if (topicJaccardDistance > TRIP.topicJaccardDistance) {
133
+ tripped.push(`topicJaccardDistance=${topicJaccardDistance.toFixed(2)}`);
134
+ }
135
+ if (templateOverlap < TRIP.templateOverlap) {
136
+ tripped.push(`templateOverlap=${templateOverlap.toFixed(2)}`);
137
+ }
138
+ if (authorshipMismatch) {
139
+ tripped.push(`authorshipMismatch (section=${sectionAuthorCoverage.toFixed(2)}, rest=${restAuthorCoverage.toFixed(2)})`);
140
+ }
141
+ if (tripped.length < 2)
142
+ continue;
143
+ const escalate = tripped.length >= 3 && sectionPages.length > ESCALATE_TO_ERROR_AT;
144
+ const severity = escalate ? "error" : "warning";
145
+ findings.push({
146
+ ruleId: "links/host-section-divergence",
147
+ severity,
148
+ message: `Section ${prefix} (${sectionPages.length} pages) diverges from the rest of the host on ` +
149
+ `${tripped.length} signals: ${tripped.join(", ")}.`,
150
+ fix: "Either (a) integrate this section editorially with the rest of the host (cross-link from primary navigation, " +
151
+ "share authorship/schema, align template) or (b) move it to a subdomain so it builds reputation on its own. " +
152
+ "Google's May 2024 site-reputation-abuse policy targets sections that ride a host's reputation without integrating into it.",
153
+ relatedUrls: sectionPages.slice(0, SAMPLE_URLS).map((p) => p.url),
154
+ });
155
+ }
156
+ return findings;
157
+ }
158
+ //# sourceMappingURL=host-section-divergence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-section-divergence.js","sourceRoot":"","sources":["../../../src/rules/links/host-section-divergence.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,14 @@
1
1
  import type { ParsedPage, RuleResult } from "../../types.js";
2
- export declare function linkDepthRule(pages: ParsedPage[], adjacency: Map<string, Set<string>>, rootUrl: string, maxDepth: number, inbound: Map<string, number>): RuleResult[];
2
+ export declare function linkDepthRule(pages: ParsedPage[], adjacency: Map<string, Set<string>>, rootUrl: string, maxDepth: number, inbound: Map<string, number>,
3
+ /**
4
+ * 2026-05-03 calibration credibility fix: when we only audited a sampled
5
+ * subset of the site (e.g. 50 of 269 Zapier pages), the link graph we
6
+ * built reflects the sample, not the real site. Pages flagged
7
+ * "unreachable from root" might just be sampling artifacts — the
8
+ * real path back to the root exists, we just didn't fetch the
9
+ * intermediary pages. Pass `sampled: true` to skip the unreachable-
10
+ * from-root check; depth-from-root keeps working since the BFS still
11
+ * runs on whatever subgraph we have.
12
+ */
13
+ sampled?: boolean): RuleResult[];
3
14
  //# sourceMappingURL=link-depth.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"link-depth.d.ts","sourceRoot":"","sources":["../../../src/rules/links/link-depth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE7D,wBAAgB,aAAa,CAC3B,KAAK,EAAE,UAAU,EAAE,EACnB,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,EACnC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC3B,UAAU,EAAE,CAgDd"}
1
+ {"version":3,"file":"link-depth.d.ts","sourceRoot":"","sources":["../../../src/rules/links/link-depth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE7D,wBAAgB,aAAa,CAC3B,KAAK,EAAE,UAAU,EAAE,EACnB,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,EACnC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;AAC5B;;;;;;;;;GASG;AACH,OAAO,UAAQ,GACd,UAAU,EAAE,CAkDd"}
@@ -1,4 +1,15 @@
1
- export function linkDepthRule(pages, adjacency, rootUrl, maxDepth, inbound) {
1
+ export function linkDepthRule(pages, adjacency, rootUrl, maxDepth, inbound,
2
+ /**
3
+ * 2026-05-03 calibration credibility fix: when we only audited a sampled
4
+ * subset of the site (e.g. 50 of 269 Zapier pages), the link graph we
5
+ * built reflects the sample, not the real site. Pages flagged
6
+ * "unreachable from root" might just be sampling artifacts — the
7
+ * real path back to the root exists, we just didn't fetch the
8
+ * intermediary pages. Pass `sampled: true` to skip the unreachable-
9
+ * from-root check; depth-from-root keeps working since the BFS still
10
+ * runs on whatever subgraph we have.
11
+ */
12
+ sampled = false) {
2
13
  const queue = [{ url: rootUrl, depth: 0 }];
3
14
  const visited = new Map([[rootUrl, 0]]);
4
15
  while (queue.length > 0) {
@@ -17,17 +28,19 @@ export function linkDepthRule(pages, adjacency, rootUrl, maxDepth, inbound) {
17
28
  queue.push({ url: next, depth: candidate });
18
29
  }
19
30
  }
20
- const unreachable = pages
21
- .filter((page) => page.url !== rootUrl)
22
- .filter((page) => (inbound.get(page.url) ?? 0) > 0)
23
- .filter((page) => visited.get(page.url) === undefined)
24
- .map((page) => ({
25
- ruleId: "links/unreachable-from-root",
26
- severity: "warning",
27
- message: `${page.url} is not reachable from the crawl root via internal links (but has inbound links).`,
28
- pageUrl: page.url,
29
- fix: "This page is unreachable from the site root. Add a navigation path to it."
30
- }));
31
+ const unreachable = sampled
32
+ ? []
33
+ : pages
34
+ .filter((page) => page.url !== rootUrl)
35
+ .filter((page) => (inbound.get(page.url) ?? 0) > 0)
36
+ .filter((page) => visited.get(page.url) === undefined)
37
+ .map((page) => ({
38
+ ruleId: "links/unreachable-from-root",
39
+ severity: "warning",
40
+ message: `${page.url} is not reachable from the crawl root via internal links (but has inbound links).`,
41
+ pageUrl: page.url,
42
+ fix: "This page is unreachable from the site root. Add a navigation path to it."
43
+ }));
31
44
  const deep = pages
32
45
  .filter((page) => page.url !== rootUrl)
33
46
  .filter((page) => {
@@ -1 +1 @@
1
- {"version":3,"file":"link-depth.js","sourceRoot":"","sources":["../../../src/rules/links/link-depth.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,aAAa,CAC3B,KAAmB,EACnB,SAAmC,EACnC,OAAe,EACf,QAAgB,EAChB,OAA4B;IAE5B,MAAM,KAAK,GAA0C,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;IAClF,MAAM,OAAO,GAAG,IAAI,GAAG,CAAiB,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAExD,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC;QAC9B,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,SAAS;QACX,CAAC;QACD,MAAM,SAAS,GAAG,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,EAAU,CAAC;QAClE,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;YAC7B,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACnC,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,GAAG,CAAC,CAAC;YACpC,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,IAAI,SAAS,EAAE,CAAC;gBACpD,SAAS;YACX,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;YAC7B,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;IAED,MAAM,WAAW,GAAG,KAAK;SACtB,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,KAAK,OAAO,CAAC;SACtC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;SAClD,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC;SACrD,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACd,MAAM,EAAE,6BAAsC;QAC9C,QAAQ,EAAE,SAAkB;QAC5B,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,mFAAmF;QACvG,OAAO,EAAE,IAAI,CAAC,GAAG;QACjB,GAAG,EAAE,2EAA2E;KACjF,CAAC,CAAC,CAAC;IAEN,MAAM,IAAI,GAAG,KAAK;SACf,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,KAAK,OAAO,CAAC;SACtC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE;QACf,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAChC,OAAO,CAAC,KAAK,SAAS,IAAI,CAAC,GAAG,QAAQ,CAAC;IACzC,CAAC,CAAC;SACD,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACd,MAAM,EAAE,kBAAkB;QAC1B,QAAQ,EAAE,MAAe;QACzB,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,mBAAmB,QAAQ,oBAAoB;QACnE,OAAO,EAAE,IAAI,CAAC,GAAG;QACjB,GAAG,EAAE,yDAAyD;KAC/D,CAAC,CAAC,CAAC;IAEN,OAAO,CAAC,GAAG,WAAW,EAAE,GAAG,IAAI,CAAC,CAAC;AACnC,CAAC"}
1
+ {"version":3,"file":"link-depth.js","sourceRoot":"","sources":["../../../src/rules/links/link-depth.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,aAAa,CAC3B,KAAmB,EACnB,SAAmC,EACnC,OAAe,EACf,QAAgB,EAChB,OAA4B;AAC5B;;;;;;;;;GASG;AACH,OAAO,GAAG,KAAK;IAEf,MAAM,KAAK,GAA0C,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;IAClF,MAAM,OAAO,GAAG,IAAI,GAAG,CAAiB,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAExD,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC;QAC9B,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,SAAS;QACX,CAAC;QACD,MAAM,SAAS,GAAG,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,EAAU,CAAC;QAClE,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;YAC7B,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACnC,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,GAAG,CAAC,CAAC;YACpC,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,IAAI,SAAS,EAAE,CAAC;gBACpD,SAAS;YACX,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;YAC7B,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;IAED,MAAM,WAAW,GAAG,OAAO;QACzB,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,KAAK;aACF,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,KAAK,OAAO,CAAC;aACtC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;aAClD,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC;aACrD,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YACd,MAAM,EAAE,6BAAsC;YAC9C,QAAQ,EAAE,SAAkB;YAC5B,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,mFAAmF;YACvG,OAAO,EAAE,IAAI,CAAC,GAAG;YACjB,GAAG,EAAE,2EAA2E;SACjF,CAAC,CAAC,CAAC;IAEV,MAAM,IAAI,GAAG,KAAK;SACf,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,KAAK,OAAO,CAAC;SACtC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE;QACf,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAChC,OAAO,CAAC,KAAK,SAAS,IAAI,CAAC,GAAG,QAAQ,CAAC;IACzC,CAAC,CAAC;SACD,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACd,MAAM,EAAE,kBAAkB;QAC1B,QAAQ,EAAE,MAAe;QACzB,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,mBAAmB,QAAQ,oBAAoB;QACnE,OAAO,EAAE,IAAI,CAAC,GAAG;QACjB,GAAG,EAAE,yDAAyD;KAC/D,CAAC,CAAC,CAAC;IAEN,OAAO,CAAC,GAAG,WAAW,EAAE,GAAG,IAAI,CAAC,CAAC;AACnC,CAAC"}
@@ -1 +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,CA6DhD,CAAC;AAEF,4GAA4G;AAC5G,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAE3D"}
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,CAkEhD,CAAC;AAEF,4GAA4G;AAC5G,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAE3D"}
@@ -13,12 +13,16 @@ export const RULE_SCOPE = {
13
13
  "content/meta-uniqueness": "corpus",
14
14
  "content/missing-author": "page",
15
15
  "content/eeat-signals": "page",
16
+ "content/title-uniqueness": "corpus",
17
+ "content/heading-structure": "page",
18
+ "content/image-alt-text": "page",
16
19
  // links
17
20
  "links/orphan-pages": "corpus",
18
21
  "links/dead-ends": "corpus",
19
22
  "links/cluster-connectivity": "corpus",
20
23
  "links/link-depth": "corpus",
21
24
  "links/unreachable-from-root": "corpus",
25
+ "links/host-section-divergence": "corpus",
22
26
  // tech
23
27
  "tech/canonical-consistency": "corpus",
24
28
  "tech/canonical-noindex-conflict": "page",
@@ -29,6 +33,7 @@ export const RULE_SCOPE = {
29
33
  "tech/hreflang-consistency": "corpus",
30
34
  "tech/robots-compliance": "corpus",
31
35
  "tech/robots-sitemap-presence": "corpus",
36
+ "tech/og-completeness": "page",
32
37
  // schema
33
38
  "schema/json-ld-valid": "page",
34
39
  "schema/required-fields": "page",
@@ -1 +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,yBAAyB,EAAE,QAAQ;IACnC,wBAAwB,EAAE,MAAM;IAChC,sBAAsB,EAAE,MAAM;IAE9B,QAAQ;IACR,oBAAoB,EAAE,QAAQ;IAC9B,iBAAiB,EAAE,QAAQ;IAC3B,4BAA4B,EAAE,QAAQ;IACtC,kBAAkB,EAAE,QAAQ;IAC5B,6BAA6B,EAAE,QAAQ;IAEvC,OAAO;IACP,4BAA4B,EAAE,QAAQ;IACtC,iCAAiC,EAAE,MAAM;IACzC,8BAA8B,EAAE,QAAQ;IACxC,2BAA2B,EAAE,QAAQ;IACrC,qBAAqB,EAAE,MAAM;IAC7B,eAAe,EAAE,MAAM;IACvB,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,kEAAkE;IAClE,kEAAkE;IAClE,uCAAuC;IACvC,sBAAsB,EAAE,QAAQ;IAEhC,eAAe;IACf,sBAAsB,EAAE,MAAM;IAC9B,6BAA6B,EAAE,QAAQ;IAEvC,iBAAiB;IACjB,qBAAqB,EAAE,QAAQ;IAE/B,oCAAoC;IACpC,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,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"}
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,yBAAyB,EAAE,QAAQ;IACnC,wBAAwB,EAAE,MAAM;IAChC,sBAAsB,EAAE,MAAM;IAC9B,0BAA0B,EAAE,QAAQ;IACpC,2BAA2B,EAAE,MAAM;IACnC,wBAAwB,EAAE,MAAM;IAEhC,QAAQ;IACR,oBAAoB,EAAE,QAAQ;IAC9B,iBAAiB,EAAE,QAAQ;IAC3B,4BAA4B,EAAE,QAAQ;IACtC,kBAAkB,EAAE,QAAQ;IAC5B,6BAA6B,EAAE,QAAQ;IACvC,+BAA+B,EAAE,QAAQ;IAEzC,OAAO;IACP,4BAA4B,EAAE,QAAQ;IACtC,iCAAiC,EAAE,MAAM;IACzC,8BAA8B,EAAE,QAAQ;IACxC,2BAA2B,EAAE,QAAQ;IACrC,qBAAqB,EAAE,MAAM;IAC7B,eAAe,EAAE,MAAM;IACvB,2BAA2B,EAAE,QAAQ;IACrC,wBAAwB,EAAE,QAAQ;IAClC,8BAA8B,EAAE,QAAQ;IACxC,sBAAsB,EAAE,MAAM;IAE9B,SAAS;IACT,sBAAsB,EAAE,MAAM;IAC9B,wBAAwB,EAAE,MAAM;IAChC,oBAAoB,EAAE,QAAQ;IAE9B,kEAAkE;IAClE,kEAAkE;IAClE,uCAAuC;IACvC,sBAAsB,EAAE,QAAQ;IAEhC,eAAe;IACf,sBAAsB,EAAE,MAAM;IAC9B,6BAA6B,EAAE,QAAQ;IAEvC,iBAAiB;IACjB,qBAAqB,EAAE,QAAQ;IAE/B,oCAAoC;IACpC,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,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"}
@@ -1 +1 @@
1
- {"version":3,"file":"doorway-pattern.d.ts","sourceRoot":"","sources":["../../../src/rules/spam/doorway-pattern.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAMrD,wBAAgB,kBAAkB,CAChC,kBAAkB,EAAE,SAAS,EAAE,EAC/B,eAAe,EAAE,SAAS,EAAE,EAC5B,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,EAC5B,KAAK,CAAC,EAAE,UAAU,EAAE,GACnB,UAAU,EAAE,CAoDd"}
1
+ {"version":3,"file":"doorway-pattern.d.ts","sourceRoot":"","sources":["../../../src/rules/spam/doorway-pattern.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAMrD,wBAAgB,kBAAkB,CAChC,kBAAkB,EAAE,SAAS,EAAE,EAC/B,eAAe,EAAE,SAAS,EAAE,EAC5B,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,EAC5B,KAAK,CAAC,EAAE,UAAU,EAAE,GACnB,UAAU,EAAE,CA6Ed"}
@@ -10,6 +10,17 @@ export function doorwayPatternRule(nearDuplicatePairs, entitySwapPairs, thinCont
10
10
  pageMap.set(p.url, p);
11
11
  }
12
12
  }
13
+ // 2026-05-03 calibration finding: catalog directories (Zapier integrations,
14
+ // Segment source/destination pages, Wise currency pairs) are by-design
15
+ // near-duplicate + entity-swap + identical-structure. With the old ≥3-of-5
16
+ // threshold, that triple alone fired this rule on every pair — generating
17
+ // C(N,2) critical findings on catalogs that ship in production and rank.
18
+ // The fix reframes the gate: structural similarity alone never converts a
19
+ // near-dup/entity-swap pair into a doorway finding. We additionally require
20
+ // a CONTENT-QUALITY signal (thin content OR identical metaDescription),
21
+ // because real doorways degenerate on at least one of those — catalog
22
+ // records, by contrast, have unique per-record body text and per-record
23
+ // metas. See docs/superpowers/specs/2026-05-03-calibration-against-reputable-pseo.md.
13
24
  for (const pair of nearDuplicatePairs) {
14
25
  const key = pairKey(pair.leftUrl, pair.rightUrl);
15
26
  if (!entitySet.has(key)) {
@@ -27,18 +38,30 @@ export function doorwayPatternRule(nearDuplicatePairs, entitySwapPairs, thinCont
27
38
  if (leftPage && rightPage && leftPage.structureSignature === rightPage.structureSignature) {
28
39
  signals.push("identical-structure");
29
40
  }
30
- if (leftPage && rightPage && leftPage.metaDescription && rightPage.metaDescription &&
31
- leftPage.metaDescription === rightPage.metaDescription) {
41
+ const isIdenticalMeta = Boolean(leftPage && rightPage && leftPage.metaDescription && rightPage.metaDescription &&
42
+ leftPage.metaDescription === rightPage.metaDescription);
43
+ if (isIdenticalMeta) {
32
44
  signals.push("identical-meta");
33
45
  }
34
- if (signals.length < 3) {
46
+ // Require 3 total AND at least one content-quality signal beyond the
47
+ // structural pair (near-dup + entity-swap + identical-structure is the
48
+ // catalog shape; not enough on its own to call a doorway).
49
+ if (signals.length < 3 || (!isThin && !isIdenticalMeta)) {
35
50
  continue;
36
51
  }
37
52
  findings.push({
38
53
  ruleId: "spam/doorway-pattern",
39
54
  severity: "critical",
40
55
  message: `${left} and ${right} match doorway-pattern signals (${signals.join(" + ")}).`,
41
- relatedUrls: [left, right],
56
+ // 2026-05-03 calibration credibility fix: emit in the same pair-shape
57
+ // as `spam/near-duplicate` (pageUrl + relatedUrls=[other]) so the
58
+ // enrich-findings cluster pass collapses C(N,2) findings into one
59
+ // cluster finding per template-tied group. Without this, Segment's
60
+ // 276-pair audit produced 276 separate "doorway" lines in the report,
61
+ // overwhelming users.
62
+ pageUrl: left,
63
+ relatedUrls: [right],
64
+ similarity: pair.similarity,
42
65
  fix: "This page matches multiple spam signals. Prioritize adding unique, substantive content and differentiating the page structure."
43
66
  });
44
67
  }
@@ -1 +1 @@
1
- {"version":3,"file":"doorway-pattern.js","sourceRoot":"","sources":["../../../src/rules/spam/doorway-pattern.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"doorway-pattern.js","sourceRoot":"","sources":["../../../src/rules/spam/doorway-pattern.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,3 @@
1
1
  import type { ParsedPage, RuleResult } from "../../types.js";
2
- export declare function publicationVelocityRule(pages: ParsedPage[], maxPerDay: number): RuleResult[];
2
+ export declare function publicationVelocityRule(pages: ParsedPage[], maxPerDay: number, corpusFraction?: number): RuleResult[];
3
3
  //# sourceMappingURL=publication-velocity.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"publication-velocity.d.ts","sourceRoot":"","sources":["../../../src/rules/spam/publication-velocity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE7D,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,UAAU,EAAE,CAyB5F"}
1
+ {"version":3,"file":"publication-velocity.d.ts","sourceRoot":"","sources":["../../../src/rules/spam/publication-velocity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE7D,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,UAAU,EAAE,EACnB,SAAS,EAAE,MAAM,EACjB,cAAc,SAAO,GACpB,UAAU,EAAE,CA+Bd"}
@@ -1,4 +1,6 @@
1
- export function publicationVelocityRule(pages, maxPerDay) {
1
+ export function publicationVelocityRule(pages, maxPerDay, corpusFraction = 0.10) {
2
+ const corpusFloor = corpusFraction > 0 ? Math.ceil(pages.length * corpusFraction) : 0;
3
+ const effectiveMax = Math.max(maxPerDay, corpusFloor);
2
4
  const byDay = new Map();
3
5
  for (const page of pages) {
4
6
  if (!page.publishedDate) {
@@ -11,12 +13,15 @@ export function publicationVelocityRule(pages, maxPerDay) {
11
13
  }
12
14
  const findings = [];
13
15
  for (const [day, dayPages] of byDay.entries()) {
14
- if (dayPages.length > maxPerDay) {
16
+ if (dayPages.length > effectiveMax) {
17
+ const reason = effectiveMax === corpusFloor && corpusFloor > maxPerDay
18
+ ? `${dayPages.length} pages share publish date ${day}, exceeding ${(corpusFraction * 100).toFixed(0)}% of the ${pages.length}-page corpus (${effectiveMax}/day).`
19
+ : `${dayPages.length} pages share publish date ${day}, exceeding max/day ${effectiveMax}.`;
15
20
  findings.push({
16
21
  ruleId: "spam/publication-velocity",
17
22
  severity: "warning",
18
- message: `${dayPages.length} pages share publish date ${day}, exceeding max/day ${maxPerDay}.`,
19
- fix: "Stagger publication dates across pages to avoid appearing auto-generated."
23
+ message: reason,
24
+ fix: "Stagger publication dates across pages to avoid appearing auto-generated. Google's March 27, 2026 core update tightened scaled-content-abuse signals on date-stacked corpora."
20
25
  });
21
26
  }
22
27
  }
@@ -1 +1 @@
1
- {"version":3,"file":"publication-velocity.js","sourceRoot":"","sources":["../../../src/rules/spam/publication-velocity.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,uBAAuB,CAAC,KAAmB,EAAE,SAAiB;IAC5E,MAAM,KAAK,GAAG,IAAI,GAAG,EAAwB,CAAC;IAC9C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;YACxB,SAAS;QACX,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC5C,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;QACnC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjB,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACxB,CAAC;IAED,MAAM,QAAQ,GAAiB,EAAE,CAAC;IAClC,KAAK,MAAM,CAAC,GAAG,EAAE,QAAQ,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC;QAC9C,IAAI,QAAQ,CAAC,MAAM,GAAG,SAAS,EAAE,CAAC;YAChC,QAAQ,CAAC,IAAI,CAAC;gBACZ,MAAM,EAAE,2BAA2B;gBACnC,QAAQ,EAAE,SAAS;gBACnB,OAAO,EAAE,GAAG,QAAQ,CAAC,MAAM,6BAA6B,GAAG,uBAAuB,SAAS,GAAG;gBAC9F,GAAG,EAAE,2EAA2E;aACjF,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
1
+ {"version":3,"file":"publication-velocity.js","sourceRoot":"","sources":["../../../src/rules/spam/publication-velocity.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,uBAAuB,CACrC,KAAmB,EACnB,SAAiB,EACjB,cAAc,GAAG,IAAI;IAErB,MAAM,WAAW,GAAG,cAAc,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACtF,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;IAEtD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAwB,CAAC;IAC9C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;YACxB,SAAS;QACX,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC5C,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;QACnC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjB,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACxB,CAAC;IAED,MAAM,QAAQ,GAAiB,EAAE,CAAC;IAClC,KAAK,MAAM,CAAC,GAAG,EAAE,QAAQ,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC;QAC9C,IAAI,QAAQ,CAAC,MAAM,GAAG,YAAY,EAAE,CAAC;YACnC,MAAM,MAAM,GAAG,YAAY,KAAK,WAAW,IAAI,WAAW,GAAG,SAAS;gBACpE,CAAC,CAAC,GAAG,QAAQ,CAAC,MAAM,6BAA6B,GAAG,eAAe,CAAC,cAAc,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,KAAK,CAAC,MAAM,iBAAiB,YAAY,QAAQ;gBACjK,CAAC,CAAC,GAAG,QAAQ,CAAC,MAAM,6BAA6B,GAAG,uBAAuB,YAAY,GAAG,CAAC;YAC7F,QAAQ,CAAC,IAAI,CAAC;gBACZ,MAAM,EAAE,2BAA2B;gBACnC,QAAQ,EAAE,SAAS;gBACnB,OAAO,EAAE,MAAM;gBACf,GAAG,EAAE,+KAA+K;aACrL,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -76,7 +76,7 @@ export function templateCoverageRule(pages, entityPatterns, minPages) {
76
76
  severity: "info",
77
77
  message: `${clusterDir} has ${names.length} pages across ${dimensions.length} dimensions: ${dimDesc}. Coverage: ${names.length} of ${totalCombinations} combinations (${coveragePct}%).`,
78
78
  fix: totalCombinations > names.length * 5
79
- ? "Low coverage suggests an overly broad template matrix. Consider narrowing dimensions to combinations you can differentiate with unique content."
79
+ ? "Low coverage suggests an overly broad template matrix. Consider narrowing dimensions to combinations you can differentiate with unique content. Sparse high-dimension matrices are exactly what the March 27, 2026 core update down-weighted on programmatic corpora."
80
80
  : "Coverage is reasonable. Ensure each combination provides genuinely unique content.",
81
81
  relatedUrls: group.map((p) => p.url).sort()
82
82
  });
@@ -1 +1 @@
1
- {"version":3,"file":"template-coverage.js","sourceRoot":"","sources":["../../../src/rules/spam/template-coverage.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"template-coverage.js","sourceRoot":"","sources":["../../../src/rules/spam/template-coverage.ts"],"names":[],"mappings":"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"}
@@ -12,7 +12,7 @@ export function templateDiversityRule(pages, minUniqueRatio) {
12
12
  ruleId: "spam/template-diversity",
13
13
  severity: "warning",
14
14
  message: `Template diversity ratio is ${ratio.toFixed(2)} (min ${minUniqueRatio.toFixed(2)}).`,
15
- fix: "Vary the HTML structure across pages. Add conditional sections, different layouts, or page-specific components."
15
+ fix: "Vary the HTML structure across pages. Add conditional sections, different layouts, or page-specific components. Identical-structure corpora are a primary scaled-content-abuse signal that the March 27, 2026 core update reinforced."
16
16
  }
17
17
  ];
18
18
  }
@@ -1 +1 @@
1
- {"version":3,"file":"template-diversity.js","sourceRoot":"","sources":["../../../src/rules/spam/template-diversity.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,qBAAqB,CACnC,KAAmB,EACnB,cAAsB;IAEtB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC;IAC1E,MAAM,KAAK,GAAG,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IACpC,IAAI,KAAK,IAAI,cAAc,EAAE,CAAC;QAC5B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,OAAO;QACL;YACE,MAAM,EAAE,yBAAyB;YACjC,QAAQ,EAAE,SAAS;YACnB,OAAO,EAAE,+BAA+B,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI;YAC9F,GAAG,EAAE,iHAAiH;SACvH;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"template-diversity.js","sourceRoot":"","sources":["../../../src/rules/spam/template-diversity.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,qBAAqB,CACnC,KAAmB,EACnB,cAAsB;IAEtB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC;IAC1E,MAAM,KAAK,GAAG,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IACpC,IAAI,KAAK,IAAI,cAAc,EAAE,CAAC;QAC5B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,OAAO;QACL;YACE,MAAM,EAAE,yBAAyB;YACjC,QAAQ,EAAE,SAAS;YACnB,OAAO,EAAE,+BAA+B,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI;YAC9F,GAAG,EAAE,uOAAuO;SAC7O;KACF,CAAC;AACJ,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"hreflang-consistency.d.ts","sourceRoot":"","sources":["../../../src/rules/tech/hreflang-consistency.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAGlF,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,UAAU,EAAE,EACnB,aAAa,EAAE,mBAAmB,GACjC,UAAU,EAAE,CA0Gd"}
1
+ {"version":3,"file":"hreflang-consistency.d.ts","sourceRoot":"","sources":["../../../src/rules/tech/hreflang-consistency.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAGlF,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,UAAU,EAAE,EACnB,aAAa,EAAE,mBAAmB,GACjC,UAAU,EAAE,CAqId"}
@@ -33,17 +33,37 @@ export function hreflangConsistencyRule(pages, normalizeOpts) {
33
33
  });
34
34
  continue;
35
35
  }
36
- if (!/^https?:\/\//i.test(entry.href)) {
36
+ if (!/^https?:\/\/[^\/]/i.test(entry.href)) {
37
+ // Tightened the prefix check to require at least one host character
38
+ // after `https://` — otherwise malformed hrefs like literal
39
+ // "https://" pass the regex but crash normalizeAuditUrl downstream
40
+ // (calibration round 2 surfaced this on webflow.com/templates).
37
41
  findings.push({
38
42
  ruleId: "tech/hreflang-consistency",
39
43
  severity: "warning",
40
- message: `${page.url} has non-absolute hreflang href (${entry.href}) for ${entry.lang}.`,
44
+ message: `${page.url} has non-absolute or malformed hreflang href (${entry.href}) for ${entry.lang}.`,
41
45
  pageUrl: page.url,
42
- fix: `Change the hreflang href for ${entry.lang} to an absolute URL (starting with https://).`
46
+ fix: `Change the hreflang href for ${entry.lang} to an absolute URL (starting with https:// and including a host).`
47
+ });
48
+ continue;
49
+ }
50
+ let normalizedHref;
51
+ try {
52
+ normalizedHref = normalizeAuditUrl(entry.href, normalizeOpts);
53
+ }
54
+ catch {
55
+ // Defensive: any URL-parse failure here is unreachable given the
56
+ // tightened regex above, but emit an honest finding rather than
57
+ // letting an exception propagate and abort the entire audit.
58
+ findings.push({
59
+ ruleId: "tech/hreflang-consistency",
60
+ severity: "warning",
61
+ message: `${page.url} has unparseable hreflang href (${entry.href}) for ${entry.lang}.`,
62
+ pageUrl: page.url,
63
+ fix: `Fix the hreflang href for ${entry.lang}; it could not be parsed as a URL.`,
43
64
  });
44
65
  continue;
45
66
  }
46
- const normalizedHref = normalizeAuditUrl(entry.href, normalizeOpts);
47
67
  const pageRefs = hreflangMap.get(page.url) ?? new Map();
48
68
  pageRefs.set(lang, normalizedHref);
49
69
  hreflangMap.set(page.url, pageRefs);
@@ -71,6 +91,15 @@ export function hreflangConsistencyRule(pages, normalizeOpts) {
71
91
  checkedPairs.add(pairKey);
72
92
  const targetRefs = hreflangMap.get(targetUrl);
73
93
  if (!targetRefs) {
94
+ // 2026-05-03 calibration finding: on a sampled crawl we only audit a
95
+ // subset of pages. If the hreflang target isn't in our sample, we
96
+ // simply have no data on its annotations — flagging it as "no
97
+ // hreflang back" is a false positive that scales with sample size
98
+ // (~385 fires on a 25-page Wise sample). Only assert reciprocity on
99
+ // pages we actually parsed.
100
+ const wasParsed = pages.some((p) => p.url === targetUrl);
101
+ if (!wasParsed)
102
+ continue;
74
103
  findings.push({
75
104
  ruleId: "tech/hreflang-consistency",
76
105
  severity: "warning",
@@ -1 +1 @@
1
- {"version":3,"file":"hreflang-consistency.js","sourceRoot":"","sources":["../../../src/rules/tech/hreflang-consistency.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"hreflang-consistency.js","sourceRoot":"","sources":["../../../src/rules/tech/hreflang-consistency.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,14 @@
1
1
  import type { ParsedPage, RuleResult } from "../../types.js";
2
+ /**
3
+ * tech/og-completeness — flags pages missing the core Open Graph metadata
4
+ * that drives social-share previews and is increasingly used by AI Overviews
5
+ * as a fallback summary signal.
6
+ *
7
+ * Required: og:title, og:description, og:image.
8
+ *
9
+ * The rule was referenced in the v0.4.x README without ever shipping. The
10
+ * 2026-05-03 blind-spot audit surfaced it as a tier-1 gap; this is the
11
+ * v0.5.2 fix.
12
+ */
2
13
  export declare function ogCompletenessRule(pages: ParsedPage[]): RuleResult[];
3
14
  //# sourceMappingURL=og-completeness.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"og-completeness.d.ts","sourceRoot":"","sources":["../../../src/rules/tech/og-completeness.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE7D,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,UAAU,EAAE,CAiCpE"}
1
+ {"version":3,"file":"og-completeness.d.ts","sourceRoot":"","sources":["../../../src/rules/tech/og-completeness.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE7D;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,UAAU,EAAE,CAiBpE"}