@rankcli/agent-runtime 0.0.29 → 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 = [];
@@ -33049,6 +33052,167 @@ function findAppRoot(files) {
33049
33052
  return `${nonRoot[0]}/`;
33050
33053
  }
33051
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
+
33052
33216
  // src/git/commit-helper.ts
33053
33217
  var import_child_process2 = require("child_process");
33054
33218
  var import_util2 = require("util");
@@ -35594,19 +35758,16 @@ __export(frameworks_exports, {
35594
35758
  loadRecipe: () => loadRecipe
35595
35759
  });
35596
35760
  var SUPPORTED_FRAMEWORKS = [
35597
- "react",
35598
- "nextjs",
35599
- "vue",
35600
- "nuxt",
35601
35761
  "angular",
35602
- "svelte",
35603
- "sveltekit",
35604
35762
  "astro",
35605
- "remix",
35606
- "gatsby",
35607
- "rails",
35608
- "laravel",
35609
35763
  "django",
35764
+ "laravel",
35765
+ "nextjs",
35766
+ "nuxt",
35767
+ "rails",
35768
+ "react",
35769
+ "sveltekit",
35770
+ "vue",
35610
35771
  "wordpress"
35611
35772
  ];
35612
35773
  // Annotate the CommonJS export names for ESM import in node:
@@ -35656,7 +35817,9 @@ var SUPPORTED_FRAMEWORKS = [
35656
35817
  analyzeUrlSafety,
35657
35818
  analyzers,
35658
35819
  applyFixes,
35820
+ assessReplacement,
35659
35821
  buildDeterministicFixes,
35822
+ buildFrameworkGuidance,
35660
35823
  buildGSCApiRequest,
35661
35824
  buildGSCRequest,
35662
35825
  calculateAIVisibilityScore,
@@ -35697,6 +35860,7 @@ var SUPPORTED_FRAMEWORKS = [
35697
35860
  createPullRequest,
35698
35861
  createSEOCommit,
35699
35862
  createSEOCommits,
35863
+ describeConcerns,
35700
35864
  detectDuplicates,
35701
35865
  detectFramework,
35702
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 = [];
@@ -29481,6 +29481,167 @@ function findAppRoot(files) {
29481
29481
  return `${nonRoot[0]}/`;
29482
29482
  }
29483
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
+
29484
29645
  // src/git/commit-helper.ts
29485
29646
  import { execSync, exec as exec2 } from "child_process";
29486
29647
  import { promisify as promisify2 } from "util";
@@ -32019,19 +32180,16 @@ __export(frameworks_exports, {
32019
32180
  loadRecipe: () => loadRecipe
32020
32181
  });
32021
32182
  var SUPPORTED_FRAMEWORKS = [
32022
- "react",
32023
- "nextjs",
32024
- "vue",
32025
- "nuxt",
32026
32183
  "angular",
32027
- "svelte",
32028
- "sveltekit",
32029
32184
  "astro",
32030
- "remix",
32031
- "gatsby",
32032
- "rails",
32033
- "laravel",
32034
32185
  "django",
32186
+ "laravel",
32187
+ "nextjs",
32188
+ "nuxt",
32189
+ "rails",
32190
+ "react",
32191
+ "sveltekit",
32192
+ "vue",
32035
32193
  "wordpress"
32036
32194
  ];
32037
32195
  export {
@@ -32080,7 +32238,9 @@ export {
32080
32238
  analyzeUrlSafety,
32081
32239
  analyzers_exports as analyzers,
32082
32240
  applyFixes,
32241
+ assessReplacement,
32083
32242
  buildDeterministicFixes,
32243
+ buildFrameworkGuidance,
32084
32244
  buildGSCApiRequest,
32085
32245
  buildGSCRequest,
32086
32246
  calculateAIVisibilityScore,
@@ -32121,6 +32281,7 @@ export {
32121
32281
  createPullRequest,
32122
32282
  createSEOCommit,
32123
32283
  createSEOCommits,
32284
+ describeConcerns,
32124
32285
  detectDuplicates,
32125
32286
  detectFramework4 as detectFramework,
32126
32287
  detectGitHubPages,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rankcli/agent-runtime",
3
- "version": "0.0.29",
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",