geo-checker 0.3.1 → 0.3.2

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/cli.cjs CHANGED
@@ -286,10 +286,12 @@ async function buildContext(url, opts = {}) {
286
286
  "Site appears to be JS-rendered (sparse body + SPA root element). Re-run with --render for accurate results."
287
287
  );
288
288
  }
289
- const [robotsRaw, llmsRaw, llmsFullRaw] = await Promise.all([
289
+ const [robotsRaw, llmsRaw, llmsFullRaw, skillMdRaw, agentPermissionsRaw] = await Promise.all([
290
290
  fetchText(`${origin}/robots.txt`, opts),
291
291
  fetchText(`${origin}/llms.txt`, opts),
292
- fetchText(`${origin}/llms-full.txt`, opts)
292
+ fetchText(`${origin}/llms-full.txt`, opts),
293
+ fetchText(`${origin}/skill.md`, opts),
294
+ fetchText(`${origin}/agent-permissions.json`, opts)
293
295
  ]);
294
296
  let sitemapUrl = null;
295
297
  const robots = robotsRaw ? parseRobots(robotsRaw) : null;
@@ -297,6 +299,13 @@ async function buildContext(url, opts = {}) {
297
299
  if (!sitemapUrl) sitemapUrl = `${origin}/sitemap.xml`;
298
300
  const sitemapRaw = await fetchText(sitemapUrl, opts);
299
301
  const sitemap = sitemapRaw ? parseSitemap(sitemapRaw) : null;
302
+ let agentPermissions = null;
303
+ if (agentPermissionsRaw && agentPermissionsRaw.trim().length > 0) {
304
+ try {
305
+ agentPermissions = JSON.parse(agentPermissionsRaw);
306
+ } catch {
307
+ }
308
+ }
300
309
  return {
301
310
  url,
302
311
  finalUrl,
@@ -311,7 +320,9 @@ async function buildContext(url, opts = {}) {
311
320
  jsonLd: extractJsonLd($),
312
321
  renderMode,
313
322
  fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
314
- warnings
323
+ warnings,
324
+ skillMd: skillMdRaw && skillMdRaw.trim().length > 0 ? skillMdRaw : null,
325
+ agentPermissions
315
326
  };
316
327
  }
317
328
 
@@ -320,10 +331,11 @@ function defineRule(rule) {
320
331
  return rule;
321
332
  }
322
333
  var CATEGORY_WEIGHTS = {
323
- crawler: 25,
324
- "structured-data": 30,
325
- citation: 25,
326
- content: 20
334
+ crawler: 20,
335
+ "structured-data": 25,
336
+ citation: 20,
337
+ content: 15,
338
+ aeo: 20
327
339
  };
328
340
 
329
341
  // src/engine.ts
@@ -337,7 +349,8 @@ async function runRules(ctx, rules, opts = {}) {
337
349
  crawler: { score: 0, weight: weights.crawler, results: [] },
338
350
  "structured-data": { score: 0, weight: weights["structured-data"], results: [] },
339
351
  citation: { score: 0, weight: weights.citation, results: [] },
340
- content: { score: 0, weight: weights.content, results: [] }
352
+ content: { score: 0, weight: weights.content, results: [] },
353
+ aeo: { score: 0, weight: weights.aeo, results: [] }
341
354
  };
342
355
  for (const rule of rules) {
343
356
  if (onlySet && !onlySet.has(rule.id) && (!rule.stableId || !onlySet.has(rule.stableId))) continue;
@@ -1927,12 +1940,130 @@ var contentRules = [
1927
1940
  externalCitationsRule
1928
1941
  ];
1929
1942
 
1943
+ // src/rules/aeo/skill-md.ts
1944
+ var aeoSkillMdRule = defineRule({
1945
+ id: "aeo.skill-md",
1946
+ stableId: "aeo.skill-md",
1947
+ category: "aeo",
1948
+ group: "opportunity",
1949
+ weight: 3,
1950
+ impact: "high",
1951
+ effort: "low",
1952
+ docsUrl: "https://github.com/BaRam-OSS/geo-checker/blob/main/docs/rules.md#aeoskill-md",
1953
+ title: "skill.md is present",
1954
+ title_ko: "skill.md \uD30C\uC77C \uC874\uC7AC \uC5EC\uBD80",
1955
+ description: "A /skill.md file describes site capabilities so AI agents know what this site can do for them.",
1956
+ run(ctx) {
1957
+ if (ctx.skillMd !== null) {
1958
+ return {
1959
+ status: "pass",
1960
+ score: 1,
1961
+ rationale: "skill.md found at site root.",
1962
+ rationale_ko: "skill.md\uAC00 \uC0AC\uC774\uD2B8 \uB8E8\uD2B8\uC5D0 \uC874\uC7AC\uD569\uB2C8\uB2E4."
1963
+ };
1964
+ }
1965
+ return {
1966
+ status: "warn",
1967
+ score: 0,
1968
+ rationale: "No /skill.md found. Add one to describe your site capabilities to AI agents.",
1969
+ rationale_ko: "/skill.md\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. AI \uC5D0\uC774\uC804\uD2B8\uAC00 \uC0AC\uC774\uD2B8 \uAE30\uB2A5\uC744 \uD30C\uC545\uD560 \uC218 \uC788\uB3C4\uB85D \uCD94\uAC00\uD558\uC138\uC694.",
1970
+ fixHint: "Create /skill.md listing what services, products, and capabilities this site offers."
1971
+ };
1972
+ }
1973
+ });
1974
+
1975
+ // src/rules/aeo/agent-permissions.ts
1976
+ var aeoAgentPermissionsRule = defineRule({
1977
+ id: "aeo.agent-permissions",
1978
+ stableId: "aeo.agent-permissions",
1979
+ category: "aeo",
1980
+ group: "opportunity",
1981
+ weight: 3,
1982
+ impact: "medium",
1983
+ effort: "low",
1984
+ docsUrl: "https://github.com/BaRam-OSS/geo-checker/blob/main/docs/rules.md#aeoagent-permissions",
1985
+ title: "agent-permissions.json is present",
1986
+ title_ko: "agent-permissions.json \uD30C\uC77C \uC874\uC7AC \uC5EC\uBD80",
1987
+ description: "Declares explicit read/summarize/cite/train permissions for AI agents.",
1988
+ run(ctx) {
1989
+ if (ctx.agentPermissions !== null) {
1990
+ return {
1991
+ status: "pass",
1992
+ score: 1,
1993
+ rationale: "agent-permissions.json found at site root.",
1994
+ rationale_ko: "agent-permissions.json\uC774 \uC0AC\uC774\uD2B8 \uB8E8\uD2B8\uC5D0 \uC874\uC7AC\uD569\uB2C8\uB2E4.",
1995
+ evidence: ctx.agentPermissions
1996
+ };
1997
+ }
1998
+ return {
1999
+ status: "warn",
2000
+ score: 0,
2001
+ rationale: "No /agent-permissions.json found. Add one to declare AI agent access policy.",
2002
+ rationale_ko: "/agent-permissions.json\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. AI \uC5D0\uC774\uC804\uD2B8 \uC811\uADFC \uC815\uCC45\uC744 \uBA85\uC2DC\uD558\uB824\uBA74 \uCD94\uAC00\uD558\uC138\uC694.",
2003
+ fixHint: "Create /agent-permissions.json with read, summarize, cite, and train permission flags."
2004
+ };
2005
+ }
2006
+ });
2007
+
2008
+ // src/rules/aeo/token-length.ts
2009
+ var THRESHOLD_OPTIMAL = 15e3;
2010
+ var THRESHOLD_MAX = 25e3;
2011
+ var aeoTokenLengthRule = defineRule({
2012
+ id: "aeo.token-length",
2013
+ stableId: "aeo.token-length",
2014
+ category: "aeo",
2015
+ group: "diagnostic",
2016
+ weight: 4,
2017
+ impact: "medium",
2018
+ effort: "medium",
2019
+ docsUrl: "https://github.com/BaRam-OSS/geo-checker/blob/main/docs/rules.md#aeotoken-length",
2020
+ title: "Content token length within AI agent limits",
2021
+ title_ko: "\uCF58\uD150\uCE20 \uD1A0\uD070 \uC218 AI \uC5D0\uC774\uC804\uD2B8 \uAD8C\uC7A5 \uBC94\uC704",
2022
+ description: "Pages under 15K tokens are optimal for AI agents (per Addy Osmani's AEO guidance).",
2023
+ run(ctx) {
2024
+ const text = ctx.$("body").text();
2025
+ const tokenEstimate = Math.round(text.length / 3);
2026
+ const evidence = { tokenEstimate, thresholds: { optimal: THRESHOLD_OPTIMAL, max: THRESHOLD_MAX } };
2027
+ if (tokenEstimate <= THRESHOLD_OPTIMAL) {
2028
+ return {
2029
+ status: "pass",
2030
+ score: 1,
2031
+ rationale: `Estimated ~${tokenEstimate.toLocaleString()} tokens \u2014 within optimal range.`,
2032
+ rationale_ko: `\uC608\uC0C1 \uD1A0\uD070 \uC218 ~${tokenEstimate.toLocaleString()} \u2014 \uAD8C\uC7A5 \uBC94\uC704(15K) \uC774\uB0B4\uC785\uB2C8\uB2E4.`,
2033
+ evidence
2034
+ };
2035
+ }
2036
+ if (tokenEstimate <= THRESHOLD_MAX) {
2037
+ return {
2038
+ status: "warn",
2039
+ score: 0.5,
2040
+ rationale: `Estimated ~${tokenEstimate.toLocaleString()} tokens \u2014 exceeds 15K recommendation.`,
2041
+ rationale_ko: `\uC608\uC0C1 \uD1A0\uD070 \uC218 ~${tokenEstimate.toLocaleString()} \u2014 15K \uAD8C\uC7A5\uCE58\uB97C \uCD08\uACFC\uD569\uB2C8\uB2E4.`,
2042
+ fixHint: "Consider splitting content into shorter, focused pages.",
2043
+ evidence
2044
+ };
2045
+ }
2046
+ return {
2047
+ status: "fail",
2048
+ score: 0,
2049
+ rationale: `Estimated ~${tokenEstimate.toLocaleString()} tokens \u2014 exceeds 25K agent processing limit.`,
2050
+ rationale_ko: `\uC608\uC0C1 \uD1A0\uD070 \uC218 ~${tokenEstimate.toLocaleString()} \u2014 AI \uC5D0\uC774\uC804\uD2B8 \uCC98\uB9AC \uD55C\uACC4(25K)\uB97C \uCD08\uACFC\uD569\uB2C8\uB2E4.`,
2051
+ fixHint: "Split this page into multiple focused pages under 15K tokens.",
2052
+ evidence
2053
+ };
2054
+ }
2055
+ });
2056
+
2057
+ // src/rules/aeo/index.ts
2058
+ var aeoRules = [aeoSkillMdRule, aeoAgentPermissionsRule, aeoTokenLengthRule];
2059
+
1930
2060
  // src/rules/index.ts
1931
2061
  var defaultRules = [
1932
2062
  ...crawlerRules,
1933
2063
  ...structuredDataRules,
1934
2064
  ...citationRules,
1935
- ...contentRules
2065
+ ...contentRules,
2066
+ ...aeoRules
1936
2067
  ];
1937
2068
 
1938
2069
  // src/config.ts
@@ -2128,7 +2259,8 @@ var CATEGORY_LABELS = {
2128
2259
  crawler: "AI Crawler Access",
2129
2260
  "structured-data": "Structured Data",
2130
2261
  citation: "Citation Signals",
2131
- content: "Content Structure"
2262
+ content: "Content Structure",
2263
+ aeo: "AEO Stack"
2132
2264
  };
2133
2265
  function scoreBadge(score) {
2134
2266
  const color = score >= 85 ? "brightgreen" : score >= 60 ? "yellow" : "red";
@@ -2423,7 +2555,8 @@ var CATEGORY_LABELS2 = {
2423
2555
  crawler: "AI Crawler Access",
2424
2556
  "structured-data": "Structured Data",
2425
2557
  citation: "Citation Signals",
2426
- content: "Content Structure"
2558
+ content: "Content Structure",
2559
+ aeo: "AEO Stack"
2427
2560
  };
2428
2561
  function colorScore(score) {
2429
2562
  if (score >= 85) return import_kleur.default.green().bold(`${score}`);
@@ -2523,7 +2656,8 @@ var CATEGORY_LABELS3 = {
2523
2656
  crawler: "AI Crawler Access",
2524
2657
  "structured-data": "Structured Data",
2525
2658
  citation: "Citation Signals",
2526
- content: "Content Structure"
2659
+ content: "Content Structure",
2660
+ aeo: "AEO Stack"
2527
2661
  };
2528
2662
  var IMPACT_ORDER = {
2529
2663
  critical: 4,
@@ -2606,13 +2740,15 @@ function partitionResults(report) {
2606
2740
  crawler: [],
2607
2741
  "structured-data": [],
2608
2742
  citation: [],
2609
- content: []
2743
+ content: [],
2744
+ aeo: []
2610
2745
  };
2611
2746
  const passed = {
2612
2747
  crawler: [],
2613
2748
  "structured-data": [],
2614
2749
  citation: [],
2615
- content: []
2750
+ content: [],
2751
+ aeo: []
2616
2752
  };
2617
2753
  for (const cat of Object.keys(report.categories)) {
2618
2754
  for (const r of report.categories[cat].results) {