@rankcli/agent-runtime 0.0.14 → 0.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +34 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +269 -75
- package/dist/index.mjs +238 -45
- package/package.json +1 -1
- package/src/audit/checks/agent-experience.ts +108 -0
- package/src/audit/checks/ai-readiness.ts +67 -0
- package/src/audit/checks/rag-chunk-readiness.test.ts +159 -0
- package/src/audit/checks/rag-chunk-readiness.ts +163 -0
- package/src/audit/engine.ts +11 -5
- package/src/audit/types.ts +24 -0
package/dist/index.mjs
CHANGED
|
@@ -919,6 +919,24 @@ var ISSUE_DEFINITIONS = {
|
|
|
919
919
|
impact: "Your content will not be used for Bard/Gemini AI training (regular search unaffected).",
|
|
920
920
|
howToFix: 'Remove "User-agent: Google-Extended Disallow: /" if you want Google AI visibility.'
|
|
921
921
|
},
|
|
922
|
+
CLOUDFLARE_AI_CRAWLER_GATE_AMBIGUOUS: {
|
|
923
|
+
code: "CLOUDFLARE_AI_CRAWLER_GATE_AMBIGUOUS",
|
|
924
|
+
severity: "warning",
|
|
925
|
+
category: "ai-readiness",
|
|
926
|
+
title: "No explicit AI crawler rules on a Cloudflare-fronted site",
|
|
927
|
+
description: 'This site appears to be served through Cloudflare but robots.txt has no explicit Allow/Disallow rules for AI crawlers. Cloudflare blocks "mixed-use" AI crawlers by default on ad-hosting zones starting September 15, 2026, and is rolling out Pay Per Crawl / Pay Per Use gating beyond that.',
|
|
928
|
+
impact: "Without an explicit rule, whether AI crawlers (and future citation opportunities in ChatGPT, Claude, Perplexity, etc.) can reach this site now depends on Cloudflare account-level bot-management defaults, not on this codebase \u2014 a silent, invisible-to-git failure mode.",
|
|
929
|
+
howToFix: "Add explicit User-agent rules for GPTBot, Claude-Web/ClaudeBot, PerplexityBot, and Google-Extended in robots.txt, and confirm the matching allow/block posture in the Cloudflare dashboard under Bot Management \u2192 AI Crawl Control. If you'd rather charge than block, Cloudflare's Pay Per Crawl (AI Crawl Control \u2192 Payments tab) lets you set a per-crawl price instead of a flat allow/deny \u2014 worth a look if you get meaningful AI-crawler traffic."
|
|
930
|
+
},
|
|
931
|
+
NO_AGENT_EXPERIENCE_SURFACE: {
|
|
932
|
+
code: "NO_AGENT_EXPERIENCE_SURFACE",
|
|
933
|
+
severity: "notice",
|
|
934
|
+
category: "ai-readiness",
|
|
935
|
+
title: "No agent-facing discovery surface found",
|
|
936
|
+
description: "None of the emerging AI-agent discovery conventions were found: llms-full.txt, SKILL.md, a discoverable MCP server, or an OpenAPI spec. This is a genuinely new, low-adoption category as of 2026 \u2014 most sites have none of these yet \u2014 so this is an opportunity, not a compliance failure.",
|
|
937
|
+
impact: "AI agents (not just chatbots answering questions, but agents that browse and act on a user's behalf) increasingly prefer sites that expose machine-readable capabilities over ones that require scraping and guessing at HTML structure. Being an early, discoverable site in this space is a low-competition differentiator right now.",
|
|
938
|
+
howToFix: "Start with whichever fits your site: llms-full.txt (a single Markdown dump of your key content \u2014 measured as fetched roughly 2x more than plain llms.txt), a SKILL.md capability manifest, or an OpenAPI spec at /openapi.json if you already have an API. A discoverable MCP server is the highest-effort, highest-payoff option if your product has one to expose."
|
|
939
|
+
},
|
|
922
940
|
HIGH_JS_RENDERING_RATIO: {
|
|
923
941
|
code: "HIGH_JS_RENDERING_RATIO",
|
|
924
942
|
severity: "warning",
|
|
@@ -3854,6 +3872,61 @@ async function checkInternalRedirects(internalLinks, batchSize = 10) {
|
|
|
3854
3872
|
|
|
3855
3873
|
// src/audit/checks/ai-readiness.ts
|
|
3856
3874
|
import * as cheerio13 from "cheerio";
|
|
3875
|
+
|
|
3876
|
+
// src/audit/checks/agent-experience.ts
|
|
3877
|
+
var SIGNAL_PATHS = [
|
|
3878
|
+
{ path: "/llms-full.txt", description: "Full-site content dump for AI agents \u2014 the fuller sibling of llms.txt, measured as fetched roughly 2x more often" },
|
|
3879
|
+
{ path: "/skill.md", description: "SKILL.md capability manifest (Anthropic Agent Skills convention)" },
|
|
3880
|
+
{ path: "/mcp", description: "MCP (Model Context Protocol) server, discoverable at a conventional path" },
|
|
3881
|
+
{ path: "/.well-known/mcp.json", description: "MCP server descriptor at the .well-known convention" },
|
|
3882
|
+
{ path: "/openapi.json", description: "OpenAPI spec \u2014 lets an agent discover and call your API directly instead of scraping HTML" },
|
|
3883
|
+
{ path: "/.well-known/ai-plugin.json", description: "AI plugin manifest (older but still-referenced convention for agent tool discovery)" }
|
|
3884
|
+
];
|
|
3885
|
+
async function fetchBody(baseUrl, path3) {
|
|
3886
|
+
try {
|
|
3887
|
+
const url = new URL(path3, baseUrl).href;
|
|
3888
|
+
const response = await httpGet(url, {
|
|
3889
|
+
timeout: 8e3,
|
|
3890
|
+
validateStatus: () => true
|
|
3891
|
+
});
|
|
3892
|
+
return { status: response.status, body: String(response.data ?? "") };
|
|
3893
|
+
} catch {
|
|
3894
|
+
return null;
|
|
3895
|
+
}
|
|
3896
|
+
}
|
|
3897
|
+
async function pathExists(baseUrl, path3, fallbackBody) {
|
|
3898
|
+
const result = await fetchBody(baseUrl, path3);
|
|
3899
|
+
if (!result) return false;
|
|
3900
|
+
if (result.status === 404) return false;
|
|
3901
|
+
if (result.status < 200 || result.status >= 400) return false;
|
|
3902
|
+
if (fallbackBody != null && result.body === fallbackBody) return false;
|
|
3903
|
+
return true;
|
|
3904
|
+
}
|
|
3905
|
+
async function checkAgentExperience(baseUrl) {
|
|
3906
|
+
const issues = [];
|
|
3907
|
+
const probePath = `/__rankcli_ax_probe_${Math.random().toString(36).slice(2)}`;
|
|
3908
|
+
const baseline = await fetchBody(baseUrl, probePath);
|
|
3909
|
+
const fallbackBody = baseline && baseline.status >= 200 && baseline.status < 400 ? baseline.body : null;
|
|
3910
|
+
const signals = await Promise.all(
|
|
3911
|
+
SIGNAL_PATHS.map(async ({ path: path3, description }) => ({
|
|
3912
|
+
path: path3,
|
|
3913
|
+
description,
|
|
3914
|
+
present: await pathExists(baseUrl, path3, fallbackBody)
|
|
3915
|
+
}))
|
|
3916
|
+
);
|
|
3917
|
+
const presentCount = signals.filter((s) => s.present).length;
|
|
3918
|
+
const score = Math.round(presentCount / signals.length * 100);
|
|
3919
|
+
if (presentCount === 0) {
|
|
3920
|
+
issues.push({
|
|
3921
|
+
...ISSUE_DEFINITIONS.NO_AGENT_EXPERIENCE_SURFACE,
|
|
3922
|
+
affectedUrls: [baseUrl],
|
|
3923
|
+
details: { checkedPaths: SIGNAL_PATHS.map((s) => s.path) }
|
|
3924
|
+
});
|
|
3925
|
+
}
|
|
3926
|
+
return { issues, data: { signals, score } };
|
|
3927
|
+
}
|
|
3928
|
+
|
|
3929
|
+
// src/audit/checks/ai-readiness.ts
|
|
3857
3930
|
var AI_BOTS = {
|
|
3858
3931
|
GPTBot: "GPTBot",
|
|
3859
3932
|
"ChatGPT-User": "ChatGPT-User",
|
|
@@ -4074,6 +4147,32 @@ function checkJSRenderingRatio(html, url) {
|
|
|
4074
4147
|
data: { ratio, staticWordCount }
|
|
4075
4148
|
};
|
|
4076
4149
|
}
|
|
4150
|
+
async function checkCloudflareAICrawlerGate(baseUrl, botBlocking) {
|
|
4151
|
+
const issues = [];
|
|
4152
|
+
let behindCloudflare = false;
|
|
4153
|
+
try {
|
|
4154
|
+
const response = await httpGet(baseUrl, {
|
|
4155
|
+
timeout: 1e4,
|
|
4156
|
+
validateStatus: () => true
|
|
4157
|
+
});
|
|
4158
|
+
const server = response.headers["server"] || "";
|
|
4159
|
+
behindCloudflare = server.toLowerCase().includes("cloudflare") || "cf-ray" in response.headers;
|
|
4160
|
+
} catch {
|
|
4161
|
+
behindCloudflare = false;
|
|
4162
|
+
}
|
|
4163
|
+
const hasExplicitAIRules = botBlocking.robotsExists && botBlocking.blockedBots.length > 0;
|
|
4164
|
+
const ambiguous = behindCloudflare && !hasExplicitAIRules;
|
|
4165
|
+
if (ambiguous) {
|
|
4166
|
+
issues.push({
|
|
4167
|
+
...ISSUE_DEFINITIONS.CLOUDFLARE_AI_CRAWLER_GATE_AMBIGUOUS,
|
|
4168
|
+
affectedUrls: [baseUrl]
|
|
4169
|
+
});
|
|
4170
|
+
}
|
|
4171
|
+
return {
|
|
4172
|
+
issues,
|
|
4173
|
+
data: { behindCloudflare, hasExplicitAIRules, ambiguous }
|
|
4174
|
+
};
|
|
4175
|
+
}
|
|
4077
4176
|
async function runAIReadinessChecks(baseUrl, html) {
|
|
4078
4177
|
const allIssues = [];
|
|
4079
4178
|
const llmsResult = await checkLlmsTxt(baseUrl);
|
|
@@ -4082,12 +4181,18 @@ async function runAIReadinessChecks(baseUrl, html) {
|
|
|
4082
4181
|
allIssues.push(...botResult.issues);
|
|
4083
4182
|
const jsResult = checkJSRenderingRatio(html, baseUrl);
|
|
4084
4183
|
allIssues.push(...jsResult.issues);
|
|
4184
|
+
const cfGateResult = await checkCloudflareAICrawlerGate(baseUrl, botResult.data);
|
|
4185
|
+
allIssues.push(...cfGateResult.issues);
|
|
4186
|
+
const agentExperienceResult = await checkAgentExperience(baseUrl);
|
|
4187
|
+
allIssues.push(...agentExperienceResult.issues);
|
|
4085
4188
|
return {
|
|
4086
4189
|
issues: allIssues,
|
|
4087
4190
|
data: {
|
|
4088
4191
|
llmsTxt: llmsResult.data,
|
|
4089
4192
|
botBlocking: botResult.data,
|
|
4090
|
-
jsRenderingRatio: jsResult.data.ratio
|
|
4193
|
+
jsRenderingRatio: jsResult.data.ratio,
|
|
4194
|
+
cloudflareAIGate: cfGateResult.data,
|
|
4195
|
+
agentExperience: agentExperienceResult.data
|
|
4091
4196
|
}
|
|
4092
4197
|
};
|
|
4093
4198
|
}
|
|
@@ -12534,8 +12639,94 @@ function analyzeAIContentStructure(html, url) {
|
|
|
12534
12639
|
};
|
|
12535
12640
|
}
|
|
12536
12641
|
|
|
12537
|
-
// src/audit/checks/
|
|
12642
|
+
// src/audit/checks/rag-chunk-readiness.ts
|
|
12538
12643
|
import * as cheerio44 from "cheerio";
|
|
12644
|
+
var MIN_GOOD_WORDS = 150;
|
|
12645
|
+
var MAX_GOOD_WORDS = 450;
|
|
12646
|
+
var TOKENS_PER_WORD = 1.33;
|
|
12647
|
+
var DANGLING_OPENERS = /^(this|it|these|that|they|such|those|the former|the latter)\b/i;
|
|
12648
|
+
function classifySize(wordCount) {
|
|
12649
|
+
if (wordCount < MIN_GOOD_WORDS) return "too-short";
|
|
12650
|
+
if (wordCount > MAX_GOOD_WORDS) return "too-long";
|
|
12651
|
+
return "good";
|
|
12652
|
+
}
|
|
12653
|
+
function extractSections($) {
|
|
12654
|
+
const elements = $("h1, h2, h3, p, li, blockquote").toArray();
|
|
12655
|
+
const sections = [];
|
|
12656
|
+
let current = null;
|
|
12657
|
+
for (const el of elements) {
|
|
12658
|
+
const tag = el.tagName?.toLowerCase();
|
|
12659
|
+
if (tag === "h1" || tag === "h2" || tag === "h3") {
|
|
12660
|
+
if (current) sections.push(current);
|
|
12661
|
+
current = { heading: $(el).text().trim(), headingLevel: parseInt(tag.slice(1), 10), text: "" };
|
|
12662
|
+
} else if (current) {
|
|
12663
|
+
current.text += " " + $(el).text();
|
|
12664
|
+
}
|
|
12665
|
+
}
|
|
12666
|
+
if (current) sections.push(current);
|
|
12667
|
+
return sections;
|
|
12668
|
+
}
|
|
12669
|
+
function analyzeRAGChunkReadiness(html, url) {
|
|
12670
|
+
const issues = [];
|
|
12671
|
+
const $ = cheerio44.load(html);
|
|
12672
|
+
$("nav, footer, aside, script, style, noscript, header").remove();
|
|
12673
|
+
const rawSections = extractSections($);
|
|
12674
|
+
const sections = rawSections.map((s) => {
|
|
12675
|
+
const words = s.text.trim().split(/\s+/).filter(Boolean);
|
|
12676
|
+
const wordCount = words.length;
|
|
12677
|
+
const estimatedTokens = Math.round(wordCount * TOKENS_PER_WORD);
|
|
12678
|
+
const firstSentence = s.text.trim().split(/[.!?]/)[0] || "";
|
|
12679
|
+
return {
|
|
12680
|
+
heading: s.heading,
|
|
12681
|
+
headingLevel: s.headingLevel,
|
|
12682
|
+
wordCount,
|
|
12683
|
+
estimatedTokens,
|
|
12684
|
+
sizeQuality: classifySize(wordCount),
|
|
12685
|
+
startsWithDanglingReference: DANGLING_OPENERS.test(firstSentence.trim())
|
|
12686
|
+
};
|
|
12687
|
+
});
|
|
12688
|
+
const totalSections = sections.length;
|
|
12689
|
+
const wellSizedSectionCount = sections.filter((s) => s.sizeQuality === "good").length;
|
|
12690
|
+
const danglingReferenceCount = sections.filter((s) => s.startsWithDanglingReference).length;
|
|
12691
|
+
const chunkReadinessScore = totalSections === 0 ? 0 : Math.round(
|
|
12692
|
+
(wellSizedSectionCount / totalSections * 0.7 + (totalSections - danglingReferenceCount) / totalSections * 0.3) * 100
|
|
12693
|
+
);
|
|
12694
|
+
if (totalSections >= 3 && wellSizedSectionCount / totalSections < 0.5) {
|
|
12695
|
+
const tooLong = sections.filter((s) => s.sizeQuality === "too-long").length;
|
|
12696
|
+
const tooShort = sections.filter((s) => s.sizeQuality === "too-short").length;
|
|
12697
|
+
issues.push({
|
|
12698
|
+
code: "RAG_CHUNK_SIZE_MISMATCH",
|
|
12699
|
+
severity: "notice",
|
|
12700
|
+
category: "ai-readiness",
|
|
12701
|
+
title: "Most sections are poorly sized for AI retrieval chunking",
|
|
12702
|
+
description: `${wellSizedSectionCount} of ${totalSections} sections fall in a well-sized range for RAG retrieval (roughly 150-450 words); ${tooLong} are too long and likely to get split mid-thought, ${tooShort} are too short and likely to get merged with unrelated neighbors.`,
|
|
12703
|
+
impact: "When an AI answer engine retrieves a chunk of this page in response to a query, oversized sections risk being cut off mid-point and undersized ones risk losing standalone context \u2014 both reduce the odds the retrieved chunk reads coherently in an AI-generated answer.",
|
|
12704
|
+
howToFix: "Break up long sections (450+ words) with an additional H2/H3 subheading roughly every 300-400 words. Merge very short sections (under 150 words) into a neighboring section or expand them with enough context to stand alone.",
|
|
12705
|
+
affectedUrls: [url],
|
|
12706
|
+
details: { wellSizedSectionCount, totalSections, tooLong, tooShort }
|
|
12707
|
+
});
|
|
12708
|
+
}
|
|
12709
|
+
if (totalSections >= 3 && danglingReferenceCount / totalSections > 0.3) {
|
|
12710
|
+
issues.push({
|
|
12711
|
+
code: "RAG_CHUNK_DANGLING_REFERENCES",
|
|
12712
|
+
severity: "notice",
|
|
12713
|
+
category: "ai-readiness",
|
|
12714
|
+
title: "Several sections open with a reference to prior context",
|
|
12715
|
+
description: `${danglingReferenceCount} of ${totalSections} sections start with a pronoun or demonstrative ("This...", "It...", "These...") that depends on the previous section to make sense.`,
|
|
12716
|
+
impact: "A RAG system that retrieves one of these sections on its own \u2014 which is exactly how retrieval works, one chunk at a time \u2014 surfaces a sentence whose subject is undefined, reading as broken or confusing in an AI-generated answer.",
|
|
12717
|
+
howToFix: 'Open each section by naming its actual subject instead of referring back to the previous one \u2014 e.g. "This approach reduces cost" becomes "Caching reduces cost."',
|
|
12718
|
+
affectedUrls: [url],
|
|
12719
|
+
details: { danglingReferenceCount, totalSections }
|
|
12720
|
+
});
|
|
12721
|
+
}
|
|
12722
|
+
return {
|
|
12723
|
+
issues,
|
|
12724
|
+
data: { sections, totalSections, wellSizedSectionCount, danglingReferenceCount, chunkReadinessScore }
|
|
12725
|
+
};
|
|
12726
|
+
}
|
|
12727
|
+
|
|
12728
|
+
// src/audit/checks/citation-quality.ts
|
|
12729
|
+
import * as cheerio45 from "cheerio";
|
|
12539
12730
|
var REPUTABLE_SOURCES = {
|
|
12540
12731
|
academic: [
|
|
12541
12732
|
"scholar.google.com",
|
|
@@ -12602,7 +12793,7 @@ var REPUTABLE_SOURCES = {
|
|
|
12602
12793
|
};
|
|
12603
12794
|
function analyzeCitationQuality(html, url) {
|
|
12604
12795
|
const issues = [];
|
|
12605
|
-
const $ =
|
|
12796
|
+
const $ = cheerio45.load(html);
|
|
12606
12797
|
const parsedUrl = new URL(url);
|
|
12607
12798
|
const currentDomain = parsedUrl.hostname;
|
|
12608
12799
|
const $content = $("body").clone();
|
|
@@ -12831,12 +13022,12 @@ function analyzeCitationQuality(html, url) {
|
|
|
12831
13022
|
}
|
|
12832
13023
|
|
|
12833
13024
|
// src/audit/checks/answer-conciseness.ts
|
|
12834
|
-
import * as
|
|
13025
|
+
import * as cheerio46 from "cheerio";
|
|
12835
13026
|
var IDEAL_ANSWER_LENGTH = { min: 40, max: 150 };
|
|
12836
13027
|
var MAX_FIRST_SENTENCE = 200;
|
|
12837
13028
|
function analyzeAnswerConciseness(html, url) {
|
|
12838
13029
|
const issues = [];
|
|
12839
|
-
const $ =
|
|
13030
|
+
const $ = cheerio46.load(html);
|
|
12840
13031
|
$("nav, footer, aside, script, style, noscript, header").remove();
|
|
12841
13032
|
const headings = $("h1, h2, h3, h4, h5, h6");
|
|
12842
13033
|
let totalHeadings = 0;
|
|
@@ -13027,7 +13218,7 @@ function analyzeAnswerConciseness(html, url) {
|
|
|
13027
13218
|
}
|
|
13028
13219
|
|
|
13029
13220
|
// src/audit/checks/brand-mention-optimization.ts
|
|
13030
|
-
import * as
|
|
13221
|
+
import * as cheerio47 from "cheerio";
|
|
13031
13222
|
var BRAND_DEFINITION_PATTERNS = [
|
|
13032
13223
|
/(?:we are|we're|is a|is the|is an)\s+(?:leading|premier|top|best|trusted|innovative|professional)/i,
|
|
13033
13224
|
/(?:our mission|our vision|we help|we provide|we offer|we specialize)/i,
|
|
@@ -13065,7 +13256,7 @@ var REVIEW_PLATFORMS = [
|
|
|
13065
13256
|
];
|
|
13066
13257
|
function analyzeBrandMentionOptimization(html, url) {
|
|
13067
13258
|
const issues = [];
|
|
13068
|
-
const $ =
|
|
13259
|
+
const $ = cheerio47.load(html);
|
|
13069
13260
|
const parsedUrl = new URL(url);
|
|
13070
13261
|
const $content = $("body").clone();
|
|
13071
13262
|
$content.find("nav, footer, script, style, noscript").remove();
|
|
@@ -13257,7 +13448,7 @@ function analyzeBrandMentionOptimization(html, url) {
|
|
|
13257
13448
|
}
|
|
13258
13449
|
|
|
13259
13450
|
// src/audit/checks/ai-citation-worthiness.ts
|
|
13260
|
-
import * as
|
|
13451
|
+
import * as cheerio48 from "cheerio";
|
|
13261
13452
|
var ORIGINAL_DATA_PATTERNS = [
|
|
13262
13453
|
/(?:our data|our research|our study|our analysis|we found|we discovered)/i,
|
|
13263
13454
|
/(?:survey of|surveyed|interviewed|analyzed)\s+\d+/i,
|
|
@@ -13304,7 +13495,7 @@ var STOCK_IMAGE_DOMAINS = [
|
|
|
13304
13495
|
];
|
|
13305
13496
|
function analyzeAICitationWorthiness(html, url) {
|
|
13306
13497
|
const issues = [];
|
|
13307
|
-
const $ =
|
|
13498
|
+
const $ = cheerio48.load(html);
|
|
13308
13499
|
const $content = $("body").clone();
|
|
13309
13500
|
$content.find("nav, footer, script, style, noscript, aside").remove();
|
|
13310
13501
|
const bodyText = $content.text();
|
|
@@ -13529,7 +13720,7 @@ function analyzeAICitationWorthiness(html, url) {
|
|
|
13529
13720
|
}
|
|
13530
13721
|
|
|
13531
13722
|
// src/audit/checks/review-ecosystem.ts
|
|
13532
|
-
import * as
|
|
13723
|
+
import * as cheerio49 from "cheerio";
|
|
13533
13724
|
var REVIEW_PLATFORMS2 = {
|
|
13534
13725
|
general: [
|
|
13535
13726
|
{ name: "Google Business", domain: "google.com/maps", aliases: ["goo.gl/maps", "maps.google"] },
|
|
@@ -13589,7 +13780,7 @@ var TRUST_BADGE_PATTERNS2 = [
|
|
|
13589
13780
|
];
|
|
13590
13781
|
function analyzeReviewEcosystem(html, url) {
|
|
13591
13782
|
const issues = [];
|
|
13592
|
-
const $ =
|
|
13783
|
+
const $ = cheerio49.load(html);
|
|
13593
13784
|
const allLinks = $("a[href]").map((_, a) => $(a).attr("href") || "").get();
|
|
13594
13785
|
const allLinksLower = allLinks.map((l) => l.toLowerCase());
|
|
13595
13786
|
const linkedPlatforms = [];
|
|
@@ -14271,10 +14462,10 @@ function analyzeStructure(content) {
|
|
|
14271
14462
|
}
|
|
14272
14463
|
|
|
14273
14464
|
// src/audit/checks/html-compliance.ts
|
|
14274
|
-
import * as
|
|
14465
|
+
import * as cheerio50 from "cheerio";
|
|
14275
14466
|
async function analyzeHtmlCompliance(html, url, headers) {
|
|
14276
14467
|
const issues = [];
|
|
14277
|
-
const $ =
|
|
14468
|
+
const $ = cheerio50.load(html);
|
|
14278
14469
|
const parsedUrl = new URL(url);
|
|
14279
14470
|
const doctypeMatch = html.match(/<!DOCTYPE\s+([^>]+)>/i);
|
|
14280
14471
|
const hasDoctype = doctypeMatch !== null;
|
|
@@ -14838,7 +15029,7 @@ function getAssetType(url) {
|
|
|
14838
15029
|
}
|
|
14839
15030
|
|
|
14840
15031
|
// src/audit/checks/dom-size.ts
|
|
14841
|
-
import * as
|
|
15032
|
+
import * as cheerio51 from "cheerio";
|
|
14842
15033
|
var THRESHOLDS = {
|
|
14843
15034
|
totalElements: {
|
|
14844
15035
|
warning: 1500,
|
|
@@ -14855,7 +15046,7 @@ var THRESHOLDS = {
|
|
|
14855
15046
|
};
|
|
14856
15047
|
function analyzeDomSize(html, url) {
|
|
14857
15048
|
const issues = [];
|
|
14858
|
-
const $ =
|
|
15049
|
+
const $ = cheerio51.load(html);
|
|
14859
15050
|
const allElements = $("*");
|
|
14860
15051
|
const totalElements = allElements.length;
|
|
14861
15052
|
let maxDepth = 0;
|
|
@@ -15011,10 +15202,10 @@ function getDomReductionSuggestions(breakdown) {
|
|
|
15011
15202
|
}
|
|
15012
15203
|
|
|
15013
15204
|
// src/audit/checks/image-dimensions.ts
|
|
15014
|
-
import * as
|
|
15205
|
+
import * as cheerio52 from "cheerio";
|
|
15015
15206
|
function analyzeImageDimensions(html, url) {
|
|
15016
15207
|
const issues = [];
|
|
15017
|
-
const $ =
|
|
15208
|
+
const $ = cheerio52.load(html);
|
|
15018
15209
|
const images = $("img");
|
|
15019
15210
|
const totalImages = images.length;
|
|
15020
15211
|
let withDimensions = 0;
|
|
@@ -15125,7 +15316,7 @@ function truncateSrc(src) {
|
|
|
15125
15316
|
}
|
|
15126
15317
|
|
|
15127
15318
|
// src/audit/checks/color-contrast.ts
|
|
15128
|
-
import * as
|
|
15319
|
+
import * as cheerio53 from "cheerio";
|
|
15129
15320
|
var KNOWN_LOW_CONTRAST_PAIRS = [
|
|
15130
15321
|
{ fg: "#999999", bg: "#ffffff", ratio: 2.85 },
|
|
15131
15322
|
{ fg: "#888888", bg: "#ffffff", ratio: 3.54 },
|
|
@@ -15160,7 +15351,7 @@ var NAMED_COLORS = {
|
|
|
15160
15351
|
};
|
|
15161
15352
|
function analyzeColorContrast(html, url) {
|
|
15162
15353
|
const issues = [];
|
|
15163
|
-
const $ =
|
|
15354
|
+
const $ = cheerio53.load(html);
|
|
15164
15355
|
const potentialIssues = [];
|
|
15165
15356
|
let elementsAnalyzed = 0;
|
|
15166
15357
|
let passedChecks = 0;
|
|
@@ -15358,7 +15549,7 @@ function calculateContrastRatio(fg, bg) {
|
|
|
15358
15549
|
}
|
|
15359
15550
|
|
|
15360
15551
|
// src/audit/checks/asset-minification.ts
|
|
15361
|
-
import * as
|
|
15552
|
+
import * as cheerio54 from "cheerio";
|
|
15362
15553
|
function isMinified(content, type) {
|
|
15363
15554
|
const lines = content.split("\n");
|
|
15364
15555
|
const totalLines = lines.length;
|
|
@@ -15386,7 +15577,7 @@ function isMinified(content, type) {
|
|
|
15386
15577
|
};
|
|
15387
15578
|
}
|
|
15388
15579
|
function extractAssetUrls(html, baseUrl) {
|
|
15389
|
-
const $ =
|
|
15580
|
+
const $ = cheerio54.load(html);
|
|
15390
15581
|
const base = new URL(baseUrl);
|
|
15391
15582
|
const css = [];
|
|
15392
15583
|
const js = [];
|
|
@@ -15532,10 +15723,10 @@ async function analyzeAssetMinification(html, url) {
|
|
|
15532
15723
|
}
|
|
15533
15724
|
|
|
15534
15725
|
// src/audit/checks/page-resources.ts
|
|
15535
|
-
import * as
|
|
15726
|
+
import * as cheerio55 from "cheerio";
|
|
15536
15727
|
function analyzePageResources(html, url) {
|
|
15537
15728
|
const issues = [];
|
|
15538
|
-
const $ =
|
|
15729
|
+
const $ = cheerio55.load(html);
|
|
15539
15730
|
const baseUrl = new URL(url);
|
|
15540
15731
|
const baseHostname = baseUrl.hostname;
|
|
15541
15732
|
const stylesheets = [];
|
|
@@ -15735,7 +15926,7 @@ function analyzePageResources(html, url) {
|
|
|
15735
15926
|
}
|
|
15736
15927
|
|
|
15737
15928
|
// src/audit/checks/responsive-css.ts
|
|
15738
|
-
import * as
|
|
15929
|
+
import * as cheerio56 from "cheerio";
|
|
15739
15930
|
function extractMediaQueries(css) {
|
|
15740
15931
|
const mediaQueries = [];
|
|
15741
15932
|
const regex = /@media\s*([^{]+)/g;
|
|
@@ -15785,7 +15976,7 @@ function classifyBreakpoints(mediaQueries) {
|
|
|
15785
15976
|
}
|
|
15786
15977
|
async function analyzeResponsiveCss(html, url) {
|
|
15787
15978
|
const issues = [];
|
|
15788
|
-
const $ =
|
|
15979
|
+
const $ = cheerio56.load(html);
|
|
15789
15980
|
const baseUrl = new URL(url);
|
|
15790
15981
|
const viewportMeta = $('meta[name="viewport"]').attr("content");
|
|
15791
15982
|
const hasViewport = !!viewportMeta;
|
|
@@ -16522,10 +16713,10 @@ var urlSafetyDatabase = {
|
|
|
16522
16713
|
};
|
|
16523
16714
|
|
|
16524
16715
|
// src/audit/checks/tracking-verification.ts
|
|
16525
|
-
import * as
|
|
16716
|
+
import * as cheerio57 from "cheerio";
|
|
16526
16717
|
function analyzeTrackingVerification(html, url) {
|
|
16527
16718
|
const issues = [];
|
|
16528
|
-
const $ =
|
|
16719
|
+
const $ = cheerio57.load(html);
|
|
16529
16720
|
const ga4Match = html.match(/gtag\s*\(\s*['"]config['"]\s*,\s*['"](G-[A-Z0-9]+)['"]/i) || html.match(/googletagmanager\.com\/gtag\/js\?id=(G-[A-Z0-9]+)/i);
|
|
16530
16721
|
const hasGa4 = !!ga4Match;
|
|
16531
16722
|
const ga4MeasurementId = ga4Match ? ga4Match[1] : void 0;
|
|
@@ -16659,10 +16850,10 @@ async function runFullAudit(options) {
|
|
|
16659
16850
|
const runExtendedChecks = tier !== "free";
|
|
16660
16851
|
const parsedUrl = new URL(url);
|
|
16661
16852
|
const domain = parsedUrl.hostname;
|
|
16662
|
-
console.
|
|
16853
|
+
console.error(`
|
|
16663
16854
|
\u{1F50D} Running comprehensive SEO audit on ${url}...
|
|
16664
16855
|
`);
|
|
16665
|
-
console.
|
|
16856
|
+
console.error("\u{1F4CB} Phase 1: Crawlability checks + page fetch (parallel)...");
|
|
16666
16857
|
const [crawlabilityResult, fetchResult] = await Promise.all([
|
|
16667
16858
|
runCrawlabilityChecks(url).catch((err) => {
|
|
16668
16859
|
console.error("Crawlability check failed:", err);
|
|
@@ -16697,7 +16888,7 @@ async function runFullAudit(options) {
|
|
|
16697
16888
|
}
|
|
16698
16889
|
const html = fetchResult.data;
|
|
16699
16890
|
const headers = fetchResult.headers;
|
|
16700
|
-
console.
|
|
16891
|
+
console.error(`\u{1F4DD} Phase 2: Running synchronous HTML checks (tier: ${tier}, limit: ${checksLimit})...`);
|
|
16701
16892
|
const onPageResult = analyzeOnPage(html, url);
|
|
16702
16893
|
const structuredDataResult = analyzeStructuredData2(html, url);
|
|
16703
16894
|
const mobileResult = analyzeMobile(html, url);
|
|
@@ -16737,6 +16928,7 @@ async function runFullAudit(options) {
|
|
|
16737
16928
|
const entityResult = runPremiumChecks ? analyzeEntitySEO(html, url) : { issues: [], data: {} };
|
|
16738
16929
|
const qdfFreshnessResult = runPremiumChecks ? analyzeFreshnessSignals(html, url) : { issues: [], data: {} };
|
|
16739
16930
|
const aiContentStructureResult = runPremiumChecks ? analyzeAIContentStructure(html, url) : { issues: [], data: {} };
|
|
16931
|
+
const ragChunkReadinessResult = runPremiumChecks ? analyzeRAGChunkReadiness(html, url) : { issues: [], data: {} };
|
|
16740
16932
|
const citationQualityResult = runPremiumChecks ? analyzeCitationQuality(html, url) : { issues: [], data: {} };
|
|
16741
16933
|
const answerConcisenessResult = runPremiumChecks ? analyzeAnswerConciseness(html, url) : { issues: [], data: {} };
|
|
16742
16934
|
const brandMentionResult = runPremiumChecks ? analyzeBrandMentionOptimization(html, url) : { issues: [], data: {} };
|
|
@@ -16775,6 +16967,7 @@ async function runFullAudit(options) {
|
|
|
16775
16967
|
...entityResult.issues,
|
|
16776
16968
|
...qdfFreshnessResult.issues,
|
|
16777
16969
|
...aiContentStructureResult.issues,
|
|
16970
|
+
...ragChunkReadinessResult.issues,
|
|
16778
16971
|
...citationQualityResult.issues,
|
|
16779
16972
|
...answerConcisenessResult.issues,
|
|
16780
16973
|
...brandMentionResult.issues,
|
|
@@ -16798,7 +16991,7 @@ async function runFullAudit(options) {
|
|
|
16798
16991
|
};
|
|
16799
16992
|
}
|
|
16800
16993
|
}
|
|
16801
|
-
console.
|
|
16994
|
+
console.error("\u{1F517} Phase 3: Running async checks (parallel)...");
|
|
16802
16995
|
const safeAsync = async (name, fn, timeoutMs = 1e4) => {
|
|
16803
16996
|
try {
|
|
16804
16997
|
const resultPromise = fn();
|
|
@@ -16911,7 +17104,7 @@ async function runFullAudit(options) {
|
|
|
16911
17104
|
loadTime: perfData.loadTime,
|
|
16912
17105
|
issues: allIssues.map((i) => i.code)
|
|
16913
17106
|
});
|
|
16914
|
-
console.
|
|
17107
|
+
console.error("\n\u2705 Audit complete!\n");
|
|
16915
17108
|
return createReport(url, domain, allIssues, pages);
|
|
16916
17109
|
}
|
|
16917
17110
|
function createReport(url, domain, issues, pages) {
|
|
@@ -17072,10 +17265,10 @@ function groupIssuesByCategory(issues) {
|
|
|
17072
17265
|
}
|
|
17073
17266
|
|
|
17074
17267
|
// src/audit/checks/duplicate-content.ts
|
|
17075
|
-
import * as
|
|
17268
|
+
import * as cheerio58 from "cheerio";
|
|
17076
17269
|
import { createHash } from "crypto";
|
|
17077
17270
|
function extractContentHash(html, url) {
|
|
17078
|
-
const $ =
|
|
17271
|
+
const $ = cheerio58.load(html);
|
|
17079
17272
|
$("script, style, nav, header, footer, aside, .nav, .header, .footer, .sidebar").remove();
|
|
17080
17273
|
const title = $("title").text().trim();
|
|
17081
17274
|
const bodyText = $("body").text().replace(/\s+/g, " ").trim();
|
|
@@ -18303,7 +18496,7 @@ var PRIORITY_WEIGHTS = {
|
|
|
18303
18496
|
};
|
|
18304
18497
|
|
|
18305
18498
|
// src/keywords/engine.ts
|
|
18306
|
-
import * as
|
|
18499
|
+
import * as cheerio60 from "cheerio";
|
|
18307
18500
|
|
|
18308
18501
|
// src/keywords/prioritizer.ts
|
|
18309
18502
|
function prioritizeKeywords(keywords, siteProfile, existingMeta) {
|
|
@@ -18597,7 +18790,7 @@ function enrichKeywordsWithEstimates(keywords) {
|
|
|
18597
18790
|
}
|
|
18598
18791
|
|
|
18599
18792
|
// src/keywords/sources/free-sources.ts
|
|
18600
|
-
import * as
|
|
18793
|
+
import * as cheerio59 from "cheerio";
|
|
18601
18794
|
var USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36";
|
|
18602
18795
|
async function getPeopleAlsoAsk(query) {
|
|
18603
18796
|
try {
|
|
@@ -18606,7 +18799,7 @@ async function getPeopleAlsoAsk(query) {
|
|
|
18606
18799
|
headers: { "User-Agent": USER_AGENT },
|
|
18607
18800
|
timeout: 1e4
|
|
18608
18801
|
});
|
|
18609
|
-
const $ =
|
|
18802
|
+
const $ = cheerio59.load(response.data);
|
|
18610
18803
|
const questions = [];
|
|
18611
18804
|
$("[data-sgrd]").each((_, el) => {
|
|
18612
18805
|
const text = $(el).text().trim();
|
|
@@ -18632,7 +18825,7 @@ async function getRelatedSearches(query) {
|
|
|
18632
18825
|
headers: { "User-Agent": USER_AGENT },
|
|
18633
18826
|
timeout: 1e4
|
|
18634
18827
|
});
|
|
18635
|
-
const $ =
|
|
18828
|
+
const $ = cheerio59.load(response.data);
|
|
18636
18829
|
const related = [];
|
|
18637
18830
|
$("div[data-ved] a").each((_, el) => {
|
|
18638
18831
|
const href = $(el).attr("href");
|
|
@@ -18722,7 +18915,7 @@ async function analyzeCompetitorTitles(query) {
|
|
|
18722
18915
|
headers: { "User-Agent": USER_AGENT },
|
|
18723
18916
|
timeout: 1e4
|
|
18724
18917
|
});
|
|
18725
|
-
const $ =
|
|
18918
|
+
const $ = cheerio59.load(response.data);
|
|
18726
18919
|
const titles = [];
|
|
18727
18920
|
const keywords = /* @__PURE__ */ new Set();
|
|
18728
18921
|
$("h3").each((_, el) => {
|
|
@@ -19054,7 +19247,7 @@ async function fetchPageMeta(url) {
|
|
|
19054
19247
|
const response = await httpGet(url, {
|
|
19055
19248
|
timeout: 1e4
|
|
19056
19249
|
});
|
|
19057
|
-
const $ =
|
|
19250
|
+
const $ = cheerio60.load(response.data);
|
|
19058
19251
|
return {
|
|
19059
19252
|
url,
|
|
19060
19253
|
title: $("title").text().trim() || void 0,
|
|
@@ -19185,7 +19378,7 @@ async function extractSeedKeywords(url) {
|
|
|
19185
19378
|
const response = await httpGet(url, {
|
|
19186
19379
|
timeout: 1e4
|
|
19187
19380
|
});
|
|
19188
|
-
const $ =
|
|
19381
|
+
const $ = cheerio60.load(response.data);
|
|
19189
19382
|
const seeds = /* @__PURE__ */ new Set();
|
|
19190
19383
|
const title = $("title").text().toLowerCase();
|
|
19191
19384
|
const titleWords = title.split(/[\s\-|:]+/).filter((w) => w.length > 3);
|
|
@@ -19207,7 +19400,7 @@ async function extractSeedKeywords(url) {
|
|
|
19207
19400
|
}
|
|
19208
19401
|
|
|
19209
19402
|
// src/keywords/site-crawler.ts
|
|
19210
|
-
import * as
|
|
19403
|
+
import * as cheerio61 from "cheerio";
|
|
19211
19404
|
var EXCLUDED_PATHS = [
|
|
19212
19405
|
"/cdn-cgi/",
|
|
19213
19406
|
"/wp-admin/",
|
|
@@ -19319,7 +19512,7 @@ async function crawlPage(url, timeout) {
|
|
|
19319
19512
|
validateStatus: (status) => status === 200
|
|
19320
19513
|
});
|
|
19321
19514
|
const html = response.data;
|
|
19322
|
-
const $ =
|
|
19515
|
+
const $ = cheerio61.load(html);
|
|
19323
19516
|
$('script, style, noscript, iframe, nav, footer, header, aside, [role="navigation"]').remove();
|
|
19324
19517
|
const title = $("title").text().trim();
|
|
19325
19518
|
const description = $('meta[name="description"]').attr("content")?.trim() || "";
|
|
@@ -21976,7 +22169,7 @@ function getDateRange(days = 28) {
|
|
|
21976
22169
|
}
|
|
21977
22170
|
|
|
21978
22171
|
// src/keywords/sources/competitor-analysis.ts
|
|
21979
|
-
import * as
|
|
22172
|
+
import * as cheerio62 from "cheerio";
|
|
21980
22173
|
var USER_AGENT2 = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36";
|
|
21981
22174
|
async function discoverCompetitorKeywords(yourDomain, seedKeywords, competitors) {
|
|
21982
22175
|
const yourKeywords = /* @__PURE__ */ new Set();
|
|
@@ -22073,7 +22266,7 @@ async function analyzeSERP(query) {
|
|
|
22073
22266
|
headers: { "User-Agent": USER_AGENT2 },
|
|
22074
22267
|
timeout: 1e4
|
|
22075
22268
|
});
|
|
22076
|
-
const $ =
|
|
22269
|
+
const $ = cheerio62.load(response.data);
|
|
22077
22270
|
const results = [];
|
|
22078
22271
|
const relatedSearches = [];
|
|
22079
22272
|
const peopleAlsoAsk = [];
|