@rankcli/agent-runtime 0.0.28 → 0.0.30

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.js CHANGED
@@ -3352,7 +3352,9 @@ __export(index_exports, {
3352
3352
  analyzeUrlSafety: () => analyzeUrlSafety,
3353
3353
  analyzers: () => analyzers_exports,
3354
3354
  applyFixes: () => applyFixes,
3355
+ assessReplacement: () => assessReplacement,
3355
3356
  buildDeterministicFixes: () => buildDeterministicFixes,
3357
+ buildFrameworkGuidance: () => buildFrameworkGuidance,
3356
3358
  buildGSCApiRequest: () => buildGSCApiRequest,
3357
3359
  buildGSCRequest: () => buildGSCRequest,
3358
3360
  calculateAIVisibilityScore: () => calculateAIVisibilityScore,
@@ -3393,6 +3395,7 @@ __export(index_exports, {
3393
3395
  createPullRequest: () => createPullRequest,
3394
3396
  createSEOCommit: () => createSEOCommit,
3395
3397
  createSEOCommits: () => createSEOCommits,
3398
+ describeConcerns: () => describeConcerns,
3396
3399
  detectDuplicates: () => detectDuplicates,
3397
3400
  detectFramework: () => detectFramework4,
3398
3401
  detectGitHubPages: () => detectGitHubPages,
@@ -12740,20 +12743,20 @@ function findMissingPreconnects(thirdPartyDomains, existingHints) {
12740
12743
  }
12741
12744
  })
12742
12745
  );
12743
- const missing = [];
12746
+ const missing2 = [];
12744
12747
  for (const domain of thirdPartyDomains) {
12745
12748
  try {
12746
12749
  const hostname = new URL(domain).hostname;
12747
12750
  const recommended = CRITICAL_THIRD_PARTIES[hostname] || [domain];
12748
12751
  for (const rec of recommended) {
12749
12752
  if (!preconnected.has(rec)) {
12750
- missing.push(rec);
12753
+ missing2.push(rec);
12751
12754
  }
12752
12755
  }
12753
12756
  } catch {
12754
12757
  }
12755
12758
  }
12756
- return [...new Set(missing)];
12759
+ return [...new Set(missing2)];
12757
12760
  }
