@answerable-kit/audit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/README.md +58 -0
  2. package/dist/checks/a1-title.d.ts +2 -0
  3. package/dist/checks/a1-title.d.ts.map +1 -0
  4. package/dist/checks/a1-title.js +34 -0
  5. package/dist/checks/a1-title.js.map +1 -0
  6. package/dist/checks/a10-apple-touch.d.ts +2 -0
  7. package/dist/checks/a10-apple-touch.d.ts.map +1 -0
  8. package/dist/checks/a10-apple-touch.js +21 -0
  9. package/dist/checks/a10-apple-touch.js.map +1 -0
  10. package/dist/checks/a3-description.d.ts +2 -0
  11. package/dist/checks/a3-description.d.ts.map +1 -0
  12. package/dist/checks/a3-description.js +34 -0
  13. package/dist/checks/a3-description.js.map +1 -0
  14. package/dist/checks/a4-canonical.d.ts +2 -0
  15. package/dist/checks/a4-canonical.d.ts.map +1 -0
  16. package/dist/checks/a4-canonical.js +32 -0
  17. package/dist/checks/a4-canonical.js.map +1 -0
  18. package/dist/checks/a5-html-lang.d.ts +2 -0
  19. package/dist/checks/a5-html-lang.d.ts.map +1 -0
  20. package/dist/checks/a5-html-lang.js +38 -0
  21. package/dist/checks/a5-html-lang.js.map +1 -0
  22. package/dist/checks/a6-viewport.d.ts +2 -0
  23. package/dist/checks/a6-viewport.d.ts.map +1 -0
  24. package/dist/checks/a6-viewport.js +30 -0
  25. package/dist/checks/a6-viewport.js.map +1 -0
  26. package/dist/checks/a7-charset.d.ts +2 -0
  27. package/dist/checks/a7-charset.d.ts.map +1 -0
  28. package/dist/checks/a7-charset.js +32 -0
  29. package/dist/checks/a7-charset.js.map +1 -0
  30. package/dist/checks/a8-robots.d.ts +2 -0
  31. package/dist/checks/a8-robots.d.ts.map +1 -0
  32. package/dist/checks/a8-robots.js +46 -0
  33. package/dist/checks/a8-robots.js.map +1 -0
  34. package/dist/checks/a9-favicon.d.ts +2 -0
  35. package/dist/checks/a9-favicon.d.ts.map +1 -0
  36. package/dist/checks/a9-favicon.js +28 -0
  37. package/dist/checks/a9-favicon.js.map +1 -0
  38. package/dist/checks/b1-single-h1.d.ts +2 -0
  39. package/dist/checks/b1-single-h1.d.ts.map +1 -0
  40. package/dist/checks/b1-single-h1.js +32 -0
  41. package/dist/checks/b1-single-h1.js.map +1 -0
  42. package/dist/checks/b11-internal-links.d.ts +2 -0
  43. package/dist/checks/b11-internal-links.d.ts.map +1 -0
  44. package/dist/checks/b11-internal-links.js +30 -0
  45. package/dist/checks/b11-internal-links.js.map +1 -0
  46. package/dist/checks/b14-lists-or-tables.d.ts +2 -0
  47. package/dist/checks/b14-lists-or-tables.d.ts.map +1 -0
  48. package/dist/checks/b14-lists-or-tables.js +26 -0
  49. package/dist/checks/b14-lists-or-tables.js.map +1 -0
  50. package/dist/checks/b3-heading-hierarchy.d.ts +2 -0
  51. package/dist/checks/b3-heading-hierarchy.d.ts.map +1 -0
  52. package/dist/checks/b3-heading-hierarchy.js +36 -0
  53. package/dist/checks/b3-heading-hierarchy.js.map +1 -0
  54. package/dist/checks/b4-h2-sections.d.ts +2 -0
  55. package/dist/checks/b4-h2-sections.d.ts.map +1 -0
  56. package/dist/checks/b4-h2-sections.js +32 -0
  57. package/dist/checks/b4-h2-sections.js.map +1 -0
  58. package/dist/checks/b8-external-citations.d.ts +2 -0
  59. package/dist/checks/b8-external-citations.d.ts.map +1 -0
  60. package/dist/checks/b8-external-citations.js +41 -0
  61. package/dist/checks/b8-external-citations.js.map +1 -0
  62. package/dist/checks/c1-json-ld.d.ts +2 -0
  63. package/dist/checks/c1-json-ld.d.ts.map +1 -0
  64. package/dist/checks/c1-json-ld.js +69 -0
  65. package/dist/checks/c1-json-ld.js.map +1 -0
  66. package/dist/checks/c2-organization.d.ts +2 -0
  67. package/dist/checks/c2-organization.d.ts.map +1 -0
  68. package/dist/checks/c2-organization.js +67 -0
  69. package/dist/checks/c2-organization.js.map +1 -0
  70. package/dist/checks/d1-about-page-linked.d.ts +2 -0
  71. package/dist/checks/d1-about-page-linked.d.ts.map +1 -0
  72. package/dist/checks/d1-about-page-linked.js +24 -0
  73. package/dist/checks/d1-about-page-linked.js.map +1 -0
  74. package/dist/checks/d2-privacy-linked.d.ts +2 -0
  75. package/dist/checks/d2-privacy-linked.d.ts.map +1 -0
  76. package/dist/checks/d2-privacy-linked.js +24 -0
  77. package/dist/checks/d2-privacy-linked.js.map +1 -0
  78. package/dist/checks/d3-terms-linked.d.ts +2 -0
  79. package/dist/checks/d3-terms-linked.d.ts.map +1 -0
  80. package/dist/checks/d3-terms-linked.js +24 -0
  81. package/dist/checks/d3-terms-linked.js.map +1 -0
  82. package/dist/checks/d4-contact-accessible.d.ts +2 -0
  83. package/dist/checks/d4-contact-accessible.d.ts.map +1 -0
  84. package/dist/checks/d4-contact-accessible.js +30 -0
  85. package/dist/checks/d4-contact-accessible.js.map +1 -0
  86. package/dist/checks/d5-chrome-trust-link.d.ts +2 -0
  87. package/dist/checks/d5-chrome-trust-link.d.ts.map +1 -0
  88. package/dist/checks/d5-chrome-trust-link.js +24 -0
  89. package/dist/checks/d5-chrome-trust-link.js.map +1 -0
  90. package/dist/checks/d6-footer-trust-links.d.ts +2 -0
  91. package/dist/checks/d6-footer-trust-links.d.ts.map +1 -0
  92. package/dist/checks/d6-footer-trust-links.js +49 -0
  93. package/dist/checks/d6-footer-trust-links.js.map +1 -0
  94. package/dist/checks/e1-review-profile.d.ts +2 -0
  95. package/dist/checks/e1-review-profile.d.ts.map +1 -0
  96. package/dist/checks/e1-review-profile.js +38 -0
  97. package/dist/checks/e1-review-profile.js.map +1 -0
  98. package/dist/checks/e10-same-as-three.d.ts +2 -0
  99. package/dist/checks/e10-same-as-three.d.ts.map +1 -0
  100. package/dist/checks/e10-same-as-three.js +81 -0
  101. package/dist/checks/e10-same-as-three.js.map +1 -0
  102. package/dist/checks/e11-linkedin-linked.d.ts +2 -0
  103. package/dist/checks/e11-linkedin-linked.d.ts.map +1 -0
  104. package/dist/checks/e11-linkedin-linked.js +24 -0
  105. package/dist/checks/e11-linkedin-linked.js.map +1 -0
  106. package/dist/checks/e7-github-linked.d.ts +2 -0
  107. package/dist/checks/e7-github-linked.d.ts.map +1 -0
  108. package/dist/checks/e7-github-linked.js +24 -0
  109. package/dist/checks/e7-github-linked.js.map +1 -0
  110. package/dist/checks/f1-og-title.d.ts +2 -0
  111. package/dist/checks/f1-og-title.d.ts.map +1 -0
  112. package/dist/checks/f1-og-title.js +21 -0
  113. package/dist/checks/f1-og-title.js.map +1 -0
  114. package/dist/checks/f2-og-description.d.ts +2 -0
  115. package/dist/checks/f2-og-description.d.ts.map +1 -0
  116. package/dist/checks/f2-og-description.js +21 -0
  117. package/dist/checks/f2-og-description.js.map +1 -0
  118. package/dist/checks/f3-og-image.d.ts +2 -0
  119. package/dist/checks/f3-og-image.d.ts.map +1 -0
  120. package/dist/checks/f3-og-image.js +28 -0
  121. package/dist/checks/f3-og-image.js.map +1 -0
  122. package/dist/checks/f5-og-url.d.ts +2 -0
  123. package/dist/checks/f5-og-url.d.ts.map +1 -0
  124. package/dist/checks/f5-og-url.js +28 -0
  125. package/dist/checks/f5-og-url.js.map +1 -0
  126. package/dist/checks/f6-twitter-card.d.ts +2 -0
  127. package/dist/checks/f6-twitter-card.d.ts.map +1 -0
  128. package/dist/checks/f6-twitter-card.js +29 -0
  129. package/dist/checks/f6-twitter-card.js.map +1 -0
  130. package/dist/checks/f7-twitter-image.d.ts +2 -0
  131. package/dist/checks/f7-twitter-image.d.ts.map +1 -0
  132. package/dist/checks/f7-twitter-image.js +33 -0
  133. package/dist/checks/f7-twitter-image.js.map +1 -0
  134. package/dist/checks/registry.d.ts +10 -0
  135. package/dist/checks/registry.d.ts.map +1 -0
  136. package/dist/checks/registry.js +75 -0
  137. package/dist/checks/registry.js.map +1 -0
  138. package/dist/crawler.d.ts +40 -0
  139. package/dist/crawler.d.ts.map +1 -0
  140. package/dist/crawler.js +66 -0
  141. package/dist/crawler.js.map +1 -0
  142. package/dist/index.d.ts +48 -0
  143. package/dist/index.d.ts.map +1 -0
  144. package/dist/index.js +47 -0
  145. package/dist/index.js.map +1 -0
  146. package/dist/parser.d.ts +15 -0
  147. package/dist/parser.d.ts.map +1 -0
  148. package/dist/parser.js +10 -0
  149. package/dist/parser.js.map +1 -0
  150. package/dist/reporters/console.d.ts +12 -0
  151. package/dist/reporters/console.d.ts.map +1 -0
  152. package/dist/reporters/console.js +110 -0
  153. package/dist/reporters/console.js.map +1 -0
  154. package/dist/runner.d.ts +35 -0
  155. package/dist/runner.d.ts.map +1 -0
  156. package/dist/runner.js +120 -0
  157. package/dist/runner.js.map +1 -0
  158. package/dist/types.d.ts +52 -0
  159. package/dist/types.d.ts.map +1 -0
  160. package/dist/types.js +2 -0
  161. package/dist/types.js.map +1 -0
  162. package/package.json +48 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"e1-review-profile.js","sourceRoot":"","sources":["../../src/checks/e1-review-profile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAGnD,MAAM,YAAY,GAAG;IACnB,QAAQ;IACR,iBAAiB;IACjB,cAAc;IACd,gBAAgB;IAChB,eAAe;IACf,oBAAoB;IACpB,YAAY;CACJ,CAAC;AAEX,MAAM,CAAC,MAAM,eAAe,GAAG,WAAW,CAAW;IACnD,EAAE,EAAE,IAAI;IACR,QAAQ,EAAE,mBAAmB;IAC7B,QAAQ,EAAE,QAAQ;IAClB,MAAM,EAAE,CAAC;IACT,WAAW,EAAE,0EAA0E;IACvF,SAAS,EACP,+MAA+M;IACjN,OAAO,EAAE,uCAAuC;IAChD,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE;QACf,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;QAClC,GAAG,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE;YAC5B,MAAM,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YACxC,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;gBAChC,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;oBACtC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;gBACpB,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAC;QACH,IAAI,OAAO,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACvB,OAAO;gBACL,MAAM,EAAE,MAAM;gBACd,iBAAiB,EAAE,kCAAkC,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,iEAAiE;aAC1J,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,0BAA0B,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;IAC3F,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const e10SameAsThree: import("@answerable-kit/core").Check<import("cheerio").CheerioAPI>;
2
+ //# sourceMappingURL=e10-same-as-three.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"e10-same-as-three.d.ts","sourceRoot":"","sources":["../../src/checks/e10-same-as-three.ts"],"names":[],"mappings":"AA0CA,eAAO,MAAM,cAAc,oEAuCzB,CAAC"}
@@ -0,0 +1,81 @@
1
+ import { defineCheck } from '@answerable-kit/core';
2
+ const MIN_SAME_AS = 3;
3
+ /**
4
+ * Recursively walk a JSON-LD value looking for an `Organization` node
5
+ * (plain object, member of an array, or nested under `@graph`) and
6
+ * return its `sameAs` value as a string[]. The first Organization
7
+ * with a non-empty sameAs wins.
8
+ */
9
+ function findSameAs(parsed) {
10
+ if (Array.isArray(parsed)) {
11
+ for (const item of parsed) {
12
+ const out = findSameAs(item);
13
+ if (out.length > 0)
14
+ return out;
15
+ }
16
+ return [];
17
+ }
18
+ if (parsed === null || typeof parsed !== 'object')
19
+ return [];
20
+ const node = parsed;
21
+ const typeField = node['@type'];
22
+ const isOrg = typeField === 'Organization' ||
23
+ (Array.isArray(typeField) && typeField.includes('Organization'));
24
+ if (isOrg) {
25
+ const sameAs = node.sameAs;
26
+ if (Array.isArray(sameAs)) {
27
+ return sameAs.filter((v) => typeof v === 'string');
28
+ }
29
+ if (typeof sameAs === 'string')
30
+ return [sameAs];
31
+ }
32
+ const graph = node['@graph'];
33
+ if (Array.isArray(graph)) {
34
+ for (const child of graph) {
35
+ const out = findSameAs(child);
36
+ if (out.length > 0)
37
+ return out;
38
+ }
39
+ }
40
+ return [];
41
+ }
42
+ export const e10SameAsThree = defineCheck({
43
+ id: 'E10',
44
+ category: 'offsite-citations',
45
+ severity: 'medium',
46
+ points: 2,
47
+ description: 'Organization JSON-LD sameAs has ≥3 authoritative profile URLs',
48
+ rationale: 'The sameAs property on Organization JSON-LD is the most direct entity-graph signal you control. Three or more authoritative profiles (Twitter, LinkedIn, GitHub, Wikipedia, Crunchbase) ties your brand into the wider knowledge graph.',
49
+ docsUrl: 'https://answerable.dev/docs/checks/E10',
50
+ run: ({ dom }) => {
51
+ let best = [];
52
+ dom('script[type="application/ld+json"]').each((_, el) => {
53
+ const raw = dom(el).text();
54
+ try {
55
+ const parsed = JSON.parse(raw);
56
+ const sameAs = findSameAs(parsed);
57
+ if (sameAs.length > best.length) {
58
+ best = sameAs;
59
+ }
60
+ }
61
+ catch {
62
+ // Invalid JSON handled by C1.
63
+ }
64
+ });
65
+ if (best.length === 0) {
66
+ return {
67
+ status: 'fail',
68
+ fixRecommendation: `Add a sameAs array to your Organization JSON-LD with ≥${MIN_SAME_AS} authoritative profile URLs. Use organization({ sameAs: [...] }) from @answerable-kit/schemas.`,
69
+ };
70
+ }
71
+ if (best.length < MIN_SAME_AS) {
72
+ return {
73
+ status: 'warn',
74
+ evidence: `sameAs has ${best.length} entr${best.length === 1 ? 'y' : 'ies'} (need ≥${MIN_SAME_AS})`,
75
+ fixRecommendation: `Add more sameAs URLs (target ≥${MIN_SAME_AS}). Common picks: Twitter/X, LinkedIn, GitHub, Wikipedia.`,
76
+ };
77
+ }
78
+ return { status: 'pass', evidence: `sameAs has ${best.length} entries` };
79
+ },
80
+ });
81
+ //# sourceMappingURL=e10-same-as-three.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"e10-same-as-three.js","sourceRoot":"","sources":["../../src/checks/e10-same-as-three.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare const e11LinkedinLinked: import("@answerable-kit/core").Check<import("cheerio").CheerioAPI>;
2
+ //# sourceMappingURL=e11-linkedin-linked.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"e11-linkedin-linked.d.ts","sourceRoot":"","sources":["../../src/checks/e11-linkedin-linked.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,iBAAiB,oEAuB5B,CAAC"}
@@ -0,0 +1,24 @@
1
+ import { defineCheck } from '@answerable-kit/core';
2
+ export const e11LinkedinLinked = defineCheck({
3
+ id: 'E11',
4
+ category: 'offsite-citations',
5
+ severity: 'low',
6
+ points: 1,
7
+ description: 'LinkedIn company or personal profile linked',
8
+ rationale: 'LinkedIn is the highest-authority "who works here" signal. For B2B-leaning products, a LinkedIn link in the footer or About page improves entity-graph completeness with minimal effort.',
9
+ docsUrl: 'https://answerable.dev/docs/checks/E11',
10
+ run: ({ dom }) => {
11
+ const links = dom('a[href]').filter((_, el) => {
12
+ const href = (dom(el).attr('href') ?? '').toLowerCase();
13
+ return href.includes('linkedin.com');
14
+ });
15
+ if (links.length === 0) {
16
+ return {
17
+ status: 'warn',
18
+ fixRecommendation: 'Link your LinkedIn company or personal profile from the footer or About page.',
19
+ };
20
+ }
21
+ return { status: 'pass', evidence: `${links.length} linkedin.com link(s)` };
22
+ },
23
+ });
24
+ //# sourceMappingURL=e11-linkedin-linked.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"e11-linkedin-linked.js","sourceRoot":"","sources":["../../src/checks/e11-linkedin-linked.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAGnD,MAAM,CAAC,MAAM,iBAAiB,GAAG,WAAW,CAAW;IACrD,EAAE,EAAE,KAAK;IACT,QAAQ,EAAE,mBAAmB;IAC7B,QAAQ,EAAE,KAAK;IACf,MAAM,EAAE,CAAC;IACT,WAAW,EAAE,6CAA6C;IAC1D,SAAS,EACP,0LAA0L;IAC5L,OAAO,EAAE,wCAAwC;IACjD,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE;QACf,MAAM,KAAK,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE;YAC5C,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;YACxD,OAAO,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;QACvC,CAAC,CAAC,CAAC;QACH,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,OAAO;gBACL,MAAM,EAAE,MAAM;gBACd,iBAAiB,EACf,+EAA+E;aAClF,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC,MAAM,uBAAuB,EAAE,CAAC;IAC9E,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const e7GithubLinked: import("@answerable-kit/core").Check<import("cheerio").CheerioAPI>;
2
+ //# sourceMappingURL=e7-github-linked.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"e7-github-linked.d.ts","sourceRoot":"","sources":["../../src/checks/e7-github-linked.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,cAAc,oEAuBzB,CAAC"}
@@ -0,0 +1,24 @@
1
+ import { defineCheck } from '@answerable-kit/core';
2
+ export const e7GithubLinked = defineCheck({
3
+ id: 'E7',
4
+ category: 'offsite-citations',
5
+ severity: 'medium',
6
+ points: 2,
7
+ description: 'GitHub profile / repository linked from the page',
8
+ rationale: 'For technical products, GitHub is the canonical proof-of-work surface. A visible GitHub link tells AI engines (and developer users) you ship real code; absence is a soft red flag for any dev-tools product.',
9
+ docsUrl: 'https://answerable.dev/docs/checks/E7',
10
+ run: ({ dom }) => {
11
+ const links = dom('a[href]').filter((_, el) => {
12
+ const href = (dom(el).attr('href') ?? '').toLowerCase();
13
+ return href.includes('github.com');
14
+ });
15
+ if (links.length === 0) {
16
+ return {
17
+ status: 'warn',
18
+ fixRecommendation: "Link your GitHub from somewhere visible (footer or 'About' page). Skip this check for non-technical products by setting the audit to ignore E7.",
19
+ };
20
+ }
21
+ return { status: 'pass', evidence: `${links.length} github.com link(s)` };
22
+ },
23
+ });
24
+ //# sourceMappingURL=e7-github-linked.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"e7-github-linked.js","sourceRoot":"","sources":["../../src/checks/e7-github-linked.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAGnD,MAAM,CAAC,MAAM,cAAc,GAAG,WAAW,CAAW;IAClD,EAAE,EAAE,IAAI;IACR,QAAQ,EAAE,mBAAmB;IAC7B,QAAQ,EAAE,QAAQ;IAClB,MAAM,EAAE,CAAC;IACT,WAAW,EAAE,kDAAkD;IAC/D,SAAS,EACP,+MAA+M;IACjN,OAAO,EAAE,uCAAuC;IAChD,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE;QACf,MAAM,KAAK,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE;YAC5C,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;YACxD,OAAO,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;QACrC,CAAC,CAAC,CAAC;QACH,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,OAAO;gBACL,MAAM,EAAE,MAAM;gBACd,iBAAiB,EACf,iJAAiJ;aACpJ,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC,MAAM,qBAAqB,EAAE,CAAC;IAC5E,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const f1OgTitle: import("@answerable-kit/core").Check<import("cheerio").CheerioAPI>;
2
+ //# sourceMappingURL=f1-og-title.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"f1-og-title.d.ts","sourceRoot":"","sources":["../../src/checks/f1-og-title.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,SAAS,oEAoBpB,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { defineCheck } from '@answerable-kit/core';
2
+ export const f1OgTitle = defineCheck({
3
+ id: 'F1',
4
+ category: 'og-and-social',
5
+ severity: 'high',
6
+ points: 1,
7
+ description: 'og:title set',
8
+ rationale: 'Open Graph title is what appears when your page is shared on Facebook, LinkedIn, Slack, iMessage, and dozens of other platforms. Without it, those platforms fall back to the <title> tag — which often differs from how you want the shared link to read.',
9
+ docsUrl: 'https://answerable.dev/docs/checks/F1',
10
+ run: ({ dom }) => {
11
+ const content = dom('meta[property="og:title"]').attr('content')?.trim() ?? '';
12
+ if (!content) {
13
+ return {
14
+ status: 'fail',
15
+ fixRecommendation: 'Add <meta property="og:title" content="..."> inside <head>. defineSeo() emits this automatically.',
16
+ };
17
+ }
18
+ return { status: 'pass', evidence: `og:title="${content}"` };
19
+ },
20
+ });
21
+ //# sourceMappingURL=f1-og-title.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"f1-og-title.js","sourceRoot":"","sources":["../../src/checks/f1-og-title.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAGnD,MAAM,CAAC,MAAM,SAAS,GAAG,WAAW,CAAW;IAC7C,EAAE,EAAE,IAAI;IACR,QAAQ,EAAE,eAAe;IACzB,QAAQ,EAAE,MAAM;IAChB,MAAM,EAAE,CAAC;IACT,WAAW,EAAE,cAAc;IAC3B,SAAS,EACP,4PAA4P;IAC9P,OAAO,EAAE,uCAAuC;IAChD,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE;QACf,MAAM,OAAO,GAAG,GAAG,CAAC,2BAA2B,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QAC/E,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO;gBACL,MAAM,EAAE,MAAM;gBACd,iBAAiB,EACf,mGAAmG;aACtG,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,OAAO,GAAG,EAAE,CAAC;IAC/D,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const f2OgDescription: import("@answerable-kit/core").Check<import("cheerio").CheerioAPI>;
2
+ //# sourceMappingURL=f2-og-description.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"f2-og-description.d.ts","sourceRoot":"","sources":["../../src/checks/f2-og-description.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,eAAe,oEAoB1B,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { defineCheck } from '@answerable-kit/core';
2
+ export const f2OgDescription = defineCheck({
3
+ id: 'F2',
4
+ category: 'og-and-social',
5
+ severity: 'high',
6
+ points: 1,
7
+ description: 'og:description set',
8
+ rationale: 'Open Graph description is the body text underneath the link preview when your page is shared. Without it, social platforms either pull from meta description (if you have one) or leave the snippet blank — which kills click-through.',
9
+ docsUrl: 'https://answerable.dev/docs/checks/F2',
10
+ run: ({ dom }) => {
11
+ const content = dom('meta[property="og:description"]').attr('content')?.trim() ?? '';
12
+ if (!content) {
13
+ return {
14
+ status: 'fail',
15
+ fixRecommendation: 'Add <meta property="og:description" content="..."> inside <head>. defineSeo() emits this automatically.',
16
+ };
17
+ }
18
+ return { status: 'pass', evidence: `og:description="${content}"` };
19
+ },
20
+ });
21
+ //# sourceMappingURL=f2-og-description.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"f2-og-description.js","sourceRoot":"","sources":["../../src/checks/f2-og-description.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAGnD,MAAM,CAAC,MAAM,eAAe,GAAG,WAAW,CAAW;IACnD,EAAE,EAAE,IAAI;IACR,QAAQ,EAAE,eAAe;IACzB,QAAQ,EAAE,MAAM;IAChB,MAAM,EAAE,CAAC;IACT,WAAW,EAAE,oBAAoB;IACjC,SAAS,EACP,wOAAwO;IAC1O,OAAO,EAAE,uCAAuC;IAChD,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE;QACf,MAAM,OAAO,GAAG,GAAG,CAAC,iCAAiC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QACrF,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO;gBACL,MAAM,EAAE,MAAM;gBACd,iBAAiB,EACf,yGAAyG;aAC5G,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,mBAAmB,OAAO,GAAG,EAAE,CAAC;IACrE,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const f3OgImage: import("@answerable-kit/core").Check<import("cheerio").CheerioAPI>;
2
+ //# sourceMappingURL=f3-og-image.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"f3-og-image.d.ts","sourceRoot":"","sources":["../../src/checks/f3-og-image.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,SAAS,oEA4BpB,CAAC"}
@@ -0,0 +1,28 @@
1
+ import { defineCheck } from '@answerable-kit/core';
2
+ export const f3OgImage = defineCheck({
3
+ id: 'F3',
4
+ category: 'og-and-social',
5
+ severity: 'high',
6
+ points: 2,
7
+ description: 'og:image declared with an absolute http(s) URL',
8
+ rationale: 'The Open Graph image is the single biggest driver of social-share click-through. A 1200×630 dedicated image roughly triples engagement vs no image. Relative URLs are valid spec but crawled inconsistently — Facebook and LinkedIn both prefer absolute.',
9
+ docsUrl: 'https://answerable.dev/docs/checks/F3',
10
+ run: ({ dom }) => {
11
+ const content = dom('meta[property="og:image"]').attr('content')?.trim() ?? '';
12
+ if (!content) {
13
+ return {
14
+ status: 'fail',
15
+ fixRecommendation: 'Add <meta property="og:image" content="https://example.com/og.png">. Target 1200×630 px.',
16
+ };
17
+ }
18
+ if (!/^https?:\/\//i.test(content)) {
19
+ return {
20
+ status: 'warn',
21
+ evidence: `og:image="${content}" (relative)`,
22
+ fixRecommendation: 'Use an absolute https URL for og:image. Facebook and LinkedIn crawlers handle absolute paths most reliably.',
23
+ };
24
+ }
25
+ return { status: 'pass', evidence: `og:image="${content}"` };
26
+ },
27
+ });
28
+ //# sourceMappingURL=f3-og-image.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"f3-og-image.js","sourceRoot":"","sources":["../../src/checks/f3-og-image.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAGnD,MAAM,CAAC,MAAM,SAAS,GAAG,WAAW,CAAW;IAC7C,EAAE,EAAE,IAAI;IACR,QAAQ,EAAE,eAAe;IACzB,QAAQ,EAAE,MAAM;IAChB,MAAM,EAAE,CAAC;IACT,WAAW,EAAE,gDAAgD;IAC7D,SAAS,EACP,2PAA2P;IAC7P,OAAO,EAAE,uCAAuC;IAChD,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE;QACf,MAAM,OAAO,GAAG,GAAG,CAAC,2BAA2B,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QAC/E,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO;gBACL,MAAM,EAAE,MAAM;gBACd,iBAAiB,EACf,0FAA0F;aAC7F,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YACnC,OAAO;gBACL,MAAM,EAAE,MAAM;gBACd,QAAQ,EAAE,aAAa,OAAO,cAAc;gBAC5C,iBAAiB,EACf,6GAA6G;aAChH,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,OAAO,GAAG,EAAE,CAAC;IAC/D,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const f5OgUrl: import("@answerable-kit/core").Check<import("cheerio").CheerioAPI>;
2
+ //# sourceMappingURL=f5-og-url.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"f5-og-url.d.ts","sourceRoot":"","sources":["../../src/checks/f5-og-url.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,OAAO,oEA2BlB,CAAC"}
@@ -0,0 +1,28 @@
1
+ import { defineCheck } from '@answerable-kit/core';
2
+ export const f5OgUrl = defineCheck({
3
+ id: 'F5',
4
+ category: 'og-and-social',
5
+ severity: 'medium',
6
+ points: 1,
7
+ description: 'og:url declared as the canonical absolute URL',
8
+ rationale: "og:url lets social platforms know which canonical URL to associate share counts and link metadata with. Without it, the same page reachable via UTM-tagged links accumulates separate share counts. Should match the page's canonical link.",
9
+ docsUrl: 'https://answerable.dev/docs/checks/F5',
10
+ run: ({ dom }) => {
11
+ const content = dom('meta[property="og:url"]').attr('content')?.trim() ?? '';
12
+ if (!content) {
13
+ return {
14
+ status: 'fail',
15
+ fixRecommendation: 'Add <meta property="og:url" content="https://example.com/page">. Should match the canonical link.',
16
+ };
17
+ }
18
+ if (!/^https?:\/\//i.test(content)) {
19
+ return {
20
+ status: 'warn',
21
+ evidence: `og:url="${content}" (relative)`,
22
+ fixRecommendation: 'Use an absolute https URL for og:url.',
23
+ };
24
+ }
25
+ return { status: 'pass', evidence: `og:url="${content}"` };
26
+ },
27
+ });
28
+ //# sourceMappingURL=f5-og-url.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"f5-og-url.js","sourceRoot":"","sources":["../../src/checks/f5-og-url.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAGnD,MAAM,CAAC,MAAM,OAAO,GAAG,WAAW,CAAW;IAC3C,EAAE,EAAE,IAAI;IACR,QAAQ,EAAE,eAAe;IACzB,QAAQ,EAAE,QAAQ;IAClB,MAAM,EAAE,CAAC;IACT,WAAW,EAAE,+CAA+C;IAC5D,SAAS,EACP,6OAA6O;IAC/O,OAAO,EAAE,uCAAuC;IAChD,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE;QACf,MAAM,OAAO,GAAG,GAAG,CAAC,yBAAyB,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QAC7E,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO;gBACL,MAAM,EAAE,MAAM;gBACd,iBAAiB,EACf,mGAAmG;aACtG,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YACnC,OAAO;gBACL,MAAM,EAAE,MAAM;gBACd,QAAQ,EAAE,WAAW,OAAO,cAAc;gBAC1C,iBAAiB,EAAE,uCAAuC;aAC3D,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,OAAO,GAAG,EAAE,CAAC;IAC7D,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const f6TwitterCard: import("@answerable-kit/core").Check<import("cheerio").CheerioAPI>;
2
+ //# sourceMappingURL=f6-twitter-card.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"f6-twitter-card.d.ts","sourceRoot":"","sources":["../../src/checks/f6-twitter-card.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,aAAa,oEA2BxB,CAAC"}
@@ -0,0 +1,29 @@
1
+ import { defineCheck } from '@answerable-kit/core';
2
+ const VALID_CARDS = new Set(['summary', 'summary_large_image', 'app', 'player']);
3
+ export const f6TwitterCard = defineCheck({
4
+ id: 'F6',
5
+ category: 'og-and-social',
6
+ severity: 'high',
7
+ points: 1,
8
+ description: 'twitter:card set to a recognised card type',
9
+ rationale: 'twitter:card controls how the page renders when shared on X (Twitter). Without it, the platform falls back to a tiny text-only card. `summary_large_image` is the default people actually want — full-bleed image and prominent title.',
10
+ docsUrl: 'https://answerable.dev/docs/checks/F6',
11
+ run: ({ dom }) => {
12
+ const content = dom('meta[name="twitter:card"]').attr('content')?.trim().toLowerCase() ?? '';
13
+ if (!content) {
14
+ return {
15
+ status: 'fail',
16
+ fixRecommendation: 'Add <meta name="twitter:card" content="summary_large_image"> inside <head>.',
17
+ };
18
+ }
19
+ if (!VALID_CARDS.has(content)) {
20
+ return {
21
+ status: 'warn',
22
+ evidence: `twitter:card="${content}"`,
23
+ fixRecommendation: `Use one of: summary, summary_large_image, app, player. Got "${content}".`,
24
+ };
25
+ }
26
+ return { status: 'pass', evidence: `twitter:card="${content}"` };
27
+ },
28
+ });
29
+ //# sourceMappingURL=f6-twitter-card.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"f6-twitter-card.js","sourceRoot":"","sources":["../../src/checks/f6-twitter-card.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAGnD,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,qBAAqB,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC;AAEjF,MAAM,CAAC,MAAM,aAAa,GAAG,WAAW,CAAW;IACjD,EAAE,EAAE,IAAI;IACR,QAAQ,EAAE,eAAe;IACzB,QAAQ,EAAE,MAAM;IAChB,MAAM,EAAE,CAAC;IACT,WAAW,EAAE,4CAA4C;IACzD,SAAS,EACP,wOAAwO;IAC1O,OAAO,EAAE,uCAAuC;IAChD,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE;QACf,MAAM,OAAO,GAAG,GAAG,CAAC,2BAA2B,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC;QAC7F,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO;gBACL,MAAM,EAAE,MAAM;gBACd,iBAAiB,EACf,6EAA6E;aAChF,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YAC9B,OAAO;gBACL,MAAM,EAAE,MAAM;gBACd,QAAQ,EAAE,iBAAiB,OAAO,GAAG;gBACrC,iBAAiB,EAAE,+DAA+D,OAAO,IAAI;aAC9F,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,iBAAiB,OAAO,GAAG,EAAE,CAAC;IACnE,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const f7TwitterImage: import("@answerable-kit/core").Check<import("cheerio").CheerioAPI>;
2
+ //# sourceMappingURL=f7-twitter-image.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"f7-twitter-image.d.ts","sourceRoot":"","sources":["../../src/checks/f7-twitter-image.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,cAAc,oEAgCzB,CAAC"}
@@ -0,0 +1,33 @@
1
+ import { defineCheck } from '@answerable-kit/core';
2
+ export const f7TwitterImage = defineCheck({
3
+ id: 'F7',
4
+ category: 'og-and-social',
5
+ severity: 'medium',
6
+ points: 1,
7
+ description: 'twitter:image declared (or og:image fallback acceptable)',
8
+ rationale: 'X (Twitter) falls back to og:image when twitter:image is missing, which is fine for most cases. Set a dedicated twitter:image only when the share preview should differ from the OG image (e.g. text-optimised crop for the 2:1 Twitter aspect).',
9
+ docsUrl: 'https://answerable.dev/docs/checks/F7',
10
+ run: ({ dom }) => {
11
+ const twitterImage = dom('meta[name="twitter:image"]').attr('content')?.trim();
12
+ if (twitterImage !== undefined && twitterImage !== '') {
13
+ if (!/^https?:\/\//i.test(twitterImage)) {
14
+ return {
15
+ status: 'warn',
16
+ evidence: `twitter:image="${twitterImage}" (relative)`,
17
+ fixRecommendation: 'Use an absolute https URL for twitter:image.',
18
+ };
19
+ }
20
+ return { status: 'pass', evidence: `twitter:image="${twitterImage}"` };
21
+ }
22
+ // Twitter falls back to og:image when twitter:image is missing — acceptable.
23
+ const ogImage = dom('meta[property="og:image"]').attr('content')?.trim();
24
+ if (ogImage !== undefined && ogImage !== '') {
25
+ return { status: 'pass', evidence: 'Falls back to og:image' };
26
+ }
27
+ return {
28
+ status: 'fail',
29
+ fixRecommendation: 'Add <meta name="twitter:image" content="https://example.com/og.png"> or set og:image as the fallback.',
30
+ };
31
+ },
32
+ });
33
+ //# sourceMappingURL=f7-twitter-image.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"f7-twitter-image.js","sourceRoot":"","sources":["../../src/checks/f7-twitter-image.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAGnD,MAAM,CAAC,MAAM,cAAc,GAAG,WAAW,CAAW;IAClD,EAAE,EAAE,IAAI;IACR,QAAQ,EAAE,eAAe;IACzB,QAAQ,EAAE,QAAQ;IAClB,MAAM,EAAE,CAAC;IACT,WAAW,EAAE,0DAA0D;IACvE,SAAS,EACP,kPAAkP;IACpP,OAAO,EAAE,uCAAuC;IAChD,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE;QACf,MAAM,YAAY,GAAG,GAAG,CAAC,4BAA4B,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,CAAC;QAC/E,IAAI,YAAY,KAAK,SAAS,IAAI,YAAY,KAAK,EAAE,EAAE,CAAC;YACtD,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;gBACxC,OAAO;oBACL,MAAM,EAAE,MAAM;oBACd,QAAQ,EAAE,kBAAkB,YAAY,cAAc;oBACtD,iBAAiB,EAAE,8CAA8C;iBAClE,CAAC;YACJ,CAAC;YACD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,kBAAkB,YAAY,GAAG,EAAE,CAAC;QACzE,CAAC;QACD,6EAA6E;QAC7E,MAAM,OAAO,GAAG,GAAG,CAAC,2BAA2B,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,CAAC;QACzE,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,EAAE,EAAE,CAAC;YAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,wBAAwB,EAAE,CAAC;QAChE,CAAC;QACD,OAAO;YACL,MAAM,EAAE,MAAM;YACd,iBAAiB,EACf,uGAAuG;SAC1G,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { Check } from '@answerable-kit/core';
2
+ import type { AuditDom } from '../parser.js';
3
+ /**
4
+ * Every check registered with the audit engine, in stable AUDIT-FRAMEWORK
5
+ * order. Subsequent PRs append to this list — never reorder, never
6
+ * renumber. Stable IDs (`A1`, `A3`, ...) are part of the public API
7
+ * (users pin `--ignore A4` in CI).
8
+ */
9
+ export declare const DEFAULT_CHECKS: readonly Check<AuditDom>[];
10
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/checks/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAmC7C;;;;;GAKG;AACH,eAAO,MAAM,cAAc,EAAE,SAAS,KAAK,CAAC,QAAQ,CAAC,EAkCpD,CAAC"}
@@ -0,0 +1,75 @@
1
+ import { a1Title } from './a1-title.js';
2
+ import { a3Description } from './a3-description.js';
3
+ import { a4Canonical } from './a4-canonical.js';
4
+ import { a5HtmlLang } from './a5-html-lang.js';
5
+ import { a6Viewport } from './a6-viewport.js';
6
+ import { a7Charset } from './a7-charset.js';
7
+ import { a8Robots } from './a8-robots.js';
8
+ import { a9Favicon } from './a9-favicon.js';
9
+ import { a10AppleTouchIcon } from './a10-apple-touch.js';
10
+ import { b1SingleH1 } from './b1-single-h1.js';
11
+ import { b3HeadingHierarchy } from './b3-heading-hierarchy.js';
12
+ import { b4H2Sections } from './b4-h2-sections.js';
13
+ import { b8ExternalCitations } from './b8-external-citations.js';
14
+ import { b11InternalLinks } from './b11-internal-links.js';
15
+ import { b14ListsOrTables } from './b14-lists-or-tables.js';
16
+ import { c1JsonLd } from './c1-json-ld.js';
17
+ import { c2Organization } from './c2-organization.js';
18
+ import { d1AboutPageLinked } from './d1-about-page-linked.js';
19
+ import { d2PrivacyLinked } from './d2-privacy-linked.js';
20
+ import { d3TermsLinked } from './d3-terms-linked.js';
21
+ import { d4ContactAccessible } from './d4-contact-accessible.js';
22
+ import { d5ChromeTrustLink } from './d5-chrome-trust-link.js';
23
+ import { d6FooterTrustLinks } from './d6-footer-trust-links.js';
24
+ import { e1ReviewProfile } from './e1-review-profile.js';
25
+ import { e7GithubLinked } from './e7-github-linked.js';
26
+ import { e10SameAsThree } from './e10-same-as-three.js';
27
+ import { e11LinkedinLinked } from './e11-linkedin-linked.js';
28
+ import { f1OgTitle } from './f1-og-title.js';
29
+ import { f2OgDescription } from './f2-og-description.js';
30
+ import { f3OgImage } from './f3-og-image.js';
31
+ import { f5OgUrl } from './f5-og-url.js';
32
+ import { f6TwitterCard } from './f6-twitter-card.js';
33
+ import { f7TwitterImage } from './f7-twitter-image.js';
34
+ /**
35
+ * Every check registered with the audit engine, in stable AUDIT-FRAMEWORK
36
+ * order. Subsequent PRs append to this list — never reorder, never
37
+ * renumber. Stable IDs (`A1`, `A3`, ...) are part of the public API
38
+ * (users pin `--ignore A4` in CI).
39
+ */
40
+ export const DEFAULT_CHECKS = [
41
+ a1Title,
42
+ a3Description,
43
+ a4Canonical,
44
+ a5HtmlLang,
45
+ a6Viewport,
46
+ a7Charset,
47
+ a8Robots,
48
+ a9Favicon,
49
+ a10AppleTouchIcon,
50
+ b1SingleH1,
51
+ b3HeadingHierarchy,
52
+ b4H2Sections,
53
+ b8ExternalCitations,
54
+ b11InternalLinks,
55
+ b14ListsOrTables,
56
+ c1JsonLd,
57
+ c2Organization,
58
+ d1AboutPageLinked,
59
+ d2PrivacyLinked,
60
+ d3TermsLinked,
61
+ d4ContactAccessible,
62
+ d5ChromeTrustLink,
63
+ d6FooterTrustLinks,
64
+ e1ReviewProfile,
65
+ e7GithubLinked,
66
+ e10SameAsThree,
67
+ e11LinkedinLinked,
68
+ f1OgTitle,
69
+ f2OgDescription,
70
+ f3OgImage,
71
+ f5OgUrl,
72
+ f6TwitterCard,
73
+ f7TwitterImage,
74
+ ];
75
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/checks/registry.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,cAAc,GAA+B;IACxD,OAAO;IACP,aAAa;IACb,WAAW;IACX,UAAU;IACV,UAAU;IACV,SAAS;IACT,QAAQ;IACR,SAAS;IACT,iBAAiB;IACjB,UAAU;IACV,kBAAkB;IAClB,YAAY;IACZ,mBAAmB;IACnB,gBAAgB;IAChB,gBAAgB;IAChB,QAAQ;IACR,cAAc;IACd,iBAAiB;IACjB,eAAe;IACf,aAAa;IACb,mBAAmB;IACnB,iBAAiB;IACjB,kBAAkB;IAClB,eAAe;IACf,cAAc;IACd,cAAc;IACd,iBAAiB;IACjB,SAAS;IACT,eAAe;IACf,SAAS;IACT,OAAO;IACP,aAAa;IACb,cAAc;CACf,CAAC"}
@@ -0,0 +1,40 @@
1
+ import { AnswerableError } from '@answerable-kit/core';
2
+ import { type AuditDom } from './parser.js';
3
+ export declare const DEFAULT_USER_AGENT = "Answerable/0.0.0 (+https://github.com/Anuj7411/answerable)";
4
+ export declare const DEFAULT_TIMEOUT_MS = 15000;
5
+ export interface FetchAndParseOptions {
6
+ readonly userAgent?: string;
7
+ readonly timeoutMs?: number;
8
+ /** Test-injection seam. Defaults to the global `fetch`. */
9
+ readonly fetchImpl?: typeof fetch;
10
+ }
11
+ export interface FetchAndParseResult {
12
+ readonly url: string;
13
+ readonly finalUrl: string;
14
+ readonly status: number;
15
+ readonly html: string;
16
+ readonly dom: AuditDom;
17
+ }
18
+ /**
19
+ * Thrown when the crawler can't reach the target URL or the response
20
+ * isn't a 2xx with HTML-shaped content.
21
+ */
22
+ export declare class CrawlError extends AnswerableError {
23
+ readonly url: string;
24
+ readonly httpStatus: number | undefined;
25
+ constructor(message: string, options: {
26
+ readonly url: string;
27
+ readonly status?: number;
28
+ readonly cause?: unknown;
29
+ });
30
+ }
31
+ /**
32
+ * Fetch a URL and parse the response into a cheerio DOM. Follows
33
+ * redirects (delegated to native fetch). Sets a polite User-Agent.
34
+ * Throws `CrawlError` on network failure or non-2xx response.
35
+ *
36
+ * @throws InvalidUrlError if `url` is not a valid absolute http(s) URL.
37
+ * @throws CrawlError on network failure, timeout, or non-2xx response.
38
+ */
39
+ export declare function fetchAndParse(url: string, options?: FetchAndParseOptions): Promise<FetchAndParseResult>;
40
+ //# sourceMappingURL=crawler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crawler.d.ts","sourceRoot":"","sources":["../src/crawler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAoB,MAAM,sBAAsB,CAAC;AACzE,OAAO,EAAE,KAAK,QAAQ,EAAY,MAAM,aAAa,CAAC;AAEtD,eAAO,MAAM,kBAAkB,+DAA+D,CAAC;AAE/F,eAAO,MAAM,kBAAkB,QAAS,CAAC;AAEzC,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,2DAA2D;IAC3D,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CACnC;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC;CACxB;AAED;;;GAGG;AACH,qBAAa,UAAW,SAAQ,eAAe;IAC7C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;gBAGtC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;QAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAOxF;AAED;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,mBAAmB,CAAC,CAwC9B"}