@rankcli/agent-runtime 0.0.15 → 0.0.17

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 (203) hide show
  1. package/README.md +5 -4
  2. package/dist/index.d.mts +44 -393
  3. package/dist/index.d.ts +44 -393
  4. package/dist/index.js +22098 -26131
  5. package/dist/index.mjs +11834 -15857
  6. package/package.json +10 -4
  7. package/scripts/build-deno.ts +0 -134
  8. package/src/analyzers/core-web-vitals-analyzer.test.ts +0 -236
  9. package/src/analyzers/core-web-vitals-analyzer.ts +0 -557
  10. package/src/analyzers/geo-analyzer.test.ts +0 -310
  11. package/src/analyzers/geo-analyzer.ts +0 -814
  12. package/src/analyzers/image-optimization-analyzer.test.ts +0 -145
  13. package/src/analyzers/image-optimization-analyzer.ts +0 -348
  14. package/src/analyzers/index.ts +0 -233
  15. package/src/analyzers/internal-linking-analyzer.test.ts +0 -141
  16. package/src/analyzers/internal-linking-analyzer.ts +0 -419
  17. package/src/analyzers/mobile-seo-analyzer.test.ts +0 -140
  18. package/src/analyzers/mobile-seo-analyzer.ts +0 -455
  19. package/src/analyzers/security-headers-analyzer.test.ts +0 -115
  20. package/src/analyzers/security-headers-analyzer.ts +0 -318
  21. package/src/analyzers/structured-data-analyzer.test.ts +0 -210
  22. package/src/analyzers/structured-data-analyzer.ts +0 -590
  23. package/src/audit/ai/analyzer.ts +0 -347
  24. package/src/audit/ai/index.ts +0 -29
  25. package/src/audit/ai/prompts/content-analysis.ts +0 -271
  26. package/src/audit/ai/types.ts +0 -179
  27. package/src/audit/checks/additional-checks.ts +0 -439
  28. package/src/audit/checks/agent-experience.ts +0 -108
  29. package/src/audit/checks/ai-citation-worthiness.ts +0 -399
  30. package/src/audit/checks/ai-content-structure.ts +0 -325
  31. package/src/audit/checks/ai-readiness.ts +0 -406
  32. package/src/audit/checks/anchor-text.ts +0 -179
  33. package/src/audit/checks/answer-conciseness.ts +0 -322
  34. package/src/audit/checks/asset-minification.ts +0 -270
  35. package/src/audit/checks/bing-optimization.ts +0 -206
  36. package/src/audit/checks/brand-mention-optimization.ts +0 -349
  37. package/src/audit/checks/caching-headers.ts +0 -305
  38. package/src/audit/checks/canonical-advanced.ts +0 -150
  39. package/src/audit/checks/canonical-domain.ts +0 -196
  40. package/src/audit/checks/citation-quality.ts +0 -358
  41. package/src/audit/checks/client-rendering.ts +0 -542
  42. package/src/audit/checks/color-contrast.ts +0 -342
  43. package/src/audit/checks/content-freshness.ts +0 -170
  44. package/src/audit/checks/content-science.ts +0 -589
  45. package/src/audit/checks/conversion-elements.ts +0 -526
  46. package/src/audit/checks/crawlability.ts +0 -220
  47. package/src/audit/checks/directory-listing.ts +0 -172
  48. package/src/audit/checks/dom-analysis.ts +0 -191
  49. package/src/audit/checks/dom-size.ts +0 -246
  50. package/src/audit/checks/duplicate-content.ts +0 -194
  51. package/src/audit/checks/eeat-signals.ts +0 -990
  52. package/src/audit/checks/entity-seo.ts +0 -396
  53. package/src/audit/checks/featured-snippet.ts +0 -473
  54. package/src/audit/checks/freshness-signals.ts +0 -443
  55. package/src/audit/checks/funnel-intent.ts +0 -463
  56. package/src/audit/checks/hreflang.ts +0 -174
  57. package/src/audit/checks/html-compliance.ts +0 -302
  58. package/src/audit/checks/image-dimensions.ts +0 -167
  59. package/src/audit/checks/images.ts +0 -160
  60. package/src/audit/checks/indexnow.ts +0 -275
  61. package/src/audit/checks/interactive-tools.ts +0 -475
  62. package/src/audit/checks/internal-link-graph.ts +0 -436
  63. package/src/audit/checks/keyword-analysis.ts +0 -239
  64. package/src/audit/checks/keyword-cannibalization.ts +0 -385
  65. package/src/audit/checks/keyword-placement.ts +0 -471
  66. package/src/audit/checks/links.ts +0 -273
  67. package/src/audit/checks/llms-txt.ts +0 -224
  68. package/src/audit/checks/local-seo.ts +0 -296
  69. package/src/audit/checks/mobile.ts +0 -167
  70. package/src/audit/checks/modern-images.ts +0 -226
  71. package/src/audit/checks/navboost-signals.ts +0 -395
  72. package/src/audit/checks/on-page.ts +0 -209
  73. package/src/audit/checks/page-resources.ts +0 -285
  74. package/src/audit/checks/pagination.ts +0 -180
  75. package/src/audit/checks/performance.ts +0 -153
  76. package/src/audit/checks/platform-presence.ts +0 -580
  77. package/src/audit/checks/rag-chunk-readiness.test.ts +0 -159
  78. package/src/audit/checks/rag-chunk-readiness.ts +0 -163
  79. package/src/audit/checks/redirect-analysis.ts +0 -153
  80. package/src/audit/checks/redirect-chain.ts +0 -389
  81. package/src/audit/checks/resource-hints.ts +0 -420
  82. package/src/audit/checks/responsive-css.ts +0 -247
  83. package/src/audit/checks/responsive-images.ts +0 -396
  84. package/src/audit/checks/review-ecosystem.ts +0 -415
  85. package/src/audit/checks/robots-validation.ts +0 -373
  86. package/src/audit/checks/security-headers.ts +0 -188
  87. package/src/audit/checks/security.ts +0 -144
  88. package/src/audit/checks/serp-preview.ts +0 -251
  89. package/src/audit/checks/site-maturity.ts +0 -453
  90. package/src/audit/checks/social-meta.test.ts +0 -275
  91. package/src/audit/checks/social-meta.ts +0 -134
  92. package/src/audit/checks/soft-404.ts +0 -151
  93. package/src/audit/checks/structured-data.ts +0 -238
  94. package/src/audit/checks/tech-detection.ts +0 -496
  95. package/src/audit/checks/topical-clusters.ts +0 -435
  96. package/src/audit/checks/tracker-bloat.ts +0 -462
  97. package/src/audit/checks/tracking-verification.test.ts +0 -371
  98. package/src/audit/checks/tracking-verification.ts +0 -636
  99. package/src/audit/checks/url-safety.ts +0 -682
  100. package/src/audit/deno-entry.ts +0 -66
  101. package/src/audit/discovery/index.ts +0 -15
  102. package/src/audit/discovery/link-crawler.ts +0 -233
  103. package/src/audit/discovery/repo-routes.ts +0 -347
  104. package/src/audit/engine.ts +0 -633
  105. package/src/audit/fixes/index.ts +0 -209
  106. package/src/audit/fixes/social-meta-fixes.test.ts +0 -329
  107. package/src/audit/fixes/social-meta-fixes.ts +0 -463
  108. package/src/audit/index.ts +0 -74
  109. package/src/audit/runner.test.ts +0 -299
  110. package/src/audit/runner.ts +0 -130
  111. package/src/audit/types.ts +0 -1987
  112. package/src/content/featured-snippet.ts +0 -367
  113. package/src/content/generator.test.ts +0 -534
  114. package/src/content/generator.ts +0 -501
  115. package/src/content/headline.ts +0 -317
  116. package/src/content/index.ts +0 -62
  117. package/src/content/intent.ts +0 -258
  118. package/src/content/keyword-density.ts +0 -349
  119. package/src/content/readability.ts +0 -262
  120. package/src/executor.ts +0 -336
  121. package/src/fixer/framework-fixes.test.ts +0 -489
  122. package/src/fixer/framework-fixes.ts +0 -5287
  123. package/src/fixer/index.ts +0 -6
  124. package/src/fixer/schemas.ts +0 -971
  125. package/src/fixer.ts +0 -642
  126. package/src/frameworks/detector.test.ts +0 -248
  127. package/src/frameworks/detector.ts +0 -899
  128. package/src/frameworks/index.ts +0 -68
  129. package/src/frameworks/recipes/angular.yaml +0 -171
  130. package/src/frameworks/recipes/astro.yaml +0 -206
  131. package/src/frameworks/recipes/django.yaml +0 -180
  132. package/src/frameworks/recipes/laravel.yaml +0 -137
  133. package/src/frameworks/recipes/nextjs.yaml +0 -268
  134. package/src/frameworks/recipes/nuxt.yaml +0 -175
  135. package/src/frameworks/recipes/rails.yaml +0 -188
  136. package/src/frameworks/recipes/react.yaml +0 -202
  137. package/src/frameworks/recipes/sveltekit.yaml +0 -154
  138. package/src/frameworks/recipes/vue.yaml +0 -137
  139. package/src/frameworks/recipes/wordpress.yaml +0 -209
  140. package/src/frameworks/suggestion-engine.ts +0 -357
  141. package/src/geo/geo-content.test.ts +0 -305
  142. package/src/geo/geo-content.ts +0 -266
  143. package/src/geo/geo-history.test.ts +0 -473
  144. package/src/geo/geo-history.ts +0 -433
  145. package/src/geo/geo-tracker.test.ts +0 -359
  146. package/src/geo/geo-tracker.ts +0 -411
  147. package/src/geo/index.ts +0 -11
  148. package/src/geo/llm-citation-checker.ts +0 -188
  149. package/src/git/commit-helper.test.ts +0 -261
  150. package/src/git/commit-helper.ts +0 -329
  151. package/src/git/index.ts +0 -12
  152. package/src/git/pr-helper.test.ts +0 -284
  153. package/src/git/pr-helper.ts +0 -307
  154. package/src/index.ts +0 -76
  155. package/src/keywords/ai-keyword-engine.ts +0 -1062
  156. package/src/keywords/ai-summarizer.ts +0 -387
  157. package/src/keywords/ci-mode.ts +0 -555
  158. package/src/keywords/engine.ts +0 -359
  159. package/src/keywords/index.ts +0 -151
  160. package/src/keywords/llm-judge.ts +0 -357
  161. package/src/keywords/nlp-analysis.ts +0 -706
  162. package/src/keywords/prioritizer.ts +0 -295
  163. package/src/keywords/site-crawler.ts +0 -342
  164. package/src/keywords/sources/autocomplete.ts +0 -139
  165. package/src/keywords/sources/competitive-search.ts +0 -450
  166. package/src/keywords/sources/competitor-analysis.ts +0 -374
  167. package/src/keywords/sources/dataforseo.ts +0 -206
  168. package/src/keywords/sources/free-sources.ts +0 -294
  169. package/src/keywords/sources/gsc.ts +0 -123
  170. package/src/keywords/topic-grouping.ts +0 -327
  171. package/src/keywords/types.ts +0 -144
  172. package/src/keywords/wizard.ts +0 -457
  173. package/src/loader.ts +0 -40
  174. package/src/ranking/index.ts +0 -5
  175. package/src/ranking/serp-client.ts +0 -348
  176. package/src/ranking/tracker.ts +0 -380
  177. package/src/ranking/types.ts +0 -123
  178. package/src/reports/index.ts +0 -7
  179. package/src/reports/report-generator.test.ts +0 -293
  180. package/src/reports/report-generator.ts +0 -713
  181. package/src/scheduler/alerts.test.ts +0 -458
  182. package/src/scheduler/alerts.ts +0 -328
  183. package/src/scheduler/index.ts +0 -8
  184. package/src/scheduler/scheduled-audit.test.ts +0 -377
  185. package/src/scheduler/scheduled-audit.ts +0 -149
  186. package/src/test/integration-test.ts +0 -325
  187. package/src/tools/analyzer.ts +0 -373
  188. package/src/tools/crawl.ts +0 -293
  189. package/src/tools/files.ts +0 -301
  190. package/src/tools/h1-fixer.ts +0 -249
  191. package/src/tools/index.ts +0 -67
  192. package/src/tracking/github-action.ts +0 -326
  193. package/src/tracking/google-analytics.ts +0 -265
  194. package/src/tracking/index.ts +0 -45
  195. package/src/tracking/report-generator.ts +0 -386
  196. package/src/tracking/search-console.ts +0 -335
  197. package/src/types.ts +0 -148
  198. package/src/utils/http.ts +0 -302
  199. package/src/wasm-adapter.ts +0 -297
  200. package/src/wasm-entry.ts +0 -14
  201. package/tsconfig.json +0 -17
  202. package/tsup.wasm.config.ts +0 -26
  203. package/vitest.config.ts +0 -15