12758
12761
  function analyzeResourceHints(html, url) {
12759
12762
  const issues = [];
@@ -32956,6 +32959,8 @@ function buildDeterministicFixes(input) {
32956
32959
  const codes = new Set(input.issueCodes);
32957
32960
  const has = (...wanted) => wanted.some((code) => codes.has(code));
32958
32961
  const root = input.appRoot || "";
32962
+ const publicDir = input.publicDir === void 0 ? "public/" : input.publicDir.replace(/\/?$/, input.publicDir ? "/" : "");
32963
+ const served = `${root}${publicDir}`;
32959
32964
  const files = [];
32960
32965
  const addressed = [];
32961
32966
  const base = input.siteUrl.replace(/\/$/, "");
@@ -32963,7 +32968,7 @@ function buildDeterministicFixes(input) {
32963
32968
  const description = input.description ?? metaDescriptionOf(input.existing.indexHtml);
32964
32969
  if (has("ROBOTS_TXT_MISSING", "AI_BOTS_BLOCKED", "AI_CRAWLERS_BLOCKED") && !input.existing.robotsTxt) {
32965
32970
  files.push({
32966
- path: `${root}public/robots.txt`,
32971
+ path: `${served}robots.txt`,
32967
32972
  content: generateAIFriendlyRobotsTxt(base),
32968
32973
  message: "fix(seo): add robots.txt allowing search and AI crawlers",
32969
32974
  action: "created"
@@ -32972,7 +32977,7 @@ function buildDeterministicFixes(input) {
32972
32977
  }
32973
32978
  if (has("SITEMAP_MISSING") && !input.existing.sitemapXml) {
32974
32979
  files.push({
32975
- path: `${root}public/sitemap.xml`,
32980
+ path: `${served}sitemap.xml`,
32976
32981
  content: sitemap(pages),
32977
32982
  message: "fix(seo): add sitemap.xml",
32978
32983
  action: "created"
@@ -32981,7 +32986,7 @@ function buildDeterministicFixes(input) {
32981
32986
  }
32982
32987
  if (has("LLMS_TXT_MISSING") && !input.existing.llmsTxt) {
32983
32988
  files.push({
32984
- path: `${root}public/llms.txt`,
32989
+ path: `${served}llms.txt`,
32985
32990
  content: llmsTxt(input.siteName, base, description, pages),
32986
32991
  message: "fix(seo): add llms.txt for AI assistants",
32987
32992
  action: "created"
@@ -33035,12 +33040,179 @@ function findAppRoot(files) {
33035
33040
  const best = candidates[0];
33036
33041
  return best === "" ? "" : `${best}/`;
33037
33042
  }
33043
+ const PAGES_MARKERS = ["CNAME", ".nojekyll", "404.html", "site.webmanifest"];
33044
+ const staticRoots = real.filter((f) => f === "index.html" || f.endsWith("/index.html")).map(directoryOf).filter((dir) => PAGES_MARKERS.some((marker) => real.includes(dir ? `${dir}/${marker}` : marker)));
33045
+ if (staticRoots.length > 0) {
33046
+ staticRoots.sort((a, b) => a.split("/").length - b.split("/").length || a.localeCompare(b));
33047
+ return staticRoots[0] === "" ? "" : `${staticRoots[0]}/`;
33048
+ }
33038
33049
  const nonRoot = [...packageDirs].filter((dir) => dir !== "");
33039
33050
  if (packageDirs.has("") || nonRoot.length === 0) return "";
33040
33051
  nonRoot.sort((a, b) => a.split("/").length - b.split("/").length || a.localeCompare(b));
33041
33052
  return `${nonRoot[0]}/`;
33042
33053
  }
33043
33054
 
33055
+ // src/fixer/safety.ts
33056
+ var HTML_EXT = /\.html?$/i;
33057
+ var SOURCE_EXT = /\.(ts|tsx|js|jsx|mjs|cjs|vue|svelte|astro)$/i;
33058
+ function attributeValues(html, pattern) {
33059
+ const found = /* @__PURE__ */ new Set();
33060
+ for (const match of html.matchAll(pattern)) {
33061
+ const value = match[1]?.trim();
33062
+ if (value) found.add(value);
33063
+ }
33064
+ return found;
33065
+ }
33066
+ function scriptSources(html) {
33067
+ return attributeValues(html, /<script[^>]+src=["']([^"']+)["']/gi);
33068
+ }
33069
+ function stylesheetHrefs(html) {
33070
+ return attributeValues(html, /<link[^>]+href=["']([^"']+)["'][^>]*>/gi);
33071
+ }
33072
+ function verificationTokens(html) {
33073
+ const found = /* @__PURE__ */ new Set();
33074
+ for (const match of html.matchAll(/<meta[^>]+name=["']([^"']*(?:site-verification|verify|verification)[^"']*)["'][^>]*>/gi)) {
33075
+ found.add(match[1].toLowerCase());
33076
+ }
33077
+ return found;
33078
+ }
33079
+ function importSpecifiers(source) {
33080
+ const found = /* @__PURE__ */ new Set();
33081
+ for (const match of source.matchAll(/\bfrom\s+["']([^"']+)["']/g)) found.add(match[1]);
33082
+ for (const match of source.matchAll(/\brequire\(\s*["']([^"']+)["']\s*\)/g)) found.add(match[1]);
33083
+ return found;
33084
+ }
33085
+ function missing(before, after) {
33086
+ return [...before].filter((value) => !after.has(value));
33087
+ }
33088
+ function xmlConcern(xml) {
33089
+ const stack = [];
33090
+ for (const match of xml.matchAll(/<\/?([A-Za-z_][\w:.-]*)[^>]*?(\/?)>/g)) {
33091
+ const [tag, name, selfClosing] = match;
33092
+ if (tag.startsWith("<?") || tag.startsWith("<!")) continue;
33093
+ if (tag.startsWith("</")) {
33094
+ if (stack.pop() !== name) return `unbalanced </${name}>`;
33095
+ } else if (!selfClosing) {
33096
+ stack.push(name);
33097
+ }
33098
+ }
33099
+ return stack.length ? `unclosed <${stack[stack.length - 1]}>` : null;
33100
+ }
33101
+ function sitemapUrlCount(xml) {
33102
+ return [...xml.matchAll(/<loc>/gi)].length;
33103
+ }
33104
+ function newestLastmod(xml) {
33105
+ const dates = [...xml.matchAll(/<lastmod>([^<]+)<\/lastmod>/gi)].map((m) => Date.parse(m[1].trim())).filter((t) => !Number.isNaN(t));
33106
+ return dates.length ? Math.max(...dates) : null;
33107
+ }
33108
+ function jsonLdConcerns(html) {
33109
+ const bad = [];
33110
+ for (const match of html.matchAll(/<script[^>]+type=["']application\/ld\+json["'][^>]*>([\s\S]*?)<\/script>/gi)) {
33111
+ try {
33112
+ JSON.parse(match[1]);
33113
+ } catch (error) {
33114
+ bad.push(error.message);
33115
+ }
33116
+ }
33117
+ return bad;
33118
+ }
33119
+ function assessReplacement(path3, original, replacement) {
33120
+ const concerns = [];
33121
+ const add = (code, detail) => concerns.push({ code, detail });
33122
+ if (/\.json$/i.test(path3) || /\.webmanifest$/i.test(path3)) {
33123
+ try {
33124
+ JSON.parse(replacement);
33125
+ } catch (error) {
33126
+ add("invalid_json", error.message);
33127
+ }
33128
+ }
33129
+ if (/\.xml$/i.test(path3)) {
33130
+ const problem = xmlConcern(replacement);
33131
+ if (problem) add("invalid_xml", problem);
33132
+ }
33133
+ if (HTML_EXT.test(path3)) {
33134
+ for (const problem of jsonLdConcerns(replacement)) add("invalid_json_ld", problem);
33135
+ }
33136
+ if (original === null) return concerns;
33137
+ if (replacement.length < original.length * 0.6) {
33138
+ add("shrunk", `${Math.round(replacement.length / original.length * 100)}% of the original length`);
33139
+ }
33140
+ if (HTML_EXT.test(path3)) {
33141
+ for (const tag of ["</head>", "</body>", "</html>"]) {
33142
+ if (original.includes(tag) && !replacement.includes(tag)) add("lost_closing_tag", tag);
33143
+ }
33144
+ const lostScripts = missing(scriptSources(original), scriptSources(replacement));
33145
+ if (lostScripts.length) add("lost_script", lostScripts.join(", "));
33146
+ const lostStyles = missing(stylesheetHrefs(original), stylesheetHrefs(replacement));
33147
+ if (lostStyles.length) add("lost_stylesheet", lostStyles.join(", "));
33148
+ const lostVerification = missing(verificationTokens(original), verificationTokens(replacement));
33149
+ if (lostVerification.length) add("lost_verification", lostVerification.join(", "));
33150
+ }
33151
+ if (SOURCE_EXT.test(path3)) {
33152
+ const lostImports = missing(importSpecifiers(original), importSpecifiers(replacement));
33153
+ if (lostImports.length) add("lost_import", lostImports.join(", "));
33154
+ }
33155
+ if (/sitemap[^/]*\.xml$/i.test(path3)) {
33156
+ const before = sitemapUrlCount(original);
33157
+ const after = sitemapUrlCount(replacement);
33158
+ if (after < before) add("fewer_sitemap_urls", `${before} -> ${after}`);
33159
+ const newestBefore = newestLastmod(original);
33160
+ const newestAfter = newestLastmod(replacement);
33161
+ if (newestBefore !== null && newestAfter !== null && newestAfter < newestBefore) {
33162
+ add(
33163
+ "lastmod_moved_backwards",
33164
+ `${new Date(newestBefore).toISOString().slice(0, 10)} -> ${new Date(newestAfter).toISOString().slice(0, 10)}`
33165
+ );
33166
+ }
33167
+ }
33168
+ return concerns;
33169
+ }
33170
+ function describeConcerns(concerns) {
33171
+ return concerns.map((c) => `${c.code} (${c.detail})`).join("; ");
33172
+ }
33173
+
33174
+ // src/fixer/framework-guidance.ts
33175
+ var DEFAULT_MAX_CHARS = 4e3;
33176
+ function checkBlock(check) {
33177
+ const fix = check.howToFix?.trim();
33178
+ return [
33179
+ `### ${check.title}${check.code ? ` (${check.code})` : ""}`,
33180
+ check.description?.trim(),
33181
+ fix ? `How this framework wants it fixed:
33182
+ ${fix}` : null
33183
+ ].filter(Boolean).join("\n");
33184
+ }
33185
+ function buildFrameworkGuidance(recipe, options = {}) {
33186
+ if (!recipe) return "";
33187
+ const budget = options.maxChars ?? DEFAULT_MAX_CHARS;
33188
+ const reported = new Set(options.issueCodes ?? []);
33189
+ const header = [
33190
+ `## ${recipe.display_name} conventions`,
33191
+ `Renders on the server by default: ${recipe.ssr_default ? "yes" : "no"}.`,
33192
+ recipe.seo_challenges?.length ? `Known traps in this framework:
33193
+ ${recipe.seo_challenges.map((c) => `- ${c}`).join("\n")}` : null
33194
+ ].filter(Boolean).join("\n");
33195
+ const checks = recipe.checks ?? [];
33196
+ const relevant = checks.filter((c) => c.code && reported.has(c.code));
33197
+ const rest = checks.filter((c) => !relevant.includes(c));
33198
+ let out = header;
33199
+ let included = 0;
33200
+ for (const check of [...relevant, ...rest]) {
33201
+ const block = checkBlock(check);
33202
+ if (out.length + block.length + 2 > budget) break;
33203
+ out += `
33204
+
33205
+ ${block}`;
33206
+ included++;
33207
+ }
33208
+ if (included < checks.length) {
33209
+ out += `
33210
+
33211
+ _(${checks.length - included} more ${recipe.display_name} checks omitted.)_`;
33212
+ }
33213
+ return out;
33214
+ }
33215
+
33044
33216
  // src/git/commit-helper.ts
33045
33217
  var import_child_process2 = require("child_process");
33046
33218
  var import_util2 = require("util");
@@ -35586,19 +35758,16 @@ __export(frameworks_exports, {
35586
35758
  loadRecipe: () => loadRecipe
35587
35759
  });
35588
35760
  var SUPPORTED_FRAMEWORKS = [
35589
- "react",
35590
- "nextjs",
35591
- "vue",
35592
- "nuxt",
35593
35761
  "angular",
35594
- "svelte",
35595
- "sveltekit",
35596
35762
  "astro",
35597
- "remix",
35598
- "gatsby",
35599
- "rails",
35600
- "laravel",
35601
35763
  "django",
35764
+ "laravel",
35765
+ "nextjs",
35766
+ "nuxt",
35767
+ "rails",
35768
+ "react",
35769
+ "sveltekit",
35770
+ "vue",
35602
35771
  "wordpress"
35603
35772
  ];
35604
35773
  // Annotate the CommonJS export names for ESM import in node:
@@ -35648,7 +35817,9 @@ var SUPPORTED_FRAMEWORKS = [
35648
35817
  analyzeUrlSafety,
35649
35818
  analyzers,
35650
35819
  applyFixes,
35820
+ assessReplacement,
35651
35821
  buildDeterministicFixes,
35822
+ buildFrameworkGuidance,
35652
35823
  buildGSCApiRequest,
35653
35824
  buildGSCRequest,
35654
35825
  calculateAIVisibilityScore,
@@ -35689,6 +35860,7 @@ var SUPPORTED_FRAMEWORKS = [
35689
35860
  createPullRequest,
35690
35861
  createSEOCommit,
35691
35862
  createSEOCommits,
35863
+ describeConcerns,
35692
35864
  detectDuplicates,
35693
35865
  detectFramework,
35694
35866
  detectGitHubPages,
package/dist/index.mjs CHANGED
@@ -9199,20 +9199,20 @@ function findMissingPreconnects(thirdPartyDomains, existingHints) {
9199
9199
  }
9200
9200
  })
9201
9201
  );
9202
- const missing = [];
9202
+ const missing2 = [];
9203
9203
  for (const domain of thirdPartyDomains) {
9204
9204
  try {
9205
9205
  const hostname = new URL(domain).hostname;
9206
9206
  const recommended = CRITICAL_THIRD_PARTIES[hostname] || [domain];
9207
9207
  for (const rec of recommended) {
9208
9208
  if (!preconnected.has(rec)) {
9209
- missing.push(rec);
9209
+ missing2.push(rec);
9210
9210
  }
9211
9211
  }
9212
9212
  } catch {
9213
9213
  }
9214
9214
  }
9215
- return [...new Set(missing)];
9215
+ return [...new Set(missing2)];
9216
9216
  }
9217
9217
  function analyzeResourceHints(html, url) {
9218
9218
  const issues = [];
@@ -29388,6 +29388,8 @@ function buildDeterministicFixes(input) {
29388
29388
  const codes = new Set(input.issueCodes);
29389
29389
  const has = (...wanted) => wanted.some((code) => codes.has(code));
29390
29390
  const root = input.appRoot || "";
29391
+ const publicDir = input.publicDir === void 0 ? "public/" : input.publicDir.replace(/\/?$/, input.publicDir ? "/" : "");
29392
+ const served = `${root}${publicDir}`;
29391
29393
  const files = [];
29392
29394
  const addressed = [];
29393
29395
  const base = input.siteUrl.replace(/\/$/, "");
@@ -29395,7 +29397,7 @@ function buildDeterministicFixes(input) {
29395
29397
  const description = input.description ?? metaDescriptionOf(input.existing.indexHtml);
29396
29398
  if (has("ROBOTS_TXT_MISSING", "AI_BOTS_BLOCKED", "AI_CRAWLERS_BLOCKED") && !input.existing.robotsTxt) {
29397
29399
  files.push({
29398
- path: `${root}public/robots.txt`,
29400
+ path: `${served}robots.txt`,
29399
29401
  content: generateAIFriendlyRobotsTxt(base),
29400
29402
  message: "fix(seo): add robots.txt allowing search and AI crawlers",
29401
29403
  action: "created"
@@ -29404,7 +29406,7 @@ function buildDeterministicFixes(input) {
29404
29406
  }
29405
29407
  if (has("SITEMAP_MISSING") && !input.existing.sitemapXml) {
29406
29408
  files.push({
29407
- path: `${root}public/sitemap.xml`,
29409
+ path: `${served}sitemap.xml`,
29408
29410
  content: sitemap(pages),
29409
29411
  message: "fix(seo): add sitemap.xml",
29410
29412
  action: "created"
@@ -29413,7 +29415,7 @@ function buildDeterministicFixes(input) {
29413
29415
  }
29414
29416
  if (has("LLMS_TXT_MISSING") && !input.existing.llmsTxt) {
29415
29417
  files.push({
29416
- path: `${root}public/llms.txt`,
29418
+ path: `${served}llms.txt`,
29417
29419
  content: llmsTxt(input.siteName, base, description, pages),
29418
29420
  message: "fix(seo): add llms.txt for AI assistants",
29419
29421
  action: "created"
@@ -29467,12 +29469,179 @@ function findAppRoot(files) {
29467
29469
  const best = candidates[0];
29468
29470
  return best === "" ? "" : `${best}/`;
29469
29471
  }
29472
+ const PAGES_MARKERS = ["CNAME", ".nojekyll", "404.html", "site.webmanifest"];
29473
+ const staticRoots = real.filter((f) => f === "index.html" || f.endsWith("/index.html")).map(directoryOf).filter((dir) => PAGES_MARKERS.some((marker) => real.includes(dir ? `${dir}/${marker}` : marker)));
29474
+ if (staticRoots.length > 0) {
29475
+ staticRoots.sort((a, b) => a.split("/").length - b.split("/").length || a.localeCompare(b));
29476
+ return staticRoots[0] === "" ? "" : `${staticRoots[0]}/`;
29477
+ }
29470
29478
  const nonRoot = [...packageDirs].filter((dir) => dir !== "");
29471
29479
  if (packageDirs.has("") || nonRoot.length === 0) return "";
29472
29480
  nonRoot.sort((a, b) => a.split("/").length - b.split("/").length || a.localeCompare(b));
29473
29481
  return `${nonRoot[0]}/`;
29474
29482
  }
29475
29483
 
29484
+ // src/fixer/safety.ts
29485
+ var HTML_EXT = /\.html?$/i;
29486
+ var SOURCE_EXT = /\.(ts|tsx|js|jsx|mjs|cjs|vue|svelte|astro)$/i;
29487
+ function attributeValues(html, pattern) {
29488
+ const found = /* @__PURE__ */ new Set();
29489
+ for (const match of html.matchAll(pattern)) {
29490
+ const value = match[1]?.trim();
29491
+ if (value) found.add(value);
29492
+ }
29493
+ return found;
29494
+ }
29495
+ function scriptSources(html) {
29496
+ return attributeValues(html, /<script[^>]+src=["']([^"']+)["']/gi);
29497
+ }
29498
+ function stylesheetHrefs(html) {
29499
+ return attributeValues(html, /<link[^>]+href=["']([^"']+)["'][^>]*>/gi);
29500
+ }
29501
+ function verificationTokens(html) {
29502
+ const found = /* @__PURE__ */ new Set();
29503
+ for (const match of html.matchAll(/<meta[^>]+name=["']([^"']*(?:site-verification|verify|verification)[^"']*)["'][^>]*>/gi)) {
29504
+ found.add(match[1].toLowerCase());
29505
+ }
29506
+ return found;
29507
+ }
29508
+ function importSpecifiers(source) {
29509
+ const found = /* @__PURE__ */ new Set();
29510
+ for (const match of source.matchAll(/\bfrom\s+["']([^"']+)["']/g)) found.add(match[1]);
29511
+ for (const match of source.matchAll(/\brequire\(\s*["']([^"']+)["']\s*\)/g)) found.add(match[1]);
29512
+ return found;
29513
+ }
29514
+ function missing(before, after) {
29515
+ return [...before].filter((value) => !after.has(value));
29516
+ }
29517
+ function xmlConcern(xml) {
29518
+ const stack = [];
29519
+ for (const match of xml.matchAll(/<\/?([A-Za-z_][\w:.-]*)[^>]*?(\/?)>/g)) {
29520
+ const [tag, name, selfClosing] = match;
29521
+ if (tag.startsWith("<?") || tag.startsWith("<!")) continue;
29522
+ if (tag.startsWith("</")) {
29523
+ if (stack.pop() !== name) return `unbalanced </${name}>`;
29524
+ } else if (!selfClosing) {
29525
+ stack.push(name);
29526
+ }
29527
+ }
29528
+ return stack.length ? `unclosed <${stack[stack.length - 1]}>` : null;
29529
+ }
29530
+ function sitemapUrlCount(xml) {
29531
+ return [...xml.matchAll(/<loc>/gi)].length;
29532
+ }
29533
+ function newestLastmod(xml) {
29534
+ const dates = [...xml.matchAll(/<lastmod>([^<]+)<\/lastmod>/gi)].map((m) => Date.parse(m[1].trim())).filter((t) => !Number.isNaN(t));
29535
+ return dates.length ? Math.max(...dates) : null;
29536
+ }
29537
+ function jsonLdConcerns(html) {
29538
+ const bad = [];
29539
+ for (const match of html.matchAll(/<script[^>]+type=["']application\/ld\+json["'][^>]*>([\s\S]*?)<\/script>/gi)) {
29540
+ try {
29541
+ JSON.parse(match[1]);
29542
+ } catch (error) {
29543
+ bad.push(error.message);
29544
+ }
29545
+ }
29546
+ return bad;
29547
+ }
29548
+ function assessReplacement(path3, original, replacement) {
29549
+ const concerns = [];
29550
+ const add = (code, detail) => concerns.push({ code, detail });
29551
+ if (/\.json$/i.test(path3) || /\.webmanifest$/i.test(path3)) {
29552
+ try {
29553
+ JSON.parse(replacement);
29554
+ } catch (error) {
29555
+ add("invalid_json", error.message);
29556
+ }
29557
+ }
29558
+ if (/\.xml$/i.test(path3)) {
29559
+ const problem = xmlConcern(replacement);
29560
+ if (problem) add("invalid_xml", problem);
29561
+ }
29562
+ if (HTML_EXT.test(path3)) {
29563
+ for (const problem of jsonLdConcerns(replacement)) add("invalid_json_ld", problem);
29564
+ }
29565
+ if (original === null) return concerns;
29566
+ if (replacement.length < original.length * 0.6) {
29567
+ add("shrunk", `${Math.round(replacement.length / original.length * 100)}% of the original length`);
29568
+ }
29569
+ if (HTML_EXT.test(path3)) {
29570
+ for (const tag of ["</head>", "</body>", "</html>"]) {
29571
+ if (original.includes(tag) && !replacement.includes(tag)) add("lost_closing_tag", tag);
29572
+ }
29573
+ const lostScripts = missing(scriptSources(original), scriptSources(replacement));
29574
+ if (lostScripts.length) add("lost_script", lostScripts.join(", "));
29575
+ const lostStyles = missing(stylesheetHrefs(original), stylesheetHrefs(replacement));
29576
+ if (lostStyles.length) add("lost_stylesheet", lostStyles.join(", "));
29577
+ const lostVerification = missing(verificationTokens(original), verificationTokens(replacement));
29578
+ if (lostVerification.length) add("lost_verification", lostVerification.join(", "));
29579
+ }
29580
+ if (SOURCE_EXT.test(path3)) {
29581
+ const lostImports = missing(importSpecifiers(original), importSpecifiers(replacement));
29582
+ if (lostImports.length) add("lost_import", lostImports.join(", "));
29583
+ }
29584
+ if (/sitemap[^/]*\.xml$/i.test(path3)) {
29585
+ const before = sitemapUrlCount(original);
29586
+ const after = sitemapUrlCount(replacement);
29587
+ if (after < before) add("fewer_sitemap_urls", `${before} -> ${after}`);
29588
+ const newestBefore = newestLastmod(original);
29589
+ const newestAfter = newestLastmod(replacement);
29590
+ if (newestBefore !== null && newestAfter !== null && newestAfter < newestBefore) {
29591
+ add(
29592
+ "lastmod_moved_backwards",
29593
+ `${new Date(newestBefore).toISOString().slice(0, 10)} -> ${new Date(newestAfter).toISOString().slice(0, 10)}`
29594
+ );
29595
+ }
29596
+ }
29597
+ return concerns;
29598
+ }
29599
+ function describeConcerns(concerns) {
29600
+ return concerns.map((c) => `${c.code} (${c.detail})`).join("; ");
29601
+ }
29602
+
29603
+ // src/fixer/framework-guidance.ts
29604
+ var DEFAULT_MAX_CHARS = 4e3;
29605
+ function checkBlock(check) {
29606
+ const fix = check.howToFix?.trim();
29607
+ return [
29608
+ `### ${check.title}${check.code ? ` (${check.code})` : ""}`,
29609
+ check.description?.trim(),
29610
+ fix ? `How this framework wants it fixed:
29611
+ ${fix}` : null
29612
+ ].filter(Boolean).join("\n");
29613
+ }
29614
+ function buildFrameworkGuidance(recipe, options = {}) {
29615
+ if (!recipe) return "";
29616
+ const budget = options.maxChars ?? DEFAULT_MAX_CHARS;
29617
+ const reported = new Set(options.issueCodes ?? []);
29618
+ const header = [
29619
+ `## ${recipe.display_name} conventions`,
29620
+ `Renders on the server by default: ${recipe.ssr_default ? "yes" : "no"}.`,
29621
+ recipe.seo_challenges?.length ? `Known traps in this framework:
29622
+ ${recipe.seo_challenges.map((c) => `- ${c}`).join("\n")}` : null
29623
+ ].filter(Boolean).join("\n");
29624
+ const checks = recipe.checks ?? [];
29625
+ const relevant = checks.filter((c) => c.code && reported.has(c.code));
29626
+ const rest = checks.filter((c) => !relevant.includes(c));
29627
+ let out = header;
29628
+ let included = 0;
29629
+ for (const check of [...relevant, ...rest]) {
29630
+ const block = checkBlock(check);
29631
+ if (out.length + block.length + 2 > budget) break;
29632
+ out += `
29633
+
29634
+ ${block}`;
29635
+ included++;
29636
+ }
29637
+ if (included < checks.length) {
29638
+ out += `
29639
+
29640
+ _(${checks.length - included} more ${recipe.display_name} checks omitted.)_`;
29641
+ }
29642
+ return out;
29643
+ }
29644
+
29476
29645
  // src/git/commit-helper.ts
29477
29646
  import { execSync, exec as exec2 } from "child_process";
29478
29647
  import { promisify as promisify2 } from "util";
@@ -32011,19 +32180,16 @@ __export(frameworks_exports, {
32011
32180
  loadRecipe: () => loadRecipe
32012
32181
  });
32013
32182
  var SUPPORTED_FRAMEWORKS = [
32014
- "react",
32015
- "nextjs",
32016
- "vue",
32017
- "nuxt",
32018
32183
  "angular",
32019
- "svelte",
32020
- "sveltekit",
32021
32184
  "astro",
32022
- "remix",
32023
- "gatsby",
32024
- "rails",
32025
- "laravel",
32026
32185
  "django",
32186
+ "laravel",
32187
+ "nextjs",
32188
+ "nuxt",
32189
+ "rails",
32190
+ "react",
32191
+ "sveltekit",
32192
+ "vue",
32027
32193
  "wordpress"
32028
32194
  ];
32029
32195
  export {
@@ -32072,7 +32238,9 @@ export {
32072
32238
  analyzeUrlSafety,
32073
32239
  analyzers_exports as analyzers,
32074
32240
  applyFixes,
32241
+ assessReplacement,
32075
32242
  buildDeterministicFixes,
32243
+ buildFrameworkGuidance,
32076
32244
  buildGSCApiRequest,
32077
32245
  buildGSCRequest,
32078
32246
  calculateAIVisibilityScore,
@@ -32113,6 +32281,7 @@ export {
32113
32281
  createPullRequest,
32114
32282
  createSEOCommit,
32115
32283
  createSEOCommits,
32284
+ describeConcerns,
32116
32285
  detectDuplicates,
32117
32286
  detectFramework4 as detectFramework,
32118
32287
  detectGitHubPages,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rankcli/agent-runtime",
3
- "version": "0.0.28",
3
+ "version": "0.0.30",
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",