@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.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/citation-quality.ts
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 $ = cheerio44.load(html);
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 cheerio45 from "cheerio";
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 $ = cheerio45.load(html);
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 cheerio46 from "cheerio";
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 $ = cheerio46.load(html);
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 cheerio47 from "cheerio";
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 $ = cheerio47.load(html);
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 cheerio48 from "cheerio";
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 $ = cheerio48.load(html);
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 cheerio49 from "cheerio";
14465
+ import * as cheerio50 from "cheerio";
14275
14466
  async function analyzeHtmlCompliance(html, url, headers) {
14276
14467
  const issues = [];
14277
- const $ = cheerio49.load(html);
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 cheerio50 from "cheerio";
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 $ = cheerio50.load(html);
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 cheerio51 from "cheerio";
15205
+ import * as cheerio52 from "cheerio";
15015
15206
  function analyzeImageDimensions(html, url) {
15016
15207
  const issues = [];
15017
- const $ = cheerio51.load(html);
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 cheerio52 from "cheerio";
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 $ = cheerio52.load(html);
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 cheerio53 from "cheerio";
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 $ = cheerio53.load(html);
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 cheerio54 from "cheerio";
15726
+ import * as cheerio55 from "cheerio";
15536
15727
  function analyzePageResources(html, url) {
15537
15728
  const issues = [];
15538
- const $ = cheerio54.load(html);
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 cheerio55 from "cheerio";
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 $ = cheerio55.load(html);
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 cheerio56 from "cheerio";
16716
+ import * as cheerio57 from "cheerio";
16526
16717
  function analyzeTrackingVerification(html, url) {
16527
16718
  const issues = [];
16528
- const $ = cheerio56.load(html);
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.log(`
16853
+ console.error(`
16663
16854
  \u{1F50D} Running comprehensive SEO audit on ${url}...
16664
16855
  `);
16665
- console.log("\u{1F4CB} Phase 1: Crawlability checks + page fetch (parallel)...");
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.log(`\u{1F4DD} Phase 2: Running synchronous HTML checks (tier: ${tier}, limit: ${checksLimit})...`);
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.log("\u{1F517} Phase 3: Running async checks (parallel)...");
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.log("\n\u2705 Audit complete!\n");
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 cheerio57 from "cheerio";
17268
+ import * as cheerio58 from "cheerio";
17076
17269
  import { createHash } from "crypto";
17077
17270
  function extractContentHash(html, url) {
17078
- const $ = cheerio57.load(html);
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 cheerio59 from "cheerio";
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 cheerio58 from "cheerio";
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 $ = cheerio58.load(response.data);
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 $ = cheerio58.load(response.data);
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 $ = cheerio58.load(response.data);
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 $ = cheerio59.load(response.data);
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 $ = cheerio59.load(response.data);
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 cheerio60 from "cheerio";
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 $ = cheerio60.load(html);
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 cheerio61 from "cheerio";
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 $ = cheerio61.load(response.data);
22269
+ const $ = cheerio62.load(response.data);
22077
22270
  const results = [];
22078
22271
  const relatedSearches = [];
22079
22272
  const peopleAlsoAsk = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rankcli/agent-runtime",
3
- "version": "0.0.14",
3
+ "version": "0.0.15",
4
4
  "description": "RankCLI agent runtime - executes SEO audits and fixes with AI",
5
5
  "homepage": "https://rankcli.dev",
6
6
  "main": "dist/index.js",