@@ -1,108 +0,0 @@
1
- /**
2
- * Agent Experience (AX) readiness
3
- *
4
- * Classic GEO asks "can an AI crawler read this page and cite it in an
5
- * answer?" — that's what ai-readiness.ts covers. AX asks a different
6
- * question: "can an AI AGENT (not just a chatbot) discover what this site
7
- * can DO and act on it?" — a distinct, newer discipline (named tooling in
8
- * this space: AgentGrade and similar agent-readiness scanners, 2026).
9
- *
10
- * This checks for the machine-readable discovery surface an agent looks
11
- * for before it can act: llms-full.txt (the fuller, more-fetched sibling
12
- * of llms.txt), a SKILL.md capability manifest, a discoverable MCP server,
13
- * and an OpenAPI spec. None of these are required — most sites won't have
14
- * any of them yet, this is genuinely emerging — so absence is reported as
15
- * a low-severity opportunity, not a failure.
16
- */
17
-
18
- import { httpGet } from '../../utils/http.js';
19
- import type { AuditIssue } from '../types.js';
20
- import { ISSUE_DEFINITIONS } from '../types.js';
21
-
22
- export interface AgentExperienceSignal {
23
- path: string;
24
- present: boolean;
25
- description: string;
26
- }
27
-
28
- export interface AgentExperienceData {
29
- signals: AgentExperienceSignal[];
30
- score: number; // 0-100, percentage of signals present
31
- }
32
-
33
- const SIGNAL_PATHS: Array<{ path: string; description: string }> = [
34
- { path: '/llms-full.txt', description: 'Full-site content dump for AI agents — the fuller sibling of llms.txt, measured as fetched roughly 2x more often' },
35
- { path: '/skill.md', description: 'SKILL.md capability manifest (Anthropic Agent Skills convention)' },
36
- { path: '/mcp', description: 'MCP (Model Context Protocol) server, discoverable at a conventional path' },
37
- { path: '/.well-known/mcp.json', description: 'MCP server descriptor at the .well-known convention' },
38
- { path: '/openapi.json', description: 'OpenAPI spec — lets an agent discover and call your API directly instead of scraping HTML' },
39
- { path: '/.well-known/ai-plugin.json', description: 'AI plugin manifest (older but still-referenced convention for agent tool discovery)' },
40
- ];
41
-
42
- async function fetchBody(baseUrl: string, path: string): Promise<{ status: number; body: string } | null> {
43
- try {
44
- const url = new URL(path, baseUrl).href;
45
- const response = await httpGet<string>(url, {
46
- timeout: 8000,
47
- validateStatus: () => true,
48
- });
49
- return { status: response.status, body: String(response.data ?? '') };
50
- } catch {
51
- return null;
52
- }
53
- }
54
-
55
- /**
56
- * Many sites — anything client-side-routed (React/Vue/etc. SPAs in
57
- * particular) — serve their app shell with HTTP 200 for EVERY unmatched
58
- * path rather than a real 404. A naive "status 200 = found" check would
59
- * report every single signal as present on such a site, which is worse
60
- * than not checking at all — actively misleading rather than merely
61
- * incomplete. Fetching one definitely-nonexistent path first and
62
- * comparing every signal's response against it catches this: identical
63
- * bytes back means "this is the fallback shell," not a real resource,
64
- * regardless of what status code came back with it.
65
- */
66
- async function pathExists(baseUrl: string, path: string, fallbackBody: string | null): Promise<boolean> {
67
- const result = await fetchBody(baseUrl, path);
68
- if (!result) return false;
69
- if (result.status === 404) return false;
70
- if (result.status < 200 || result.status >= 400) return false;
71
- if (fallbackBody != null && result.body === fallbackBody) return false;
72
- return true;
73
- }
74
-
75
- export async function checkAgentExperience(
76
- baseUrl: string
77
- ): Promise<{ issues: AuditIssue[]; data: AgentExperienceData }> {
78
- const issues: AuditIssue[] = [];
79
-
80
- const probePath = `/__rankcli_ax_probe_${Math.random().toString(36).slice(2)}`;
81
- const baseline = await fetchBody(baseUrl, probePath);
82
- const fallbackBody = baseline && baseline.status >= 200 && baseline.status < 400 ? baseline.body : null;
83
-
84
- const signals = await Promise.all(
85
- SIGNAL_PATHS.map(async ({ path, description }) => ({
86
- path,
87
- description,
88
- present: await pathExists(baseUrl, path, fallbackBody),
89
- }))
90
- );
91
-
92
- const presentCount = signals.filter((s) => s.present).length;
93
- const score = Math.round((presentCount / signals.length) * 100);
94
-
95
- // Only worth flagging when a site has essentially no agent-facing
96
- // discovery surface at all — this is a forward-looking opportunity
97
- // check, not a compliance requirement, so it stays low-severity and
98
- // only fires below a meaningful threshold.
99
- if (presentCount === 0) {
100
- issues.push({
101
- ...ISSUE_DEFINITIONS.NO_AGENT_EXPERIENCE_SURFACE,
102
- affectedUrls: [baseUrl],
103
- details: { checkedPaths: SIGNAL_PATHS.map((s) => s.path) },
104
- });
105
- }
106
-
107
- return { issues, data: { signals, score } };
108
- }
@@ -1,399 +0,0 @@
1
- /**
2
- * AI Citation Worthiness Checks
3
- *
4
- * AI systems prefer to cite content that demonstrates originality and
5
- * human expertise - things AI can't easily fake. This check analyzes
6
- * signals that make content worthy of being cited as a source.
7
- *
8
- * Key insight: "If AI can easily create it, don't publish it.
9
- * Add what AI can't fake: original data, real experience, real
10
- * screenshots, real documentation, and real opinions."
11
- */
12
-
13
- import * as cheerio from 'cheerio';
14
- import type { AuditIssue } from '../types.js';
15
-
16
- export interface AICitationWorthinessData {
17
- originalitySignals: {
18
- hasOriginalData: boolean;
19
- hasScreenshots: boolean;
20
- hasOriginalImages: boolean;
21
- hasDatedContent: boolean;
22
- hasFirstPersonExperience: boolean;
23
- };
24
- expertiseSignals: {
25
- hasAuthorByline: boolean;
26
- hasAuthorCredentials: boolean;
27
- hasExpertQuotes: boolean;
28
- hasMethodology: boolean;
29
- hasPrimaryResearch: boolean;
30
- };
31
- proofSignals: {
32
- hasSpecificNumbers: boolean;
33
- hasCaseStudies: boolean;
34
- hasBeforeAfter: boolean;
35
- hasDocumentation: boolean;
36
- hasRealExamples: boolean;
37
- };
38
- citationWorthinessScore: number;
39
- aiProofElements: string[];
40
- }
41
-
42
- // Patterns indicating original data/research
43
- const ORIGINAL_DATA_PATTERNS = [
44
- /(?:our data|our research|our study|our analysis|we found|we discovered)/i,
45
- /(?:survey of|surveyed|interviewed|analyzed)\s+\d+/i,
46
- /(?:based on|from)\s+\d+[,\d]*\s+(?:data points|samples|responses|users|customers)/i,
47
- /(?:internal data|proprietary data|our database)/i,
48
- ];
49
-
50
- // Patterns indicating first-person experience
51
- const EXPERIENCE_PATTERNS = [
52
- /(?:in my experience|from my experience|i've found|i've seen|i've learned)/i,
53
- /(?:when i|after i|before i|i personally|i tested|i tried|i used)/i,
54
- /(?:our team|we tested|we tried|we implemented|we built)/i,
55
- /(?:case study|real example|actual example|here's what happened)/i,
56
- ];
57
-
58
- // Patterns indicating expert quotes
59
- const EXPERT_QUOTE_PATTERNS = [
60
- /(?:according to|says|said|explains|notes)\s+[A-Z][a-z]+\s+[A-Z][a-z]+/,
61
- /"[^"]{20,}"\s*[-–—]\s*[A-Z]/,
62
- /[A-Z][a-z]+\s+[A-Z][a-z]+,?\s+(?:CEO|CTO|founder|director|manager|expert|analyst|researcher)/i,
63
- ];
64
-
65
- // Patterns for specific numbers (not generic)
66
- const SPECIFIC_NUMBER_PATTERNS = [
67
- /\$[\d,]+(?:\.\d{2})?(?:\s*(?:million|billion|k|m|b))?/i, // Dollar amounts
68
- /\d+(?:\.\d+)?%/, // Percentages
69
- /\d{1,2}\/\d{1,2}\/\d{2,4}/, // Dates
70
- /(?:in|since|from)\s+(?:19|20)\d{2}/, // Years
71
- /\d+(?:,\d{3})+/, // Large numbers with commas
72
- /(?:increased|decreased|grew|dropped)\s+(?:by\s+)?\d+/i,
73
- ];
74
-
75
- // Stock image domains to detect
76
- const STOCK_IMAGE_DOMAINS = [
77
- 'unsplash.com', 'pexels.com', 'shutterstock.com', 'istockphoto.com',
78
- 'gettyimages.com', 'stock.adobe.com', 'depositphotos.com', 'pixabay.com',
79
- 'freepik.com', 'rawpixel.com', '123rf.com', 'stocksy.com',
80
- ];
81
-
82
- export function analyzeAICitationWorthiness(
83
- html: string,
84
- url: string
85
- ): { issues: AuditIssue[]; data: AICitationWorthinessData } {
86
- const issues: AuditIssue[] = [];
87
- const $ = cheerio.load(html);
88
-
89
- // Remove non-content elements
90
- const $content = $('body').clone();
91
- $content.find('nav, footer, script, style, noscript, aside').remove();
92
- const bodyText = $content.text();
93
-
94
- const aiProofElements: string[] = [];
95
-
96
- // === ORIGINALITY SIGNALS ===
97
-
98
- // Check for original data
99
- let hasOriginalData = false;
100
- for (const pattern of ORIGINAL_DATA_PATTERNS) {
101
- if (pattern.test(bodyText)) {
102
- hasOriginalData = true;
103
- aiProofElements.push('Original data/research');
104
- break;
105
- }
106
- }
107
-
108
- // Check for screenshots
109
- const images = $content.find('img');
110
- let hasScreenshots = false;
111
- let hasOriginalImages = false;
112
- let stockImageCount = 0;
113
- let totalImages = 0;
114
-
115
- images.each((_, img) => {
116
- const src = $(img).attr('src') || '';
117
- const alt = $(img).attr('alt') || '';
118
- const srcLower = src.toLowerCase();
119
- const altLower = alt.toLowerCase();
120
- totalImages++;
121
-
122
- // Check for screenshot patterns
123
- if (
124
- srcLower.includes('screenshot') ||
125
- srcLower.includes('screen-') ||
126
- altLower.includes('screenshot') ||
127
- srcLower.includes('capture') ||
128
- /screen[_-]?\d+/i.test(src)
129
- ) {
130
- hasScreenshots = true;
131
- aiProofElements.push('Screenshots');
132
- }
133
-
134
- // Check for stock images
135
- const isStockImage = STOCK_IMAGE_DOMAINS.some(domain => srcLower.includes(domain));
136
- if (isStockImage) {
137
- stockImageCount++;
138
- }
139
- });
140
-
141
- // If most images aren't stock photos, consider them original
142
- if (totalImages > 0 && stockImageCount < totalImages * 0.5) {
143
- hasOriginalImages = true;
144
- if (totalImages >= 3 && stockImageCount === 0) {
145
- aiProofElements.push('Original images');
146
- }
147
- }
148
-
149
- // Check for dated content (shows real experience over time)
150
- const datePatterns = [
151
- /(?:updated|published|written|posted)\s+(?:on\s+)?(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)/i,
152
- /(?:last updated|originally published):\s*\d/i,
153
- /<time[^>]*datetime/i,
154
- ];
155
- const hasDatedContent = datePatterns.some(p => p.test(html));
156
- if (hasDatedContent) {
157
- aiProofElements.push('Dated/timestamped content');
158
- }
159
-
160
- // Check for first-person experience
161
- let hasFirstPersonExperience = false;
162
- for (const pattern of EXPERIENCE_PATTERNS) {
163
- if (pattern.test(bodyText)) {
164
- hasFirstPersonExperience = true;
165
- aiProofElements.push('First-person experience');
166
- break;
167
- }
168
- }
169
-
170
- // === EXPERTISE SIGNALS ===
171
-
172
- // Check for author byline
173
- const hasAuthorByline =
174
- $('[class*="author"], [rel="author"], [itemprop="author"], .byline').length > 0 ||
175
- /by\s+[A-Z][a-z]+\s+[A-Z][a-z]+/i.test(bodyText);
176
-
177
- // Check for author credentials
178
- const credentialPatterns = [
179
- /(?:PhD|Ph\.D|MD|M\.D|MBA|CPA|JD|J\.D)/,
180
- /(?:certified|licensed|accredited)\s+\w+/i,
181
- /\d+\+?\s*years?\s+(?:of\s+)?experience/i,
182
- /(?:expert|specialist|professional)\s+in/i,
183
- ];
184
- const hasAuthorCredentials = credentialPatterns.some(p => p.test(bodyText));
185
- if (hasAuthorCredentials) {
186
- aiProofElements.push('Expert credentials');
187
- }
188
-
189
- // Check for expert quotes
190
- let hasExpertQuotes = false;
191
- for (const pattern of EXPERT_QUOTE_PATTERNS) {
192
- if (pattern.test(bodyText)) {
193
- hasExpertQuotes = true;
194
- aiProofElements.push('Expert quotes');
195
- break;
196
- }
197
- }
198
-
199
- // Check for methodology section
200
- const hasMethodology =
201
- /(?:methodology|method|approach|how we|our process)/i.test(
202
- $('h2, h3, h4').text()
203
- ) ||
204
- /(?:we measured|we calculated|we analyzed|data was collected)/i.test(bodyText);
205
-
206
- // Check for primary research indicators
207
- const hasPrimaryResearch =
208
- /(?:original research|primary research|we conducted|our study found)/i.test(bodyText) ||
209
- /(?:sample size|n\s*=\s*\d+|participants|respondents)/i.test(bodyText);
210
-
211
- if (hasPrimaryResearch) {
212
- aiProofElements.push('Primary research');
213
- }
214
-
215
- // === PROOF SIGNALS ===
216
-
217
- // Check for specific numbers
218
- let specificNumberCount = 0;
219
- for (const pattern of SPECIFIC_NUMBER_PATTERNS) {
220
- const matches = bodyText.match(pattern);
221
- if (matches) {
222
- specificNumberCount += matches.length;
223
- }
224
- }
225
- const hasSpecificNumbers = specificNumberCount >= 5;
226
- if (hasSpecificNumbers) {
227
- aiProofElements.push('Specific data points');
228
- }
229
-
230
- // Check for case studies
231
- const hasCaseStudies =
232
- /(?:case study|case studies|success story|client story)/i.test(bodyText) ||
233
- $('[class*="case-study"], [id*="case-study"]').length > 0;
234
-
235
- if (hasCaseStudies) {
236
- aiProofElements.push('Case studies');
237
- }
238
-
239
- // Check for before/after comparisons
240
- const hasBeforeAfter =
241
- /(?:before and after|before\/after|before vs|the results)/i.test(bodyText) ||
242
- $('[class*="before-after"], [class*="comparison"]').length > 0;
243
-
244
- // Check for documentation/code examples
245
- const hasDocumentation =
246
- $('pre code, .highlight, .code-block').length > 0 ||
247
- /```[\s\S]*?```/.test(bodyText) ||
248
- $('[class*="documentation"]').length > 0;
249
-
250
- if (hasDocumentation) {
251
- aiProofElements.push('Code/documentation');
252
- }
253
-
254
- // Check for real examples
255
- const hasRealExamples =
256
- /(?:for example|here's an example|real example|actual example|let me show)/i.test(bodyText) ||
257
- /(?:example:|e\.g\.,|such as:)/i.test(bodyText);
258
-
259
- // === CALCULATE SCORE ===
260
- let citationWorthinessScore = 20; // Base score
261
-
262
- // Originality signals (up to 35 points)
263
- if (hasOriginalData) citationWorthinessScore += 15;
264
- if (hasScreenshots) citationWorthinessScore += 5;
265
- if (hasOriginalImages) citationWorthinessScore += 5;
266
- if (hasDatedContent) citationWorthinessScore += 5;
267
- if (hasFirstPersonExperience) citationWorthinessScore += 5;
268
-
269
- // Expertise signals (up to 25 points)
270
- if (hasAuthorByline) citationWorthinessScore += 5;
271
- if (hasAuthorCredentials) citationWorthinessScore += 10;
272
- if (hasExpertQuotes) citationWorthinessScore += 5;
273
- if (hasMethodology) citationWorthinessScore += 5;
274
- if (hasPrimaryResearch) citationWorthinessScore += 10;
275
-
276
- // Proof signals (up to 20 points)
277
- if (hasSpecificNumbers) citationWorthinessScore += 5;
278
- if (hasCaseStudies) citationWorthinessScore += 5;
279
- if (hasBeforeAfter) citationWorthinessScore += 3;
280
- if (hasDocumentation) citationWorthinessScore += 4;
281
- if (hasRealExamples) citationWorthinessScore += 3;
282
-
283
- citationWorthinessScore = Math.min(100, Math.max(0, citationWorthinessScore));
284
-
285
- // === GENERATE ISSUES ===
286
-
287
- // No original data or research
288
- if (!hasOriginalData && !hasPrimaryResearch) {
289
- issues.push({
290
- code: 'AI_NO_ORIGINAL_DATA',
291
- severity: 'notice',
292
- category: 'ai-readiness',
293
- title: 'No original data or research signals',
294
- description: 'Content lacks original data, surveys, or research findings. AI prefers citing sources with unique, first-party data.',
295
- impact: 'Generic content without original data is less likely to be cited as a source.',
296
- howToFix: 'Add original data: survey results, internal analytics, case study numbers, or primary research findings. Use phrases like "Our data shows..." or "We surveyed X users...".',
297
- affectedUrls: [url],
298
- });
299
- }
300
-
301
- // No first-person experience
302
- if (!hasFirstPersonExperience && !hasCaseStudies) {
303
- issues.push({
304
- code: 'AI_NO_EXPERIENCE_SIGNALS',
305
- severity: 'notice',
306
- category: 'ai-readiness',
307
- title: 'No first-hand experience signals',
308
- description: 'Content reads like it could be AI-generated. No "I tested...", "We implemented...", or case study language found.',
309
- impact: 'AI may prefer sources that demonstrate real-world experience over generic explanations.',
310
- howToFix: 'Add personal experience: "In my experience...", "When we tested...", "Here\'s what happened when...". Include specific scenarios you\'ve encountered.',
311
- affectedUrls: [url],
312
- });
313
- }
314
-
315
- // Stock images detected
316
- if (stockImageCount > 0 && stockImageCount >= totalImages * 0.5) {
317
- issues.push({
318
- code: 'AI_STOCK_IMAGE_HEAVY',
319
- severity: 'notice',
320
- category: 'ai-readiness',
321
- title: 'Heavy use of stock images',
322
- description: `${stockImageCount} of ${totalImages} images appear to be from stock photo sites. Original visuals signal authenticity.`,
323
- impact: 'Stock-heavy content appears less authentic and expert to both users and AI.',
324
- howToFix: 'Replace stock photos with original screenshots, diagrams, photos, or custom graphics that AI cannot easily replicate.',
325
- affectedUrls: [url],
326
- details: {
327
- stockImageCount,
328
- totalImages,
329
- },
330
- });
331
- }
332
-
333
- // No author credentials
334
- if (!hasAuthorByline && !hasAuthorCredentials) {
335
- issues.push({
336
- code: 'AI_NO_AUTHOR_EXPERTISE',
337
- severity: 'notice',
338
- category: 'ai-readiness',
339
- title: 'No author expertise signals',
340
- description: 'No author byline or credentials found. AI values content from identifiable experts.',
341
- impact: 'Anonymous content is less trustworthy for AI citation purposes.',
342
- howToFix: 'Add author byline with credentials: name, title, years of experience, certifications, or relevant expertise.',
343
- affectedUrls: [url],
344
- });
345
- }
346
-
347
- // Low citation worthiness
348
- if (citationWorthinessScore < 40) {
349
- issues.push({
350
- code: 'AI_LOW_CITATION_WORTHINESS',
351
- severity: 'warning',
352
- category: 'ai-readiness',
353
- title: 'Content lacks AI-proof originality signals',
354
- description: `Citation worthiness score: ${citationWorthinessScore}/100. Content appears generic and could be easily replicated by AI.`,
355
- impact: 'AI may not cite this content as it lacks unique value that AI cannot generate itself.',
356
- howToFix: 'Add what AI can\'t fake: original data, real screenshots, dated experiences, expert quotes, case studies, and specific proof points.',
357
- affectedUrls: [url],
358
- details: {
359
- citationWorthinessScore,
360
- aiProofElements: aiProofElements.length > 0 ? aiProofElements : ['None detected'],
361
- missing: [
362
- !hasOriginalData && 'Original data/research',
363
- !hasFirstPersonExperience && 'First-person experience',
364
- !hasAuthorCredentials && 'Expert credentials',
365
- !hasSpecificNumbers && 'Specific data points',
366
- ].filter(Boolean),
367
- },
368
- });
369
- }
370
-
371
- return {
372
- issues,
373
- data: {
374
- originalitySignals: {
375
- hasOriginalData,
376
- hasScreenshots,
377
- hasOriginalImages,
378
- hasDatedContent,
379
- hasFirstPersonExperience,
380
- },
381
- expertiseSignals: {
382
- hasAuthorByline,
383
- hasAuthorCredentials,
384
- hasExpertQuotes,
385
- hasMethodology,
386
- hasPrimaryResearch,
387
- },
388
- proofSignals: {
389
- hasSpecificNumbers,
390
- hasCaseStudies,
391
- hasBeforeAfter,
392
- hasDocumentation,
393
- hasRealExamples,
394
- },
395
- citationWorthinessScore,
396
- aiProofElements,
397
- },
398
- };
399
- }