seo 0.2.7 → 0.2.8

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.
@@ -327,7 +327,7 @@ CREATE INDEX IF NOT EXISTS idx_performance_reports_url ON performance_reports(ur
327
327
  updated_at = ?
328
328
  WHERE credential_key = ? AND property_site_url = ? AND quota_date = ?`).run(+!!s,a,r.count,e.outcome===`released`?r.count:0,+!!o,(e.blockedUntil??new Date(e.reservation.resetAt)).getTime(),+!!o,n.getTime(),n.getTime(),e.reservation.credentialKey,e.reservation.property,e.reservation.quotaDate)}).immediate()}function n_(e){return y(`sha256`).update(`${e.clientId}\n${e.accountEmail.toLowerCase()}`).digest(`base64url`)}function r_(e,t){let n=e.headers.get(`retry-after`);if(!n)return;let r=Number(n);if(Number.isFinite(r)&&r>=0)return new Date(t.getTime()+r*1e3);let i=Date.parse(n);return Number.isFinite(i)?new Date(i):void 0}function i_(e,t){return e.status===401?new k(`AUTH_EXPIRED`,"Google rejected the access token. Run `seo auth login` again."):e.status===403?new k(`ACCESS_DENIED`,`Google Search Console denied URL Inspection access to ${t.siteUrl}. Check the selected account and property permissions.`):e.status===404?new k(`PROPERTY_NOT_FOUND`,`Google Search Console could not inspect URLs under ${t.siteUrl}. Check the property value and account access.`):e.status>=500?new k(`PROVIDER_UNAVAILABLE`,`Google URL Inspection is unavailable (${e.status}). Try again later.`):new k(`PROVIDER_UNAVAILABLE`,`URL Inspection failed with ${e.status}.`)}async function a_(e){let t=Gg(e.siteUrl,e.inspectionUrl),{client:n,quotaIdentity:r}=await qg(),i=e_({credentialKey:n_(r),property:e.siteUrl,limit:e.quotaLimit}),a;try{a=await Kg(n,`https://searchconsole.googleapis.com/v1/urlInspection/index:inspect`,{method:`POST`,body:JSON.stringify({siteUrl:e.siteUrl,inspectionUrl:t,...e.languageCode?{languageCode:e.languageCode}:{}})})}catch(e){throw t_({reservation:i,outcome:`consumed`}),e instanceof k?e:new k(`PROVIDER_UNAVAILABLE`,`URL Inspection request failed: ${e instanceof Error?e.message:String(e)}`)}if(a.status===429){let t=new Date,n=r_(a,t);throw t_({reservation:i,outcome:`exhausted`,blockedUntil:n,now:t}),new Jg({property:e.siteUrl,resetAt:(n??new Date(i.resetAt)).toISOString(),used:i.used+i.count,limit:i.limit,reason:`provider`,requestSent:!0})}if(t_({reservation:i,outcome:`consumed`}),!a.ok)throw i_(a,e);return await a.json()}function o_(e,t){let n=Object.fromEntries(new Intl.DateTimeFormat(`en-US`,{timeZone:t,year:`numeric`,month:`2-digit`,day:`2-digit`}).formatToParts(e).map(e=>[e.type,e.value]));return new Date(Date.UTC(Number(n.year),Number(n.month)-1,Number(n.day)))}function s_(e,t){let n=new Date(`${e}T00:00:00.000Z`);return n.setUTCDate(n.getUTCDate()+t),n.toISOString().slice(0,10)}function c_(e=new Date){let t=o_(e,`America/Los_Angeles`);return t.setUTCDate(t.getUTCDate()-4),t.toISOString().slice(0,10)}function l_(e=28,t=new Date){let n=c_(t);return{startDate:s_(n,-(e-1)),endDate:n}}function u_(e){return l_(e)}function d_(e=28){return typeof e==`number`?u_(e):e}async function J(e,t,n={}){let{client:r}=await qg(),i=A(),a=vn([e,t]),o=i.prepare(`SELECT response_json, row_count FROM gsc_cache WHERE site_url = ? AND query_hash = ? AND expires_at > ?`).get(e,a,Date.now());if(!n.refresh&&o?.response_json){let e=JSON.parse(o.response_json);return{rows:e,calls:0,rowsFetched:o.row_count??e.length}}let s=encodeURIComponent(e),c=[],{maxRows:l,...u}=t,d=l??t.rowLimit,f=0,p=0;for(;;){let t=typeof d==`number`?d-c.length:void 0;if(typeof t==`number`&&t<=0)break;let n=Math.min(u.rowLimit??25e3,t??25e3,25e3),i={...u,rowLimit:n,startRow:f,dataState:u.dataState??`final`,dimensions:u.dimensions??[`query`,`page`],type:u.type??`web`,aggregationType:u.aggregationType??`auto`},a=await Kg(r,`https://www.googleapis.com/webmasters/v3/sites/${s}/searchAnalytics/query`,{method:`POST`,body:JSON.stringify(i)});if(p+=1,!a.ok)throw a.status===429?new k(`RATE_LIMITED`,`GSC rate limit hit. Back off for 15 minutes.`):a.status===401?new k(`AUTH_EXPIRED`,"Google rejected the access token. Run `seo auth login` again."):a.status===403?new k(`ACCESS_DENIED`,`Google Search Console denied access to ${e}. Check the selected Google account and property permissions.`):a.status===404?new k(`PROPERTY_NOT_FOUND`,`Google Search Console could not find ${e}. Check the property value and account access.`):a.status>=500?new k(`PROVIDER_UNAVAILABLE`,`Google Search Console is unavailable (${a.status}). Try again later.`):new k(`PROVIDER_UNAVAILABLE`,`GSC query failed with ${a.status}.`);let o=(await a.json()).rows??[];if(c.push(...o),o.length<n||typeof d==`number`&&c.length>=d)break;f+=n}return i.prepare(`INSERT OR REPLACE INTO gsc_cache
329
329
  (site_url, query_hash, request_json, response_json, row_count, fetched_at, expires_at)
330
- VALUES (?, ?, ?, ?, ?, ?, ?)`).run(e,a,JSON.stringify(t),JSON.stringify(c),c.length,Date.now(),Date.now()+864e5),{rows:c,calls:p,rowsFetched:c.length}}async function f_(e,t,n=28){let{startDate:r,endDate:i}=u_(n),a=await J(e,{startDate:r,endDate:i,dimensions:[`page`],dimensionFilterGroups:[{groupType:`and`,filters:[{dimension:`page`,operator:`equals`,expression:t}]}]});return a.rows[0]?{clicks:a.rows[0].clicks,impressions:a.rows[0].impressions,ctr:a.rows[0].ctr,position:a.rows[0].position}:void 0}async function p_(e,t,n=28){return(await m_(e,t,n)).values}async function m_(e,t,n=28){let r=new Set(t),i=25e3;if(!r.size)return{values:new Map,returnedRows:0,retainedRowLimit:i,retainedRowLimitReached:!1};let{startDate:a,endDate:o}=d_(n),s=await J(e,{startDate:a,endDate:o,dimensions:[`page`],rowLimit:i,maxRows:i}),c=new Map;for(let e of s.rows){let t=e.keys[0];!t||!r.has(t)||c.set(t,{clicks:e.clicks,impressions:e.impressions,ctr:e.ctr,position:e.position})}return{values:c,returnedRows:s.rowsFetched,retainedRowLimit:i,retainedRowLimitReached:s.rowsFetched>=i}}async function h_(e,t,n=28){let{startDate:r,endDate:i}=u_(n),a=(await J(e,{startDate:r,endDate:i,dimensions:[`query`],dimensionFilterGroups:[{groupType:`and`,filters:[{dimension:`page`,operator:`equals`,expression:t}]}],rowLimit:10})).rows[0],o=a?.keys[0];return a&&o?{query:o,clicks:a.clicks,impressions:a.impressions,ctr:a.ctr,position:a.position}:void 0}async function g_(e,t,n=28){return(await __(e,t,n)).values}async function __(e,t,n=28){let r=new Set(t),i=25e3;if(!r.size)return{values:new Map,returnedRows:0,retainedRowLimit:i,retainedRowLimitReached:!1};let{startDate:a,endDate:o}=d_(n),s=await J(e,{startDate:a,endDate:o,dimensions:[`page`,`query`],rowLimit:i,maxRows:i}),c=new Map;for(let e of s.rows){let t=e.keys[0],n=e.keys[1];!t||!n||!r.has(t)||c.has(t)||c.set(t,{query:n,clicks:e.clicks,impressions:e.impressions,ctr:e.ctr,position:e.position})}return{values:c,returnedRows:s.rowsFetched,retainedRowLimit:i,retainedRowLimitReached:s.rowsFetched>=i}}async function v_(e=!1){let{client:t}=await qg(),n=await Kg(t,`https://www.googleapis.com/webmasters/v3/sites`);if(!n.ok)throw Error(`GSC site list failed with ${n.status}.`);let r=(await n.json()).siteEntry??[],i=A().prepare(`INSERT OR REPLACE INTO sites (site_url, display_name, permission, added_at, is_default) VALUES (?, ?, ?, ?, COALESCE((SELECT is_default FROM sites WHERE site_url = ?), 0))`);for(let e of r)i.run(e.siteUrl,e.siteUrl,e.permissionLevel??null,Date.now(),e.siteUrl);return r}function y_(e){try{let t=new URL(e);return t.hash=``,t.pathname!==`/`&&(t.pathname=t.pathname.replace(/\/$/,``)),t.toString()}catch{return e.trim().replace(/\/$/,``)}}function b_(e,t){return y_(e)===y_(t)}function x_(e,t){try{return new URL(e,t).toString()}catch{return e}}function S_(e){let t=[];return e.httpStatus!==void 0&&(e.httpStatus<200||e.httpStatus>299)&&t.push(`http-non-2xx`),(e.httpStatus===204||e.httpStatus===205)&&t.push(`http-no-content`),e.fetchDiagnostics?.blocked&&t.push(`blocked`),e.fetchDiagnostics&&!e.fetchDiagnostics.fetched&&e.fetchDiagnostics.source!==`cache`&&t.push(`fetch-incomplete`),((e.fetchDiagnostics?.redirectChain?.length??0)>0||e.page&&!b_(e.url,e.page.finalUrl))&&t.push(`redirected`),pm(e.page?.metaRobots,`noindex`)&&t.push(`meta-noindex`),mm(e.page?.xRobotsTag,`noindex`)&&t.push(`x-robots-noindex`),e.page?.canonical&&!b_(e.page.finalUrl,x_(e.page.canonical,e.page.finalUrl))&&t.push(`canonical-mismatch`),t}const C_={id:`arial-20-v1`,fontFamily:`Arial`,fontSizePixels:20,fontWeight:400},w_=new Map([[`'`,3.8],[`ijl`,4.4],[`|`,5.2],[` !,;:./\\[]ftI`,5.6],["-(){}`r",6.7],[`"`,7.1],[`*`,7.8],[`^`,9.4],[`cJksvxyz`,10],[`?_#$0123456789abdeghnopquL`,11.1],[`+<=>~`,11.7],[`FTZ`,12.2],[`&ABEKPRSVXY`,13.3],[`CDHNUw`,14.4],[`GOQ`,15.6],[`mM`,16.7],[`%`,17.8],[`W`,18.9],[`@`,20.3]].flatMap(([e,t])=>[...e].map(e=>[e,t]))),T_=new Map([[`\xA0`,5.6],[`‘`,4.4],[`’`,4.4],[`“`,7.1],[`”`,7.1],[`–`,11.1],[`—`,20],[`…`,16.7],[`•`,7]]),E_=new Map([[`AV`,-1.5],[`VA`,-1.5],[`AW`,-.7],[`WA`,-.7],[`To`,-2.2],[`Ta`,-2.2],[`Te`,-2.2],[`Ty`,-1.1],[`Yo`,-1.8],[`LT`,-1.5],[`FA`,-1.1],[`PA`,-1.5],[`We`,-.4]]),D_=new Intl.Segmenter(`und`,{granularity:`grapheme`});function O_(e,t){let n={high:0,medium:1,low:2};return n[t]>n[e]?t:e}function k_(e){let t=w_.get(e);if(t!==void 0)return{confidence:`high`,kerningCharacter:e,width:t};let n=T_.get(e);if(n!==void 0)return{confidence:`high`,width:n};if(/^[\p{Mark}\p{Format}]+$/u.test(e))return{confidence:`high`,width:0};let r=e.normalize(`NFD`),i=[...r].find(e=>!/\p{Mark}/u.test(e)),a=i?w_.get(i):void 0;return a!==void 0&&/^\p{Script=Latin}\p{Mark}*$/u.test(r)?{confidence:`high`,kerningCharacter:i,width:a}:/\p{Extended_Pictographic}|\p{Emoji_Presentation}|\p{Regional_Indicator}|\u20e3/u.test(e)?{confidence:`medium`,width:23}:/^[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}\p{Mark}]+$/u.test(e)?{confidence:`medium`,width:20}:/^\p{Script=Latin}\p{Mark}*$/u.test(r)?{confidence:`medium`,width:11.1}:{confidence:`low`,width:11.1}}function A_(e){let t=e.trim().replace(/\s+/gu,` `),n=[...D_.segment(t)].map(e=>e.segment),r=[],i=`high`,a,o=0;for(let e of n){let t=k_(e);i=O_(i,t.confidence),t.confidence===`low`&&!r.includes(e)&&r.push(e),a&&t.kerningCharacter&&(o+=E_.get(`${a}${t.kerningCharacter}`)??0),o+=t.width,a=t.kerningCharacter}let s=Math.round(o*10)/10;return{estimatedPixels:s,referencePixels:580,status:s>580?`over-reference`:s>=550?`near-reference`:`within-reference`,confidence:i,profile:C_,graphemeCount:n.length,fallbackGraphemes:r.slice(0,10)}}const j_={fetchPage:Hg,queryPageMetrics:f_,now:()=>new Date};function M_(e){let t=[],n=e.page;return n.title||t.push({principle:`C.2`,evidenceRef:`The HTML document has no title element.`,action:`Add a concise, descriptive title that identifies this page.`,effort:`S`,confidence:`high`}),n.headings.every(e=>e.level!==1)&&t.push({principle:`C.4`,evidenceRef:`The extracted document has no H1 heading.`,action:`Add a descriptive H1 for the page’s main visible topic.`,effort:`S`,confidence:`high`}),!n.canonical&&(!n.canonicalEvidence||n.canonicalEvidence.status===`missing`)&&t.push({principle:`C.7`,evidenceRef:`No canonical link tag detected.`,action:`If duplicate or parameter variants exist, declare the preferred canonical URL consistently; otherwise no change is required.`,effort:`S`,confidence:`medium`}),t}async function N_(e,t=j_){let n=await t.fetchPage(e.url,{js:e.js,refresh:e.refresh}),r=await Lh(n,e.extractor),i=r.headings.filter(e=>e.level===1),a=[];if(r.title||a.push({code:`missing_title`,title:`Title missing`,detail:`No <title> element was found.`,principle:`C.2`,evidenceRef:`HTML document has no <title> element.`,severity:`high`}),r.title){let e=A_(r.title);e.status===`over-reference`&&a.push({code:`title_too_wide`,title:`Title may truncate on some devices`,detail:`Estimated title width ${e.estimatedPixels}px exceeds the ${e.referencePixels}px review reference.`,principle:`C.3`,evidenceRef:`Title width estimate: ${e.estimatedPixels}px using ${e.profile.id} (${e.confidence} confidence).`,severity:`low`})}i.length===0&&a.push({code:`h1_missing`,title:`Missing H1`,detail:`No H1 element was found.`,principle:`C.4`,evidenceRef:`Detected 0 H1 elements.`,severity:`low`});let o=r.canonicalEvidence;if(o?.status===`conflicting`){let e=[...new Set(o.candidates.flatMap(e=>e.resolved&&!e.ignoredReason?[e.resolved]:[]))];a.push({code:`canonical_conflict`,title:`Canonical declarations conflict`,detail:`Canonical declarations identify ${e.length} different targets.`,principle:`C.7`,evidenceRef:`Canonical targets: ${e.join(`, `)}.`,severity:`high`})}else if(o?.status===`duplicate`)a.push({code:`canonical_multiple`,title:`Canonical is declared more than once`,detail:`Multiple eligible declarations identify the same target; keep one canonical method.`,principle:`C.7`,evidenceRef:`${o.candidates.length} canonical declarations were found for ${o.selectedUrl}.`,severity:`low`});else if(o?.status===`outside-head-only`)a.push({code:`canonical_outside_head`,title:`Canonical appears outside the document head`,detail:`Google only accepts HTML canonical link elements in the head.`,principle:`C.7`,evidenceRef:`Ignored canonical value ${o.candidates[0]?.raw??`(empty)`}.`,severity:`medium`});else if(o?.status===`invalid`){let e=o.candidates[0]?.raw??`(empty)`;a.push({code:`canonical_invalid`,title:`Canonical URL is invalid`,detail:`The canonical link does not resolve to an HTTP or HTTPS URL.`,principle:`C.7`,evidenceRef:`Canonical value ${e} is not a valid HTTP or HTTPS URL.`,severity:`high`})}else if(r.canonical){let e;try{let t=new URL(r.canonical,r.finalUrl);[`http:`,`https:`].includes(t.protocol)&&(e=t.toString())}catch{}e?b_(e,r.finalUrl)||a.push({code:`canonical_mismatch`,title:`Canonical differs from final URL`,detail:`Canonical points to ${e}, fetched final URL is ${r.finalUrl}.`,principle:`C.7`,evidenceRef:`Canonical ${e} differs from final URL ${r.finalUrl}.`,severity:`medium`}):a.push({code:`canonical_invalid`,title:`Canonical URL is invalid`,detail:`The canonical link does not resolve to an HTTP or HTTPS URL.`,principle:`C.7`,evidenceRef:`Canonical value ${r.canonical} is not a valid HTTP or HTTPS URL.`,severity:`high`})}let s=e.site?await t.queryPageMetrics(e.site,e.url):void 0,c={url:e.url,fetchedAt:t.now().toISOString(),page:r,fetchDiagnostics:n.diagnostics,metrics:s,issues:a,recommendations:[],warnings:r.warnings};return c.recommendations=M_(c),c}const P_=new Set([`ac`,`co`,`com`,`edu`,`gov`,`net`,`org`]);function F_(e){return e.normalize(`NFKC`).replace(/([a-z])([A-Z])/g,`$1 $2`).toLowerCase().replace(/^https?:\/\//,``).replace(/^sc-domain:/,``).replace(/[^\p{L}\p{N}]+/gu,` `).replace(/\s+/g,` `).trim()}function I_(e){let t=e.replace(/^sc-domain:/,``).replace(/^https?:\/\//,``).split(`/`)[0];if(!t)return;let n=t.split(`.`).filter(Boolean);if(!n.length)return;let r=n.at(-1)??``,i=n.at(-2)??``,a=r.length===2&&P_.has(i)?2:1;return n.at(-(a+1))??n[0]}function L_(e){return[...new Set(e.map(F_).filter(Boolean))]}function R_(e){return e.length>3&&e.endsWith(`s`)?e.slice(0,-1):e}function z_(e,t){return e===t||R_(e)===R_(t)}function B_(e,t){if(!t.length||t.length>e.length)return!1;for(let n=0;n<=e.length-t.length;n++)if(t.every((t,r)=>z_(e[n+r]??``,t)))return!0;return!1}function V_(e){let t=I_(e.siteUrl);return L_([e.name,e.id,t].filter(e=>!!e).flatMap(e=>{let t=F_(e);return[t,t.replace(/\s+/g,``)]}))}function H_(e,t){let n=F_(e);if(!n||!t.length)return!1;let r=n.split(` `),i=n.replace(/\s+/g,``);return t.some(e=>{let t=F_(e);if(!t)return!1;let n=t.replace(/\s+/g,``),a=[...n].length,o=(a>=6||/[^\p{ASCII}]/u.test(n)&&a>=2)&&i.includes(n),s=t.split(` `);return s.length===1?r.some(e=>z_(e,t))||o:B_(r,s)||o})}function U_(e){if(e.includeBrand)return!1;let t=e.brandTerms?.length?e.brandTerms:V_({siteUrl:e.siteUrl});return H_(e.query,t)}function W_(e=28){return l_(e)}function G_(e,t,n){let r=F_(t);if(!r)return;let i=e.get(r)??{term:r,score:0,evidence:[]};i.score+=n.clicks*2+n.impressions*n.ctr+Math.max(0,5-n.position)*10,i.evidence.push(n),e.set(r,i)}function K_(e,t){let n=F_(e),r=n.replace(/\s+/g,``);return t.some(e=>{let t=F_(e),i=t.replace(/\s+/g,``);return n===t||r===i})}async function q_(e){let t=V_({id:e.id,name:e.name,siteUrl:e.site}),n=W_(e.days??28),{rows:r}=await J(e.site,{...n,dimensions:[`query`],type:`web`,dataState:`final`},{refresh:e.refresh}),i=e.minImpressions??10,a=new Map;for(let e of r){let n=e.keys[0]??``;if(e.impressions<i||e.position>3||e.ctr<.1||!H_(n,t))continue;let r={query:n,clicks:e.clicks,impressions:e.impressions,ctr:e.ctr,position:e.position},o=F_(n);o.split(` `).length<=4&&(K_(n,t)||n.includes(`.`))&&G_(a,o,r);for(let e of t)H_(n,[e])&&G_(a,e,r)}let o=[...a.values()].map(e=>({...e,score:Number(e.score.toFixed(2)),evidence:e.evidence.sort((e,t)=>t.clicks-e.clicks).slice(0,3)})).sort((e,t)=>t.score-e.score).slice(0,e.limit??10);return{site:e.site,generatedAt:new Date().toISOString(),derivedTerms:t,suggestedTerms:o.length?o.map(e=>e.term):t,candidates:o}}const J_={1:.27,2:.15,3:.1,4:.07,5:.05,6:.035,7:.025,8:.02,9:.015,10:.012};function Y_(e){return e.normalize(`NFKD`).replace(/\p{Diacritic}/gu,``).replace(/[’']/g,`'`).toLowerCase().replace(/\s+/g,` `).trim()}function X_(e){let t=Y_(e);return typeof Intl<`u`&&`Segmenter`in Intl?[...new Intl.Segmenter(`und`,{granularity:`word`}).segment(t)].filter(e=>e.isWordLike).map(e=>e.segment.trim()).filter(e=>e.length>1):(t.match(/[\p{L}\p{N}]+/gu)??[]).filter(e=>e.length>1)}function Z_(e){return X_(e)}function Q_(e,t){let n=new Set(e),r=new Set(t),i=[...n].filter(e=>r.has(e)),a=new Set([...n,...r]);return a.size===0?0:i.length/a.size}function $_(e=28,t=new Date){return l_(e,t)}const ev=[{intent:`forum/reddit`,patterns:[{regex:/\breddit\b/,label:`reddit`},{regex:/\bforums?\b/,label:`forum`}],action:`Retrieve the page or pages associated with this query. If relevant pages lack practical first-party detail, add evidence, examples, and limitations without imitating forum posts.`},{intent:`comparison`,patterns:[{regex:/\S+\s+vs\.?\s+\S+/,label:`vs`},{regex:/\S+\s+versus\s+\S+/,label:`versus`},{regex:/\balternatives?\s+to\b|\balternatives?\s*$/,label:`alternative comparison`}],action:`Retrieve the page or pages associated with this query. If relevant pages do not satisfy comparison intent, explain criteria, tradeoffs, alternatives, and which situations fit each option.`},{intent:`reviews`,patterns:[{regex:/\breviews?\b/,label:`review`},{regex:/\bcomplaints?\b/,label:`complaint`},{regex:/\bworth\s+it\b/,label:`worth it`}],action:`Retrieve the page or pages associated with this query. If relevant pages do not satisfy review intent, add original evidence, pros, cons, limitations, and suitable alternatives.`},{intent:`experience`,patterns:[{regex:/\bfirst[ -]?hand\b/,label:`first-hand`},{regex:/\bmy\s+experience(?:\s+with)?\b/,label:`my experience`},{regex:/\bexperience\s+with\b/,label:`experience with`},{regex:/\bpeople\s+say\b/,label:`people say`},{regex:/\breal\s+users?\b/,label:`real user`},{regex:/\buser\s+opinions?\b/,label:`user opinion`}],action:`Retrieve the page or pages associated with this query. If relevant pages do not satisfy experience-seeking intent, add genuine first-party examples, methodology, and clearly stated limitations.`},{intent:`recommendation`,patterns:[{regex:/\bbest\b/,label:`best`},{regex:/\brecommend(?:ed|ation|ations|ing)?\b/,label:`recommendation`},{regex:/\btop\s+\d+\b/,label:`ranked list`}],action:`Retrieve the page or pages associated with this query. If relevant pages do not satisfy recommendation intent, make selection criteria, evidence, tradeoffs, and use-case fit explicit.`}];function tv(e){let t=Y_(e),n=ev.map(e=>({pattern:e,terms:e.patterns.filter(({regex:e})=>e.test(t)).map(({label:e})=>e)})).filter(({terms:e})=>e.length>0),r=n[0];if(r)return{intent:r.pattern.intent,signals:n.map(({pattern:e})=>e.intent),matchedTerms:n.flatMap(({terms:e})=>e),confidence:`low`,method:`query-language-heuristic`,action:r.pattern.action}}function Y(e){if(e.value===void 0)return e.fallback;if(!Number.isInteger(e.value)||e.value<e.minimum||e.value>e.maximum)throw new k(`INVALID_INPUT`,`${e.label} must be a whole number between ${e.minimum} and ${e.maximum}.`);return e.value}function nv(e,t){return l_(e,t)}function rv(e,t){if(!e.startDate&&!e.endDate)return;if(!e.startDate||!e.endDate)throw new k(`INVALID_INPUT`,`Start date and end date must be provided together.`);let n=/^\d{4}-\d{2}-\d{2}$/,r=new Date(`${e.startDate}T00:00:00.000Z`),i=new Date(`${e.endDate}T00:00:00.000Z`);if(!n.test(e.startDate)||!n.test(e.endDate)||Number.isNaN(r.getTime())||Number.isNaN(i.getTime())||r.toISOString().slice(0,10)!==e.startDate||i.toISOString().slice(0,10)!==e.endDate||r>i)throw new k(`INVALID_INPUT`,`Start date and end date must be valid YYYY-MM-DD values in ascending order.`);let a=Math.floor((i.getTime()-r.getTime())/864e5)+1;if(a>t)throw new k(`INVALID_INPUT`,`Date range must not exceed ${t} days.`);return{range:{startDate:e.startDate,endDate:e.endDate},days:a}}function iv(e,t){let n=new Date(`${e}T00:00:00.000Z`),r=new Date(Date.UTC(n.getUTCFullYear(),n.getUTCMonth()-t,1)),i=new Date(Date.UTC(r.getUTCFullYear(),r.getUTCMonth()+1,0)).getUTCDate();return r.setUTCDate(Math.min(n.getUTCDate(),i)),r.toISOString().slice(0,10)}function av(e,t){if(!e.site.trim())throw new k(`INVALID_INPUT`,`site must not be empty.`);let n=Y({value:e.days,fallback:28,minimum:1,maximum:548,label:`days`}),r=rv(e,548);if(r&&e.days!==void 0)throw new k(`INVALID_INPUT`,`Use either days or an explicit startDate/endDate range, not both.`);let i=r?.range??nv(n,t),a=nv(1,t).endDate,o=new Date(`${a}T00:00:00.000Z`);o.setUTCDate(o.getUTCDate()+4);let s=iv(o.toISOString().slice(0,10),16);if(i.endDate>a)throw new k(`INVALID_INPUT`,`endDate must be on or before the latest finalized GSC date, ${a}.`);if(i.startDate<s)throw new k(`INVALID_INPUT`,`startDate must be on or after ${s}, the current 16-month GSC retention boundary.`);return{days:r?.days??n,range:i,availableDateWindow:{earliestDate:s,latestFinalDate:a},limit:Y({value:e.limit,fallback:25,minimum:1,maximum:100,label:`limit`}),minImpressions:Y({value:e.minImpressions,fallback:20,minimum:0,maximum:1e9,label:`minImpressions`}),maxRows:Y({value:e.maxRows,fallback:5e4,minimum:1,maximum:5e4,label:`maxRows`})}}function ov(e,t){let n=[...e].map(e=>e.codePointAt(0)??0),r=[...t].map(e=>e.codePointAt(0)??0);for(let e=0;e<Math.min(n.length,r.length);e++){let t=(n[e]??0)-(r[e]??0);if(t!==0)return t}return n.length-r.length}function sv(e,t){return t.impressions-e.impressions||t.clicks-e.clicks||e.position-t.position||ov(e.query,t.query)}function cv(e,t){return e.keys.length===1&&!!t&&Number.isFinite(e.clicks)&&Number.isFinite(e.impressions)&&Number.isFinite(e.ctr)&&Number.isFinite(e.position)&&e.clicks>=0&&e.impressions>0&&e.clicks<=e.impressions&&e.ctr>=0&&e.ctr<=1&&e.position>0}function lv(e){return`${e.clicks}\u0000${e.impressions}\u0000${e.ctr}\u0000${e.position}`}function uv(e,t){let n=new Map;for(let r of e){let e=r.keys[0]?.trim()??``;if(!cv(r,e)){t.invalidRows++;continue}let i=n.get(e)??[];i.push(r),n.set(e,i)}let r=[];for(let[e,i]of n){if(new Set(i.map(lv)).size>1){t.conflictingRows+=i.length;continue}t.duplicateRows+=i.length-1;let n=i[0];n&&r.push({query:e,clicks:n.clicks,impressions:n.impressions,ctr:n.ctr,position:n.position})}return r}const dv={searchAnalytics:J,now:()=>new Date};async function fv(e,t=dv){let n=t.now(),r=av(e,n),i=await t.searchAnalytics(e.site,{...r.range,dimensions:[`query`],type:`web`,dataState:`final`,aggregationType:`auto`,maxRows:r.maxRows},{refresh:e.refresh}),a={sourceRows:i.rows.length,invalidRows:0,duplicateRows:0,conflictingRows:0,brandRows:0,belowMinimumRows:0,eligibleRows:0},o=uv(i.rows,a).filter(t=>t.impressions<r.minImpressions?(a.belowMinimumRows++,!1):U_({query:t.query,siteUrl:e.site,brandTerms:e.brandTerms,includeBrand:e.includeBrand})?(a.brandRows++,!1):!0).sort(sv);a.eligibleRows=o.length;let s=i.rowsFetched>=r.maxRows,c=[];return s&&c.push(`GSC reached the ${r.maxRows}-row retention cap. Lower-click queries may be missing.`),a.invalidRows>0&&c.push(`${a.invalidRows} malformed GSC rows were excluded.`),a.conflictingRows>0&&c.push(`${a.conflictingRows} conflicting duplicate GSC rows were excluded.`),{site:e.site,generatedAt:n.toISOString(),rangeDays:r.days,dateRange:r.range,filters:{limit:r.limit,minImpressions:r.minImpressions,maxRows:r.maxRows},source:{provider:`google-search-console`,dimensions:[`query`],searchType:`web`,dataState:`final`,aggregationType:`auto`,rowsFetched:i.rowsFetched,calls:i.calls,maxRows:r.maxRows,possiblyTruncated:s,completeness:s?`possibly-truncated`:`retained-query-rows-only`,availableDateWindow:{...r.availableDateWindow,basis:`rolling-16-month-retention-with-finalization-lag`}},selection:a,rows:o,warnings:c,caveats:[`Search Console exposes retained top query rows and does not guarantee every query.`,`Clicks, impressions, CTR, and position are observed GSC metrics; classifications and generated prompts are heuristics.`]}}function pv(e){return e.sourceRows===0?`empty`:e.partial?`partial`:e.classifiedRows===0?`filtered`:`available`}async function mv(e,t=dv){let n=await fv(e,t),r=n.rows.flatMap(e=>{let t=tv(e.query);return t?[{row:e,classification:t}]:[]}),i=r.slice(0,n.filters.limit).map(({row:e,classification:t})=>({...e,intent:t.intent,signals:t.signals,matchedTerms:t.matchedTerms,confidence:t.confidence,evidenceScope:`retained-gsc-query-language`,action:t.action})),a=n.source.possiblyTruncated||n.selection.invalidRows>0||n.selection.conflictingRows>0,o=pv({sourceRows:n.selection.sourceRows,classifiedRows:r.length,partial:a}),s=o===`empty`?`GSC returned no retained query rows for this range.`:o===`partial`&&r.length===0?`No matching intent language appeared in the retained rows, but incomplete source evidence makes that negative inconclusive.`:o===`partial`?`Partial evidence: ${r.length} retained queries matched explicit intent language, but incomplete source rows may omit other queries.`:o===`filtered`?`No retained ${e.includeBrand?``:`non-brand `}queries met both the thresholds and the explicit intent-language rules.`:`${r.length} retained queries matched explicit community, comparison, review, experience, or recommendation language; treat each as a review hypothesis.`;return{schemaVersion:2,site:n.site,generatedAt:n.generatedAt,rangeDays:n.rangeDays,dateRange:n.dateRange,dataStatus:o,source:n.source,filters:{...n.filters,brandFiltering:e.includeBrand?`included`:`excluded`},methodology:{id:`gsc_community_intent_query_heuristic`,version:2,classificationUnit:`retained-query-row`,classificationType:`language-heuristic`,classificationLanguage:`english-patterns`,pageContentVerified:!1,estimatedTrafficLift:!1},selection:{...n.selection,unclassifiedRows:n.rows.length-r.length,classifiedRows:r.length,returnedRows:i.length,limitedRows:r.length-i.length},summary:{classifiedQueries:r.length,returnedQueries:i.length,returnedImpressions:i.reduce((e,t)=>e+t.impressions,0),returnedClicks:i.reduce((e,t)=>e+t.clicks,0),verdict:s},items:i,warnings:n.warnings,caveats:[...n.caveats,`Intent labels come from explicit query-language patterns, not SERP, page-content, or audience research.`,`Classification patterns are English-only; non-English queries remain in source counts but may not be classified.`,`Review the ranking page and live search results before changing content.`]}}const hv=new Set([`a`,`an`,`and`,`are`,`as`,`at`,`be`,`by`,`for`,`from`,`how`,`in`,`is`,`of`,`on`,`or`,`the`,`to`,`with`]);function gv(e){return e.normalize(`NFKD`).replace(/\p{Diacritic}/gu,``).replace(/([\p{L}\p{N}])[’']([\p{L}\p{N}])/gu,`$1$2`).toLowerCase().replace(/&/g,` and `).replace(/[^\p{L}\p{N}]+/gu,` `).replace(/\s+/g,` `).trim()}function _v(e){return[...e].some(e=>(e.codePointAt(0)??0)>127)}function vv(e){let t=gv(e).split(` `).filter(e=>e.length>1||_v(e)),n=t.filter(e=>(e.length>2||_v(e))&&!hv.has(e));return[...new Set(n.length?n:t)]}function yv(e,t){let n=gv(e),r=gv(t);if(!n||!r)return 0;let i=RegExp(`(?:^|\\s)${n.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}(?=\\s|$)`,`g`);return[...r.matchAll(i)].length}function bv(e,t=``){let n=vv(e),r=gv(t),i=n.filter(e=>r.split(` `).some(t=>t===e||t.startsWith(e))),a=n.filter(e=>!i.includes(e));return{phraseCount:yv(e,t),matchedTerms:i,missingTerms:a,termCoverage:n.length?i.length/n.length:0}}function xv(e){let t=e.query.trim();return t.length<=80?t:e.queryTerms.slice(0,7).join(` `)||`${t.slice(0,77)}...`}function Sv(e){let t=[];return e.fields.title.termCoverage<.8&&t.push(`title`),e.fields.h1.termCoverage<.8&&t.push(`H1`),e.fields.metaDescription.termCoverage<.8&&t.push(`meta description`),t}function Cv(e){return e===1?`does`:`do`}function wv(e){let t=0;return e.title.termCoverage<.8&&(t+=3),e.h1.termCoverage<.8&&(t+=1),e.metaDescription.termCoverage<.8&&(t+=1),e.mainContent.termCoverage<.8&&(t+=5),t}function Tv(e){return e.mainContent.termCoverage<.8?`content-gap`:e.title.termCoverage<.8||e.metaDescription.termCoverage<.8||e.h1.termCoverage<.8?`serp-framing`:`covered`}function Ev(e){let t=[...e.technicalSignals];return e.fields.title.phraseCount===0&&t.push(`exact-phrase-missing`),e.fields.title.termCoverage<.8&&t.push(`title-gap`),e.fields.metaDescription.termCoverage<.8&&t.push(`meta-description-gap`),e.fields.h1.termCoverage<.8&&t.push(`h1-gap`),e.fields.mainContent.termCoverage<.8&&t.push(`body-gap`),e.technicalSignals.length>0?{classification:`technical-check`,signals:t}:{classification:Tv(e.fields),signals:t}}function Dv(e){let t=xv(e);if(e.status===`failed`)return`I could not fetch or read the page, so do not change the content yet. Check the fetch error, blocking, or rendering issue first, then rerun this report.`;if(e.classification===`technical-check`)return e.signals.includes(`redirected`)&&e.finalUrl?`This GSC URL resolves to ${e.finalUrl}. Confirm that this is the page Google should rank for "${t}" before editing titles or copy.`:`Check whether this URL is indexable, canonical, and fetchable before editing copy for "${t}". If the page has a technical issue, fix that first.`;if(e.classification===`content-gap`){let n=e.fields.mainContent.missingTerms.slice(0,4);return n.length?`The page ranks for "${t}" but the main content does not clearly cover: ${n.join(`, `)}. Add a short, useful section or intro sentence that answers that angle directly.`:`The page ranks for "${t}" but the main content does not clearly answer that angle. Add a short section that covers it directly.`}if(e.classification===`serp-framing`){let n=Sv(e);return`The main content covers the important terms for "${t}", but the ${n.length?n.join(`, `):`title and meta`} ${Cv(n.length)} not make that exact search angle clear enough. Test clearer wording there before rewriting the body.`}return`The page already covers "${t}". Do not add more copy for this query; test the title/meta, improve internal links, or check whether the SERP format is limiting clicks.`}function Ov(e){if(e.status===`failed`)return`Content verification failed.`;if(e.classification===`technical-check`)return e.signals.includes(`redirected`)&&e.finalUrl?`GSC URL resolves to ${e.finalUrl}; verify the canonical target first.`:`Fetch diagnostics show a technical check is needed before judging content.`;if(e.classification===`covered`)return`The page covers the query across the main on-page signals.`;let t=e.fields.title.phraseCount>0,n=e.fields.h1.phraseCount>0,r=e.fields.mainContent.phraseCount>0,i=Math.round(e.fields.mainContent.termCoverage*100);return t&&n&&r?`Exact query appears in title, H1, and main content.`:i>=100?`Main content covers every meaningful query term, but exact SERP wording may be weak.`:`Main content covers ${i}% of meaningful query terms.`}function kv(e){let t=Av(e.query,e.page),n=Ev({technicalSignals:S_({url:e.url,page:e.page,fetchDiagnostics:e.fetchDiagnostics,httpStatus:e.httpStatus}),fields:t}),r={verifiedAt:e.verifiedAt??new Date().toISOString(),query:e.query,url:e.url,finalUrl:e.page.finalUrl,status:`verified`,httpStatus:e.httpStatus,warnings:[...new Set([...e.warnings??[],...e.page.warnings])],wordCount:e.page.wordCount,fetchDiagnostics:e.fetchDiagnostics,contentGapScore:wv(t),queryTerms:vv(e.query),fields:t,classification:n.classification,signals:n.signals,recommendation:``,summary:``};return{...r,recommendation:Dv(r),summary:Ov(r)}}function Av(e,t){return{title:bv(e,t.title),h1:bv(e,t.headings.filter(e=>e.level===1).map(e=>e.text).join(` `)),metaDescription:bv(e,t.metaDescription),mainContent:bv(e,t.contentText)}}async function jv(e){let t;try{t=await(e.fetch??Hg)(e.url,{js:e.js??`auto`,refresh:e.refresh,rate:e.rate});let n=await(e.extract??Lh)(t,`defuddle`);return kv({query:e.query,url:e.url,page:n,fetchDiagnostics:t.diagnostics,httpStatus:t.status,warnings:t.warnings,verifiedAt:e.verifiedAt})}catch(n){let r=S_({url:e.url,fetchDiagnostics:t?.diagnostics,httpStatus:t?.status});return{verifiedAt:e.verifiedAt??new Date().toISOString(),query:e.query,url:e.url,status:`failed`,error:n instanceof Error?n.message:String(n),httpStatus:t?.status,warnings:t?.warnings??[],wordCount:void 0,fetchDiagnostics:t?.diagnostics,contentGapScore:0,queryTerms:vv(e.query),fields:{title:bv(e.query),h1:bv(e.query),metaDescription:bv(e.query),mainContent:bv(e.query)},classification:`fetch-failed`,signals:r,recommendation:`Verification failed; inspect fetch diagnostics before making content calls.`,summary:`Content verification failed.`}}}function Mv(e){return Math.max(1,Math.min(10,Math.round(e)))}function Nv(e){return J_[Mv(e)]??.01}function Pv(e,t){if(e.length===0)return;let n=[...e].sort((e,t)=>e-t);return n[Math.min(n.length-1,Math.max(0,Math.ceil(t/100*n.length)-1))]}function Fv(e){let t=e.rows.filter(t=>!e.excludedRows?.has(t)),n=t.reduce((e,t)=>e+t.impressions,0),r=new Map;for(let e of t){let t=e.keys?.[1]||`${e.keys?.[0]??`query`}:${e.position}`,n=r.get(t)??{clicks:0,impressions:0};n.clicks+=e.clicks,n.impressions+=e.impressions,r.set(t,n)}let i=[...r.values()].filter(e=>e.impressions>=30),a=i.map(e=>e.clicks/e.impressions),o=a.filter(e=>e>0),s=i.reduce((e,t)=>e+t.impressions,0),c=s>=1e3&&a.length>=5&&o.length>=3,l=Nv(e.position),u=e.samplePopulation??`positive_url_samples`,d=c?Pv(u===`all_qualified_url_samples`?a:o,75):void 0,f=l*.5,p=l*1.5,m=d!==void 0,h=m?Math.max(f,Math.min(d,p)):l,g=m&&d<f?`_floored`:m&&d>p?`_capped`:``,_=m?`site_gsc_position_bucket_robust_p75${u===`all_qualified_url_samples`?`_all_samples`:``}${e.sourceSuffix??``}${g}`:e.fallbackSource??`default_position_curve`;return{ctr:Number(h.toFixed(4)),source:_,samplePopulation:u,rows:t.length,impressions:n,qualifiedImpressions:s,urlSamples:a.length,positiveUrlSamples:o.length}}function Iv(e){let t={},n=1/0;for(let r of Object.keys(J_).map(Number)){let i=e[r]??Fv({rows:[],position:r}),a=Math.min(i.ctr,n),o=a<i.ctr;n=a,t[String(r)]={...i,ctr:Number(a.toFixed(4)),source:o?`${i.source}_monotonic_smoothed`:i.source}}return t}function Lv(e,t={}){let n=new Map;for(let t of e){let e=Mv(t.position);n.set(e,[...n.get(e)??[],t])}let r={};for(let e of Object.keys(J_).map(Number))r[e]=Fv({rows:n.get(e)??[],position:e,samplePopulation:t.samplePopulation,fallbackSource:t.fallbackSource});let i=Iv(r);function a(e,r,a){let o=Mv(e.position),s=new Set(r),c={};for(let e of Object.keys(J_).map(Number))c[e]=Fv({rows:n.get(e)??[],position:e,excludedRows:s,sourceSuffix:a,samplePopulation:t.samplePopulation,fallbackSource:t.fallbackSource});return Iv(c)[String(o)]??i[String(o)]??Fv({rows:[],position:o})}return{byPosition:i,forRow(e){return a(e,[e],`_leave_one_out`)},forUrl(t){let n=t.keys?.[1];return a(t,n?e.filter(e=>b_(e.keys?.[1]??``,n)):[t],`_leave_target_url_out`)},forAggregate(e,t=[]){return a(e,t,t.length?`_leave_group_out`:void 0)}}}function Rv(e){let t=S_(e);return t.length>0?{status:`technical-check`,reason:`Fetch or indexability evidence needs checking before making an on-page recommendation.`,signals:t,httpStatus:e.httpStatus}:e.page?{status:`verified`,reason:`The recommendation uses extracted on-page evidence.`,signals:[],httpStatus:e.httpStatus,fields:Av(e.query,e.page)}:{status:`unverified`,reason:e.fetchDiagnostics?.fetched?`The page was fetched, but extracted content was not available for verification.`:`On-page content was not verified for this analysis.`,signals:[],httpStatus:e.httpStatus}}function zv(e){if(e.verification.status===`technical-check`)return`technical-check`;if(e.verification.status===`verified`&&e.verification.fields){let t=Tv(e.verification.fields);if(t!==`covered`)return t}return e.position>10?`ranking`:e.expectedCtr!==void 0&&e.ctr<e.expectedCtr*.65?`ctr`:e.verification.status===`verified`?`covered`:`unverified`}function Bv(e){return e.type===`technical-check`?`Check fetchability, indexability, redirects, and canonical signals for this URL before changing content for "${e.query}".`:e.type===`unverified`?`${e.position>10?`GSC shows the query outside page one.`:`GSC shows page-one visibility.`} Verify the live page and current SERP intent before deciding whether content, internal links, or snippet wording needs work.`:e.type===`content-gap`?`The extracted copy does not clearly cover the main terms in "${e.query}". Confirm the search intent, then improve the existing answer if the query belongs on this page.`:e.type===`serp-framing`?`The extracted body covers "${e.query}", but the title, meta description, or H1 does not reflect it clearly. Check the live SERP before testing more precise framing.`:e.type===`ranking`?`This query ranks outside page one, so no CTR lift is claimed. Check intent fit, content quality, internal links, and competing pages before choosing an edit.`:e.type===`ctr`?`This page-one query is below its directional CTR benchmark. Review the live SERP, then test title and meta-description framing without assuming the body needs rewriting.`:`The extracted page covers "${e.query}" and its CTR is not materially below the benchmark. Avoid a blind rewrite; monitor the query and inspect the SERP for format changes.`}function Vv(e,t){return e?{applicable:!0,expectedCtr:e.ctr,source:e.source,peerRows:e.rows,peerImpressions:e.impressions,qualifiedPeerImpressions:e.qualifiedImpressions,urlSamples:e.urlSamples,positiveUrlSamples:e.positiveUrlSamples,excludedTargetRows:t}:{applicable:!1,source:`not_applicable_outside_page_one`,peerRows:0,peerImpressions:0,qualifiedPeerImpressions:0,urlSamples:0,positiveUrlSamples:0,excludedTargetRows:t}}function Hv(e){return/\p{L}/u.test(e)}function Uv(e){return/\d/.test(e)}function Wv(e){let t=e.toLowerCase(),n=(t.match(/(?:^|\s)-?(?:site|filetype):/g)??[]).length;return e.length>120&&(t.includes(`x_keyword_search`)||t.includes(`min_faves`)||t.includes(`filter:`)||t.includes(` since:`)||t.includes(` until:`)||n>=3)}function Gv(e){let t=e.toLowerCase();return/(?:^|\s)-?(?:site|filetype|inurl|intitle|intext):\S+/.test(t)}function Kv(e){let t=e.toLowerCase();return t.includes(`ranking/`)||/\bmoved up\b/.test(t)}function qv(e){return/^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/i.test(e.trim())}function Jv(e){return!Z_(e).length||Gv(e)||Wv(e)||Kv(e)||qv(e)}function Yv(e){let t=Z_(e);if(!t.length||!Hv(e)||Jv(e))return!0;let n=t.filter(Hv);return!!(t.length===1&&Uv(t[0]??``)||n.length<=1&&t.some(Uv))}function Xv(e,t,n,r){return e===void 0||!Number.isFinite(e)?t:Math.min(r,Math.max(n,Math.floor(e)))}function Zv(e){try{let t=new URL(e);return t.protocol===`http:`||t.protocol===`https:`}catch{return!1}}function Qv(e){return Number.isFinite(e.clicks)&&Number.isFinite(e.impressions)&&Number.isFinite(e.ctr)&&Number.isFinite(e.position)&&e.clicks>=0&&e.impressions>0&&e.clicks<=e.impressions&&e.ctr>=0&&e.ctr<=1&&e.position>0}function $v(e){return U_({query:e.query,siteUrl:e.site,brandTerms:e.brandTerms,includeBrand:e.includeBrand})}function ey(e){return{minImpressions:Xv(e.minImpressions,10,1,2**53-1),limit:Xv(e.limit,25,1,100)}}function ty(e){let t={sourceRows:e.rows.length,invalidRows:0,wrongPageRows:0,belowMinimumRows:0,lowActionabilityRows:0,brandRows:0,eligibleRows:0,returnedRows:0,limitedRows:0},n=[];for(let r of e.rows){let i=r.keys[0]?.trim()??``,a=r.keys[1]??``;if(r.keys.length<2||!i||!Zv(a)||!Qv(r)){t.invalidRows++;continue}if(!b_(a,e.url)){t.wrongPageRows++;continue}if(r.impressions<e.minImpressions){t.belowMinimumRows++;continue}if(Yv(i)){t.lowActionabilityRows++;continue}if($v({query:i,site:e.site,brandTerms:e.brandTerms,includeBrand:e.includeBrand})){t.brandRows++;continue}t.eligibleRows++,n.push(r)}return{eligibleRows:n,selection:t}}function ny(e){return e.rows.filter(t=>{let n=t.keys[0]?.trim()??``;return t.keys.length>=2&&Zv(t.keys[1]??``)&&Qv(t)&&t.position>=1&&t.position<=10&&!!n&&!Yv(n)&&!$v({query:n,site:e.site,brandTerms:e.brandTerms,includeBrand:e.includeBrand})})}function ry(e,t){return e<t?-1:+(e>t)}function iy(e,t){return(t.estimatedCtrClickShortfall??-1)-(e.estimatedCtrClickShortfall??-1)||t.impressions-e.impressions||e.position-t.position||ry(e.query,t.query)||ry(e.url,t.url)}function ay(e){let t=e.row.keys[0]?.trim()??``,n=e.row.position>=1&&e.row.position<=10?e.benchmarkContext.forAggregate(e.row,e.excludedTargetRows):void 0,r=n?Number((n.ctr*e.row.impressions).toFixed(2)):void 0,i=n?Number(Math.max(0,n.ctr*e.row.impressions-e.row.clicks).toFixed(2)):void 0,a=Rv({query:t,url:e.reportInput.url,page:e.reportInput.page,fetchDiagnostics:e.reportInput.fetchDiagnostics,httpStatus:e.reportInput.httpStatus}),o=zv({position:e.row.position,ctr:e.row.ctr,expectedCtr:n?.ctr,verification:a});return{query:t,url:e.row.keys[1]??e.reportInput.url,clicks:e.row.clicks,impressions:e.row.impressions,ctr:e.row.ctr,position:e.row.position,expectedCtr:n?.ctr,expectedClicks:r,estimatedCtrClickShortfall:i,estimatedClickLift:i,opportunityType:o,benchmark:Vv(n,e.excludedTargetRows.length),verification:a,recommendation:Bv({query:t,position:e.row.position,type:o})}}function oy(e){let{minImpressions:t,limit:n}=ey(e),{eligibleRows:r,selection:i}=ty({rows:e.targetRows,site:e.site,url:e.url,minImpressions:t,brandTerms:e.brandTerms,includeBrand:e.includeBrand}),a=ny({rows:e.benchmarkRows,site:e.site,brandTerms:e.brandTerms,includeBrand:e.includeBrand}),o=a.filter(t=>b_(t.keys[1]??``,e.url)),s=Lv(a),c=r.map(t=>ay({row:t,reportInput:e,benchmarkContext:s,excludedTargetRows:o})).sort(iy).slice(0,n),l=Number(c.reduce((e,t)=>e+(t.estimatedCtrClickShortfall??0),0).toFixed(2));return{site:e.site,url:e.url,minImpressions:t,limit:n,httpStatus:e.httpStatus,dataStatus:e.targetRows.length===0?`empty`:r.length===0?`filtered`:`available`,sourceRows:e.targetRows.length,eligibleRows:r.length,returnedRows:c.length,benchmarkSourceRows:e.benchmarkRows.length,benchmarkEligibleRows:a.length,excludedTargetBenchmarkRows:o.length,selection:{...i,returnedRows:c.length,limitedRows:Math.max(0,r.length-c.length)},items:c,summary:{clicks:c.reduce((e,t)=>e+t.clicks,0),impressions:c.reduce((e,t)=>e+t.impressions,0),opportunities:c.filter(e=>e.opportunityType!==`covered`&&e.opportunityType!==`unverified`).length,estimatedCtrClickShortfall:l,estimatedClickLift:l}}}const sy=new Map([`technical-check`,`content-gap`,`serp-framing`,`ranking`,`ctr`,`unverified`,`covered`].map((e,t)=>[e,t]));function cy(e,t,n){return e===1?t:n}function ly(e){return e.opportunityType!==`covered`&&e.opportunityType!==`unverified`}function uy(e,t){return(sy.get(e.opportunityType)??99)-(sy.get(t.opportunityType)??99)||(t.estimatedCtrClickShortfall??-1)-(e.estimatedCtrClickShortfall??-1)||t.impressions-e.impressions||(e.query<t.query?-1:+(e.query>t.query))}function dy(e){if(!e.length)return`no-data`;if(e.some(e=>e.opportunityType===`technical-check`))return`technical-check`;let t=new Map;for(let n of e)t.set(n.opportunityType,(t.get(n.opportunityType)??0)+1);return[...t.entries()].sort(([e,t],[n,r])=>r-t||(sy.get(e)??99)-(sy.get(n)??99))[0]?.[0]??`no-data`}function fy(e){let t=[e.belowMinimumRows?`${e.belowMinimumRows} below the impression threshold`:void 0,e.brandRows?`${e.brandRows} branded`:void 0,e.lowActionabilityRows?`${e.lowActionabilityRows} low-actionability`:void 0,e.invalidRows?`${e.invalidRows} invalid`:void 0,e.wrongPageRows?`${e.wrongPageRows} for another URL`:void 0].filter(e=>!!e);return t.length?` Rows removed: ${t.join(`, `)}.`:``}function py(e){let{analysis:t,focus:n}=e;if(t.dataStatus===`empty`)return`No GSC query rows were found for this exact URL in the selected window.`;if(t.dataStatus===`filtered`)return`GSC returned rows for this URL, but none met the report criteria.${fy(t.selection)}`;if(n===`technical-check`)return`Technical fetch or indexability evidence needs checking before content or snippet changes.`;if(n===`unverified`)return`GSC visibility exists, but the page was not verified and no specific change is justified yet.`;if(n===`covered`)return`The verified page covers the sampled queries and CTR is not materially below its directional benchmark.`;let r=t.summary.opportunities,i=`${r} ${cy(r,`actionable observation`,`actionable observations`)} found.`;return n===`content-gap`?`${i} Verified body coverage is the main issue; confirm intent before expanding the page.`:n===`serp-framing`?`${i} The body is relevant, but its title, meta description, or H1 framing is weak.`:n===`ranking`?`${i} Most upside requires stronger relevance or authority; no CTR-only lift is claimed for page-two queries.`:`${i} The page-one CTR shortfall is about ${t.summary.estimatedCtrClickShortfall.toFixed(0)} clicks against the directional benchmark.`}function my(e){let t=e.items.filter(ly).sort(uy),n=t[0];if(!n)return e.items.some(e=>e.opportunityType===`unverified`)?[`Verify the live page and current SERP before turning these GSC observations into an editing brief.`]:e.items.length?[`Avoid an unnecessary rewrite. Monitor the queries and inspect current SERP formats before changing the page.`]:[`No page-level action is justified from this result. Lower the impression threshold only when long-tail inspection is useful.`];let r=[`Start with "${n.query}". ${n.recommendation}`],i=t.filter(e=>e.opportunityType===`technical-check`).length;i&&r.push(`Resolve the technical evidence affecting ${i} ${cy(i,`row`,`rows`)} before content work.`);let a=t.filter(e=>e.opportunityType===`content-gap`).length;return a>=3&&r.push(`Group the ${a} verified content gaps into useful sections rather than adding one thin paragraph per query.`),r}const hy=1e5,gy={searchAnalytics:J,fetch:Hg,extract:Lh,now:()=>new Date};function _y(e){return e instanceof Error?e.message:String(e)}function vy(e){return[...new Set(e.filter(Boolean))]}function yy(e){if(e===void 0)return 28;if(!Number.isInteger(e)||e<1||e>548)throw new k(`INVALID_INPUT`,`Days must be a whole number between 1 and 548.`);return e}async function by(e){let t=[],n;try{n=await e.dependencies.fetch(e.url,{js:e.js??`auto`,refresh:e.refresh}),t.push(...n.warnings)}catch(e){let t=`Page fetch failed: ${_y(e)}`;return{verification:{status:`failed`,reason:t},warnings:[t]}}try{let r=await e.dependencies.extract(n);return t.push(...r.warnings),{fetched:n,page:r,verification:{status:`verified`,reason:`The live page was fetched and extracted for on-page checks.`},warnings:vy(t)}}catch(e){let r=`Page extraction failed: ${_y(e)}`;return{fetched:n,verification:{status:`failed`,reason:r},warnings:vy([...t,r])}}}function xy(e){let{analysis:t,reportInput:n,range:r}=e;return[`Date window: ${r.startDate} to ${r.endDate}, using final GSC data where available.`,`Selection: ${t.selection.sourceRows} exact-URL rows, ${t.selection.eligibleRows} eligible, ${t.selection.returnedRows} returned.`,`Minimum query impressions: ${t.minImpressions}.`,`Brand filtering: ${n.includeBrand?`brand queries included`:`brand queries excluded when detected or configured`}.`,`Content verification: ${e.verification.status} (${e.verification.reason})`,`CTR benchmarks are directional. GSC query data omits anonymized queries and does not explain SERP features or intent changes.`,e.benchmarkTruncated?`The site-wide benchmark reached its ${hy.toLocaleString(`en-US`)}-row cap, so provenance is complete but the peer sample is truncated.`:``].filter(Boolean)}async function Sy(e,t=gy){let n=Gg(e.site,e.url),r=yy(e.days),i=$_(r),a=await t.searchAnalytics(e.site,{...i,dimensions:[`query`,`page`],type:`web`,dataState:`final`,dimensionFilterGroups:[{groupType:`and`,filters:[{dimension:`page`,operator:`equals`,expression:n}]}]},{refresh:e.refresh}),o=oy({targetRows:a.rows,benchmarkRows:[],site:e.site,url:n,minImpressions:e.minImpressions,limit:e.limit,brandTerms:e.brandTerms,includeBrand:e.includeBrand}),s=(e.verifyContent??!0)&&o.eligibleRows>0?by({url:n,refresh:e.refresh,js:e.js,dependencies:t}):Promise.resolve({verification:{status:`skipped`,reason:o.eligibleRows===0?`No eligible query rows required page verification.`:`Page verification was disabled.`},warnings:[]}),c=o.eligibleRows>0?t.searchAnalytics(e.site,{...i,dimensions:[`query`,`page`],type:`web`,dataState:`final`,maxRows:hy},{refresh:e.refresh}):Promise.resolve({rows:[],calls:0,rowsFetched:0}),[l,u]=await Promise.all([s,c]),d=oy({targetRows:a.rows,benchmarkRows:u.rows,site:e.site,url:n,minImpressions:e.minImpressions,limit:e.limit,brandTerms:e.brandTerms,includeBrand:e.includeBrand,page:l.page,fetchDiagnostics:l.fetched?.diagnostics,httpStatus:l.fetched?.status}),f=dy(d.items),p=py({analysis:d,focus:f}),m=u.rowsFetched>=hy;return{site:e.site,url:n,generatedAt:t.now().toISOString(),range:i,rangeDays:r,source:{provider:`google-search-console`,dimensions:[`query`,`page`],searchType:`web`,dataState:`final`,targetRowsFetched:a.rowsFetched,targetCalls:a.calls},dataStatus:d.dataStatus,selection:d.selection,verification:l.verification,benchmark:{sourceRows:d.benchmarkSourceRows,eligibleRows:d.benchmarkEligibleRows,excludedTargetRows:d.excludedTargetBenchmarkRows,rowsFetched:u.rowsFetched,calls:u.calls,maxRows:hy,possiblyTruncated:m},page:l.page&&l.fetched?{finalUrl:l.page.finalUrl,status:l.fetched.status,title:l.page.title,h1:l.page.headings.find(e=>e.level===1)?.text,wordCount:l.page.wordCount,fetchDiagnostics:l.fetched.diagnostics}:void 0,summary:{queries:d.returnedRows,clicks:d.summary.clicks,impressions:d.summary.impressions,opportunities:d.summary.opportunities,estimatedCtrClickShortfall:d.summary.estimatedCtrClickShortfall,estimatedClickLift:d.summary.estimatedCtrClickShortfall,verdict:p,focus:f},items:d.items,warnings:l.warnings,caveats:xy({reportInput:e,analysis:d,range:i,benchmarkTruncated:m,verification:l.verification}),recommendations:my(d)}}function Cy(e){let t=e.toLowerCase();return/\b(vs|versus|alternative|alternatives|compare|comparison|best)\b/.test(t)?`comparison`:/\b(price|pricing|cost|buy|deal|coupon|review|reviews)\b/.test(t)?`commercial`:/\b(how|steps|guide|tutorial|setup|fix|create|make)\b/.test(t)?`how-to`:/\b(what is|meaning|definition|explained|why)\b/.test(t)?`definition`:/\b(near me|city|state|county|postcode|zip|london|new york)\b/.test(t)?`local`:/\b(login|sign in|app|dashboard|support)\b/.test(t)?`navigational`:`general`}function wy(e){if(!e.items.length)return 0;let t=e.summary.opportunities/e.items.length,n=e.items.filter(e=>e.opportunityType===`content-gap`).length*8,r=e.items.filter(e=>e.opportunityType===`serp-framing`).length*5,i=e.items.filter(e=>e.opportunityType===`ctr`).length*4;return Math.max(0,Math.min(100,Math.round(100-t*40-n-r-i)))}function Ty(e){return e.split(/\s+/).filter(Boolean).map(e=>`${e[0]?.toUpperCase()??``}${e.slice(1)}`).join(` `)}function Ey(e){return e.estimatedCtrClickShortfall??e.estimatedClickLift??0}function Dy(e,t){return e===`comparison`?`${Ty(t)}: what to choose`:e===`how-to`?`How to ${t.replace(/^how to\s+/i,``)}`:e===`definition`?`${Ty(t)}, in plain English`:e===`commercial`?`${Ty(t)}: costs and trade-offs`:Ty(t)}function Oy(e){let t=new Map;for(let n of e.items){let e=Cy(n.query),r=t.get(e)??{queries:0,impressions:0,clicks:0};r.queries+=1,r.impressions+=n.impressions,r.clicks+=n.clicks,t.set(e,r)}return[...t.entries()].map(([e,t])=>({intent:e,...t})).sort((e,t)=>t.impressions-e.impressions)}function ky(e){let t=e.items.filter(e=>e.opportunityType!==`covered`&&e.opportunityType!==`technical-check`&&e.opportunityType!==`unverified`).sort((e,t)=>Ey(t)-Ey(e)),n=t[0],r=t.filter(e=>e.verification.status===`verified`&&(e.opportunityType===`content-gap`||e.opportunityType===`ranking`)),i=r.slice(0,5).map(e=>{let t=Cy(e.query);return{heading:Dy(t,e.query),why:e.recommendation,queries:[e.query,...r.filter(n=>n.query!==e.query&&Cy(n.query)===t).slice(0,3).map(e=>e.query)]}});return{titleAngle:n?`Make the title clearly promise the "${n.query}" answer.`:void 0,h1Angle:n?`Keep the H1 aligned with the page topic, then use subheadings for "${n.query}" variants.`:void 0,metaAngle:n?`Use the meta description to state the outcome, trade-off, or direct answer searchers get.`:void 0,sections:i,internalLinkAnchors:r.slice(0,8).map(e=>e.query).filter((e,t,n)=>n.indexOf(e)===t)}}function Ay(e){let t=e.items.filter(e=>e.opportunityType===`technical-check`),n=e.items.filter(e=>e.opportunityType===`unverified`),r=e.items.filter(e=>e.opportunityType===`content-gap`),i=e.items.filter(e=>e.opportunityType===`serp-framing`||e.opportunityType===`ctr`),a=e.items.filter(e=>e.opportunityType===`ranking`),o=[];if(t.length&&o.push({title:`Resolve technical evidence first`,plainEnglish:`${t.length} query observation is attached to a URL with conflicting fetch or indexability evidence.`,action:`Check status, redirects, robots directives, noindex, and canonical signals before producing a content brief.`,queries:t.slice(0,8).map(e=>e.query)}),r.length&&o.push({title:`Add missing answer coverage`,plainEnglish:`${r.length} query angle needs clearer body coverage.`,action:`Add one useful section that directly answers the grouped query angles. Do not add one thin paragraph per query.`,queries:r.slice(0,8).map(e=>e.query)}),i.length&&o.push({title:`Tighten the SERP framing`,plainEnglish:`${i.length} query angle looks covered but under-framed in title, H1, or meta copy.`,action:`Test clearer title/meta wording before rewriting the body. Keep the promise specific and human.`,queries:i.slice(0,8).map(e=>e.query)}),a.length&&o.push({title:`Strengthen relevance and links`,plainEnglish:`${a.length} query angle is still too low for a simple CTR change.`,action:`Improve the relevant answer block and add internal links from pages that already cover related demand.`,queries:a.slice(0,8).map(e=>e.query)}),!o.length){if(n.length)return o.push({title:`Verify the page before editing`,plainEnglish:`GSC visibility exists, but live content evidence was not available.`,action:`Fetch and inspect the live page and current SERP before turning these observations into a content brief.`,queries:n.slice(0,8).map(e=>e.query)}),o;o.push({title:`Avoid unnecessary content expansion`,plainEnglish:`The sampled queries look mostly covered. More copy may dilute the page.`,action:`Review title/meta tests, internal links, and SERP format before adding new sections.`,queries:e.items.slice(0,5).map(e=>e.query)})}return o}function jy(e){let t=Oy(e),n=t[0]?.intent??`general`,r=e.items.filter(e=>e.opportunityType!==`covered`&&e.opportunityType!==`technical-check`&&e.opportunityType!==`unverified`).sort((e,t)=>Ey(t)-Ey(e))[0]?.query??e.items[0]?.query;return{site:e.site,url:e.url,generatedAt:e.generatedAt,sourceReport:e,summary:{score:wy(e),primaryIntent:n,primaryQuery:r,queries:e.summary.queries,opportunities:e.summary.opportunities,estimatedClickLift:e.summary.estimatedClickLift,verdict:e.summary.verdict},intentMix:t,brief:ky(e),topActions:Ay(e),caveats:e.caveats}}async function My(e){return jy(await Sy(e))}const Ny={high:3,medium:2,low:1};function Py(e,t={}){let n=new Map(e.pages.flatMap(e=>[[e.url,e],[e.finalUrl,e]])),r=e.issues.filter(e=>Fy(e,t)).map(e=>{let t=n.get(e.url);return{url:e.url,ruleId:e.ruleId,title:e.title,category:e.category,severity:e.severity,detail:e.detail,evidence:e.evidence,clicks:e.searchMetrics?.clicks??t?.searchMetrics?.clicks??0,impressions:e.searchMetrics?.impressions??t?.searchMetrics?.impressions??0,sessions:t?.analytics?.sessions??0}}).sort((e,t)=>Ny[t.severity]-Ny[e.severity]||t.clicks-e.clicks||t.impressions-e.impressions||t.sessions-e.sessions||e.url.localeCompare(t.url)),i=t.limit??100,a=r.slice(0,i);return{affectedUrls:a,selection:{totalMatchedRows:r.length,returnedRows:a.length,limit:i,truncated:a.length<r.length}}}function Fy(e,t){return!(t.ruleId&&e.ruleId!==t.ruleId||t.category&&e.category!==t.category||t.severity&&e.severity!==t.severity)}const Iy=2e6;function Ly(e){return y(`sha256`).update(e).digest(`hex`)}function Ry(e,t){if(!e)return;let n=t.toLowerCase();return Object.entries(e).find(([e])=>e.toLowerCase()===n)?.[1]}function zy(e,t){return e?e.split(/,(?=\s*<)/u).map(e=>{let n=e.match(/^\s*<([^>]+)>\s*(.*)$/u);if(n?.[1])try{let e=new URL(n[1],t).toString(),r=n[2]??``,i=r.match(/(?:^|;)\s*rel=(?:"([^"]+)"|([^;\s]+))/iu),a=r.match(/(?:^|;)\s*type=(?:"([^"]+)"|([^;\s]+))/iu);return{url:e,rel:(i?.[1]??i?.[2]??``).toLowerCase().split(/\s+/u),type:(a?.[1]??a?.[2]??``).toLowerCase()}}catch{return}}).filter(e=>!!e):[]}function By(e){let t=new URL(e);return t.hash=``,t.pathname!==`/`&&(t.pathname=t.pathname.replace(/\/$/u,``)),t.toString()}function Vy(e,t){if(!e||!t)return null;try{return By(e)===By(t)}catch{return!1}}function Hy(e){return e.replace(/^---[\s\S]*?---\s*/u,``).replace(/```[\s\S]*?```/gu,` `).replace(/[#>*_`[\]()|:-]/gu,` `).split(/\s+/u).filter(Boolean).length}function Uy(e){let t=new Map,n=!1,r;for(let[i,a]of e.split(/\r?\n/u).entries()){let e=a.trim();if(i===0&&e===`---`){n=!0;continue}if(n){e===`---`&&(n=!1);continue}if(!r&&(e.startsWith("```")||e.startsWith(`~~~`))){r=e.startsWith("```")?"```":`~~~`;continue}if(r){e.startsWith(r)&&(r=void 0);continue}e.length<40||/^(?:#{1,6}\s|[-+*]\s|\d+[.)]\s|>|\||`|\]\()/u.test(e)||t.set(e,(t.get(e)??0)+1)}return[...t.values()].reduce((e,t)=>e+Math.max(0,t-1),0)}function Wy(e,t){return(e.headers.get(`x-robots-tag`)??``).toLowerCase().includes(`noindex`)?!0:[...t.matchAll(/<meta\s+[^>]*>/giu)].some(e=>{let t=e[0],n=t.match(/\bname\s*=\s*["']([^"']+)["']/iu)?.[1],r=t.match(/\bcontent\s*=\s*["']([^"']+)["']/iu)?.[1];return n?.toLowerCase()===`robots`&&/\bnoindex\b/iu.test(r??``)})}function Gy(e,t){let n=(t.contentSample?.replace(/\s+/gu,` `).trim())?.split(` `).slice(0,10).join(` `),r=e.replace(/\s+/gu,` `),i=e.match(/^```/gmu)?.length??0,a=Hy(e),o=t.wordCount??0;return{frontmatterTitle:/^---[\s\S]*?^title:\s*.+$/mu.test(e),h1Count:e.match(/^#\s+\S.+$/gmu)?.length??0,codeFenceBalanced:i%2==0,tableRows:e.match(/^\s*\|.*\|\s*$/gmu)?.length??0,links:e.match(/\[[^\]]+\]\([^)]+\)/gu)?.length??0,wordCount:a,rawHtmlTags:e.match(/<(?:div|span|section|article|nav|button)\b/giu)?.length??0,rawSvgTags:e.match(/<svg\b/giu)?.length??0,rawScriptTags:e.match(/<script\b/giu)?.length??0,rawStyleTags:e.match(/<style\b/giu)?.length??0,suspiciousConcatenations:e.match(/\b[a-z]{4,}[A-Z][A-Za-z]{3,}\b/gu)?.length??0,repeatedLines:Uy(e),sourceWordCount:o,wordRetentionRatio:o>0?Math.round(a/o*1e3)/1e3:null,introductoryCopyRetained:n?r.includes(n):null,navigationOnly:Hy(e)<25}}function Ky(e,t){let n=new AbortController,r=()=>n.abort(),i=setTimeout(r,e);return t?.aborted?n.abort():t?.addEventListener(`abort`,r,{once:!0}),{signal:n.signal,cleanup:()=>{clearTimeout(i),t?.removeEventListener(`abort`,r)}}}async function qy(e){if(!e.body)return``;let t=e.body.getReader(),n=[],r=0;try{for(;;){let e=await t.read();if(e.done)break;if(r+=e.value.byteLength,r>Iy)throw Error(`Response exceeds ${Iy} bytes.`);n.push(e.value)}}finally{await t.cancel().catch(()=>void 0)}return Buffer.concat(n).toString(`utf8`)}async function Jy(e){let t=Ky(e.timeoutMs,e.signal);try{let n=await e.fetch(e.url,{profile:`bot`,redirect:`follow`,headers:{accept:e.accept},signal:t.signal}),r=await qy(n),i=zy(n.headers.get(`link`)??void 0,e.url),a=Number(n.headers.get(`x-markdown-tokens`));return{observation:{requestedUrl:e.url,finalUrl:n.url||e.url,status:n.status,contentType:n.headers.get(`content-type`)??void 0,bytes:Buffer.byteLength(r),sha256:Ly(r),canonicalUrl:i.find(e=>e.rel.includes(`canonical`))?.url,alternateUrl:i.find(e=>e.rel.includes(`alternate`)&&e.type===`text/markdown`)?.url,varyAccept:(n.headers.get(`vary`)??``).split(`,`).some(e=>e.trim().toLowerCase()===`accept`),...Number.isInteger(a)&&a>=0?{markdownTokens:a}:{},contentSignal:n.headers.get(`content-signal`)??void 0},body:r}}catch(t){return{observation:{requestedUrl:e.url,varyAccept:!1,error:t instanceof Error?t.message:String(t)}}}finally{t.cleanup()}}async function Yy(e){let t=[...new Set(e.page.markdownAlternates??[])].sort(),n=zy(Ry(e.page.responseHeaders,`link`),e.page.finalUrl).filter(e=>e.rel.includes(`alternate`)&&e.type===`text/markdown`).map(e=>e.url).sort(),r={htmlUrl:e.page.finalUrl,advertisedUrls:t,htmlAlternateUnique:t.length===1,httpAlternateUrls:n,explicitMatchesNegotiated:null,repeatedHashStable:null,markdownCanonicalMatchesHtml:null};if(t.length!==1)return r;let i=t[0],[a,o,s]=await Promise.all([Jy({url:i,accept:`text/markdown`,timeoutMs:e.timeoutMs,fetch:e.fetch,signal:e.signal}),Jy({url:e.page.finalUrl,accept:`text/markdown`,timeoutMs:e.timeoutMs,fetch:e.fetch,signal:e.signal}),Jy({url:i,accept:`text/markdown`,timeoutMs:e.timeoutMs,fetch:e.fetch,signal:e.signal})]);return r.explicit=a.observation,r.negotiated=o.observation,r.repeated=s.observation,r.explicitMatchesNegotiated=a.observation.sha256&&o.observation.sha256?a.observation.sha256===o.observation.sha256:null,r.repeatedHashStable=a.observation.sha256&&s.observation.sha256?a.observation.sha256===s.observation.sha256:null,r.markdownCanonicalMatchesHtml=Vy(a.observation.canonicalUrl,e.page.finalUrl),a.body&&(r.quality=Gy(a.body,e.page)),r}async function Xy(e){let t=Ky(e.timeoutMs,e.signal);try{let n=await e.fetch(e.url,{profile:`bot`,redirect:e.redirect??`follow`,headers:e.accept?{accept:e.accept}:void 0,signal:t.signal});return{response:n,body:await qy(n)}}finally{t.cleanup()}}function Zy(e){return e instanceof Error?e.message:String(e)}async function Qy(e){let t=new URL(`/.well-known/agent-skills/index.json`,e.origin).toString();try{let{response:n,body:r}=await Xy({url:t,timeoutMs:e.timeoutMs,fetch:e.fetch,signal:e.signal,accept:`application/json`}),i=n.headers.get(`content-type`)??void 0,a=n.headers.get(`access-control-allow-origin`)??void 0;if(n.status<200||n.status>=300)return{indexUrl:t,status:n.status,contentType:i,validIndex:!1,skills:[]};let o=JSON.parse(r),s=Array.isArray(o.skills)?o.skills.slice(0,25):[],c=await Promise.all(s.map(async n=>{let r=typeof n.name==`string`?n.name:void 0,i=typeof n.digest==`string`?n.digest:void 0,a=typeof n.url==`string`?n.url:void 0;if(!a)return{name:r,declaredDigest:i,digestMatches:null,frontmatterValid:null,sameOrigin:!1,error:`Skill URL is missing.`};let o=new URL(a,t).toString(),s=new URL(o).origin===new URL(e.origin).origin;if(!s)return{name:r,url:o,declaredDigest:i,digestMatches:null,frontmatterValid:null,sameOrigin:s,error:`Skill URL is off origin.`};try{let t=await Xy({url:o,timeoutMs:e.timeoutMs,fetch:e.fetch,signal:e.signal,accept:`text/markdown,text/plain;q=0.9`}),n=`sha256:${Ly(t.body)}`,a=/^---\s*\n[\s\S]*?^name:\s*\S.+$[\s\S]*?^description:\s*\S.+$[\s\S]*?^---\s*$/mu.test(t.body);return{name:r,url:o,status:t.response.status,contentType:t.response.headers.get(`content-type`)??void 0,cors:t.response.headers.get(`access-control-allow-origin`)??void 0,declaredDigest:i,observedDigest:n,digestMatches:i?i===n:null,frontmatterValid:a,sameOrigin:s}}catch(e){return{name:r,url:o,declaredDigest:i,digestMatches:null,frontmatterValid:null,sameOrigin:s,error:Zy(e)}}}));return{indexUrl:t,status:n.status,contentType:i,cors:a,validIndex:typeof o.$schema==`string`&&Array.isArray(o.skills),skills:c}}catch(e){return{indexUrl:t,validIndex:!1,skills:[],error:Zy(e)}}}function $y(e,t){let n=[],r=[];for(let i of e.matchAll(/\[([^\u005d]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/gu))if(!(!i[1]||!i[2]))try{n.push({label:i[1],url:new URL(i[2],t).toString()})}catch{r.push(i[2])}return{links:n,invalidLinks:r}}async function eb(e){let t=new URL(`/llms.txt`,e.origin).toString();try{let[n,r]=await Promise.all([Xy({url:t,timeoutMs:e.timeoutMs,fetch:e.fetch,signal:e.signal,accept:`text/plain,text/markdown;q=0.9`}),Xy({url:t,timeoutMs:e.timeoutMs,fetch:e.fetch,signal:e.signal,accept:`text/plain,text/markdown;q=0.9`})]),i=n.response.status>=200&&n.response.status<300,a=$y(n.body,t),o=a.links.slice(0,100),s=a.links.length>o.length,c=new Map;for(let e of o)c.set(e.url,(c.get(e.url)??0)+1);let l=[...c].filter(([,e])=>e>1).map(([e])=>e).sort(),u=new Set(e.pages.map(e=>By(e.finalUrl))),d=await Promise.all(o.map(async t=>{let n=new URL(t.url).origin===new URL(e.origin).origin;try{let r=await Xy({url:t.url,timeoutMs:e.timeoutMs,fetch:e.fetch,signal:e.signal}),i=n&&r.response.status>=200&&r.response.status<300?!Wy(r.response,r.body):void 0,a=r.response.url||void 0;return{...t,sameOrigin:n,status:r.response.status,finalUrl:a,redirected:r.response.redirected,...i===void 0?{}:{indexableTarget:i}}}catch(e){return{...t,sameOrigin:n,redirected:!1,error:Zy(e)}}})),f=new Set(d.filter(e=>e.sameOrigin&&!new URL(e.url).pathname.startsWith(`/.well-known/`)&&!new URL(e.url).pathname.endsWith(`.md`)).map(e=>By(e.url)));return{url:t,exists:i,status:n.response.status,contentType:n.response.headers.get(`content-type`)??void 0,bytes:Buffer.byteLength(n.body),sha256:Ly(n.body),repeatedHashStable:Ly(n.body)===Ly(r.body),headingCount:n.body.match(/^#{1,2}\s+\S/gmu)?.length??0,totalParsedLinks:a.links.length,linkLimitReached:s,links:d,invalidLinks:a.invalidLinks.sort(),duplicateLinks:l,offSiteLinks:d.filter(e=>!e.sameOrigin).map(e=>e.url).sort(),redirectedLinks:d.filter(e=>e.redirected).map(e=>e.url).sort(),nonIndexableLinks:d.filter(e=>e.indexableTarget===!1).map(e=>e.url).sort(),missingCrawlRoutes:[...f].filter(e=>!u.has(e)).sort(),oversized:Buffer.byteLength(n.body)>1e5}}catch(e){return{url:t,exists:!1,repeatedHashStable:null,headingCount:0,totalParsedLinks:0,linkLimitReached:!1,links:[],invalidLinks:[],duplicateLinks:[],offSiteLinks:[],redirectedLinks:[],nonIndexableLinks:[],missingCrawlRoutes:[],oversized:!1,error:Zy(e)}}}async function tb(e){let t=new URL(`/agent-routes.json`,e.origin).toString();try{let n=await Xy({url:t,timeoutMs:e.timeoutMs,fetch:e.fetch,signal:e.signal,accept:`application/json`}),r=JSON.parse(n.body),i=Array.isArray(r.pages)?r.pages:[],a=i.map(e=>e.htmlPath).filter(e=>typeof e==`string`).sort(),o=i.map(e=>e.markdownPath).filter(e=>typeof e==`string`).sort(),s=new Set(e.pages.map(e=>new URL(e.finalUrl).pathname.replace(/\/$/u,``)||`/`)),c=new Set(e.observations.flatMap(e=>e.advertisedUrls.map(e=>new URL(e).pathname)));return{url:t,status:n.response.status,valid:Array.isArray(r.pages),declaredHtmlRoutes:a,declaredMarkdownRoutes:o,missingHtmlRoutes:a.filter(e=>!s.has(e.replace(/\/$/u,``)||`/`)).sort(),missingMarkdownRoutes:o.filter(e=>!c.has(e)).sort(),orphanMarkdownRoutes:[...c].filter(e=>!o.includes(e)).sort()}}catch(e){return{url:t,valid:!1,declaredHtmlRoutes:[],declaredMarkdownRoutes:[],missingHtmlRoutes:[],missingMarkdownRoutes:[],orphanMarkdownRoutes:[],error:Zy(e)}}}async function nb(e){try{let t=await Xy({url:e.url,timeoutMs:e.timeoutMs,fetch:e.fetch,signal:e.signal,redirect:`manual`});return{status:t.response.status,location:t.response.headers.get(`location`)??void 0}}catch(e){return{error:Zy(e)}}}async function rb(e){let t=new URL(e.startUrl).origin,n=e.pages.filter(e=>e.status>=200&&e.status<300&&/^\s*(?:text\/html|application\/xhtml\+xml)\b/iu.test(e.contentType??``)).sort((e,t)=>e.finalUrl.localeCompare(t.finalUrl)),r=new ae({concurrency:e.concurrency??4}),i=await Promise.all(n.map(t=>r.add(()=>Yy({page:t,timeoutMs:e.timeoutMs,fetch:e.fetch,signal:e.signal})))),[a,o,s]=await Promise.all([Qy({...e,origin:t}),eb({...e,origin:t,pages:n}),tb({...e,origin:t,pages:n,observations:i})]),c=await Jy({url:e.startUrl,accept:`text/markdown;q=0,text/html;q=1`,timeoutMs:e.timeoutMs,fetch:e.fetch,signal:e.signal}),l=new URL(e.startUrl),u=new URL(l);u.protocol=`http:`;let d=new URL(l);d.hostname=l.hostname.startsWith(`www.`)?l.hostname.slice(4):`www.${l.hostname}`;let[f,p]=await Promise.all([nb({...e,url:u.toString()}),nb({...e,url:d.toString()})]),m=f.location?new URL(f.location,u).toString():void 0,h=p.location?new URL(p.location,d).toString():void 0,g=c.observation.contentType,_=i.filter(e=>e.htmlAlternateUnique).length,v=i.filter(e=>e.explicit?.status!==void 0).length,y=[...n.length===e.pages.length?[]:[`Agent representations were evaluated for ${n.length} successful HTML pages from ${e.pages.length} retained documents.`],...s.valid?[]:[`No valid public agent route manifest was available for orphan checks.`]].sort(),b=n.map(e=>Ry(e.responseHeaders,`content-signal`)).filter(e=>!!e),x=i.map(e=>e.explicit?.contentSignal).filter(e=>!!e),ee=[...new Set([...b,...x])].sort();return{profile:`content`,profileApplicability:{content:{status:`evaluated`,reason:`This site publishes documents, so access, representations, discovery, identity, and extraction quality were evaluated.`},api:{status:`notApplicable`,reason:`No public API surface was selected for this content-site run.`},application:{status:`notApplicable`,reason:`A local MCP product does not make this website a public remote agent endpoint.`},commerce:{status:`notApplicable`,reason:`No product checkout or transactional surface was selected.`}},dataStatus:n.length===0?`unavailable`:v===n.length?`complete`:`partial`,markdownAlternates:{eligibleHtmlPages:n.length,advertisedPages:_,evaluatedPages:v,exactByteMatches:i.filter(e=>e.explicitMatchesNegotiated).length,stableResponses:i.filter(e=>e.repeatedHashStable).length,pages:i},contentNegotiation:{qZeroHonoured:g===void 0?null:!/^\s*text\/markdown\b/iu.test(g),qZeroStatus:c.observation.status,qZeroContentType:g,error:c.observation.error},routeManifest:s,agentSkills:a,llmsTxt:o,contentSignals:{htmlValues:[...new Set(b)].sort(),markdownValues:[...new Set(x)].sort(),missingHtmlPages:n.length-b.length,missingMarkdownPages:i.length-x.length,consistent:n.length>0&&i.length>0?ee.length===1&&b.length===n.length&&x.length===i.length:null},protocolVariants:{http:{url:u.toString(),status:f.status,location:m,permanentRedirectToHttps:f.status===void 0?null:[301,308].includes(f.status)&&!!m?.startsWith(`https://`),error:f.error},www:{url:d.toString(),status:p.status,location:h,redirectsToPreferredHost:p.status===void 0?null:[301,308].includes(p.status)&&!!(h&&new URL(h).hostname===l.hostname&&new URL(h).protocol===`https:`),error:p.error},hstsOnStartPage:n.find(t=>By(t.finalUrl)===By(e.startUrl))?.hasHsts??null},warnings:y}}function ib(e,t){return{section:e,...t}}function ab(e,t,n){return{id:e,title:t,checks:n}}function ob(e){return e.markdownAlternates.pages.filter(e=>!e.htmlAlternateUnique||e.httpAlternateUrls.length!==1||e.explicit?.status!==200||!/^\s*text\/markdown\b/iu.test(e.explicit.contentType??``)||e.explicitMatchesNegotiated!==!0||e.repeatedHashStable!==!0||e.markdownCanonicalMatchesHtml!==!0||e.explicit?.varyAccept!==!0||e.negotiated?.varyAccept!==!0).map(e=>e.htmlUrl)}function sb(e){return e.markdownAlternates.pages.filter(e=>{let t=e.quality;return!t||!t.frontmatterTitle||t.h1Count!==1||!t.codeFenceBalanced||t.rawHtmlTags>0||t.rawSvgTags>0||t.rawScriptTags>0||t.rawStyleTags>0||t.navigationOnly||t.repeatedLines>0||t.wordRetentionRatio!==null&&t.wordRetentionRatio<.4}).map(e=>e.htmlUrl)}function cb(e){return[`api`,`application`,`commerce`].map(t=>ib(`profile`,{id:`profile-${t}`,status:`notApplicable`,title:`${t[0]?.toUpperCase()}${t.slice(1)} checks are not applicable`,plainEnglish:e.profileApplicability[t].reason,action:`Choose this profile only when the site exposes that capability publicly.`,evidence:{profile:t,selectedProfile:e.profile}}))}function lb(e){let t=t=>e.filter(e=>e.status===t).length;return{checks:e.length,passed:t(`pass`),warnings:t(`warning`),failed:t(`fail`),unknown:t(`unknown`),information:t(`info`),notApplicable:t(`notApplicable`)}}function ub(e){let t=e.markdownAlternates,n=ob(e),r=sb(e),i=t.eligibleHtmlPages>0&&t.advertisedPages===t.eligibleHtmlPages&&t.evaluatedPages===t.eligibleHtmlPages,a=t.eligibleHtmlPages>0&&t.exactByteMatches===t.eligibleHtmlPages,o=t.eligibleHtmlPages>0&&t.stableResponses===t.eligibleHtmlPages,s=t.pages.filter(e=>e.explicit?.bytes!==void 0&&e.explicit?.markdownTokens!==void 0&&e.explicit.markdownTokens===Math.ceil(e.explicit.bytes/4)&&e.explicit.markdownTokens===e.negotiated?.markdownTokens).length;return[ib(`representations`,{id:`markdown-coverage`,status:t.eligibleHtmlPages===0?`unknown`:i?`pass`:`fail`,title:i?`Every successful HTML page advertises one Markdown alternative`:`Markdown alternative coverage is incomplete`,plainEnglish:`${t.advertisedPages} of ${t.eligibleHtmlPages} successful HTML pages advertised one Markdown alternative, and ${t.evaluatedPages} were fetched.`,action:i?`Keep route generation tied to the same deterministic page manifest.`:`Add one text/markdown alternate for each public HTML page and make every advertised URL return successfully.`,evidence:{eligibleHtmlPages:t.eligibleHtmlPages,advertisedPages:t.advertisedPages,evaluatedPages:t.evaluatedPages},urls:n.slice(0,25)}),ib(`representations`,{id:`markdown-token-estimates`,status:t.evaluatedPages===0?`unknown`:s===t.evaluatedPages?`pass`:`warning`,title:s===t.evaluatedPages?`Every Markdown response includes the same token estimate`:`Some Markdown responses are missing a stable token estimate`,plainEnglish:`${s} of ${t.evaluatedPages} evaluated pages returned matching X-Markdown-Tokens values on the explicit and negotiated responses. The value is a quick size estimate, not an exact model-specific token count.`,action:`Generate the estimate from the stored Markdown bytes and return the same value for explicit and negotiated responses.`,urls:t.pages.filter(e=>e.explicit?.markdownTokens===void 0||e.explicit.markdownTokens!==e.negotiated?.markdownTokens).map(e=>e.htmlUrl).slice(0,25)}),ib(`representations`,{id:`markdown-negotiation`,status:t.eligibleHtmlPages===0?`unknown`:a&&e.contentNegotiation.qZeroHonoured?`pass`:`fail`,title:a&&e.contentNegotiation.qZeroHonoured?`Explicit and negotiated Markdown use the same bytes`:`Content negotiation does not match the explicit Markdown contract`,plainEnglish:`${t.exactByteMatches} of ${t.eligibleHtmlPages} pages returned identical explicit and negotiated Markdown. A request that refuses Markdown ${e.contentNegotiation.qZeroHonoured?`received HTML`:`did not produce confirmed HTML evidence`}.`,action:`Use one stored Markdown artifact for both routes, honour Accept q-values, and send Vary: Accept on negotiated responses.`,evidence:{exactByteMatches:t.exactByteMatches,qZero:e.contentNegotiation},urls:n.slice(0,25)}),ib(`representations`,{id:`markdown-determinism`,status:t.eligibleHtmlPages===0?`unknown`:o?`pass`:`fail`,title:o?`Repeated Markdown responses are stable`:`Repeated Markdown responses changed during the audit`,plainEnglish:`${t.stableResponses} of ${t.eligibleHtmlPages} repeated explicit responses kept the same SHA-256 digest.`,action:`Generate Markdown during the build and remove runtime rewriting, timestamps, or random output.`,evidence:{stableResponses:t.stableResponses},urls:n.slice(0,25)}),ib(`representations`,{id:`markdown-quality`,status:t.evaluatedPages===0?`unknown`:r.length===0?`pass`:`warning`,title:r.length===0?`Markdown keeps the useful document structure cleanly`:`Some Markdown alternatives need an extraction review`,plainEnglish:r.length===0?`The evaluated alternatives retained one H1, frontmatter titles, balanced code fences, useful copy, and no leaked SVG, script, style, or layout tags.`:`${r.length} Markdown alternative${r.length===1?``:`s`} lost important structure, looked navigation-only, or leaked presentation markup.`,action:`Open the affected Markdown directly and fix the shared converter or component exclusion rules rather than patching generated files.`,urls:r.slice(0,25)})]}function db(e){let t=e.agentSkills,n=t.validIndex&&t.skills.length>0&&t.skills.every(e=>e.sameOrigin&&e.status===200&&/^\s*text\/markdown\b/iu.test(e.contentType??``)&&!!e.cors&&e.digestMatches===!0&&e.frontmatterValid===!0)&&!!t.cors,r=e.llmsTxt,i=r.links.filter(e=>!e.status||e.status<200||e.status>=400),a=r.exists&&/^\s*(?:text\/plain|text\/markdown)\b/iu.test(r.contentType??``)&&r.headingCount>0&&r.links.length>0&&!r.linkLimitReached&&!r.oversized&&r.invalidLinks.length===0&&r.duplicateLinks.length===0&&r.redirectedLinks.length===0&&r.nonIndexableLinks.length===0&&r.missingCrawlRoutes.length===0&&i.length===0&&r.repeatedHashStable===!0;return[ib(`discovery`,{id:`agent-skills`,status:n?`pass`:t.status===404?`info`:`warning`,title:n?`Agent Skills discovery is valid and digest verified`:`Agent Skills discovery is absent or needs attention`,plainEnglish:n?`${t.skills.length} published skill${t.skills.length===1?``:`s`} matched the declared SHA-256 digest, valid SKILL.md frontmatter, Markdown content type, and cross-origin access policy.`:`Agent Skills is optional for a content site, but a published index should resolve same-origin Markdown skill files, allow intended cross-origin clients, and verify their declared bytes.`,action:`If you publish Agent Skills, keep the discovery index and SHA-256 digests generated from the exact deployed skill bytes.`,evidence:t}),ib(`discovery`,{id:`llms-txt`,status:r.exists?a?`pass`:`warning`:`info`,title:r.exists?a?`llms.txt is short, stable, and its links resolve`:`llms.txt exists but needs a content or link review`:`llms.txt is not published`,plainEnglish:r.exists?`${r.links.length} declared links were checked. ${r.invalidLinks.length} were malformed, ${r.duplicateLinks.length} were duplicated, ${r.redirectedLinks.length} redirected, ${r.nonIndexableLinks.length} reached non-indexable pages, ${i.length} did not resolve, and ${r.missingCrawlRoutes.length} were missing from the crawl inventory. ${r.offSiteLinks.length} linked to other sites.`:`llms.txt is optional and its absence is not a search ranking problem.`,action:r.exists?`Keep the file curated, deterministic, and limited to useful entry points whose links still resolve.`:`Add it only when an intended consumer uses it. Do not treat it as a Google ranking requirement.`,evidence:{status:r.status,contentType:r.contentType,bytes:r.bytes,oversized:r.oversized,totalParsedLinks:r.totalParsedLinks,linkLimitReached:r.linkLimitReached,links:r.links.length,invalidLinks:r.invalidLinks,duplicateLinks:r.duplicateLinks,offSiteLinks:r.offSiteLinks,redirectedLinks:r.redirectedLinks,nonIndexableLinks:r.nonIndexableLinks,missingCrawlRoutes:r.missingCrawlRoutes,repeatedHashStable:r.repeatedHashStable},urls:[...i.map(e=>e.url),...r.redirectedLinks,...r.nonIndexableLinks,...r.missingCrawlRoutes].slice(0,25)}),ib(`discovery`,{id:`content-signals`,status:e.contentSignals.consistent===null?`unknown`:e.contentSignals.consistent?`pass`:`warning`,title:e.contentSignals.consistent?`HTML and Markdown publish one explicit Content Signals policy`:`Content Signals are missing or inconsistent across representations`,plainEnglish:e.contentSignals.consistent?`The same ${e.contentSignals.htmlValues[0]} policy was observed on every evaluated HTML and Markdown response.`:`${e.contentSignals.missingHtmlPages} HTML responses and ${e.contentSignals.missingMarkdownPages} Markdown responses were missing the header, or the observed values differed.`,action:`Choose the site policy deliberately and return the same Content-Signal value on HTML and Markdown responses.`,evidence:e.contentSignals}),ib(`discovery`,{id:`route-manifest`,status:e.routeManifest.valid?e.routeManifest.missingHtmlRoutes.length===0&&e.routeManifest.missingMarkdownRoutes.length===0&&e.routeManifest.orphanMarkdownRoutes.length===0?`pass`:`warning`:`info`,title:e.routeManifest.valid?`The public route manifest agrees with the crawl`:`No public route manifest was available`,plainEnglish:e.routeManifest.valid?`The manifest declared ${e.routeManifest.declaredHtmlRoutes.length} HTML routes and ${e.routeManifest.declaredMarkdownRoutes.length} Markdown routes. Missing or orphan routes are listed in the evidence.`:`A public route manifest is optional. Without one, this crawl cannot prove that no orphan Markdown files were deployed.`,action:`Generate one deterministic route inventory from the build when exact HTML and Markdown parity matters.`,evidence:e.routeManifest})]}function fb(e,t){let n=e.ai?.robotsTxt?.botAccess??[],r=n.filter(e=>e.allowed===!1),i=e.ai?.robotsTxt?.availability===`available`||e.ai?.robotsTxt?.availability===`absent`,a=t.protocolVariants;return[ib(`access`,{id:`crawler-access`,status:i?r.length>0?`warning`:`pass`:`unknown`,title:r.length?`robots.txt blocks selected crawler tokens`:`No selected crawler token is blocked at the start URL`,plainEnglish:i?`${r.length} of ${n.length} selected crawler tokens were blocked by the start URL robots.txt policy.`:`The robots.txt response was not stable enough to make a crawler-access claim.`,action:`Keep blocks intentional. A crawler token being allowed here does not prove that the service will crawl, index, or cite the site.`,evidence:{availability:e.ai?.robotsTxt?.availability,blocked:r,botAccess:n}}),ib(`access`,{id:`protocol-canonicalization`,status:a.http.permanentRedirectToHttps===!0&&a.hstsOnStartPage===!0?`pass`:a.http.permanentRedirectToHttps===null?`unknown`:`warning`,title:a.http.permanentRedirectToHttps===!0&&a.hstsOnStartPage===!0?`HTTP redirects permanently to HTTPS and HTTPS sends HSTS`:`Protocol canonicalization needs attention or could not be proven`,plainEnglish:`The HTTP variant ${a.http.permanentRedirectToHttps===!0?`redirected permanently to HTTPS`:`did not produce a confirmed permanent HTTPS redirect`}. The HTTPS start page ${a.hstsOnStartPage?`sent HSTS`:`did not provide confirmed HSTS evidence`}.`,action:`Use one permanent HTTP-to-HTTPS redirect and send HSTS only after HTTPS is stable across the site.`,evidence:a})]}function pb(e){let t=new Set(e.pages.flatMap(e=>e.schemaTypes??[])),n=t.has(`SoftwareApplication`),r=t.has(`WebSite`),i=t.has(`Person`)||t.has(`Organization`),a=[`WebPage`,`TechArticle`,`CollectionPage`].filter(e=>t.has(e)),o=[...new Set(e.pages.flatMap(e=>[...e.schemaSameAs??[],...e.socialProfileLinks??[]]))];return[ib(`identity`,{id:`identity-graph`,status:n&&r&&i&&a.length>0?`pass`:`warning`,title:n&&r&&i&&a.length>0?`Structured identity covers the software, website, creator, and pages`:`The structured identity graph is missing a useful entity layer`,plainEnglish:`Software identity: ${n?`present`:`missing`}. Website identity: ${r?`present`:`missing`}. Creator or publisher identity: ${i?`present`:`missing`}. Page-level types: ${a.length?a.join(`, `):`missing`}.`,action:`Connect truthful SoftwareApplication, WebSite, creator or publisher, and page nodes. Use sameAs only for official profiles that identify the same entity.`,evidence:{schemaTypes:[...t].sort(),officialProfiles:o.sort()}})]}function mb(e){let t=e.agentDiscovery;if(!t){let t={content:{status:`evaluated`,reason:`The content profile was selected but discovery evidence was not collected.`},api:{status:`notApplicable`,reason:`Not selected.`},application:{status:`notApplicable`,reason:`Not selected.`},commerce:{status:`notApplicable`,reason:`Not selected.`}},n=ib(`collection`,{id:`agent-discovery-evidence`,status:`unknown`,title:`Agent discovery evidence was not collected`,plainEnglish:`This saved crawl predates the focused content-profile checks or was run without them.`,action:`Run the agent-readiness report with a URL or start a fresh crawl with agent discovery enabled.`}),r=cb({profile:`content`,profileApplicability:t}),i=[n,...r];return{reportId:e.id,url:e.config.url,generatedAt:e.generatedAt,profile:`content`,dataStatus:`unavailable`,assessment:`evidence-only`,headline:`No agent-discovery evidence is stored in this crawl. Run a fresh focused check.`,profileApplicability:t,summary:lb(i),sections:[ab(`collection`,`Evidence collection`,[n]),ab(`profile`,`Profile scope`,r)],checks:i,topActions:[n],caveats:[`Not evaluated does not mean absent or broken.`,`This report does not measure rankings, AI mentions, citations, or selection.`]}}let n=[ab(`profile`,`Profile scope`,cb(t)),ab(`access`,`Crawler and protocol access`,fb(e,t)),ab(`representations`,`HTML and Markdown representations`,ub(t)),ab(`discovery`,`Agent discovery files`,db(t)),ab(`identity`,`Identity evidence`,pb(e))],r=n.flatMap(e=>e.checks),i=r.filter(e=>[`fail`,`warning`,`unknown`].includes(e.status)),a={fail:0,warning:1,unknown:2,pass:3,info:4,notApplicable:5},o=[...i].sort((e,t)=>a[e.status]-a[t.status]).slice(0,10),s=lb(r);return{reportId:e.id,url:e.config.url,generatedAt:e.generatedAt,profile:t.profile,dataStatus:t.dataStatus,assessment:`evidence-only`,headline:`${s.passed} checks passed, ${s.warnings} need review, and ${s.failed} failed for the content profile.`,profileApplicability:t.profileApplicability,summary:s,sections:n,checks:r,topActions:o,caveats:[...e.caveats,...t.warnings,`A clean content profile does not prove indexing, rankings, AI mentions, citations, or selection.`,`A crawler token being allowed does not prove that a service fetched or used the page.`,`API, application, and commerce checks were not applicable to this content-site run.`]}}const hb=new Set([`organization`,`localbusiness`,`person`,`product`,`softwareapplication`,`website`]),gb=new Set([`organization`,`localbusiness`]),_b=new Set([...gb,`person`]);function vb(e){return hb.has(e.toLowerCase())}function yb(e){return gb.has(e.toLowerCase())}function bb(e){return _b.has(e.toLowerCase())}function xb(e){return[...new Set(e.filter(e=>!!e))]}function Sb(e,t){return t?Math.round(e/t*100):0}function Cb(e){let t=e.pages.filter(e=>e.indexable&&e.status<400),n=t.length||1,r={};for(let e of t)for(let t of e.schemaTypes??[])r[t]=(r[t]??0)+1;let i=t.filter(e=>e.schemaTypes?.some(vb)),a=t.flatMap(e=>e.schemaSameAsEvidence??[]).filter(e=>e.subjectTypes.some(bb)),o=a.filter(e=>e.subjectTypes.some(yb)),s=xb(a.map(e=>e.url)),c={};for(let e of a)for(let t of e.subjectTypes)bb(t)&&(c[t]=xb([...c[t]??[],e.url]));let l=xb(t.flatMap(e=>e.socialProfileLinks??[])),u=xb(t.map(e=>e.author)),d=t.filter(e=>e.author||e.geo?.hasAuthor),f=t.filter(e=>e.hasDate||e.geo?.hasDate),p=t.filter(e=>e.title&&e.h1),m=[{coverage:Sb(i.length,n),check:{id:`entity-schema`,title:`Entity schema is present`,plainEnglish:`${Sb(i.length,n)}% of evaluated indexable pages include Organization, LocalBusiness, Person, Product, SoftwareApplication, or WebSite schema.`,action:`Add accurate schema only where it describes the software, website, organization, person, product, or local business shown on the page.`,evidence:{schemaTypes:r},urls:t.filter(e=>!i.includes(e)).slice(0,10).map(e=>e.finalUrl)}},{coverage:o.length?100:0,check:{id:`same-as`,title:`Site entity profiles are connected`,plainEnglish:o.length?`The crawl found sameAs links attached to Organization or LocalBusiness structured data.`:s.length?`The crawl found Person sameAs links, but no Organization or LocalBusiness profile evidence for the site entity.`:l.length?`The crawl found social-domain links, but they are not enough to prove official profile ownership.`:`The crawl did not find entity-scoped sameAs evidence.`,action:`Connect official profiles with sameAs on Organization, Person, or LocalBusiness schema. Use only profiles you control.`,evidence:{sameAs:s,siteSameAs:xb(o.map(e=>e.url)),sameAsEvidence:a,unclassifiedSocialLinks:l}}},{coverage:Math.round((Sb(d.length,n)+Sb(f.length,n))/2),check:{id:`authority-freshness`,title:`Author and date signals observed`,plainEnglish:`${Sb(d.length,n)}% of evaluated indexable pages expose an author signal and ${Sb(f.length,n)}% expose a date signal.`,action:`For editorial content, identify authors and show accurate published or modified dates when that information is useful to readers.`,evidence:{authorCoverage:Sb(d.length,n),dateCoverage:Sb(f.length,n),authors:u}}},{coverage:Sb(p.length,n),check:{id:`entity-naming`,title:`Title and H1 coverage observed`,plainEnglish:`${Sb(p.length,n)}% of evaluated indexable pages have both a title and H1.`,action:`Review pages missing either signal. Keep brand, product, and person names accurate wherever they appear.`}}].map(({coverage:e,check:t})=>({...t,status:`info`,evidence:{...t.evidence,observedCoveragePercent:e}})),h=e.status===`completed`&&t.length>0?`complete`:`partial`;return{reportId:e.id,url:e.config.url,generatedAt:e.generatedAt,dataStatus:h,evaluatedPages:t.length,crawlPages:e.pages.length,assessment:`evidence-only`,headline:h===`partial`?`Entity evidence is incomplete; treat these findings as scoped to the evaluated pages, not the whole site.`:`Entity signals are reported as observations, not a ranking, visibility, or machine-understanding score.`,caveats:[...e.caveats,`This report deliberately has no aggregate entity score. Add schema, authors, dates, and sameAs only where they accurately fit the page and a documented use case.`,...h===`partial`?[`Entity coverage is based on ${t.length} evaluated indexable page${t.length===1?``:`s`} from a ${e.status} crawl.`]:[],...l.length&&!s.length?[`Social-domain links are unclassified navigation evidence, not proof that the linked profiles are official.`]:[]],checks:m,entities:{schemaTypes:r,sameAs:s,sameAsByType:c,socialProfiles:l,authors:u}}}function wb(e,t){return t<=0?0:Math.round(e/t*100)}function Tb(e,t){return Math.round(Math.max(0,Math.min(100,e))/100*t)}function Eb(e,t){if(t===0)return`info`;let n=t?e/t:0;return n>=.85?`pass`:n>=.5?`warning`:`fail`}function Db(e,t,n){return{id:e,title:t,checks:n}}function Ob(e){let{score:t,maxScore:n,...r}=e,i=e.evaluated??!0;return{...r,evaluated:i,status:i?Eb(t,n):`unknown`}}function kb(e){return e.pages.filter(e=>e.indexable&&e.status<400)}function Ab(e,t){return e.filter(t).slice(0,10).map(e=>e.finalUrl)}function jb(e){let t=e.agentDiscovery,n=kb(e),r=n.length,i=r>0,a=e.summary.pageLimitReached,o=e.ai?.robotsTxt,s=o?.botAccess??[],c=s.filter(e=>e.allowed===!1),l=s.filter(e=>e.allowed===null),u=o?.availability===`rate-limited`||o?.availability===`unreachable`,d=o!==void 0,f=d&&s.length>0,p=f&&!u,m=d&&!u,h=u||!d||!f,g=s.filter(e=>e.declared||e.coveredByWildcard),_=e.ai?.agentResources?.filter(e=>e.exists),v=e.pages.filter(e=>e.structuredDataFormats?.includes(`json-ld`)||(e.invalidJsonLdCount??0)>0),y=v.filter(e=>(e.invalidJsonLdCount??0)>0),b=n.filter(e=>e.geo?.answerable),x=n.filter(e=>e.geo?.semanticHtml),ee=n.filter(e=>e.title&&e.h1),te=n.filter(e=>e.metaDescription),ne=n.filter(e=>e.lang),S=n.filter(e=>e.schemaTypes?.some(vb)||(e.schemaSameAs?.length??0)>0||(e.socialProfileLinks?.length??0)>0),C=n.map(e=>({url:e.finalUrl,control:_m({metaRobots:e.metaRobots,xRobotsTag:e.xRobotsTag})})).filter(({control:e})=>e.status!==`not-restricted`),re=C.filter(({control:e})=>e.status===`blocked`),w=C.filter(({control:e})=>e.status===`limited`),ie=[Db(`agent-access`,`Agent access`,[Ob({id:`robots-ai-bots`,section:`agent-access`,maxScore:20,evaluated:p,score:u?0:s.length?c.length?Tb(wb(s.length-c.length,s.length),20):20:8,title:u?`robots.txt crawler-policy evidence is unavailable`:f?c.length?`robots.txt blocks selected crawler tokens at the start URL`:`robots.txt allows selected crawler tokens at the start URL`:`robots.txt crawler-policy evidence was not collected`,plainEnglish:u?`The robots.txt request failed, so this report cannot say whether selected crawler tokens are allowed at the start URL.`:f?c.length?`robots.txt policy blocks ${c.length} selected crawler ${c.length===1?`token`:`tokens`} at the start URL.`:`robots.txt policy does not block the selected crawler tokens at the start URL. This does not verify actual or site-wide fetchability.`:`This crawl does not include per-bot robots.txt policy data for the start URL.`,action:u?`Restore a stable robots.txt response, then rerun this report before making crawler-policy claims.`:f?c.length?`Only block AI crawlers intentionally. If discovery matters, remove accidental Disallow rules for the blocked user agents.`:`Keep robots.txt explicit and intentional so humans and agents can see what is allowed.`:`Rerun the crawl with top-level per-bot robots.txt evidence before making crawler-policy claims.`,evidence:{blockedBots:c,unknownBots:l,declaredBots:g.length,availability:o?.availability,status:o?.status,error:o?.error,scope:`start-url-robots-policy`,startUrl:e.config.url}}),Ob({id:`robots-sitemap`,section:`agent-access`,maxScore:0,evaluated:m,score:0,title:m?`robots.txt sitemap declarations were checked`:`robots.txt sitemap declarations were not evaluated`,plainEnglish:u?`The robots.txt response was unavailable, so sitemap declarations could not be checked.`:d?o.sitemapUrls.length?`robots.txt declares at least one sitemap URL.`:`robots.txt does not declare a sitemap URL.`:`This crawl does not include top-level robots.txt evidence, so sitemap declarations could not be checked.`,action:u?`Restore robots.txt availability and rerun before evaluating sitemap declarations.`:d?`Add a Sitemap line to robots.txt so crawlers can discover the full index quickly.`:`Rerun the crawl with top-level robots.txt evidence before evaluating sitemap declarations.`,evidence:{sitemapUrls:o?.sitemapUrls??[]}}),Ob({id:`agent-resources`,section:`agent-access`,maxScore:0,score:0,title:`Agent descriptor files are discoverable`,plainEnglish:_?.length?`${_.length} machine-readable agent resource was found.`:`No OpenAPI, MCP, ai-plugin, or agent descriptor file was found at the common locations.`,action:`Publish useful descriptors only when they are real: OpenAPI for APIs, MCP metadata for tools, or agent.json for site capabilities.`,evidence:{resources:e.ai?.agentResources??[]}})]),Db(`machine-readable`,`Machine-readable signals`,[Ob({id:`llms-txt`,section:`machine-readable`,maxScore:0,score:0,title:`llms.txt is optional`,plainEnglish:e.ai?.llmsTxt?.exists?t?.llmsTxt?`An optional llms.txt file was found and its body was checked. It declared ${t.llmsTxt.links.length} links, ${t.llmsTxt.duplicateLinks.length} duplicates, and ${t.llmsTxt.missingCrawlRoutes.length} links outside the crawled route inventory. Google says llms.txt has no positive or negative Search impact.`:`An optional llms.txt file was found. Its body was not validated in this crawl. Google says it has no positive or negative Search impact.`:`No llms.txt file was found. Google says the file is not needed for Search and does not affect visibility.`,action:`No SEO action is required. Generate one only for a specific agent or service that consumes it.`,evidence:{llmsTxt:e.ai?.llmsTxt,validation:t?.llmsTxt,googleSearchImpact:`none`,guidanceUrl:`https://developers.google.com/search/updates#clarifying-guidance-on-llms-txt-files`}}),Ob({id:`valid-json-ld`,section:`machine-readable`,maxScore:8,evaluated:v.length>0,score:y.length?3:8,title:v.length?`Observed JSON-LD parses cleanly`:`JSON-LD syntax was not evaluated`,plainEnglish:v.length?y.length?`${y.length} evaluated page contains invalid JSON-LD.`:`The JSON-LD observed on evaluated pages parsed cleanly.`:`No JSON-LD was observed, so this report cannot make a syntax claim.`,action:y.length?`Fix invalid JSON-LD before relying on its structured-data claims.`:`Use structured data only where it accurately represents the visible page and a supported use case.`,urls:y.slice(0,10).map(e=>e.finalUrl)})]),Db(`content-clarity`,`Content clarity`,[Ob({id:`answerable-content`,section:`content-clarity`,maxScore:0,evaluated:i,score:0,title:`Short opening paragraphs are an unscored observation`,plainEnglish:i?`${wb(b.length,r)}% of indexable pages have at least one 25-word paragraph near the start. This does not establish content quality, citation likelihood, or Google AI eligibility.`:`No indexable 2xx pages were available, so opening-paragraph coverage was not evaluated.`,action:`Write for the reader and the page intent. Do not create artificial answer blocks or chunk content solely for AI Search.`,urls:Ab(n,e=>!e.geo?.answerable),evidence:{observedPages:b.length,evaluatedPages:r,heuristic:`one-of-first-three-paragraphs-has-at-least-25-words`,googleSearchImpact:`not-established`,guidanceUrl:`https://developers.google.com/search/docs/fundamentals/ai-optimization-guide`}}),Ob({id:`semantic-html`,section:`content-clarity`,maxScore:0,evaluated:i,score:0,title:`Semantic HTML coverage observed`,plainEnglish:i?`${wb(x.length,r)}% of indexable pages use semantic structure.`:`No indexable 2xx pages were available, so semantic HTML coverage was not evaluated.`,action:`Use real headings, lists, tables, nav, main, article, and section elements instead of layout-only divs.`}),Ob({id:`titles-headings-meta`,section:`content-clarity`,maxScore:0,evaluated:i,score:0,title:`Title, H1, and description coverage observed`,plainEnglish:i?`${wb(ee.length,r)}% of indexable pages have title/H1 coverage and ${wb(te.length,r)}% have meta descriptions.`:`No indexable 2xx pages were available, so title, H1, and description coverage was not evaluated.`,action:`Write a unique title and descriptive H1. Add a useful meta description when a summary would help searchers understand the page.`}),Ob({id:`language`,section:`content-clarity`,maxScore:0,evaluated:i,score:0,title:`HTML language declaration coverage observed`,plainEnglish:i?`${wb(ne.length,r)}% of indexable pages declare a language.`:`No indexable 2xx pages were available, so language declaration coverage was not evaluated.`,action:`Set the html lang attribute so crawlers and assistive tech understand the page language.`})]),Db(`entity-signals`,`Entity signals`,[Ob({id:`entity-schema`,section:`entity-signals`,maxScore:0,evaluated:i,score:0,title:`Entity-signal coverage observed`,plainEnglish:i?`${wb(S.length,r)}% of indexable pages include entity signals such as Organization, Person, Product, sameAs, or social profile links.`:`No indexable 2xx pages were available, so entity-signal coverage was not evaluated.`,action:`Make the homepage and key pages explicit about the brand, product, people, and official profiles using accurate schema and links.`,urls:Ab(n,e=>!S.includes(e))}),Ob({id:`authors-dates`,section:`entity-signals`,maxScore:0,score:0,title:`People and dates are visible where they matter`,plainEnglish:`Author and date signals help agents judge freshness and accountability.`,action:`Add visible authors, reviewed dates, and updated dates to content where trust and freshness matter.`})]),Db(`technical-ux`,`Technical UX`,[Ob({id:`snippet-controls`,section:`technical-ux`,maxScore:0,evaluated:i,score:0,title:i?C.length?`Page-level snippet restrictions observed`:`No page-level snippet restriction detected`:`Snippet controls were not evaluated`,plainEnglish:i?C.length?`${re.length} indexable ${re.length===1?`page has`:`pages have`} snippets blocked and ${w.length} ${w.length===1?`has`:`have`} a positive max-snippet limit.`:`No nosnippet or restrictive max-snippet directive was detected on the evaluated indexable pages.`:`No indexable 2xx pages were available, so snippet controls were not evaluated.`,action:C.length?`Confirm each restriction is intentional. Do not remove publisher controls solely to satisfy this report.`:`No action is required. This observation does not guarantee that Google will select or show a snippet.`,urls:C.slice(0,10).map(({url:e})=>e),evidence:{evaluatedPages:n.length,blockedPages:re.length,limitedPages:w.length,restrictions:C.slice(0,10)}}),Ob({id:`https-viewport-readable`,section:`technical-ux`,maxScore:0,score:0,title:`Pages are secure, mobile-ready, and readable from HTML`,plainEnglish:`Agents need stable HTML content, not just a visual page that works in one browser session.`,action:`Serve HTTPS, include viewport metadata, and make important copy available in the initial HTML or rendered output.`}),Ob({id:`status-indexability`,section:`technical-ux`,maxScore:12,evaluated:e.pages.length>0,score:Tb(wb(n.length,e.pages.length||1),12),title:e.pages.length?`Crawled-page indexability observed`:`Crawled-page indexability was not evaluated`,plainEnglish:e.pages.length?`${wb(n.length,e.pages.length)}% of crawled pages are indexable 2xx pages.`:`No crawled pages were available, so status and indexability were not evaluated.`,action:`Fix 4xx/5xx pages, accidental noindex, and blocked internal URLs before expanding AI discovery work.`})]),Db(`agent-content`,`Agent content observations`,[Ob({id:`markdown-alternates`,section:`agent-content`,maxScore:0,score:0,evaluated:!!t,title:t?`Markdown alternatives were checked`:`Markdown alternatives were not evaluated`,plainEnglish:t?`${t.markdownAlternates.advertisedPages} of ${t.markdownAlternates.eligibleHtmlPages} successful HTML pages advertised one Markdown alternative. ${t.markdownAlternates.exactByteMatches} explicit responses matched content negotiation byte for byte.`:`This crawl does not include the focused representation checks, so it cannot say whether HTML pages publish stable Markdown alternatives.`,action:`Use the focused agent-readiness report when you need route coverage, content negotiation, byte stability, or extraction-quality evidence.`,evidence:t?{eligibleHtmlPages:t.markdownAlternates.eligibleHtmlPages,advertisedPages:t.markdownAlternates.advertisedPages,exactByteMatches:t.markdownAlternates.exactByteMatches,stableResponses:t.markdownAlternates.stableResponses}:void 0}),Ob({id:`agent-skills-discovery`,section:`agent-content`,maxScore:0,score:0,evaluated:!!t,title:t?`Agent Skills discovery was checked`:`Agent Skills discovery was not evaluated`,plainEnglish:t?`${t.agentSkills.skills.length} published skill${t.agentSkills.skills.length===1?``:`s`} were found. ${t.agentSkills.skills.filter(e=>e.digestMatches).length} matched their declared SHA-256 digest.`:`This crawl checked common legacy descriptor paths but did not validate the Agent Skills discovery index or its file digests.`,action:`Publish Agent Skills only for real reusable capabilities. Generate the index digest from the exact deployed SKILL.md bytes.`,evidence:t?.agentSkills}),Ob({id:`agent-content-profile`,section:`agent-content`,maxScore:0,score:0,evaluated:!!t,title:t?`The content profile was selected explicitly`:`No focused agent-readiness profile was selected`,plainEnglish:t?`Document access, representations, discovery, identity, and extraction quality were evaluated. API, application, and commerce checks were marked not applicable rather than failed.`:`A normal crawl does not assume that a content site also exposes a public API, remote agent endpoint, or checkout.`,action:`Run a different profile only when that public capability really exists.`,evidence:t?.profileApplicability})]),Db(`crawl-completeness`,`Crawl completeness`,[Ob({id:`crawl-depth`,section:`crawl-completeness`,maxScore:8,score:a?3:8,title:a?`The crawl hit the page cap`:`The crawl finished without hitting the page cap`,plainEnglish:a?`The crawl stopped at maxPages (${e.config.maxPages}), so the report may under-sample the site.`:`The crawl did not hit the configured page cap.`,action:`Raise --max-pages or crawl from sitemap mode when you need a complete site inventory.`}),Ob({id:`internal-link-discovery`,section:`crawl-completeness`,maxScore:0,score:0,title:`Internal links expose more of the site`,plainEnglish:`A healthy crawl should discover important pages through internal links, not only through a seed URL.`,action:`Link important pages from navigational, hub, or contextual links so crawlers can find them naturally.`})])],T=ie.flatMap(e=>e.checks),ae=e.status===`completed`&&!h?`complete`:`partial`,oe=T.filter(e=>e.evaluated&&(e.status===`warning`||e.status===`fail`)&&[`robots-ai-bots`,`valid-json-ld`,`status-indexability`,`crawl-depth`].includes(e.id)).sort((e,t)=>Number(t.status===`fail`)-Number(e.status===`fail`)||e.id.localeCompare(t.id)).slice(0,8);return{reportId:e.id,url:e.config.url,generatedAt:e.generatedAt,dataStatus:ae,assessment:`evidence-only`,headline:ae===`partial`?`AI-search evidence is incomplete. Fix the collection gaps before drawing site-wide conclusions.`:oe.length?`${oe.length} evidence-backed technical ${oe.length===1?`action remains`:`actions remain`}. Optional observations are not treated as ranking or citation factors.`:`No evidence-backed technical action was found in this crawl. This is not a visibility or citation verdict.`,sections:ie,checks:T,topActions:oe,botAccess:s,agentResources:e.ai?.agentResources??[],caveats:[...e.caveats,`This report deliberately has no aggregate readiness score. Google documents no separate technical requirements for its generative AI Search features.`,`llms.txt is treated as optional agent-discovery metadata, not a Google Search ranking or visibility factor.`,`Paragraph length and placement are observations only. Google does not require content chunking or special answer blocks for generative AI features.`,`Snippet controls reflect page-level publisher directives only. No detected restriction does not guarantee selection, visibility, or a displayed snippet.`,...o?u?[`robots.txt was unavailable, so crawler access and sitemap declaration checks are inconclusive.`]:f?[]:[`robots.txt was collected without per-bot policy evidence, so the crawler-policy check is inconclusive.`]:[`This crawl report does not include top-level robots.txt AI bot data.`],`robots.txt checks describe policy for the configured start URL only. They do not verify actual crawler requests or site-wide access.`]}}const Mb=new Set([`connection_error`,`client_error`,`server_error`,`robots_blocked`,`noindex`,`x_robots_noindex`,`canonicalized_page`]);function Nb(e){let t=new Map;for(let n of e.issues){if(!Mb.has(n.ruleId))continue;let e=t.get(n.url)??[];e.push({ruleId:n.ruleId,title:n.title,severity:n.severity}),t.set(n.url,e)}return e.pages.map(e=>{let n=e.geo??{semanticHtml:!1,structuredData:!1,hasAuthor:!1,hasDate:!1,questionHeadings:0,structuredBlocks:0,answerable:!1},r=_m({metaRobots:e.metaRobots,xRobotsTag:e.xRobotsTag});return{url:e.finalUrl,issueCount:t.get(e.url)?.length??0,issues:t.get(e.url)??[],searchEligibility:{successfulHtmlResponse:e.status>=200&&e.status<300&&e.contentType?.toLowerCase().includes(`text/html`)===!0,crawlAllowed:e.robotsTxt?.allowed??null,indexableCandidate:e.indexable,declaredIndexability:e.declaredIndexability,indexability:e.indexability,snippetEligibility:r},observations:{semanticHtml:n.semanticHtml,structuredData:n.structuredData,hasAuthor:n.hasAuthor,hasDate:n.hasDate,questionHeadings:n.questionHeadings,structuredBlocks:n.structuredBlocks,answerable:n.answerable}}}).filter(e=>e.issueCount>0||!e.searchEligibility.successfulHtmlResponse||e.searchEligibility.crawlAllowed===!1||!e.searchEligibility.indexableCandidate||e.searchEligibility.snippetEligibility.status!==`not-restricted`).sort((e,t)=>t.issueCount-e.issueCount||e.url.localeCompare(t.url))}function Pb(e){let t=[];return e.pages.length===0&&t.push(`no-pages-evaluated`),e.status!==`completed`&&t.push(`crawl-not-complete`),e.requestEvidenceStatus===`partial`&&t.push(`request-evidence-partial`),e.requestEvidenceStatus===`unavailable`&&t.push(`request-evidence-unavailable`),e.summary.pageLimitReached&&t.push(`page-limit-reached`),e.summary.skippedUrlsByImpact.coverageAffecting>0&&t.push(`coverage-affecting-skips`),e.summary.failedRequests>0&&t.push(`failed-requests`),e.summary.extractionFailures>0&&t.push(`extraction-failures`),t}function Fb(e,t={}){let n=Nb(e),r=t.limit??50,i=n.slice(0,r),a=Pb(e),o=i.length<n.length;return{schemaVersion:1,dataStatus:e.pages.length?a.length?`partial`:`complete`:`unavailable`,source:{provider:`seo-crawl`,reportId:e.id,definitionId:e.definitionId,generatedAt:e.generatedAt,startUrl:e.config.url,crawlStatus:e.status,requestEvidenceStatus:e.requestEvidenceStatus,configuredMaxPages:e.config.maxPages,pageLimitReached:e.summary.pageLimitReached,discoveredUrls:e.summary.discoveredUrls,queuedUrls:e.summary.queuedUrls,crawledUrls:e.summary.crawledUrls,skippedUrls:e.summary.skippedUrls,coverageAffectingSkippedUrls:e.summary.skippedUrlsByImpact.coverageAffecting,nonImpactingSkippedUrls:e.summary.skippedUrlsByImpact.nonImpacting,failedRequests:e.summary.failedRequests,extractionFailures:e.summary.extractionFailures,partialReasons:a},selection:{evaluatedPages:e.pages.length,totalMatchedPages:n.length,returnedPages:i.length,limit:r,truncated:o},eligibilityGaps:i,warnings:e.warnings,caveats:[...e.caveats,...o?[`${n.length-i.length} matched pages were omitted by the output limit.`]:[],`No detected restriction among evaluated pages does not prove indexing, selection, visibility, or citation.`]}}function Ib(e){return Math.ceil(e.length/4)}function Lb(e){return new URL(e).hostname.replace(/^www\./,``)}function Rb(e){return e.title??e.h1??(new URL(e.finalUrl).pathname||e.finalUrl)}function zb(e){let t=new URL(e.finalUrl).pathname.toLowerCase();return/\/(docs|documentation|guide|guides|help|kb|learn)\b/.test(t)?`Docs and guides`:/\/(blog|articles|news|resources)\b/.test(t)?`Articles`:/\/(product|products|features|tools|solutions)\b/.test(t)?`Products and tools`:/\/(pricing|plans)\b/.test(t)?`Pricing`:/\/(about|company|team|contact)\b/.test(t)?`Company`:`Core pages`}function Bb(e){return/\/(login|logout|cart|checkout|account|admin|wp-admin|search|tag|privacy|terms|cookie|legal)\b/i.test(new URL(e).pathname)}function Vb(e,t=[]){return t.some(t=>{if(!t.includes(`*`))return e.includes(t);let n=t.split(`*`),r=0;for(let t of n){if(!t)continue;let n=e.slice(r).indexOf(t);if(n===-1)return!1;r+=n+t.length}return!0})}function Hb(e,t={}){return e.pages.filter(e=>e.indexable&&e.status<400).filter(e=>!Bb(e.finalUrl)).filter(e=>!Vb(e.finalUrl,t.exclude)).sort((e,t)=>(t.internalLinkAuthorityScore??0)-(e.internalLinkAuthorityScore??0)||(t.wordCount??0)-(e.wordCount??0)||e.finalUrl.localeCompare(t.finalUrl))}function Ub(e){let t=e.ai?.llmsTxt,n=e.agentDiscovery?.llmsTxt,r=Hb(e),i=[];n?.exists&&!/^\s*(?:text\/plain|text\/markdown)\b/iu.test(n.contentType??``)&&i.push({id:`llms-content-type`,severity:`medium`,title:`llms.txt uses an unexpected content type`,plainEnglish:`The file returned ${n.contentType??`no content type`} instead of plain text or Markdown.`,action:`Serve the deterministic text file as text/plain or text/markdown so consumers do not mistake it for HTML.`,evidence:{contentType:n.contentType}}),n?.exists&&n.headingCount===0&&i.push({id:`llms-structure`,severity:`medium`,title:`llms.txt has no useful heading structure`,plainEnglish:`The fetched file did not contain a level-one or level-two Markdown heading.`,action:`Start with one clear title, then group a short list of useful entry points under descriptive headings.`}),n?.duplicateLinks.length&&i.push({id:`llms-duplicate-links`,severity:`low`,title:`llms.txt repeats the same link`,plainEnglish:`${n.duplicateLinks.length} URL${n.duplicateLinks.length===1?` appears`:`s appear`} more than once.`,action:`Remove duplicate entries so the file remains short and deliberate.`,evidence:{urls:n.duplicateLinks}});let a=n?.links.filter(e=>!e.status||e.status<200||e.status>=400||e.error)??[];return a.length&&i.push({id:`llms-broken-links`,severity:`medium`,title:`llms.txt links did not resolve`,plainEnglish:`${a.length} declared link${a.length===1?``:`s`} did not return a usable response during this audit.`,action:`Update or remove stale entries, then rerun the focused agent-readiness report.`,evidence:{links:a}}),n?.missingCrawlRoutes.length&&i.push({id:`llms-outside-crawl`,severity:`low`,title:`llms.txt links fall outside the crawl inventory`,plainEnglish:`${n.missingCrawlRoutes.length} same-origin route${n.missingCrawlRoutes.length===1?` was`:`s were`} not present in the retained crawl.`,action:`Confirm those routes are intentional and indexable, or remove stale declarations.`,evidence:{urls:n.missingCrawlRoutes}}),n?.repeatedHashStable===!1&&i.push({id:`llms-unstable-body`,severity:`medium`,title:`llms.txt changed between repeated requests`,plainEnglish:`Two requests in the same audit returned different SHA-256 digests.`,action:`Generate the file during the build and remove timestamps, random ordering, or runtime rewriting.`}),r.length<3&&i.push({id:`thin-llms-inventory`,severity:`low`,title:`The crawl has a small llms.txt inventory`,plainEnglish:`There are very few indexable candidate pages to include in llms.txt.`,action:`If you choose to generate the optional file, run a deeper crawl or sitemap crawl first.`,evidence:{candidatePages:r.length}}),{reportId:e.id,url:e.config.url,exists:!!t?.exists,llmsTxtUrl:t?.url??new URL(`/llms.txt`,e.config.url).toString(),status:t?.status,optional:!0,googleSearchImpact:`none`,guidanceUrl:`https://developers.google.com/search/updates#clarifying-guidance-on-llms-txt-files`,headline:t?.exists?n?i.length?`The optional llms.txt file was validated and ${i.length} content or link issue${i.length===1?` needs`:`s need`} review. It has no Google Search visibility impact.`:`The optional llms.txt file was validated with ${n.links.length} resolving link${n.links.length===1?``:`s`} and a stable body. It has no Google Search visibility impact.`:`An optional llms.txt file is present, but its body was not validated in this crawl. It has no Google Search visibility impact.`:`No llms.txt file was found. This is not an SEO issue and requires no action.`,issues:i,recommendedPages:r.slice(0,25).map(e=>({url:e.finalUrl,title:e.title,section:zb(e),reason:(e.internalLinkAuthorityScore??0)>0?`High internal link authority in this crawl.`:`Indexable page with useful content signals.`}))}}function Wb(e,t={}){let n=t.maxUrls??250,r=t.tokenBudget??12e3,i=t.title??Lb(e.config.url),a=t.description??`Curated entry points for agents reading ${Lb(e.config.url)}.`,o=Hb(e,t).slice(0,n),s=new Map;for(let e of o){let t=zb(e);s.set(t,[...s.get(t)??[],e])}let c=[`# ${i}`,``,`> ${a}`,``],l=0,u={};for(let[e,t]of s){let n=[`## ${e}`,``],i=0;for(let e of t){let t=Rb(e).replace(/\]/g,`\\]`),a=e.metaDescription?` - ${e.metaDescription.replace(/\s+/g,` `).slice(0,160)}`:``,o=`- [${t}](${e.finalUrl})${a}`;if(Ib([...c,...n,o,``].join(`
330
+ VALUES (?, ?, ?, ?, ?, ?, ?)`).run(e,a,JSON.stringify(t),JSON.stringify(c),c.length,Date.now(),Date.now()+864e5),{rows:c,calls:p,rowsFetched:c.length}}async function f_(e,t,n=28){let{startDate:r,endDate:i}=u_(n),a=await J(e,{startDate:r,endDate:i,dimensions:[`page`],dimensionFilterGroups:[{groupType:`and`,filters:[{dimension:`page`,operator:`equals`,expression:t}]}]});return a.rows[0]?{clicks:a.rows[0].clicks,impressions:a.rows[0].impressions,ctr:a.rows[0].ctr,position:a.rows[0].position}:void 0}async function p_(e,t,n=28){return(await m_(e,t,n)).values}async function m_(e,t,n=28){let r=new Set(t),i=25e3;if(!r.size)return{values:new Map,returnedRows:0,retainedRowLimit:i,retainedRowLimitReached:!1};let{startDate:a,endDate:o}=d_(n),s=await J(e,{startDate:a,endDate:o,dimensions:[`page`],rowLimit:i,maxRows:i}),c=new Map;for(let e of s.rows){let t=e.keys[0];!t||!r.has(t)||c.set(t,{clicks:e.clicks,impressions:e.impressions,ctr:e.ctr,position:e.position})}return{values:c,returnedRows:s.rowsFetched,retainedRowLimit:i,retainedRowLimitReached:s.rowsFetched>=i}}async function h_(e,t,n=28){let{startDate:r,endDate:i}=u_(n),a=(await J(e,{startDate:r,endDate:i,dimensions:[`query`],dimensionFilterGroups:[{groupType:`and`,filters:[{dimension:`page`,operator:`equals`,expression:t}]}],rowLimit:10})).rows[0],o=a?.keys[0];return a&&o?{query:o,clicks:a.clicks,impressions:a.impressions,ctr:a.ctr,position:a.position}:void 0}async function g_(e,t,n=28){return(await __(e,t,n)).values}async function __(e,t,n=28){let r=new Set(t),i=25e3;if(!r.size)return{values:new Map,returnedRows:0,retainedRowLimit:i,retainedRowLimitReached:!1};let{startDate:a,endDate:o}=d_(n),s=await J(e,{startDate:a,endDate:o,dimensions:[`page`,`query`],rowLimit:i,maxRows:i}),c=new Map;for(let e of s.rows){let t=e.keys[0],n=e.keys[1];!t||!n||!r.has(t)||c.has(t)||c.set(t,{query:n,clicks:e.clicks,impressions:e.impressions,ctr:e.ctr,position:e.position})}return{values:c,returnedRows:s.rowsFetched,retainedRowLimit:i,retainedRowLimitReached:s.rowsFetched>=i}}async function v_(e=!1){let{client:t}=await qg(),n=await Kg(t,`https://www.googleapis.com/webmasters/v3/sites`);if(!n.ok)throw Error(`GSC site list failed with ${n.status}.`);let r=(await n.json()).siteEntry??[],i=A().prepare(`INSERT OR REPLACE INTO sites (site_url, display_name, permission, added_at, is_default) VALUES (?, ?, ?, ?, COALESCE((SELECT is_default FROM sites WHERE site_url = ?), 0))`);for(let e of r)i.run(e.siteUrl,e.siteUrl,e.permissionLevel??null,Date.now(),e.siteUrl);return r}function y_(e){try{let t=new URL(e);return t.hash=``,t.pathname!==`/`&&(t.pathname=t.pathname.replace(/\/$/,``)),t.toString()}catch{return e.trim().replace(/\/$/,``)}}function b_(e,t){return y_(e)===y_(t)}function x_(e,t){try{return new URL(e,t).toString()}catch{return e}}function S_(e){let t=[];return e.httpStatus!==void 0&&(e.httpStatus<200||e.httpStatus>299)&&t.push(`http-non-2xx`),(e.httpStatus===204||e.httpStatus===205)&&t.push(`http-no-content`),e.fetchDiagnostics?.blocked&&t.push(`blocked`),e.fetchDiagnostics&&!e.fetchDiagnostics.fetched&&e.fetchDiagnostics.source!==`cache`&&t.push(`fetch-incomplete`),((e.fetchDiagnostics?.redirectChain?.length??0)>0||e.page&&!b_(e.url,e.page.finalUrl))&&t.push(`redirected`),pm(e.page?.metaRobots,`noindex`)&&t.push(`meta-noindex`),mm(e.page?.xRobotsTag,`noindex`)&&t.push(`x-robots-noindex`),e.page?.canonical&&!b_(e.page.finalUrl,x_(e.page.canonical,e.page.finalUrl))&&t.push(`canonical-mismatch`),t}const C_={id:`arial-20-v1`,fontFamily:`Arial`,fontSizePixels:20,fontWeight:400},w_=new Map([[`'`,3.8],[`ijl`,4.4],[`|`,5.2],[` !,;:./\\[]ftI`,5.6],["-(){}`r",6.7],[`"`,7.1],[`*`,7.8],[`^`,9.4],[`cJksvxyz`,10],[`?_#$0123456789abdeghnopquL`,11.1],[`+<=>~`,11.7],[`FTZ`,12.2],[`&ABEKPRSVXY`,13.3],[`CDHNUw`,14.4],[`GOQ`,15.6],[`mM`,16.7],[`%`,17.8],[`W`,18.9],[`@`,20.3]].flatMap(([e,t])=>[...e].map(e=>[e,t]))),T_=new Map([[`\xA0`,5.6],[`‘`,4.4],[`’`,4.4],[`“`,7.1],[`”`,7.1],[`–`,11.1],[`—`,20],[`…`,16.7],[`•`,7]]),E_=new Map([[`AV`,-1.5],[`VA`,-1.5],[`AW`,-.7],[`WA`,-.7],[`To`,-2.2],[`Ta`,-2.2],[`Te`,-2.2],[`Ty`,-1.1],[`Yo`,-1.8],[`LT`,-1.5],[`FA`,-1.1],[`PA`,-1.5],[`We`,-.4]]),D_=new Intl.Segmenter(`und`,{granularity:`grapheme`});function O_(e,t){let n={high:0,medium:1,low:2};return n[t]>n[e]?t:e}function k_(e){let t=w_.get(e);if(t!==void 0)return{confidence:`high`,kerningCharacter:e,width:t};let n=T_.get(e);if(n!==void 0)return{confidence:`high`,width:n};if(/^[\p{Mark}\p{Format}]+$/u.test(e))return{confidence:`high`,width:0};let r=e.normalize(`NFD`),i=[...r].find(e=>!/\p{Mark}/u.test(e)),a=i?w_.get(i):void 0;return a!==void 0&&/^\p{Script=Latin}\p{Mark}*$/u.test(r)?{confidence:`high`,kerningCharacter:i,width:a}:/\p{Extended_Pictographic}|\p{Emoji_Presentation}|\p{Regional_Indicator}|\u20e3/u.test(e)?{confidence:`medium`,width:23}:/^[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}\p{Mark}]+$/u.test(e)?{confidence:`medium`,width:20}:/^\p{Script=Latin}\p{Mark}*$/u.test(r)?{confidence:`medium`,width:11.1}:{confidence:`low`,width:11.1}}function A_(e){let t=e.trim().replace(/\s+/gu,` `),n=[...D_.segment(t)].map(e=>e.segment),r=[],i=`high`,a,o=0;for(let e of n){let t=k_(e);i=O_(i,t.confidence),t.confidence===`low`&&!r.includes(e)&&r.push(e),a&&t.kerningCharacter&&(o+=E_.get(`${a}${t.kerningCharacter}`)??0),o+=t.width,a=t.kerningCharacter}let s=Math.round(o*10)/10;return{estimatedPixels:s,referencePixels:580,status:s>580?`over-reference`:s>=550?`near-reference`:`within-reference`,confidence:i,profile:C_,graphemeCount:n.length,fallbackGraphemes:r.slice(0,10)}}const j_={fetchPage:Hg,queryPageMetrics:f_,now:()=>new Date};function M_(e){let t=[],n=e.page;return n.title||t.push({principle:`C.2`,evidenceRef:`The HTML document has no title element.`,action:`Add a concise, descriptive title that identifies this page.`,effort:`S`,confidence:`high`}),n.headings.every(e=>e.level!==1)&&t.push({principle:`C.4`,evidenceRef:`The extracted document has no H1 heading.`,action:`Add a descriptive H1 for the page’s main visible topic.`,effort:`S`,confidence:`high`}),!n.canonical&&(!n.canonicalEvidence||n.canonicalEvidence.status===`missing`)&&t.push({principle:`C.7`,evidenceRef:`No canonical link tag detected.`,action:`If duplicate or parameter variants exist, declare the preferred canonical URL consistently; otherwise no change is required.`,effort:`S`,confidence:`medium`}),t}async function N_(e,t=j_){let n=await t.fetchPage(e.url,{js:e.js,refresh:e.refresh}),r=await Lh(n,e.extractor),i=r.headings.filter(e=>e.level===1),a=[];if(r.title||a.push({code:`missing_title`,title:`Title missing`,detail:`No <title> element was found.`,principle:`C.2`,evidenceRef:`HTML document has no <title> element.`,severity:`high`}),r.title){let e=A_(r.title);e.status===`over-reference`&&a.push({code:`title_too_wide`,title:`Title may truncate on some devices`,detail:`Estimated title width ${e.estimatedPixels}px exceeds the ${e.referencePixels}px review reference.`,principle:`C.3`,evidenceRef:`Title width estimate: ${e.estimatedPixels}px using ${e.profile.id} (${e.confidence} confidence).`,severity:`low`})}i.length===0&&a.push({code:`h1_missing`,title:`Missing H1`,detail:`No H1 element was found.`,principle:`C.4`,evidenceRef:`Detected 0 H1 elements.`,severity:`low`});let o=r.canonicalEvidence;if(o?.status===`conflicting`){let e=[...new Set(o.candidates.flatMap(e=>e.resolved&&!e.ignoredReason?[e.resolved]:[]))];a.push({code:`canonical_conflict`,title:`Canonical declarations conflict`,detail:`Canonical declarations identify ${e.length} different targets.`,principle:`C.7`,evidenceRef:`Canonical targets: ${e.join(`, `)}.`,severity:`high`})}else if(o?.status===`duplicate`)a.push({code:`canonical_multiple`,title:`Canonical is declared more than once`,detail:`Multiple eligible declarations identify the same target; keep one canonical method.`,principle:`C.7`,evidenceRef:`${o.candidates.length} canonical declarations were found for ${o.selectedUrl}.`,severity:`low`});else if(o?.status===`outside-head-only`)a.push({code:`canonical_outside_head`,title:`Canonical appears outside the document head`,detail:`Google only accepts HTML canonical link elements in the head.`,principle:`C.7`,evidenceRef:`Ignored canonical value ${o.candidates[0]?.raw??`(empty)`}.`,severity:`medium`});else if(o?.status===`invalid`){let e=o.candidates[0]?.raw??`(empty)`;a.push({code:`canonical_invalid`,title:`Canonical URL is invalid`,detail:`The canonical link does not resolve to an HTTP or HTTPS URL.`,principle:`C.7`,evidenceRef:`Canonical value ${e} is not a valid HTTP or HTTPS URL.`,severity:`high`})}else if(r.canonical){let e;try{let t=new URL(r.canonical,r.finalUrl);[`http:`,`https:`].includes(t.protocol)&&(e=t.toString())}catch{}e?b_(e,r.finalUrl)||a.push({code:`canonical_mismatch`,title:`Canonical differs from final URL`,detail:`Canonical points to ${e}, fetched final URL is ${r.finalUrl}.`,principle:`C.7`,evidenceRef:`Canonical ${e} differs from final URL ${r.finalUrl}.`,severity:`medium`}):a.push({code:`canonical_invalid`,title:`Canonical URL is invalid`,detail:`The canonical link does not resolve to an HTTP or HTTPS URL.`,principle:`C.7`,evidenceRef:`Canonical value ${r.canonical} is not a valid HTTP or HTTPS URL.`,severity:`high`})}let s=e.site?await t.queryPageMetrics(e.site,e.url):void 0,c={url:e.url,fetchedAt:t.now().toISOString(),page:r,fetchDiagnostics:n.diagnostics,metrics:s,issues:a,recommendations:[],warnings:r.warnings};return c.recommendations=M_(c),c}const P_=new Set([`ac`,`co`,`com`,`edu`,`gov`,`net`,`org`]);function F_(e){return e.normalize(`NFKC`).replace(/([a-z])([A-Z])/g,`$1 $2`).toLowerCase().replace(/^https?:\/\//,``).replace(/^sc-domain:/,``).replace(/[^\p{L}\p{N}]+/gu,` `).replace(/\s+/g,` `).trim()}function I_(e){let t=e.replace(/^sc-domain:/,``).replace(/^https?:\/\//,``).split(`/`)[0];if(!t)return;let n=t.split(`.`).filter(Boolean);if(!n.length)return;let r=n.at(-1)??``,i=n.at(-2)??``,a=r.length===2&&P_.has(i)?2:1;return n.at(-(a+1))??n[0]}function L_(e){return[...new Set(e.map(F_).filter(Boolean))]}function R_(e){return e.length>3&&e.endsWith(`s`)?e.slice(0,-1):e}function z_(e,t){return e===t||R_(e)===R_(t)}function B_(e,t){if(!t.length||t.length>e.length)return!1;for(let n=0;n<=e.length-t.length;n++)if(t.every((t,r)=>z_(e[n+r]??``,t)))return!0;return!1}function V_(e){let t=I_(e.siteUrl);return L_([e.name,e.id,t].filter(e=>!!e).flatMap(e=>{let t=F_(e);return[t,t.replace(/\s+/g,``)]}))}function H_(e,t){let n=F_(e);if(!n||!t.length)return!1;let r=n.split(` `),i=n.replace(/\s+/g,``);return t.some(e=>{let t=F_(e);if(!t)return!1;let n=t.replace(/\s+/g,``),a=[...n].length,o=(a>=6||/[^\p{ASCII}]/u.test(n)&&a>=2)&&i.includes(n),s=t.split(` `);return s.length===1?r.some(e=>z_(e,t))||o:B_(r,s)||o})}function U_(e){if(e.includeBrand)return!1;let t=e.brandTerms?.length?e.brandTerms:V_({siteUrl:e.siteUrl});return H_(e.query,t)}function W_(e=28){return l_(e)}function G_(e,t,n){let r=F_(t);if(!r)return;let i=e.get(r)??{term:r,score:0,evidence:[]};i.score+=n.clicks*2+n.impressions*n.ctr+Math.max(0,5-n.position)*10,i.evidence.push(n),e.set(r,i)}function K_(e,t){let n=F_(e),r=n.replace(/\s+/g,``);return t.some(e=>{let t=F_(e),i=t.replace(/\s+/g,``);return n===t||r===i})}async function q_(e){let t=V_({id:e.id,name:e.name,siteUrl:e.site}),n=W_(e.days??28),{rows:r}=await J(e.site,{...n,dimensions:[`query`],type:`web`,dataState:`final`},{refresh:e.refresh}),i=e.minImpressions??10,a=new Map;for(let e of r){let n=e.keys[0]??``;if(e.impressions<i||e.position>3||e.ctr<.1||!H_(n,t))continue;let r={query:n,clicks:e.clicks,impressions:e.impressions,ctr:e.ctr,position:e.position},o=F_(n);o.split(` `).length<=4&&(K_(n,t)||n.includes(`.`))&&G_(a,o,r);for(let e of t)H_(n,[e])&&G_(a,e,r)}let o=[...a.values()].map(e=>({...e,score:Number(e.score.toFixed(2)),evidence:e.evidence.sort((e,t)=>t.clicks-e.clicks).slice(0,3)})).sort((e,t)=>t.score-e.score).slice(0,e.limit??10);return{site:e.site,generatedAt:new Date().toISOString(),derivedTerms:t,suggestedTerms:o.length?o.map(e=>e.term):t,candidates:o}}const J_={1:.27,2:.15,3:.1,4:.07,5:.05,6:.035,7:.025,8:.02,9:.015,10:.012};function Y_(e){return e.normalize(`NFKD`).replace(/\p{Diacritic}/gu,``).replace(/[’']/g,`'`).toLowerCase().replace(/\s+/g,` `).trim()}function X_(e){let t=Y_(e);return typeof Intl<`u`&&`Segmenter`in Intl?[...new Intl.Segmenter(`und`,{granularity:`word`}).segment(t)].filter(e=>e.isWordLike).map(e=>e.segment.trim()).filter(e=>e.length>1):(t.match(/[\p{L}\p{N}]+/gu)??[]).filter(e=>e.length>1)}function Z_(e){return X_(e)}function Q_(e,t){let n=new Set(e),r=new Set(t),i=[...n].filter(e=>r.has(e)),a=new Set([...n,...r]);return a.size===0?0:i.length/a.size}function $_(e=28,t=new Date){return l_(e,t)}const ev=[{intent:`forum/reddit`,patterns:[{regex:/\breddit\b/,label:`reddit`},{regex:/\bforums?\b/,label:`forum`}],action:`Retrieve the page or pages associated with this query. If relevant pages lack practical first-party detail, add evidence, examples, and limitations without imitating forum posts.`},{intent:`comparison`,patterns:[{regex:/\S+\s+vs\.?\s+\S+/,label:`vs`},{regex:/\S+\s+versus\s+\S+/,label:`versus`},{regex:/\balternatives?\s+to\b|\balternatives?\s*$/,label:`alternative comparison`}],action:`Retrieve the page or pages associated with this query. If relevant pages do not satisfy comparison intent, explain criteria, tradeoffs, alternatives, and which situations fit each option.`},{intent:`reviews`,patterns:[{regex:/\breviews?\b/,label:`review`},{regex:/\bcomplaints?\b/,label:`complaint`},{regex:/\bworth\s+it\b/,label:`worth it`}],action:`Retrieve the page or pages associated with this query. If relevant pages do not satisfy review intent, add original evidence, pros, cons, limitations, and suitable alternatives.`},{intent:`experience`,patterns:[{regex:/\bfirst[ -]?hand\b/,label:`first-hand`},{regex:/\bmy\s+experience(?:\s+with)?\b/,label:`my experience`},{regex:/\bexperience\s+with\b/,label:`experience with`},{regex:/\bpeople\s+say\b/,label:`people say`},{regex:/\breal\s+users?\b/,label:`real user`},{regex:/\buser\s+opinions?\b/,label:`user opinion`}],action:`Retrieve the page or pages associated with this query. If relevant pages do not satisfy experience-seeking intent, add genuine first-party examples, methodology, and clearly stated limitations.`},{intent:`recommendation`,patterns:[{regex:/\bbest\b/,label:`best`},{regex:/\brecommend(?:ed|ation|ations|ing)?\b/,label:`recommendation`},{regex:/\btop\s+\d+\b/,label:`ranked list`}],action:`Retrieve the page or pages associated with this query. If relevant pages do not satisfy recommendation intent, make selection criteria, evidence, tradeoffs, and use-case fit explicit.`}];function tv(e){let t=Y_(e),n=ev.map(e=>({pattern:e,terms:e.patterns.filter(({regex:e})=>e.test(t)).map(({label:e})=>e)})).filter(({terms:e})=>e.length>0),r=n[0];if(r)return{intent:r.pattern.intent,signals:n.map(({pattern:e})=>e.intent),matchedTerms:n.flatMap(({terms:e})=>e),confidence:`low`,method:`query-language-heuristic`,action:r.pattern.action}}function Y(e){if(e.value===void 0)return e.fallback;if(!Number.isInteger(e.value)||e.value<e.minimum||e.value>e.maximum)throw new k(`INVALID_INPUT`,`${e.label} must be a whole number between ${e.minimum} and ${e.maximum}.`);return e.value}function nv(e,t){return l_(e,t)}function rv(e,t){if(!e.startDate&&!e.endDate)return;if(!e.startDate||!e.endDate)throw new k(`INVALID_INPUT`,`Start date and end date must be provided together.`);let n=/^\d{4}-\d{2}-\d{2}$/,r=new Date(`${e.startDate}T00:00:00.000Z`),i=new Date(`${e.endDate}T00:00:00.000Z`);if(!n.test(e.startDate)||!n.test(e.endDate)||Number.isNaN(r.getTime())||Number.isNaN(i.getTime())||r.toISOString().slice(0,10)!==e.startDate||i.toISOString().slice(0,10)!==e.endDate||r>i)throw new k(`INVALID_INPUT`,`Start date and end date must be valid YYYY-MM-DD values in ascending order.`);let a=Math.floor((i.getTime()-r.getTime())/864e5)+1;if(a>t)throw new k(`INVALID_INPUT`,`Date range must not exceed ${t} days.`);return{range:{startDate:e.startDate,endDate:e.endDate},days:a}}function iv(e,t){let n=new Date(`${e}T00:00:00.000Z`),r=new Date(Date.UTC(n.getUTCFullYear(),n.getUTCMonth()-t,1)),i=new Date(Date.UTC(r.getUTCFullYear(),r.getUTCMonth()+1,0)).getUTCDate();return r.setUTCDate(Math.min(n.getUTCDate(),i)),r.toISOString().slice(0,10)}function av(e,t){if(!e.site.trim())throw new k(`INVALID_INPUT`,`site must not be empty.`);let n=Y({value:e.days,fallback:28,minimum:1,maximum:548,label:`days`}),r=rv(e,548);if(r&&e.days!==void 0)throw new k(`INVALID_INPUT`,`Use either days or an explicit startDate/endDate range, not both.`);let i=r?.range??nv(n,t),a=nv(1,t).endDate,o=new Date(`${a}T00:00:00.000Z`);o.setUTCDate(o.getUTCDate()+4);let s=iv(o.toISOString().slice(0,10),16);if(i.endDate>a)throw new k(`INVALID_INPUT`,`endDate must be on or before the latest finalized GSC date, ${a}.`);if(i.startDate<s)throw new k(`INVALID_INPUT`,`startDate must be on or after ${s}, the current 16-month GSC retention boundary.`);return{days:r?.days??n,range:i,availableDateWindow:{earliestDate:s,latestFinalDate:a},limit:Y({value:e.limit,fallback:25,minimum:1,maximum:100,label:`limit`}),minImpressions:Y({value:e.minImpressions,fallback:20,minimum:0,maximum:1e9,label:`minImpressions`}),maxRows:Y({value:e.maxRows,fallback:5e4,minimum:1,maximum:5e4,label:`maxRows`})}}function ov(e,t){let n=[...e].map(e=>e.codePointAt(0)??0),r=[...t].map(e=>e.codePointAt(0)??0);for(let e=0;e<Math.min(n.length,r.length);e++){let t=(n[e]??0)-(r[e]??0);if(t!==0)return t}return n.length-r.length}function sv(e,t){return t.impressions-e.impressions||t.clicks-e.clicks||e.position-t.position||ov(e.query,t.query)}function cv(e,t){return e.keys.length===1&&!!t&&Number.isFinite(e.clicks)&&Number.isFinite(e.impressions)&&Number.isFinite(e.ctr)&&Number.isFinite(e.position)&&e.clicks>=0&&e.impressions>0&&e.clicks<=e.impressions&&e.ctr>=0&&e.ctr<=1&&e.position>0}function lv(e){return`${e.clicks}\u0000${e.impressions}\u0000${e.ctr}\u0000${e.position}`}function uv(e,t){let n=new Map;for(let r of e){let e=r.keys[0]?.trim()??``;if(!cv(r,e)){t.invalidRows++;continue}let i=n.get(e)??[];i.push(r),n.set(e,i)}let r=[];for(let[e,i]of n){if(new Set(i.map(lv)).size>1){t.conflictingRows+=i.length;continue}t.duplicateRows+=i.length-1;let n=i[0];n&&r.push({query:e,clicks:n.clicks,impressions:n.impressions,ctr:n.ctr,position:n.position})}return r}const dv={searchAnalytics:J,now:()=>new Date};async function fv(e,t=dv){let n=t.now(),r=av(e,n),i=await t.searchAnalytics(e.site,{...r.range,dimensions:[`query`],type:`web`,dataState:`final`,aggregationType:`auto`,maxRows:r.maxRows},{refresh:e.refresh}),a={sourceRows:i.rows.length,invalidRows:0,duplicateRows:0,conflictingRows:0,brandRows:0,belowMinimumRows:0,eligibleRows:0},o=uv(i.rows,a).filter(t=>t.impressions<r.minImpressions?(a.belowMinimumRows++,!1):U_({query:t.query,siteUrl:e.site,brandTerms:e.brandTerms,includeBrand:e.includeBrand})?(a.brandRows++,!1):!0).sort(sv);a.eligibleRows=o.length;let s=i.rowsFetched>=r.maxRows,c=[];return s&&c.push(`GSC reached the ${r.maxRows}-row retention cap. Lower-click queries may be missing.`),a.invalidRows>0&&c.push(`${a.invalidRows} malformed GSC rows were excluded.`),a.conflictingRows>0&&c.push(`${a.conflictingRows} conflicting duplicate GSC rows were excluded.`),{site:e.site,generatedAt:n.toISOString(),rangeDays:r.days,dateRange:r.range,filters:{limit:r.limit,minImpressions:r.minImpressions,maxRows:r.maxRows},source:{provider:`google-search-console`,dimensions:[`query`],searchType:`web`,dataState:`final`,aggregationType:`auto`,rowsFetched:i.rowsFetched,calls:i.calls,maxRows:r.maxRows,possiblyTruncated:s,completeness:s?`possibly-truncated`:`retained-query-rows-only`,availableDateWindow:{...r.availableDateWindow,basis:`rolling-16-month-retention-with-finalization-lag`}},selection:a,rows:o,warnings:c,caveats:[`Search Console exposes retained top query rows and does not guarantee every query.`,`Clicks, impressions, CTR, and position are observed GSC metrics; classifications and generated prompts are heuristics.`]}}function pv(e){return e.sourceRows===0?`empty`:e.partial?`partial`:e.classifiedRows===0?`filtered`:`available`}async function mv(e,t=dv){let n=await fv(e,t),r=n.rows.flatMap(e=>{let t=tv(e.query);return t?[{row:e,classification:t}]:[]}),i=r.slice(0,n.filters.limit).map(({row:e,classification:t})=>({...e,intent:t.intent,signals:t.signals,matchedTerms:t.matchedTerms,confidence:t.confidence,evidenceScope:`retained-gsc-query-language`,action:t.action})),a=n.source.possiblyTruncated||n.selection.invalidRows>0||n.selection.conflictingRows>0,o=pv({sourceRows:n.selection.sourceRows,classifiedRows:r.length,partial:a}),s=o===`empty`?`GSC returned no retained query rows for this range.`:o===`partial`&&r.length===0?`No matching intent language appeared in the retained rows, but incomplete source evidence makes that negative inconclusive.`:o===`partial`?`Partial evidence: ${r.length} retained queries matched explicit intent language, but incomplete source rows may omit other queries.`:o===`filtered`?`No retained ${e.includeBrand?``:`non-brand `}queries met both the thresholds and the explicit intent-language rules.`:`${r.length} retained queries matched explicit community, comparison, review, experience, or recommendation language; treat each as a review hypothesis.`;return{schemaVersion:2,site:n.site,generatedAt:n.generatedAt,rangeDays:n.rangeDays,dateRange:n.dateRange,dataStatus:o,source:n.source,filters:{...n.filters,brandFiltering:e.includeBrand?`included`:`excluded`},methodology:{id:`gsc_community_intent_query_heuristic`,version:2,classificationUnit:`retained-query-row`,classificationType:`language-heuristic`,classificationLanguage:`english-patterns`,pageContentVerified:!1,estimatedTrafficLift:!1},selection:{...n.selection,unclassifiedRows:n.rows.length-r.length,classifiedRows:r.length,returnedRows:i.length,limitedRows:r.length-i.length},summary:{classifiedQueries:r.length,returnedQueries:i.length,returnedImpressions:i.reduce((e,t)=>e+t.impressions,0),returnedClicks:i.reduce((e,t)=>e+t.clicks,0),verdict:s},items:i,warnings:n.warnings,caveats:[...n.caveats,`Intent labels come from explicit query-language patterns, not SERP, page-content, or audience research.`,`Classification patterns are English-only; non-English queries remain in source counts but may not be classified.`,`Review the ranking page and live search results before changing content.`]}}const hv=new Set([`a`,`an`,`and`,`are`,`as`,`at`,`be`,`by`,`for`,`from`,`how`,`in`,`is`,`of`,`on`,`or`,`the`,`to`,`with`]);function gv(e){return e.normalize(`NFKD`).replace(/\p{Diacritic}/gu,``).replace(/([\p{L}\p{N}])[’']([\p{L}\p{N}])/gu,`$1$2`).toLowerCase().replace(/&/g,` and `).replace(/[^\p{L}\p{N}]+/gu,` `).replace(/\s+/g,` `).trim()}function _v(e){return[...e].some(e=>(e.codePointAt(0)??0)>127)}function vv(e){let t=gv(e).split(` `).filter(e=>e.length>1||_v(e)),n=t.filter(e=>(e.length>2||_v(e))&&!hv.has(e));return[...new Set(n.length?n:t)]}function yv(e,t){let n=gv(e),r=gv(t);if(!n||!r)return 0;let i=RegExp(`(?:^|\\s)${n.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}(?=\\s|$)`,`g`);return[...r.matchAll(i)].length}function bv(e,t=``){let n=vv(e),r=gv(t),i=n.filter(e=>r.split(` `).some(t=>t===e||t.startsWith(e))),a=n.filter(e=>!i.includes(e));return{phraseCount:yv(e,t),matchedTerms:i,missingTerms:a,termCoverage:n.length?i.length/n.length:0}}function xv(e){let t=e.query.trim();return t.length<=80?t:e.queryTerms.slice(0,7).join(` `)||`${t.slice(0,77)}...`}function Sv(e){let t=[];return e.fields.title.termCoverage<.8&&t.push(`title`),e.fields.h1.termCoverage<.8&&t.push(`H1`),e.fields.metaDescription.termCoverage<.8&&t.push(`meta description`),t}function Cv(e){return e===1?`does`:`do`}function wv(e){let t=0;return e.title.termCoverage<.8&&(t+=3),e.h1.termCoverage<.8&&(t+=1),e.metaDescription.termCoverage<.8&&(t+=1),e.mainContent.termCoverage<.8&&(t+=5),t}function Tv(e){return e.mainContent.termCoverage<.8?`content-gap`:e.title.termCoverage<.8||e.metaDescription.termCoverage<.8||e.h1.termCoverage<.8?`serp-framing`:`covered`}function Ev(e){let t=[...e.technicalSignals];return e.fields.title.phraseCount===0&&t.push(`exact-phrase-missing`),e.fields.title.termCoverage<.8&&t.push(`title-gap`),e.fields.metaDescription.termCoverage<.8&&t.push(`meta-description-gap`),e.fields.h1.termCoverage<.8&&t.push(`h1-gap`),e.fields.mainContent.termCoverage<.8&&t.push(`body-gap`),e.technicalSignals.length>0?{classification:`technical-check`,signals:t}:{classification:Tv(e.fields),signals:t}}function Dv(e){let t=xv(e);if(e.status===`failed`)return`I could not fetch or read the page, so do not change the content yet. Check the fetch error, blocking, or rendering issue first, then rerun this report.`;if(e.classification===`technical-check`)return e.signals.includes(`redirected`)&&e.finalUrl?`This GSC URL resolves to ${e.finalUrl}. Confirm that this is the page Google should rank for "${t}" before editing titles or copy.`:`Check whether this URL is indexable, canonical, and fetchable before editing copy for "${t}". If the page has a technical issue, fix that first.`;if(e.classification===`content-gap`){let n=e.fields.mainContent.missingTerms.slice(0,4);return n.length?`The page ranks for "${t}" but the main content does not clearly cover: ${n.join(`, `)}. Add a short, useful section or intro sentence that answers that angle directly.`:`The page ranks for "${t}" but the main content does not clearly answer that angle. Add a short section that covers it directly.`}if(e.classification===`serp-framing`){let n=Sv(e);return`The main content covers the important terms for "${t}", but the ${n.length?n.join(`, `):`title and meta`} ${Cv(n.length)} not make that exact search angle clear enough. Test clearer wording there before rewriting the body.`}return`The page already covers "${t}". Do not add more copy for this query; test the title/meta, improve internal links, or check whether the SERP format is limiting clicks.`}function Ov(e){if(e.status===`failed`)return`Content verification failed.`;if(e.classification===`technical-check`)return e.signals.includes(`redirected`)&&e.finalUrl?`GSC URL resolves to ${e.finalUrl}; verify the canonical target first.`:`Fetch diagnostics show a technical check is needed before judging content.`;if(e.classification===`covered`)return`The page covers the query across the main on-page signals.`;let t=e.fields.title.phraseCount>0,n=e.fields.h1.phraseCount>0,r=e.fields.mainContent.phraseCount>0,i=Math.round(e.fields.mainContent.termCoverage*100);return t&&n&&r?`Exact query appears in title, H1, and main content.`:i>=100?`Main content covers every meaningful query term, but exact SERP wording may be weak.`:`Main content covers ${i}% of meaningful query terms.`}function kv(e){let t=Av(e.query,e.page),n=Ev({technicalSignals:S_({url:e.url,page:e.page,fetchDiagnostics:e.fetchDiagnostics,httpStatus:e.httpStatus}),fields:t}),r={verifiedAt:e.verifiedAt??new Date().toISOString(),query:e.query,url:e.url,finalUrl:e.page.finalUrl,status:`verified`,httpStatus:e.httpStatus,warnings:[...new Set([...e.warnings??[],...e.page.warnings])],wordCount:e.page.wordCount,fetchDiagnostics:e.fetchDiagnostics,contentGapScore:wv(t),queryTerms:vv(e.query),fields:t,classification:n.classification,signals:n.signals,recommendation:``,summary:``};return{...r,recommendation:Dv(r),summary:Ov(r)}}function Av(e,t){return{title:bv(e,t.title),h1:bv(e,t.headings.filter(e=>e.level===1).map(e=>e.text).join(` `)),metaDescription:bv(e,t.metaDescription),mainContent:bv(e,t.contentText)}}async function jv(e){let t;try{t=await(e.fetch??Hg)(e.url,{js:e.js??`auto`,refresh:e.refresh,rate:e.rate});let n=await(e.extract??Lh)(t,`defuddle`);return kv({query:e.query,url:e.url,page:n,fetchDiagnostics:t.diagnostics,httpStatus:t.status,warnings:t.warnings,verifiedAt:e.verifiedAt})}catch(n){let r=S_({url:e.url,fetchDiagnostics:t?.diagnostics,httpStatus:t?.status});return{verifiedAt:e.verifiedAt??new Date().toISOString(),query:e.query,url:e.url,status:`failed`,error:n instanceof Error?n.message:String(n),httpStatus:t?.status,warnings:t?.warnings??[],wordCount:void 0,fetchDiagnostics:t?.diagnostics,contentGapScore:0,queryTerms:vv(e.query),fields:{title:bv(e.query),h1:bv(e.query),metaDescription:bv(e.query),mainContent:bv(e.query)},classification:`fetch-failed`,signals:r,recommendation:`Verification failed; inspect fetch diagnostics before making content calls.`,summary:`Content verification failed.`}}}function Mv(e){return Math.max(1,Math.min(10,Math.round(e)))}function Nv(e){return J_[Mv(e)]??.01}function Pv(e,t){if(e.length===0)return;let n=[...e].sort((e,t)=>e-t);return n[Math.min(n.length-1,Math.max(0,Math.ceil(t/100*n.length)-1))]}function Fv(e){let t=e.rows.filter(t=>!e.excludedRows?.has(t)),n=t.reduce((e,t)=>e+t.impressions,0),r=new Map;for(let e of t){let t=e.keys?.[1]||`${e.keys?.[0]??`query`}:${e.position}`,n=r.get(t)??{clicks:0,impressions:0};n.clicks+=e.clicks,n.impressions+=e.impressions,r.set(t,n)}let i=[...r.values()].filter(e=>e.impressions>=30),a=i.map(e=>e.clicks/e.impressions),o=a.filter(e=>e>0),s=i.reduce((e,t)=>e+t.impressions,0),c=s>=1e3&&a.length>=5&&o.length>=3,l=Nv(e.position),u=e.samplePopulation??`positive_url_samples`,d=c?Pv(u===`all_qualified_url_samples`?a:o,75):void 0,f=l*.5,p=l*1.5,m=d!==void 0,h=m?Math.max(f,Math.min(d,p)):l,g=m&&d<f?`_floored`:m&&d>p?`_capped`:``,_=m?`site_gsc_position_bucket_robust_p75${u===`all_qualified_url_samples`?`_all_samples`:``}${e.sourceSuffix??``}${g}`:e.fallbackSource??`default_position_curve`;return{ctr:Number(h.toFixed(4)),source:_,samplePopulation:u,rows:t.length,impressions:n,qualifiedImpressions:s,urlSamples:a.length,positiveUrlSamples:o.length}}function Iv(e){let t={},n=1/0;for(let r of Object.keys(J_).map(Number)){let i=e[r]??Fv({rows:[],position:r}),a=Math.min(i.ctr,n),o=a<i.ctr;n=a,t[String(r)]={...i,ctr:Number(a.toFixed(4)),source:o?`${i.source}_monotonic_smoothed`:i.source}}return t}function Lv(e,t={}){let n=new Map;for(let t of e){let e=Mv(t.position);n.set(e,[...n.get(e)??[],t])}let r={};for(let e of Object.keys(J_).map(Number))r[e]=Fv({rows:n.get(e)??[],position:e,samplePopulation:t.samplePopulation,fallbackSource:t.fallbackSource});let i=Iv(r);function a(e,r,a){let o=Mv(e.position),s=new Set(r),c={};for(let e of Object.keys(J_).map(Number))c[e]=Fv({rows:n.get(e)??[],position:e,excludedRows:s,sourceSuffix:a,samplePopulation:t.samplePopulation,fallbackSource:t.fallbackSource});return Iv(c)[String(o)]??i[String(o)]??Fv({rows:[],position:o})}return{byPosition:i,forRow(e){return a(e,[e],`_leave_one_out`)},forUrl(t){let n=t.keys?.[1];return a(t,n?e.filter(e=>b_(e.keys?.[1]??``,n)):[t],`_leave_target_url_out`)},forAggregate(e,t=[]){return a(e,t,t.length?`_leave_group_out`:void 0)}}}function Rv(e){let t=S_(e);return t.length>0?{status:`technical-check`,reason:`Fetch or indexability evidence needs checking before making an on-page recommendation.`,signals:t,httpStatus:e.httpStatus}:e.page?{status:`verified`,reason:`The recommendation uses extracted on-page evidence.`,signals:[],httpStatus:e.httpStatus,fields:Av(e.query,e.page)}:{status:`unverified`,reason:e.fetchDiagnostics?.fetched?`The page was fetched, but extracted content was not available for verification.`:`On-page content was not verified for this analysis.`,signals:[],httpStatus:e.httpStatus}}function zv(e){if(e.verification.status===`technical-check`)return`technical-check`;if(e.verification.status===`verified`&&e.verification.fields){let t=Tv(e.verification.fields);if(t!==`covered`)return t}return e.position>10?`ranking`:e.expectedCtr!==void 0&&e.ctr<e.expectedCtr*.65?`ctr`:e.verification.status===`verified`?`covered`:`unverified`}function Bv(e){return e.type===`technical-check`?`Check fetchability, indexability, redirects, and canonical signals for this URL before changing content for "${e.query}".`:e.type===`unverified`?`${e.position>10?`GSC shows the query outside page one.`:`GSC shows page-one visibility.`} Verify the live page and current SERP intent before deciding whether content, internal links, or snippet wording needs work.`:e.type===`content-gap`?`The extracted copy does not clearly cover the main terms in "${e.query}". Confirm the search intent, then improve the existing answer if the query belongs on this page.`:e.type===`serp-framing`?`The extracted body covers "${e.query}", but the title, meta description, or H1 does not reflect it clearly. Check the live SERP before testing more precise framing.`:e.type===`ranking`?`This query ranks outside page one, so no CTR lift is claimed. Check intent fit, content quality, internal links, and competing pages before choosing an edit.`:e.type===`ctr`?`This page-one query is below its directional CTR benchmark. Review the live SERP, then test title and meta-description framing without assuming the body needs rewriting.`:`The extracted page covers "${e.query}" and its CTR is not materially below the benchmark. Avoid a blind rewrite; monitor the query and inspect the SERP for format changes.`}function Vv(e,t){return e?{applicable:!0,expectedCtr:e.ctr,source:e.source,peerRows:e.rows,peerImpressions:e.impressions,qualifiedPeerImpressions:e.qualifiedImpressions,urlSamples:e.urlSamples,positiveUrlSamples:e.positiveUrlSamples,excludedTargetRows:t}:{applicable:!1,source:`not_applicable_outside_page_one`,peerRows:0,peerImpressions:0,qualifiedPeerImpressions:0,urlSamples:0,positiveUrlSamples:0,excludedTargetRows:t}}function Hv(e){return/\p{L}/u.test(e)}function Uv(e){return/\d/.test(e)}function Wv(e){let t=e.toLowerCase(),n=(t.match(/(?:^|\s)-?(?:site|filetype):/g)??[]).length;return e.length>120&&(t.includes(`x_keyword_search`)||t.includes(`min_faves`)||t.includes(`filter:`)||t.includes(` since:`)||t.includes(` until:`)||n>=3)}function Gv(e){let t=e.toLowerCase();return/(?:^|\s)-?(?:site|filetype|inurl|intitle|intext):\S+/.test(t)}function Kv(e){let t=e.toLowerCase();return t.includes(`ranking/`)||/\bmoved up\b/.test(t)}function qv(e){return/^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/i.test(e.trim())}function Jv(e){return!Z_(e).length||Gv(e)||Wv(e)||Kv(e)||qv(e)}function Yv(e){let t=Z_(e);if(!t.length||!Hv(e)||Jv(e))return!0;let n=t.filter(Hv);return!!(t.length===1&&Uv(t[0]??``)||n.length<=1&&t.some(Uv))}function Xv(e,t,n,r){return e===void 0||!Number.isFinite(e)?t:Math.min(r,Math.max(n,Math.floor(e)))}function Zv(e){try{let t=new URL(e);return t.protocol===`http:`||t.protocol===`https:`}catch{return!1}}function Qv(e){return Number.isFinite(e.clicks)&&Number.isFinite(e.impressions)&&Number.isFinite(e.ctr)&&Number.isFinite(e.position)&&e.clicks>=0&&e.impressions>0&&e.clicks<=e.impressions&&e.ctr>=0&&e.ctr<=1&&e.position>0}function $v(e){return U_({query:e.query,siteUrl:e.site,brandTerms:e.brandTerms,includeBrand:e.includeBrand})}function ey(e){return{minImpressions:Xv(e.minImpressions,10,1,2**53-1),limit:Xv(e.limit,25,1,100)}}function ty(e){let t={sourceRows:e.rows.length,invalidRows:0,wrongPageRows:0,belowMinimumRows:0,lowActionabilityRows:0,brandRows:0,eligibleRows:0,returnedRows:0,limitedRows:0},n=[];for(let r of e.rows){let i=r.keys[0]?.trim()??``,a=r.keys[1]??``;if(r.keys.length<2||!i||!Zv(a)||!Qv(r)){t.invalidRows++;continue}if(!b_(a,e.url)){t.wrongPageRows++;continue}if(r.impressions<e.minImpressions){t.belowMinimumRows++;continue}if(Yv(i)){t.lowActionabilityRows++;continue}if($v({query:i,site:e.site,brandTerms:e.brandTerms,includeBrand:e.includeBrand})){t.brandRows++;continue}t.eligibleRows++,n.push(r)}return{eligibleRows:n,selection:t}}function ny(e){return e.rows.filter(t=>{let n=t.keys[0]?.trim()??``;return t.keys.length>=2&&Zv(t.keys[1]??``)&&Qv(t)&&t.position>=1&&t.position<=10&&!!n&&!Yv(n)&&!$v({query:n,site:e.site,brandTerms:e.brandTerms,includeBrand:e.includeBrand})})}function ry(e,t){return e<t?-1:+(e>t)}function iy(e,t){return(t.estimatedCtrClickShortfall??-1)-(e.estimatedCtrClickShortfall??-1)||t.impressions-e.impressions||e.position-t.position||ry(e.query,t.query)||ry(e.url,t.url)}function ay(e){let t=e.row.keys[0]?.trim()??``,n=e.row.position>=1&&e.row.position<=10?e.benchmarkContext.forAggregate(e.row,e.excludedTargetRows):void 0,r=n?Number((n.ctr*e.row.impressions).toFixed(2)):void 0,i=n?Number(Math.max(0,n.ctr*e.row.impressions-e.row.clicks).toFixed(2)):void 0,a=Rv({query:t,url:e.reportInput.url,page:e.reportInput.page,fetchDiagnostics:e.reportInput.fetchDiagnostics,httpStatus:e.reportInput.httpStatus}),o=zv({position:e.row.position,ctr:e.row.ctr,expectedCtr:n?.ctr,verification:a});return{query:t,url:e.row.keys[1]??e.reportInput.url,clicks:e.row.clicks,impressions:e.row.impressions,ctr:e.row.ctr,position:e.row.position,expectedCtr:n?.ctr,expectedClicks:r,estimatedCtrClickShortfall:i,estimatedClickLift:i,opportunityType:o,benchmark:Vv(n,e.excludedTargetRows.length),verification:a,recommendation:Bv({query:t,position:e.row.position,type:o})}}function oy(e){let{minImpressions:t,limit:n}=ey(e),{eligibleRows:r,selection:i}=ty({rows:e.targetRows,site:e.site,url:e.url,minImpressions:t,brandTerms:e.brandTerms,includeBrand:e.includeBrand}),a=ny({rows:e.benchmarkRows,site:e.site,brandTerms:e.brandTerms,includeBrand:e.includeBrand}),o=a.filter(t=>b_(t.keys[1]??``,e.url)),s=Lv(a),c=r.map(t=>ay({row:t,reportInput:e,benchmarkContext:s,excludedTargetRows:o})).sort(iy).slice(0,n),l=Number(c.reduce((e,t)=>e+(t.estimatedCtrClickShortfall??0),0).toFixed(2));return{site:e.site,url:e.url,minImpressions:t,limit:n,httpStatus:e.httpStatus,dataStatus:e.targetRows.length===0?`empty`:r.length===0?`filtered`:`available`,sourceRows:e.targetRows.length,eligibleRows:r.length,returnedRows:c.length,benchmarkSourceRows:e.benchmarkRows.length,benchmarkEligibleRows:a.length,excludedTargetBenchmarkRows:o.length,selection:{...i,returnedRows:c.length,limitedRows:Math.max(0,r.length-c.length)},items:c,summary:{clicks:c.reduce((e,t)=>e+t.clicks,0),impressions:c.reduce((e,t)=>e+t.impressions,0),opportunities:c.filter(e=>e.opportunityType!==`covered`&&e.opportunityType!==`unverified`).length,estimatedCtrClickShortfall:l,estimatedClickLift:l}}}const sy=new Map([`technical-check`,`content-gap`,`serp-framing`,`ranking`,`ctr`,`unverified`,`covered`].map((e,t)=>[e,t]));function cy(e,t,n){return e===1?t:n}function ly(e){return e.opportunityType!==`covered`&&e.opportunityType!==`unverified`}function uy(e,t){return(sy.get(e.opportunityType)??99)-(sy.get(t.opportunityType)??99)||(t.estimatedCtrClickShortfall??-1)-(e.estimatedCtrClickShortfall??-1)||t.impressions-e.impressions||(e.query<t.query?-1:+(e.query>t.query))}function dy(e){if(!e.length)return`no-data`;if(e.some(e=>e.opportunityType===`technical-check`))return`technical-check`;let t=new Map;for(let n of e)t.set(n.opportunityType,(t.get(n.opportunityType)??0)+1);return[...t.entries()].sort(([e,t],[n,r])=>r-t||(sy.get(e)??99)-(sy.get(n)??99))[0]?.[0]??`no-data`}function fy(e){let t=[e.belowMinimumRows?`${e.belowMinimumRows} below the impression threshold`:void 0,e.brandRows?`${e.brandRows} branded`:void 0,e.lowActionabilityRows?`${e.lowActionabilityRows} low-actionability`:void 0,e.invalidRows?`${e.invalidRows} invalid`:void 0,e.wrongPageRows?`${e.wrongPageRows} for another URL`:void 0].filter(e=>!!e);return t.length?` Rows removed: ${t.join(`, `)}.`:``}function py(e){let{analysis:t,focus:n}=e;if(t.dataStatus===`empty`)return`No GSC query rows were found for this exact URL in the selected window.`;if(t.dataStatus===`filtered`)return`GSC returned rows for this URL, but none met the report criteria.${fy(t.selection)}`;if(n===`technical-check`)return`Technical fetch or indexability evidence needs checking before content or snippet changes.`;if(n===`unverified`)return`GSC visibility exists, but the page was not verified and no specific change is justified yet.`;if(n===`covered`)return`The verified page covers the sampled queries and CTR is not materially below its directional benchmark.`;let r=t.summary.opportunities,i=`${r} ${cy(r,`actionable observation`,`actionable observations`)} found.`;return n===`content-gap`?`${i} Verified body coverage is the main issue; confirm intent before expanding the page.`:n===`serp-framing`?`${i} The body is relevant, but its title, meta description, or H1 framing is weak.`:n===`ranking`?`${i} Most upside requires stronger relevance or authority; no CTR-only lift is claimed for page-two queries.`:`${i} The page-one CTR shortfall is about ${t.summary.estimatedCtrClickShortfall.toFixed(0)} clicks against the directional benchmark.`}function my(e){let t=e.items.filter(ly).sort(uy),n=t[0];if(!n)return e.items.some(e=>e.opportunityType===`unverified`)?[`Verify the live page and current SERP before turning these GSC observations into an editing brief.`]:e.items.length?[`Avoid an unnecessary rewrite. Monitor the queries and inspect current SERP formats before changing the page.`]:[`No page-level action is justified from this result. Lower the impression threshold only when long-tail inspection is useful.`];let r=[`Start with "${n.query}". ${n.recommendation}`],i=t.filter(e=>e.opportunityType===`technical-check`).length;i&&r.push(`Resolve the technical evidence affecting ${i} ${cy(i,`row`,`rows`)} before content work.`);let a=t.filter(e=>e.opportunityType===`content-gap`).length;return a>=3&&r.push(`Group the ${a} verified content gaps into useful sections rather than adding one thin paragraph per query.`),r}const hy=1e5,gy={searchAnalytics:J,fetch:Hg,extract:Lh,now:()=>new Date};function _y(e){return e instanceof Error?e.message:String(e)}function vy(e){return[...new Set(e.filter(Boolean))]}function yy(e){if(e===void 0)return 28;if(!Number.isInteger(e)||e<1||e>548)throw new k(`INVALID_INPUT`,`Days must be a whole number between 1 and 548.`);return e}async function by(e){let t=[],n;try{n=await e.dependencies.fetch(e.url,{js:e.js??`auto`,refresh:e.refresh}),t.push(...n.warnings)}catch(e){let t=`Page fetch failed: ${_y(e)}`;return{verification:{status:`failed`,reason:t},warnings:[t]}}try{let r=await e.dependencies.extract(n);return t.push(...r.warnings),{fetched:n,page:r,verification:{status:`verified`,reason:`The live page was fetched and extracted for on-page checks.`},warnings:vy(t)}}catch(e){let r=`Page extraction failed: ${_y(e)}`;return{fetched:n,verification:{status:`failed`,reason:r},warnings:vy([...t,r])}}}function xy(e){let{analysis:t,reportInput:n,range:r}=e;return[`Date window: ${r.startDate} to ${r.endDate}, using final GSC data where available.`,`Selection: ${t.selection.sourceRows} exact-URL rows, ${t.selection.eligibleRows} eligible, ${t.selection.returnedRows} returned.`,`Minimum query impressions: ${t.minImpressions}.`,`Brand filtering: ${n.includeBrand?`brand queries included`:`brand queries excluded when detected or configured`}.`,`Content verification: ${e.verification.status} (${e.verification.reason})`,`CTR benchmarks are directional. GSC query data omits anonymized queries and does not explain SERP features or intent changes.`,e.benchmarkTruncated?`The site-wide benchmark reached its ${hy.toLocaleString(`en-US`)}-row cap, so provenance is complete but the peer sample is truncated.`:``].filter(Boolean)}async function Sy(e,t=gy){let n=Gg(e.site,e.url),r=yy(e.days),i=$_(r),a=await t.searchAnalytics(e.site,{...i,dimensions:[`query`,`page`],type:`web`,dataState:`final`,dimensionFilterGroups:[{groupType:`and`,filters:[{dimension:`page`,operator:`equals`,expression:n}]}]},{refresh:e.refresh}),o=oy({targetRows:a.rows,benchmarkRows:[],site:e.site,url:n,minImpressions:e.minImpressions,limit:e.limit,brandTerms:e.brandTerms,includeBrand:e.includeBrand}),s=(e.verifyContent??!0)&&o.eligibleRows>0?by({url:n,refresh:e.refresh,js:e.js,dependencies:t}):Promise.resolve({verification:{status:`skipped`,reason:o.eligibleRows===0?`No eligible query rows required page verification.`:`Page verification was disabled.`},warnings:[]}),c=o.eligibleRows>0?t.searchAnalytics(e.site,{...i,dimensions:[`query`,`page`],type:`web`,dataState:`final`,maxRows:hy},{refresh:e.refresh}):Promise.resolve({rows:[],calls:0,rowsFetched:0}),[l,u]=await Promise.all([s,c]),d=oy({targetRows:a.rows,benchmarkRows:u.rows,site:e.site,url:n,minImpressions:e.minImpressions,limit:e.limit,brandTerms:e.brandTerms,includeBrand:e.includeBrand,page:l.page,fetchDiagnostics:l.fetched?.diagnostics,httpStatus:l.fetched?.status}),f=dy(d.items),p=py({analysis:d,focus:f}),m=u.rowsFetched>=hy;return{site:e.site,url:n,generatedAt:t.now().toISOString(),range:i,rangeDays:r,source:{provider:`google-search-console`,dimensions:[`query`,`page`],searchType:`web`,dataState:`final`,targetRowsFetched:a.rowsFetched,targetCalls:a.calls},dataStatus:d.dataStatus,selection:d.selection,verification:l.verification,benchmark:{sourceRows:d.benchmarkSourceRows,eligibleRows:d.benchmarkEligibleRows,excludedTargetRows:d.excludedTargetBenchmarkRows,rowsFetched:u.rowsFetched,calls:u.calls,maxRows:hy,possiblyTruncated:m},page:l.page&&l.fetched?{finalUrl:l.page.finalUrl,status:l.fetched.status,title:l.page.title,h1:l.page.headings.find(e=>e.level===1)?.text,wordCount:l.page.wordCount,fetchDiagnostics:l.fetched.diagnostics}:void 0,summary:{queries:d.returnedRows,clicks:d.summary.clicks,impressions:d.summary.impressions,opportunities:d.summary.opportunities,estimatedCtrClickShortfall:d.summary.estimatedCtrClickShortfall,estimatedClickLift:d.summary.estimatedCtrClickShortfall,verdict:p,focus:f},items:d.items,warnings:l.warnings,caveats:xy({reportInput:e,analysis:d,range:i,benchmarkTruncated:m,verification:l.verification}),recommendations:my(d)}}function Cy(e){let t=e.toLowerCase();return/\b(vs|versus|alternative|alternatives|compare|comparison|best)\b/.test(t)?`comparison`:/\b(price|pricing|cost|buy|deal|coupon|review|reviews)\b/.test(t)?`commercial`:/\b(how|steps|guide|tutorial|setup|fix|create|make)\b/.test(t)?`how-to`:/\b(what is|meaning|definition|explained|why)\b/.test(t)?`definition`:/\b(near me|city|state|county|postcode|zip|london|new york)\b/.test(t)?`local`:/\b(login|sign in|app|dashboard|support)\b/.test(t)?`navigational`:`general`}function wy(e){if(!e.items.length)return 0;let t=e.summary.opportunities/e.items.length,n=e.items.filter(e=>e.opportunityType===`content-gap`).length*8,r=e.items.filter(e=>e.opportunityType===`serp-framing`).length*5,i=e.items.filter(e=>e.opportunityType===`ctr`).length*4;return Math.max(0,Math.min(100,Math.round(100-t*40-n-r-i)))}function Ty(e){return e.split(/\s+/).filter(Boolean).map(e=>`${e[0]?.toUpperCase()??``}${e.slice(1)}`).join(` `)}function Ey(e){return e.estimatedCtrClickShortfall??e.estimatedClickLift??0}function Dy(e,t){return e===`comparison`?`${Ty(t)}: what to choose`:e===`how-to`?`How to ${t.replace(/^how to\s+/i,``)}`:e===`definition`?`${Ty(t)}, in plain English`:e===`commercial`?`${Ty(t)}: costs and trade-offs`:Ty(t)}function Oy(e){let t=new Map;for(let n of e.items){let e=Cy(n.query),r=t.get(e)??{queries:0,impressions:0,clicks:0};r.queries+=1,r.impressions+=n.impressions,r.clicks+=n.clicks,t.set(e,r)}return[...t.entries()].map(([e,t])=>({intent:e,...t})).sort((e,t)=>t.impressions-e.impressions)}function ky(e){let t=e.items.filter(e=>e.opportunityType!==`covered`&&e.opportunityType!==`technical-check`&&e.opportunityType!==`unverified`).sort((e,t)=>Ey(t)-Ey(e)),n=t[0],r=t.filter(e=>e.verification.status===`verified`&&(e.opportunityType===`content-gap`||e.opportunityType===`ranking`)),i=r.slice(0,5).map(e=>{let t=Cy(e.query);return{heading:Dy(t,e.query),why:e.recommendation,queries:[e.query,...r.filter(n=>n.query!==e.query&&Cy(n.query)===t).slice(0,3).map(e=>e.query)]}});return{titleAngle:n?`Make the title clearly promise the "${n.query}" answer.`:void 0,h1Angle:n?`Keep the H1 aligned with the page topic, then use subheadings for "${n.query}" variants.`:void 0,metaAngle:n?`Use the meta description to state the outcome, trade-off, or direct answer searchers get.`:void 0,sections:i,internalLinkAnchors:r.slice(0,8).map(e=>e.query).filter((e,t,n)=>n.indexOf(e)===t)}}function Ay(e){let t=e.items.filter(e=>e.opportunityType===`technical-check`),n=e.items.filter(e=>e.opportunityType===`unverified`),r=e.items.filter(e=>e.opportunityType===`content-gap`),i=e.items.filter(e=>e.opportunityType===`serp-framing`||e.opportunityType===`ctr`),a=e.items.filter(e=>e.opportunityType===`ranking`),o=[];if(t.length&&o.push({title:`Resolve technical evidence first`,plainEnglish:`${t.length} query observation is attached to a URL with conflicting fetch or indexability evidence.`,action:`Check status, redirects, robots directives, noindex, and canonical signals before producing a content brief.`,queries:t.slice(0,8).map(e=>e.query)}),r.length&&o.push({title:`Add missing answer coverage`,plainEnglish:`${r.length} query angle needs clearer body coverage.`,action:`Add one useful section that directly answers the grouped query angles. Do not add one thin paragraph per query.`,queries:r.slice(0,8).map(e=>e.query)}),i.length&&o.push({title:`Tighten the SERP framing`,plainEnglish:`${i.length} query angle looks covered but under-framed in title, H1, or meta copy.`,action:`Test clearer title/meta wording before rewriting the body. Keep the promise specific and human.`,queries:i.slice(0,8).map(e=>e.query)}),a.length&&o.push({title:`Strengthen relevance and links`,plainEnglish:`${a.length} query angle is still too low for a simple CTR change.`,action:`Improve the relevant answer block and add internal links from pages that already cover related demand.`,queries:a.slice(0,8).map(e=>e.query)}),!o.length){if(n.length)return o.push({title:`Verify the page before editing`,plainEnglish:`GSC visibility exists, but live content evidence was not available.`,action:`Fetch and inspect the live page and current SERP before turning these observations into a content brief.`,queries:n.slice(0,8).map(e=>e.query)}),o;o.push({title:`Avoid unnecessary content expansion`,plainEnglish:`The sampled queries look mostly covered. More copy may dilute the page.`,action:`Review title/meta tests, internal links, and SERP format before adding new sections.`,queries:e.items.slice(0,5).map(e=>e.query)})}return o}function jy(e){let t=Oy(e),n=t[0]?.intent??`general`,r=e.items.filter(e=>e.opportunityType!==`covered`&&e.opportunityType!==`technical-check`&&e.opportunityType!==`unverified`).sort((e,t)=>Ey(t)-Ey(e))[0]?.query??e.items[0]?.query;return{site:e.site,url:e.url,generatedAt:e.generatedAt,sourceReport:e,summary:{score:wy(e),primaryIntent:n,primaryQuery:r,queries:e.summary.queries,opportunities:e.summary.opportunities,estimatedClickLift:e.summary.estimatedClickLift,verdict:e.summary.verdict},intentMix:t,brief:ky(e),topActions:Ay(e),caveats:e.caveats}}async function My(e){return jy(await Sy(e))}const Ny={high:3,medium:2,low:1};function Py(e,t={}){let n=new Map(e.pages.flatMap(e=>[[e.url,e],[e.finalUrl,e]])),r=e.issues.filter(e=>Fy(e,t)).map(e=>{let t=n.get(e.url);return{url:e.url,ruleId:e.ruleId,title:e.title,category:e.category,severity:e.severity,detail:e.detail,evidence:e.evidence,clicks:e.searchMetrics?.clicks??t?.searchMetrics?.clicks??0,impressions:e.searchMetrics?.impressions??t?.searchMetrics?.impressions??0,sessions:t?.analytics?.sessions??0}}).sort((e,t)=>Ny[t.severity]-Ny[e.severity]||t.clicks-e.clicks||t.impressions-e.impressions||t.sessions-e.sessions||e.url.localeCompare(t.url)),i=t.limit??100,a=r.slice(0,i);return{affectedUrls:a,selection:{totalMatchedRows:r.length,returnedRows:a.length,limit:i,truncated:a.length<r.length}}}function Fy(e,t){return!(t.ruleId&&e.ruleId!==t.ruleId||t.category&&e.category!==t.category||t.severity&&e.severity!==t.severity)}const Iy=2e6;function Ly(e){return y(`sha256`).update(e).digest(`hex`)}function Ry(e,t){if(!e)return;let n=t.toLowerCase();return Object.entries(e).find(([e])=>e.toLowerCase()===n)?.[1]}function zy(e,t){return e?e.split(/,(?=\s*<)/u).map(e=>{let n=e.match(/^\s*<([^>]+)>\s*(.*)$/u);if(n?.[1])try{let e=new URL(n[1],t).toString(),r=n[2]??``,i=r.match(/(?:^|;)\s*rel=(?:"([^"]+)"|([^;\s]+))/iu),a=r.match(/(?:^|;)\s*type=(?:"([^"]+)"|([^;\s]+))/iu);return{url:e,rel:(i?.[1]??i?.[2]??``).toLowerCase().split(/\s+/u),type:(a?.[1]??a?.[2]??``).toLowerCase()}}catch{return}}).filter(e=>!!e):[]}function By(e){let t=new URL(e);return t.hash=``,t.pathname!==`/`&&(t.pathname=t.pathname.replace(/\/$/u,``)),t.toString()}function Vy(e,t){if(!e||!t)return null;try{return By(e)===By(t)}catch{return!1}}function Hy(e){return e.replace(/^---[\s\S]*?---\s*/u,``).replace(/```[\s\S]*?```/gu,` `).replace(/[#>*_`[\]()|:-]/gu,` `).split(/\s+/u).filter(Boolean).length}function Uy(e){let t=new Map,n=!1,r;for(let[i,a]of e.split(/\r?\n/u).entries()){let e=a.trim();if(i===0&&e===`---`){n=!0;continue}if(n){e===`---`&&(n=!1);continue}if(!r&&(e.startsWith("```")||e.startsWith(`~~~`))){r=e.startsWith("```")?"```":`~~~`;continue}if(r){e.startsWith(r)&&(r=void 0);continue}e.length<40||/^(?:#{1,6}\s|[-+*]\s|\d+[.)]\s|>|\||`|\]\()/u.test(e)||t.set(e,(t.get(e)??0)+1)}return[...t.values()].reduce((e,t)=>e+Math.max(0,t-1),0)}function Wy(e,t){return(e.headers.get(`x-robots-tag`)??``).toLowerCase().includes(`noindex`)?!0:[...t.matchAll(/<meta\s+[^>]*>/giu)].some(e=>{let t=e[0],n=t.match(/\bname\s*=\s*["']([^"']+)["']/iu)?.[1],r=t.match(/\bcontent\s*=\s*["']([^"']+)["']/iu)?.[1];return n?.toLowerCase()===`robots`&&/\bnoindex\b/iu.test(r??``)})}function Gy(e,t){let n=(t.contentSample?.replace(/\s+/gu,` `).trim())?.split(` `).slice(0,10).join(` `),r=e.replace(/\s+/gu,` `),i=e.match(/^```/gmu)?.length??0,a=Hy(e),o=t.wordCount??0;return{frontmatterTitle:/^---[\s\S]*?^title:\s*.+$/mu.test(e),h1Count:e.match(/^#\s+\S.+$/gmu)?.length??0,codeFenceBalanced:i%2==0,tableRows:e.match(/^\s*\|.*\|\s*$/gmu)?.length??0,links:e.match(/\[[^\]]+\]\([^)]+\)/gu)?.length??0,wordCount:a,rawHtmlTags:e.match(/<(?:div|span|section|article|nav|button)\b/giu)?.length??0,rawSvgTags:e.match(/<svg\b/giu)?.length??0,rawScriptTags:e.match(/<script\b/giu)?.length??0,rawStyleTags:e.match(/<style\b/giu)?.length??0,suspiciousConcatenations:e.match(/\b[a-z]{4,}[A-Z][A-Za-z]{3,}\b/gu)?.length??0,repeatedLines:Uy(e),sourceWordCount:o,wordRetentionRatio:o>0?Math.round(a/o*1e3)/1e3:null,introductoryCopyRetained:n?r.includes(n):null,navigationOnly:Hy(e)<25}}function Ky(e,t){let n=new AbortController,r=()=>n.abort(),i=setTimeout(r,e);return t?.aborted?n.abort():t?.addEventListener(`abort`,r,{once:!0}),{signal:n.signal,cleanup:()=>{clearTimeout(i),t?.removeEventListener(`abort`,r)}}}async function qy(e){if(!e.body)return``;let t=e.body.getReader(),n=[],r=0;try{for(;;){let e=await t.read();if(e.done)break;if(r+=e.value.byteLength,r>Iy)throw Error(`Response exceeds ${Iy} bytes.`);n.push(e.value)}}finally{await t.cancel().catch(()=>void 0)}return Buffer.concat(n).toString(`utf8`)}async function Jy(e){let t=Ky(e.timeoutMs,e.signal);try{let n=await e.fetch(e.url,{profile:`bot`,redirect:`follow`,headers:{accept:e.accept},signal:t.signal}),r=await qy(n),i=zy(n.headers.get(`link`)??void 0,e.url),a=Number(n.headers.get(`x-markdown-tokens`));return{observation:{requestedUrl:e.url,finalUrl:n.url||e.url,status:n.status,contentType:n.headers.get(`content-type`)??void 0,bytes:Buffer.byteLength(r),sha256:Ly(r),canonicalUrl:i.find(e=>e.rel.includes(`canonical`))?.url,alternateUrl:i.find(e=>e.rel.includes(`alternate`)&&e.type===`text/markdown`)?.url,varyAccept:(n.headers.get(`vary`)??``).split(`,`).some(e=>e.trim().toLowerCase()===`accept`),...Number.isInteger(a)&&a>=0?{markdownTokens:a}:{},contentSignal:n.headers.get(`content-signal`)??void 0},body:r}}catch(t){return{observation:{requestedUrl:e.url,varyAccept:!1,error:t instanceof Error?t.message:String(t)}}}finally{t.cleanup()}}async function Yy(e){let t=[...new Set(e.page.markdownAlternates??[])].sort(),n=zy(Ry(e.page.responseHeaders,`link`),e.page.finalUrl).filter(e=>e.rel.includes(`alternate`)&&e.type===`text/markdown`).map(e=>e.url).sort(),r={htmlUrl:e.page.finalUrl,advertisedUrls:t,htmlAlternateUnique:t.length===1,httpAlternateUrls:n,explicitMatchesNegotiated:null,repeatedHashStable:null,markdownCanonicalMatchesHtml:null},i=t.length===1?t[0]:void 0,[a,o,s]=await Promise.all([i?Jy({url:i,accept:`text/markdown`,timeoutMs:e.timeoutMs,fetch:e.fetch,signal:e.signal}):Promise.resolve(void 0),Jy({url:e.page.finalUrl,accept:`text/markdown`,timeoutMs:e.timeoutMs,fetch:e.fetch,signal:e.signal}),Jy({url:i??e.page.finalUrl,accept:`text/markdown`,timeoutMs:e.timeoutMs,fetch:e.fetch,signal:e.signal})]);a&&(r.explicit=a.observation),r.negotiated=o.observation,r.repeated=s.observation,r.explicitMatchesNegotiated=a?.observation.sha256&&o.observation.sha256?a.observation.sha256===o.observation.sha256:null;let c=a??o;return r.repeatedHashStable=c.observation.sha256&&s.observation.sha256?c.observation.sha256===s.observation.sha256:null,r.markdownCanonicalMatchesHtml=Vy(c.observation.canonicalUrl,e.page.finalUrl),c.body&&(r.quality=Gy(c.body,e.page)),r}async function Xy(e){let t=Ky(e.timeoutMs,e.signal);try{let n=await e.fetch(e.url,{profile:`bot`,redirect:e.redirect??`follow`,headers:e.accept?{accept:e.accept}:void 0,signal:t.signal});return{response:n,body:await qy(n)}}finally{t.cleanup()}}function Zy(e){return e instanceof Error?e.message:String(e)}async function Qy(e){let t=new URL(`/.well-known/agent-skills/index.json`,e.origin).toString();try{let{response:n,body:r}=await Xy({url:t,timeoutMs:e.timeoutMs,fetch:e.fetch,signal:e.signal,accept:`application/json`}),i=n.headers.get(`content-type`)??void 0,a=n.headers.get(`access-control-allow-origin`)??void 0;if(n.status<200||n.status>=300)return{indexUrl:t,status:n.status,contentType:i,validIndex:!1,skills:[]};let o=JSON.parse(r),s=Array.isArray(o.skills)?o.skills.slice(0,25):[],c=await Promise.all(s.map(async n=>{let r=typeof n.name==`string`?n.name:void 0,i=typeof n.digest==`string`?n.digest:void 0,a=typeof n.url==`string`?n.url:void 0;if(!a)return{name:r,declaredDigest:i,digestMatches:null,frontmatterValid:null,sameOrigin:!1,error:`Skill URL is missing.`};let o=new URL(a,t).toString(),s=new URL(o).origin===new URL(e.origin).origin;if(!s)return{name:r,url:o,declaredDigest:i,digestMatches:null,frontmatterValid:null,sameOrigin:s,error:`Skill URL is off origin.`};try{let t=await Xy({url:o,timeoutMs:e.timeoutMs,fetch:e.fetch,signal:e.signal,accept:`text/markdown,text/plain;q=0.9`}),n=`sha256:${Ly(t.body)}`,a=/^---\s*\n[\s\S]*?^name:\s*\S.+$[\s\S]*?^description:\s*\S.+$[\s\S]*?^---\s*$/mu.test(t.body);return{name:r,url:o,status:t.response.status,contentType:t.response.headers.get(`content-type`)??void 0,cors:t.response.headers.get(`access-control-allow-origin`)??void 0,declaredDigest:i,observedDigest:n,digestMatches:i?i===n:null,frontmatterValid:a,sameOrigin:s}}catch(e){return{name:r,url:o,declaredDigest:i,digestMatches:null,frontmatterValid:null,sameOrigin:s,error:Zy(e)}}}));return{indexUrl:t,status:n.status,contentType:i,cors:a,validIndex:typeof o.$schema==`string`&&Array.isArray(o.skills),skills:c}}catch(e){return{indexUrl:t,validIndex:!1,skills:[],error:Zy(e)}}}function $y(e,t){let n=[],r=[];for(let i of e.matchAll(/\[([^\u005d]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/gu))if(!(!i[1]||!i[2]))try{n.push({label:i[1],url:new URL(i[2],t).toString()})}catch{r.push(i[2])}return{links:n,invalidLinks:r}}async function eb(e){let t=new URL(`/llms.txt`,e.origin).toString();try{let[n,r]=await Promise.all([Xy({url:t,timeoutMs:e.timeoutMs,fetch:e.fetch,signal:e.signal,accept:`text/plain,text/markdown;q=0.9`}),Xy({url:t,timeoutMs:e.timeoutMs,fetch:e.fetch,signal:e.signal,accept:`text/plain,text/markdown;q=0.9`})]),i=n.response.status>=200&&n.response.status<300,a=$y(n.body,t),o=a.links.slice(0,100),s=a.links.length>o.length,c=new Map;for(let e of o)c.set(e.url,(c.get(e.url)??0)+1);let l=[...c].filter(([,e])=>e>1).map(([e])=>e).sort(),u=new Set(e.pages.map(e=>By(e.finalUrl))),d=await Promise.all(o.map(async t=>{let n=new URL(t.url).origin===new URL(e.origin).origin;try{let r=await Xy({url:t.url,timeoutMs:e.timeoutMs,fetch:e.fetch,signal:e.signal}),i=n&&r.response.status>=200&&r.response.status<300?!Wy(r.response,r.body):void 0,a=r.response.url||void 0;return{...t,sameOrigin:n,status:r.response.status,finalUrl:a,redirected:r.response.redirected,...i===void 0?{}:{indexableTarget:i}}}catch(e){return{...t,sameOrigin:n,redirected:!1,error:Zy(e)}}})),f=new Set(d.filter(e=>e.sameOrigin&&!new URL(e.url).pathname.startsWith(`/.well-known/`)&&!new URL(e.url).pathname.endsWith(`.md`)).map(e=>By(e.url)));return{url:t,exists:i,status:n.response.status,contentType:n.response.headers.get(`content-type`)??void 0,bytes:Buffer.byteLength(n.body),sha256:Ly(n.body),repeatedHashStable:Ly(n.body)===Ly(r.body),headingCount:n.body.match(/^#{1,2}\s+\S/gmu)?.length??0,totalParsedLinks:a.links.length,linkLimitReached:s,links:d,invalidLinks:a.invalidLinks.sort(),duplicateLinks:l,offSiteLinks:d.filter(e=>!e.sameOrigin).map(e=>e.url).sort(),redirectedLinks:d.filter(e=>e.redirected).map(e=>e.url).sort(),nonIndexableLinks:d.filter(e=>e.indexableTarget===!1).map(e=>e.url).sort(),missingCrawlRoutes:[...f].filter(e=>!u.has(e)).sort(),oversized:Buffer.byteLength(n.body)>1e5}}catch(e){return{url:t,exists:!1,repeatedHashStable:null,headingCount:0,totalParsedLinks:0,linkLimitReached:!1,links:[],invalidLinks:[],duplicateLinks:[],offSiteLinks:[],redirectedLinks:[],nonIndexableLinks:[],missingCrawlRoutes:[],oversized:!1,error:Zy(e)}}}async function tb(e){let t=new URL(`/agent-routes.json`,e.origin).toString();try{let n=await Xy({url:t,timeoutMs:e.timeoutMs,fetch:e.fetch,signal:e.signal,accept:`application/json`}),r=JSON.parse(n.body),i=Array.isArray(r.pages)?r.pages:[],a=i.map(e=>e.htmlPath).filter(e=>typeof e==`string`).sort(),o=i.map(e=>e.markdownPath).filter(e=>typeof e==`string`).sort(),s=new Set(e.pages.map(e=>new URL(e.finalUrl).pathname.replace(/\/$/u,``)||`/`)),c=new Set(e.observations.flatMap(e=>e.advertisedUrls.map(e=>new URL(e).pathname)));return{url:t,status:n.response.status,valid:Array.isArray(r.pages),declaredHtmlRoutes:a,declaredMarkdownRoutes:o,missingHtmlRoutes:a.filter(e=>!s.has(e.replace(/\/$/u,``)||`/`)).sort(),missingMarkdownRoutes:o.filter(e=>!c.has(e)).sort(),orphanMarkdownRoutes:[...c].filter(e=>!o.includes(e)).sort()}}catch(e){return{url:t,valid:!1,declaredHtmlRoutes:[],declaredMarkdownRoutes:[],missingHtmlRoutes:[],missingMarkdownRoutes:[],orphanMarkdownRoutes:[],error:Zy(e)}}}async function nb(e){try{let t=await Xy({url:e.url,timeoutMs:e.timeoutMs,fetch:e.fetch,signal:e.signal,redirect:`manual`});return{status:t.response.status,location:t.response.headers.get(`location`)??void 0}}catch(e){return{error:Zy(e)}}}async function rb(e){let t=new URL(e.startUrl).origin,n=e.pages.filter(e=>e.status>=200&&e.status<300&&/^\s*(?:text\/html|application\/xhtml\+xml)\b/iu.test(e.contentType??``)).sort((e,t)=>e.finalUrl.localeCompare(t.finalUrl)),r=new ae({concurrency:e.concurrency??4}),i=await Promise.all(n.map(t=>r.add(()=>Yy({page:t,timeoutMs:e.timeoutMs,fetch:e.fetch,signal:e.signal})))),[a,o,s]=await Promise.all([Qy({...e,origin:t}),eb({...e,origin:t,pages:n}),tb({...e,origin:t,pages:n,observations:i})]),c=await Jy({url:e.startUrl,accept:`text/markdown;q=0,text/html;q=1`,timeoutMs:e.timeoutMs,fetch:e.fetch,signal:e.signal}),l=new URL(e.startUrl),u=new URL(l);u.protocol=`http:`;let d=new URL(l);d.hostname=l.hostname.startsWith(`www.`)?l.hostname.slice(4):`www.${l.hostname}`;let[f,p]=await Promise.all([nb({...e,url:u.toString()}),nb({...e,url:d.toString()})]),m=f.location?new URL(f.location,u).toString():void 0,h=p.location?new URL(p.location,d).toString():void 0,g=c.observation.contentType,_=i.filter(e=>e.htmlAlternateUnique).length,v=i.filter(e=>{let t=e.explicit??e.negotiated;return t?.status!==void 0&&t.status>=200&&t.status<300&&/^\s*text\/markdown\b/iu.test(t.contentType??``)}).length,y=[...n.length===e.pages.length?[]:[`Agent representations were evaluated for ${n.length} successful HTML pages from ${e.pages.length} retained documents.`],...s.valid?[]:[`No valid public agent route manifest was available for orphan checks.`]].sort(),b=n.map(e=>Ry(e.responseHeaders,`content-signal`)).filter(e=>!!e),x=i.map(e=>e.explicit?.contentSignal??e.negotiated?.contentSignal).filter(e=>!!e),ee=[...new Set([...b,...x])].sort();return{profile:`content`,profileApplicability:{content:{status:`evaluated`,reason:`This site publishes documents, so access, representations, discovery, identity, and extraction quality were evaluated.`},api:{status:`notApplicable`,reason:`No public API surface was selected for this content-site run.`},application:{status:`notApplicable`,reason:`A local MCP product does not make this website a public remote agent endpoint.`},commerce:{status:`notApplicable`,reason:`No product checkout or transactional surface was selected.`}},dataStatus:n.length===0?`unavailable`:v===n.length?`complete`:`partial`,markdownAlternates:{eligibleHtmlPages:n.length,advertisedPages:_,evaluatedPages:v,exactByteMatches:i.filter(e=>e.explicitMatchesNegotiated).length,stableResponses:i.filter(e=>e.repeatedHashStable).length,pages:i},contentNegotiation:{qZeroHonoured:g===void 0?null:!/^\s*text\/markdown\b/iu.test(g),qZeroStatus:c.observation.status,qZeroContentType:g,error:c.observation.error},routeManifest:s,agentSkills:a,llmsTxt:o,contentSignals:{htmlValues:[...new Set(b)].sort(),markdownValues:[...new Set(x)].sort(),missingHtmlPages:n.length-b.length,missingMarkdownPages:i.length-x.length,consistent:n.length>0&&i.length>0?ee.length===1&&b.length===n.length&&x.length===i.length:null},protocolVariants:{http:{url:u.toString(),status:f.status,location:m,permanentRedirectToHttps:f.status===void 0?null:[301,308].includes(f.status)&&!!m?.startsWith(`https://`),error:f.error},www:{url:d.toString(),status:p.status,location:h,redirectsToPreferredHost:p.status===void 0?null:[301,308].includes(p.status)&&!!(h&&new URL(h).hostname===l.hostname&&new URL(h).protocol===`https:`),error:p.error},hstsOnStartPage:n.find(t=>By(t.finalUrl)===By(e.startUrl))?.hasHsts??null},warnings:y}}function ib(e,t){return{section:e,...t}}function ab(e,t,n){return{id:e,title:t,checks:n}}function ob(e){return e.markdownAlternates.pages.filter(e=>{let t=e.negotiated?.status===200&&/^\s*text\/markdown\b/iu.test(e.negotiated.contentType??``)&&e.negotiated.varyAccept,n=!e.htmlAlternateUnique||e.httpAlternateUrls.length===1&&e.explicit?.status===200&&/^\s*text\/markdown\b/iu.test(e.explicit.contentType??``)&&e.explicitMatchesNegotiated===!0&&e.markdownCanonicalMatchesHtml===!0;return!t||!n||e.repeatedHashStable!==!0}).map(e=>e.htmlUrl)}function sb(e){return e.markdownAlternates.pages.filter(e=>{let t=e.quality;return!t||!t.frontmatterTitle||t.h1Count!==1||!t.codeFenceBalanced||t.rawHtmlTags>0||t.rawSvgTags>0||t.rawScriptTags>0||t.rawStyleTags>0||t.navigationOnly||t.repeatedLines>0||t.wordRetentionRatio!==null&&t.wordRetentionRatio<.4}).map(e=>e.htmlUrl)}function cb(e){return[`api`,`application`,`commerce`].map(t=>ib(`profile`,{id:`profile-${t}`,status:`notApplicable`,title:`${t[0]?.toUpperCase()}${t.slice(1)} checks are not applicable`,plainEnglish:e.profileApplicability[t].reason,action:`Choose this profile only when the site exposes that capability publicly.`,evidence:{profile:t,selectedProfile:e.profile}}))}function lb(e){let t=t=>e.filter(e=>e.status===t).length;return{checks:e.length,passed:t(`pass`),warnings:t(`warning`),failed:t(`fail`),unknown:t(`unknown`),information:t(`info`),notApplicable:t(`notApplicable`)}}function ub(e){let t=e.markdownAlternates,n=ob(e),r=sb(e),i=t.pages.filter(e=>e.negotiated?.status===200&&/^\s*text\/markdown\b/iu.test(e.negotiated.contentType??``)).length,a=t.pages.filter(e=>e.htmlAlternateUnique).length,o=t.eligibleHtmlPages>0&&t.evaluatedPages===t.eligibleHtmlPages,s=t.eligibleHtmlPages>0&&i===t.eligibleHtmlPages&&t.pages.every(e=>e.negotiated?.varyAccept)&&t.pages.every(e=>!e.htmlAlternateUnique||e.explicitMatchesNegotiated===!0),c=t.eligibleHtmlPages>0&&t.stableResponses===t.eligibleHtmlPages,l=t.pages.filter(e=>(e.explicit??e.negotiated)?.markdownTokens!==void 0&&(!e.explicit||!e.negotiated||e.explicit.markdownTokens===e.negotiated.markdownTokens)).length;return[ib(`representations`,{id:`markdown-coverage`,status:t.eligibleHtmlPages===0?`unknown`:o?`pass`:`fail`,title:o?`Every successful HTML page has a Markdown representation`:`Markdown representation coverage is incomplete`,plainEnglish:`${t.evaluatedPages} of ${t.eligibleHtmlPages} successful HTML pages returned Markdown. ${a} advertised an explicit alternative and ${i} supported content negotiation.`,action:o?`Keep the negotiated or explicit Markdown response stable as routes change.`:`Return text/markdown through content negotiation or advertise one working Markdown alternative for each public HTML page.`,evidence:{eligibleHtmlPages:t.eligibleHtmlPages,advertisedPages:t.advertisedPages,negotiatedPages:i,evaluatedPages:t.evaluatedPages},urls:n.slice(0,25)}),ib(`representations`,{id:`markdown-token-estimates`,status:t.evaluatedPages===0?`unknown`:l===t.evaluatedPages?`pass`:`warning`,title:l===t.evaluatedPages?`Every Markdown response includes a token estimate`:`Some Markdown responses are missing a stable token estimate`,plainEnglish:`${l} of ${t.evaluatedPages} evaluated pages returned X-Markdown-Tokens. Paired explicit and negotiated responses agreed where both existed. The value is an estimate, not an exact model-specific token count.`,action:`Return a stable token estimate and keep it consistent when both explicit and negotiated responses exist.`,urls:t.pages.filter(e=>(e.explicit??e.negotiated)?.markdownTokens===void 0||e.explicit!==void 0&&e.negotiated!==void 0&&e.explicit.markdownTokens!==e.negotiated.markdownTokens).map(e=>e.htmlUrl).slice(0,25)}),ib(`representations`,{id:`markdown-negotiation`,status:t.eligibleHtmlPages===0?`unknown`:s&&e.contentNegotiation.qZeroHonoured?`pass`:`fail`,title:s&&e.contentNegotiation.qZeroHonoured?`Markdown content negotiation works across the site`:`Markdown content negotiation needs attention`,plainEnglish:`${i} of ${t.eligibleHtmlPages} pages returned negotiated Markdown. ${t.exactByteMatches} of ${a} paired explicit responses matched byte for byte. A request that refuses Markdown ${e.contentNegotiation.qZeroHonoured?`received HTML`:`did not produce confirmed HTML evidence`}.`,action:`Honour Accept q-values, send Vary: Accept, and keep paired explicit and negotiated responses identical when both exist.`,evidence:{negotiatedPages:i,explicitPages:a,exactByteMatches:t.exactByteMatches,qZero:e.contentNegotiation},urls:n.slice(0,25)}),ib(`representations`,{id:`markdown-determinism`,status:t.eligibleHtmlPages===0?`unknown`:c?`pass`:`fail`,title:c?`Repeated Markdown responses are stable`:`Repeated Markdown responses changed during the audit`,plainEnglish:`${t.stableResponses} of ${t.eligibleHtmlPages} repeated Markdown responses kept the same SHA-256 digest.`,action:`Remove timestamps, random output, or other runtime rewriting that changes the same Markdown response between requests.`,evidence:{stableResponses:t.stableResponses},urls:n.slice(0,25)}),ib(`representations`,{id:`markdown-quality`,status:t.evaluatedPages===0?`unknown`:r.length===0?`pass`:`warning`,title:r.length===0?`Markdown keeps the useful document structure cleanly`:`Some Markdown alternatives need an extraction review`,plainEnglish:r.length===0?`The evaluated alternatives retained one H1, frontmatter titles, balanced code fences, useful copy, and no leaked SVG, script, style, or layout tags.`:`${r.length} Markdown alternative${r.length===1?``:`s`} lost important structure, looked navigation-only, or leaked presentation markup.`,action:`Open the affected Markdown directly and fix the shared converter or component exclusion rules rather than patching generated files.`,urls:r.slice(0,25)})]}function db(e){let t=e.agentSkills,n=t.validIndex&&t.skills.length>0&&t.skills.every(e=>e.sameOrigin&&e.status===200&&/^\s*text\/markdown\b/iu.test(e.contentType??``)&&!!e.cors&&e.digestMatches===!0&&e.frontmatterValid===!0)&&!!t.cors,r=e.llmsTxt,i=r.links.filter(e=>!e.status||e.status<200||e.status>=400),a=r.exists&&/^\s*(?:text\/plain|text\/markdown)\b/iu.test(r.contentType??``)&&r.headingCount>0&&r.links.length>0&&!r.linkLimitReached&&!r.oversized&&r.invalidLinks.length===0&&r.duplicateLinks.length===0&&r.redirectedLinks.length===0&&r.nonIndexableLinks.length===0&&r.missingCrawlRoutes.length===0&&i.length===0&&r.repeatedHashStable===!0;return[ib(`discovery`,{id:`agent-skills`,status:n?`pass`:t.status===404?`info`:`warning`,title:n?`Agent Skills discovery is valid and digest verified`:`Agent Skills discovery is absent or needs attention`,plainEnglish:n?`${t.skills.length} published skill${t.skills.length===1?``:`s`} matched the declared SHA-256 digest, valid SKILL.md frontmatter, Markdown content type, and cross-origin access policy.`:`Agent Skills is optional for a content site, but a published index should resolve same-origin Markdown skill files, allow intended cross-origin clients, and verify their declared bytes.`,action:`If you publish Agent Skills, keep the discovery index and SHA-256 digests generated from the exact deployed skill bytes.`,evidence:t}),ib(`discovery`,{id:`llms-txt`,status:r.exists?a?`pass`:`warning`:`info`,title:r.exists?a?`llms.txt is short, stable, and its links resolve`:`llms.txt exists but needs a content or link review`:`llms.txt is not published`,plainEnglish:r.exists?`${r.links.length} declared links were checked. ${r.invalidLinks.length} were malformed, ${r.duplicateLinks.length} were duplicated, ${r.redirectedLinks.length} redirected, ${r.nonIndexableLinks.length} reached non-indexable pages, ${i.length} did not resolve, and ${r.missingCrawlRoutes.length} were missing from the crawl inventory. ${r.offSiteLinks.length} linked to other sites.`:`llms.txt is optional and its absence is not a search ranking problem.`,action:r.exists?`Keep the file curated, deterministic, and limited to useful entry points whose links still resolve.`:`Add it only when an intended consumer uses it. Do not treat it as a Google ranking requirement.`,evidence:{status:r.status,contentType:r.contentType,bytes:r.bytes,oversized:r.oversized,totalParsedLinks:r.totalParsedLinks,linkLimitReached:r.linkLimitReached,links:r.links.length,invalidLinks:r.invalidLinks,duplicateLinks:r.duplicateLinks,offSiteLinks:r.offSiteLinks,redirectedLinks:r.redirectedLinks,nonIndexableLinks:r.nonIndexableLinks,missingCrawlRoutes:r.missingCrawlRoutes,repeatedHashStable:r.repeatedHashStable},urls:[...i.map(e=>e.url),...r.redirectedLinks,...r.nonIndexableLinks,...r.missingCrawlRoutes].slice(0,25)}),ib(`discovery`,{id:`content-signals`,status:e.contentSignals.consistent===null?`unknown`:e.contentSignals.consistent?`pass`:`warning`,title:e.contentSignals.consistent?`HTML and Markdown publish one explicit Content Signals policy`:`Content Signals are missing or inconsistent across representations`,plainEnglish:e.contentSignals.consistent?`The same ${e.contentSignals.htmlValues[0]} policy was observed on every evaluated HTML and Markdown response.`:`${e.contentSignals.missingHtmlPages} HTML responses and ${e.contentSignals.missingMarkdownPages} Markdown responses were missing the header, or the observed values differed.`,action:`Choose the site policy deliberately and return the same Content-Signal value on HTML and Markdown responses.`,evidence:e.contentSignals}),ib(`discovery`,{id:`route-manifest`,status:e.routeManifest.valid?e.routeManifest.missingHtmlRoutes.length===0&&e.routeManifest.missingMarkdownRoutes.length===0&&e.routeManifest.orphanMarkdownRoutes.length===0?`pass`:`warning`:`info`,title:e.routeManifest.valid?`The public route manifest agrees with the crawl`:`No public route manifest was available`,plainEnglish:e.routeManifest.valid?`The manifest declared ${e.routeManifest.declaredHtmlRoutes.length} HTML routes and ${e.routeManifest.declaredMarkdownRoutes.length} Markdown routes. Missing or orphan routes are listed in the evidence.`:`A public route manifest is optional. Without one, this crawl cannot prove that no orphan Markdown files were deployed.`,action:`Generate one deterministic route inventory from the build when exact HTML and Markdown parity matters.`,evidence:e.routeManifest})]}function fb(e,t){let n=e.ai?.robotsTxt?.botAccess??[],r=n.filter(e=>e.allowed===!1),i=e.ai?.robotsTxt?.availability===`available`||e.ai?.robotsTxt?.availability===`absent`,a=t.protocolVariants;return[ib(`access`,{id:`crawler-access`,status:i?r.length>0?`warning`:`pass`:`unknown`,title:r.length?`robots.txt blocks selected crawler tokens`:`No selected crawler token is blocked at the start URL`,plainEnglish:i?`${r.length} of ${n.length} selected crawler tokens were blocked by the start URL robots.txt policy.`:`The robots.txt response was not stable enough to make a crawler-access claim.`,action:`Keep blocks intentional. A crawler token being allowed here does not prove that the service will crawl, index, or cite the site.`,evidence:{availability:e.ai?.robotsTxt?.availability,blocked:r,botAccess:n}}),ib(`access`,{id:`protocol-canonicalization`,status:a.http.permanentRedirectToHttps===!0&&a.hstsOnStartPage===!0?`pass`:a.http.permanentRedirectToHttps===null?`unknown`:`warning`,title:a.http.permanentRedirectToHttps===!0&&a.hstsOnStartPage===!0?`HTTP redirects permanently to HTTPS and HTTPS sends HSTS`:`Protocol canonicalization needs attention or could not be proven`,plainEnglish:`The HTTP variant ${a.http.permanentRedirectToHttps===!0?`redirected permanently to HTTPS`:`did not produce a confirmed permanent HTTPS redirect`}. The HTTPS start page ${a.hstsOnStartPage?`sent HSTS`:`did not provide confirmed HSTS evidence`}.`,action:`Use one permanent HTTP-to-HTTPS redirect and send HSTS only after HTTPS is stable across the site.`,evidence:a})]}function pb(e){let t=new Set(e.pages.flatMap(e=>e.schemaTypes??[])),n=t.has(`SoftwareApplication`),r=t.has(`WebSite`),i=t.has(`Person`)||t.has(`Organization`),a=[`WebPage`,`TechArticle`,`CollectionPage`].filter(e=>t.has(e)),o=[...new Set(e.pages.flatMap(e=>[...e.schemaSameAs??[],...e.socialProfileLinks??[]]))];return[ib(`identity`,{id:`identity-graph`,status:r&&i&&a.length>0?`pass`:`warning`,title:r&&i&&a.length>0?`Structured identity covers the website, creator, and pages`:`The structured identity graph is missing a useful entity layer`,plainEnglish:`Website identity: ${r?`present`:`missing`}. Creator or publisher identity: ${i?`present`:`missing`}. Page-level types: ${a.length?a.join(`, `):`missing`}. Software identity: ${n?`present`:`not observed and not required for a content site`}.`,action:`Connect truthful WebSite, creator or publisher, and page nodes. Add SoftwareApplication only when the site actually represents software. Use sameAs only for official profiles that identify the same entity.`,evidence:{schemaTypes:[...t].sort(),officialProfiles:o.sort()}})]}function mb(e){let t=e.agentDiscovery;if(!t){let t={content:{status:`evaluated`,reason:`The content profile was selected but discovery evidence was not collected.`},api:{status:`notApplicable`,reason:`Not selected.`},application:{status:`notApplicable`,reason:`Not selected.`},commerce:{status:`notApplicable`,reason:`Not selected.`}},n=ib(`collection`,{id:`agent-discovery-evidence`,status:`unknown`,title:`Agent discovery evidence was not collected`,plainEnglish:`This saved crawl predates the focused content-profile checks or was run without them.`,action:`Run the agent-readiness report with a URL or start a fresh crawl with agent discovery enabled.`}),r=cb({profile:`content`,profileApplicability:t}),i=[n,...r];return{reportId:e.id,url:e.config.url,generatedAt:e.generatedAt,profile:`content`,dataStatus:`unavailable`,assessment:`evidence-only`,headline:`No agent-discovery evidence is stored in this crawl. Run a fresh focused check.`,profileApplicability:t,summary:lb(i),sections:[ab(`collection`,`Evidence collection`,[n]),ab(`profile`,`Profile scope`,r)],checks:i,topActions:[n],caveats:[`Not evaluated does not mean absent or broken.`,`This report does not measure rankings, AI mentions, citations, or selection.`]}}let n=[ab(`profile`,`Profile scope`,cb(t)),ab(`access`,`Crawler and protocol access`,fb(e,t)),ab(`representations`,`HTML and Markdown representations`,ub(t)),ab(`discovery`,`Agent discovery files`,db(t)),ab(`identity`,`Identity evidence`,pb(e))],r=n.flatMap(e=>e.checks),i=r.filter(e=>[`fail`,`warning`,`unknown`].includes(e.status)),a={fail:0,warning:1,unknown:2,pass:3,info:4,notApplicable:5},o=[...i].sort((e,t)=>a[e.status]-a[t.status]).slice(0,10),s=lb(r);return{reportId:e.id,url:e.config.url,generatedAt:e.generatedAt,profile:t.profile,dataStatus:t.dataStatus,assessment:`evidence-only`,headline:`${s.passed} checks passed, ${s.warnings} need review, and ${s.failed} failed for the content profile.`,profileApplicability:t.profileApplicability,summary:s,sections:n,checks:r,topActions:o,caveats:[...e.caveats,...t.warnings,`A clean content profile does not prove indexing, rankings, AI mentions, citations, or selection.`,`A crawler token being allowed does not prove that a service fetched or used the page.`,`API, application, and commerce checks were not applicable to this content-site run.`]}}const hb=new Set([`organization`,`localbusiness`,`person`,`product`,`softwareapplication`,`website`]),gb=new Set([`organization`,`localbusiness`]),_b=new Set([...gb,`person`]);function vb(e){return hb.has(e.toLowerCase())}function yb(e){return gb.has(e.toLowerCase())}function bb(e){return _b.has(e.toLowerCase())}function xb(e){return[...new Set(e.filter(e=>!!e))]}function Sb(e,t){return t?Math.round(e/t*100):0}function Cb(e){let t=e.pages.filter(e=>e.indexable&&e.status<400),n=t.length||1,r={};for(let e of t)for(let t of e.schemaTypes??[])r[t]=(r[t]??0)+1;let i=t.filter(e=>e.schemaTypes?.some(vb)),a=t.flatMap(e=>e.schemaSameAsEvidence??[]).filter(e=>e.subjectTypes.some(bb)),o=a.filter(e=>e.subjectTypes.some(yb)),s=xb(a.map(e=>e.url)),c={};for(let e of a)for(let t of e.subjectTypes)bb(t)&&(c[t]=xb([...c[t]??[],e.url]));let l=xb(t.flatMap(e=>e.socialProfileLinks??[])),u=xb(t.map(e=>e.author)),d=t.filter(e=>e.author||e.geo?.hasAuthor),f=t.filter(e=>e.hasDate||e.geo?.hasDate),p=t.filter(e=>e.title&&e.h1),m=[{coverage:Sb(i.length,n),check:{id:`entity-schema`,title:`Entity schema is present`,plainEnglish:`${Sb(i.length,n)}% of evaluated indexable pages include Organization, LocalBusiness, Person, Product, SoftwareApplication, or WebSite schema.`,action:`Add accurate schema only where it describes the software, website, organization, person, product, or local business shown on the page.`,evidence:{schemaTypes:r},urls:t.filter(e=>!i.includes(e)).slice(0,10).map(e=>e.finalUrl)}},{coverage:o.length?100:0,check:{id:`same-as`,title:`Site entity profiles are connected`,plainEnglish:o.length?`The crawl found sameAs links attached to Organization or LocalBusiness structured data.`:s.length?`The crawl found Person sameAs links, but no Organization or LocalBusiness profile evidence for the site entity.`:l.length?`The crawl found social-domain links, but they are not enough to prove official profile ownership.`:`The crawl did not find entity-scoped sameAs evidence.`,action:`Connect official profiles with sameAs on Organization, Person, or LocalBusiness schema. Use only profiles you control.`,evidence:{sameAs:s,siteSameAs:xb(o.map(e=>e.url)),sameAsEvidence:a,unclassifiedSocialLinks:l}}},{coverage:Math.round((Sb(d.length,n)+Sb(f.length,n))/2),check:{id:`authority-freshness`,title:`Author and date signals observed`,plainEnglish:`${Sb(d.length,n)}% of evaluated indexable pages expose an author signal and ${Sb(f.length,n)}% expose a date signal.`,action:`For editorial content, identify authors and show accurate published or modified dates when that information is useful to readers.`,evidence:{authorCoverage:Sb(d.length,n),dateCoverage:Sb(f.length,n),authors:u}}},{coverage:Sb(p.length,n),check:{id:`entity-naming`,title:`Title and H1 coverage observed`,plainEnglish:`${Sb(p.length,n)}% of evaluated indexable pages have both a title and H1.`,action:`Review pages missing either signal. Keep brand, product, and person names accurate wherever they appear.`}}].map(({coverage:e,check:t})=>({...t,status:`info`,evidence:{...t.evidence,observedCoveragePercent:e}})),h=e.status===`completed`&&t.length>0?`complete`:`partial`;return{reportId:e.id,url:e.config.url,generatedAt:e.generatedAt,dataStatus:h,evaluatedPages:t.length,crawlPages:e.pages.length,assessment:`evidence-only`,headline:h===`partial`?`Entity evidence is incomplete; treat these findings as scoped to the evaluated pages, not the whole site.`:`Entity signals are reported as observations, not a ranking, visibility, or machine-understanding score.`,caveats:[...e.caveats,`This report deliberately has no aggregate entity score. Add schema, authors, dates, and sameAs only where they accurately fit the page and a documented use case.`,...h===`partial`?[`Entity coverage is based on ${t.length} evaluated indexable page${t.length===1?``:`s`} from a ${e.status} crawl.`]:[],...l.length&&!s.length?[`Social-domain links are unclassified navigation evidence, not proof that the linked profiles are official.`]:[]],checks:m,entities:{schemaTypes:r,sameAs:s,sameAsByType:c,socialProfiles:l,authors:u}}}function wb(e,t){return t<=0?0:Math.round(e/t*100)}function Tb(e,t){return Math.round(Math.max(0,Math.min(100,e))/100*t)}function Eb(e,t){if(t===0)return`info`;let n=t?e/t:0;return n>=.85?`pass`:n>=.5?`warning`:`fail`}function Db(e,t,n){return{id:e,title:t,checks:n}}function Ob(e){let{score:t,maxScore:n,...r}=e,i=e.evaluated??!0;return{...r,evaluated:i,status:i?Eb(t,n):`unknown`}}function kb(e){return e.pages.filter(e=>e.indexable&&e.status<400)}function Ab(e,t){return e.filter(t).slice(0,10).map(e=>e.finalUrl)}function jb(e){let t=e.agentDiscovery,n=kb(e),r=n.length,i=r>0,a=e.summary.pageLimitReached,o=e.ai?.robotsTxt,s=o?.botAccess??[],c=s.filter(e=>e.allowed===!1),l=s.filter(e=>e.allowed===null),u=o?.availability===`rate-limited`||o?.availability===`unreachable`,d=o!==void 0,f=d&&s.length>0,p=f&&!u,m=d&&!u,h=u||!d||!f,g=s.filter(e=>e.declared||e.coveredByWildcard),_=e.ai?.agentResources?.filter(e=>e.exists),v=e.pages.filter(e=>e.structuredDataFormats?.includes(`json-ld`)||(e.invalidJsonLdCount??0)>0),y=v.filter(e=>(e.invalidJsonLdCount??0)>0),b=n.filter(e=>e.geo?.answerable),x=n.filter(e=>e.geo?.semanticHtml),ee=n.filter(e=>e.title&&e.h1),te=n.filter(e=>e.metaDescription),ne=n.filter(e=>e.lang),S=n.filter(e=>e.schemaTypes?.some(vb)||(e.schemaSameAs?.length??0)>0||(e.socialProfileLinks?.length??0)>0),C=n.map(e=>({url:e.finalUrl,control:_m({metaRobots:e.metaRobots,xRobotsTag:e.xRobotsTag})})).filter(({control:e})=>e.status!==`not-restricted`),re=C.filter(({control:e})=>e.status===`blocked`),w=C.filter(({control:e})=>e.status===`limited`),ie=[Db(`agent-access`,`Agent access`,[Ob({id:`robots-ai-bots`,section:`agent-access`,maxScore:20,evaluated:p,score:u?0:s.length?c.length?Tb(wb(s.length-c.length,s.length),20):20:8,title:u?`robots.txt crawler-policy evidence is unavailable`:f?c.length?`robots.txt blocks selected crawler tokens at the start URL`:`robots.txt allows selected crawler tokens at the start URL`:`robots.txt crawler-policy evidence was not collected`,plainEnglish:u?`The robots.txt request failed, so this report cannot say whether selected crawler tokens are allowed at the start URL.`:f?c.length?`robots.txt policy blocks ${c.length} selected crawler ${c.length===1?`token`:`tokens`} at the start URL.`:`robots.txt policy does not block the selected crawler tokens at the start URL. This does not verify actual or site-wide fetchability.`:`This crawl does not include per-bot robots.txt policy data for the start URL.`,action:u?`Restore a stable robots.txt response, then rerun this report before making crawler-policy claims.`:f?c.length?`Only block AI crawlers intentionally. If discovery matters, remove accidental Disallow rules for the blocked user agents.`:`Keep robots.txt explicit and intentional so humans and agents can see what is allowed.`:`Rerun the crawl with top-level per-bot robots.txt evidence before making crawler-policy claims.`,evidence:{blockedBots:c,unknownBots:l,declaredBots:g.length,availability:o?.availability,status:o?.status,error:o?.error,scope:`start-url-robots-policy`,startUrl:e.config.url}}),Ob({id:`robots-sitemap`,section:`agent-access`,maxScore:0,evaluated:m,score:0,title:m?`robots.txt sitemap declarations were checked`:`robots.txt sitemap declarations were not evaluated`,plainEnglish:u?`The robots.txt response was unavailable, so sitemap declarations could not be checked.`:d?o.sitemapUrls.length?`robots.txt declares at least one sitemap URL.`:`robots.txt does not declare a sitemap URL.`:`This crawl does not include top-level robots.txt evidence, so sitemap declarations could not be checked.`,action:u?`Restore robots.txt availability and rerun before evaluating sitemap declarations.`:d?`Add a Sitemap line to robots.txt so crawlers can discover the full index quickly.`:`Rerun the crawl with top-level robots.txt evidence before evaluating sitemap declarations.`,evidence:{sitemapUrls:o?.sitemapUrls??[]}}),Ob({id:`agent-resources`,section:`agent-access`,maxScore:0,score:0,title:`Agent descriptor files are discoverable`,plainEnglish:_?.length?`${_.length} machine-readable agent resource was found.`:`No OpenAPI, MCP, ai-plugin, or agent descriptor file was found at the common locations.`,action:`Publish useful descriptors only when they are real: OpenAPI for APIs, MCP metadata for tools, or agent.json for site capabilities.`,evidence:{resources:e.ai?.agentResources??[]}})]),Db(`machine-readable`,`Machine-readable signals`,[Ob({id:`llms-txt`,section:`machine-readable`,maxScore:0,score:0,title:`llms.txt is optional`,plainEnglish:e.ai?.llmsTxt?.exists?t?.llmsTxt?`An optional llms.txt file was found and its body was checked. It declared ${t.llmsTxt.links.length} links, ${t.llmsTxt.duplicateLinks.length} duplicates, and ${t.llmsTxt.missingCrawlRoutes.length} links outside the crawled route inventory. Google says llms.txt has no positive or negative Search impact.`:`An optional llms.txt file was found. Its body was not validated in this crawl. Google says it has no positive or negative Search impact.`:`No llms.txt file was found. Google says the file is not needed for Search and does not affect visibility.`,action:`No SEO action is required. Generate one only for a specific agent or service that consumes it.`,evidence:{llmsTxt:e.ai?.llmsTxt,validation:t?.llmsTxt,googleSearchImpact:`none`,guidanceUrl:`https://developers.google.com/search/updates#clarifying-guidance-on-llms-txt-files`}}),Ob({id:`valid-json-ld`,section:`machine-readable`,maxScore:8,evaluated:v.length>0,score:y.length?3:8,title:v.length?`Observed JSON-LD parses cleanly`:`JSON-LD syntax was not evaluated`,plainEnglish:v.length?y.length?`${y.length} evaluated page contains invalid JSON-LD.`:`The JSON-LD observed on evaluated pages parsed cleanly.`:`No JSON-LD was observed, so this report cannot make a syntax claim.`,action:y.length?`Fix invalid JSON-LD before relying on its structured-data claims.`:`Use structured data only where it accurately represents the visible page and a supported use case.`,urls:y.slice(0,10).map(e=>e.finalUrl)})]),Db(`content-clarity`,`Content clarity`,[Ob({id:`answerable-content`,section:`content-clarity`,maxScore:0,evaluated:i,score:0,title:`Short opening paragraphs are an unscored observation`,plainEnglish:i?`${wb(b.length,r)}% of indexable pages have at least one 25-word paragraph near the start. This does not establish content quality, citation likelihood, or Google AI eligibility.`:`No indexable 2xx pages were available, so opening-paragraph coverage was not evaluated.`,action:`Write for the reader and the page intent. Do not create artificial answer blocks or chunk content solely for AI Search.`,urls:Ab(n,e=>!e.geo?.answerable),evidence:{observedPages:b.length,evaluatedPages:r,heuristic:`one-of-first-three-paragraphs-has-at-least-25-words`,googleSearchImpact:`not-established`,guidanceUrl:`https://developers.google.com/search/docs/fundamentals/ai-optimization-guide`}}),Ob({id:`semantic-html`,section:`content-clarity`,maxScore:0,evaluated:i,score:0,title:`Semantic HTML coverage observed`,plainEnglish:i?`${wb(x.length,r)}% of indexable pages use semantic structure.`:`No indexable 2xx pages were available, so semantic HTML coverage was not evaluated.`,action:`Use real headings, lists, tables, nav, main, article, and section elements instead of layout-only divs.`}),Ob({id:`titles-headings-meta`,section:`content-clarity`,maxScore:0,evaluated:i,score:0,title:`Title, H1, and description coverage observed`,plainEnglish:i?`${wb(ee.length,r)}% of indexable pages have title/H1 coverage and ${wb(te.length,r)}% have meta descriptions.`:`No indexable 2xx pages were available, so title, H1, and description coverage was not evaluated.`,action:`Write a unique title and descriptive H1. Add a useful meta description when a summary would help searchers understand the page.`}),Ob({id:`language`,section:`content-clarity`,maxScore:0,evaluated:i,score:0,title:`HTML language declaration coverage observed`,plainEnglish:i?`${wb(ne.length,r)}% of indexable pages declare a language.`:`No indexable 2xx pages were available, so language declaration coverage was not evaluated.`,action:`Set the html lang attribute so crawlers and assistive tech understand the page language.`})]),Db(`entity-signals`,`Entity signals`,[Ob({id:`entity-schema`,section:`entity-signals`,maxScore:0,evaluated:i,score:0,title:`Entity-signal coverage observed`,plainEnglish:i?`${wb(S.length,r)}% of indexable pages include entity signals such as Organization, Person, Product, sameAs, or social profile links.`:`No indexable 2xx pages were available, so entity-signal coverage was not evaluated.`,action:`Make the homepage and key pages explicit about the brand, product, people, and official profiles using accurate schema and links.`,urls:Ab(n,e=>!S.includes(e))}),Ob({id:`authors-dates`,section:`entity-signals`,maxScore:0,score:0,title:`People and dates are visible where they matter`,plainEnglish:`Author and date signals help agents judge freshness and accountability.`,action:`Add visible authors, reviewed dates, and updated dates to content where trust and freshness matter.`})]),Db(`technical-ux`,`Technical UX`,[Ob({id:`snippet-controls`,section:`technical-ux`,maxScore:0,evaluated:i,score:0,title:i?C.length?`Page-level snippet restrictions observed`:`No page-level snippet restriction detected`:`Snippet controls were not evaluated`,plainEnglish:i?C.length?`${re.length} indexable ${re.length===1?`page has`:`pages have`} snippets blocked and ${w.length} ${w.length===1?`has`:`have`} a positive max-snippet limit.`:`No nosnippet or restrictive max-snippet directive was detected on the evaluated indexable pages.`:`No indexable 2xx pages were available, so snippet controls were not evaluated.`,action:C.length?`Confirm each restriction is intentional. Do not remove publisher controls solely to satisfy this report.`:`No action is required. This observation does not guarantee that Google will select or show a snippet.`,urls:C.slice(0,10).map(({url:e})=>e),evidence:{evaluatedPages:n.length,blockedPages:re.length,limitedPages:w.length,restrictions:C.slice(0,10)}}),Ob({id:`https-viewport-readable`,section:`technical-ux`,maxScore:0,score:0,title:`Pages are secure, mobile-ready, and readable from HTML`,plainEnglish:`Agents need stable HTML content, not just a visual page that works in one browser session.`,action:`Serve HTTPS, include viewport metadata, and make important copy available in the initial HTML or rendered output.`}),Ob({id:`status-indexability`,section:`technical-ux`,maxScore:12,evaluated:e.pages.length>0,score:Tb(wb(n.length,e.pages.length||1),12),title:e.pages.length?`Crawled-page indexability observed`:`Crawled-page indexability was not evaluated`,plainEnglish:e.pages.length?`${wb(n.length,e.pages.length)}% of crawled pages are indexable 2xx pages.`:`No crawled pages were available, so status and indexability were not evaluated.`,action:`Fix 4xx/5xx pages, accidental noindex, and blocked internal URLs before expanding AI discovery work.`})]),Db(`agent-content`,`Agent content observations`,[Ob({id:`markdown-alternates`,section:`agent-content`,maxScore:0,score:0,evaluated:!!t,title:t?`Markdown alternatives were checked`:`Markdown alternatives were not evaluated`,plainEnglish:t?`${t.markdownAlternates.evaluatedPages} of ${t.markdownAlternates.eligibleHtmlPages} successful HTML pages returned Markdown through an explicit alternative or content negotiation. ${t.markdownAlternates.exactByteMatches} paired explicit responses matched content negotiation byte for byte.`:`This crawl does not include the focused representation checks, so it cannot say whether HTML pages publish stable Markdown alternatives.`,action:`Use the focused agent-readiness report when you need route coverage, content negotiation, byte stability, or extraction-quality evidence.`,evidence:t?{eligibleHtmlPages:t.markdownAlternates.eligibleHtmlPages,advertisedPages:t.markdownAlternates.advertisedPages,exactByteMatches:t.markdownAlternates.exactByteMatches,stableResponses:t.markdownAlternates.stableResponses}:void 0}),Ob({id:`agent-skills-discovery`,section:`agent-content`,maxScore:0,score:0,evaluated:!!t,title:t?`Agent Skills discovery was checked`:`Agent Skills discovery was not evaluated`,plainEnglish:t?`${t.agentSkills.skills.length} published skill${t.agentSkills.skills.length===1?``:`s`} were found. ${t.agentSkills.skills.filter(e=>e.digestMatches).length} matched their declared SHA-256 digest.`:`This crawl checked common legacy descriptor paths but did not validate the Agent Skills discovery index or its file digests.`,action:`Publish Agent Skills only for real reusable capabilities. Generate the index digest from the exact deployed SKILL.md bytes.`,evidence:t?.agentSkills}),Ob({id:`agent-content-profile`,section:`agent-content`,maxScore:0,score:0,evaluated:!!t,title:t?`The content profile was selected explicitly`:`No focused agent-readiness profile was selected`,plainEnglish:t?`Document access, representations, discovery, identity, and extraction quality were evaluated. API, application, and commerce checks were marked not applicable rather than failed.`:`A normal crawl does not assume that a content site also exposes a public API, remote agent endpoint, or checkout.`,action:`Run a different profile only when that public capability really exists.`,evidence:t?.profileApplicability})]),Db(`crawl-completeness`,`Crawl completeness`,[Ob({id:`crawl-depth`,section:`crawl-completeness`,maxScore:8,score:a?3:8,title:a?`The crawl hit the page cap`:`The crawl finished without hitting the page cap`,plainEnglish:a?`The crawl stopped at maxPages (${e.config.maxPages}), so the report may under-sample the site.`:`The crawl did not hit the configured page cap.`,action:`Raise --max-pages or crawl from sitemap mode when you need a complete site inventory.`}),Ob({id:`internal-link-discovery`,section:`crawl-completeness`,maxScore:0,score:0,title:`Internal links expose more of the site`,plainEnglish:`A healthy crawl should discover important pages through internal links, not only through a seed URL.`,action:`Link important pages from navigational, hub, or contextual links so crawlers can find them naturally.`})])],T=ie.flatMap(e=>e.checks),ae=e.status===`completed`&&!h?`complete`:`partial`,oe=T.filter(e=>e.evaluated&&(e.status===`warning`||e.status===`fail`)&&[`robots-ai-bots`,`valid-json-ld`,`status-indexability`,`crawl-depth`].includes(e.id)).sort((e,t)=>Number(t.status===`fail`)-Number(e.status===`fail`)||e.id.localeCompare(t.id)).slice(0,8);return{reportId:e.id,url:e.config.url,generatedAt:e.generatedAt,dataStatus:ae,assessment:`evidence-only`,headline:ae===`partial`?`AI-search evidence is incomplete. Fix the collection gaps before drawing site-wide conclusions.`:oe.length?`${oe.length} evidence-backed technical ${oe.length===1?`action remains`:`actions remain`}. Optional observations are not treated as ranking or citation factors.`:`No evidence-backed technical action was found in this crawl. This is not a visibility or citation verdict.`,sections:ie,checks:T,topActions:oe,botAccess:s,agentResources:e.ai?.agentResources??[],caveats:[...e.caveats,`This report deliberately has no aggregate readiness score. Google documents no separate technical requirements for its generative AI Search features.`,`llms.txt is treated as optional agent-discovery metadata, not a Google Search ranking or visibility factor.`,`Paragraph length and placement are observations only. Google does not require content chunking or special answer blocks for generative AI features.`,`Snippet controls reflect page-level publisher directives only. No detected restriction does not guarantee selection, visibility, or a displayed snippet.`,...o?u?[`robots.txt was unavailable, so crawler access and sitemap declaration checks are inconclusive.`]:f?[]:[`robots.txt was collected without per-bot policy evidence, so the crawler-policy check is inconclusive.`]:[`This crawl report does not include top-level robots.txt AI bot data.`],`robots.txt checks describe policy for the configured start URL only. They do not verify actual crawler requests or site-wide access.`]}}const Mb=new Set([`connection_error`,`client_error`,`server_error`,`robots_blocked`,`noindex`,`x_robots_noindex`,`canonicalized_page`]);function Nb(e){let t=new Map;for(let n of e.issues){if(!Mb.has(n.ruleId))continue;let e=t.get(n.url)??[];e.push({ruleId:n.ruleId,title:n.title,severity:n.severity}),t.set(n.url,e)}return e.pages.map(e=>{let n=e.geo??{semanticHtml:!1,structuredData:!1,hasAuthor:!1,hasDate:!1,questionHeadings:0,structuredBlocks:0,answerable:!1},r=_m({metaRobots:e.metaRobots,xRobotsTag:e.xRobotsTag});return{url:e.finalUrl,issueCount:t.get(e.url)?.length??0,issues:t.get(e.url)??[],searchEligibility:{successfulHtmlResponse:e.status>=200&&e.status<300&&e.contentType?.toLowerCase().includes(`text/html`)===!0,crawlAllowed:e.robotsTxt?.allowed??null,indexableCandidate:e.indexable,declaredIndexability:e.declaredIndexability,indexability:e.indexability,snippetEligibility:r},observations:{semanticHtml:n.semanticHtml,structuredData:n.structuredData,hasAuthor:n.hasAuthor,hasDate:n.hasDate,questionHeadings:n.questionHeadings,structuredBlocks:n.structuredBlocks,answerable:n.answerable}}}).filter(e=>e.issueCount>0||!e.searchEligibility.successfulHtmlResponse||e.searchEligibility.crawlAllowed===!1||!e.searchEligibility.indexableCandidate||e.searchEligibility.snippetEligibility.status!==`not-restricted`).sort((e,t)=>t.issueCount-e.issueCount||e.url.localeCompare(t.url))}function Pb(e){let t=[];return e.pages.length===0&&t.push(`no-pages-evaluated`),e.status!==`completed`&&t.push(`crawl-not-complete`),e.requestEvidenceStatus===`partial`&&t.push(`request-evidence-partial`),e.requestEvidenceStatus===`unavailable`&&t.push(`request-evidence-unavailable`),e.summary.pageLimitReached&&t.push(`page-limit-reached`),e.summary.skippedUrlsByImpact.coverageAffecting>0&&t.push(`coverage-affecting-skips`),e.summary.failedRequests>0&&t.push(`failed-requests`),e.summary.extractionFailures>0&&t.push(`extraction-failures`),t}function Fb(e,t={}){let n=Nb(e),r=t.limit??50,i=n.slice(0,r),a=Pb(e),o=i.length<n.length;return{schemaVersion:1,dataStatus:e.pages.length?a.length?`partial`:`complete`:`unavailable`,source:{provider:`seo-crawl`,reportId:e.id,definitionId:e.definitionId,generatedAt:e.generatedAt,startUrl:e.config.url,crawlStatus:e.status,requestEvidenceStatus:e.requestEvidenceStatus,configuredMaxPages:e.config.maxPages,pageLimitReached:e.summary.pageLimitReached,discoveredUrls:e.summary.discoveredUrls,queuedUrls:e.summary.queuedUrls,crawledUrls:e.summary.crawledUrls,skippedUrls:e.summary.skippedUrls,coverageAffectingSkippedUrls:e.summary.skippedUrlsByImpact.coverageAffecting,nonImpactingSkippedUrls:e.summary.skippedUrlsByImpact.nonImpacting,failedRequests:e.summary.failedRequests,extractionFailures:e.summary.extractionFailures,partialReasons:a},selection:{evaluatedPages:e.pages.length,totalMatchedPages:n.length,returnedPages:i.length,limit:r,truncated:o},eligibilityGaps:i,warnings:e.warnings,caveats:[...e.caveats,...o?[`${n.length-i.length} matched pages were omitted by the output limit.`]:[],`No detected restriction among evaluated pages does not prove indexing, selection, visibility, or citation.`]}}function Ib(e){return Math.ceil(e.length/4)}function Lb(e){return new URL(e).hostname.replace(/^www\./,``)}function Rb(e){return e.title??e.h1??(new URL(e.finalUrl).pathname||e.finalUrl)}function zb(e){let t=new URL(e.finalUrl).pathname.toLowerCase();return/\/(docs|documentation|guide|guides|help|kb|learn)\b/.test(t)?`Docs and guides`:/\/(blog|articles|news|resources)\b/.test(t)?`Articles`:/\/(product|products|features|tools|solutions)\b/.test(t)?`Products and tools`:/\/(pricing|plans)\b/.test(t)?`Pricing`:/\/(about|company|team|contact)\b/.test(t)?`Company`:`Core pages`}function Bb(e){return/\/(login|logout|cart|checkout|account|admin|wp-admin|search|tag|privacy|terms|cookie|legal)\b/i.test(new URL(e).pathname)}function Vb(e,t=[]){return t.some(t=>{if(!t.includes(`*`))return e.includes(t);let n=t.split(`*`),r=0;for(let t of n){if(!t)continue;let n=e.slice(r).indexOf(t);if(n===-1)return!1;r+=n+t.length}return!0})}function Hb(e,t={}){return e.pages.filter(e=>e.indexable&&e.status<400).filter(e=>!Bb(e.finalUrl)).filter(e=>!Vb(e.finalUrl,t.exclude)).sort((e,t)=>(t.internalLinkAuthorityScore??0)-(e.internalLinkAuthorityScore??0)||(t.wordCount??0)-(e.wordCount??0)||e.finalUrl.localeCompare(t.finalUrl))}function Ub(e){let t=e.ai?.llmsTxt,n=e.agentDiscovery?.llmsTxt,r=Hb(e),i=[];n?.exists&&!/^\s*(?:text\/plain|text\/markdown)\b/iu.test(n.contentType??``)&&i.push({id:`llms-content-type`,severity:`medium`,title:`llms.txt uses an unexpected content type`,plainEnglish:`The file returned ${n.contentType??`no content type`} instead of plain text or Markdown.`,action:`Serve the deterministic text file as text/plain or text/markdown so consumers do not mistake it for HTML.`,evidence:{contentType:n.contentType}}),n?.exists&&n.headingCount===0&&i.push({id:`llms-structure`,severity:`medium`,title:`llms.txt has no useful heading structure`,plainEnglish:`The fetched file did not contain a level-one or level-two Markdown heading.`,action:`Start with one clear title, then group a short list of useful entry points under descriptive headings.`}),n?.duplicateLinks.length&&i.push({id:`llms-duplicate-links`,severity:`low`,title:`llms.txt repeats the same link`,plainEnglish:`${n.duplicateLinks.length} URL${n.duplicateLinks.length===1?` appears`:`s appear`} more than once.`,action:`Remove duplicate entries so the file remains short and deliberate.`,evidence:{urls:n.duplicateLinks}});let a=n?.links.filter(e=>!e.status||e.status<200||e.status>=400||e.error)??[];return a.length&&i.push({id:`llms-broken-links`,severity:`medium`,title:`llms.txt links did not resolve`,plainEnglish:`${a.length} declared link${a.length===1?``:`s`} did not return a usable response during this audit.`,action:`Update or remove stale entries, then rerun the focused agent-readiness report.`,evidence:{links:a}}),n?.missingCrawlRoutes.length&&i.push({id:`llms-outside-crawl`,severity:`low`,title:`llms.txt links fall outside the crawl inventory`,plainEnglish:`${n.missingCrawlRoutes.length} same-origin route${n.missingCrawlRoutes.length===1?` was`:`s were`} not present in the retained crawl.`,action:`Confirm those routes are intentional and indexable, or remove stale declarations.`,evidence:{urls:n.missingCrawlRoutes}}),n?.repeatedHashStable===!1&&i.push({id:`llms-unstable-body`,severity:`medium`,title:`llms.txt changed between repeated requests`,plainEnglish:`Two requests in the same audit returned different SHA-256 digests.`,action:`Generate the file during the build and remove timestamps, random ordering, or runtime rewriting.`}),r.length<3&&i.push({id:`thin-llms-inventory`,severity:`low`,title:`The crawl has a small llms.txt inventory`,plainEnglish:`There are very few indexable candidate pages to include in llms.txt.`,action:`If you choose to generate the optional file, run a deeper crawl or sitemap crawl first.`,evidence:{candidatePages:r.length}}),{reportId:e.id,url:e.config.url,exists:!!t?.exists,llmsTxtUrl:t?.url??new URL(`/llms.txt`,e.config.url).toString(),status:t?.status,optional:!0,googleSearchImpact:`none`,guidanceUrl:`https://developers.google.com/search/updates#clarifying-guidance-on-llms-txt-files`,headline:t?.exists?n?i.length?`The optional llms.txt file was validated and ${i.length} content or link issue${i.length===1?` needs`:`s need`} review. It has no Google Search visibility impact.`:`The optional llms.txt file was validated with ${n.links.length} resolving link${n.links.length===1?``:`s`} and a stable body. It has no Google Search visibility impact.`:`An optional llms.txt file is present, but its body was not validated in this crawl. It has no Google Search visibility impact.`:`No llms.txt file was found. This is not an SEO issue and requires no action.`,issues:i,recommendedPages:r.slice(0,25).map(e=>({url:e.finalUrl,title:e.title,section:zb(e),reason:(e.internalLinkAuthorityScore??0)>0?`High internal link authority in this crawl.`:`Indexable page with useful content signals.`}))}}function Wb(e,t={}){let n=t.maxUrls??250,r=t.tokenBudget??12e3,i=t.title??Lb(e.config.url),a=t.description??`Curated entry points for agents reading ${Lb(e.config.url)}.`,o=Hb(e,t).slice(0,n),s=new Map;for(let e of o){let t=zb(e);s.set(t,[...s.get(t)??[],e])}let c=[`# ${i}`,``,`> ${a}`,``],l=0,u={};for(let[e,t]of s){let n=[`## ${e}`,``],i=0;for(let e of t){let t=Rb(e).replace(/\]/g,`\\]`),a=e.metaDescription?` - ${e.metaDescription.replace(/\s+/g,` `).slice(0,160)}`:``,o=`- [${t}](${e.finalUrl})${a}`;if(Ib([...c,...n,o,``].join(`
331
331
  `))>r&&l>0)break;n.push(o),i+=1,l+=1}if(!i||(n.push(``),c.push(...n),u[e]=i,i<t.length))break}c.push(`## Notes`,``,`- Optional metadata for agents and services that explicitly support llms.txt.`,`- [Google Search guidance](https://developers.google.com/search/updates#clarifying-guidance-on-llms-txt-files) says llms.txt does not affect visibility or rankings.`,`- Generated from crawl report ${e.id}.`,`- Crawl generated at ${e.generatedAt}.`),e.caveats.length&&c.push(...e.caveats.map(e=>`- Caveat: ${e}`));let d=`${c.join(`
332
332
  `)}\n`;return{content:d,includedUrls:l,estimatedTokens:Ib(d),sections:u}}const Gb={pass:1,warn:.5,fail:0},Kb={"ai-bot-access":20,https:10,"indexable-pages":15,"structured-data":15,"valid-json-ld":10,"entity-identity":20,"answerable-content":10},qb=[`ai-bot-access`,`https`,`indexable-pages`,`structured-data`,`valid-json-ld`,`entity-identity`,`answerable-content`];function Jb(e){return e.pages.filter(e=>e.indexable&&e.status<400)}function Yb(e,t){return t>0?Math.round(e/t*100):0}function Xb(e){return e.slice(0,10).map(e=>e.finalUrl)}function Zb(e,t,n){return t===0?`unknown`:e/t>=n?`pass`:e>0?`warn`:`fail`}function Qb(e){let t={id:`ai-bot-access`,label:`AI crawler access in the start-URL robots policy`,weight:Kb[`ai-bot-access`]},n=e.ai?.robotsTxt,r=e.config.url,i=`Fetch ${n?.url??`${new URL(r).origin}/robots.txt`} and confirm every Disallow rule for the listed AI crawler tokens is intentional.`;if(!n)return{...t,status:`unknown`,reason:`This crawl did not collect top-level robots.txt policy evidence for AI crawler tokens.`,observed:{robotsEvidence:`not-collected`,startUrl:r},finding:`AI crawler access could not be evaluated because per-bot robots.txt evidence was not collected.`,verification:i};let a=n.availability===`rate-limited`||n.availability===`unreachable`||n.availability===`access-blocked`,o=n.botAccess??[];if(a||o.length===0)return{...t,status:`unknown`,reason:a?`robots.txt was ${n.availability}, so the AI crawler policy could not be read.`:`No AI crawler tokens were evaluated against the start-URL robots policy.`,observed:{availability:n.availability,status:n.status,error:n.error,botAccess:o,startUrl:r},finding:`AI crawler access is inconclusive from this crawl. Restore a readable robots.txt response and rerun before drawing a conclusion.`,verification:i};let s=o.filter(e=>e.allowed===!1),c=s.map(e=>e.userAgent),l=s.length===0?`pass`:s.length===o.length?`fail`:`warn`,u=l===`pass`?`The start-URL robots policy does not block any evaluated AI crawler token. This describes policy at the entry point, not proven fetchability of every URL.`:l===`fail`?`The start-URL robots policy blocks all ${o.length} evaluated AI crawler tokens. This may be intentional; confirm before treating it as a defect.`:`The start-URL robots policy blocks ${s.length} of ${o.length} evaluated AI crawler tokens (${c.join(`, `)}). This may be intentional; confirm before treating it as a defect.`;return{...t,status:l,observed:{availability:n.availability,status:n.status,evaluatedTokens:o.length,blockedTokens:s.length,blockedUserAgents:c,botAccess:o,startUrl:r},finding:u,verification:i}}function $b(e){let t={id:`https`,label:`Secure transport on evaluated pages`,weight:Kb.https},n=`Load a sample of the returned pages and confirm each is served over HTTPS with no mixed-content downgrade.`;if(e.length===0)return{...t,status:`unknown`,reason:`No indexable 2xx pages were evaluated in this crawl.`,observed:{evaluatedPages:0},finding:`HTTPS coverage could not be evaluated because no indexable 2xx pages were available.`,verification:n};let r=e.filter(e=>e.isHttps===!0),i=Zb(r.length,e.length,1);return{...t,status:i,observed:{evaluatedPages:e.length,httpsPages:r.length,coveragePercent:Yb(r.length,e.length),insecureSample:Xb(e.filter(e=>e.isHttps!==!0))},finding:i===`pass`?`Every evaluated indexable page was served over HTTPS.`:`${Yb(r.length,e.length)}% of evaluated indexable pages were served over HTTPS.`,verification:n}}function ex(e){let t={id:`indexable-pages`,label:`Indexable 2xx share of crawled pages`,weight:Kb[`indexable-pages`]},n=`Review the non-indexable crawled URLs and confirm each noindex, redirect, canonical, or error state is intended.`,r=e.pages.length;if(r===0)return{...t,status:`unknown`,reason:`No pages were crawled, so indexability could not be evaluated.`,observed:{crawledPages:0},finding:`Indexability could not be evaluated because no pages were crawled.`,verification:n};let i=Jb(e).length,a=Zb(i,r,.85);return{...t,status:a,observed:{crawledPages:r,indexablePages:i,coveragePercent:Yb(i,r)},finding:`${Yb(i,r)}% of crawled pages are indexable 2xx pages. Non-indexable states can be intentional; this check flags share, not intent.`,verification:n}}function tx(e){let t={id:`structured-data`,label:`Structured-data coverage on evaluated pages`,weight:Kb[`structured-data`]},n=`Open a sample of the pages without structured data and confirm markup exists only where it accurately represents the visible page.`;if(e.length===0)return{...t,status:`unknown`,reason:`No indexable 2xx pages were evaluated in this crawl.`,observed:{evaluatedPages:0},finding:`Structured-data coverage could not be evaluated because no indexable 2xx pages were available.`,verification:n};let r=e.filter(e=>(e.structuredDataFormats?.length??0)>0||(e.schemaTypes?.length??0)>0),i=Zb(r.length,e.length,.5);return{...t,status:i,observed:{evaluatedPages:e.length,pagesWithStructuredData:r.length,coveragePercent:Yb(r.length,e.length),missingSample:Xb(e.filter(e=>!r.includes(e)))},finding:`${Yb(r.length,e.length)}% of evaluated indexable pages expose structured data. Coverage is a convenience signal for agents, not a documented ranking or citation requirement.`,verification:n}}function nx(e){let t={id:`valid-json-ld`,label:`JSON-LD parses cleanly where present`,weight:Kb[`valid-json-ld`]},n=`Validate the flagged pages with a JSON-LD or Rich Results parser and fix any syntax errors.`,r=e.pages.filter(e=>e.structuredDataFormats?.includes(`json-ld`)||(e.invalidJsonLdCount??0)>0);if(r.length===0)return{...t,status:`unknown`,reason:`No JSON-LD was observed, so its syntax could not be evaluated.`,observed:{jsonLdPages:0},finding:`JSON-LD validity could not be evaluated because no JSON-LD was observed.`,verification:n};let i=r.filter(e=>(e.invalidJsonLdCount??0)>0),a=i.length===0?`pass`:`fail`;return{...t,status:a,observed:{jsonLdPages:r.length,invalidJsonLdPages:i.length,invalidSample:Xb(i)},finding:a===`pass`?`JSON-LD on ${r.length} observed ${r.length===1?`page`:`pages`} parsed cleanly. Clean syntax is not proof of entity recognition or rich-result eligibility.`:`${i.length} of ${r.length} pages with JSON-LD contain invalid JSON-LD.`,verification:n}}function rx(e){let t={id:`entity-identity`,label:`Entity identity connected with sameAs`,weight:Kb[`entity-identity`]},n=`Confirm the homepage and key pages carry accurate Organization, Person, or LocalBusiness schema with sameAs links to profiles you control.`,r=Cb(e);if(r.evaluatedPages===0)return{...t,status:`unknown`,reason:`No indexable 2xx pages were evaluated in this crawl.`,observed:{evaluatedPages:0},finding:`Entity identity could not be evaluated because no indexable 2xx pages were available.`,verification:n};let i=Object.keys(r.entities.schemaTypes).some(vb),a=Object.entries(r.entities.sameAsByType).filter(([e])=>yb(e)).flatMap(([,e])=>e),o=[...new Set(a)].sort(),s=o.length?`pass`:i?`warn`:`fail`;return{...t,status:s,observed:{evaluatedPages:r.evaluatedPages,schemaTypes:r.entities.schemaTypes,siteSameAs:o,sameAs:r.entities.sameAs,socialProfiles:r.entities.socialProfiles},finding:s===`pass`?`The crawl found sameAs links attached to Organization or LocalBusiness structured data for the site entity.`:s===`warn`?`Entity schema is present, but no Organization or LocalBusiness sameAs evidence connects the site entity to official profiles.`:`No Organization, LocalBusiness, Person, Product, SoftwareApplication, or WebSite schema was found on the evaluated pages.`,verification:n}}function ix(e){let t={id:`answerable-content`,label:`Substantive opening content (heuristic)`,weight:Kb[`answerable-content`]},n=`Read a sample of the flagged pages and confirm the opening content answers the page intent for a human reader.`;if(e.length===0)return{...t,status:`unknown`,reason:`No indexable 2xx pages were evaluated in this crawl.`,observed:{evaluatedPages:0},finding:`Opening-content coverage could not be evaluated because no indexable 2xx pages were available.`,verification:n};let r=e.filter(e=>e.geo?.answerable),i=Zb(r.length,e.length,.5);return{...t,status:i,observed:{evaluatedPages:e.length,answerablePages:r.length,coveragePercent:Yb(r.length,e.length),heuristic:`one-of-first-three-paragraphs-has-at-least-25-words`,missingSample:Xb(e.filter(e=>!e.geo?.answerable))},finding:`${Yb(r.length,e.length)}% of evaluated indexable pages open with at least one substantive paragraph. This is a readability heuristic, not a content-quality or citation measure.`,verification:n}}function ax(e){let t=[],n=Ub(e);t.push({id:`llms-txt`,label:`Optional llms.txt discovery file`,observed:{exists:n.exists,url:n.llmsTxtUrl,status:n.status,googleSearchImpact:n.googleSearchImpact},note:`llms.txt is optional agent-discovery metadata. Google reports no positive or negative Search impact, so presence and absence are left out of the score.`});let r=(e.ai?.agentResources??[]).filter(e=>e.exists);t.push({id:`agent-descriptors`,label:`Machine-readable agent descriptors`,observed:{detected:r.length,resources:e.ai?.agentResources??[]},note:`Descriptor files such as OpenAPI or agent metadata help machine discovery only when they are real. Their absence is not a search defect, so they are unscored.`});let i=Jb(e).map(e=>({url:e.finalUrl,control:_m({metaRobots:e.metaRobots,xRobotsTag:e.xRobotsTag})})).filter(({control:e})=>e.status!==`not-restricted`);return t.push({id:`snippet-controls`,label:`Page-level snippet directives`,observed:{restrictedPages:i.length,restrictions:i.slice(0,10)},note:`Snippet directives are intentional publisher controls. They are recorded as observations, not scored, because a restriction can be deliberate.`}),t}function ox(e){return e===null?`unscored`:e>=80?`strong`:e>=50?`moderate`:`weak`}function sx(e){let t=Jb(e),n=Fb(e),r=n.dataStatus===`complete`,i={"ai-bot-access":Qb(e),https:$b(t),"indexable-pages":ex(e),"structured-data":tx(t),"valid-json-ld":nx(e),"entity-identity":rx(e),"answerable-content":ix(t)},a=qb.map(e=>i[e]),o={pass:a.filter(e=>e.status===`pass`).length,warn:a.filter(e=>e.status===`warn`).length,fail:a.filter(e=>e.status===`fail`).length,unknown:a.filter(e=>e.status===`unknown`).length,scored:0},s=a.filter(e=>e.status!==`unknown`);o.scored=s.length;let c=s.reduce((e,t)=>e+t.weight,0),l=a.reduce((e,t)=>e+t.weight,0),u=s.reduce((e,t)=>e+t.weight*Gb[t.status],0),d=c>0?Math.round(u/c*100):null,f=o.unknown>0||!r;d===100&&f&&(d=99);let p=a.filter(e=>e.status===`unknown`).map(e=>({id:e.id,reason:e.reason??`Evidence was unavailable for this check.`})),m=ax(e),h=ox(d),g=d===null?`AI search scorecard could not be scored because no check had known evidence in this crawl.`:`AI search scorecard: ${d}/100, a heuristic summary of ${o.scored} of ${a.length} of this tool's own checks with known evidence.${f?` Score is partial: ${o.unknown} ${o.unknown===1?`check was`:`checks were`} unknown${r?``:` and the crawl was incomplete`}, so a clean 100 is not possible.`:``}`,_=[...e.caveats,`This score is a heuristic summary of this tool's own checks. It is not a Google or AI-engine requirement, an eligibility verdict, a ranking factor, or a prediction of citations, indexing, visibility, or traffic.`,`Only checks with known evidence are scored. Unknown checks are excluded and are never counted as failures.`,...r?[]:[`The crawl was incomplete (${n.source.partialReasons.join(`, `)||`partial evidence`}), so this score is scoped to the evaluated pages and cannot be a clean 100.`],...i[`ai-bot-access`].status===`warn`||i[`ai-bot-access`].status===`fail`?[`Blocking AI crawler tokens can be an intentional publisher choice. Confirm intent before treating the robots policy as a defect.`]:[],`llms.txt, agent descriptors, and snippet directives are recorded as observations, not scored, because their absence or presence can be intentional.`];return{reportId:e.id,url:e.config.url,generatedAt:e.generatedAt,methodology:{id:`seo-ai-search-scorecard`,version:1,summary:`A deterministic 0-100 heuristic summary of this tool's own AI-search checks over one crawl. It is not a Google or AI-engine requirement, eligibility verdict, or ranking predictor.`,statusCredit:Gb,weights:{...Kb},formula:`score = round(100 * sum(weight_i * credit(status_i)) / sum(weight_i)) over checks with a known status, where credit is pass 1, warn 0.5, fail 0. Unknown checks are excluded from both sums. A partial or incomplete crawl caps the score below 100.`},score:d,scoreLabel:`heuristic-check-summary`,maxScore:100,band:h,partial:f,crawlComplete:r,counts:o,weightScored:c,weightTotal:l,excluded:p,checks:a,observations:m,headline:g,caveats:_,source:{provider:`seo-crawl`,crawlStatus:e.status,requestEvidenceStatus:e.requestEvidenceStatus,startUrl:e.config.url,configuredMaxPages:e.config.maxPages,pageLimitReached:e.summary.pageLimitReached,evaluatedPages:t.length,crawledPages:e.pages.length,partialReasons:n.source.partialReasons}}}const cx=[{id:`missing_title`,title:`Title missing`,category:`metadata`,defaultSeverity:`high`,whyItMatters:`The title is the main search-result headline and one of the clearest page-topic signals. Without it, search engines invent a label and users get a weaker reason to click.`,howToFix:`Add one descriptive title that names the page topic and matches the main query intent. Keep it useful for humans first.`,impactIfIgnored:`The page can look vague in search results, earn lower CTR, and send weaker relevance signals.`,howToVerify:"Re-run `seo audit-page --url <url>` and confirm the title is present in the page audit output.",agentHints:{evidenceFields:[`page.title`],suggestedCommands:[`seo audit-page --url <url> --json`]}},{id:`title_too_wide`,title:`Title may truncate on some devices`,category:`metadata`,defaultSeverity:`low`,whyItMatters:`Google has no fixed title length limit, but title links are truncated to fit the available device width. Important wording placed late may not always display.`,howToFix:`Review the title rather than shortening it blindly. Put the clearest page-specific wording first and remove filler only when meaning is preserved.`,impactIfIgnored:`Some result layouts may truncate later words. This is a display estimate, not an indexing or ranking defect.`,howToVerify:`Re-run the audit and review estimatedPixels, referencePixels, confidence, and profile. Confirm the important wording appears early.`,agentHints:{evidenceFields:[`page.title`,`issue.evidence.estimatedPixels`,`issue.evidence.referencePixels`,`issue.evidence.confidence`,`issue.evidence.profile`],suggestedCommands:[`seo audit-page --url <url> --json`]}},{id:`title_duplicate`,title:`Duplicate title`,category:`metadata`,defaultSeverity:`medium`,whyItMatters:`When multiple pages share the same title, search engines and users get a weaker signal about which page is the right result.`,howToFix:`Rewrite each title so it reflects the specific page, including the unique entity, intent, location, product, or angle.`,impactIfIgnored:`Search engines may pick the wrong page to rank, and users may see repetitive snippets that do not explain the difference.`,howToVerify:`Re-run the crawl and confirm this title appears on only one indexable URL.`,agentHints:{evidenceFields:[`page.title`,`issue.evidence.duplicateCount`,`issue.evidence.sampleUrls`],suggestedCommands:[`seo crawl <url> --json`]}},{id:`h1_missing`,title:`Missing H1`,category:`headings`,defaultSeverity:`low`,whyItMatters:`A descriptive main heading helps readers and assistive technology identify the page topic and navigate its content.`,howToFix:`Add one clear H1 near the top of the main content. Make it describe the specific page, not just a generic section label.`,impactIfIgnored:`The visible document outline may be less clear to readers and assistive technology.`,howToVerify:`Re-run the crawl and confirm an H1 describes the page’s main visible topic.`,agentHints:{evidenceFields:[`page.h1`,`page.h1Count`],suggestedCommands:[`seo crawl <url> --max-pages 1 --json`]}},{id:`canonical_invalid`,title:`Canonical URL is invalid`,category:`canonical`,defaultSeverity:`high`,whyItMatters:`A malformed or non-HTTP canonical cannot identify a valid preferred page and may be ignored by search engines.`,howToFix:`Replace it with one absolute HTTP or HTTPS URL for the intended preferred page.`,impactIfIgnored:`Search engines receive unusable consolidation evidence and may choose a different canonical on their own.`,howToVerify:`Re-run the page audit and confirm the canonical resolves to the intended HTTP or HTTPS URL.`,agentHints:{evidenceFields:[`page.canonical`,`page.canonicalRaw`,`issue.evidence.canonicalRaw`],suggestedCommands:[`seo audit-page --url <url> --json`]}},{id:`canonical_conflict`,title:`Canonical declarations conflict`,category:`canonical`,defaultSeverity:`high`,whyItMatters:`Different canonical targets in HTML or HTTP headers give search engines contradictory consolidation signals.`,howToFix:`Choose one preferred absolute URL and make every canonical declaration agree, or remove the extra declaration source.`,impactIfIgnored:`Search engines may ignore the declarations or select a different canonical than the site intended.`,howToVerify:`Re-run the crawl and confirm canonicalStatus is single with one intended target.`,agentHints:{evidenceFields:[`page.canonicalCandidates`,`page.canonicalStatus`],suggestedCommands:[`seo crawl <url> --max-pages 1 --json`]}},{id:`canonical_multiple`,title:`Canonical declared more than once`,category:`canonical`,defaultSeverity:`low`,whyItMatters:`Repeated canonical declarations are error-prone even when they currently point to the same URL.`,howToFix:`Keep one canonical method per response where practical. If HTML and HTTP headers are both required, keep their targets identical.`,impactIfIgnored:`A future template or header change can create a hidden canonical conflict.`,howToVerify:`Re-run the crawl and confirm canonicalStatus is single.`,agentHints:{evidenceFields:[`page.canonicalCandidates`,`page.canonicalStatus`],suggestedCommands:[`seo crawl <url> --max-pages 1 --json`]}},{id:`canonical_outside_head`,title:`Canonical appears outside the HTML head`,category:`canonical`,defaultSeverity:`medium`,whyItMatters:`Google only accepts an HTML canonical link element in the document head.`,howToFix:`Move the canonical link element into a valid head section and keep one absolute preferred URL.`,impactIfIgnored:`The body declaration may be ignored, leaving the page without the intended canonical signal.`,howToVerify:`Re-run the crawl and confirm the canonical candidate source is html-head.`,agentHints:{evidenceFields:[`page.canonicalCandidates`,`page.canonicalStatus`],suggestedCommands:[`seo crawl <url> --max-pages 1 --json`]}},{id:`canonical_mismatch`,title:`Canonical differs from final URL`,category:`canonical`,defaultSeverity:`medium`,whyItMatters:`A canonical pointing somewhere else tells search engines that another URL may be the preferred page. That is useful when intentional and risky when accidental.`,howToFix:`If this page should rank, make the canonical self-referencing. If another URL is preferred, make sure internal links and redirects also point there.`,impactIfIgnored:`Search engines may consolidate signals into the wrong URL or drop this URL from search results.`,howToVerify:`Re-run the page audit and confirm the canonical matches the fetched final URL, or inspect the preferred target instead.`,agentHints:{evidenceFields:[`page.canonical`,`page.finalUrl`],suggestedCommands:[`seo audit-page --url <url> --json`]}},{id:`canonical_non_absolute`,title:`Canonical uses a relative URL`,category:`canonical`,defaultSeverity:`low`,whyItMatters:`Search engines can usually resolve relative canonicals, but absolute canonical URLs are clearer and less fragile across mirrors, previews, and rendered variants.`,howToFix:`Use a full https URL in the canonical tag, including protocol, host, and path.`,impactIfIgnored:`Canonical signals are easier to misread when pages are copied, proxied, or rendered from alternate origins.`,howToVerify:`Re-run the crawl and confirm canonicalRaw starts with http:// or https://.`,agentHints:{evidenceFields:[`page.canonicalRaw`,`page.canonical`],suggestedCommands:[`seo crawl <url> --max-pages 1 --json`]}},{id:`canonical_chain`,title:`Canonical chain`,category:`canonical`,defaultSeverity:`medium`,whyItMatters:`A canonical that points to another URL which then canonicalizes again creates an indirect consolidation path.`,howToFix:`Point the first page directly at the final preferred URL, or make the intermediate canonical target self-referencing.`,impactIfIgnored:`Search engines may ignore or reinterpret the canonical path, especially at scale.`,howToVerify:`Re-run the crawl and confirm the canonical target is self-referencing.`,agentHints:{evidenceFields:[`page.canonical`,`issue.evidence.nextCanonical`,`issue.evidence.chain`],suggestedCommands:[`seo crawl <url> --json`]}},{id:`connection_error`,title:`Connection error`,category:`response`,defaultSeverity:`high`,whyItMatters:`A URL that cannot be fetched is invisible to users, search crawlers, and AI agents. It may be a DNS, TLS, timeout, firewall, or server availability problem.`,howToFix:`Check DNS, TLS certificates, firewall/CDN rules, server logs, and timeout behavior. Make the URL return a stable 2xx response or one direct redirect to the replacement.`,impactIfIgnored:`The page cannot be crawled, ranked, or cited while the connection failure remains.`,howToVerify:`Re-run the crawl and confirm the URL returns a normal HTTP status instead of status 0.`},{id:`client_error`,title:`Client error`,category:`response`,defaultSeverity:`high`,whyItMatters:`A 4xx URL is a dead end for users and search engines. Internal links to 4xx pages waste crawl attention and leak authority.`,howToFix:`Restore the page, update links to the correct URL, or add one direct 301 to the best replacement.`,impactIfIgnored:`Users hit broken pages and search engines may drop the URL or reduce trust in nearby internal links.`,howToVerify:`Re-run the crawl and confirm the URL returns a 2xx status or redirects once to a useful replacement.`},{id:`server_error`,title:`Server error`,category:`response`,defaultSeverity:`high`,whyItMatters:`A 5xx means the server failed to deliver the page. Persistent 5xx errors can slow crawling and remove pages from search.`,howToFix:`Check application logs, hosting health, upstream services, and cache/CDN rules. Return 200 when fixed, or 503 only for planned maintenance.`,impactIfIgnored:`Search engines may crawl less often and users cannot reach the affected pages.`,howToVerify:`Re-run the crawl and confirm the affected URL no longer returns a 5xx status.`},{id:`redirected_url`,title:`URL redirects`,category:`response`,defaultSeverity:`low`,whyItMatters:`This crawl reached the page through a redirect. A direct redirect can be intentional. It becomes worth fixing when internal links, canonicals, or sitemap entries still point to the old URL.`,howToFix:`Check why the old URL was requested. Update internal links, canonicals, and sitemap entries to the final URL when appropriate. Keep one direct 301 when the old URL must remain supported.`,impactIfIgnored:`An avoidable redirect adds a request and can turn into a chain later. An intentional direct redirect needs no change.`,howToVerify:`Re-run the crawl and confirm the requested URL is the same as the final URL, or that the redirect is intentional.`,agentHints:{evidenceFields:[`page.url`,`page.finalUrl`,`page.status`],suggestedCommands:[`seo redirect-trace --url <url> --json`]}},{id:`redirect_chain`,title:`Redirect chain`,category:`response`,defaultSeverity:`medium`,whyItMatters:`Multiple redirect hops slow users and crawlers, and each hop is another place a migration can break.`,howToFix:`Point internal links, canonicals, and sitemap URLs directly at the final destination. Keep only one necessary 301 from old URLs.`,impactIfIgnored:`Pages load slower, crawl paths are less clear, and future redirects can turn into loops or dead ends.`,howToVerify:`Re-run the crawl and confirm fetchDiagnostics.redirectChain has zero or one hop.`},{id:`slow_response`,title:`Slow response in this crawl`,category:`response`,defaultSeverity:`low`,whyItMatters:`The crawler observed a slow HTML response. One request is a diagnostic sample, not a field performance verdict, but repeated slow responses are worth investigating.`,howToFix:`Repeat the check and use the performance report before changing anything. If the delay persists, check server timing, cache headers, database queries, CDN caching, and origin health.`,impactIfIgnored:`Persistent slow HTML can delay users and crawlers. A one-off slow sample may be network or origin variance.`,howToVerify:`Re-run the crawl and compare repeated response times. Use field data where available for a user-experience decision.`},{id:`large_html`,title:`Large HTML response`,category:`performance`,defaultSeverity:`low`,whyItMatters:`Large HTML takes longer to download and parse before the browser can even start the rest of the page.`,howToFix:`Trim duplicated markup, remove heavy inline data, split non-critical content, and move large scripts or styles out of the HTML response.`,impactIfIgnored:`Pages can feel slow on mobile connections and crawlers spend more time processing each URL.`,howToVerify:`Re-run the crawl and confirm sizeBytes is below the large HTML threshold for normal pages.`,agentHints:{evidenceFields:[`page.sizeBytes`,`issue.evidence.thresholdBytes`],suggestedCommands:[`seo crawl <url> --json`]}},{id:`no_compression`,title:`No text compression`,category:`performance`,defaultSeverity:`low`,whyItMatters:`HTML without gzip or brotli sends more bytes than necessary and slows every first page load.`,howToFix:`Enable gzip or brotli for HTML and other text responses at the server, CDN, or reverse proxy.`,impactIfIgnored:`Users download more data, pages load slower, and the origin may spend more bandwidth than needed.`,howToVerify:`Re-run the crawl and confirm compression is br or gzip for sizeable HTML responses.`,agentHints:{evidenceFields:[`page.compression`,`page.sizeBytes`,`issue.evidence.thresholdBytes`],suggestedCommands:[`seo crawl <url> --json`]}},{id:`http_not_secure`,title:`Page is not served over HTTPS`,category:`security`,defaultSeverity:`medium`,whyItMatters:`HTTPS is a baseline trust signal. Browsers label HTTP pages as not secure, and users can be intercepted or downgraded.`,howToFix:`Install TLS, serve the page on HTTPS, and redirect the HTTP URL to the HTTPS version with a permanent redirect.`,impactIfIgnored:`Users see trust warnings, conversions suffer, and search quality signals are weaker.`,howToVerify:`Re-run the crawl and confirm finalUrl starts with https:// and isHttps is true.`},{id:`mixed_content`,title:`Mixed content`,category:`security`,defaultSeverity:`medium`,whyItMatters:`An HTTPS page that loads HTTP resources can trigger browser warnings or blocked images, scripts, fonts, or media.`,howToFix:`Change insecure resource URLs to HTTPS, update CDN origins, or remove resources that are no longer available securely.`,impactIfIgnored:`Parts of the page may break, users lose trust, and the security guarantee of HTTPS is weakened.`,howToVerify:`Re-run the crawl and confirm mixedContentCount is zero.`,agentHints:{evidenceFields:[`page.mixedContentSamples`,`issue.evidence.mixedContentSamples`],suggestedCommands:[`seo crawl <url> --json`]}},{id:`hsts_missing`,title:`HSTS header missing`,category:`security`,defaultSeverity:`low`,whyItMatters:`HSTS tells browsers to keep using HTTPS, which reduces downgrade risk after the first secure visit.`,howToFix:`Send a Strict-Transport-Security header with a suitable max-age once the whole site is stable on HTTPS.`,impactIfIgnored:`Repeat visitors have a larger downgrade window if a network or link tries to send them to HTTP.`,howToVerify:`Re-run the crawl and confirm hasHsts is true for HTTPS pages.`},{id:`broken_internal_link`,title:`Broken internal link`,category:`links`,defaultSeverity:`high`,whyItMatters:`Internal links that land on broken URLs waste crawl paths, frustrate users, and leak authority inside the site.`,howToFix:`Update every internal link to the working destination, restore the missing page, or add one direct 301 to the best replacement.`,impactIfIgnored:`Users hit dead ends and search engines keep discovering broken URLs from your own pages.`,howToVerify:`Re-run the crawl and confirm the linked URL no longer returns a broken status.`},{id:`broken_external_link`,title:`Broken external link`,category:`links`,defaultSeverity:`medium`,whyItMatters:`External links are part of the page experience and source trail. Broken references make content feel stale and less trustworthy.`,howToFix:`Replace the link with a working source, update the URL, or remove the reference if it no longer supports the page.`,impactIfIgnored:`Readers and agents following citations hit dead ends, which weakens trust in the content.`,howToVerify:`Re-run the crawl with external checks enabled and confirm the URL no longer has a confirmed-broken externalLinkChecks result.`},{id:`orphan_page`,title:`No observed internal links`,category:`links`,defaultSeverity:`low`,whyItMatters:`This crawl did not find an internal link to the page. That is worth checking, but a partial crawl cannot prove that the page is a true sitewide orphan.`,howToFix:`Check the page in a complete crawl and in the sitemap. If it is meant to be discovered, add a relevant link from a hub, navigation path, related page, or template.`,impactIfIgnored:`A genuinely unlinked page can be hard for users and crawlers to find. A page omitted by the crawl needs no change yet.`,howToVerify:`Re-run a complete crawl from the same site entry point and confirm an observed internal link reaches the page.`},{id:`deep_page`,title:`Deep crawl path`,category:`links`,defaultSeverity:`low`,whyItMatters:`This crawl reached the page several links away from its start URL. A capped crawl or a non-homepage start URL does not prove the shortest sitewide path.`,howToFix:`Check whether the page is important and whether a shorter user path makes sense. Add a link from a closer hub, category page, related content block, or navigation path only when it helps users.`,impactIfIgnored:`A genuinely deep important page can be harder to reach. Treat this as a crawl-path review, not a sitewide architecture verdict.`,howToVerify:`Re-run a complete crawl from the same start URL and confirm crawlDepth changed after a justified link update.`},{id:`weak_internal_links_to_valuable_page`,title:`Review internal links to a valuable page`,category:`links`,defaultSeverity:`low`,whyItMatters:`The page has observed search or analytics value but few inlinks within this crawl. It is a review cue, not proof that the page lacks enough internal authority.`,howToFix:`Check the page's role and current navigation first. Add contextual links from relevant hubs, templates, or related pages only when they help users reach it.`,impactIfIgnored:`A valuable page may be harder to find if it is genuinely underlinked. Do not treat the observed count as a ranking forecast.`,howToVerify:`Re-run the same crawl after a justified link change and confirm the observed inlink count changed.`},{id:`missing_meta_description`,title:`Meta description missing`,category:`metadata`,defaultSeverity:`medium`,whyItMatters:`The meta description is often used as search-result copy. Without it, search engines choose their own snippet.`,howToFix:`Write a concise, unique description that explains the page value and matches the main search intent.`,impactIfIgnored:`Search snippets may be generic or less persuasive, especially on pages that already rank.`,howToVerify:`Re-run the crawl and confirm the page has a meta description.`},{id:`meta_description_duplicate`,title:`Duplicate meta description`,category:`metadata`,defaultSeverity:`medium`,whyItMatters:`Repeated descriptions make different pages look interchangeable and can weaken snippet quality across a section.`,howToFix:`Write a distinct description for each page that explains what is unique about that URL.`,impactIfIgnored:`Search snippets stay generic, and search engines may rewrite them or struggle to distinguish similar pages.`,howToVerify:`Re-run the crawl and confirm this description appears on only one URL.`,agentHints:{evidenceFields:[`page.metaDescription`,`issue.evidence.duplicateCount`,`issue.evidence.sampleUrls`],suggestedCommands:[`seo crawl <url> --json`]}},{id:`canonical_missing`,title:`No canonical declared`,category:`canonical`,defaultSeverity:`low`,whyItMatters:`The page did not declare a canonical. That is often fine for a unique URL. It matters when parameter, protocol, pagination, or duplicate-content variants need a preferred URL.`,howToFix:`First check whether equivalent URL variants exist. Add a self-referencing canonical only when the site uses canonical tags as part of a deliberate duplicate-URL strategy.`,impactIfIgnored:`A unique page may need no action. If duplicate variants exist, search engines can choose a different preferred URL.`,howToVerify:`Re-run the crawl and confirm the canonical field is present and points to the preferred URL.`},{id:`noindex`,title:`Noindex found`,category:`indexability`,defaultSeverity:`medium`,whyItMatters:`Noindex tells search engines not to show the page in search results. It is useful when intentional and risky when accidental.`,howToFix:`Remove the noindex directive if the page should rank. Leave it in place only for intentionally private or low-value pages.`,impactIfIgnored:`The page cannot earn organic search traffic while the noindex directive remains.`,howToVerify:`Re-run the crawl or URL Inspection and confirm no effective meta robots or X-Robots-Tag noindex remains, including the equivalent none directive.`},{id:`nofollow`,title:`Nofollow found`,category:`indexability`,defaultSeverity:`low`,whyItMatters:`Nofollow asks crawlers not to follow links from the page. On important pages, that can block discovery and weaken internal authority flow.`,howToFix:`Remove nofollow from meta robots or X-Robots-Tag unless the page is intentionally isolated from link discovery.`,impactIfIgnored:`Internal links on the page may pass weaker discovery and authority signals.`,howToVerify:`Re-run the crawl and confirm the effective meta robots and X-Robots-Tag rules no longer block following, including through the equivalent none directive.`,agentHints:{evidenceFields:[`page.metaRobots`,`page.xRobotsTag`],suggestedCommands:[`seo crawl <url> --max-pages 1 --json`]}},{id:`x_robots_noindex`,title:`X-Robots-Tag noindex`,category:`indexability`,defaultSeverity:`medium`,whyItMatters:`An X-Robots-Tag noindex header can remove a page from search even when the HTML looks indexable.`,howToFix:`Remove the noindex directive from server, CDN, or framework response headers if the page should rank.`,impactIfIgnored:`The page remains excluded from search results while the header is present.`,howToVerify:`Re-run the crawl and confirm the effective X-Robots-Tag rules no longer block indexing, including through the equivalent none directive.`,agentHints:{evidenceFields:[`page.xRobotsTag`,`page.responseHeaders`],suggestedCommands:[`seo crawl <url> --max-pages 1 --json`]}},{id:`robots_blocked`,title:`Blocked by robots.txt`,category:`indexability`,defaultSeverity:`medium`,whyItMatters:`Robots.txt blocking prevents crawlers from fetching the URL. If accidental, search engines and AI systems cannot evaluate the page content.`,howToFix:`Update robots.txt so important public pages are allowed, or leave the block in place only for intentionally private or low-value paths.`,impactIfIgnored:`The page may stay uncrawled, stale, or missing from search and AI retrieval systems.`,howToVerify:`Re-run the crawl and confirm robotsTxt.allowed is true for the URL.`,agentHints:{evidenceFields:[`page.robotsTxt`],suggestedCommands:[`seo crawl <url> --json`]}},{id:`canonicalized_page`,title:`Canonicalized page`,category:`indexability`,defaultSeverity:`low`,whyItMatters:`A canonicalized page tells search engines another URL should receive the ranking signals. That is fine when intentional and risky when accidental.`,howToFix:`If this URL should rank, make the canonical self-referencing. If another URL is preferred, make internal links and sitemap entries point to that preferred URL.`,impactIfIgnored:`The page may be excluded from search in favor of the canonical target.`,howToVerify:`Re-run the crawl and confirm the canonical target matches the intended indexable URL.`,agentHints:{evidenceFields:[`page.canonical`,`page.finalUrl`,`page.indexability`],suggestedCommands:[`seo crawl <url> --max-pages 1 --json`]}},{id:`duplicate_content`,title:`Duplicate content`,category:`content`,defaultSeverity:`medium`,whyItMatters:`Pages with the same main content force search engines to choose a winner and can split internal signals across duplicates.`,howToFix:`Consolidate duplicate pages with redirects or canonicals, or make each page meaningfully different for its own intent.`,impactIfIgnored:`The wrong URL may rank, similar pages may underperform, and AI systems may cite a less useful duplicate.`,howToVerify:`Re-run the crawl and confirm the duplicate mainContentHash appears on only one URL.`,agentHints:{evidenceFields:[`page.mainContentHash`,`issue.evidence.duplicateCount`,`issue.evidence.sampleUrls`],suggestedCommands:[`seo crawl <url> --json`]}},{id:`query_coverage_missing`,title:`Review top-query text coverage`,category:`content`,defaultSeverity:`low`,whyItMatters:`The top reported query has terms that were not found in the title, description, H1, or retained content sample. Exact terms alone do not prove relevance, and the sample cannot assess every section or useful synonym.`,howToFix:`Read the page and the search results before changing copy. Add or clarify wording only when it makes the page answer the query better. Do not force exact terms into a page that already serves the intent.`,impactIfIgnored:`Use this as a review prompt. It does not predict rankings, clicks, or a need to rewrite the page.`,howToVerify:`Re-run the crawl with the same GSC property and review the query, sampled text, and any deliberate wording changes together.`,agentHints:{evidenceFields:[`page.topQuery`,`issue.evidence.missingTerms`,`issue.evidence.coverage`],suggestedCommands:[`seo crawl <url> --project <id> --json`]}},{id:`image_missing_alt`,title:`Images lack alt attributes`,category:`images`,defaultSeverity:`medium`,whyItMatters:`An image with no alt attribute gives screen readers no declared text alternative. A deliberately empty alt attribute is valid for decorative images and is not included here.`,howToFix:`Add concise alt text to meaningful images that currently have no alt attribute. Keep alt="" on decorative images.`,impactIfIgnored:`People using screen readers may miss the purpose of a meaningful image, and search engines have less image context.`,howToVerify:`Re-run the crawl and confirm imagesMissingAlt is zero. Decorative images can keep an empty alt attribute.`},{id:`image_oversized_candidate`,title:`Oversized image candidates`,category:`images`,defaultSeverity:`low`,whyItMatters:`Very large images can slow pages down, especially when the browser only needs a small rendered size.`,howToFix:`Resize the source image, add responsive srcset sizes, and serve modern compressed formats such as WebP or AVIF where supported.`,impactIfIgnored:`Users may download more image data than needed, which hurts load speed and wastes bandwidth.`,howToVerify:`Re-run the crawl and confirm oversizedImageCandidates is empty, then spot-check the page in a browser performance trace if the image is important.`,agentHints:{evidenceFields:[`page.oversizedImageCandidates`,`issue.evidence.thresholdPx`,`issue.evidence.candidates`],suggestedCommands:[`seo crawl <url> --max-pages 10 --json`]}},{id:`viewport_missing`,title:`Viewport missing`,category:`mobile`,defaultSeverity:`medium`,whyItMatters:`The viewport tag lets mobile browsers render responsive layouts correctly.`,howToFix:'Add `<meta name="viewport" content="width=device-width, initial-scale=1">` to the document head.',impactIfIgnored:`Mobile users may get a zoomed-out desktop page and mobile-first search quality can suffer.`,howToVerify:`Re-run the crawl and confirm hasViewport is true.`},{id:`lang_missing`,title:`Language missing`,category:`international`,defaultSeverity:`low`,whyItMatters:`The html lang attribute helps browsers, screen readers, search engines, and translators understand the page language.`,howToFix:'Set the language on the root html element, for example `<html lang="en">`.',impactIfIgnored:`Accessibility and language targeting signals are weaker.`,howToVerify:`Re-run the crawl and confirm the lang field is populated.`},{id:`hreflang_invalid`,title:`Malformed hreflang values`,category:`international`,defaultSeverity:`low`,whyItMatters:`These hreflang values do not match the basic language or language-region shape. This check does not validate every officially supported language or region code.`,howToFix:`Use a supported language or language-region code such as en, en-gb, fr-ca, or x-default. Check the current Google hreflang documentation before changing a locale set.`,impactIfIgnored:`Search engines may ignore malformed annotations and show a less relevant regional page.`,howToVerify:`Re-run the crawl and confirm no hreflang values are marked malformed. Validate the full locale set against Google documentation.`,agentHints:{evidenceFields:[`page.hreflang`,`issue.evidence.malformed`],suggestedCommands:[`seo crawl <url> --json`]}},{id:`hreflang_duplicate`,title:`Repeated hreflang values`,category:`international`,defaultSeverity:`low`,whyItMatters:`Repeated hreflang declarations make a locale set harder to review. They only conflict when the same language code points to different URLs.`,howToFix:`Keep one hreflang declaration per language code. If repeated codes point to different URLs, decide which page is the intended locale target.`,impactIfIgnored:`Conflicting duplicate targets may be ignored. Identical repeats are usually cleanup work, not a search emergency.`,howToVerify:`Re-run the crawl and confirm each hreflang code appears only once per page.`,agentHints:{evidenceFields:[`page.hreflang`,`issue.evidence.duplicateCodes`],suggestedCommands:[`seo crawl <url> --json`]}},{id:`hreflang_incomplete`,title:`Hreflang missing self reference`,category:`international`,defaultSeverity:`low`,whyItMatters:`A page that declares hreflang should include an alternate that points back to its own final URL. An x-default entry does not replace that self reference.`,howToFix:`Add one hreflang entry that points to this page's final URL. Keep x-default as an additional fallback only where it is useful.`,impactIfIgnored:`Search engines may treat the hreflang set as incomplete and show a less relevant language variant.`,howToVerify:`Re-run the crawl and confirm one hreflang href matches the fetched final URL.`,agentHints:{evidenceFields:[`page.lang`,`page.hreflang`],suggestedCommands:[`seo crawl <url> --json`]}},{id:`jsonld_invalid`,title:`Invalid JSON-LD`,category:`structured-data`,defaultSeverity:`medium`,whyItMatters:`Broken JSON-LD can make structured data unreadable even when the right schema was intended.`,howToFix:`Fix the JSON syntax in each application/ld+json script. Validate quotes, commas, braces, and any server-rendered values inserted into the block.`,impactIfIgnored:`Consumers cannot reliably read the invalid block, and Google cannot use unreadable markup for supported Search features.`,howToVerify:`Re-run the crawl and confirm invalidJsonLdCount is zero, then validate the page with a structured data tester.`,agentHints:{evidenceFields:[`page.invalidJsonLdSamples`,`issue.evidence.invalidJsonLdSamples`],suggestedCommands:[`seo crawl <url> --max-pages 1 --json`]}},{id:`rich_result_required_fields_missing`,title:`Required rich-result properties are missing`,category:`structured-data`,defaultSeverity:`medium`,whyItMatters:`Schema.org vocabulary and valid JSON syntax do not make markup eligible for a Google rich result when documented required properties are absent.`,howToFix:`Add the missing properties only when they accurately represent visible page content. Follow the linked Google feature documentation and validate the result.`,impactIfIgnored:`Google can understand some of the markup while still treating the item as ineligible for that enhanced Search feature.`,howToVerify:`Re-run the crawl, review googleRichResults, and confirm no supported item reports missing-required-properties. Then use Google’s Rich Results Test.`,agentHints:{evidenceFields:[`page.googleRichResults`,`issue.evidence.assessments`],suggestedCommands:[`seo crawl <url> --max-pages 1 --json`]}},{id:`og_title_missing`,title:`Open Graph title missing`,category:`social`,defaultSeverity:`low`,whyItMatters:`Open Graph tags control how pages appear in social feeds, chat previews, and many sharing surfaces.`,howToFix:`Add an og:title that matches the page title or a share-friendly version of it.`,impactIfIgnored:`Shared links can look generic, reducing clicks from social and messaging apps.`,howToVerify:`Re-run the crawl and confirm openGraphTitle is present.`},{id:`og_description_missing`,title:`Open Graph description missing`,category:`social`,defaultSeverity:`low`,whyItMatters:`The Open Graph description is often the short pitch shown when a page is shared in chat, Slack, social feeds, and link previews.`,howToFix:`Add an og:description that briefly explains why the page is worth opening.`,impactIfIgnored:`Shared links can appear vague or empty, which reduces clicks from discovery surfaces.`,howToVerify:`Re-run the crawl and confirm openGraphDescription is present.`},{id:`og_image_missing`,title:`Open Graph image missing`,category:`social`,defaultSeverity:`low`,whyItMatters:`An Open Graph image helps shared links stand out and makes previews feel trustworthy and complete.`,howToFix:`Add an og:image URL with a representative image that crawlers can fetch over HTTPS.`,impactIfIgnored:`Shared links may render without a visual preview, reducing click appeal.`,howToVerify:`Re-run the crawl and confirm openGraphImage is present.`},{id:`twitter_card_missing`,title:`No Twitter card declared`,category:`social`,defaultSeverity:`low`,whyItMatters:`A Twitter card tells X which preview format to use. X can sometimes fall back to Open Graph metadata, so this is a sharing observation, not a search issue.`,howToFix:`Add twitter:card and matching metadata only when you need a specific X preview. Keep Open Graph metadata accurate either way.`,impactIfIgnored:`X may use fallback metadata or a simple link preview. Organic rankings are not affected.`,howToVerify:`Re-run the crawl and confirm twitterCard is present if a specific X preview is required.`}],lx={title_too_wide:`review`,canonical_missing:`review`,canonical_multiple:`review`,canonical_mismatch:`review`,canonical_non_absolute:`review`,canonicalized_page:`review`,noindex:`review`,nofollow:`review`,x_robots_noindex:`review`,robots_blocked:`review`,orphan_page:`review`,redirected_url:`review`,slow_response:`review`,image_oversized_candidate:`review`,hsts_missing:`review`,og_title_missing:`review`,og_description_missing:`review`,og_image_missing:`review`,twitter_card_missing:`review`},ux=new Map(cx.map(e=>[e.id,{...e,recommendation:lx[e.id]??`fix`}]));function dx(){return[...ux.values()]}function fx(e){return ux.get(e)}function px(e){return fx(e)?.recommendation??`fix`}function mx(e){if(e)try{let t=new URL(e);return t.hash=``,t.toString()}catch{return e}}function hx(e){let t=mx(e.url),n=mx(e.finalUrl);return!!(t&&n&&t!==n)}function gx(e){return e.status>=200&&e.status<300}function _x(e){return/^\s*(?:text\/html|application\/xhtml\+xml)\b/i.test(e.contentType??``)}function vx(e){let t=e.contentAuditAllowed??(!e.blocked&&e.robotsTxt?.allowed!==!1);return gx(e)&&_x(e)&&!hx(e)&&t}function yx(e,t){if(!e||!t)return!1;try{let n=new URL(e),r=new URL(t);return n.hash=``,r.hash=``,n.toString()===r.toString()}catch{return e===t}}function bx(e){try{let t=new URL(e??``);return t.protocol===`http:`||t.protocol===`https:`}catch{return!1}}function xx(e){if(e)try{let t=new URL(e);return t.hash=``,t.toString()}catch{return}}const Sx=2e3,Cx=2*1024*1024,wx=4*1024,Tx=.6;function X(e,t,n,r){let i=fx(e);if(!i)throw Error(`Missing rule guidance for ${e}`);return{ruleId:e,title:i.title,category:i.category,severity:i.defaultSeverity,url:t.url,detail:n,evidence:r,searchMetrics:t.searchMetrics}}function Ex(e,t,n,r){let i=fx(e);if(!i)throw Error(`Missing rule guidance for ${e}`);return{ruleId:e,title:i.title,category:i.category,severity:i.defaultSeverity,url:t.requestedUrl,detail:n,evidence:r}}function Dx(e){return e===0||e===404||e===410||(e??0)>=500}function Ox(e){let t=e.trim().toLowerCase();return t===`x-default`||/^[a-z]{2}(?:-[a-z]{4})?(?:-[a-z]{2})?$/.test(t)}function kx(e){return(e.searchMetrics?.clicks??0)>0||(e.searchMetrics?.impressions??0)>=100||(e.analytics?.sessions??0)>=25}function Ax(e){return e?.trim().replace(/\s+/g,` `).toLowerCase()||void 0}function jx(e,t){let n=new Map;for(let r of e){if(!vx(r))continue;let e=t(r)?.trim(),i=Ax(e);if(!i||!e)continue;let a=n.get(i)??{count:0,sampleUrls:[],value:e};a.count+=1,a.sampleUrls.length<10&&a.sampleUrls.push(r.url),n.set(i,a)}return new Map([...n].filter(([,e])=>e.count>1))}function Mx(e){let t=new Map;for(let n of e){if(!vx(n)||n.wordCount<20||!n.mainContentHash)continue;let e=t.get(n.mainContentHash)??{count:0,sampleUrls:[]};e.count+=1,e.sampleUrls.length<10&&e.sampleUrls.push(n.url),t.set(n.mainContentHash,e)}return new Map([...t].filter(([,e])=>e.count>1))}const Nx=new Set([`a`,`an`,`and`,`are`,`for`,`from`,`how`,`in`,`is`,`of`,`on`,`or`,`the`,`to`,`vs`,`what`,`with`]);function Px(e){return[...new Set(e.toLowerCase().split(/[^a-z0-9]+/).filter(e=>e.length>2&&!Nx.has(e)))]}function Fx(e){let t=e.topQuery?.query;if(!t||(e.topQuery?.impressions??0)<50)return;let n=Px(t);if(!n.length)return;let r=[e.title,e.metaDescription,e.h1,e.contentSample].filter(Boolean).join(` `).toLowerCase(),i=n.filter(e=>r.includes(e));return{query:t,matchedTerms:i,missingTerms:n.filter(e=>!r.includes(e)),coverage:i.length/n.length}}function Ix(e){let t=[];for(let n of e){if(n.outcome===`failure`){if(n.failureKind===`aborted`)continue;t.push(Ex(`connection_error`,n,n.error,{outcome:n.outcome,failureKind:n.failureKind,durationMs:n.durationMs,error:n.error}));continue}if(n.outcome===`skipped`)continue;let e=n.redirectChain??[];(e.length>0||!yx(n.requestedUrl,n.finalUrl))&&t.push(Ex(`redirected_url`,n,n.finalUrl?`Final URL: ${n.finalUrl}`:void 0,{requestedUrl:n.requestedUrl,finalUrl:n.finalUrl,status:e[0]?.status,finalStatus:n.status})),e.length>1&&t.push(Ex(`redirect_chain`,n,`${e.length} hops`,{redirectChain:e,hops:e.length}))}return t}function Lx(e,t={}){let n=[],r=jx(e,e=>e.title),i=jx(e,e=>e.metaDescription),a=Mx(e),o=new Map;for(let t of e)if(vx(t))for(let e of[t.url,t.finalUrl]){let n=xx(e);n&&o.set(n,t)}for(let s of e){if(s.status===0){n.push(X(`connection_error`,s,s.error,{status:s.status,error:s.error}));continue}if(s.status>=500){n.push(X(`server_error`,s,String(s.status),{status:s.status})),(s.internalInlinkCount??0)>0&&n.push(X(`broken_internal_link`,s,String(s.status),{status:s.status,internalInlinkCount:s.internalInlinkCount}));continue}if(s.status>=400){n.push(X(`client_error`,s,String(s.status),{status:s.status})),Dx(s.status)&&(s.internalInlinkCount??0)>0&&n.push(X(`broken_internal_link`,s,String(s.status),{status:s.status,internalInlinkCount:s.internalInlinkCount}));continue}if(s.status>=300){n.push(X(`redirected_url`,s,`Status ${s.status}`,{requestedUrl:s.url,finalUrl:s.finalUrl,status:s.status}));continue}if(s.status<200)continue;let e=hx(s);e&&n.push(X(`redirected_url`,s,`Final URL: ${s.finalUrl}`,{requestedUrl:s.url,finalUrl:s.finalUrl,status:s.status}));let c=s.fetchDiagnostics?.redirectChain??[];if(c.length>1&&n.push(X(`redirect_chain`,s,`${c.length} hops`,{redirectChain:c,hops:c.length})),(s.responseTimeMs??0)>Sx&&n.push(X(`slow_response`,s,`${s.responseTimeMs}ms`,{responseTimeMs:s.responseTimeMs,thresholdMs:Sx})),e)continue;_x(s)&&(s.sizeBytes??0)>Cx&&n.push(X(`large_html`,s,`${Math.round((s.sizeBytes??0)/1024)} KB`,{sizeBytes:s.sizeBytes,thresholdBytes:Cx})),_x(s)&&!s.compression&&(s.sizeBytes??0)>wx&&n.push(X(`no_compression`,s,void 0,{sizeBytes:s.sizeBytes,thresholdBytes:wx,compression:s.compression})),s.isHttps===!1?n.push(X(`http_not_secure`,s,void 0,{finalUrl:s.finalUrl})):s.isHttps===!0&&((s.mixedContentCount??0)>0&&n.push(X(`mixed_content`,s,`${s.mixedContentCount} insecure resources`,{mixedContentCount:s.mixedContentCount,mixedContentSamples:s.mixedContentSamples})),s.hasHsts===!1&&n.push(X(`hsts_missing`,s,void 0,{header:`strict-transport-security`})));for(let e of s.externalLinkChecks??[])e.state===`confirmed-broken`&&n.push(X(`broken_external_link`,s,e.url,{url:e.url,status:e.status,error:e.error,state:e.state,attempts:e.attempts}));s.indexable&&(s.internalInlinkCount??0)===0&&!yx(s.url,t.startUrl)&&n.push(X(`orphan_page`,s,`No internal inlinks`,{internalInlinkCount:s.internalInlinkCount??0})),(s.crawlDepth??0)>4&&n.push(X(`deep_page`,s,`Depth ${s.crawlDepth}`,{crawlDepth:s.crawlDepth,threshold:4})),s.indexable&&kx(s)&&(s.internalInlinkCount??0)<=1&&n.push(X(`weak_internal_links_to_valuable_page`,s,`${s.internalInlinkCount??0} internal inlinks`,{internalInlinkCount:s.internalInlinkCount??0,searchMetrics:s.searchMetrics,analytics:s.analytics})),s.robotsTxt?.allowed===!1&&n.push(X(`robots_blocked`,s,s.robotsTxt.matchedLine,{robotsTxt:s.robotsTxt})),mm(s.xRobotsTag,`noindex`)&&n.push(X(`x_robots_noindex`,s,s.xRobotsTag,{xRobotsTag:s.xRobotsTag}));let l=mm(s.xRobotsTag,`nofollow`);if(l&&n.push(X(`nofollow`,s,s.xRobotsTag,{xRobotsTag:s.xRobotsTag})),!vx(s))continue;let u=s.title?.trim(),d=u?A_(u):void 0,f=r.get(Ax(u)??``);u?d?.status===`over-reference`&&n.push(X(`title_too_wide`,s,`${d.estimatedPixels}px`,{title:u,...d})):n.push(X(`missing_title`,s)),u&&f&&n.push(X(`title_duplicate`,s,f.value,{title:u,duplicateCount:f.count,sampleUrls:f.sampleUrls}));let p=s.metaDescription?.trim(),m=i.get(Ax(p)??``);p||n.push(X(`missing_meta_description`,s)),p&&m&&n.push(X(`meta_description_duplicate`,s,m.value,{metaDescription:p,duplicateCount:m.count,sampleUrls:m.sampleUrls}));let h=s.h1Count??+!!s.h1;if(h===0&&n.push(X(`h1_missing`,s,`No H1 found`,{h1Count:h})),s.canonicalStatus===`conflicting`?n.push(X(`canonical_conflict`,s,void 0,{candidates:s.canonicalCandidates})):s.canonicalStatus===`duplicate`?n.push(X(`canonical_multiple`,s,s.canonical,{canonical:s.canonical,candidates:s.canonicalCandidates})):s.canonicalStatus===`outside-head-only`&&n.push(X(`canonical_outside_head`,s,s.canonicalRaw,{candidates:s.canonicalCandidates})),!s.canonical&&![`conflicting`,`outside-head-only`].includes(s.canonicalStatus??``))n.push(s.canonicalRaw?X(`canonical_invalid`,s,s.canonicalRaw,{canonicalRaw:s.canonicalRaw}):X(`canonical_missing`,s));else{s.canonicalRaw&&!bx(s.canonicalRaw)&&n.push(X(`canonical_non_absolute`,s,s.canonicalRaw,{canonicalRaw:s.canonicalRaw,canonical:s.canonical}));let e=o.get(xx(s.canonical)??``);e?.canonical&&!yx(e.canonical,s.canonical)&&n.push(X(`canonical_chain`,s,s.canonical,{canonical:s.canonical,nextCanonical:e.canonical,chain:[s.finalUrl,s.canonical,e.canonical]}))}pm(s.metaRobots,`noindex`)&&n.push(X(`noindex`,s,s.indexability,{metaRobots:s.metaRobots})),pm(s.metaRobots,`nofollow`)&&!l&&n.push(X(`nofollow`,s,s.metaRobots,{metaRobots:s.metaRobots})),s.canonical&&!yx(s.canonical,s.finalUrl)&&(s.declaredIndexability===`canonical-hint-other`||s.declaredIndexability===void 0&&/canonical/i.test(s.indexability??``))?n.push(X(`canonicalized_page`,s,s.canonical,{canonical:s.canonical,finalUrl:s.finalUrl,indexability:s.indexability})):s.canonical&&!yx(s.canonical,s.finalUrl)&&n.push(X(`canonical_mismatch`,s,s.canonical,{canonical:s.canonical,finalUrl:s.finalUrl}));let g=s.mainContentHash?a.get(s.mainContentHash):void 0;g&&n.push(X(`duplicate_content`,s,`Duplicate main content`,{mainContentHash:s.mainContentHash,duplicateCount:g.count,sampleUrls:g.sampleUrls}));let _=Fx(s);_&&_.coverage<Tx&&n.push(X(`query_coverage_missing`,s,_.query,{query:_.query,impressions:s.topQuery?.impressions,matchedTerms:_.matchedTerms,missingTerms:_.missingTerms,coverage:_.coverage,threshold:Tx})),(s.imagesMissingAlt??0)>0&&n.push(X(`image_missing_alt`,s,`${s.imagesMissingAlt} of ${s.imagesTotal??0} images`,{imagesTotal:s.imagesTotal,imagesMissingAlt:s.imagesMissingAlt})),s.oversizedImageCandidates?.length&&n.push(X(`image_oversized_candidate`,s,`${s.oversizedImageCandidates.length} image candidate${s.oversizedImageCandidates.length===1?``:`s`}`,{thresholdPx:2e3,candidates:s.oversizedImageCandidates.slice(0,10)})),s.hasViewport||n.push(X(`viewport_missing`,s)),s.lang||n.push(X(`lang_missing`,s));let v=s.hreflang??[];if(v.length){let e=v.filter(e=>!Ox(e.hreflang));e.length&&n.push(X(`hreflang_invalid`,s,`${e.length} malformed`,{malformed:e}));let t=[...new Set(v.map(e=>e.hreflang.trim().toLowerCase()).filter((e,t,n)=>n.indexOf(e)!==t))];t.length&&n.push(X(`hreflang_duplicate`,s,t.join(`, `),{duplicateCodes:t})),v.some(e=>yx(e.href,s.finalUrl))||n.push(X(`hreflang_incomplete`,s,s.lang,{lang:s.lang,finalUrl:s.finalUrl,hreflang:v}))}(s.invalidJsonLdCount??0)>0&&n.push(X(`jsonld_invalid`,s,`${s.invalidJsonLdCount} invalid`,{invalidJsonLdCount:s.invalidJsonLdCount,invalidJsonLdSamples:s.invalidJsonLdSamples}));let y=(s.googleRichResults??[]).filter(e=>e.status===`missing-required-properties`),b=s.googleRichResultsSelection?.eligibleByStatus[`missing-required-properties`]??0,x=Math.max(y.length,b);if(x){let e=y.map(e=>`${e.schemaType}: ${e.missingRequiredProperties.join(`, `)}`),t=x-e.length;n.push(X(`rich_result_required_fields_missing`,s,[...e,...t>0?[`${t} more incomplete assessment${t===1?``:`s`} omitted from bounded detail`]:[]].join(`; `),{assessments:y,...s.googleRichResultsSelection?{selection:s.googleRichResultsSelection}:{}}))}s.openGraphTitle||n.push(X(`og_title_missing`,s)),s.openGraphDescription||n.push(X(`og_description_missing`,s)),s.openGraphImage||n.push(X(`og_image_missing`,s)),s.twitterCard||n.push(X(`twitter_card_missing`,s))}return n}const Rx=[`asset-url`,`configured-exclusion`,`duplicate-normalization`,`invalid-url`,`off-origin`,`origin-backpressure`,`queue-safety-limit`,`robots-disallowed`,`robots-uncertain`,`legacy-unclassified`],zx={"asset-url":`non-impacting`,"configured-exclusion":`non-impacting`,"duplicate-normalization":`non-impacting`,"invalid-url":`non-impacting`,"off-origin":`non-impacting`,"origin-backpressure":`coverage-affecting`,"queue-safety-limit":`coverage-affecting`,"robots-disallowed":`coverage-affecting`,"robots-uncertain":`coverage-affecting`,"legacy-unclassified":`coverage-affecting`},Bx=new Map(Rx.map((e,t)=>[e,t]));function Vx(e){return typeof e==`string`&&Rx.includes(e)}function Hx(e){return typeof e==`number`&&Number.isSafeInteger(e)&&e>0?e:0}function Ux(e){return zx[e]}function Wx(e){let t=new Map;if(Array.isArray(e.skipReasons))for(let n of e.skipReasons){if(!n||typeof n!=`object`)continue;let{reason:e,count:r}=n;if(!Vx(e))continue;let i=Hx(r);i&&t.set(e,(t.get(e)??0)+i)}let n=Hx(e.skippedUrls),r=[...t.values()].reduce((e,t)=>e+t,0);n>r&&t.set(`legacy-unclassified`,(t.get(`legacy-unclassified`)??0)+n-r);let i=[...t.entries()].map(([e,t])=>({reason:e,impact:Ux(e),count:t})).sort((e,t)=>(Bx.get(e.reason)??2**53-1)-(Bx.get(t.reason)??2**53-1)),a=i.reduce((e,t)=>(t.impact===`coverage-affecting`?e.coverageAffecting+=t.count:e.nonImpacting+=t.count,e),{coverageAffecting:0,nonImpacting:0});return{skippedUrls:a.coverageAffecting+a.nonImpacting,skipReasons:i,skippedUrlsByImpact:a}}function Gx(e={}){return Wx({skipReasons:Object.entries(e).map(([e,t])=>({reason:e,count:t}))}).skipReasons}const Kx=new Set([`authorization`,`cookie`,`proxy-authorization`,`set-cookie`,`www-authenticate`,`x-api-key`,`api-key`,`access-token`,`refresh-token`]),qx=/^(access_?token|auth|api_?key|client_?secret|code|jwt|key|password|refresh_?token|secret|session|sig|signature|token)$/i;function Jx(e=[]){return[...new Set(e.filter(Boolean))].sort()}function Yx(e){return{concurrency:e.fetchRate?.concurrency??e.concurrency??8,...e.fetchRate?.intervalCap===void 0?{}:{intervalCap:e.fetchRate.intervalCap},...e.fetchRate?.intervalMs===void 0?{}:{intervalMs:e.fetchRate.intervalMs},...e.fetchRate?.backpressure?{backpressure:e.fetchRate.backpressure}:{}}}function Xx(e){return{url:new URL(e.url).toString(),mode:e.mode??`site`,urls:Jx(e.urls??[]),maxPages:e.maxPages??500,maxDepth:e.maxDepth??16,concurrency:e.concurrency??8,timeoutMs:e.timeoutMs??2e4,include:Jx(e.include??[]),exclude:Jx(e.exclude??[]),respectRobots:e.respectRobots??!0,useSitemap:e.useSitemap??!0,checkExternal:e.checkExternal??!0,checkAgentDiscovery:e.checkAgentDiscovery??!1,js:Rg(e.js),refresh:e.refresh??!1,fetchRate:Yx(e)}}function Zx(e){let t=Xx(e);return y(`sha256`).update(JSON.stringify(t)).digest(`hex`).slice(0,16)}function Qx(e){let t={config:Xx(e.config),site:e.site??null,googleAnalyticsPropertyId:e.googleAnalyticsPropertyId??null};return`crawl_def_${y(`sha256`).update(JSON.stringify(t)).digest(`hex`).slice(0,20)}`}function $x(){return`crawl_${b().replaceAll(`-`,``)}`}function eS(e){let t=new Map;for(let n of e){let e=t.get(n.ruleId)??{ruleId:n.ruleId,title:n.title,category:n.category,severity:n.severity,count:0,sampleUrls:[]};e.count+=1,e.sampleUrls.length<10&&e.sampleUrls.push(n.url),t.set(n.ruleId,e)}let n={high:3,medium:2,low:1};return[...t.values()].sort((e,t)=>n[t.severity]-n[e.severity]||t.count-e.count)}function tS(e){let t=e.toLowerCase();return Kx.has(t)||qx.test(t.replace(/-/g,`_`))}function nS(e){try{let t=new URL(e);for(let e of[...t.searchParams.keys()])qx.test(e)&&t.searchParams.set(e,`[redacted]`);return t.toString()}catch{return e}}function rS(e){return e.replace(/https?:\/\/[^\s"'<>`]+/g,e=>nS(e)).replace(/\b(access_?token|api_?key|auth|client_?secret|jwt|password|refresh_?token|secret|session|signature|token)(\s*[=:]\s*)[^\s,;&]+/gi,(e,t,n)=>`${t}${n}[redacted]`).replace(/\b[A-Za-z]:\\[^\s"'<>`]+/g,`[local-path]`).replace(/\/(?:Users|home|tmp|var\/folders)\/[^\s"'<>`]+/g,`[local-path]`)}function iS(e,t){return t&&tS(t)?`[redacted]`:typeof e==`string`?rS(e):Array.isArray(e)?e.map(e=>iS(e)):!e||typeof e!=`object`?e:Object.fromEntries(Object.entries(e).map(([e,t])=>[e,iS(t,e)]))}function aS(e){return iS(e)}function oS(e){return iS(e).map(({seoScore:e,geoScore:t,...n})=>n)}function sS(e){return iS(e)}function cS(e=[]){return e.map(e=>rS(e))}function lS(e,t){return e<t?-1:+(e>t)}function uS(e){return[...e].sort((e,t)=>lS(e.url,t.url))}function dS(e){return[...e].sort((e,t)=>lS(e.requestedUrl,t.requestedUrl)||lS(e.outcome,t.outcome)||lS(e.outcome===`response`?`${e.status}:${e.finalUrl}`:e.outcome===`skipped`?e.reason:`${e.failureKind}:${e.error}`,t.outcome===`response`?`${t.status}:${t.finalUrl}`:t.outcome===`skipped`?t.reason:`${t.failureKind}:${t.error}`))}function fS(e){let t={},n={},r={},i=0,a=0;for(let n of e.pages)t[String(n.status)]=(t[String(n.status)]??0)+1,typeof n.responseTimeMs==`number`&&(i+=n.responseTimeMs,a+=1);let o=e.requests??[],s=o.filter(e=>e.outcome===`failure`&&e.failureKind!==`aborted`),c=o.filter(e=>e.outcome===`failure`&&e.failureKind===`aborted`),l=0,u=0;for(let e of o){let t=e.outcome===`response`?String(e.status):e.outcome===`skipped`?e.reason:e.failureKind===`aborted`?`aborted`:`no-response`;n[t]=(n[t]??0)+1,typeof e.durationMs==`number`&&(l+=e.durationMs,u+=1)}for(let t of e.issues)r[t.category]=(r[t.category]??0)+1;let d=pS(e.pages,e.stats);return{totalPages:e.pages.length,indexablePages:e.pages.filter(e=>e.indexable).length,nonIndexablePages:e.pages.filter(e=>!e.indexable).length,statusErrors:e.pages.filter(e=>e.status===0||e.status>=400).length,...d,attemptedRequests:e.requestEvidenceStatus===`unavailable`?0:o.length,responseRequests:o.filter(e=>e.outcome===`response`).length,failedRequests:s.length,abortedRequests:c.length,extractionFailures:o.filter(e=>e.outcome===`response`&&e.extraction===`failed`).length,requestByStatus:n,avgRequestMs:u?Math.round(l/u):void 0,highIssues:e.issues.filter(e=>e.severity===`high`).length,mediumIssues:e.issues.filter(e=>e.severity===`medium`).length,lowIssues:e.issues.filter(e=>e.severity===`low`).length,avgResponseMs:a?Math.round(i/a):void 0,byStatus:t,byCategory:r}}function pS(e,t={}){let n=new Set,r=0;for(let t of e){n.add(t.url);for(let e of t.sampleInternalLinks??[])n.add(e);for(let e of t.sampleExternalLinks??[])n.add(e);r+=t.outgoingInternalCount}let i=Wx({skippedUrls:t.skippedUrls,skipReasons:t.skipReasons??Gx(t.skipReasonCounts)});return{discoveredUrls:t.discoveredUrls??n.size,queuedUrls:t.queuedUrls??e.length,crawledUrls:t.crawledUrls??e.length,skippedUrls:i.skippedUrls,skipReasons:i.skipReasons,skippedUrlsByImpact:i.skippedUrlsByImpact,failedUrls:t.failedUrls??e.filter(e=>e.status===0||e.status>=400).length,observedInternalLinks:t.observedInternalLinks??r,pageLimitReached:t.pageLimitReached??!1}}function mS(e){try{let t=new URL(e);return t.hash=``,t.toString()}catch{return}}function hS(e,t){let n=t&&Object.keys(t).length>0,r=e.some(e=>e.internalInlinkCount!==void 0||e.internalLinkAuthorityScore!==void 0);if(!n&&r)return e.map(e=>({...e,internalInlinkCount:e.internalInlinkCount??0,internalLinkAuthorityScore:e.internalLinkAuthorityScore??0}));let i=new Map;for(let t of e)for(let e of[t.url,t.finalUrl]){let n=mS(e);n&&i.set(n,t.url)}let a=new Map;for(let t of e)a.set(t.url,0);for(let r of e){let e=n?t?.[r.url]??t?.[r.finalUrl]??[]:r.sampleInternalLinks??[],o=new Set;for(let t of e){let e=mS(t),n=e?i.get(e):void 0;!n||n===r.url||o.add(n)}for(let e of o)a.set(e,(a.get(e)??0)+1)}let o=Math.max(0,...a.values());return e.map(e=>{let t=a.get(e.url)??0;return{...e,internalInlinkCount:t,internalLinkAuthorityScore:o?Math.round(t/o*100):0}})}function gS(e){let t=aS(Xx(e.config)),n=Qx({config:t,site:e.site,googleAnalyticsPropertyId:e.googleAnalyticsPropertyId}),r=uS(oS(hS(e.pages??[],e.linkGraph))),i=dS(iS(e.requests??[])),a=e.requestEvidenceStatus??(e.requests?`available`:`unavailable`);if(a===`unavailable`&&i.length>0)throw Error(`Unavailable request evidence cannot include request observations.`);let o=sS(e.issues??[...Ix(i),...Lx(r,{startUrl:t.url})]),s=r;return{id:e.id??$x(),definitionId:n,projectId:e.projectId,site:e.site,googleAnalyticsPropertyId:e.googleAnalyticsPropertyId,generatedAt:e.generatedAt??new Date().toISOString(),status:e.status??`completed`,configHash:Zx(t),config:t,summary:fS({pages:s,requests:i,requestEvidenceStatus:a,issues:o,stats:e.stats}),requestEvidenceStatus:a,requests:i,pages:s,issues:o,issueGroups:eS(o),...e.dataSources?{dataSources:iS(e.dataSources)}:{},...e.ai?{ai:iS(e.ai)}:{},...e.sitemapDiscovery?{sitemapDiscovery:iS(e.sitemapDiscovery)}:{},...e.externalLinkVerification?{externalLinkVerification:iS(e.externalLinkVerification)}:{},...e.agentDiscovery?{agentDiscovery:iS(e.agentDiscovery)}:{},warnings:cS(e.warnings).sort(lS),caveats:cS(e.caveats).sort(lS)}}function _S(e){let t=e.requestEvidenceStatus??(Object.hasOwn(e,`requests`)?`available`:`unavailable`),n=sS(e.issues??[]),r=dS(iS(e.requests??[])??[]),i=uS(oS(hS(e.pages??[])));return{...iS(e),requestEvidenceStatus:t,requests:r,definitionId:e.definitionId??Qx({config:e.config,site:e.site,googleAnalyticsPropertyId:e.googleAnalyticsPropertyId}),summary:fS({pages:i,requests:r,requestEvidenceStatus:t,issues:n,stats:e.summary}),pages:i,issues:n,issueGroups:eS(n),warnings:cS(e.warnings).sort(lS),caveats:cS(e.caveats).sort(lS)}}function vS(e){return{id:e.id,configHash:e.config_hash,site:e.site_url??void 0,url:e.url,status:e.status,totalPages:e.total_pages,issueCount:e.issue_count,createdAt:new Date(e.created_at).toISOString(),storageVersion:bS(e.report_json)}}function yS(e){let t={};return e.projectId&&(t.projectId=e.projectId),e.site&&(t.site=e.site),{kind:`seo.crawl_report`,version:6,savedAt:new Date().toISOString(),...t.projectId||t.site?{tenant:t}:{},report:e}}function bS(e){try{let t=JSON.parse(e);return t.kind===`seo.crawl_report`&&typeof t.version==`number`?t.version:0}catch{return 0}}function xS(e){let t=JSON.parse(e);return t&&typeof t==`object`&&`kind`in t&&t.kind===`seo.crawl_report`&&`report`in t?_S(t.report):_S(t)}function SS(e){if(A().prepare(`INSERT INTO crawl_reports
333
333
  (id, config_hash, site_url, url, status, total_pages, issue_count, created_at, report_json)
@@ -550,11 +550,11 @@ CREATE INDEX IF NOT EXISTS idx_performance_reports_url ON performance_reports(ur
550
550
  `).trim()}async function mI(e){let t=await aF(e);return XF({workflow:`monthly-report`,site:e.site,summary:`Monthly report generated for ${t.month}. ${t.headline}`,steps:[{tool:`seo_monthly_report`,status:`completed`,summary:`Generated report for ${t.period.startDate} to ${t.period.endDate}.`}],actions:t.priorities,output:{report:t}})}const hI=new Set([`quick-win`,`striking-distance`]);function gI(e){return e?e.id.endsWith(`-salary`)?`salary-page`:e.id:`no-template`}function _I(e){if(!hI.has(e.source)||e.grouped)return;let t=gI(e.template);return[e.source,e.category,t,Y_(e.title)].join(`|`)}function vI(e){if(!hI.has(e.source)||e.grouped)return;let t=gI(e.template);return[e.source,e.category,t,Y_(e.target)].join(`|`)}function yI(e){let t=e[0];if(!t)return``;let n=new Set(e.map(e=>e.target)).size;return n<=1&&e.length<=1?t.action:n<=1?`${e.length} findings point at this URL. Start with the highest-impact issue first: ${t.action}`:`${n} URLs have the same issue. Apply this action across the affected pages: ${t.action}`}function bI(e){let t=e.reduce((e,t)=>e+t.impact,0),n=new Set(e.map(e=>e.action)).size,r=e.slice(0,3).map(e=>e.target).join(`, `),i=n>1?` ${n} action variants are preserved in grouped findings.`:``;return`Grouped ${e.length} matching findings; total impact ${t.toFixed(2)}.${i} Top targets: ${r}.`}function xI(e){let t=[...e].sort((e,t)=>t.score-e.score),n=t[0];if(!n)throw Error(`Cannot group an empty priority list.`);let r=t.reduce((e,t)=>e+t.impact,0),i=t.reduce((e,t)=>e+t.score,0),a=Number(Math.min(n.score*(1+Math.log1p(t.length-1)/8),300).toFixed(2));return{...n,score:a,impact:Number(r.toFixed(2)),action:yI(t),evidence:bI(t),grouped:{count:t.length,totalImpact:Number(r.toFixed(2)),totalScore:Number(i.toFixed(2)),findings:t.map(e=>({source:e.source,title:e.title,target:e.target,category:e.category,score:e.score,impact:e.impact,impactKind:e.impactKind,confidence:e.confidence,template:e.template,analytics:e.analytics,action:e.action,evidence:e.evidence}))}}}function SI(e,t){let n=new Map,r=[];for(let i of e){let e=t(i);if(!e){r.push(i);continue}let a=n.get(e)??[];a.push(i),n.set(e,a)}return[...r,...[...n.values()].flatMap(e=>e.length>1?[xI(e)]:e)]}function CI(e){return SI(SI(e,vI),_I).sort((e,t)=>t.score-e.score)}function wI(e){let t=e.templates?.find(t=>t.id===e.id);return t?.urlCount??t?.count}function TI(e,t){let n=YF({source:e.source,impact:e.impact,confidence:e.confidence,effort:e.effort,verification:e.verification,templateCount:e.template?.count,analyticsSessions:t?e.analytics?.sessions:void 0});return{...e,category:JF(e.source,e.verification),score:n.final,scoreBreakdown:n}}function EI(e){return e.groups.filter(e=>e.count>=3||e.totalClickLoss>=10).map(t=>({source:`decay`,title:`${t.label} cluster`,target:t.sampleUrls[0]??e.site,impact:t.totalClickLoss,impactKind:`observed_retained_query_clicks`,confidence:`medium`,effort:t.diagnosis===`lost_ctr`?`S`:`M`,template:{id:t.template.id,label:t.template.label,count:t.count},action:t.recommendation,evidence:`${t.count} matching query/page rows retained in both windows had ${t.totalClickLoss.toFixed(0)} fewer clicks. Examples: ${t.sampleQueries.slice(0,3).join(`; `)}.`}))}async function DI(e){let t=await Mk({...e,verifyContent:e.verifyContent??!0,verifyLimit:e.verifyLimit??Math.min(e.limit??5,5)}),n=$_(e.days??28),r=await BC({propertyId:e.googleAnalyticsPropertyId,startDate:n.startDate,endDate:n.endDate}),i=zC({propertyId:e.googleAnalyticsPropertyId,source:r.source,warning:r.warning}),a=i.warnings,o=[],s=Math.max(e.limit??25,25);for(let e of t.strikingDistance.items){let n=wI({templates:t.strikingDistance.templates,id:e.template.id}),i=LC(r.values,e.url);o.push({source:`striking-distance`,title:e.query,target:e.url,impact:e.priority.score,impactKind:`heuristic_priority_score`,confidence:e.recommendation.confidence,verification:e.contentVerification?.classification,template:{id:e.template.id,label:e.template.label,count:n??1},analytics:i,action:e.recommendation.action,evidence:e.recommendation.evidence})}for(let e of t.quickWins.items.slice(0,s)){let n=wI({templates:t.quickWins.templates,id:e.template.id}),i=LC(r.values,e.url);o.push({source:`quick-win`,title:e.query,target:e.url,impact:Number(e.estimatedCtrClickShortfall.toFixed(2)),impactKind:`heuristic_ctr_click_shortfall`,confidence:e.recommendation.confidence,effort:e.recommendation.effort,verification:e.contentVerification?.classification,template:{id:e.template.id,label:e.template.label,count:n??1},analytics:i,action:e.recommendation.action,evidence:e.recommendation.evidenceRef})}for(let e of t.decay.items){let n=wI({templates:t.decay.templates,id:e.template.id}),i=LC(r.values,e.url);o.push({source:`decay`,title:e.query,target:e.url,impact:e.clickLoss,impactKind:`observed_retained_query_clicks`,confidence:e.recommendation.confidence,effort:e.recommendation.effort,template:{id:e.template.id,label:e.template.label,count:n??1},analytics:i,action:e.recommendation.action,evidence:e.recommendation.evidenceRef})}o.push(...EI({groups:t.decay.groups,site:e.site}));for(let e of t.cannibalization.items)o.push({source:`cannibalization`,title:e.query,target:e.suggestedOwnerUrl,impact:e.priority.score,impactKind:`heuristic_multi_url_exposure`,confidence:e.recommendation.confidence,effort:e.recommendation.effort,template:e.template?{id:e.template.id,label:e.template.label,count:e.pages.length}:void 0,action:e.recommendation.action,evidence:e.recommendation.evidenceRef});for(let n of t.priorities)o.push({source:`diagnosis`,title:n.label,target:e.site,impact:n.confidence===`high`?150:n.confidence===`medium`?75:25,impactKind:`ordinal`,confidence:n.confidence,action:n.action,evidence:n.reason});for(let e of t.quickWins.templateRecommendations)o.push({source:`template`,title:`${e.templateLabel} opportunity template`,target:`${e.templateLabel} template (${e.urlCount} URLs)`,impact:e.totalEstimatedCtrClickShortfall,impactKind:`heuristic_ctr_click_shortfall`,confidence:`medium`,effort:`M`,template:{id:e.templateId,label:e.templateLabel,count:e.urlCount},action:e.action,evidence:e.evidence});let c=CI(o.map(e=>TI(e,i.canRank)).filter(e=>e.score>0).sort((e,t)=>t.score-e.score)).slice(0,e.limit??25);return XF({workflow:`refresh-priorities`,site:e.site,summary:`${c.length} ranked SEO priorities refreshed.`,steps:[{tool:`seo_diagnose_property`,status:`completed`,summary:`Combined diagnosis, decay, striking-distance, quick-win, and cannibalisation signals.`}],actions:c.slice(0,5).map(e=>({title:e.title,action:e.action,confidence:e.confidence})),output:{queue:c,warnings:a,diagnosis:t,analyticsSource:r.source,analyticsRankingEligible:i.canRank,analyticsRankingApplied:c.some(e=>e.scoreBreakdown.analytics>1)}})}function OI(e){return{inspected:e?.summary.inspected??0,alerts:e?.summary.alerts??0,currentIssues:e?.summary.currentIssues??0,failed:e?.summary.failed??0,quotaBlocked:e?.summary.quotaBlocked??0,deferred:e?.summary.deferred??0}}async function kI(e){let t=e.recoverLinks??!0,n=!!(e.urls?.length||e.sitemaps?.length);if(!e.startUrl&&!n&&!t)throw Error(`Pass startUrl, urls, sitemaps, or enable link recovery for technical-watch.`);let[r,i,a]=await Promise.all([e.startUrl?nj({site:e.site,startUrl:e.startUrl,limit:e.limit,refresh:e.refresh,js:e.js}):void 0,e.sitemaps?.length?mM({site:e.site,sitemaps:e.sitemaps,properties:e.properties,dailyLimit:e.dailyLimit,inspectLimit:e.inspectLimit,maxUrls:e.maxUrls,languageCode:e.languageCode,refresh:e.refresh}):e.urls?.length?cM({site:e.site,urls:e.urls,languageCode:e.languageCode,dailyLimit:e.dailyLimit}):void 0,t?EM({site:e.site,days:e.recoverDays,limit:e.recoverLimit??10,minClicks:e.recoverMinClicks,minImpressions:e.recoverMinImpressions,refresh:e.refresh,js:e.js}):void 0]),o=OI(i),s=(r?.summary.highPriorityRecommendations??0)+o.currentIssues+(a?.summary.high??0)+(a?.summary.medium??0),c=o.failed+o.quotaBlocked+o.deferred,l=[];return a?.items[0]&&l.push({title:`Recover search-value URL`,action:a.items[0].recommendation.action,confidence:a.items[0].recommendation.confidence}),s>0&&l.push({title:`Triage technical findings`,action:`Prioritize current index issues, crawl actions, and recoverable search-value URLs before content work.`,confidence:`high`}),c>0&&l.push({title:`Complete URL Inspection coverage`,action:`Retry failed or deferred URL Inspection checks after resolving access, provider, or quota errors. Do not treat incomplete checks as SEO defects.`,confidence:`high`}),l.length||l.push({title:`No material technical finding`,action:`Keep the saved crawl and index snapshots; repeat the recovery check on the next run.`,confidence:`medium`}),XF({workflow:`technical-watch`,site:e.site,summary:`${Z(s,`material technical finding`)}; ${Z(c,`incomplete URL Inspection check`)}.`,steps:[{tool:`seo_crawl_diff`,status:r?`completed`:`skipped`,summary:r?`Crawled ${Z(r.summary.crawled,`URL`)}; ${r.summary.changed} changed, ${Z(r.summary.highPriorityRecommendations,`high-priority crawl action`)}.`:`No start URL passed.`},{tool:e.sitemaps?.length?`seo_index_monitor`:`seo_index_watch`,status:i?`completed`:`skipped`,summary:i?`Inspected ${Z(o.inspected,`selected URL`)}; ${Z(o.currentIssues,`current review in selected results`)}, ${Z(o.alerts,`new alert`)}, ${Z(o.failed,`failed check`)}, ${o.quotaBlocked+o.deferred} quota-blocked or deferred.`:`No inspection URLs or sitemaps passed.`},{tool:`seo_link_recover`,status:a?`completed`:`skipped`,summary:a?`Checked ${Z(a.summary.checked,`search-value URL`)}; ${Z(a.summary.recoverable,`recoverable issue`)}, ${a.summary.high} high severity.`:`Link recovery disabled.`}],actions:l,output:{crawl:r,index:i,recovery:a}})}function AI(e,t){return e<t?-1:+(e>t)}function jI(e){let t=e.items.filter(e=>e.clickDelta>0).sort((e,t)=>t.clickDelta-e.clickDelta||AI(e.key,t.key)),n=e.items.filter(e=>e.clickDelta<0).sort((e,t)=>e.clickDelta-t.clickDelta||AI(e.key,t.key)),r=t.length+n.length;return{dataStatus:e.dataStatus,coverage:{evidenceScope:`matched-retained-segments`,eligibleRows:e.selection.matchedRows,returnedRows:e.selection.returnedRows,limitedRows:e.selection.limitedRows,resultLimit:e.filters.limit,sourceRowLimit:e.filters.maxRows,movedRows:r,unchangedRows:Math.max(0,e.selection.returnedRows-r),sourcePossiblyTruncated:e.source.before.possiblyTruncated||e.source.after.possiblyTruncated},summary:e.summary,unmatchedSegments:e.unmatchedSegments,warnings:e.warnings,winners:t,losers:n}}function MI(e){return Math.abs(e).toLocaleString(`en-GB`)}function NI(e){return`${Math.round(e*100)}%`}function PI(e,t){return e<t?-1:+(e>t)}function FI(e){return`${e.returnedRows} of ${e.eligibleRows} eligible matched retained rows returned under result limit ${e.resultLimit}${e.sourcePossiblyTruncated?`; at least one source window reached the ${e.sourceRowLimit}-row source limit`:``}`}function II(e){return e.clickDelta>0?`gained`:e.clickDelta<0?`lost`:`held flat`}function LI(e,t){return e===`page`?t.key.replace(/^https?:\/\//,``):t.key}function RI(e,t){if(t.dataStatus===`unavailable`||t.dataStatus===`empty`)return{dimension:e,dataStatus:t.dataStatus,coverage:t.coverage,unmatchedRows:t.summary.unmatchedRows,summary:`${e}: no matched retained segment movement was available. ${t.summary.verdict} Scope: ${FI(t.coverage)}.`};let n=t.dataStatus===`partial`?` Evidence is partial; ${t.summary.unmatchedRows} one-window rows were not treated as zero.`:``,r=` Scope: ${FI(t.coverage)}.`,i=t.winners[0],a=t.losers[0];return i&&a?{dimension:e,dataStatus:t.dataStatus,coverage:t.coverage,unmatchedRows:t.summary.unmatchedRows,winner:i,loser:a,summary:`${e}: top winner ${LI(e,i)} ${II(i)} ${MI(i.clickDelta)} clicks; top loser ${LI(e,a)} ${II(a)} ${MI(a.clickDelta)} clicks.${n}${r}`}:i?{dimension:e,dataStatus:t.dataStatus,coverage:t.coverage,unmatchedRows:t.summary.unmatchedRows,winner:i,summary:`${e}: top winner ${LI(e,i)} ${II(i)} ${MI(i.clickDelta)} clicks; no material loser appeared in the returned segment rows.${n}${r}`}:a?{dimension:e,dataStatus:t.dataStatus,coverage:t.coverage,unmatchedRows:t.summary.unmatchedRows,loser:a,summary:`${e}: top loser ${LI(e,a)} ${II(a)} ${MI(a.clickDelta)} clicks; no material winner appeared in the returned segment rows.${n}${r}`}:{dimension:e,dataStatus:t.dataStatus,coverage:t.coverage,unmatchedRows:t.summary.unmatchedRows,summary:`${e}: no material winner or loser appeared in the returned matched retained segment rows.${n}${r}`}}function zI(e){let t=e.insights.find(e=>e.dimension===`page`),n=e.insights.find(e=>e.dimension===`query`);return`Cause not established; ${t?.dataStatus===`empty`||t?.dataStatus===`unavailable`?`page segment evidence unavailable`:t?.winner?`top returned page gained ${MI(t.winner.clickDelta)} clicks`:t?.loser?`top returned page lost ${MI(t.loser.clickDelta)} clicks`:`no material page movement found in returned rows`}; ${n?.dataStatus===`empty`||n?.dataStatus===`unavailable`?`query segment evidence unavailable`:n?.winner?`top returned query gained ${MI(n.winner.clickDelta)} clicks`:n?.loser?`top returned query lost ${MI(n.loser.clickDelta)} clicks`:`no material query movement found in returned rows`}.`}function BI(e){let t=[],n=e.segments.page.winners.length,r=e.segments.page.losers.length,i=e.segments.query.winners.length,a=e.segments.query.losers.length,o=e.templateMovement[0];return o&&t.push({title:`${o.direction===`winner`?`Protect`:`Fix`} ${o.signature}`,action:o.direction===`winner`?`${o.signature} is a repeated winning URL pattern across ${o.urlCount} URLs in the returned matched-retained page subset. Preserve the shared title/H1/content/internal-link pattern before making broad edits.`:`${o.signature} is a repeated losing URL pattern across ${o.urlCount} URLs in the returned matched-retained page subset. Check indexability, canonical/robots state, internal links, and whether the shared template still matches search intent.`,confidence:o.confidence}),e.update.confounders.length>0?t.push({title:`Test overlapping site changes first`,action:`Start with the known overlapping changes, then compare changed sections against unchanged sections. Keep the Google update window as timing context, not a cause.`,confidence:`medium`}):e.update.classification===`significant-movement-with-update-overlap`?t.push({title:`Map movement by template`,action:`Group the top page and query movers into templates or content types. Preserve winning patterns and investigate losing templates, but do not treat timing overlap as proof of an update effect.`,confidence:`medium`}):t.push({title:`Treat this as exploratory`,action:`Use the segment splits as a triage view, but do not over-attribute the movement to a Google update yet.`,confidence:`low`}),n>0&&r===0&&t.push({title:`Protect the winning pages`,action:`The returned page split contains positive movers and no negative movers. Check those pages for common query intent, title/H1 pattern, internal links, and template shape before making broad edits.`,confidence:`medium`}),(r>0||a>0)&&t.push({title:`Triage the largest losers first`,action:`For losing pages and queries, check whether impressions dropped, average position worsened, or CTR fell. That separates ranking/index loss from snippet or SERP layout issues.`,confidence:`medium`}),i>0&&a===0&&t.push({title:`Expand winning query angles`,action:`The returned query split contains positive movers and no negative movers. Use that query wording to improve internal anchors and supporting copy on related pages.`,confidence:`medium`}),t}function VI(e,t){return e.filter(e=>t===`winner`?e.clickDelta>0:e.clickDelta<0)}function HI(e){let t=new Map;for(let n of e.items){let r=fj(n.key,e.clusters);if(r===`/`)continue;let i=e.clusters.find(e=>e.signature===r),a=t.get(r)??{cluster:i,signature:r,items:[],clickDelta:0,impressionDelta:0};a.items.push(n),a.clickDelta+=n.clickDelta,a.impressionDelta+=n.impressionDelta,t.set(r,a)}return[...t.values()]}function UI(e){return e?[...new Set(e.shape.variableSegments.flatMap(e=>e.tokenExamples))].slice(0,6):[]}function WI(e){return HI({items:VI(e.items,e.direction),clusters:e.clusters}).map(t=>{let n=Math.abs(t.clickDelta),r=e.totalMovement?n/e.totalMovement:0;if(t.items.length<3||r<.2)return;let i=t.items.length>=10&&r>=.35&&e.segmentCoverage.limitedRows===0&&!e.segmentCoverage.sourcePossiblyTruncated?`high`:`medium`,a=e.direction===`winner`?`gained`:`lost`,o=UI(t.cluster),s=o.length?` Common URL terms: ${o.join(`, `)}.`:``;return{signature:t.signature,direction:e.direction,confidence:i,urlCount:t.items.length,clickDelta:Number(t.clickDelta.toFixed(3)),impressionDelta:Number(t.impressionDelta.toFixed(3)),movementShare:Number(r.toFixed(3)),movementShareScope:`returned-directional-page-click-movement`,confidenceScope:`pattern-within-returned-matched-retained-page-segments`,segmentCoverage:e.segmentCoverage,commonTerms:o,sampleUrls:t.items.slice(0,5).map(e=>e.key),summary:`${t.signature} ${a} ${MI(t.clickDelta)} clicks across ${t.items.length} moved URLs (${NI(r)} of returned ${e.direction} page click movement). Coverage: ${e.segmentCoverage.returnedRows} of ${e.segmentCoverage.eligibleRows} eligible matched retained page rows returned under result limit ${e.segmentCoverage.resultLimit}.${s}`}}).filter(e=>!!e).sort((e,t)=>Math.abs(t.clickDelta)-Math.abs(e.clickDelta)||t.urlCount-e.urlCount||PI(e.signature,t.signature))}function GI(e){let t=[...e.winners,...e.losers].filter(e=>e.key.startsWith(`http`)).filter(e=>Math.abs(e.clickDelta)>0).sort((e,t)=>Math.abs(t.clickDelta)-Math.abs(e.clickDelta)||PI(e.key,t.key));if(t.length<6)return[];let n=mj(t.map(e=>e.key),{minUrls:3,minShare:.15,limit:20}).filter(e=>e.signature!==`/`&&e.urlCount>=3);if(!n.length)return[];let r=e.winners.reduce((e,t)=>e+Math.max(0,t.clickDelta),0),i=Math.abs(e.losers.reduce((e,t)=>e+Math.min(0,t.clickDelta),0));return[...WI({direction:`winner`,items:t,clusters:n,totalMovement:r,segmentCoverage:e.coverage}),...WI({direction:`loser`,items:t,clusters:n,totalMovement:i,segmentCoverage:e.coverage})].slice(0,6)}async function KI(e,t={}){let n=e.limit??20,r=t.updateCorrelation??bk,i=t.segmentImpact??iD,[a,o,s,c,l]=await Promise.all([r(e),i({...e,dimension:`page`,limit:n}),i({...e,dimension:`query`,limit:n}),i({...e,dimension:`device`,limit:n}),i({...e,dimension:`country`,limit:n})]),u={page:jI(o),query:jI(s),device:jI(c),country:jI(l)},d=[RI(`page`,u.page),RI(`query`,u.query),RI(`device`,u.device),RI(`country`,u.country)],f=GI(u.page),p=[u.page,u.query,u.device,u.country].every(e=>e.dataStatus===`empty`||e.dataStatus===`unavailable`)?`skipped`:`completed`;return XF({workflow:`update-postmortem`,site:e.site,summary:zI({update:a,insights:d}),steps:[{tool:`seo_update_correlate`,status:`completed`,summary:`Cause not established. ${a.summary}`},{tool:`seo_segment_impact`,status:p,summary:d.map(e=>e.summary).join(` `)}],actions:BI({update:a,segments:u,templateMovement:f}),output:{update:a,insights:d,templateMovement:f,segments:u}})}function qI(e){return e?.analytics.google?.propertyId}function JI(e){return e.toLowerCase().replace(/^https?:\/\//,``).replace(/^sc-domain:/,``).replace(/[^a-z0-9]+/g,`-`).replace(/^-+|-+$/g,``).slice(0,60)}function YI(e=[]){return[...new Set(e.map(e=>e.trim()).filter(Boolean))]}function XI(e){let t=JI(e.id??e.name??e.siteUrl);if(!t)throw Error(`Client id could not be derived.`);return t}function ZI(){return[...nt().clients].sort((e,t)=>e.name.localeCompare(t.name))}function QI(e){let t=nt().clients;if(!e)return t.find(e=>e.isDefault);let n=e.toLowerCase();return t.find(e=>e.id.toLowerCase()===n||e.name.toLowerCase()===n)}function $I(e){let t=nt(),n=XI(e),r=t.clients.find(e=>e.id===n),i=Date.now(),a={id:n,name:e.name??r?.name??n,siteUrl:e.siteUrl,startUrl:e.startUrl??r?.startUrl,watchUrls:YI(e.watchUrls??r?.watchUrls),brandTerms:YI(e.brandTerms??r?.brandTerms??V_({id:n,name:e.name??r?.name,siteUrl:e.siteUrl})),analytics:e.analytics??r?.analytics??{},reportDay:e.reportDay??r?.reportDay,technicalWeekday:e.technicalWeekday??r?.technicalWeekday,isDefault:e.isDefault??r?.isDefault,createdAt:r?.createdAt??i,updatedAt:i},o=t.clients.filter(e=>e.id!==n);if(a.isDefault){for(let e of o)e.isDefault=!1;t.defaultSite=a.siteUrl}return t.clients=[...o,a],rt(t),a}function eL(e){let t=nt(),n=QI(e);return n?(t.clients=t.clients.filter(e=>e.id!==n.id),t.defaultSite===n.siteUrl&&!t.clients.some(e=>e.siteUrl===n.siteUrl)&&!t.sites.some(e=>e.siteUrl===n.siteUrl)&&(t.defaultSite=void 0),rt(t),!0):!1}function tL(e){let t=nt(),n=QI(e);if(!n)throw Error(`Client not found: ${e}`);return t.clients=t.clients.map(e=>({...e,isDefault:e.id===n.id})),t.defaultSite=n.siteUrl,rt(t),{...n,isDefault:!0}}const nL=[`https://www.googleapis.com/auth/webmasters.readonly`,`https://www.googleapis.com/auth/analytics.readonly`];async function rL(e={}){let t=Ie(),n=nt(),r=xt(),a=It(),o=await dt(),s=gt(),c=o?!!Ct(o.client_source):void 0,l=[];try{let t=(e.checkDatabase??_n)();l.push({id:`local-database`,label:`Local database`,status:`pass`,detail:t.dbPath})}catch(e){l.push({id:`local-database`,label:`Local database`,status:`fail`,detail:e instanceof Error?e.message:`The local database could not be opened.`,fix:"Upgrade or reinstall `seo`. If this still fails, report it at https://github.com/iannuttall/seo/issues."})}l.push({id:`config-dir`,label:`Config directory`,status:i(t.configDir)?`pass`:`warn`,detail:t.configDir,fix:i(t.configDir)?void 0:"Run `seo start` or `seo auth setup-client`."}),l.push({id:`oauth-client`,label:`Google credentials`,status:a.configured?`pass`:c===!1||a.error?`fail`:r.sharedConfigured||r.byoConfigured?`pass`:`fail`,detail:a.configured?`Service account configured for ${a.identity}.`:c===!1?`Stored Google login uses the ${o?.client_source===`shared`?`shared seo app`:`BYO client`}, but that client is not configured.`:a.error?a.error:r.sharedConfigured?`Shared client configured.`:r.byoConfigured?`BYO client configured at ${t.oauthClientFile}.`:`No shared or BYO Google OAuth client configured.`,fix:a.configured?void 0:c===!1?o?.client_source===`shared`?"Reinstall `seo`. If the shared client is still missing, report it at https://github.com/iannuttall/seo/issues.":"Restore the same client with `seo auth setup-client`, or run `seo auth logout` before signing in with a different client.":a.error?"Set one valid service account credential source, then run `seo doctor` again.":r.sharedConfigured||r.byoConfigured?void 0:"Run `seo auth setup-client` or set SEO_GOOGLE_CLIENT_ID and SEO_GOOGLE_CLIENT_SECRET."}),l.push({id:`oauth-client-file`,label:`BYO OAuth file`,status:s?`pass`:`warn`,detail:s?t.oauthClientFile:`No BYO OAuth client file. This is fine if using a shared client.`}),l.push({id:`google-login`,label:`Google login`,status:a.configured||o?`pass`:`fail`,detail:a.configured?`Using service account ${a.identity}.`:o?`Signed in as ${o.account_email}.`:`No Google token found.`,fix:a.configured||o?void 0:"Run `seo auth login`, or configure a service account for CI."});let u=new Set(o?.scope.split(/\s+/).filter(Boolean)??[]);for(let e of nL)l.push({id:`scope:${e}`,label:e.endsWith(`webmasters.readonly`)?`Search Console scope`:`Google Analytics scope`,status:a.configured||o&&u.has(e)?`pass`:o?`fail`:`warn`,detail:a.configured?`${e} requested by the service account.`:o?u.has(e)?e:`Missing ${e}.`:`Cannot inspect scopes until login is complete.`,fix:!a.configured&&o&&!u.has(e)?"Run `seo auth logout` then `seo auth login`.":void 0});return l.push({id:`default-site`,label:`Saved GSC property`,status:`pass`,detail:n.defaultSite??`No saved default. Human CLI commands will prompt; agents can pass --site.`}),l.push({id:`default-google-analytics`,label:`Saved Google Analytics property`,status:`pass`,detail:n.analytics.google.defaultPropertyId??`No saved default. Human CLI commands will prompt; agents can pass --property.`}),{ok:l.every(e=>e.status!==`fail`),generatedAt:new Date().toISOString(),checks:l}}var iL=class{name=`dataforseo`;capabilities={overview:!0};getAuthHeader(){let e=nt(),t=e.providers.dataForSeoLogin,n=e.providers.dataForSeoPassword;if(!t||!n)throw Error(`DataForSEO credentials missing. Add login and password to config.json.`);return`Basic ${Buffer.from(`${t}:${n}`).toString(`base64`)}`}async keywordOverview(e,t={}){let r=await n(`https://api.dataforseo.com/v3/dataforseo_labs/google/keyword_overview/live`,{method:`POST`,headers:{authorization:this.getAuthHeader(),"content-type":`application/json`},body:JSON.stringify([{keyword:e,language_code:`en`,location_code:2840}])});if(!r.ok)throw Error(`DataForSEO request failed with ${r.status}.`);let i=await r.json(),a=i.tasks?.[0]?.result?.[0];return{data:{phrase:a?.keyword??e,volume:a?.search_volume,competition:a?.competition,cpc:a?.cpc,difficulty:a?.keyword_difficulty},usage:{provider:`DataForSEO`,units:1,unitLabel:`tasks`,estimatedUsd:i.cost,calls:1},warnings:t.database?[`DataForSEO overview currently ignores the database option.`]:void 0}}};function aL(e){return e.trim().split(`
551
551
  `).map(e=>e.split(`;`).map(e=>e.replace(/^"|"$/g,``).trim()))}function oL(e){return e/1e3*.05}async function sL(e,t,r,i,a,o=!1){let s=nt().providers.semrushApiKey;if(!s)throw Error(`Semrush API key missing. Add it to config.json first.`);let c=Object.fromEntries(Object.entries({...t,key:s,type:e}).filter(([,e])=>e!==void 0)),l=A(),u=vn([e,c]),d=l.prepare(`SELECT response_json, credits_used FROM semrush_cache WHERE endpoint = ? AND query_hash = ? AND expires_at > ?`).get(e,u,Date.now());if(!o&&d?.response_json)return{data:JSON.parse(d.response_json),usage:{provider:`Semrush`,units:d.credits_used??0,unitLabel:`units`,estimatedUsd:oL(d.credits_used??0),calls:1,cacheHits:1},cached:!0};let f=new URL(`https://api.semrush.com/`);f.search=new URLSearchParams(c).toString();let p=await n(f),m=await p.text();if(!p.ok||m.startsWith(`ERROR ::`))throw Error(m||`Semrush request failed with ${p.status}.`);let h=aL(m),g=r(h),_=Math.max(0,h.length-1)*a;return l.prepare(`INSERT OR REPLACE INTO semrush_cache
552
552
  (endpoint, query_hash, request_json, response_json, credits_used, fetched_at, expires_at)
553
- VALUES (?, ?, ?, ?, ?, ?, ?)`).run(e,u,JSON.stringify(c),JSON.stringify(g),_,Date.now(),Date.now()+i),{data:g,usage:{provider:`Semrush`,units:_,unitLabel:`units`,estimatedUsd:oL(_),calls:1}}}function cL(e){let[t,n]=e;if(!t||!n)return{phrase:``};let r=Object.fromEntries(t.map((e,t)=>[e,n[t]]));return{phrase:r.Ph??``,volume:Number(r.Nq??0)||void 0,cpc:Number(r.Cp??0)||void 0,competition:Number(r.Co??0)||void 0,difficulty:Number(r.Kd??0)||void 0,results:Number(r.Nr??0)||void 0}}function lL(e){let[t,...n]=e;return t?n.map(e=>{let n=Object.fromEntries(t.map((t,n)=>[t,e[n]]));return{phrase:n.Ph??``,volume:Number(n.Nq??0)||void 0,difficulty:Number(n.Kd??0)||void 0,cpc:Number(n.Cp??0)||void 0,competition:Number(n.Co??0)||void 0,url:n.Ur,domain:n.Dn,position:Number(n.Po??0)||void 0}}):[]}var uL=class{name=`semrush`;capabilities={overview:!0,batchOverview:!0,related:!0,broadMatch:!0,questions:!0,difficulty:!0,urlKeywords:!0,domainKeywords:!0,maxBatchSize:100};async keywordOverview(e,t={}){return sL(`phrase_this`,{phrase:e,database:t.database??`us`,export_columns:`Ph,Nq,Cp,Co,Nr,Td,Kd`},cL,7*864e5,10,t.refresh)}async batchKeywordOverview(e,t={}){return sL(`phrase_these`,{phrase:e.join(`;`),database:t.database??`us`,export_columns:`Ph,Nq,Cp,Co,Nr,Td,Kd`},e=>lL(e).map(e=>({...e,phrase:e.phrase})),7*864e5,10,t.refresh)}async relatedKeywords(e,t={}){return sL(`phrase_related`,{phrase:e,database:t.database??`us`,display_limit:20,export_columns:`Ph,Nq,Kd,Cp,Co`},lL,14*864e5,40,t.refresh)}async questions(e,t={}){return sL(`phrase_questions`,{phrase:e,database:t.database??`us`,display_limit:20,export_columns:`Ph,Nq,Kd,Cp,Co`},lL,14*864e5,40,t.refresh)}async keywordDifficulty(e,t={}){return sL(`phrase_kdi`,{phrase:e.join(`;`),database:t.database??`us`,export_columns:`Ph,Kd`},e=>lL(e).map(e=>({phrase:e.phrase,kd:e.difficulty??0})),7*864e5,50,t.refresh)}async domainKeywords(e,t={}){return sL(`domain_organic`,{domain:e,database:t.database??`us`,display_limit:100,export_columns:`Ph,Nq,Cp,Co,Kd,Po,Ur,Dn`},lL,7*864e5,10,t.refresh)}async urlKeywords(e,t={}){return sL(`url_organic`,{url:e,database:t.database??`us`,display_limit:100,export_columns:`Ph,Nq,Cp,Co,Kd,Po,Ur,Dn`},lL,7*864e5,10,t.refresh)}};function dL(e){let t=nt(),n=e??t.providers.prefer;if(n===`authoritative`&&t.providers.semrushApiKey)return new uL;if(n===`cheap`&&t.providers.dataForSeoLogin&&t.providers.dataForSeoPassword)return new iL;if(t.providers.semrushApiKey)return new uL;if(t.providers.dataForSeoLogin&&t.providers.dataForSeoPassword)return new iL}new class{getGoogleOAuthClient(){let e=gt();if(e)return{clientId:e.clientId,clientSecret:e.clientSecret,source:`byo`};let t=nt(),n=process.env.SEO_GOOGLE_CLIENT_ID??process.env.GSC_CLIENT_ID??bt.clientId??t.auth.sharedClientId,r=process.env.SEO_GOOGLE_CLIENT_SECRET??process.env.GSC_CLIENT_SECRET??bt.clientSecret??t.auth.sharedClientSecret;if(!(!n||!r))return{clientId:n,clientSecret:r,source:`shared`}}readGoogleTokens(){return dt()}writeGoogleTokens(e){return ft(e)}deleteGoogleTokens(){return pt()}};const fL=`0.2.7`,pL=`affected-urls.agent-readiness.ai-readiness.ai-referrals.ai-search-scorecard.audit-page.audit-urls.cannibalisation.community-intent.compare-crawls.content-optimization.crawl-diff.site-crawl.ctr-underperformers.decaying-pages.setup-check.entity-readiness.explain-crawl-issue.geo-gaps.crawl-report.index-coverage.index-coverage-plan.index-monitor.index-watch.internal-links.link-recovery.crawl-history.crawler-rules.llms-txt-audit.generate-llms-txt.measure-change.monthly-report.okf-build.okf-validate.page-opportunities.performance-audit.pseo-audit.query-clusters.quick-wins.redirect-trace.narrative-report.second-page.segment-impact.striking-distance.seo-to-ai-query.top-fixes.traffic-anomaly.update-correlation.search-performance-overview.monthly-action-plan.refresh-priorities.technical-watch.update-postmortem`.split(`.`),mL=[{days:1,event:`active_d1`},{days:7,event:`active_d7`},{days:30,event:`active_d30`}],hL=[`GITHUB_ACTIONS`,`GITLAB_CI`,`CIRCLECI`,`TRAVIS`,`BUILDKITE`,`JENKINS_URL`,`TEAMCITY_VERSION`,`TF_BUILD`,`BITBUCKET_BUILD_NUMBER`,`CODEBUILD_BUILD_ID`];function gL(e){return e.env??process.env}function _L(e){return e.now??new Date}function vL(e){return e.stateFile??Ie().telemetryStateFile}function yL(e){return e===`1`||e?.toLowerCase()===`true`}function bL(e=process.env){return e.NODE_ENV===`test`||!!e.NODE_TEST_CONTEXT||yL(e.CI)||hL.some(t=>!!e[t])}function xL(e){if(bL(e))return`ci`;if(yL(e.DO_NOT_TRACK))return`do_not_track`;if(yL(e.SEOSKILL_TELEMETRY_DISABLED)||yL(e.SEO_TELEMETRY_DISABLED))return`environment`}function SL(e){return typeof e==`string`&&[`first_run`,`setup_complete`,`first_audit_complete`,`active_d1`,`active_d7`,`active_d30`].includes(e)}function CL(e){return typeof e==`string`&&pL.includes(e)}function wL(e){return typeof e==`string`&&/^\d{4}-W(?:0[1-9]|[1-4]\d|5[0-3])$/.test(e)}function TL(e){if(!e||typeof e!=`object`||Array.isArray(e))return!1;let t=e;return typeof t.telemetryEnabled==`boolean`&&typeof t.firstRunAt==`string`&&Number.isFinite(Date.parse(t.firstRunAt))&&wL(t.cohort)&&Array.isArray(t.sentMilestones)&&t.sentMilestones.every(SL)}function EL(e){if(!i(e))return{kind:`missing`};let t=Ke(e);return TL(t)?{kind:`valid`,state:t}:{kind:`invalid`}}function DL(e,t){qe(e,t,384)}function OL(e){let t=new Date(Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate())),n=t.getUTCDay()||7;t.setUTCDate(t.getUTCDate()+4-n);let r=new Date(Date.UTC(t.getUTCFullYear(),0,1)),i=Math.ceil(((t.getTime()-r.getTime())/864e5+1)/7);return`${t.getUTCFullYear()}-W${String(i).padStart(2,`0`)}`}function kL(e,t=!0){let n=_L(e);return{telemetryEnabled:t,firstRunAt:n.toISOString(),cohort:OL(n),sentMilestones:[]}}function AL(e={}){let t=e.env??process.env,n=e.clientName?.toLowerCase()??``;return n.includes(`claude`)?`claude-code`:n.includes(`cursor`)?`cursor`:n.includes(`codex`)?`codex`:e.clientName?`unknown`:t.CLAUDECODE||t.CLAUDE_CODE?`claude-code`:t.CURSOR_AGENT||t.CURSOR_TRACE_ID||t.CURSOR_SESSION_ID?`cursor`:t.CODEX_HOME||t.CODEX_THREAD_ID||t.CODEX_SANDBOX?`codex`:`cli`}function jL(e){return e.agent??AL({clientName:e.clientName,env:gL(e)})}function ML(e){return e.replace(/^v/,``).split(`.`)[0]||`unknown`}function NL(e,t,n={},r={}){return{event:e,version:r.version??fL,agent:jL(r),os:r.processPlatform??p(),arch:r.processArch??d(),node:ML(r.nodeVersion??process.version),cohort:t.cohort,schema:1,...n.errorCategory?{errorCategory:n.errorCategory}:{},...n.report?{report:n.report}:{}}}function PL(e,t){let n=t.fetch??globalThis.fetch;try{n(t.endpoint??`https://seoskill.dev/api/t`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify(e),signal:AbortSignal.timeout(2e3)}).catch(()=>void 0)}catch{}}function FL(e){let t=vL(e),n=xL(gL(e)),r=EL(t);return n?{enabled:!1,reason:n,stateFile:t,...r.kind===`valid`?{state:r.state}:{}}:r.kind===`invalid`?{enabled:!1,reason:`invalid_state`,stateFile:t}:r.kind===`valid`&&!r.state.telemetryEnabled?{enabled:!1,reason:`local_setting`,stateFile:t,state:r.state}:{enabled:!0,stateFile:t,...r.kind===`valid`?{state:r.state}:{}}}function IL(e={}){return FL(e)}function LL(e,t={}){let n=vL(t),r=EL(n),i=r.kind===`valid`?r.state:kL(t,e);return i.telemetryEnabled=e,DL(n,i),FL(t)}function RL(e){let t=FL(e);return t.enabled?t.state:void 0}function zL(e={}){let t=FL(e);if(!t.enabled)return t;let n=t.stateFile,r=t.state??kL(e),i=[];if(!r.sentMilestones.includes(`first_run`)){try{(e.notice??(e=>process.stderr.write(e)))(`SEO Skill collects anonymous usage data to improve the tool (event name,
553
+ VALUES (?, ?, ?, ?, ?, ?, ?)`).run(e,u,JSON.stringify(c),JSON.stringify(g),_,Date.now(),Date.now()+i),{data:g,usage:{provider:`Semrush`,units:_,unitLabel:`units`,estimatedUsd:oL(_),calls:1}}}function cL(e){let[t,n]=e;if(!t||!n)return{phrase:``};let r=Object.fromEntries(t.map((e,t)=>[e,n[t]]));return{phrase:r.Ph??``,volume:Number(r.Nq??0)||void 0,cpc:Number(r.Cp??0)||void 0,competition:Number(r.Co??0)||void 0,difficulty:Number(r.Kd??0)||void 0,results:Number(r.Nr??0)||void 0}}function lL(e){let[t,...n]=e;return t?n.map(e=>{let n=Object.fromEntries(t.map((t,n)=>[t,e[n]]));return{phrase:n.Ph??``,volume:Number(n.Nq??0)||void 0,difficulty:Number(n.Kd??0)||void 0,cpc:Number(n.Cp??0)||void 0,competition:Number(n.Co??0)||void 0,url:n.Ur,domain:n.Dn,position:Number(n.Po??0)||void 0}}):[]}var uL=class{name=`semrush`;capabilities={overview:!0,batchOverview:!0,related:!0,broadMatch:!0,questions:!0,difficulty:!0,urlKeywords:!0,domainKeywords:!0,maxBatchSize:100};async keywordOverview(e,t={}){return sL(`phrase_this`,{phrase:e,database:t.database??`us`,export_columns:`Ph,Nq,Cp,Co,Nr,Td,Kd`},cL,7*864e5,10,t.refresh)}async batchKeywordOverview(e,t={}){return sL(`phrase_these`,{phrase:e.join(`;`),database:t.database??`us`,export_columns:`Ph,Nq,Cp,Co,Nr,Td,Kd`},e=>lL(e).map(e=>({...e,phrase:e.phrase})),7*864e5,10,t.refresh)}async relatedKeywords(e,t={}){return sL(`phrase_related`,{phrase:e,database:t.database??`us`,display_limit:20,export_columns:`Ph,Nq,Kd,Cp,Co`},lL,14*864e5,40,t.refresh)}async questions(e,t={}){return sL(`phrase_questions`,{phrase:e,database:t.database??`us`,display_limit:20,export_columns:`Ph,Nq,Kd,Cp,Co`},lL,14*864e5,40,t.refresh)}async keywordDifficulty(e,t={}){return sL(`phrase_kdi`,{phrase:e.join(`;`),database:t.database??`us`,export_columns:`Ph,Kd`},e=>lL(e).map(e=>({phrase:e.phrase,kd:e.difficulty??0})),7*864e5,50,t.refresh)}async domainKeywords(e,t={}){return sL(`domain_organic`,{domain:e,database:t.database??`us`,display_limit:100,export_columns:`Ph,Nq,Cp,Co,Kd,Po,Ur,Dn`},lL,7*864e5,10,t.refresh)}async urlKeywords(e,t={}){return sL(`url_organic`,{url:e,database:t.database??`us`,display_limit:100,export_columns:`Ph,Nq,Cp,Co,Kd,Po,Ur,Dn`},lL,7*864e5,10,t.refresh)}};function dL(e){let t=nt(),n=e??t.providers.prefer;if(n===`authoritative`&&t.providers.semrushApiKey)return new uL;if(n===`cheap`&&t.providers.dataForSeoLogin&&t.providers.dataForSeoPassword)return new iL;if(t.providers.semrushApiKey)return new uL;if(t.providers.dataForSeoLogin&&t.providers.dataForSeoPassword)return new iL}new class{getGoogleOAuthClient(){let e=gt();if(e)return{clientId:e.clientId,clientSecret:e.clientSecret,source:`byo`};let t=nt(),n=process.env.SEO_GOOGLE_CLIENT_ID??process.env.GSC_CLIENT_ID??bt.clientId??t.auth.sharedClientId,r=process.env.SEO_GOOGLE_CLIENT_SECRET??process.env.GSC_CLIENT_SECRET??bt.clientSecret??t.auth.sharedClientSecret;if(!(!n||!r))return{clientId:n,clientSecret:r,source:`shared`}}readGoogleTokens(){return dt()}writeGoogleTokens(e){return ft(e)}deleteGoogleTokens(){return pt()}};const fL=`0.2.8`,pL=`affected-urls.agent-readiness.ai-readiness.ai-referrals.ai-search-scorecard.audit-page.audit-urls.cannibalisation.community-intent.compare-crawls.content-optimization.crawl-diff.site-crawl.ctr-underperformers.decaying-pages.setup-check.entity-readiness.explain-crawl-issue.geo-gaps.crawl-report.index-coverage.index-coverage-plan.index-monitor.index-watch.internal-links.link-recovery.crawl-history.crawler-rules.llms-txt-audit.generate-llms-txt.measure-change.monthly-report.okf-build.okf-validate.page-opportunities.performance-audit.pseo-audit.query-clusters.quick-wins.redirect-trace.narrative-report.second-page.segment-impact.striking-distance.seo-to-ai-query.top-fixes.traffic-anomaly.update-correlation.search-performance-overview.monthly-action-plan.refresh-priorities.technical-watch.update-postmortem`.split(`.`),mL=[{days:1,event:`active_d1`},{days:7,event:`active_d7`},{days:30,event:`active_d30`}],hL=[`GITHUB_ACTIONS`,`GITLAB_CI`,`CIRCLECI`,`TRAVIS`,`BUILDKITE`,`JENKINS_URL`,`TEAMCITY_VERSION`,`TF_BUILD`,`BITBUCKET_BUILD_NUMBER`,`CODEBUILD_BUILD_ID`];function gL(e){return e.env??process.env}function _L(e){return e.now??new Date}function vL(e){return e.stateFile??Ie().telemetryStateFile}function yL(e){return e===`1`||e?.toLowerCase()===`true`}function bL(e=process.env){return e.NODE_ENV===`test`||!!e.NODE_TEST_CONTEXT||yL(e.CI)||hL.some(t=>!!e[t])}function xL(e){if(bL(e))return`ci`;if(yL(e.DO_NOT_TRACK))return`do_not_track`;if(yL(e.SEOSKILL_TELEMETRY_DISABLED)||yL(e.SEO_TELEMETRY_DISABLED))return`environment`}function SL(e){return typeof e==`string`&&[`first_run`,`setup_complete`,`first_audit_complete`,`active_d1`,`active_d7`,`active_d30`].includes(e)}function CL(e){return typeof e==`string`&&pL.includes(e)}function wL(e){return typeof e==`string`&&/^\d{4}-W(?:0[1-9]|[1-4]\d|5[0-3])$/.test(e)}function TL(e){if(!e||typeof e!=`object`||Array.isArray(e))return!1;let t=e;return typeof t.telemetryEnabled==`boolean`&&typeof t.firstRunAt==`string`&&Number.isFinite(Date.parse(t.firstRunAt))&&wL(t.cohort)&&Array.isArray(t.sentMilestones)&&t.sentMilestones.every(SL)}function EL(e){if(!i(e))return{kind:`missing`};let t=Ke(e);return TL(t)?{kind:`valid`,state:t}:{kind:`invalid`}}function DL(e,t){qe(e,t,384)}function OL(e){let t=new Date(Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate())),n=t.getUTCDay()||7;t.setUTCDate(t.getUTCDate()+4-n);let r=new Date(Date.UTC(t.getUTCFullYear(),0,1)),i=Math.ceil(((t.getTime()-r.getTime())/864e5+1)/7);return`${t.getUTCFullYear()}-W${String(i).padStart(2,`0`)}`}function kL(e,t=!0){let n=_L(e);return{telemetryEnabled:t,firstRunAt:n.toISOString(),cohort:OL(n),sentMilestones:[]}}function AL(e={}){let t=e.env??process.env,n=e.clientName?.toLowerCase()??``;return n.includes(`claude`)?`claude-code`:n.includes(`cursor`)?`cursor`:n.includes(`codex`)?`codex`:e.clientName?`unknown`:t.CLAUDECODE||t.CLAUDE_CODE?`claude-code`:t.CURSOR_AGENT||t.CURSOR_TRACE_ID||t.CURSOR_SESSION_ID?`cursor`:t.CODEX_HOME||t.CODEX_THREAD_ID||t.CODEX_SANDBOX?`codex`:`cli`}function jL(e){return e.agent??AL({clientName:e.clientName,env:gL(e)})}function ML(e){return e.replace(/^v/,``).split(`.`)[0]||`unknown`}function NL(e,t,n={},r={}){return{event:e,version:r.version??fL,agent:jL(r),os:r.processPlatform??p(),arch:r.processArch??d(),node:ML(r.nodeVersion??process.version),cohort:t.cohort,schema:1,...n.errorCategory?{errorCategory:n.errorCategory}:{},...n.report?{report:n.report}:{}}}function PL(e,t){let n=t.fetch??globalThis.fetch;try{n(t.endpoint??`https://seoskill.dev/api/t`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify(e),signal:AbortSignal.timeout(2e3)}).catch(()=>void 0)}catch{}}function FL(e){let t=vL(e),n=xL(gL(e)),r=EL(t);return n?{enabled:!1,reason:n,stateFile:t,...r.kind===`valid`?{state:r.state}:{}}:r.kind===`invalid`?{enabled:!1,reason:`invalid_state`,stateFile:t}:r.kind===`valid`&&!r.state.telemetryEnabled?{enabled:!1,reason:`local_setting`,stateFile:t,state:r.state}:{enabled:!0,stateFile:t,...r.kind===`valid`?{state:r.state}:{}}}function IL(e={}){return FL(e)}function LL(e,t={}){let n=vL(t),r=EL(n),i=r.kind===`valid`?r.state:kL(t,e);return i.telemetryEnabled=e,DL(n,i),FL(t)}function RL(e){let t=FL(e);return t.enabled?t.state:void 0}function zL(e={}){let t=FL(e);if(!t.enabled)return t;let n=t.stateFile,r=t.state??kL(e),i=[];if(!r.sentMilestones.includes(`first_run`)){try{(e.notice??(e=>process.stderr.write(e)))(`SEO Skill collects anonymous usage data to improve the tool (event name,
554
554
  report id, version, agent, OS, architecture, Node major, and install week;
555
555
  never URLs, report data, or identifiers of any kind).
556
556
  Docs: https://seoskill.dev/telemetry
557
557
  Disable: seo telemetry disable (or set DO_NOT_TRACK=1)
558
558
  `)}catch{return{enabled:!1,reason:`invalid_state`,stateFile:n}}r.sentMilestones.push(`first_run`),i.push(`first_run`)}let a=(_L(e).getTime()-Date.parse(r.firstRunAt))/864e5;for(let e of mL)a>=e.days&&!r.sentMilestones.includes(e.event)&&(r.sentMilestones.push(e.event),i.push(e.event));try{DL(n,r)}catch{return{enabled:!1,reason:`invalid_state`,stateFile:n}}for(let t of i)PL(NL(t,r,{},e),e);return{enabled:!0,stateFile:n,state:r}}function BL(e,t,n){let r=RL(n);r&&PL(NL(e,r,t,n),n)}function VL(e,t={}){CL(e)&&BL(`audit_start`,{report:e},t)}function HL(e,t={}){if(!CL(e))return;let n=RL(t);if(n&&(PL(NL(`audit_complete`,n,{report:e},t),t),!n.sentMilestones.includes(`first_audit_complete`))){n.sentMilestones.push(`first_audit_complete`);try{DL(vL(t),n)}catch{return}PL(NL(`first_audit_complete`,n,{report:e},t),t)}}function UL(e,t,n={}){CL(e)&&BL(`audit_failed`,{errorCategory:t,report:e},n)}function WL(e={}){let t=RL(e);if(!(!t||t.sentMilestones.includes(`setup_complete`))){t.sentMilestones.push(`setup_complete`);try{DL(vL(e),t)}catch{return}PL(NL(`setup_complete`,t,{},e),e)}}function GL(e){if(e instanceof Error&&e.name===`AbortError`)return`crawl_timeout`;let t=e&&typeof e==`object`&&`code`in e?String(e.code):void 0;return t===`ACCESS_DENIED`||t===`AUTH_CONFIG_REQUIRED`||t===`AUTH_EXPIRED`||t===`AUTH_REQUIRED`?`auth`:t===`INVALID_INPUT`||t===`PROPERTY_NOT_FOUND`?`config`:t===`OPTIONAL_PROVIDER_UNAVAILABLE`||t===`PROVIDER_UNAVAILABLE`||t===`RATE_LIMITED`?`network`:`unknown`}const KL=D.iso.date(`Use a YYYY-MM-DD date.`),qL=D.string().regex(/^\d{4}-(?:0[1-9]|1[0-2])$/,`Use a YYYY-MM month.`);function JL(e,t){return e===void 0?t:e}const YL={site:{mcp:`site`,schema:D.string()},days:{mcp:`days`,schema:D.number().optional()},recentDays:{mcp:`recentDays`,schema:D.number().optional()},range:{mcp:`range`,schema:D.number().optional()},limit:{mcp:`limit`,schema:D.number().optional()},checkLimit:{mcp:`checkLimit`,schema:D.number().optional()},minImpressions:{mcp:`minImpressions`,schema:D.number().optional()},includeBrand:{mcp:`includeBrand`,schema:D.boolean().optional()},verifyContent:{mcp:`verifyContent`,schema:D.boolean().optional()},verifyLimit:{mcp:`verifyLimit`,schema:D.number().optional()},js:{mcp:`js`,schema:D.boolean().optional()},fetchConcurrency:{mcp:`fetchConcurrency`,schema:D.number().optional()},fetchIntervalCap:{mcp:`fetchIntervalCap`,schema:D.number().optional()},fetchIntervalMs:{mcp:`fetchIntervalMs`,schema:D.number().optional()},refresh:{mcp:`refresh`,schema:D.boolean().optional()}};function XL(e){return Object.fromEntries(e.map(e=>{let t=YL[e];return[t.mcp,t.schema]}))}function ZL(e){return JSON.stringify(e,null,2)}function Q(e){let t=De(e);return{content:[{type:`text`,text:`Error: ${t.error.message}`}],structuredContent:t,isError:!0}}function $(e,t,n={}){return{content:[{type:`text`,text:n.markdown??e}],structuredContent:t}}const QL=D.union([KL,D.string().regex(/^(?:today|yesterday|\d+daysAgo)$/)]).describe(`Use YYYY-MM-DD or a Google Analytics relative date.`),$L={site:D.string().trim().min(1),days:D.number().int().min(1).max(548).optional(),startDate:KL.optional(),endDate:KL.optional(),limit:D.number().int().min(1).max(100).optional(),minImpressions:D.number().int().min(0).max(1e9).optional(),maxRows:D.number().int().min(1).max(5e4).optional(),brandTerms:D.array(D.string().trim().min(1)).max(100).optional(),includeBrand:D.boolean().optional(),refresh:D.boolean().optional()};function eR(e,t={}){e.registerTool(`seo_ai_referrals`,{description:`Find AI referral traffic detected in Google Analytics. maxRows bounds retained provider rows; resultLimit bounds returned landing pages.`,inputSchema:{property:D.string().trim().min(1),startDate:QL.optional(),endDate:QL.optional(),maxRows:D.number().int().min(1).max(1e5).describe(`Maximum retained Google Analytics rows. If limit is also supplied, both values must match.`).optional(),limit:D.number().int().min(1).max(1e5).describe(`Legacy alias for maxRows. If maxRows is also supplied, both values must match.`).optional(),resultLimit:D.number().int().min(1).max(1e3).describe(`Maximum ranked landing pages returned. Defaults to 25.`).optional(),refresh:D.boolean().optional()}},async({property:e,startDate:n,endDate:r,maxRows:i,limit:a,resultLimit:o,refresh:s})=>{try{if(i!==void 0&&a!==void 0&&i!==a)throw new k(`INVALID_INPUT`,`maxRows and the legacy limit option must match when both are provided.`);let c=await(t.aiReferralsReport??er)({property:e,startDate:n,endDate:r,maxRows:i,limit:a,resultLimit:o,refresh:s});return $(`${Z(c.summary.sessions,`AI referral session`)} detected across ${Z(c.summary.sources,`source`)}; evidence is ${c.dataStatus}.`,c)}catch(e){return Q(e)}}),e.registerTool(`seo_page_opportunities`,{description:`Find first-party traffic growth opportunities for one URL from GSC and page content`,inputSchema:{...XL([`site`,`days`,`limit`,`minImpressions`,`includeBrand`,`verifyContent`,`refresh`,`js`]),days:D.number().int().min(1).max(548).optional(),limit:D.number().int().min(1).max(100).optional(),minImpressions:D.number().int().positive().optional(),url:D.string().url()}},async({site:e,url:n,days:r,limit:i,minImpressions:a,includeBrand:o,verifyContent:s,refresh:c,js:l})=>{try{let u=await(t.pageOpportunitiesReport??Sy)({site:e,url:n,days:r,limit:i,minImpressions:a,includeBrand:o,verifyContent:s,refresh:c,js:JL(l,`auto`)});return $(u.summary.verdict,u)}catch(e){return Q(e)}}),e.registerTool(`seo_content_optimization`,{description:`Build a content optimization brief for one URL from first-party GSC queries and page content checks`,inputSchema:{...XL([`site`,`days`,`limit`,`minImpressions`,`includeBrand`,`verifyContent`,`refresh`,`js`]),days:D.number().int().min(1).max(548).optional(),limit:D.number().int().min(1).max(100).optional(),minImpressions:D.number().int().positive().optional(),url:D.string().url()}},async({site:e,url:n,days:r,limit:i,minImpressions:a,includeBrand:o,verifyContent:s,refresh:c,js:l})=>{try{let u=await(t.contentOptimizationReport??My)({site:e,url:n,days:r,limit:i,minImpressions:a,includeBrand:o,verifyContent:s,refresh:c,js:JL(l,`auto`)});return $(u.summary.verdict,u)}catch(e){return Q(e)}}),e.registerTool(`seo_performance_audit`,{description:`Run a local Lighthouse lab audit and optional device-specific CrUX field Core Web Vitals. Configure CrUX with SEO_CRUX_API_KEY.`,inputSchema:{url:D.string().url(),strategy:D.enum([`mobile`,`desktop`]).optional(),refresh:D.boolean().optional()}},async({url:e,strategy:t,refresh:n})=>{try{let r=await oN({url:e,strategy:t,refresh:n});return $(r.headline,r)}catch(e){return Q(e)}}),e.registerTool(`seo_to_ai_query`,{description:`Convert retained GSC queries into deterministic AI monitoring-prompt suggestions with source completeness and heuristic caveats`,inputSchema:$L},async({site:e,days:n,startDate:r,endDate:i,limit:a,minImpressions:o,maxRows:s,brandTerms:c,includeBrand:l,refresh:u})=>{try{let d=await(t.seoToAiQueryReport??BF)({site:e,days:n,startDate:r,endDate:i,limit:a,minImpressions:o,maxRows:s,brandTerms:c,includeBrand:l,refresh:u});return $(`${Z(d.summary.prompts,`monitoring-prompt suggestion`)} generated from ${Z(d.summary.returnedQueries,`retained GSC query`)}; evidence is ${d.dataStatus}.`,d)}catch(e){return Q(e)}}),e.registerTool(`seo_community_intent`,{description:`Classify explicit forum, review, comparison, experience, and recommendation language in retained GSC queries as review hypotheses`,inputSchema:$L},async({site:e,days:n,startDate:r,endDate:i,limit:a,minImpressions:o,maxRows:s,brandTerms:c,includeBrand:l,refresh:u})=>{try{let d=await(t.communityIntentReport??mv)({site:e,days:n,startDate:r,endDate:i,limit:a,minImpressions:o,maxRows:s,brandTerms:c,includeBrand:l,refresh:u});return $(`Evidence status: ${d.dataStatus}. ${d.summary.verdict}`,d)}catch(e){return Q(e)}})}function tR(e){if(!(e.fetchConcurrency===void 0&&e.fetchIntervalCap===void 0&&e.fetchIntervalMs===void 0))return{concurrency:e.fetchConcurrency,intervalCap:e.fetchIntervalCap,intervalMs:e.fetchIntervalMs}}function nR(e,t={}){let n=e.requestEvidenceStatus===`available`?`Processed ${e.requests.length} URL requests into ${e.summary.totalPages} unique documents.`:e.requestEvidenceStatus===`partial`?`Observed ${e.requests.length} URL requests and retained ${e.summary.totalPages} unique documents; some started requests were still in flight when the crawl stopped.`:`Loaded ${e.summary.totalPages} unique documents; request evidence is unavailable for this legacy report.`,r={id:e.id,definitionId:e.definitionId,headline:`${n} Found ${e.summary.highIssues} high, ${e.summary.mediumIssues} medium, and ${e.summary.lowIssues} low issues.`,status:e.status,requestEvidenceStatus:e.requestEvidenceStatus,configHash:e.configHash,summary:e.summary,dataSources:e.dataSources,ai:e.ai?{robotsTxt:e.ai.robotsTxt,llmsTxt:e.ai.llmsTxt,agentResources:e.ai.agentResources}:void 0,topFixes:Uw(e,{limit:10}),reviewObservations:Ww(e,{limit:10}),warnings:e.warnings,caveats:e.caveats};return t.includeIssues&&(r.issues=e.issues),t.includePages&&(r.requests=e.requests,r.pages=e.pages),r}function rR(e){if(!e.value||e.value===`latest`||e.value===`previous`){let t=kS({site:e.site,limit:20}).filter(t=>t.id!==e.skipId),n=e.value===`previous`?t[1]:t[0];return n?AS(n.id):void 0}return AS(e.value)}function iR(e,t){if(e&&t)throw new k(`INVALID_INPUT`,`Use either url or reportId, not both.`)}function aR(e){e.registerTool(`seo_crawl_site`,{description:`Crawl a site and run technical SEO checks. The compact result separates prioritised fixes from review observations. Set includePages/includeIssues for raw data.`,inputSchema:{url:D.string().url(),site:D.string().optional(),googleAnalyticsPropertyId:D.string().optional(),maxPages:D.number().int().positive().optional(),maxDepth:D.number().int().nonnegative().optional(),concurrency:D.number().int().positive().optional(),fetchIntervalCap:D.number().int().positive().optional(),fetchIntervalMs:D.number().int().positive().optional(),refresh:D.boolean().optional(),include:D.array(D.string()).optional(),exclude:D.array(D.string()).optional(),useSitemap:D.boolean().optional(),respectRobots:D.boolean().optional(),js:D.boolean().optional(),includePages:D.boolean().optional(),includeIssues:D.boolean().optional(),saveReport:D.boolean().optional()}},async({url:e,site:t,googleAnalyticsPropertyId:n,maxPages:r,maxDepth:i,concurrency:a,fetchIntervalCap:o,fetchIntervalMs:s,refresh:c,include:l,exclude:u,useSitemap:d,respectRobots:f,js:p,includePages:m,includeIssues:h,saveReport:g})=>{try{let _=await ow({url:e,site:t,googleAnalyticsPropertyId:n,maxPages:r,maxDepth:i,concurrency:a,refresh:c,fetchRate:tR({fetchConcurrency:a,fetchIntervalCap:o,fetchIntervalMs:s}),include:l,exclude:u,useSitemap:d,respectRobots:f,js:!!p}),v=g?OS(_):void 0;return $(`Crawl complete for ${e}. Found ${_.issues.length} issues across ${_.summary.totalPages} pages.`,{...nR(_,{includePages:m,includeIssues:h}),...v?{saved:v}:{}})}catch(e){return Q(e)}}),e.registerTool(`seo_audit_urls`,{description:`Audit an explicit list of URLs with technical SEO checks. The compact result separates prioritised fixes from review observations. Set includePages/includeIssues for raw data.`,inputSchema:{urls:D.array(D.string().url()).min(1),site:D.string().optional(),googleAnalyticsPropertyId:D.string().optional(),maxPages:D.number().int().positive().optional(),concurrency:D.number().int().positive().optional(),fetchIntervalCap:D.number().int().positive().optional(),fetchIntervalMs:D.number().int().positive().optional(),refresh:D.boolean().optional(),includePages:D.boolean().optional(),includeIssues:D.boolean().optional(),saveReport:D.boolean().optional()}},async({urls:e,site:t,googleAnalyticsPropertyId:n,maxPages:r,concurrency:i,fetchIntervalCap:a,fetchIntervalMs:o,refresh:s,includePages:c,includeIssues:l,saveReport:u})=>{try{let d=await ow({url:e[0]??``,urls:e,mode:`list`,site:t,googleAnalyticsPropertyId:n,maxPages:r??e.length,concurrency:i,refresh:s,fetchRate:tR({fetchConcurrency:i,fetchIntervalCap:a,fetchIntervalMs:o}),useSitemap:!1}),f=u?OS(d):void 0;return $(`URL audit complete. Found ${d.issues.length} issues across ${d.summary.totalPages} pages.`,{...nR(d,{includePages:c,includeIssues:l}),...f?{saved:f}:{}})}catch(e){return Q(e)}}),e.registerTool(`seo_top_fixes`,{description:`Return top technical SEO fixes for a URL by running a compact crawl.`,inputSchema:{url:D.string().url().optional(),reportId:D.string().optional(),site:D.string().optional(),maxPages:D.number().int().positive().optional(),maxDepth:D.number().int().nonnegative().optional(),fetchIntervalCap:D.number().int().positive().optional(),fetchIntervalMs:D.number().int().positive().optional(),refresh:D.boolean().optional(),category:D.string().optional(),limit:D.number().int().min(1).max(1e3).optional()}},async({url:e,reportId:t,site:n,maxPages:r,maxDepth:i,fetchIntervalCap:a,fetchIntervalMs:o,refresh:s,category:c,limit:l})=>{try{iR(e,t);let u=e?await ow({url:e,site:n,maxPages:r,maxDepth:i,refresh:s,fetchRate:tR({fetchIntervalCap:a,fetchIntervalMs:o})}):t?AS(t):MS(n);if(!u)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let d=Uw(u,{category:c,limit:l});return $(`Found ${d.length} top fix groups for ${u.config.url}.`,{url:u.config.url,reportId:u.id,summary:u.summary,dataSources:u.dataSources,topFixes:d,warnings:u.warnings,caveats:u.caveats})}catch(e){return Q(e)}}),e.registerTool(`seo_explain_issue`,{description:`Explain a crawler rule in plain English: why it matters, how to fix it, and how to verify it.`,inputSchema:{ruleId:D.string()}},async({ruleId:e})=>{let t=fx(e);return t?$(`Rule guidance for ${e}.`,t):Q(`Unknown rule: ${e}`)}),e.registerTool(`seo_list_rules`,{description:`List crawler rule ids and guidance metadata. Use this before the explain-crawl-issue report when you need valid rule ids.`,inputSchema:{category:zT.optional()}},async({category:e})=>{let t=dx().filter(t=>!e||t.category===e);return $(`Found ${t.length} crawler rules.`,{rules:t})}),e.registerTool(`seo_affected_urls`,{description:`Return affected URLs for a saved or freshly crawled report, filtered by rule, category, or severity.`,inputSchema:{url:D.string().url().optional(),reportId:D.string().optional(),site:D.string().optional(),ruleId:D.string().optional(),category:D.string().optional(),severity:D.string().optional(),maxPages:D.number().int().positive().optional(),fetchIntervalCap:D.number().int().positive().optional(),fetchIntervalMs:D.number().int().positive().optional(),refresh:D.boolean().optional(),limit:D.number().int().positive().optional()}},async({url:e,reportId:t,site:n,ruleId:r,category:i,severity:a,maxPages:o,fetchIntervalCap:s,fetchIntervalMs:c,refresh:l,limit:u})=>{try{let d=e?await ow({url:e,site:n,maxPages:o,refresh:l,fetchRate:tR({fetchIntervalCap:s,fetchIntervalMs:c})}):t?AS(t):MS(n);if(!d)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let f=Py(d,{ruleId:r,category:i,severity:a,limit:u});return $(`Returned ${f.selection.returnedRows} of ${f.selection.totalMatchedRows} affected URLs for ${d.config.url}.`,{url:d.config.url,reportId:d.id,dataSources:d.dataSources,affectedUrls:f.affectedUrls,selection:f.selection,warnings:d.warnings,caveats:d.caveats})}catch(e){return Q(e)}}),e.registerTool(`seo_geo_gaps`,{description:`Return crawl, indexability, and page-level snippet controls for Google AI Search eligibility, with optional page observations kept separate. No detected restriction does not guarantee selection or visibility.`,inputSchema:{url:D.string().url().optional(),reportId:D.string().optional(),site:D.string().optional(),maxPages:D.number().int().positive().optional(),fetchIntervalCap:D.number().int().positive().optional(),fetchIntervalMs:D.number().int().positive().optional(),refresh:D.boolean().optional(),limit:D.number().int().positive().optional()}},async({url:e,reportId:t,site:n,maxPages:r,fetchIntervalCap:i,fetchIntervalMs:a,refresh:o,limit:s})=>{try{let c=e?await ow({url:e,site:n,maxPages:r,refresh:o,fetchRate:tR({fetchIntervalCap:i,fetchIntervalMs:a})}):t?AS(t):MS(n);if(!c)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let l=Fb(c,{limit:s});return $(`Returned ${l.selection.returnedPages} of ${l.selection.totalMatchedPages} AI Search technical eligibility gap pages for ${c.config.url}; evidence is ${l.dataStatus}.`,{url:c.config.url,reportId:c.id,...l})}catch(e){return Q(e)}}),e.registerTool(`seo_agent_readiness`,{description:`Check a content site for stable Markdown alternatives, agent discovery, crawler access, and identity evidence without an aggregate score.`,inputSchema:{url:D.string().url().optional(),reportId:D.string().optional(),site:D.string().optional(),maxPages:D.number().int().positive().optional(),fetchIntervalCap:D.number().int().positive().optional(),fetchIntervalMs:D.number().int().positive().optional(),refresh:D.boolean().optional()}},async({url:e,reportId:t,site:n,maxPages:r,fetchIntervalCap:i,fetchIntervalMs:a,refresh:o})=>{try{iR(e,t);let s=e?await ow({url:e,site:n,maxPages:r,refresh:o,checkAgentDiscovery:!0,useSitemap:!0,checkExternal:!1,fetchRate:tR({fetchIntervalCap:i,fetchIntervalMs:a})}):t?AS(t):MS(n);if(!s)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let c=mb(s);return $(c.headline,c)}catch(e){return Q(e)}}),e.registerTool(`seo_ai_readiness`,{description:`Return AI-search technical evidence and optional observations without an aggregate score or citation prediction.`,inputSchema:{url:D.string().url().optional(),reportId:D.string().optional(),site:D.string().optional(),maxPages:D.number().int().positive().optional(),fetchIntervalCap:D.number().int().positive().optional(),fetchIntervalMs:D.number().int().positive().optional(),refresh:D.boolean().optional()}},async({url:e,reportId:t,site:n,maxPages:r,fetchIntervalCap:i,fetchIntervalMs:a,refresh:o})=>{try{let s=e?await ow({url:e,site:n,maxPages:r,refresh:o,fetchRate:tR({fetchIntervalCap:i,fetchIntervalMs:a})}):t?AS(t):MS(n);if(!s)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let c=jb(s);return $(c.headline,c)}catch(e){return Q(e)}}),e.registerTool(`seo_ai_search_scorecard`,{description:`Score AI-search technical readiness 0-100 from one crawl using this tool's own weighted checks. The score is a heuristic summary, not a Google or AI-engine requirement, eligibility verdict, or ranking predictor. Unknown checks are excluded and a partial crawl cannot reach 100.`,inputSchema:{url:D.string().url().optional(),reportId:D.string().optional(),site:D.string().optional(),maxPages:D.number().int().positive().optional(),fetchIntervalCap:D.number().int().positive().optional(),fetchIntervalMs:D.number().int().positive().optional(),refresh:D.boolean().optional()}},async({url:e,reportId:t,site:n,maxPages:r,fetchIntervalCap:i,fetchIntervalMs:a,refresh:o})=>{try{let s=e?await ow({url:e,site:n,maxPages:r,refresh:o,fetchRate:tR({fetchIntervalCap:i,fetchIntervalMs:a})}):t?AS(t):MS(n);if(!s)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let c=sx(s);return $(c.headline,c)}catch(e){return Q(e)}}),e.registerTool(`seo_llms_txt_audit`,{description:`Inspect optional llms.txt presence from a saved or freshly crawled report and return current Google guidance plus candidate pages.`,inputSchema:{url:D.string().url().optional(),reportId:D.string().optional(),site:D.string().optional(),maxPages:D.number().int().positive().optional(),fetchIntervalCap:D.number().int().positive().optional(),fetchIntervalMs:D.number().int().positive().optional(),refresh:D.boolean().optional()}},async({url:e,reportId:t,site:n,maxPages:r,fetchIntervalCap:i,fetchIntervalMs:a,refresh:o})=>{try{let s=e?await ow({url:e,site:n,maxPages:r,refresh:o,fetchRate:tR({fetchIntervalCap:i,fetchIntervalMs:a})}):t?AS(t):MS(n);if(!s)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let c=Ub(s);return $(c.headline,c)}catch(e){return Q(e)}}),e.registerTool(`seo_llms_txt_generate`,{description:`Generate an llms.txt draft from a saved or freshly crawled report. Returns content and metadata.`,inputSchema:{url:D.string().url().optional(),reportId:D.string().optional(),site:D.string().optional(),maxPages:D.number().int().positive().optional(),maxUrls:D.number().int().positive().optional(),tokenBudget:D.number().int().positive().optional(),exclude:D.array(D.string()).optional(),title:D.string().optional(),description:D.string().optional(),fetchIntervalCap:D.number().int().positive().optional(),fetchIntervalMs:D.number().int().positive().optional(),refresh:D.boolean().optional()}},async({url:e,reportId:t,site:n,maxPages:r,maxUrls:i,tokenBudget:a,exclude:o,title:s,description:c,fetchIntervalCap:l,fetchIntervalMs:u,refresh:d})=>{try{let f=e?await ow({url:e,site:n,maxPages:r,refresh:d,fetchRate:tR({fetchIntervalCap:l,fetchIntervalMs:u})}):t?AS(t):MS(n);if(!f)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let p=Wb(f,{maxUrls:i,tokenBudget:a,exclude:o,title:s,description:c});return $(`Generated llms.txt with ${p.includedUrls} URLs.`,p)}catch(e){return Q(e)}}),e.registerTool(`seo_entity_readiness`,{description:`Check schema, sameAs, social profile, author/date, and naming signals from a saved or freshly crawled report.`,inputSchema:{url:D.string().url().optional(),reportId:D.string().optional(),site:D.string().optional(),maxPages:D.number().int().positive().optional(),fetchIntervalCap:D.number().int().positive().optional(),fetchIntervalMs:D.number().int().positive().optional(),refresh:D.boolean().optional()}},async({url:e,reportId:t,site:n,maxPages:r,fetchIntervalCap:i,fetchIntervalMs:a,refresh:o})=>{try{let s=e?await ow({url:e,site:n,maxPages:r,refresh:o,fetchRate:tR({fetchIntervalCap:i,fetchIntervalMs:a})}):t?AS(t):MS(n);if(!s)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let c=Cb(s);return $(c.headline,c)}catch(e){return Q(e)}}),e.registerTool(`seo_okf_build`,{description:`Build a compact OKF site knowledge manifest from a saved or freshly crawled report. Set includeFiles for bounded inline markdown.`,inputSchema:{url:D.string().url().optional(),reportId:D.string().optional(),site:D.string().optional(),maxPages:D.number().int().positive().optional(),maxConcepts:D.number().int().min(1).max(100).optional(),includeFiles:D.boolean().optional(),title:D.string().min(1).max(200).optional(),fetchIntervalCap:D.number().int().positive().optional(),fetchIntervalMs:D.number().int().positive().optional(),refresh:D.boolean().optional()}},async({url:e,reportId:t,site:n,maxPages:r,maxConcepts:i,includeFiles:a,title:o,fetchIntervalCap:s,fetchIntervalMs:c,refresh:l})=>{try{let u=e?await ow({url:e,site:n,maxPages:r,refresh:l,fetchRate:tR({fetchIntervalCap:s,fetchIntervalMs:c})}):t?AS(t):MS(n);if(!u)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let d=OT(u,{maxConcepts:i??(a?25:100),title:o}),f=NT(d.files),{files:p,...m}=d;return $(`Built OKF bundle with ${d.conceptCount} concepts.`,{manifest:{...m,filePaths:p.map(e=>e.path)},...a?{files:p}:{},validation:f})}catch(e){return Q(e)}}),e.registerTool(`seo_okf_validate`,{description:`Validate OKF markdown files supplied by an agent. Use the okf-build report to generate files from a crawl.`,inputSchema:{files:D.array(D.object({path:D.string().min(1).max(512),content:D.string().max(2e6)})).min(1).max(5006)}},async({files:e})=>{let t=NT(e);return $(t.valid?`Bundle passes seo OKF checks.`:`Bundle has seo OKF issues.`,{validation:t,explanation:PT(t)})}),e.registerTool(`seo_get_crawl_report`,{description:`Return a saved crawl report by id, or the latest saved report for an optional site. Compact by default.`,inputSchema:{id:D.string().optional(),site:D.string().optional(),includePages:D.boolean().optional(),includeIssues:D.boolean().optional()}},async({id:e,site:t,includePages:n,includeIssues:r})=>{try{let i=e?AS(e):MS(t);return i?$(`Saved crawl report ${i.id}.`,{...nR(i,{includePages:n,includeIssues:r}),url:i.config.url,site:i.site,generatedAt:i.generatedAt}):Q(e?`No saved crawl report found for ${e}.`:`No saved crawl reports found.`)}catch(e){return Q(e)}}),e.registerTool(`seo_list_crawl_reports`,{description:`List saved crawl report metadata, optionally filtered by GSC site/property.`,inputSchema:{site:D.string().optional(),limit:D.number().int().positive().optional()}},async({site:e,limit:t})=>{try{let n=kS({site:e,limit:t});return $(`Found ${n.length} saved crawl reports.`,{reports:n})}catch(e){return Q(e)}}),e.registerTool(`seo_compare_crawl_reports`,{description:`Compare two saved crawl reports and return page, issue, score, and plain-English change summaries.`,inputSchema:{after:D.string().optional(),before:D.string().optional(),site:D.string().optional()}},async({after:e,before:t,site:n})=>{try{let r=rR({value:e??`latest`,site:n});if(!r)return Q(`No newer crawl report found.`);let i=rR({value:t??`previous`,site:r.site??n,skipId:r.id});if(!i)return Q(`No baseline crawl report found. Save at least two reports or pass before.`);let a=_T({before:i,after:r});return $(a.headline,a)}catch(e){return Q(e)}})}function oR(e,t={}){e.registerTool(`seo_doctor`,{description:`Check the local database, auth, scopes, config, and defaults`,inputSchema:{}},async()=>{try{let e=await rL();return $(e.ok?`Local seo setup is ready.`:`Local seo setup needs attention.`,e)}catch(e){return Q(e)}}),e.registerTool(`seo_diagnose_property`,{description:`Run end-to-end property diagnosis across anomaly, update, segment, decay, cannibalisation, and opportunity signals`,inputSchema:{...XL([`site`,`days`,`recentDays`,`limit`,`includeBrand`,`refresh`])}},async({site:e,days:t,recentDays:n,limit:r,includeBrand:i,refresh:a})=>{try{let o=await Mk({site:e,days:t,recentDays:n,limit:r,includeBrand:i,refresh:a});return $(`Diagnosis ${o.dataStatus}. Update attribution: ${o.summary.updateAttribution}.`,o)}catch(e){return Q(e)}}),e.registerTool(`seo_segment_impact`,{description:`Compare matched retained GSC segments across adjacent equal-length periods without treating missing rows as zero`,inputSchema:{site:D.string().trim().min(1),days:D.number().int().min(1).max(240).optional(),limit:D.number().int().min(1).max(100).optional(),refresh:D.boolean().optional(),dimension:D.enum([`page`,`query`,`country`,`device`]).optional(),compareDays:D.number().int().min(1).max(240).optional(),startDate:KL.optional(),endDate:KL.optional(),maxRows:D.number().int().min(1).max(25e4).optional(),unmatchedLimit:D.number().int().min(0).max(100).optional()}},async({site:e,dimension:n,days:r,compareDays:i,startDate:a,endDate:o,limit:s,maxRows:c,unmatchedLimit:l,refresh:u})=>{try{let d=await(t.segmentImpact??iD)({site:e,dimension:n,days:r,compareDays:i,startDate:a,endDate:o,limit:s,maxRows:c,unmatchedLimit:l,refresh:u});return $(d.summary.verdict,d)}catch(e){return Q(e)}}),e.registerTool(`seo_striking_distance`,{description:`Find position 11-20 query/page opportunities from GSC`,inputSchema:{...XL([`site`,`days`,`minImpressions`,`limit`,`verifyContent`,`verifyLimit`,`includeBrand`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),days:D.number().int().min(1).max(548).optional(),minImpressions:D.number().int().min(0).max(1e9).optional(),limit:D.number().int().min(1).max(100).optional(),verifyLimit:D.number().int().min(0).max(100).optional(),brandTerms:D.array(D.string().trim().min(1).max(200)).max(20).optional(),fetchConcurrency:D.number().int().min(1).max(16).optional(),fetchIntervalCap:D.number().int().min(1).max(60).optional(),fetchIntervalMs:D.number().int().min(100).max(6e4).optional()}},async({site:e,days:t,minImpressions:n,limit:r,verifyContent:i,verifyLimit:a,includeBrand:o,brandTerms:s,js:c,fetchConcurrency:l,fetchIntervalCap:u,fetchIntervalMs:d,refresh:f})=>{try{let p=await HO({site:e,days:t,minImpressions:n,limit:r,verifyContent:i,verifyLimit:a,includeBrand:o,brandTerms:s,js:JL(c,void 0),rate:tR({fetchConcurrency:l,fetchIntervalCap:u,fetchIntervalMs:d}),refresh:f});return $(p.summary.verdict,p)}catch(e){return Q(e)}}),e.registerTool(`seo_traffic_anomaly`,{description:`Detect statistically significant recent GSC traffic movement`,inputSchema:{...XL([`site`,`days`,`recentDays`,`refresh`])}},async({site:e,days:t,recentDays:n,refresh:r})=>{try{let i=await yk({site:e,days:t,recentDays:n,refresh:r});return $(`${i.anomalies.filter(e=>e.significant).length} significant anomalies found.`,i)}catch(e){return Q(e)}}),e.registerTool(`seo_update_correlate`,{description:`Overlay recent traffic anomalies against official Google ranking update windows`,inputSchema:{...XL([`site`,`days`,`recentDays`,`refresh`]),paddingDays:D.number().optional()}},async({site:e,days:t,recentDays:n,paddingDays:r,refresh:i})=>{try{let a=await bk({site:e,days:t,recentDays:n,paddingDays:r,refresh:i});return $(`Classification: ${a.classification}.`,a)}catch(e){return Q(e)}})}function sR(e){e.registerTool(`seo_content_groups`,{description:`List, create, or delete reusable page/query groups`,inputSchema:{action:D.enum([`list`,`add`,`delete`]),site:D.string().optional(),id:D.string().optional(),name:D.string().optional(),dimension:D.enum([`page`,`query`]).optional(),matchType:D.enum([`equals`,`contains`,`regex`]).optional(),pattern:D.string().optional()}},async({action:e,site:t,id:n,name:r,dimension:i,matchType:a,pattern:o})=>{try{if(e===`list`){let e=Pk(t);return $(`${e.length} content groups found.`,{groups:e})}if(e===`delete`){if(!n)throw Error(`Pass id to delete a content group.`);let e=Ik(n);return $(e?`Content group deleted.`:`Not found.`,{id:n,deleted:e})}if(!t||!r||!o)throw Error(`Pass site, name, and pattern to add a content group.`);let s=Nk({site:t,name:r,dimension:i,matchType:a,pattern:o});return $(`Content group created: ${s.name}.`,s)}catch(e){return Q(e)}}),e.registerTool(`seo_change_log`,{description:`List or record SEO annotations and site changes`,inputSchema:{action:D.enum([`list`,`add`,`delete`]),site:D.string().optional(),id:D.string().optional(),limit:D.number().optional(),scope:D.enum([`site`,`page`,`query`,`group`]).optional(),target:D.string().optional(),title:D.string().optional(),description:D.string().optional(),changedAt:D.string().optional()}},async({action:e,site:t,id:n,limit:r,scope:i,target:a,title:o,description:s,changedAt:c})=>{try{if(e===`list`){let e=ZO({site:t,limit:r});return $(`${e.length} changes found.`,{changes:e})}if(e===`delete`){if(!n)throw Error(`Pass id to delete a change.`);let e=$O(n);return $(e?`Change deleted.`:`Not found.`,{id:n,deleted:e})}if(!t||!i||!a||!o)throw Error(`Pass site, scope, target, and title to add a change.`);let l=XO({site:t,scope:i,target:a,title:o,description:s,changedAt:c??new Date().toISOString().slice(0,10)});return $(`Change recorded: ${l.title}.`,l)}catch(e){return Q(e)}}),e.registerTool(`seo_measure_change`,{description:`Measure a saved or ad hoc SEO change with equal finalized GSC windows and optional Google Analytics/control evidence`,inputSchema:{id:D.string().min(1).optional(),site:D.string().min(1).optional(),scope:D.enum([`site`,`page`,`query`,`group`]).optional(),target:D.string().min(1).optional(),title:D.string().min(1).optional(),changedAt:KL.optional(),googleAnalyticsPropertyId:D.string().min(1).optional(),controlScope:D.enum([`site`,`page`,`query`,`group`]).optional(),controlTarget:D.string().min(1).optional(),controlTitle:D.string().min(1).optional(),beforeDays:D.number().int().min(1).max(548).optional(),afterDays:D.number().int().min(1).max(548).optional(),refresh:D.boolean().optional()}},async({id:e,site:t,scope:n,target:r,title:i,changedAt:a,googleAnalyticsPropertyId:o,controlScope:s,controlTarget:c,controlTitle:l,beforeDays:u,afterDays:d,refresh:f})=>{try{let p=await uA({id:e,site:t,scope:n,target:r,title:i,changedAt:a,googleAnalyticsPropertyId:o,controlScope:s,controlTarget:c,controlTitle:l,beforeDays:u,afterDays:d,refresh:f});return $(`Measurement ${p.dataStatus}. Verdict: ${p.verdict}. Compared ${p.window.effectiveDays}/${p.window.requestedDays} finalized days.`,p)}catch(e){return Q(e)}})}function cR(e){e.registerTool(`seo_crawl_diff`,{description:`Crawl a bounded same-origin URL set and compare technical/page changes with the previous run`,inputSchema:{startUrl:D.string().url(),site:D.string().optional(),limit:D.number().optional(),refresh:D.boolean().optional(),js:D.boolean().optional()}},async({startUrl:e,site:t,limit:n,refresh:r,js:i})=>{try{let a=await nj({startUrl:e,site:t,limit:n,refresh:r,js:JL(i,`auto`)});return $(`Crawled ${a.summary.crawled} URLs. ${a.summary.changed} changed vs previous run.`,a)}catch(e){return Q(e)}}),e.registerTool(`seo_index_watch`,{description:`Inspect a bounded URL set against Google's indexed snapshot and separate current issues, regressions, recoveries, and operational failures`,inputSchema:{site:D.string(),urls:D.array(D.string().url()).min(1).max(100),languageCode:D.string().min(1).max(35).optional(),dailyLimit:D.number().int().min(1).max(2e3).optional()}},async({site:e,urls:t,languageCode:n,dailyLimit:r})=>{try{let i=await cM({site:e,urls:t,languageCode:n,dailyLimit:r});return $(`Inspected ${i.summary.inspected} URLs. ${i.summary.currentIssues} current reviews, ${i.summary.regressions} regressions, ${i.summary.failed} failed, ${i.summary.quotaBlocked} quota-blocked, ${i.summary.deferred} deferred.`,i)}catch(e){return Q(e)}}),e.registerTool(`seo_index_coverage_plan`,{description:`Plan sitemap URL allocation across GSC properties and suggest URL-prefix properties for better URL Inspection coverage`,inputSchema:{site:D.string(),sitemaps:D.array(D.string().url()).min(1).max(20),properties:D.array(D.string()).optional(),dailyLimit:D.number().int().min(1).max(2e3).optional(),targetCycleDays:D.number().int().min(1).max(365).optional(),maxUrls:D.number().int().min(1).max(25e4).optional()}},async({site:e,sitemaps:t,properties:n,dailyLimit:r,targetCycleDays:i,maxUrls:a})=>{try{let o=await Kj({site:e,sitemaps:t,properties:n,dailyLimit:r,targetCycleDays:i,maxUrls:a});return $(`Planned ${o.summary.urlCount} sitemap URLs across ${o.summary.properties} properties. ${o.summary.suggestedProperties} suggested properties.`,o)}catch(e){return Q(e)}}),e.registerTool(`seo_index_coverage`,{description:`Compare a saved crawl and sitemap with Search Console page results, then choose representative URLs for URL Inspection`,inputSchema:{site:D.string().min(1),crawlReportId:D.string().min(1).optional(),sitemaps:D.array(D.string().url()).min(1).max(20).optional(),days:D.number().int().min(1).max(548).optional(),rowLimit:D.number().int().min(1).max(25e4).optional(),maxSitemapUrls:D.number().int().min(1).max(25e4).optional(),itemsPerSection:D.number().int().min(1).max(1e3).optional(),templateClusters:D.number().int().min(1).max(200).optional(),templateSamples:D.number().int().min(1).max(25).optional(),refresh:D.boolean().optional()}},async({site:e,crawlReportId:t,sitemaps:n,days:r,rowLimit:i,maxSitemapUrls:a,itemsPerSection:o,templateClusters:s,templateSamples:c,refresh:l})=>{try{let u=await Pj({site:e,crawlReportId:t,sitemaps:n,days:r,rowLimit:i,maxSitemapUrls:a,itemsPerSection:o,templateClusters:s,templateSamples:c,refresh:l});return $(`${u.summary.retainedSearchVisibleUrls} URLs appeared in the returned Search Console data. ${u.summary.crawlableCandidatesWithoutRetainedSearchVisibility} crawlable URLs need evidence review before choosing representative URL Inspection checks.`,u)}catch(e){return Q(e)}}),e.registerTool(`seo_index_monitor`,{description:`Run bounded, locally quota-enforced URL Inspection monitoring from XML sitemaps and store Google's indexed snapshots`,inputSchema:{site:D.string(),sitemaps:D.array(D.string().url()).min(1).max(20),properties:D.array(D.string()).optional(),dailyLimit:D.number().int().min(1).max(2e3).optional(),inspectLimit:D.number().int().min(1).max(100).optional(),maxUrls:D.number().int().min(1).max(25e4).optional(),languageCode:D.string().min(1).max(35).optional()}},async({site:e,sitemaps:t,properties:n,dailyLimit:r,inspectLimit:i,maxUrls:a,languageCode:o})=>{try{let s=await mM({site:e,sitemaps:t,properties:n,dailyLimit:r,inspectLimit:i,maxUrls:a,languageCode:o});return $(`Inventory ${s.summary.inventoryUrls}; ${s.summary.due} due, ${s.summary.selected} selected, ${s.summary.inspected} inspected, ${s.summary.unselectedDue} due but not selected. ${s.summary.currentIssues} current reviews in selected results, ${s.summary.regressions} regressions, ${s.summary.failed} failed, ${s.summary.quotaBlocked} quota-blocked, ${s.summary.deferred} deferred.`,s)}catch(e){return Q(e)}}),e.registerTool(`seo_redirect_trace`,{description:`Trace redirects and report final indexability/canonical issues for a URL`,inputSchema:{url:D.string().url(),maxHops:D.number().optional(),refresh:D.boolean().optional(),js:D.boolean().optional()}},async({url:e,maxHops:t,refresh:n,js:r})=>{try{let i=await xM({url:e,maxHops:t,refresh:n,js:JL(r,`auto`)});return $(`Traced ${i.summary.hops} hops. Final status ${i.summary.finalStatus}. ${i.summary.issues.length} issues.`,i)}catch(e){return Q(e)}}),e.registerTool(`seo_link_recover`,{description:`Find GSC search-value URLs that are now broken, blocked, or poorly redirected`,inputSchema:{...XL([`site`,`days`,`limit`,`minImpressions`,`refresh`,`js`]),minClicks:D.number().optional()}},async({site:e,days:t,limit:n,minClicks:r,minImpressions:i,refresh:a,js:o})=>{try{let s=await EM({site:e,days:t,limit:n,minClicks:r,minImpressions:i,refresh:a,js:JL(o,`auto`)});return $(`Checked ${s.summary.checked} search-value URLs. ${s.summary.recoverable} recoverable issues.`,s)}catch(e){return Q(e)}})}function lR(e,t={}){e.registerTool(`seo_cannibal`,{description:`Find multi-URL query exposure candidates for intent and technical review`,inputSchema:{...XL([`site`,`days`,`limit`,`minImpressions`,`includeBrand`,`refresh`]),days:D.number().int().min(1).max(548).optional(),limit:D.number().int().min(1).max(100).optional(),minImpressions:D.number().int().min(0).max(1e9).optional(),brandTerms:D.array(D.string().trim().min(1).max(200)).max(20).optional()}},async({site:e,days:t,limit:n,minImpressions:r,brandTerms:i,includeBrand:a,refresh:o})=>{try{let s=await ID({site:e,days:t,limit:n,minImpressions:r,brandTerms:i,includeBrand:a,refresh:o});return $(s.summary.verdict,s)}catch(e){return Q(e)}}),e.registerTool(`seo_decaying`,{description:`Find query/page click declines observed in both retained GSC windows and investigation signals`,inputSchema:{...XL([`site`,`days`,`limit`,`includeBrand`,`refresh`]),days:D.number().int().min(1).max(548).optional(),limit:D.number().int().min(1).max(100).optional(),comparison:D.enum([`previous-period`,`year-over-year`]).optional(),minDropPct:D.number().min(0).max(100).optional(),minPreviousClicks:D.number().min(0).max(1e9).optional(),minClickLoss:D.number().min(0).max(1e9).optional(),brandTerms:D.array(D.string().trim().min(1).max(200)).max(20).optional()}},async({site:e,days:t,limit:n,comparison:r,minDropPct:i,minPreviousClicks:a,minClickLoss:o,brandTerms:s,includeBrand:c,refresh:l})=>{try{let u=await QD({site:e,days:t,limit:n,comparison:r,minDropPct:i,minPreviousClicks:a,minClickLoss:o,brandTerms:s,includeBrand:c,refresh:l});return $(u.summary.verdict,u)}catch(e){return Q(e)}}),e.registerTool(`seo_quick_wins`,{description:`Find quick-win CTR/position opportunities`,inputSchema:{...XL([`site`,`days`,`limit`,`minImpressions`,`verifyContent`,`verifyLimit`,`includeBrand`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),days:D.number().int().min(1).max(548).optional(),limit:D.number().int().min(1).max(100).optional(),minImpressions:D.number().int().min(0).max(1e9).optional(),verifyLimit:D.number().int().min(0).max(100).optional(),brandTerms:D.array(D.string().trim().min(1).max(200)).max(20).optional(),fetchConcurrency:D.number().int().min(1).max(16).optional(),fetchIntervalCap:D.number().int().min(1).max(60).optional(),fetchIntervalMs:D.number().int().min(100).max(6e4).optional()}},async({site:e,days:n,limit:r,minImpressions:i,brandTerms:a,verifyContent:o,verifyLimit:s,includeBrand:c,js:l,fetchConcurrency:u,fetchIntervalCap:d,fetchIntervalMs:f,refresh:p})=>{try{let m=await(t.quickWinsReport??SO)({site:e,days:n,limit:r,minImpressions:i,brandTerms:a,verifyContent:o,verifyLimit:s,includeBrand:c,js:JL(l,void 0),rate:tR({fetchConcurrency:u,fetchIntervalCap:d,fetchIntervalMs:f}),refresh:p});return $(m.summary.verdict,m)}catch(e){return Q(e)}}),e.registerTool(`seo_internal_links`,{description:`Find verified internal-link review candidates for a target URL`,inputSchema:{...XL([`site`,`days`,`limit`,`checkLimit`,`minImpressions`,`includeBrand`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),targetUrl:D.string().url().refine(e=>/^https?:\/\//.test(e),`Use an HTTP(S) URL.`),days:D.number().int().min(1).max(548).optional(),limit:D.number().int().min(1).max(100).optional(),checkLimit:D.number().int().min(1).max(200).optional(),minImpressions:D.number().min(0).max(1e9).optional(),brandTerms:D.array(D.string().trim().min(1).max(200)).max(20).optional(),fetchConcurrency:D.number().int().min(1).max(16).optional(),fetchIntervalCap:D.number().int().min(1).max(60).optional(),fetchIntervalMs:D.number().int().min(100).max(6e4).optional()}},async({site:e,targetUrl:n,days:r,limit:i,checkLimit:a,minImpressions:o,brandTerms:s,includeBrand:c,js:l,fetchConcurrency:u,fetchIntervalCap:d,fetchIntervalMs:f,refresh:p})=>{try{let m=await(t.internalLinksReport??BA)({site:e,targetUrl:n,days:r,limit:i,checkLimit:a,minImpressions:o,brandTerms:s,includeBrand:c,js:JL(l,void 0),rate:tR({fetchConcurrency:u,fetchIntervalCap:d,fetchIntervalMs:f}),refresh:p});return $(m.summary.verdict,m)}catch(e){return Q(e)}}),e.registerTool(`seo_ctr_underperformers`,{description:`Find high-impression queries underperforming CTR expectations`,inputSchema:{...XL([`site`,`minImpressions`,`limit`,`includeBrand`,`refresh`]),site:D.string().trim().min(1),minImpressions:D.number().int().min(1).max(1e9).optional(),limit:D.number().int().min(1).max(100).optional(),brandTerms:D.array(D.string().trim().min(1).max(200)).max(20).optional()}},async({site:e,minImpressions:n,limit:r,brandTerms:i,includeBrand:a,refresh:o})=>{try{let s=await(t.ctrUnderperformersReport??DE)({site:e,minImpressions:n,limit:r,brandTerms:i,includeBrand:a,refresh:o});return $(s.summary.verdict,s)}catch(e){return Q(e)}}),e.registerTool(`seo_query_cluster`,{description:`Cluster queries by token overlap`,inputSchema:{...XL([`site`,`includeBrand`,`minImpressions`,`limit`,`refresh`]),site:D.string().trim().min(1),scope:D.string().trim().min(1).max(2e3).optional(),minImpressions:D.number().int().min(1).max(1e9).optional(),limit:D.number().int().min(1).max(100).optional(),brandTerms:D.array(D.string().trim().min(1).max(200)).max(20).optional()}},async({site:e,scope:n,brandTerms:r,includeBrand:i,minImpressions:a,limit:o,refresh:s})=>{try{let c=await(t.queryClusterReport??kP)({site:e,scope:n,brand:r?.[0],brandTerms:r,includeBrand:i,minImpressions:a,limit:o,refresh:s});return $(c.summary.verdict,c)}catch(e){return Q(e)}}),e.registerTool(`semrush_call`,{description:`Raw-ish Semrush passthrough for supported keyword endpoints`,inputSchema:{endpoint:D.enum([`phrase_this`,`phrase_related`,`phrase_questions`]),phrase:D.string()}},async({endpoint:e,phrase:t})=>{try{let n=dL(`authoritative`);if(!n)throw Error(`No keyword provider configured.`);let r=e===`phrase_this`?await n.keywordOverview(t):e===`phrase_related`?await n.relatedKeywords?.(t):await n.questions?.(t);if(!r)throw Error(`Endpoint ${e} is not supported by the active provider.`);return $(ZL(r.data),r)}catch(e){return Q(e)}})}function uR(e){return{schemaVersion:e.schemaVersion,methodology:e.methodology,site:e.site,generatedAt:e.generatedAt,range:e.range,dataStatus:e.dataStatus,summary:e.summary,source:e.source,selection:e.selection,caveats:e.caveats,warnings:e.warnings,templates:e.templates.map(e=>({signature:e.signature,urlCount:e.urlCount,sampleUrls:e.sampleUrls,population:e.population,verdict:e.verdict,confidence:e.confidence,metrics:e.metrics,crawl:{requested:e.crawl.requested,attempted:e.crawl.attempted,usable:e.crawl.usable,blockedOrFailed:e.crawl.blockedOrFailed},inspection:{requested:e.inspection.requested,attempted:e.inspection.attempted,indexed:e.inspection.indexed,notIndexed:e.inspection.notIndexed,unknown:e.inspection.unknown},evidence:e.evidence,recommendation:e.recommendation}))}}function dR(e){e.registerTool(`seo_pseo_audit`,{description:`Audit pSEO templates with first-party GSC, optional crawl, and optional URL Inspection evidence`,inputSchema:{site:D.string().min(1),days:D.number().int().min(1).max(548).optional(),sitemaps:D.array(D.string().url()).max(20).optional(),maxSitemapUrls:D.number().int().min(1).max(1e5).optional(),templateLimit:D.number().int().min(1).max(100).optional(),minimumTemplateUrls:D.number().int().min(2).max(100).optional(),minimumTemplateShare:D.number().min(0).max(1).optional(),minimumTemplateImpressions:D.number().min(0).max(1e9).optional(),crawlSamples:D.number().int().min(0).max(10).optional(),inspectSamples:D.number().int().min(0).max(10).optional(),brandTerms:D.array(D.string().min(1).max(200)).max(20).optional(),includeBrand:D.boolean().optional(),refresh:D.boolean().optional(),js:D.boolean().optional(),fetchConcurrency:D.number().int().min(1).max(20).optional(),fetchIntervalCap:D.number().int().min(1).max(100).optional(),fetchIntervalMs:D.number().int().min(100).max(6e4).optional(),detail:D.enum([`summary`,`full`]).optional()}},async({site:e,days:t,sitemaps:n,templateLimit:r,maxSitemapUrls:i,minimumTemplateUrls:a,minimumTemplateShare:o,minimumTemplateImpressions:s,crawlSamples:c,inspectSamples:l,brandTerms:u,includeBrand:d,refresh:f,js:p,fetchConcurrency:m,fetchIntervalCap:h,fetchIntervalMs:g,detail:_})=>{try{let v=await nP({site:e,days:t,sitemaps:n,maxSitemapUrls:i,templateLimit:r,minimumTemplateUrls:a,minimumTemplateShare:o,minimumTemplateImpressions:s,crawlSamples:c,inspectSamples:l,brandTerms:u,includeBrand:d,refresh:f,js:JL(p,`auto`),rate:{concurrency:m,intervalCap:h,intervalMs:g}}),y=_===`full`;return $(`${Z(v.summary.templates,`pSEO template`)} audited from ${Z(v.summary.gscPages,`GSC page`)}.`,y?{report:v,presentation:cP(v)}:{report:uR(v)},y?{markdown:lP(v)}:void 0)}catch(e){return Q(e)}})}const fR={"affected-urls":{readOrder:[`dataSources for provider availability`,`selection totalMatchedRows, returnedRows, and truncated`,`the requested rule, category, and severity filters`,`each row rule, category, severity, and crawler evidence`,`warnings and caveats`],doNotClaim:[`A capped or partial crawl only supports "affected among evaluated pages", not sitewide.`,`Unavailable clicks, impressions, or sessions are not a real zero.`,`truncated true means more matching issue rows exist than were returned.`],verify:`Re-run the same bounded crawl after deployment and confirm the rule no longer triggers on the affected URLs.`,related:[{id:`explain-crawl-issue`,reason:`Explains the rule before fixes.`},{id:`top-fixes`,reason:`Ranks issues when there are too many.`},{id:`crawl-report`,reason:`Opens the source crawl snapshot.`}]},"agent-readiness":{readOrder:[`profile, profileApplicability, dataStatus, and crawl caveats`,`summary and topActions`,`HTML and Markdown representation checks with affected URLs`,`Agent Skills, llms.txt, route manifest, crawler access, and identity evidence`],doNotClaim:[`A clean content profile is not proof of indexing, rankings, AI mentions, citations, or selection.`,`API, application, and commerce checks marked notApplicable were outside this content-site run. They did not fail.`,`An allowed crawler token does not prove that a service fetched or used the page.`,`Optional discovery files are not Google ranking requirements.`],verify:`Run the same content profile against the same public route scope after deployment and confirm each failed or warning check changed.`,related:[{id:`ai-readiness`,reason:`Checks AI search technical eligibility.`},{id:`llms-txt-audit`,reason:`Inspects llms.txt in more detail.`},{id:`entity-readiness`,reason:`Reviews identity evidence.`},{id:`site-crawl`,reason:`Creates a reusable crawl baseline.`}]},"ai-readiness":{readOrder:[`dataStatus and crawl caveats`,`each check evaluated flag`,`hard access, indexability, and snippet conflicts`,`semantic structure and answerable content as observations`],doNotClaim:[`unknown means evidence was unavailable, not a pass or a fail.`,`Valid JSON-LD is syntax evidence, not entity recognition or rich-result eligibility.`,`A clean report is not indexing, ranking, AI visibility, selection, or citations.`],verify:`Re-crawl at the same scope after a change and confirm the access or indexability signal resolved.`,related:[{id:`geo-gaps`,reason:`Lists affected URLs for access rules.`},{id:`entity-readiness`,reason:`Reviews identity signals.`},{id:`ai-search-scorecard`,reason:`Scores the same evidence.`}]},"ai-referrals":{readOrder:[`dataStatus, range, methodology, and dataSource partialReasons`,`possiblyTruncated and per-query statuses`,`selection.landingPages before treating landingPages as complete`,`sources observedSessionSources for attribution evidence`],doNotClaim:[`An empty result is not proof that no AI system sent or cited the site.`,`Referral sessions did not necessarily cause conversions and are not all AI traffic.`,`totalUsers is not additive and must not be reconstructed by summing rows.`],verify:`Compare complete like-for-like Google Analytics periods and check whether high-session landing pages answer the referring context.`,related:[{id:`seo-to-ai-query`,reason:`Builds monitoring prompts.`},{id:`ai-readiness`,reason:`Checks technical AI access.`}]},"ai-search-scorecard":{readOrder:[`partial, crawlComplete, and excluded before the number`,`score, which is null when no check had known evidence`,`each check status, observed evidence, and finding`,`methodology weights, statusCredit, and formula`],doNotClaim:[`The score is a heuristic over this tool's own checks, not a Google or AI-engine requirement, eligibility verdict, or ranking prediction.`,`unknown checks are excluded from the score and are never failures.`,`A blocked AI crawler token can be an intentional publisher choice.`],verify:`Re-crawl at the same scope after a change and confirm the per-check evidence moved.`,related:[{id:`ai-readiness`,reason:`Gives the per-page evidence.`},{id:`geo-gaps`,reason:`Lists affected URLs.`},{id:`entity-readiness`,reason:`Reviews identity signals.`}]},"audit-page":{readOrder:[`fetchDiagnostics, warnings, and the final fetched URL`,`observed page fields and structured-data findings`,`issues and recommendations`],doNotClaim:[`One page cannot support a sitewide conclusion.`,`Title pixel width is a review estimator, not a Google limit.`,`The fetched version is not proof that Google indexed that version.`],verify:`Re-fetch the URL after the edit and confirm the observed field changed.`,related:[{id:`redirect-trace`,reason:`Debugs the redirect chain.`},{id:`performance-audit`,reason:`Adds page speed evidence.`},{id:`content-optimization`,reason:`Builds a query-led brief.`}]},"audit-urls":{readOrder:[`requested versus attempted, fetched, failed, retained, and omitted counts`,`response evidence and issue summaries`,`requestEvidenceStatus, warnings, and caveats`],doNotClaim:[`A successful sample does not prove the same template works everywhere.`,`A failed request may be transient network, not a persistent defect.`,`A bounded audit is never complete site coverage.`],verify:`Re-run the identical URL list after deployment; a URL dropped from the request is not a verified fix.`,related:[{id:`audit-page`,reason:`Inspects one page in depth.`},{id:`site-crawl`,reason:`Discovers pages instead.`},{id:`redirect-trace`,reason:`Traces a redirecting URL.`}]},cannibalisation:{readOrder:[`dataStatus and both source.pageExposure and source.propertyDemand with validation counts and completeness`,`selection to see why groups were excluded or suppressed`,`per-page clicks, impressions, position, impression share, reviewContext, and HHI`,`priority.score and suggestedOwnerUrl`],doNotClaim:[`URL overlap is a review candidate, not proven harmful cannibalisation.`,`suggestedOwnerUrl is a low-confidence mechanical pick that requires intent review.`,`priority.score is a heuristic, not estimated click lift, and a partial source cannot support an all-clear.`],verify:`Compare intent and live SERPs for the overlapping URLs before consolidating anything.`,related:[{id:`query-clusters`,reason:`Groups related query themes.`},{id:`internal-links`,reason:`Clarifies link ownership.`},{id:`content-optimization`,reason:`Differentiates page intent.`}]},"community-intent":{readOrder:[`dataStatus, source.completeness, warnings, and caveats`,`selection.classifiedRows for every match and selection.returnedRows for the limited set`,`per-item intent, signals, and matchedTerms`],doNotClaim:[`Intent labels are low-confidence English language heuristics, not verified intent.`,`The classifier does not inspect the live SERP or identify the ranking URL.`,`A capped or partial zero is inconclusive, not absence of demand.`],verify:`Retrieve GSC query and page rows to find the ranking URL and check its content against the observed query language.`,related:[{id:`content-optimization`,reason:`Turns intent into a brief.`},{id:`query-clusters`,reason:`Groups the demand themes.`},{id:`page-opportunities`,reason:`Reviews one ranking URL.`}]},"compare-crawls":{readOrder:[`comparability.status and its matching flags`,`before and after preserved config, requestScope, and caps`,`top-level completeness and truncated reason codes`,`summary, pageChanges, and issueChanges`],doNotClaim:[`An absent page from a capped, skipped, failed, or differently scoped crawl is not proven removed.`,`Source row caps limit joined GSC or Google Analytics evidence even when the document crawl completed.`,`A change between snapshots is not proof the release caused it.`],verify:`Re-run the same definition and request scope after deployment and confirm the delta.`,related:[{id:`crawl-diff`,reason:`Compares against the previous run.`},{id:`crawl-history`,reason:`Finds saved report ids.`},{id:`affected-urls`,reason:`Lists URLs for a changed rule.`}]},"content-optimization":{readOrder:[`sourceReport dataStatus, selection, benchmark.possiblyTruncated, and verification.status`,`summary.score and primaryIntent as heuristics`,`topActions separating technical-check and unverified from verified observations`,`intentMix and the source query metrics`],doNotClaim:[`estimatedClickLift and score are prioritisation heuristics, not forecasts or quality grades.`,`Query wording is not a content mandate.`,`Adding suggested wording is not guaranteed traffic.`],verify:`Fetch the page after the edit and confirm the missing answer block or clearer framing is present.`,related:[{id:`page-opportunities`,reason:`Adds query-level opportunities.`},{id:`internal-links`,reason:`Finds contextual links.`},{id:`quick-wins`,reason:`Ranks CTR opportunities.`}]},"crawl-diff":{readOrder:[`run versus previousRun, where a missing previous run means a baseline`,`start URL, limit, counts, timestamps, rendering, and warnings for both runs`,`summary counts traced into items before, after, and changes`,`recommendations as review actions`],doNotClaim:[`A removed URL may be outside the current discovered set or limit rather than deleted.`,`A fetch or extraction failure cannot support a resolved issue.`,`A bounded diff is not an inventory-wide audit and does not show ranking impact.`],verify:`Repeat with comparable scope after a fix and confirm the changed item.`,related:[{id:`compare-crawls`,reason:`Compares two explicit snapshots.`},{id:`technical-watch`,reason:`Runs a recurring watch.`},{id:`affected-urls`,reason:`Lists URLs for a rule.`}]},"crawl-history":{readOrder:[`each report id, site, start URL, creation time, and status`,`total pages and issue count`,`configuration hash and storage version`],doNotClaim:[`Counts are orientation metadata, not severity or completeness.`,`A smaller page count could reflect a scope change, cap, or failure, not a real site change.`,`A saved list can expose stale data.`],verify:`Load a chosen id with crawl-report and inspect its caveats before analysis.`,related:[{id:`crawl-report`,reason:`Loads a saved snapshot.`},{id:`compare-crawls`,reason:`Compares two snapshots.`},{id:`site-crawl`,reason:`Creates a new crawl.`}]},"crawl-report":{readOrder:[`id, source URL and site, generatedAt, definition id, config hash, status, and requestEvidenceStatus`,`summary counts and pageLimitReached`,`data sources, warnings, and caveats`],doNotClaim:[`A report can be internally valid yet stale, capped, or partial and cannot prove the current live state.`,`Loading a report is a local storage read, not a fresh check.`],verify:`Run a new crawl with the same configuration and retain both report ids to confirm a change.`,related:[{id:`top-fixes`,reason:`Ranks the issue queue.`},{id:`affected-urls`,reason:`Lists URLs for a rule.`},{id:`compare-crawls`,reason:`Compares two snapshots.`}]},"crawler-rules":{readOrder:[`each rule id, title, and category`,`default severity as triage metadata`,`guidance metadata`],doNotClaim:[`Listing a rule does not mean the site triggered it or that the convention is a search-engine requirement.`,`Default severity is generic triage metadata, not actual priority.`,`Similarly named rules may not share source semantics.`],verify:`Pass the exact rule id to explain-crawl-issue or affected-urls to confirm it applies to real evidence.`,related:[{id:`explain-crawl-issue`,reason:`Returns the rule contract.`},{id:`affected-urls`,reason:`Returns crawl evidence for a rule.`}]},"ctr-underperformers":{readOrder:[`dataStatus, range, source.completeness, possiblyTruncated, and validation counts`,`selection before the queue`,`per-item query, URL, impressions, position, observed CTR, target CTR, and benchmark source`,`estimated shortfall`],doNotClaim:[`The shortfall is directional prioritisation math, not a traffic forecast.`,`Page-one average position hides day, device, geography, and result-feature variation.`,`A partial empty result is not an all-clear.`],verify:`Inspect the current SERP and displayed title link, then compare a complete later period after testing framing.`,related:[{id:`quick-wins`,reason:`Ranks position 4-10 opportunities.`},{id:`page-opportunities`,reason:`Reviews one URL.`},{id:`content-optimization`,reason:`Builds a page brief.`}]},"decaying-pages":{readOrder:[`dataStatus, ranges, and source.completeness`,`current and previous fetched rows, caps, truncation, and invalid rows in selection`,`summary.observedRetainedQueryClickLoss versus the returned subset`,`per-item clicks, impressions, CTR, position, and signals`],doNotClaim:[`A diagnosis such as lost_position or lost_ctr labels correlated movement, not a cause.`,`Missing retained rows are not zero traffic.`,`Do not sum capped rows into a property-wide loss.`],verify:`Check deployments, live indexability, and SERP composition for the affected URLs before editing.`,related:[{id:`refresh-priorities`,reason:`Combines decline signals.`},{id:`segment-impact`,reason:`Shows where movement concentrates.`},{id:`measure-change`,reason:`Evaluates a fix later.`}]},"entity-readiness":{readOrder:[`dataStatus, evaluatedPages versus crawlPages, and caveats`,`entities.schemaTypes`,`sameAs, sameAsByType, and socialProfiles`,`authors`],doNotClaim:[`Checks are informational observations with coverage, not pass or fail requirements.`,`Skipped or non-indexable pages are not evidence of absence.`,`These signals do not prove Knowledge Graph inclusion, ownership, expertise, rankings, or citations.`],verify:`Confirm names, URLs, authorship, and dates with the publisher, then re-crawl the same scope.`,related:[{id:`ai-readiness`,reason:`Checks technical AI access.`},{id:`ai-search-scorecard`,reason:`Scores the evidence.`},{id:`geo-gaps`,reason:`Lists affected URLs.`}]},"explain-crawl-issue":{readOrder:[`category, default severity, and why it matters`,`fix steps and ignored impact`,`verification method and agent hints`],doNotClaim:[`Guidance describes the checked condition, not a search-engine penalty or guaranteed ranking effect.`,`Default severity is generic prioritization metadata, not actual urgency.`,`The rule definition alone does not prove any page triggered it.`],verify:`Pair the definition with affected-urls from the same saved crawl and confirm the per-URL evidence.`,related:[{id:`affected-urls`,reason:`Returns URLs for the rule.`},{id:`crawler-rules`,reason:`Finds a rule id.`},{id:`top-fixes`,reason:`Ranks the fix queue.`}]},"generate-llms-txt":{readOrder:[`content, includedUrls, estimatedTokens, and sections`,`the generated titles and descriptions against the source pages`,`the source crawl date, cap, failures, and caveats`],doNotClaim:[`Reaching maxUrls or the token budget is possible truncation, not a complete inventory.`,`A valid draft does not prove search or AI benefit, selection, indexing, or citations.`,`The crawler cannot determine publisher intent.`],verify:`After human review, fetch the published file and validate its links with llms-txt-audit.`,related:[{id:`llms-txt-audit`,reason:`Audits the published file.`},{id:`crawl-report`,reason:`Inspects the source crawl.`},{id:`okf-build`,reason:`Builds a richer knowledge pack.`}]},"geo-gaps":{readOrder:[`dataStatus, source.partialReasons, and selection evaluated, matched, returned, and truncated counts`,`per-URL issues separated from searchEligibility`,`access states, where null means unknown`],doNotClaim:[`A null access state means unknown, not allowed.`,`Semantic HTML and structured data do not override a hard access restriction or create eligibility.`,`An empty gap list does not prove indexing, visibility, selection, or complete coverage; confirm intent before treating noindex, robots, canonicals, or snippet limits as defects.`],verify:`Fix unintended response or access conflicts, then re-check the exact URL and directive after deployment.`,related:[{id:`ai-readiness`,reason:`Gives the readiness overview.`},{id:`affected-urls`,reason:`Lists URLs for a rule.`},{id:`index-coverage`,reason:`Checks index signals.`}]},"index-coverage":{readOrder:[`sources completeness, rowLimit, rowLimitReached, date range, and invalid rows`,`retainedSearchVisibility and crawlableWithoutRetainedSearchVisibility`,`blockedOrNonIndexable, sitemapOnly, searchConsoleOnly, and templateReview`,`each group count, returned, and omitted`],doNotClaim:[`A page is not indexed because it had Search Analytics visibility, nor unindexed because no row appeared.`,`A missing Search Analytics row is not proof a page is unindexed.`,`Do not turn a limited or incomplete source into a whole-site coverage percentage.`],verify:`Run index-watch on representative URLs to collect exact URL Inspection verdicts.`,related:[{id:`index-watch`,reason:`Collects URL Inspection evidence.`},{id:`index-coverage-plan`,reason:`Plans quota when large.`},{id:`site-crawl`,reason:`Provides the source crawl.`}]},"index-coverage-plan":{readOrder:[`summary sitemapUrls, urlCount, property count, daily capacity, and estimated versus target cycle days`,`properties allocations and sample URLs`,`suggestions and every warning`],doNotClaim:[`Estimated cycle time is arithmetic over discovered URLs and capacity, not a promise Google will process or index them.`,`Sitemap fetch failures or maxUrls truncation make the plan incomplete.`,`This plan does not report an index coverage percentage or an all-clear.`],verify:`Use the plan to set a realistic recurring index-monitor limit and confirm allocations against properties you control.`,related:[{id:`index-monitor`,reason:`Collects the snapshots.`},{id:`index-coverage`,reason:`Reviews index signals first.`},{id:`index-watch`,reason:`Reviews index changes.`}]},"index-monitor":{readOrder:[`dataStatus and source sitemap and inventory limits, discovered and invalid URLs, and truncation`,`summary inventory states kept distinct`,`properties allocation and execution`,`items inspectionStatus, indexStatus, regressions, recoveries, and errors`],doNotClaim:[`A due URL that was not selected has no new inspection result.`,`Failed, deferred, or quota-blocked checks are operational outcomes, not SEO defects.`,`A complete selected batch does not prove inventory-wide indexing or future inclusion.`],verify:`Investigate regressions with a live page audit and the exact inspection evidence, and retry failed checks per retryAt.`,related:[{id:`index-watch`,reason:`Reviews index changes.`},{id:`index-coverage-plan`,reason:`Plans quota allocation.`},{id:`index-coverage`,reason:`Reviews coverage signals.`}]},"index-watch":{readOrder:[`source.possiblyTruncated, dataStatus, warnings, and sitemap inventory states`,`currentIssues, regressions, recoveries, failed, quotaBlocked, and deferred separately`,`indexStatus, issueCodes, and typed changes`],doNotClaim:[`URL Inspection is Google's indexed snapshot for one URL, not a live crawl or current search appearance.`,`A NEUTRAL verdict or canonical difference may be intentional, not a defect.`,`Do not infer inventory-wide health from the bounded selected sample; a provider 429 is authoritative over the local quota ledger.`],verify:`Audit the live page, sitemap, and exact inspection evidence for any regression or current issue.`,related:[{id:`index-monitor`,reason:`Collects the snapshots.`},{id:`index-coverage`,reason:`Reviews coverage signals.`},{id:`index-coverage-plan`,reason:`Plans quota allocation.`}]},"internal-links":{readOrder:[`dataStatus, target verification, aliases, canonical, and technical signals`,`source completeness and selection attempted, checked, failed, unchecked, and exclusions`,`per-item exact versus lexical matches and linkEvidence`],doNotClaim:[`A low checkLimit can leave plausible candidates unchecked, so absence is not proven.`,`Query overlap does not prove a link belongs or that adding one improves rankings.`,`Priority is a heuristic, not estimated impact.`],verify:`Read the source passage, add one contextual link if the target is genuinely useful, and re-crawl to confirm it.`,related:[{id:`cannibalisation`,reason:`Checks URL overlap.`},{id:`content-optimization`,reason:`Builds a page brief.`},{id:`page-opportunities`,reason:`Reviews one URL.`}]},"link-recovery":{readOrder:[`range and summary.checked with severity totals`,`per-item original and final URL, GSC metrics, issue lists, and redirect trace`,`chain status, final-page indexability, canonicals, and warnings`],doNotClaim:[`clicksAtRisk and impressionsAtRisk describe the checked period, not a forecast of loss or recovery.`,`Prior visibility orders the queue but does not prove fixing a URL restores traffic.`,`Failed checks or selection limits constrain coverage.`],verify:`Add one direct 301 to the closest equivalent destination only after confirming the page should still exist, then re-trace.`,related:[{id:`redirect-trace`,reason:`Traces one URL chain.`},{id:`decaying-pages`,reason:`Shows click declines.`},{id:`audit-urls`,reason:`Runs bounded checks.`}]},"llms-txt-audit":{readOrder:[`exists, resolved file URL, response status, headline, and issues`,`recommendedPages`,`the source crawl date, cap, partial state, failed requests, and caveats`],doNotClaim:[`llms.txt presence is not a ranking signal and its absence is not a technical defect.`,`A short recommendation list from a capped crawl is not sitewide evidence.`,`Adding the file does not improve indexing, rankings, traffic, AI selection, or citations.`],verify:`If the file exists, confirm its URLs are current, canonical, public, and aligned with publisher intent.`,related:[{id:`generate-llms-txt`,reason:`Drafts a new file.`},{id:`crawl-report`,reason:`Inspects the source crawl.`},{id:`okf-build`,reason:`Builds a knowledge pack.`}]},"measure-change":{readOrder:[`dataStatus, window, source, warnings, and caveats`,`window.effectiveDays versus requestedDays and afterWindowTruncated`,`GSC before and after metrics`,`Google Analytics source.analytics.status and timezone and any control adjusted deltas`],doNotClaim:[`Timing does not prove the recorded change caused the movement.`,`not-enough-data is never positive, negative, or flat, and the delta must not be annualized or forecast.`,`clickPct null means the before value was zero, not an infinite gain.`],verify:`Use equal whole-day before and after windows with at least 7 finalized days each before reading a directional verdict.`,related:[{id:`segment-impact`,reason:`Shows where movement sits.`},{id:`decaying-pages`,reason:`Shows click declines.`},{id:`update-postmortem`,reason:`Frames movement around an update.`}]},"monthly-action-plan":{readOrder:[`workflow summary, steps, actions, and nested output`,`requested month and final-data window`,`provider status and skipped sections`,`warnings, caveats, and retained caps`],doNotClaim:[`A skipped section is not a zero and a successful workflow does not make every nested source complete.`,`Actions are a review queue, not automatic edit instructions.`,`Do not invent click, traffic, ranking, or revenue forecasts or attribute movement to an update from timing alone.`],verify:`Trace each narrative claim back to its structured diagnosis or measurement and confirm affected URLs.`,related:[{id:`monthly-report`,reason:`Produces the human brief.`},{id:`refresh-priorities`,reason:`Builds a refresh queue.`},{id:`search-performance-overview`,reason:`Gives a baseline.`}]},"monthly-report":{readOrder:[`month and the underlying date windows`,`dataStatus and source completeness`,`skipped sections, warnings, and caveats`,`the narrative and its referenced diagnosis, changes, and priorities`],doNotClaim:[`A zero from a complete source differs from an unavailable, partial, filtered, or capped section.`,`verifyLimit is a bounded subset, not full-site validation.`,`The report cannot establish why traffic changed or forecast clicks, rankings, or revenue.`],verify:`Follow a narrative reference into the structured evidence and validate the affected URLs before acting.`,related:[{id:`monthly-action-plan`,reason:`Sequences the actions.`},{id:`narrative-report`,reason:`Assembles an evidence-linked briefing.`},{id:`search-performance-overview`,reason:`Gives a baseline.`}]},"narrative-report":{readOrder:[`top-level caveats and headline`,`sections and priorities`,`diagnosis, changeMeasurements, and changeMeasurementAttempts`,`monitoring`],doNotClaim:[`A failed or skipped measurement stays visible and is not a zero.`,`Update timing, template groupings, and opportunity scores are investigation context, not ranking causes.`,`The narrative summarizes evidence and does not strengthen it or prove causation.`],verify:`Reproduce a technical failure or inspect the affected URLs before treating a narrative claim as a defect.`,related:[{id:`monthly-report`,reason:`Fixes a calendar-month brief.`},{id:`refresh-priorities`,reason:`Builds a refresh queue.`},{id:`search-performance-overview`,reason:`Gives a baseline.`}]},"okf-build":{readOrder:[`manifest schema version, report id, source URL, generation time, crawl status, title, concept count, and caveats`,`selection page counts and deterministic ordering`,`the returned validation before using files`],doNotClaim:[`If selected pages equal the limit the bundle is bounded, not exhaustive.`,`A successful structural validation does not establish ownership, freshness, factual accuracy, or completeness.`,`The builder does not make extracted claims true or current.`],verify:`Inspect citations, canonical URLs, and summaries against source pages, especially for time-dependent claims.`,related:[{id:`okf-validate`,reason:`Validates the bundle.`},{id:`crawl-report`,reason:`Inspects the source crawl.`},{id:`generate-llms-txt`,reason:`Builds agent navigation.`}]},"okf-validate":{readOrder:[`validation.valid and file and concept counts`,`each issue level, path, and message`,`explanation.nextActions as ordering guidance`],doNotClaim:[`Validation checks the file contract only and does not browse cited sources or determine whether the knowledge is true, current, owned, or complete.`,`Warnings still require review even when valid is true.`,`Never report valid true as fact checked.`],verify:`After structural success, separately verify cited URLs, claims, dates, and the recorded crawl snapshot.`,related:[{id:`okf-build`,reason:`Builds the bundle to validate.`}]},"page-opportunities":{readOrder:[`dataStatus, range, source.targetRowsFetched, selection, and benchmark`,`benchmark.possiblyTruncated`,`verification.status and reason`,`per-item query metrics, opportunity type, and benchmark and verification evidence`],doNotClaim:[`Only verified results support live-page coverage observations.`,`estimatedCtrClickShortfall is a directional calculation, not a click forecast.`,`summary.opportunities is a review queue, not a defect count, and partial data is not an all-clear.`],verify:`Inspect the live SERP and resolve any status or indexability contradiction before editing.`,related:[{id:`content-optimization`,reason:`Builds a page brief.`},{id:`quick-wins`,reason:`Ranks CTR opportunities.`},{id:`internal-links`,reason:`Finds contextual links.`}]},"performance-audit":{readOrder:[`dataStatus, source, labDataStatus, and fieldDataStatus`,`Lighthouse score, lab metrics, and labInsights`,`CrUX fieldData.metrics with scope, formFactor, and collectionPeriod`,`fetch-fallback fields when source is fetch-fallback`],doNotClaim:[`Origin-level CrUX data is not page-level evidence.`,`A fetch-fallback duration is the local fetch workflow, not TTFB, LCP, CLS, TBT, or INP, and never earns a score.`,`Do not combine lab and field values into a new score, compare mobile with desktop as equal populations, or claim a ranking outcome.`],verify:`Reproduce the relevant lab issue and re-run the audit after the change.`,related:[{id:`audit-page`,reason:`Runs an on-page audit.`},{id:`site-crawl`,reason:`Builds the site inventory.`}]},"pseo-audit":{readOrder:[`dataStatus, source, selection, warnings, and caveats`,`population separating discovered, GSC-visible, sampled, and untested URLs`,`the template verdict confirmed against crawl, inspection, and evidence`,`metrics.topQueries and queryPatterns`],doNotClaim:[`PASS is the only indexed URL Inspection verdict and coverageState is diagnostic text that must not be parsed for index state.`,`A sitemap URL with no GSC row does not prove zero demand.`,`Literal term coverage and path entity fit are review heuristics, not ranking factors, and word count is descriptive.`],verify:`For an index-risk or crawl-risk verdict, inspect the sampled URLs directly and state the sample count.`,related:[{id:`index-coverage`,reason:`Reviews coverage signals.`},{id:`affected-urls`,reason:`Lists URLs for a rule.`},{id:`content-optimization`,reason:`Builds a page brief.`}]},"query-clusters":{readOrder:[`summary cluster and query counts, clicks, impressions, threshold, limit, and brand filtering`,`per-cluster member queries, pages, demand, and performance`,`each cluster recommendation`],doNotClaim:[`Token overlap does not prove shared intent, justify a new page, or establish that current URLs compete.`,`Page-two clusters do not claim a CTR-only click lift.`,`Queries below the threshold or outside the cap are outside the conclusion, not absent demand.`],verify:`Check whether a cluster maps to one intent before changing section structure or navigation.`,related:[{id:`cannibalisation`,reason:`Checks URL overlap.`},{id:`content-optimization`,reason:`Builds a page brief.`},{id:`community-intent`,reason:`Reviews intent language.`}]},"quick-wins":{readOrder:[`dataStatus, date range, source row cap, possiblyTruncated, selection.invalidRows, and filters`,`methodology and each item benchmark confidence before using targetCtr`,`estimatedCtrClickShortfall and priority`,`verification requested, attempted, verified, failed, and technical counts`],doNotClaim:[`estimatedCtrClickShortfall and priority score are heuristics, explicitly not estimated lift.`,`Average position is not a stable rank, and an unverified item cannot support a content-coverage conclusion.`,`Filtered or capped evidence is not an all-clear.`],verify:`Inspect the live SERP, test clearer snippet framing, then compare a later complete window.`,related:[{id:`ctr-underperformers`,reason:`Adds CTR peer evidence.`},{id:`page-opportunities`,reason:`Reviews one URL.`},{id:`striking-distance`,reason:`Finds rows near page one.`}]},"redirect-trace":{readOrder:[`chain in order with requested URL, status, Location, resolved next, and duration`,`finalUrl versus finalPage and summary.finalStatus`,`summary.issues`,`metaRobots, xRobotsTag, canonical, and warnings`],doNotClaim:[`The trace is one observation from the machine and time that ran it, and CDNs, geography, cookies, or later deploys can differ.`,`A canonical elsewhere or a 302 can be intentional.`,`JavaScript rendering affects final-page extraction, not the HTTP chain.`],verify:`Re-run the trace after deployment and confirm the intended destination and status.`,related:[{id:`link-recovery`,reason:`Prioritises search-value URLs.`},{id:`audit-page`,reason:`Audits the final page.`},{id:`audit-urls`,reason:`Runs bounded checks.`}]},"refresh-priorities":{readOrder:[`workflow steps and skipped states`,`per-item source, impactKind, score breakdown, date window, completeness, verification status, and rationale`,`the top few URLs manually`],doNotClaim:[`A click decline, low CTR, second-page position, and Google Analytics session signal have different semantics and are not interchangeable.`,`Missing Google Analytics or failed fetches stay unavailable, not zero-weight proof.`,`The score is not expected clicks, revenue, ranking lift, or causal confidence.`],verify:`Confirm query intent, current content, and internal links for the top URLs before choosing a bounded edit.`,related:[{id:`decaying-pages`,reason:`Shows click declines.`},{id:`quick-wins`,reason:`Ranks CTR opportunities.`},{id:`second-page`,reason:`Finds rows near page one.`}]},"search-performance-overview":{readOrder:[`workflow envelope with summary, steps, actions, and output`,`report dataStatus, skipped sections, and partial reasons`,`priorities`,`nested anomaly, update, segment, decay, cannibalisation, striking-distance, and quick-win results`],doNotClaim:[`One complete section must not hide a failed or capped neighbour.`,`Sparse data may legitimately skip a section.`,`Update overlap, opportunity scores, average position, and timing are investigation signals, not causation or forecasts.`],verify:`Run the focused report named in actions and confirm affected URLs and provider dates before editing.`,related:[{id:`segment-impact`,reason:`Shows where movement sits.`},{id:`quick-wins`,reason:`Ranks CTR opportunities.`},{id:`striking-distance`,reason:`Finds rows near page one.`}]},"second-page":{readOrder:[`source completeness, date range, selection, methodology, provenance, and brand filtering`,`per-item impressions, clicks, CTR, and average position`,`the priority value`],doNotClaim:[`Average position is aggregated across searches, locations, devices, and time and is not an exact rank.`,`Retained limits mean the output is not a complete inventory.`,`Priority is a transparent heuristic, not expected click lift, and one row is not cannibalisation.`],verify:`Inspect the live page and search intent and confirm the query suits the URL before one defensible change.`,related:[{id:`striking-distance`,reason:`Covers positions 11-20.`},{id:`quick-wins`,reason:`Ranks CTR opportunities.`},{id:`page-opportunities`,reason:`Reviews one URL.`}]},"segment-impact":{readOrder:[`before, after, and rangeDays`,`dataStatus and source.before, source.after, and completeness for caps and truncation`,`selection invalid, duplicate, conflicting, matched, unmatched, returned, and limited rows`,`per-item clickDelta, impressionDelta, and positionDelta, then unmatchedSegments`],doNotClaim:[`A positive positionDelta is worse because a larger average position is lower in results.`,`unmatchedSegments are retained in one window only and are not gains from or losses to zero.`,`A limited or truncated result cannot support an inventory-wide winner or loser.`],verify:`Audit the top matched movers pages and query intent before drawing a segment conclusion.`,related:[{id:`traffic-anomaly`,reason:`Confirms movement first.`},{id:`decaying-pages`,reason:`Shows click declines.`},{id:`update-postmortem`,reason:`Frames movement around an update.`}]},"seo-to-ai-query":{readOrder:[`dataStatus states empty, filtered, partial, and available`,`dateRange, source.possiblyTruncated, filters, selection.invalidRows, and limited rows`,`methodology.observedAiPromptData and estimatedTrafficLift, both false`,`per-item evidenceScope`],doNotClaim:[`Generated prompt count is not AI demand and prompts do not prove anyone entered them or that an answer mentioned the site.`,`No returned rows is not an all-clear when the source is partial or filtered.`,`Each prompt inherits the narrow retained-GSC-query scope.`],verify:`Store the source query and date range beside later monitoring observations and refresh on a controlled schedule.`,related:[{id:`ai-referrals`,reason:`Adds Google Analytics referral evidence.`},{id:`community-intent`,reason:`Reviews intent language.`},{id:`ai-readiness`,reason:`Checks technical access.`}]},"setup-check":{readOrder:[`ok`,`each checks entry status, distinguishing fail from warn, with detail and fix`,`generatedAt`],doNotClaim:[`A passing result means local prerequisites look present, not that a token is still accepted by Google or that the account can access a property.`,`Missing saved defaults are usually warnings because explicit parameters can replace them.`,`A setup pass is not evidence about rankings, indexing, or site health.`],verify:`Apply the fix on a failed check, re-run setup-check, then run a small read-only report for the intended property.`,related:[{id:`search-performance-overview`,reason:`First report once setup passes.`}]},"site-crawl":{readOrder:[`status, requestEvidenceStatus, attempted and fetched counts, failures, retained pages, and pageLimitReached`,`data-source states`,`warnings and caveats, then opt-in pages, requests, and issues`],doNotClaim:[`A partial or capped crawl cannot support an all-clear or a definitive zero.`,`A crawl is not an index, traffic source, or guarantee of complete site coverage.`,`If JavaScript rendering was disabled, state that discovery boundary.`],verify:`Preserve the report id and settings, then re-run the same configuration to confirm a change.`,related:[{id:`top-fixes`,reason:`Ranks the fix queue.`},{id:`affected-urls`,reason:`Lists URLs for a rule.`},{id:`index-coverage`,reason:`Checks coverage signals.`}]},"striking-distance":{readOrder:[`range, rangeDays, dataStatus, and source`,`source.possiblyTruncated and retained-row completeness`,`selection invalid, out-of-range, branded, below-threshold, eligible, returned, and limited`,`methodology, then per-item metrics and contentVerification`],doNotClaim:[`The 11-20 band is a heuristic boundary and average position is not a literal second results page.`,`Unverified or failed pages keep lower confidence.`,`The priority score is not expected traffic or ranking lift.`],verify:`Audit a small number of high-impression candidates and fix verified technical problems first.`,related:[{id:`second-page`,reason:`Covers positions 10-20.`},{id:`quick-wins`,reason:`Ranks CTR opportunities.`},{id:`page-opportunities`,reason:`Reviews one URL.`}]},"technical-watch":{readOrder:[`each workflow step and nested crawl, index, and recovery output independently`,`crawl run ids, limits, failures, and changed items`,`inspection selected, unselected-due, attempted, failed, quota-blocked, deferred, and regression states`,`recovery clicks-at-risk as history`],doNotClaim:[`A successful run does not prove every URL is crawlable, indexed, unchanged, or issue-free.`,`URL Inspection is a provider snapshot and sitemap discovery is not proof of indexing.`,`Partial crawls, capped inventories, quotas, and failed subqueries must stay visible in any all-clear.`],verify:`Reproduce new failures and confirmed regressions, then schedule another bounded run with a rollback path.`,related:[{id:`crawl-diff`,reason:`Isolates crawl change.`},{id:`index-watch`,reason:`Isolates index change.`},{id:`link-recovery`,reason:`Recovers search-value URLs.`}]},"top-fixes":{readOrder:[`dataSources, summary, warnings, and caveats`,`per-item rule id, affected count, sample URLs, scoreFactors, and whyThisRanks`,`effort, fix guidance, and verification`],doNotClaim:[`Rank means highest among retained eligible findings, not globally highest, because the crawl or category may be capped.`,`Missing provider joins stay unavailable and a numeric zero is meaningful only when the source is complete.`,`This is not a forecast of rankings, traffic, or revenue.`],verify:`Run affected-urls for the same rule and report id, then repeat the crawl configuration after implementation.`,related:[{id:`affected-urls`,reason:`Lists URLs for a rule.`},{id:`explain-crawl-issue`,reason:`Explains the rule.`},{id:`site-crawl`,reason:`Provides the source crawl.`}]},"traffic-anomaly":{readOrder:[`coverage with requested dates, expected versus observed days, missing days, invalid rows, and status`,`anomalies with metric, baseline and comparison dates, percentChange, zScore, and significanceMethod`,`direction and significant`],doNotClaim:[`Statistical unusualness does not identify a cause, diagnose a defect, or prove an external event affected the property.`,`A null percentage from a zero baseline differs from zero change.`,`Partial coverage can change the baseline.`],verify:`If movement is significant and coverage is adequate, run segment-impact for pages and queries.`,related:[{id:`segment-impact`,reason:`Shows where movement sits.`},{id:`update-correlation`,reason:`Adds update timing.`},{id:`decaying-pages`,reason:`Shows click declines.`}]},"update-correlation":{readOrder:[`classification with attribution and confidence, returned as not-established and none`,`anomalies, overlappingUpdates, and confounders`,`evidence, caveats, and source`],doNotClaim:[`Overlap cannot show the update caused a gain or loss even with large movement and no other recorded change.`,`update-overlap-without-significant-movement is not an impact result and no-update-overlap does not explain remaining movement.`,`Temporal overlap is not a penalty, reward, or confirmed effect.`],verify:`Test saved deploys, redirects, and content changes that overlap the window before attributing movement.`,related:[{id:`update-postmortem`,reason:`Maps winners and losers.`},{id:`segment-impact`,reason:`Shows where movement sits.`},{id:`traffic-anomaly`,reason:`Confirms movement first.`}]},"update-postmortem":{readOrder:[`workflow step statuses, finalized date windows, and source completeness`,`failed measurements, retained limits, and caveats`,`update, insights, templateMovement, and segments`],doNotClaim:[`The cause remains not established even when dates overlap.`,`Retained winners and losers are not a complete inventory when sources are capped or filtered.`,`Do not claim update causation, recovery probability, or future traffic.`],verify:`Inspect representative URLs from affected segments and reproduce technical changes before any rollback.`,related:[{id:`update-correlation`,reason:`Adds update timing.`},{id:`segment-impact`,reason:`Shows where movement sits.`},{id:`measure-change`,reason:`Evaluates a fix.`}]}},pR={"affected-urls":{name:`URLs affected by a crawl issue`,description:`Open the exact pages behind a crawl finding so you can review or fix the right URLs.`,useWhen:[`A crawl summary shows an issue that needs a closer look.`,`You need the affected URLs for one rule, category, or severity.`],avoidWhen:[`You have not run or selected a crawl yet.`,`You need an overview of the most important crawl findings first.`],outcome:`A focused list of affected URLs with the evidence for each match.`},"agent-readiness":{name:`AI agent readiness`,description:`Check whether a content site gives agents clean, stable, machine-readable pages and discovery files.`,useWhen:[`You want to test Markdown alternatives, content negotiation, Agent Skills, llms.txt, identity, and crawler access together.`,`You need evidence that the agent-facing version of a content site matches its public HTML routes.`],avoidWhen:[`You need an AI visibility, mention, citation, or ranking report.`,`You are auditing a public API, interactive application, or commerce flow rather than a content site.`],outcome:`An unscored content-site assessment with failed routes, evidence, and clear next actions.`},"ai-readiness":{name:`AI search technical readiness`,description:`Check whether crawl, index, or snippet controls could block pages from Google AI search features.`,useWhen:[`You want to check technical access before investigating AI search visibility.`,`You need page-level evidence for robots, indexability, or snippet restrictions.`],avoidWhen:[`You want a visibility score or a prediction of AI citations.`,`You need evidence of visits from AI products.`],outcome:`A technical blocker list with affected pages and clear limits.`},"ai-referrals":{name:`AI referral traffic`,description:`Find visits from known AI products in Google Analytics and see a ranked, bounded set of landing pages that received them.`,useWhen:[`You want measured AI referral traffic from Google Analytics.`,`You need the landing pages and sources behind those visits.`],avoidWhen:[`Google Analytics is not connected or does not track the site.`,`You want to measure citations or unclicked mentions inside AI answers.`],outcome:`A source and landing-page breakdown of identifiable AI referrals.`},"ai-search-scorecard":{name:`AI search scorecard`,description:`Score AI-search technical readiness 0-100 from one crawl using this tool's own weighted checks, with observed evidence kept separate.`,useWhen:[`You want a single scored summary of the AI-search technical evidence this tool already collects.`,`You need per-check pass, warn, fail, or unknown states with the exact weights and formula.`],avoidWhen:[`You want a Google or AI-engine eligibility verdict, a ranking prediction, or proof of citations.`,`You need per-page fixes rather than a scored overview.`],outcome:`A 0-100 heuristic score with per-check evidence, unknown exclusions, and a partial flag.`},"audit-page":{name:`Single-page SEO audit`,description:`Check one URL for response, indexability, metadata, headings, links, and page content evidence.`,useWhen:[`One page needs a technical review before you change it.`,`A broader report points to a specific URL.`],avoidWhen:[`You need to discover issues across a whole site.`,`The page requires a logged-in browser session.`],outcome:`A page-level audit that separates observed evidence from review advice.`},"audit-urls":{name:`Selected URL audit`,description:`Audit a specific list of pages without crawling the rest of the site.`,useWhen:[`You already know which URLs need checking.`,`You want a repeatable pre-release or post-release check for key pages.`],avoidWhen:[`You need the crawler to discover pages for you.`,`You only need a deep review of one page.`],outcome:`Consistent technical checks for each supplied URL.`},cannibalisation:{name:`Query and URL overlap`,description:`Find search queries that appear across several URLs and decide whether the overlap needs attention.`,useWhen:[`Several pages may be competing for the same search intent.`,`You want query-level evidence before merging or redirecting pages.`],avoidWhen:[`You plan to treat every multi-URL query as a problem.`,`The site has too little Search Console data for meaningful overlap.`],outcome:`A ranked review list of queries, URLs, and their share of search exposure.`},"community-intent":{name:`Community and review intent`,description:`Find Search Console queries that explicitly ask for reviews, comparisons, recommendations, or real experience.`,useWhen:[`You want to see where searchers ask for proof, opinions, or comparisons.`,`You are planning content that answers community-style questions.`],avoidWhen:[`You want the tool to guess intent without explicit query wording.`,`You need proof that a new page will rank.`],outcome:`A review list of real queries grouped by the intent language they contain.`},"compare-crawls":{name:`Compare saved crawls`,description:`Compare two saved crawl snapshots to see which pages and technical findings changed.`,useWhen:[`You want to check what changed after a release or migration.`,`You need evidence that a technical fix appeared in a later crawl.`],avoidWhen:[`The two crawls used materially different scope or settings.`,`You have only one saved crawl.`],outcome:`A before-and-after view of added, removed, fixed, and new findings.`},"content-optimization":{name:`Search-led content brief`,description:`Turn a page and its Search Console queries into a practical brief for improving relevance and coverage.`,useWhen:[`An existing page has search demand but needs a clearer improvement plan.`,`You want first-party query evidence beside the current page content.`],avoidWhen:[`The URL has no useful Search Console history.`,`You want generated copy or a traffic forecast.`],outcome:`A page brief with query evidence, content checks, and specific review points.`},"crawl-diff":{name:`Monitor crawl changes`,description:`Crawl a site and compare it with the previous matching run to catch technical changes.`,useWhen:[`You want a repeatable technical check after deployments.`,`You need newly introduced or resolved crawl findings.`],avoidWhen:[`There is no comparable earlier crawl.`,`You need to compare two specific saved crawl IDs.`],outcome:`A fresh crawl plus a clear list of technical changes since the previous run.`},"crawl-history":{name:`Saved crawl history`,description:`List local crawl snapshots so you can choose the right report by site and date.`,useWhen:[`You need to find a crawl ID for review or comparison.`,`You want to confirm which crawl snapshots exist locally.`],avoidWhen:[`You need the findings inside a crawl rather than its saved metadata.`],outcome:`A compact list of saved crawls with IDs, sites, dates, and scope.`},"crawl-report":{name:`Open a saved crawl`,description:`Load a saved crawl by ID, or open the latest local crawl for a site.`,useWhen:[`You already know which saved crawl you want to inspect.`,`Another report asks for crawl evidence by report ID.`],avoidWhen:[`You need to create a new crawl.`,`You only need a filtered list for one issue.`],outcome:`The selected crawl summary with optional page and issue detail.`},"crawler-rules":{name:`Crawler rule catalog`,description:`Find the available technical check IDs and the evidence each crawler rule looks for.`,useWhen:[`You need a valid rule ID for another crawl report.`,`You want to see which technical checks are available by category.`],avoidWhen:[`You need affected pages or live crawl results.`],outcome:`A searchable catalog of crawler rules, severity, and guidance.`},"ctr-underperformers":{name:`CTR underperformers`,description:`Find high-impression queries whose click-through rate trails comparable queries at similar positions.`,useWhen:[`You want title or snippet review candidates backed by Search Console data.`,`You need to compare CTR fairly across similar average positions.`],avoidWhen:[`You want a promised click uplift.`,`The comparison groups have too little data.`],outcome:`A ranked list of query and page pairs with CTR comparison evidence.`},"decaying-pages":{name:`Pages losing search clicks`,description:`Find pages and queries with meaningful click declines across comparable Search Console periods.`,useWhen:[`You want to investigate a sustained organic search decline.`,`You need the pages and queries behind lost clicks.`],avoidWhen:[`The newer period is incomplete or not comparable.`,`You want to assume every decline is caused by old content.`],outcome:`A ranked investigation list with before-and-after search evidence.`},"entity-readiness":{name:`Entity signal review`,description:`Check names, authors, dates, structured data, and profile links that help identify who created a page.`,useWhen:[`You want to review publisher, organization, or author signals across crawled pages.`,`You need examples of missing or inconsistent entity evidence.`],avoidWhen:[`You want a knowledge graph or ranking guarantee.`,`You have not saved or supplied a crawl.`],outcome:`Page-level entity observations with examples that need human review.`},"explain-crawl-issue":{name:`Explain a crawl issue`,description:`Explain what one crawler rule means, why it may matter, and how to verify a fix.`,useWhen:[`A crawl returned a rule ID you do not recognize.`,`You need implementation and verification guidance before creating work.`],avoidWhen:[`You need the URLs affected by the rule.`,`You have not selected a valid crawler rule ID.`],outcome:`A plain-English rule explanation with fix and verification steps.`},"generate-llms-txt":{name:`Generate an llms.txt draft`,description:`Create a concise llms.txt draft from selected public pages in a crawl report.`,useWhen:[`You want a human-reviewed navigation file for useful public pages.`,`You have a current crawl that identifies suitable canonical URLs.`],avoidWhen:[`You expect the file to improve rankings, indexing, or AI citations.`,`The source crawl is old, partial, or aimed at the wrong site section.`],outcome:`An editable llms.txt draft with source and size limits kept visible.`},"geo-gaps":{name:`Google AI search controls`,description:`Find crawl, index, and snippet settings that could restrict pages in Google AI search features.`,useWhen:[`You want to find technical restrictions that apply to Google AI search.`,`You need affected pages for a crawl, index, or preview control.`],avoidWhen:[`You want a generic AI visibility score.`,`You need referral traffic or citation monitoring.`],outcome:`A focused list of technical restrictions and the pages where they appear.`},"index-coverage-plan":{name:`URL Inspection coverage plan`,description:`Choose which sitemap URLs and Search Console properties should use limited URL Inspection checks.`,useWhen:[`A large site needs a practical sampling plan for URL Inspection.`,`You want to spread checks across useful URL-prefix properties.`],avoidWhen:[`You need current Google index results rather than a plan.`,`You want complete coverage beyond the available quota.`],outcome:`A quota-aware list of URLs and properties to inspect next.`},"index-coverage":{name:`Index coverage signals`,description:`Compare the pages your site exposes with pages that had Google Search visibility, then choose representative URLs for URL Inspection.`,useWhen:[`You need to find crawlable pages that did not appear in the Search Console results returned for the selected period.`,`You want a focused review list before spending URL Inspection quota.`],avoidWhen:[`You need proof that a URL is indexed or excluded. Use URL Inspection for that URL.`,`You do not have a saved crawl for the selected site.`],outcome:`Cross-source URL groups with clear evidence limits and a representative review queue for URL Inspection.`},"index-monitor":{name:`Collect index snapshots`,description:`Check selected sitemap URLs with Google URL Inspection and save the results locally for later comparison.`,useWhen:[`You want current Google index evidence for a controlled URL set.`,`You need repeatable snapshots for future monitoring.`],avoidWhen:[`You expect every URL in a large site to be checked at once.`,`You only need to review previously saved snapshots.`],outcome:`Saved URL Inspection results with quota skips and provider failures separated.`},"index-watch":{name:`Review index changes`,description:`Compare current Google index evidence with saved snapshots to find regressions and recoveries.`,useWhen:[`You want to see which monitored URLs changed index state.`,`You need to separate page issues from quota or provider failures.`],avoidWhen:[`No earlier index snapshots exist.`,`You want a complete index count for the whole site.`],outcome:`A review queue of current issues, regressions, recoveries, and failed checks.`},"internal-links":{name:`Internal link opportunities`,description:`Find relevant pages that could add a useful internal link to a target URL.`,useWhen:[`A target page needs stronger internal discovery or contextual links.`,`You want candidate source pages checked against their current content.`],avoidWhen:[`You want links added automatically.`,`The target and candidate pages do not share a clear topic or user need.`],outcome:`Verified source-page candidates with query and page evidence for review.`},"link-recovery":{name:`Recover broken search-value URLs`,description:`Find URLs that previously earned search clicks but now fail, redirect poorly, or block indexing.`,useWhen:[`You suspect valuable URLs broke during a migration or release.`,`You want to prioritize technical recovery using Search Console evidence.`],avoidWhen:[`You want a backlink report.`,`You have no Search Console page history for the site.`],outcome:`A ranked recovery list with current URL checks and prior search value.`},"llms-txt-audit":{name:`Audit llms.txt`,description:`Check whether llms.txt exists, whether its links work, and how well it reflects useful public pages.`,useWhen:[`The site publishes llms.txt and you want to verify it.`,`You are deciding whether a concise navigation file would help agents.`],avoidWhen:[`You expect llms.txt to improve Google rankings or indexing.`,`You need to generate a new draft rather than audit the current file.`],outcome:`File, link, and page coverage findings with no visibility claims.`},"measure-change":{name:`Measure an SEO change`,description:`Compare equal Search Console periods before and after a known site change.`,useWhen:[`A release, migration, or content change has a clear date.`,`Both comparison periods contain final and comparable data.`],avoidWhen:[`The after period is incomplete.`,`You need proof that the change caused the movement.`],outcome:`A before-and-after search comparison with confounders and data limits shown.`},"monthly-action-plan":{name:`Monthly SEO action plan`,description:`Review a calendar month of search performance and turn the evidence into next actions.`,useWhen:[`You need a recurring monthly review with clear priorities.`,`The selected month has final Search Console data.`],avoidWhen:[`You only need a client-ready summary without an action queue.`,`The month is still incomplete.`],outcome:`A monthly performance summary followed by a focused action plan.`},"monthly-report":{name:`Monthly SEO report`,description:`Turn one calendar month of search evidence into a concise report for clients or teams.`,useWhen:[`You need a readable monthly summary of organic search performance.`,`The selected month has final Search Console data.`],avoidWhen:[`You need a rolling period rather than a calendar month.`,`You want a detailed technical site audit.`],outcome:`A clear monthly narrative with wins, losses, caveats, and next steps.`},"narrative-report":{name:`Client-ready SEO narrative`,description:`Turn search, change, and monitoring evidence into a readable update without hiding caveats.`,useWhen:[`You need to explain recent SEO performance to a client or team.`,`You want one narrative built from several available evidence sources.`],avoidWhen:[`You need raw rows for further analysis.`,`You want unsupported forecasts or causal claims.`],outcome:`A concise written report with evidence, limits, and practical next steps.`},"okf-build":{name:`Build site knowledge for agents`,description:`Create a compact, cited site knowledge pack from public pages in a crawl.`,useWhen:[`An agent needs reliable site facts with links back to source pages.`,`You want a portable knowledge pack from selected crawl content.`],avoidWhen:[`The crawl contains private, stale, or irrelevant pages.`,`You want the tool to invent missing facts.`],outcome:`An OKF manifest and optional markdown files with source links.`},"okf-validate":{name:`Validate a site knowledge pack`,description:`Check supplied OKF markdown files for structure, references, and broken links before use.`,useWhen:[`You created or edited an OKF pack and want to check it.`,`An agent needs to verify files before loading them.`],avoidWhen:[`You need to build the pack from a crawl first.`],outcome:`A validation result with exact file, reference, and link errors.`},"page-opportunities":{name:`Opportunities for one page`,description:`Find Search Console queries and page evidence that point to useful improvements for one URL.`,useWhen:[`You want to improve an existing page using its own search demand.`,`You need query themes and page checks in one result.`],avoidWhen:[`The page has no useful Search Console data.`,`You want a guaranteed traffic gain.`],outcome:`A ranked page improvement list tied to real queries and current content.`},"performance-audit":{name:`Page performance audit`,description:`Run Lighthouse lab checks and add CrUX field Core Web Vitals when that data is available.`,useWhen:[`You need actionable performance diagnostics for a page.`,`You want lab findings kept separate from real-user field data.`],avoidWhen:[`You want Lighthouse scores presented as field Core Web Vitals.`,`The page cannot be fetched from the local machine.`],outcome:`Lab diagnostics plus clearly labelled field metrics when available.`},"pseo-audit":{name:`Programmatic SEO audit`,description:`Review repeated page templates using search demand, crawl evidence, and optional Google index checks.`,useWhen:[`A site publishes many pages from repeatable templates.`,`You need to compare template groups before expanding or pruning them.`],avoidWhen:[`You want every repeated path labelled as spam or thin content.`,`There are too few pages to compare as a template group.`],outcome:`Template-level demand, quality, and index evidence with representative URLs.`},"query-clusters":{name:`Search query clusters`,description:`Group related Search Console queries so repeated demand themes are easier to review.`,useWhen:[`A large query list needs a clearer topic structure.`,`You want to find repeated wording within a site section.`],avoidWhen:[`You need semantic or intent classification beyond shared query terms.`,`You want clusters turned into new pages automatically.`],outcome:`Stable query groups with the search demand behind each theme.`},"quick-wins":{name:`Page-one CTR opportunities`,description:`Find page-one query and URL pairs whose CTR deserves a closer look.`,useWhen:[`You want high-impression title and snippet review candidates.`,`You need page-one opportunities ranked from Search Console data.`],avoidWhen:[`You want a promised click gain.`,`You plan to treat a CTR heuristic as a Google requirement.`],outcome:`A ranked review queue with CTR, position, impressions, and comparison evidence.`},"redirect-trace":{name:`Redirect trace`,description:`Follow every redirect hop and check the final page, indexability, and canonical signals.`,useWhen:[`A URL redirects unexpectedly or through several hops.`,`You need to verify the final destination after a migration.`],avoidWhen:[`You need to discover redirect issues across a whole site.`,`The URL requires a signed-in browser session.`],outcome:`The complete redirect chain with final response and page checks.`},"refresh-priorities":{name:`Content refresh priorities`,description:`Combine search declines and near-term opportunities into a ranked content review queue.`,useWhen:[`You need to decide which existing pages to review first.`,`You want several Search Console signals ranked in one queue.`],avoidWhen:[`You need a technical crawl plan.`,`You want the ranking treated as a traffic forecast.`],outcome:`A focused list of pages and queries to investigate next, with reasons.`},"search-performance-overview":{name:`Search performance overview`,description:`Find where Google Search performance changed and choose the next focused report to inspect it.`,useWhen:[`You are starting an investigation and do not yet know which pages or queries moved.`,`You want one view of declines, gains, overlap, and near-term opportunities.`],avoidWhen:[`You already know the exact question and can run a focused report.`,`You need a technical crawl without Search Console analysis.`],outcome:`A clear search performance summary with a short list of next reports.`},"second-page":{name:`Second-page opportunities`,description:`Find URLs averaging positions 11 to 20 and see the queries behind each opportunity.`,useWhen:[`You want existing pages that may deserve closer review near page one.`,`You need page-level evidence across all matching queries.`],avoidWhen:[`You want average position treated as a fixed rank.`,`You need proof that a change will move the page to page one.`],outcome:`A ranked URL list with query, click, impression, and position evidence.`},"segment-impact":{name:`Search impact by segment`,description:`Compare page or query groups across equal periods to find which parts of a site moved.`,useWhen:[`A traffic change may be concentrated in one directory, page type, or query group.`,`You need to separate broad movement from a local segment issue.`],avoidWhen:[`The periods are incomplete or not comparable.`,`Missing Search Console rows would be mistaken for zero activity.`],outcome:`A segment comparison showing where clicks and impressions changed.`},"seo-to-ai-query":{name:`AI monitoring prompt ideas`,description:`Turn real Search Console query wording into a repeatable set of prompts for external AI monitoring.`,useWhen:[`You need prompt ideas grounded in the way people already find the site.`,`You want a stable prompt set for manual or third-party monitoring.`],avoidWhen:[`You want proof of demand inside AI products.`,`You want citation or visibility results from this report.`],outcome:`A prompt set linked back to its source queries and search metrics.`},"setup-check":{name:`Check local setup`,description:`Check Google login, scopes, local configuration, and saved defaults before running reports.`,useWhen:[`Authentication or site selection is failing.`,`You want to verify the local setup before investigating report errors.`],avoidWhen:[`You need SEO findings rather than a setup check.`],outcome:`A local setup status with the exact problems that need fixing.`},"site-crawl":{name:`Technical SEO site crawl`,description:`Crawl a site, check technical SEO signals, and save a local baseline for follow-up reports.`,useWhen:[`You need to discover technical issues across linked pages.`,`You want a crawl snapshot for later filtering or comparison.`],avoidWhen:[`You only need to check one page or a known URL list.`,`Important content requires browser rendering that is not enabled.`],outcome:`A crawl summary with page evidence, technical findings, and saved report ID.`},"striking-distance":{name:`Queries near page one`,description:`Find Search Console query and page pairs averaging positions 11 to 20.`,useWhen:[`You want existing search demand close to page one.`,`You need a compact list of queries and URLs for content review.`],avoidWhen:[`You want average position treated as an exact daily rank.`,`You need a promised ranking or click gain.`],outcome:`A ranked list of near-page-one query and URL pairs with search evidence.`},"technical-watch":{name:`Technical SEO watch`,description:`Run crawl and index checks together to catch technical regressions on a schedule.`,useWhen:[`You want recurring checks after releases or site changes.`,`You need crawl changes and selected Google index checks in one run.`],avoidWhen:[`You have not chosen any crawl, index, or link recovery input.`,`You need a one-off deep audit rather than ongoing monitoring.`],outcome:`A combined technical change report with failures and skipped checks kept separate.`},"top-fixes":{name:`Top technical SEO fixes`,description:`Turn crawl findings into a short, ranked queue of technical issues to investigate first.`,useWhen:[`A crawl contains too many findings to review at once.`,`You need representative URLs and reasons for the highest priorities.`],avoidWhen:[`You need every affected URL for one rule.`,`You plan to treat rank as proof of business impact.`],outcome:`A focused technical fix queue with issue evidence and example URLs.`},"traffic-anomaly":{name:`Unusual search traffic changes`,description:`Find recent clicks or impressions that moved beyond the site's normal Search Console pattern.`,useWhen:[`You want an early signal of an unusual search change.`,`You need to know whether recent movement stands out from normal variation.`],avoidWhen:[`There is too little history for a useful baseline.`,`You want the report to identify the cause of the change.`],outcome:`A statistical movement signal with the dates, baseline, and limits shown.`},"update-correlation":{name:`Google update correlation`,description:`Compare search movement dates with official Google ranking update windows.`,useWhen:[`A traffic change overlaps a confirmed Google ranking update.`,`You need a timeline before investigating affected pages and queries.`],avoidWhen:[`You want overlap presented as proof of causation.`,`The traffic window does not cover the update dates.`],outcome:`A traffic and update timeline with overlaps clearly labelled as correlation.`},"update-postmortem":{name:`Google update impact review`,description:`Find the pages and queries that gained or lost around a confirmed Google ranking update.`,useWhen:[`You need winners and losers around a known update window.`,`You want to compare affected segments and known site changes.`],avoidWhen:[`You want the report to claim the update caused every change.`,`The comparison periods are incomplete or contain major unrecorded changes.`],outcome:`A post-update review of winners, losers, confounders, and next checks.`}};function mR(e){let t=pR[e],n=fR[e];if(!(!t||!n))return{...t,...n}}function hR(e){e.registerTool(`seo_audit_page`,{description:`Run a single-page technical and content audit`,inputSchema:{url:D.string().url(),site:D.string().optional(),js:D.boolean().optional(),refresh:D.boolean().optional()}},async({url:e,site:t,js:n,refresh:r})=>{try{let i=await N_({url:e,site:t,js:JL(n,`auto`),refresh:r});return $(`Audit complete for ${e}. Found ${i.issues.length} issues.`,i)}catch(e){return Q(e)}})}const gR=XL([`verifyContent`,`verifyLimit`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]);function _R(e){return{verifyContent:e.verifyContent,verifyLimit:e.verifyLimit,js:JL(e.js,void 0),rate:tR({fetchConcurrency:e.fetchConcurrency,fetchIntervalCap:e.fetchIntervalCap,fetchIntervalMs:e.fetchIntervalMs}),refresh:e.refresh}}function vR(e){e.registerTool(`seo_monthly_report`,{description:`Generate a monthly SEO report narrative`,inputSchema:{...XL([`site`,`limit`,`includeBrand`]),month:qL.optional(),...gR}},async({site:e,month:t,limit:n,includeBrand:r,...i})=>{try{let a=await aF({site:e,month:t,limit:n,includeBrand:r,..._R(i)});return $(`Monthly report generated for ${a.month}. ${a.headline}`,{...a,presentation:oF(a)},{markdown:a.markdown})}catch(e){return Q(e)}})}function yR(e){e.registerTool(`seo_report_narrative`,{description:`Generate a client-ready SEO narrative across diagnosis, changes, and monitoring`,inputSchema:{...XL([`site`,`days`,`recentDays`,`limit`,`includeBrand`]),startDate:KL.optional(),endDate:KL.optional(),changeLimit:D.number().optional(),...gR}},async({site:e,days:t,recentDays:n,startDate:r,endDate:i,limit:a,changeLimit:o,includeBrand:s,...c})=>{try{let l=await iF({site:e,days:t,recentDays:n,startDate:r,endDate:i,limit:a,changeLimit:o,includeBrand:s,..._R(c)});return $(l.headline,{...l,presentation:oF(l)},{markdown:l.markdown})}catch(e){return Q(e)}})}function bR(e){e.registerTool(`seo_second_page`,{description:`Find URLs with GSC average position above 10 through 20 and return evidence-grounded investigation prompts`,inputSchema:{...XL([`site`,`range`,`minImpressions`,`limit`,`includeBrand`]),...gR,range:D.number().int().min(1).max(548).optional(),minImpressions:D.number().int().min(0).max(1e9).optional(),limit:D.number().int().min(1).max(100).optional(),verifyLimit:D.number().int().min(0).max(100).optional(),brandTerms:D.array(D.string().trim().min(1).max(200)).max(20).optional(),fetchConcurrency:D.number().int().min(1).max(16).optional(),fetchIntervalCap:D.number().int().min(1).max(60).optional(),fetchIntervalMs:D.number().int().min(100).max(6e4).optional()}},async({site:e,range:t,minImpressions:n,limit:r,includeBrand:i,brandTerms:a,...o})=>{try{let s=await DF({site:e,range:t,minImpressions:n,limit:r,includeBrand:i,brandTerms:a,..._R(o)});return $(s.summary.verdict,s)}catch(e){return Q(e)}})}function xR(e){yR(e),vR(e),hR(e),bR(e)}const SR=D.strictObject({site:D.string().trim().min(1).max(2048),startUrl:D.string().url().optional(),urls:D.array(D.string().url()).min(1).max(100).optional(),sitemaps:D.array(D.string().url()).min(1).max(20).optional(),properties:D.array(D.string().trim().min(1).max(2048)).min(1).max(1e3).optional(),limit:D.number().int().min(1).max(25e4).optional(),refresh:D.boolean().optional(),js:D.boolean().optional(),languageCode:D.string().trim().min(1).max(35).optional(),dailyLimit:D.number().int().min(1).max(2e3).optional(),inspectLimit:D.number().int().min(1).max(100).optional(),maxUrls:D.number().int().min(1).max(25e4).optional(),recoverLinks:D.boolean().optional(),recoverDays:D.number().int().min(1).max(548).optional(),recoverLimit:D.number().int().min(1).max(100).optional(),recoverMinClicks:D.number().int().min(0).max(1e9).optional(),recoverMinImpressions:D.number().int().min(0).max(1e9).optional()}).superRefine((e,t)=>{let n=!!(e.urls?.length||e.sitemaps?.length);!e.startUrl&&!n&&e.recoverLinks===!1&&t.addIssue({code:`custom`,path:[`recoverLinks`],message:`Pass startUrl, urls, sitemaps, or enable link recovery for technical-watch.`})});function CR(e){return $(e.summary,{...e,presentation:fI(e)},{markdown:pI(e)})}function wR(e){e.registerTool(`seo_workflow_diagnose_property`,{description:`Run the full agent workflow for property diagnosis and next actions`,inputSchema:{...XL([`site`,`days`,`recentDays`,`limit`,`includeBrand`,`refresh`])}},async({site:e,days:t,recentDays:n,limit:r,includeBrand:i,refresh:a})=>{try{return CR(await oI({site:e,days:t,recentDays:n,limit:r,includeBrand:i,refresh:a}))}catch(e){return Q(e)}}),e.registerTool(`seo_workflow_monthly_report`,{description:`Run the monthly reporting workflow with next actions`,inputSchema:{...XL([`site`,`limit`,`includeBrand`,`refresh`]),month:qL.optional()}},async({site:e,month:t,limit:n,includeBrand:r,refresh:i})=>{try{return CR(await mI({site:e,month:t,limit:n,includeBrand:r,refresh:i}))}catch(e){return Q(e)}}),e.registerTool(`seo_workflow_update_postmortem`,{description:`Run winner/loser postmortem analysis for recent Google update exposure`,inputSchema:{...XL([`site`,`days`,`recentDays`,`limit`,`includeBrand`,`refresh`]),knownConfounders:D.array(D.string()).optional(),includeChangeLog:D.boolean().optional()}},async({site:e,days:t,recentDays:n,limit:r,includeBrand:i,knownConfounders:a,includeChangeLog:o,refresh:s})=>{try{return CR(await KI({site:e,days:t,recentDays:n,limit:r,includeBrand:i,knownConfounders:a,includeChangeLog:o,refresh:s}))}catch(e){return Q(e)}}),e.registerTool(`seo_workflow_technical_watch`,{description:`Run scheduled-style crawl-diff and index-watch monitoring in one workflow`,inputSchema:SR},async({site:e,startUrl:t,urls:n,sitemaps:r,properties:i,limit:a,refresh:o,js:s,languageCode:c,dailyLimit:l,inspectLimit:u,maxUrls:d,recoverLinks:f,recoverDays:p,recoverLimit:m,recoverMinClicks:h,recoverMinImpressions:g})=>{try{return CR(await kI({site:e,startUrl:t,urls:n,sitemaps:r,properties:i,limit:a,refresh:o,js:JL(s,`auto`),languageCode:c,dailyLimit:l,inspectLimit:u,maxUrls:d,recoverLinks:f,recoverDays:p,recoverLimit:m,recoverMinClicks:h,recoverMinImpressions:g}))}catch(e){return Q(e)}}),e.registerTool(`seo_workflow_refresh_priorities`,{description:`Rank decay, striking-distance, quick-win, cannibalisation, and diagnosis signals into an action queue`,inputSchema:{...XL([`site`,`days`,`recentDays`,`limit`,`includeBrand`,`verifyContent`,`verifyLimit`,`refresh`]),googleAnalyticsPropertyId:D.string().optional()}},async({site:e,days:t,recentDays:n,limit:r,includeBrand:i,googleAnalyticsPropertyId:a,verifyContent:o,verifyLimit:s,refresh:c})=>{try{return CR(await DI({site:e,days:t,recentDays:n,limit:r,includeBrand:i,googleAnalyticsPropertyId:a,verifyContent:o,verifyLimit:s,refresh:c}))}catch(e){return Q(e)}})}const TR=[`ai-search`,`crawl`,`diagnosis`,`experiments`,`monitoring`,`opportunities`,`reporting`,`setup`,`workflows`],ER=[{category:`setup`,register:oR,names:[`seo_doctor`]},{category:`diagnosis`,register:oR,names:[`seo_segment_impact`,`seo_striking_distance`,`seo_traffic_anomaly`,`seo_update_correlate`]},{category:`opportunities`,register:lR,names:[`seo_cannibal`,`seo_ctr_underperformers`,`seo_decaying`,`seo_internal_links`,`seo_query_cluster`,`seo_quick_wins`]},{category:`ai-search`,register:eR,names:[`seo_ai_referrals`,`seo_community_intent`,`seo_content_optimization`,`seo_page_opportunities`,`seo_performance_audit`,`seo_to_ai_query`]},{category:`ai-search`,register:aR,names:[`seo_ai_search_scorecard`]},{category:`crawl`,register:aR,names:[`seo_affected_urls`,`seo_agent_readiness`,`seo_ai_readiness`,`seo_audit_urls`,`seo_compare_crawl_reports`,`seo_crawl_site`,`seo_entity_readiness`,`seo_explain_issue`,`seo_geo_gaps`,`seo_get_crawl_report`,`seo_list_crawl_reports`,`seo_list_rules`,`seo_llms_txt_audit`,`seo_llms_txt_generate`,`seo_okf_build`,`seo_okf_validate`,`seo_top_fixes`]},{category:`monitoring`,register:cR,names:[`seo_crawl_diff`,`seo_index_coverage`,`seo_index_coverage_plan`,`seo_index_monitor`,`seo_index_watch`,`seo_link_recover`,`seo_redirect_trace`]},{category:`reporting`,register:xR,names:[`seo_audit_page`,`seo_monthly_report`,`seo_report_narrative`,`seo_second_page`]},{category:`experiments`,register:sR,names:[`seo_measure_change`]},{category:`reporting`,register:dR,names:[`seo_pseo_audit`]},{category:`workflows`,register:wR,names:[`seo_workflow_diagnose_property`,`seo_workflow_monthly_report`,`seo_workflow_refresh_priorities`,`seo_workflow_technical_watch`,`seo_workflow_update_postmortem`]}],DR={cannibal:`cannibalisation`,decaying:`decaying-pages`,"crawl-site":`site-crawl`,"link-recover":`link-recovery`,"llms-txt-generate":`generate-llms-txt`,"query-cluster":`query-clusters`,"report-narrative":`narrative-report`,"to-ai-query":`seo-to-ai-query`,"workflow-refresh-priorities":`refresh-priorities`,"workflow-technical-watch":`technical-watch`,"workflow-update-postmortem":`update-postmortem`,"workflow-diagnose-property":`search-performance-overview`,"workflow-monthly-report":`monthly-action-plan`,doctor:`setup-check`,"update-correlate":`update-correlation`,"compare-crawl-reports":`compare-crawls`,"list-crawl-reports":`crawl-history`,"list-rules":`crawler-rules`,"get-crawl-report":`crawl-report`,"explain-issue":`explain-crawl-issue`};function OR(e){let t=e.replace(/^seo_/,``).replaceAll(`_`,`-`);return DR[t]??t}function kR(e,t){return e.id<t.id?-1:+(e.id>t.id)}function AR(e){let t=new Map;return e({registerTool(e,n,r){t.set(e,{inputSchema:n.inputSchema,handler:r})}}),t}function jR(e){return e instanceof D.ZodObject?e:D.strictObject(e??{})}function MR(){let e=new Map,t=[];for(let n of ER){let r=e.get(n.register);r||(r=AR(n.register),e.set(n.register,r));for(let e of n.names){let i=r.get(e);if(!i)throw Error(`MCP report registry references missing tool ${e}.`);let a=OR(e),o=mR(a);if(!o)throw Error(`MCP report registry is missing guidance for ${a}.`);t.push({id:a,category:n.category,...o,inputSchema:jR(i.inputSchema),handler:i.handler})}}return t.sort(kR)}const NR=MR(),PR=new Set(NR.map(e=>e.id)),FR=Object.keys(pR).filter(e=>!PR.has(e));if(FR.length>0)throw Error(`MCP report guidance references unknown reports: ${FR.join(`, `)}.`);const IR=new Map(NR.map(e=>[e.id,e]));function LR(e){return NR.filter(t=>!e||t.category===e).map(({id:e,name:t,description:n,category:r})=>({id:e,category:r,name:t,description:n}))}function RR(e){return IR.get(e)}function zR(e){return e.issues.map(e=>`${e.path.length>0?e.path.join(`.`):`input`}: ${e.message}`).join(`; `)}function BR(e){return LR(e)}function VR(e){let t=RR(e);if(!t)throw new k(`INVALID_INPUT`,`Unknown report: ${e}.`);return{id:t.id,category:t.category,name:t.name,description:t.description,useWhen:t.useWhen,avoidWhen:t.avoidWhen,outcome:t.outcome,readOrder:t.readOrder,doNotClaim:t.doNotClaim,verify:t.verify,related:t.related,inputSchema:D.toJSONSchema(t.inputSchema)}}async function HR(e,t={}){let n=RR(e);if(!n)throw new k(`INVALID_INPUT`,`Unknown report: ${e}.`);let r=n.inputSchema.safeParse(t);if(!r.success)throw new k(`INVALID_INPUT`,`Invalid parameters for ${e}: ${zR(r.error)}`);return n.handler(r.data)}async function UR(e,t={}){try{return await HR(e,t)}catch(e){return Q(e)}}const WR=D.looseObject({}),GR=new Set(BR().map(e=>e.id));function KR(e,t={}){e.registerTool(`seo_list_reports`,{description:`List compact SEO report ids, names, and purposes, optionally by category`,inputSchema:{category:D.enum(TR).optional()},outputSchema:WR,annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0}},async({category:e})=>{let t=BR(e),n=[...new Set(t.map(e=>e.category))];return $(`${t.length} SEO reports available across ${n.length} categories.`,{reports:t,categories:n})}),e.registerTool(`seo_describe_report`,{description:`Explain when to use one SEO report, what it returns, and which parameters it accepts`,inputSchema:{id:D.string().trim().min(1).max(100)},outputSchema:WR,annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0}},async({id:e})=>{try{let t=VR(e);return $(`${t.id}: ${t.description}`,{report:t})}catch(e){return Q(e)}}),e.registerTool(`seo_run_report`,{description:`Run one SEO report by id with parameters from seo_describe_report`,inputSchema:{id:D.string().trim().min(1).max(100),params:D.record(D.string(),D.unknown()).optional()},outputSchema:WR,annotations:{destructiveHint:!1}},async({id:e,params:n})=>{let r=GR.has(e)?t.telemetry?.():void 0;r&&VL(e,r);try{let t=await UR(e,n);return r&&(t.isError?UL(e,GL(t.structuredContent?.error),r):HL(e,r)),t}catch(t){throw r&&UL(e,GL(t),r),t}})}function qR(e={}){let t=new de({name:`seo`,version:fL},{capabilities:{logging:{}}}),n=()=>({clientName:t.server.getClientVersion()?.name});return KR(t,{telemetry:e.telemetry===!1?void 0:n}),e.telemetry!==!1&&(t.server.oninitialized=()=>{zL(n())}),t}async function JR(e={}){let t=qR();if(e.test){process.stdout.write(`seo MCP server constructed successfully.
559
559
  `);return}let n=new fe;await t.connect(n)}export{bk as $,Dn as $t,BF as A,jS as At,xM as B,Cb as Bt,$I as C,Dw as Ct,DI as D,ww as Dt,kI as E,Aw as Et,YN as F,fx as Ft,nj as G,V_ as Gt,cM as H,My as Ht,nP as I,dx as It,Nk as J,v_ as Jt,BA as K,q_ as Kt,oN as L,Ub as Lt,aF as M,kS as Mt,iF as N,AS as Nt,oI as O,mw as Ot,kP as P,OS as Pt,yk as Q,En as Qt,kM as R,Wb as Rt,ZI as S,Uw as St,KI as T,jw as Tt,Kj as U,Sy as Ut,mM as V,mb as Vt,Pj as W,mv as Wt,Pk as X,a_ as Xt,Ik as Y,J as Yt,Mk as Z,er as Zt,fL as _,k as _n,Xw as _t,BR as a,cn as an,SO as at,QI as b,Yw as bt,AL as c,pt as cn,iD as ct,LL as d,nt as dn,OT as dt,wn as en,$O as et,GL as f,ht as fn,PT as ft,WL as g,Ie as gn,qw as gt,VL as h,Le as hn,_T as ht,HR as i,yn as in,HO as it,DF as j,MS as jt,aI as k,ow as kt,IL as l,vt as ln,Z as lt,UL as m,_t as mn,NT as mt,qR as n,Cn as nn,XO as nt,UR as o,tn as on,QD as ot,HL as p,rt as pn,TT as pt,uA as q,N_ as qt,VR as r,bn as rn,GO as rt,JR as s,$t as sn,ID as st,TR as t,Tn as tn,ZO as tt,zL as u,mt as un,DE as ut,rL as v,De as vn,Zw as vt,tL as w,kw as wt,qI as x,Ww as xt,eL as y,Te as yn,Jw as yt,EM as z,jb as zt};
560
- //# sourceMappingURL=dist-C0LWlNAw.js.map
560
+ //# sourceMappingURL=dist-B8eKKp3B.js.map