seo 0.1.0 → 0.2.1

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.
Files changed (189) hide show
  1. package/PRIVACY.md +2 -2
  2. package/README.md +138 -42
  3. package/TERMS.md +2 -2
  4. package/TRADEMARKS.md +9 -5
  5. package/dist/cli.js +69 -52
  6. package/dist/cli.js.map +1 -1
  7. package/dist/{dist-CSY2_p-j.js → dist-CTBhWxn6.js} +49 -49
  8. package/dist/dist-CTBhWxn6.js.map +1 -0
  9. package/dist/{dist-FeAUNn9A.js → dist-eiewVfiS.js} +1 -1
  10. package/dist/index.d.ts +186 -94
  11. package/dist/index.js +843 -415
  12. package/dist/index.js.map +1 -1
  13. package/dist/mcp.d.ts +18 -1
  14. package/dist/mcp.js +6532 -4665
  15. package/dist/mcp.js.map +1 -1
  16. package/dist/{package-uzXzqDW6.js → package-BTOI9tde.js} +2 -2
  17. package/dist/{package-uzXzqDW6.js.map → package-BTOI9tde.js.map} +1 -1
  18. package/evals/README.md +117 -0
  19. package/evals/affected-urls.json +57 -0
  20. package/evals/ai-readiness.json +84 -0
  21. package/evals/ai-referrals.json +71 -0
  22. package/evals/ai-search-scorecard.json +70 -0
  23. package/evals/audit-page.json +57 -0
  24. package/evals/audit-urls.json +58 -0
  25. package/evals/cannibalisation.json +57 -0
  26. package/evals/community-intent.json +57 -0
  27. package/evals/compare-crawls.json +56 -0
  28. package/evals/content-optimization.json +57 -0
  29. package/evals/crawl-diff.json +58 -0
  30. package/evals/crawl-history.json +44 -0
  31. package/evals/crawl-report.json +44 -0
  32. package/evals/crawler-rules.json +44 -0
  33. package/evals/ctr-underperformers.json +57 -0
  34. package/evals/decaying-pages.json +57 -0
  35. package/evals/entity-readiness.json +57 -0
  36. package/evals/explain-crawl-issue.json +44 -0
  37. package/evals/generate-llms-txt.json +44 -0
  38. package/evals/geo-gaps.json +57 -0
  39. package/evals/index-coverage-plan.json +58 -0
  40. package/evals/index-coverage.json +70 -0
  41. package/evals/index-monitor.json +58 -0
  42. package/evals/index-watch.json +68 -0
  43. package/evals/internal-links.json +57 -0
  44. package/evals/link-recovery.json +58 -0
  45. package/evals/llms-txt-audit.json +44 -0
  46. package/evals/measure-change.json +58 -0
  47. package/evals/monthly-action-plan.json +56 -0
  48. package/evals/monthly-report.json +58 -0
  49. package/evals/narrative-report.json +58 -0
  50. package/evals/okf-build.json +44 -0
  51. package/evals/okf-validate.json +44 -0
  52. package/evals/page-opportunities.json +57 -0
  53. package/evals/performance-audit.json +57 -0
  54. package/evals/pseo-audit.json +84 -0
  55. package/evals/query-clusters.json +57 -0
  56. package/evals/quick-wins.json +86 -0
  57. package/evals/redirect-trace.json +57 -0
  58. package/evals/refresh-priorities.json +57 -0
  59. package/evals/search-performance-overview.json +86 -0
  60. package/evals/second-page.json +58 -0
  61. package/evals/segment-impact.json +57 -0
  62. package/evals/seo-fix-queue.json +85 -0
  63. package/evals/seo-geo-readiness.json +84 -0
  64. package/evals/seo-site-audit.json +85 -0
  65. package/evals/seo-to-ai-query.json +57 -0
  66. package/evals/seo.json +87 -0
  67. package/evals/setup-check.json +56 -0
  68. package/evals/site-crawl.json +44 -0
  69. package/evals/striking-distance.json +85 -0
  70. package/evals/technical-watch.json +57 -0
  71. package/evals/top-fixes.json +44 -0
  72. package/evals/traffic-anomaly.json +57 -0
  73. package/evals/update-correlation.json +57 -0
  74. package/evals/update-postmortem.json +57 -0
  75. package/package.json +5 -3
  76. package/skills/README.md +50 -61
  77. package/skills/seo/SKILL.md +88 -63
  78. package/skills/seo/agents/openai.yaml +2 -2
  79. package/dist/dist-CSY2_p-j.js.map +0 -1
  80. package/skills/affected-urls/SKILL.md +0 -39
  81. package/skills/affected-urls/agents/openai.yaml +0 -4
  82. package/skills/ai-readiness/SKILL.md +0 -34
  83. package/skills/ai-readiness/agents/openai.yaml +0 -4
  84. package/skills/ai-referrals/SKILL.md +0 -31
  85. package/skills/ai-referrals/agents/openai.yaml +0 -4
  86. package/skills/audit-page/SKILL.md +0 -50
  87. package/skills/audit-page/agents/openai.yaml +0 -4
  88. package/skills/audit-urls/SKILL.md +0 -39
  89. package/skills/audit-urls/agents/openai.yaml +0 -4
  90. package/skills/cannibalisation/SKILL.md +0 -31
  91. package/skills/cannibalisation/agents/openai.yaml +0 -4
  92. package/skills/community-intent/SKILL.md +0 -39
  93. package/skills/community-intent/agents/openai.yaml +0 -4
  94. package/skills/compare-crawls/SKILL.md +0 -40
  95. package/skills/compare-crawls/agents/openai.yaml +0 -4
  96. package/skills/content-optimization/SKILL.md +0 -31
  97. package/skills/content-optimization/agents/openai.yaml +0 -4
  98. package/skills/crawl-diff/SKILL.md +0 -51
  99. package/skills/crawl-diff/agents/openai.yaml +0 -4
  100. package/skills/crawl-history/SKILL.md +0 -37
  101. package/skills/crawl-history/agents/openai.yaml +0 -4
  102. package/skills/crawl-report/SKILL.md +0 -37
  103. package/skills/crawl-report/agents/openai.yaml +0 -4
  104. package/skills/crawler-rules/SKILL.md +0 -34
  105. package/skills/crawler-rules/agents/openai.yaml +0 -4
  106. package/skills/ctr-underperformers/SKILL.md +0 -31
  107. package/skills/ctr-underperformers/agents/openai.yaml +0 -4
  108. package/skills/decaying-pages/SKILL.md +0 -31
  109. package/skills/decaying-pages/agents/openai.yaml +0 -4
  110. package/skills/entity-readiness/SKILL.md +0 -34
  111. package/skills/entity-readiness/agents/openai.yaml +0 -4
  112. package/skills/explain-crawl-issue/SKILL.md +0 -32
  113. package/skills/explain-crawl-issue/agents/openai.yaml +0 -4
  114. package/skills/generate-llms-txt/SKILL.md +0 -37
  115. package/skills/generate-llms-txt/agents/openai.yaml +0 -4
  116. package/skills/geo-gaps/SKILL.md +0 -37
  117. package/skills/geo-gaps/agents/openai.yaml +0 -4
  118. package/skills/index-coverage/SKILL.md +0 -84
  119. package/skills/index-coverage/agents/openai.yaml +0 -4
  120. package/skills/index-coverage-plan/SKILL.md +0 -50
  121. package/skills/index-coverage-plan/agents/openai.yaml +0 -4
  122. package/skills/index-monitor/SKILL.md +0 -49
  123. package/skills/index-monitor/agents/openai.yaml +0 -4
  124. package/skills/index-watch/SKILL.md +0 -77
  125. package/skills/index-watch/agents/openai.yaml +0 -4
  126. package/skills/internal-links/SKILL.md +0 -31
  127. package/skills/internal-links/agents/openai.yaml +0 -4
  128. package/skills/link-recovery/SKILL.md +0 -51
  129. package/skills/link-recovery/agents/openai.yaml +0 -4
  130. package/skills/llms-txt-audit/SKILL.md +0 -34
  131. package/skills/llms-txt-audit/agents/openai.yaml +0 -4
  132. package/skills/measure-change/SKILL.md +0 -52
  133. package/skills/measure-change/agents/openai.yaml +0 -4
  134. package/skills/monthly-action-plan/SKILL.md +0 -50
  135. package/skills/monthly-action-plan/agents/openai.yaml +0 -4
  136. package/skills/monthly-report/SKILL.md +0 -50
  137. package/skills/monthly-report/agents/openai.yaml +0 -4
  138. package/skills/narrative-report/SKILL.md +0 -51
  139. package/skills/narrative-report/agents/openai.yaml +0 -4
  140. package/skills/okf-build/SKILL.md +0 -39
  141. package/skills/okf-build/agents/openai.yaml +0 -4
  142. package/skills/okf-validate/SKILL.md +0 -43
  143. package/skills/okf-validate/agents/openai.yaml +0 -4
  144. package/skills/page-opportunities/SKILL.md +0 -31
  145. package/skills/page-opportunities/agents/openai.yaml +0 -4
  146. package/skills/performance/SKILL.md +0 -50
  147. package/skills/performance/agents/openai.yaml +0 -4
  148. package/skills/performance-audit/SKILL.md +0 -31
  149. package/skills/performance-audit/agents/openai.yaml +0 -4
  150. package/skills/pseo-audit/SKILL.md +0 -103
  151. package/skills/pseo-audit/agents/openai.yaml +0 -4
  152. package/skills/query-clusters/SKILL.md +0 -31
  153. package/skills/query-clusters/agents/openai.yaml +0 -4
  154. package/skills/quick-wins/SKILL.md +0 -31
  155. package/skills/quick-wins/agents/openai.yaml +0 -4
  156. package/skills/redirect-trace/SKILL.md +0 -51
  157. package/skills/redirect-trace/agents/openai.yaml +0 -4
  158. package/skills/refresh-priorities/SKILL.md +0 -48
  159. package/skills/refresh-priorities/agents/openai.yaml +0 -4
  160. package/skills/search-performance-overview/SKILL.md +0 -48
  161. package/skills/search-performance-overview/agents/openai.yaml +0 -4
  162. package/skills/second-page/SKILL.md +0 -50
  163. package/skills/second-page/agents/openai.yaml +0 -4
  164. package/skills/segment-impact/SKILL.md +0 -50
  165. package/skills/segment-impact/agents/openai.yaml +0 -4
  166. package/skills/seo-fix-queue/SKILL.md +0 -50
  167. package/skills/seo-fix-queue/agents/openai.yaml +0 -4
  168. package/skills/seo-geo-readiness/SKILL.md +0 -60
  169. package/skills/seo-geo-readiness/agents/openai.yaml +0 -4
  170. package/skills/seo-site-audit/SKILL.md +0 -59
  171. package/skills/seo-site-audit/agents/openai.yaml +0 -4
  172. package/skills/seo-to-ai-query/SKILL.md +0 -31
  173. package/skills/seo-to-ai-query/agents/openai.yaml +0 -4
  174. package/skills/setup-check/SKILL.md +0 -49
  175. package/skills/setup-check/agents/openai.yaml +0 -4
  176. package/skills/site-crawl/SKILL.md +0 -39
  177. package/skills/site-crawl/agents/openai.yaml +0 -4
  178. package/skills/striking-distance/SKILL.md +0 -52
  179. package/skills/striking-distance/agents/openai.yaml +0 -4
  180. package/skills/technical-watch/SKILL.md +0 -49
  181. package/skills/technical-watch/agents/openai.yaml +0 -4
  182. package/skills/top-fixes/SKILL.md +0 -38
  183. package/skills/top-fixes/agents/openai.yaml +0 -4
  184. package/skills/traffic-anomaly/SKILL.md +0 -52
  185. package/skills/traffic-anomaly/agents/openai.yaml +0 -4
  186. package/skills/update-correlation/SKILL.md +0 -51
  187. package/skills/update-correlation/agents/openai.yaml +0 -4
  188. package/skills/update-postmortem/SKILL.md +0 -49
  189. package/skills/update-postmortem/agents/openai.yaml +0 -4
@@ -7,7 +7,7 @@ import{createRequire as e}from"node:module";import{Headers as t,fetch as n}from"
7
7
  <meta name="viewport" content="width=device-width, initial-scale=1">
8
8
  <meta name="color-scheme" content="light dark">
9
9
  <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'">
10
- <title>SEO Skills is connected</title>
10
+ <title>seo is connected</title>
11
11
  <style>
12
12
  :root {
13
13
  color-scheme: light dark;
@@ -146,7 +146,7 @@ import{createRequire as e}from"node:module";import{Headers as t,fetch as n}from"
146
146
  <svg viewBox="0 0 24 24" aria-hidden="true">
147
147
  <path fill="currentColor" d="M12,8c-2.2,0-4,1.8-4,4s1.8,4,4,4s4-1.8,4-4C16,9.8,14.2,8,12,8z M3,9L3,9c0.6,0,1-0.4,1-1V5c0-0.6,0.4-1,1-1h3 c0.6,0,1-0.4,1-1S8.6,2,8,2H5C3.3,2,2,3.3,2,5v3C2,8.6,2.4,9,3,9z M8,20H5c-0.6,0-1-0.4-1-1v-3c0-0.6-0.4-1-1-1s-1,0.4-1,1v3 c0,1.7,1.3,3,3,3h3c0.6,0,1-0.4,1-1S8.6,20,8,20z M21,15L21,15c-0.6,0-1,0.4-1,1v3c0,0.6-0.4,1-1,1h-3c-0.6,0-1,0.4-1,1s0.4,1,1,1h3 c1.7,0,3-1.3,3-3v-3C22,15.4,21.6,15,21,15z M19,2h-3c-0.6,0-1,0.4-1,1s0.4,1,1,1h3c0.6,0,1,0.4,1,1v3c0,0.6,0.4,1,1,1s1-0.4,1-1V5 C22,3.3,20.7,2,19,2z" />
148
148
  </svg>
149
- SEO Skills
149
+ seo
150
150
  </span>
151
151
  </div>
152
152
  </header>
@@ -155,7 +155,7 @@ import{createRequire as e}from"node:module";import{Headers as t,fetch as n}from"
155
155
  <div class="frame content">
156
156
  <p class="status"><span class="status-mark" aria-hidden="true"></span>Google account connected</p>
157
157
  <h1>You can start your first SEO report.</h1>
158
- <p class="lead">Go back to your terminal to continue. SEO Skills will help you choose a site and save a project profile before future reports.</p>
158
+ <p class="lead">Go back to your terminal to continue, and the CLI will help you choose a site and save a project profile before future reports.</p>
159
159
  </div>
160
160
  </section>
161
161
  <div class="frame details">
@@ -165,7 +165,7 @@ import{createRequire as e}from"node:module";import{Headers as t,fetch as n}from"
165
165
  </section>
166
166
  <section class="detail">
167
167
  <h2>Read-only access</h2>
168
- <p>SEO Skills can read the Google data you choose. It cannot change your site or Google account.</p>
168
+ <p>The seo CLI can read the Google data you choose. It cannot change your site or Google account.</p>
169
169
  </section>
170
170
  </div>
171
171
  </main>
@@ -485,33 +485,35 @@ CREATE INDEX IF NOT EXISTS idx_performance_reports_url ON performance_reports(ur
485
485
  updated_at = ?
486
486
  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 Wg(e){return _(`sha256`).update(`${e.clientId}\n${e.accountEmail.toLowerCase()}`).digest(`base64url`)}function Gg(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 Kg(e,t){return e.status===401?new A(`AUTH_EXPIRED`,"Google rejected the access token. Run `seo auth login` again."):e.status===403?new A(`ACCESS_DENIED`,`Google Search Console denied URL Inspection access to ${t.siteUrl}. Check the selected account and property permissions.`):e.status===404?new A(`PROPERTY_NOT_FOUND`,`Google Search Console could not inspect URLs under ${t.siteUrl}. Check the property value and account access.`):e.status>=500?new A(`PROVIDER_UNAVAILABLE`,`Google URL Inspection is unavailable (${e.status}). Try again later.`):new A(`PROVIDER_UNAVAILABLE`,`URL Inspection failed with ${e.status}.`)}async function qg(e){let t=Ng(e.siteUrl,e.inspectionUrl),{client:n,quotaIdentity:r}=await Fg(),i=Hg({credentialKey:Wg(r),property:e.siteUrl,limit:e.quotaLimit}),a;try{a=await Pg(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 Ug({reservation:i,outcome:`consumed`}),e instanceof A?e:new A(`PROVIDER_UNAVAILABLE`,`URL Inspection request failed: ${e instanceof Error?e.message:String(e)}`)}if(a.status===429){let t=new Date,n=Gg(a,t);throw Ug({reservation:i,outcome:`exhausted`,blockedUntil:n,now:t}),new Ig({property:e.siteUrl,resetAt:(n??new Date(i.resetAt)).toISOString(),used:i.used+i.count,limit:i.limit,reason:`provider`,requestSent:!0})}if(Ug({reservation:i,outcome:`consumed`}),!a.ok)throw Kg(a,e);return await a.json()}function Jg(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 Yg(e,t){let n=new Date(`${e}T00:00:00.000Z`);return n.setUTCDate(n.getUTCDate()+t),n.toISOString().slice(0,10)}function Xg(e=new Date){let t=Jg(e,`America/Los_Angeles`);return t.setUTCDate(t.getUTCDate()-4),t.toISOString().slice(0,10)}function Zg(e=28,t=new Date){let n=Xg(t);return{startDate:Yg(n,-(e-1)),endDate:n}}function Qg(e){return Zg(e)}function $g(e=28){return typeof e==`number`?Qg(e):e}async function e_(e,t,n={}){let{client:r}=await Fg(),i=j(),a=on([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 Pg(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 A(`RATE_LIMITED`,`GSC rate limit hit. Back off for 15 minutes.`):a.status===401?new A(`AUTH_EXPIRED`,"Google rejected the access token. Run `seo auth login` again."):a.status===403?new A(`ACCESS_DENIED`,`Google Search Console denied access to ${e}. Check the selected Google account and property permissions.`):a.status===404?new A(`PROPERTY_NOT_FOUND`,`Google Search Console could not find ${e}. Check the property value and account access.`):a.status>=500?new A(`PROVIDER_UNAVAILABLE`,`Google Search Console is unavailable (${a.status}). Try again later.`):new A(`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
487
487
  (site_url, query_hash, request_json, response_json, row_count, fetched_at, expires_at)
488
- 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 t_(e,t,n=28){let{startDate:r,endDate:i}=Qg(n),a=await e_(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 n_(e,t,n=28){return(await r_(e,t,n)).values}async function r_(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}=$g(n),s=await e_(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 i_(e,t,n=28){let{startDate:r,endDate:i}=Qg(n),a=(await e_(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 a_(e,t,n=28){return(await o_(e,t,n)).values}async function o_(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}=$g(n),s=await e_(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 s_(e=!1){let{client:t}=await Fg(),n=await Pg(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=j().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 c_(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 l_(e,t){return c_(e)===c_(t)}function u_(e,t){try{return new URL(e,t).toString()}catch{return e}}function d_(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&&!l_(e.url,e.page.finalUrl))&&t.push(`redirected`),tm(e.page?.metaRobots,`noindex`)&&t.push(`meta-noindex`),nm(e.page?.xRobotsTag,`noindex`)&&t.push(`x-robots-noindex`),e.page?.canonical&&!l_(e.page.finalUrl,u_(e.page.canonical,e.page.finalUrl))&&t.push(`canonical-mismatch`),t}const f_={id:`arial-20-v1`,fontFamily:`Arial`,fontSizePixels:20,fontWeight:400},p_=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]))),m_=new Map([[`\xA0`,5.6],[`‘`,4.4],[`’`,4.4],[`“`,7.1],[`”`,7.1],[`–`,11.1],[`—`,20],[`…`,16.7],[`•`,7]]),h_=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]]),g_=new Intl.Segmenter(`und`,{granularity:`grapheme`});function __(e,t){let n={high:0,medium:1,low:2};return n[t]>n[e]?t:e}function v_(e){let t=p_.get(e);if(t!==void 0)return{confidence:`high`,kerningCharacter:e,width:t};let n=m_.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?p_.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 y_(e){let t=e.trim().replace(/\s+/gu,` `),n=[...g_.segment(t)].map(e=>e.segment),r=[],i=`high`,a,o=0;for(let e of n){let t=v_(e);i=__(i,t.confidence),t.confidence===`low`&&!r.includes(e)&&r.push(e),a&&t.kerningCharacter&&(o+=h_.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:f_,graphemeCount:n.length,fallbackGraphemes:r.slice(0,10)}}const b_={fetchPage:Ag,queryPageMetrics:t_,now:()=>new Date};function x_(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 S_(e,t=b_){let n=await t.fetchPage(e.url,{js:e.js,refresh:e.refresh}),r=await Th(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=y_(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?l_(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=x_(c),c}const C_=new Set([`ac`,`co`,`com`,`edu`,`gov`,`net`,`org`]);function w_(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 T_(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&&C_.has(i)?2:1;return n.at(-(a+1))??n[0]}function E_(e){return[...new Set(e.map(w_).filter(Boolean))]}function D_(e){return e.length>3&&e.endsWith(`s`)?e.slice(0,-1):e}function O_(e,t){return e===t||D_(e)===D_(t)}function k_(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)=>O_(e[n+r]??``,t)))return!0;return!1}function A_(e){let t=T_(e.siteUrl);return E_([e.name,e.id,t].filter(e=>!!e).flatMap(e=>{let t=w_(e);return[t,t.replace(/\s+/g,``)]}))}function j_(e,t){let n=w_(e);if(!n||!t.length)return!1;let r=n.split(` `),i=n.replace(/\s+/g,``);return t.some(e=>{let t=w_(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=>O_(e,t))||o:k_(r,s)||o})}function M_(e){if(e.includeBrand)return!1;let t=e.brandTerms?.length?e.brandTerms:A_({siteUrl:e.siteUrl});return j_(e.query,t)}function N_(e=28){return Zg(e)}function P_(e,t,n){let r=w_(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 F_(e,t){let n=w_(e),r=n.replace(/\s+/g,``);return t.some(e=>{let t=w_(e),i=t.replace(/\s+/g,``);return n===t||r===i})}async function I_(e){let t=A_({id:e.id,name:e.name,siteUrl:e.site}),n=N_(e.days??28),{rows:r}=await e_(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||!j_(n,t))continue;let r={query:n,clicks:e.clicks,impressions:e.impressions,ctr:e.ctr,position:e.position},o=w_(n);o.split(` `).length<=4&&(F_(n,t)||n.includes(`.`))&&P_(a,o,r);for(let e of t)j_(n,[e])&&P_(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 L_={1:.27,2:.15,3:.1,4:.07,5:.05,6:.035,7:.025,8:.02,9:.015,10:.012};function R_(e){return e.normalize(`NFKD`).replace(/\p{Diacritic}/gu,``).replace(/[’']/g,`'`).toLowerCase().replace(/\s+/g,` `).trim()}function z_(e){let t=R_(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 B_(e){return z_(e)}function V_(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 H_(e=28,t=new Date){return Zg(e,t)}const U_=[{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 W_(e){let t=R_(e),n=U_.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 A(`INVALID_INPUT`,`${e.label} must be a whole number between ${e.minimum} and ${e.maximum}.`);return e.value}function G_(e,t){return Zg(e,t)}function K_(e,t){if(!e.startDate&&!e.endDate)return;if(!e.startDate||!e.endDate)throw new A(`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 A(`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 A(`INVALID_INPUT`,`Date range must not exceed ${t} days.`);return{range:{startDate:e.startDate,endDate:e.endDate},days:a}}function q_(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 J_(e,t){if(!e.site.trim())throw new A(`INVALID_INPUT`,`site must not be empty.`);let n=Y({value:e.days,fallback:28,minimum:1,maximum:548,label:`days`}),r=K_(e,548);if(r&&e.days!==void 0)throw new A(`INVALID_INPUT`,`Use either days or an explicit startDate/endDate range, not both.`);let i=r?.range??G_(n,t),a=G_(1,t).endDate,o=new Date(`${a}T00:00:00.000Z`);o.setUTCDate(o.getUTCDate()+4);let s=q_(o.toISOString().slice(0,10),16);if(i.endDate>a)throw new A(`INVALID_INPUT`,`endDate must be on or before the latest finalized GSC date, ${a}.`);if(i.startDate<s)throw new A(`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 Y_(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 X_(e,t){return t.impressions-e.impressions||t.clicks-e.clicks||e.position-t.position||Y_(e.query,t.query)}function Z_(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 Q_(e){return`${e.clicks}\u0000${e.impressions}\u0000${e.ctr}\u0000${e.position}`}function $_(e,t){let n=new Map;for(let r of e){let e=r.keys[0]?.trim()??``;if(!Z_(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(Q_)).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 ev={searchAnalytics:e_,now:()=>new Date};async function tv(e,t=ev){let n=t.now(),r=J_(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=$_(i.rows,a).filter(t=>t.impressions<r.minImpressions?(a.belowMinimumRows++,!1):M_({query:t.query,siteUrl:e.site,brandTerms:e.brandTerms,includeBrand:e.includeBrand})?(a.brandRows++,!1):!0).sort(X_);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 nv(e){return e.sourceRows===0?`empty`:e.partial?`partial`:e.classifiedRows===0?`filtered`:`available`}async function rv(e,t=ev){let n=await tv(e,t),r=n.rows.flatMap(e=>{let t=W_(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=nv({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 iv=new Set([`a`,`an`,`and`,`are`,`as`,`at`,`be`,`by`,`for`,`from`,`how`,`in`,`is`,`of`,`on`,`or`,`the`,`to`,`with`]);function av(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 ov(e){return[...e].some(e=>(e.codePointAt(0)??0)>127)}function sv(e){let t=av(e).split(` `).filter(e=>e.length>1||ov(e)),n=t.filter(e=>(e.length>2||ov(e))&&!iv.has(e));return[...new Set(n.length?n:t)]}function cv(e,t){let n=av(e),r=av(t);if(!n||!r)return 0;let i=RegExp(`(?:^|\\s)${n.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}(?=\\s|$)`,`g`);return[...r.matchAll(i)].length}function lv(e,t=``){let n=sv(e),r=av(t),i=n.filter(e=>r.split(` `).some(t=>t===e||t.startsWith(e))),a=n.filter(e=>!i.includes(e));return{phraseCount:cv(e,t),matchedTerms:i,missingTerms:a,termCoverage:n.length?i.length/n.length:0}}function uv(e){let t=e.query.trim();return t.length<=80?t:e.queryTerms.slice(0,7).join(` `)||`${t.slice(0,77)}...`}function dv(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 fv(e){return e===1?`does`:`do`}function pv(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 mv(e){return e.mainContent.termCoverage<.8?`content-gap`:e.title.termCoverage<.8||e.metaDescription.termCoverage<.8||e.h1.termCoverage<.8?`serp-framing`:`covered`}function hv(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:mv(e.fields),signals:t}}function gv(e){let t=uv(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=dv(e);return`The main content covers the important terms for "${t}", but the ${n.length?n.join(`, `):`title and meta`} ${fv(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 _v(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 vv(e){let t=yv(e.query,e.page),n=hv({technicalSignals:d_({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:pv(t),queryTerms:sv(e.query),fields:t,classification:n.classification,signals:n.signals,recommendation:``,summary:``};return{...r,recommendation:gv(r),summary:_v(r)}}function yv(e,t){return{title:lv(e,t.title),h1:lv(e,t.headings.filter(e=>e.level===1).map(e=>e.text).join(` `)),metaDescription:lv(e,t.metaDescription),mainContent:lv(e,t.contentText)}}async function bv(e){let t;try{t=await(e.fetch??Ag)(e.url,{js:e.js??`auto`,refresh:e.refresh,rate:e.rate});let n=await(e.extract??Th)(t,`defuddle`);return vv({query:e.query,url:e.url,page:n,fetchDiagnostics:t.diagnostics,httpStatus:t.status,warnings:t.warnings,verifiedAt:e.verifiedAt})}catch(n){let r=d_({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:sv(e.query),fields:{title:lv(e.query),h1:lv(e.query),metaDescription:lv(e.query),mainContent:lv(e.query)},classification:`fetch-failed`,signals:r,recommendation:`Verification failed; inspect fetch diagnostics before making content calls.`,summary:`Content verification failed.`}}}function xv(e){return Math.max(1,Math.min(10,Math.round(e)))}function Sv(e){return L_[xv(e)]??.01}function Cv(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 wv(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=Sv(e.position),u=e.samplePopulation??`positive_url_samples`,d=c?Cv(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 Tv(e){let t={},n=1/0;for(let r of Object.keys(L_).map(Number)){let i=e[r]??wv({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 Ev(e,t={}){let n=new Map;for(let t of e){let e=xv(t.position);n.set(e,[...n.get(e)??[],t])}let r={};for(let e of Object.keys(L_).map(Number))r[e]=wv({rows:n.get(e)??[],position:e,samplePopulation:t.samplePopulation,fallbackSource:t.fallbackSource});let i=Tv(r);function a(e,r,a){let o=xv(e.position),s=new Set(r),c={};for(let e of Object.keys(L_).map(Number))c[e]=wv({rows:n.get(e)??[],position:e,excludedRows:s,sourceSuffix:a,samplePopulation:t.samplePopulation,fallbackSource:t.fallbackSource});return Tv(c)[String(o)]??i[String(o)]??wv({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=>l_(e.keys?.[1]??``,n)):[t],`_leave_target_url_out`)},forAggregate(e,t=[]){return a(e,t,t.length?`_leave_group_out`:void 0)}}}function Dv(e){let t=d_(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:yv(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 Ov(e){if(e.verification.status===`technical-check`)return`technical-check`;if(e.verification.status===`verified`&&e.verification.fields){let t=mv(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 kv(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 Av(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 jv(e){return/\p{L}/u.test(e)}function Mv(e){return/\d/.test(e)}function Nv(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 Pv(e){let t=e.toLowerCase();return/(?:^|\s)-?(?:site|filetype|inurl|intitle|intext):\S+/.test(t)}function Fv(e){let t=e.toLowerCase();return t.includes(`ranking/`)||/\bmoved up\b/.test(t)}function Iv(e){return/^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/i.test(e.trim())}function Lv(e){return!B_(e).length||Pv(e)||Nv(e)||Fv(e)||Iv(e)}function Rv(e){let t=B_(e);if(!t.length||!jv(e)||Lv(e))return!0;let n=t.filter(jv);return!!(t.length===1&&Mv(t[0]??``)||n.length<=1&&t.some(Mv))}function zv(e,t,n,r){return e===void 0||!Number.isFinite(e)?t:Math.min(r,Math.max(n,Math.floor(e)))}function Bv(e){try{let t=new URL(e);return t.protocol===`http:`||t.protocol===`https:`}catch{return!1}}function Vv(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 Hv(e){return M_({query:e.query,siteUrl:e.site,brandTerms:e.brandTerms,includeBrand:e.includeBrand})}function Uv(e){return{minImpressions:zv(e.minImpressions,10,1,2**53-1),limit:zv(e.limit,25,1,100)}}function Wv(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||!Bv(a)||!Vv(r)){t.invalidRows++;continue}if(!l_(a,e.url)){t.wrongPageRows++;continue}if(r.impressions<e.minImpressions){t.belowMinimumRows++;continue}if(Rv(i)){t.lowActionabilityRows++;continue}if(Hv({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 Gv(e){return e.rows.filter(t=>{let n=t.keys[0]?.trim()??``;return t.keys.length>=2&&Bv(t.keys[1]??``)&&Vv(t)&&t.position>=1&&t.position<=10&&!!n&&!Rv(n)&&!Hv({query:n,site:e.site,brandTerms:e.brandTerms,includeBrand:e.includeBrand})})}function Kv(e,t){return e<t?-1:+(e>t)}function qv(e,t){return(t.estimatedCtrClickShortfall??-1)-(e.estimatedCtrClickShortfall??-1)||t.impressions-e.impressions||e.position-t.position||Kv(e.query,t.query)||Kv(e.url,t.url)}function Jv(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=Dv({query:t,url:e.reportInput.url,page:e.reportInput.page,fetchDiagnostics:e.reportInput.fetchDiagnostics,httpStatus:e.reportInput.httpStatus}),o=Ov({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:Av(n,e.excludedTargetRows.length),verification:a,recommendation:kv({query:t,position:e.row.position,type:o})}}function Yv(e){let{minImpressions:t,limit:n}=Uv(e),{eligibleRows:r,selection:i}=Wv({rows:e.targetRows,site:e.site,url:e.url,minImpressions:t,brandTerms:e.brandTerms,includeBrand:e.includeBrand}),a=Gv({rows:e.benchmarkRows,site:e.site,brandTerms:e.brandTerms,includeBrand:e.includeBrand}),o=a.filter(t=>l_(t.keys[1]??``,e.url)),s=Ev(a),c=r.map(t=>Jv({row:t,reportInput:e,benchmarkContext:s,excludedTargetRows:o})).sort(qv).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 Xv=new Map([`technical-check`,`content-gap`,`serp-framing`,`ranking`,`ctr`,`unverified`,`covered`].map((e,t)=>[e,t]));function Zv(e,t,n){return e===1?t:n}function Qv(e){return e.opportunityType!==`covered`&&e.opportunityType!==`unverified`}function $v(e,t){return(Xv.get(e.opportunityType)??99)-(Xv.get(t.opportunityType)??99)||(t.estimatedCtrClickShortfall??-1)-(e.estimatedCtrClickShortfall??-1)||t.impressions-e.impressions||(e.query<t.query?-1:+(e.query>t.query))}function ey(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||(Xv.get(e)??99)-(Xv.get(n)??99))[0]?.[0]??`no-data`}function ty(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 ny(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.${ty(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} ${Zv(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 ry(e){let t=e.items.filter(Qv).sort($v),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} ${Zv(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 iy=1e5,ay={searchAnalytics:e_,fetch:Ag,extract:Th,now:()=>new Date};function oy(e){return e instanceof Error?e.message:String(e)}function sy(e){return[...new Set(e.filter(Boolean))]}function cy(e){if(e===void 0)return 28;if(!Number.isInteger(e)||e<1||e>548)throw new A(`INVALID_INPUT`,`Days must be a whole number between 1 and 548.`);return e}async function ly(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: ${oy(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:sy(t)}}catch(e){let r=`Page extraction failed: ${oy(e)}`;return{fetched:n,verification:{status:`failed`,reason:r},warnings:sy([...t,r])}}}function uy(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 ${iy.toLocaleString(`en-US`)}-row cap, so provenance is complete but the peer sample is truncated.`:``].filter(Boolean)}async function dy(e,t=ay){let n=Ng(e.site,e.url),r=cy(e.days),i=H_(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=Yv({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?ly({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:iy},{refresh:e.refresh}):Promise.resolve({rows:[],calls:0,rowsFetched:0}),[l,u]=await Promise.all([s,c]),d=Yv({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=ey(d.items),p=ny({analysis:d,focus:f}),m=u.rowsFetched>=iy;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:iy,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:uy({reportInput:e,analysis:d,range:i,benchmarkTruncated:m,verification:l.verification}),recommendations:ry(d)}}function fy(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 py(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 my(e){return e.split(/\s+/).filter(Boolean).map(e=>`${e[0]?.toUpperCase()??``}${e.slice(1)}`).join(` `)}function hy(e){return e.estimatedCtrClickShortfall??e.estimatedClickLift??0}function gy(e,t){return e===`comparison`?`${my(t)}: what to choose`:e===`how-to`?`How to ${t.replace(/^how to\s+/i,``)}`:e===`definition`?`${my(t)}, in plain English`:e===`commercial`?`${my(t)}: costs and trade-offs`:my(t)}function _y(e){let t=new Map;for(let n of e.items){let e=fy(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 vy(e){let t=e.items.filter(e=>e.opportunityType!==`covered`&&e.opportunityType!==`technical-check`&&e.opportunityType!==`unverified`).sort((e,t)=>hy(t)-hy(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=fy(e.query);return{heading:gy(t,e.query),why:e.recommendation,queries:[e.query,...r.filter(n=>n.query!==e.query&&fy(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 yy(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 by(e){let t=_y(e),n=t[0]?.intent??`general`,r=e.items.filter(e=>e.opportunityType!==`covered`&&e.opportunityType!==`technical-check`&&e.opportunityType!==`unverified`).sort((e,t)=>hy(t)-hy(e))[0]?.query??e.items[0]?.query;return{site:e.site,url:e.url,generatedAt:e.generatedAt,sourceReport:e,summary:{score:py(e),primaryIntent:n,primaryQuery:r,queries:e.summary.queries,opportunities:e.summary.opportunities,estimatedClickLift:e.summary.estimatedClickLift,verdict:e.summary.verdict},intentMix:t,brief:vy(e),topActions:yy(e),caveats:e.caveats}}async function xy(e){return by(await dy(e))}const Sy={high:3,medium:2,low:1};function Cy(e,t={}){let n=new Map(e.pages.flatMap(e=>[[e.url,e],[e.finalUrl,e]])),r=e.issues.filter(e=>wy(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)=>Sy[t.severity]-Sy[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 wy(e,t){return!(t.ruleId&&e.ruleId!==t.ruleId||t.category&&e.category!==t.category||t.severity&&e.severity!==t.severity)}function Ty(e,t){return t<=0?0:Math.round(e/t*100)}function Ey(e,t){return Math.round(Math.max(0,Math.min(100,e))/100*t)}function Dy(e,t){if(t===0)return`info`;let n=t?e/t:0;return n>=.85?`pass`:n>=.5?`warning`:`fail`}function Oy(e,t,n){return{id:e,title:t,checks:n}}function ky(e){let{score:t,maxScore:n,...r}=e,i=e.evaluated??!0;return{...r,evaluated:i,status:i?Dy(t,n):`unknown`}}function Ay(e){return e.pages.filter(e=>e.indexable&&e.status<400)}function jy(e,t){return e.filter(t).slice(0,10).map(e=>e.finalUrl)}function My(e){let t=Ay(e),n=t.length,r=n>0,i=e.summary.pageLimitReached,a=e.ai?.robotsTxt,o=a?.botAccess??[],s=o.filter(e=>e.allowed===!1),c=o.filter(e=>e.allowed===null),l=a?.availability===`rate-limited`||a?.availability===`unreachable`,u=a!==void 0,d=u&&o.length>0,f=d&&!l,p=u&&!l,m=l||!u||!d,h=o.filter(e=>e.declared||e.coveredByWildcard),g=e.ai?.agentResources?.filter(e=>e.exists),_=e.pages.filter(e=>e.structuredDataFormats?.includes(`json-ld`)||(e.invalidJsonLdCount??0)>0),v=_.filter(e=>(e.invalidJsonLdCount??0)>0),y=t.filter(e=>e.geo?.answerable),b=t.filter(e=>e.geo?.semanticHtml),x=t.filter(e=>e.title&&e.h1),ee=t.filter(e=>e.metaDescription),te=t.filter(e=>e.lang),S=t.filter(e=>e.schemaTypes?.some(e=>/^(Organization|LocalBusiness|Person|Product|WebSite)$/i.test(e))||(e.schemaSameAs?.length??0)>0||(e.socialProfileLinks?.length??0)>0),C=t.map(e=>({url:e.finalUrl,control:am({metaRobots:e.metaRobots,xRobotsTag:e.xRobotsTag})})).filter(({control:e})=>e.status!==`not-restricted`),w=C.filter(({control:e})=>e.status===`blocked`),T=C.filter(({control:e})=>e.status===`limited`),ne=[Oy(`agent-access`,`Agent access`,[ky({id:`robots-ai-bots`,section:`agent-access`,maxScore:20,evaluated:f,score:l?0:o.length?s.length?Ey(Ty(o.length-s.length,o.length),20):20:8,title:l?`robots.txt crawler-policy evidence is unavailable`:d?s.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:l?`The robots.txt request failed, so this report cannot say whether selected crawler tokens are allowed at the start URL.`:d?s.length?`robots.txt policy blocks ${s.length} selected crawler ${s.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:l?`Restore a stable robots.txt response, then rerun this report before making crawler-policy claims.`:d?s.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:s,unknownBots:c,declaredBots:h.length,availability:a?.availability,status:a?.status,error:a?.error,scope:`start-url-robots-policy`,startUrl:e.config.url}}),ky({id:`robots-sitemap`,section:`agent-access`,maxScore:0,evaluated:p,score:0,title:p?`robots.txt sitemap declarations were checked`:`robots.txt sitemap declarations were not evaluated`,plainEnglish:l?`The robots.txt response was unavailable, so sitemap declarations could not be checked.`:u?a.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:l?`Restore robots.txt availability and rerun before evaluating sitemap declarations.`:u?`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:a?.sitemapUrls??[]}}),ky({id:`agent-resources`,section:`agent-access`,maxScore:0,score:0,title:`Agent descriptor files are discoverable`,plainEnglish:g?.length?`${g.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??[]}})]),Oy(`machine-readable`,`Machine-readable signals`,[ky({id:`llms-txt`,section:`machine-readable`,maxScore:0,score:0,title:`llms.txt is optional`,plainEnglish:e.ai?.llmsTxt?.exists?`An optional llms.txt file was found. 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,googleSearchImpact:`none`,guidanceUrl:`https://developers.google.com/search/updates#clarifying-guidance-on-llms-txt-files`}}),ky({id:`valid-json-ld`,section:`machine-readable`,maxScore:8,evaluated:_.length>0,score:v.length?3:8,title:_.length?`Observed JSON-LD parses cleanly`:`JSON-LD syntax was not evaluated`,plainEnglish:_.length?v.length?`${v.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:v.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:v.slice(0,10).map(e=>e.finalUrl)})]),Oy(`content-clarity`,`Content clarity`,[ky({id:`answerable-content`,section:`content-clarity`,maxScore:0,evaluated:r,score:0,title:`Short opening paragraphs are an unscored observation`,plainEnglish:r?`${Ty(y.length,n)}% 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:jy(t,e=>!e.geo?.answerable),evidence:{observedPages:y.length,evaluatedPages:n,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`}}),ky({id:`semantic-html`,section:`content-clarity`,maxScore:0,evaluated:r,score:0,title:`Semantic HTML coverage observed`,plainEnglish:r?`${Ty(b.length,n)}% 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.`}),ky({id:`titles-headings-meta`,section:`content-clarity`,maxScore:0,evaluated:r,score:0,title:`Title, H1, and description coverage observed`,plainEnglish:r?`${Ty(x.length,n)}% of indexable pages have title/H1 coverage and ${Ty(ee.length,n)}% 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.`}),ky({id:`language`,section:`content-clarity`,maxScore:0,evaluated:r,score:0,title:`HTML language declaration coverage observed`,plainEnglish:r?`${Ty(te.length,n)}% 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.`})]),Oy(`entity-signals`,`Entity signals`,[ky({id:`entity-schema`,section:`entity-signals`,maxScore:0,evaluated:r,score:0,title:`Entity-signal coverage observed`,plainEnglish:r?`${Ty(S.length,n)}% 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:jy(t,e=>!S.includes(e))}),ky({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.`})]),Oy(`technical-ux`,`Technical UX`,[ky({id:`snippet-controls`,section:`technical-ux`,maxScore:0,evaluated:r,score:0,title:r?C.length?`Page-level snippet restrictions observed`:`No page-level snippet restriction detected`:`Snippet controls were not evaluated`,plainEnglish:r?C.length?`${w.length} indexable ${w.length===1?`page has`:`pages have`} snippets blocked and ${T.length} ${T.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:t.length,blockedPages:w.length,limitedPages:T.length,restrictions:C.slice(0,10)}}),ky({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.`}),ky({id:`status-indexability`,section:`technical-ux`,maxScore:12,evaluated:e.pages.length>0,score:Ey(Ty(t.length,e.pages.length||1),12),title:e.pages.length?`Crawled-page indexability observed`:`Crawled-page indexability was not evaluated`,plainEnglish:e.pages.length?`${Ty(t.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.`})]),Oy(`crawl-completeness`,`Crawl completeness`,[ky({id:`crawl-depth`,section:`crawl-completeness`,maxScore:8,score:i?3:8,title:i?`The crawl hit the page cap`:`The crawl finished without hitting the page cap`,plainEnglish:i?`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.`}),ky({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.`})])],re=ne.flatMap(e=>e.checks),E=e.status===`completed`&&!m?`complete`:`partial`,ie=re.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:E,assessment:`evidence-only`,headline:E===`partial`?`AI-search evidence is incomplete. Fix the collection gaps before drawing site-wide conclusions.`:ie.length?`${ie.length} evidence-backed technical ${ie.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:ne,checks:re,topActions:ie,botAccess:o,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.`,...a?l?[`robots.txt was unavailable, so crawler access and sitemap declaration checks are inconclusive.`]:d?[]:[`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 Ny=[{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 external link no longer appears in externalLinkChecks as broken.`},{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.`}],Py={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`},Fy=new Map(Ny.map(e=>[e.id,{...e,recommendation:Py[e.id]??`fix`}]));function Iy(){return[...Fy.values()]}function Ly(e){return Fy.get(e)}function Ry(e){return Ly(e)?.recommendation??`fix`}function zy(e){if(e)try{let t=new URL(e);return t.hash=``,t.toString()}catch{return e}}function By(e){let t=zy(e.url),n=zy(e.finalUrl);return!!(t&&n&&t!==n)}function Vy(e){return e.status>=200&&e.status<300}function Hy(e){return/^\s*(?:text\/html|application\/xhtml\+xml)\b/i.test(e.contentType??``)}function Uy(e){let t=e.contentAuditAllowed??(!e.blocked&&e.robotsTxt?.allowed!==!1);return Vy(e)&&Hy(e)&&!By(e)&&t}function Wy(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 Gy(e){try{let t=new URL(e??``);return t.protocol===`http:`||t.protocol===`https:`}catch{return!1}}function Ky(e){if(e)try{let t=new URL(e);return t.hash=``,t.toString()}catch{return}}const qy=2e3,Jy=2*1024*1024,Yy=4*1024,Xy=.6;function X(e,t,n,r){let i=Ly(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 Zy(e,t,n,r){let i=Ly(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 Qy(e){return e===0||e===404||e===410||(e??0)>=500}function $y(e){let t=e.trim().toLowerCase();return t===`x-default`||/^[a-z]{2}(?:-[a-z]{4})?(?:-[a-z]{2})?$/.test(t)}function eb(e){return(e.searchMetrics?.clicks??0)>0||(e.searchMetrics?.impressions??0)>=100||(e.analytics?.sessions??0)>=25}function tb(e){return e?.trim().replace(/\s+/g,` `).toLowerCase()||void 0}function nb(e,t){let n=new Map;for(let r of e){if(!Uy(r))continue;let e=t(r)?.trim(),i=tb(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 rb(e){let t=new Map;for(let n of e){if(!Uy(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 ib=new Set([`a`,`an`,`and`,`are`,`for`,`from`,`how`,`in`,`is`,`of`,`on`,`or`,`the`,`to`,`vs`,`what`,`with`]);function ab(e){return[...new Set(e.toLowerCase().split(/[^a-z0-9]+/).filter(e=>e.length>2&&!ib.has(e)))]}function ob(e){let t=e.topQuery?.query;if(!t||(e.topQuery?.impressions??0)<50)return;let n=ab(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 sb(e){let t=[];for(let n of e){if(n.outcome===`failure`){if(n.failureKind===`aborted`)continue;t.push(Zy(`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||!Wy(n.requestedUrl,n.finalUrl))&&t.push(Zy(`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(Zy(`redirect_chain`,n,`${e.length} hops`,{redirectChain:e,hops:e.length}))}return t}function cb(e,t={}){let n=[],r=nb(e,e=>e.title),i=nb(e,e=>e.metaDescription),a=rb(e),o=new Map;for(let t of e)if(Uy(t))for(let e of[t.url,t.finalUrl]){let n=Ky(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})),Qy(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=By(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)>qy&&n.push(X(`slow_response`,s,`${s.responseTimeMs}ms`,{responseTimeMs:s.responseTimeMs,thresholdMs:qy})),e)continue;Hy(s)&&(s.sizeBytes??0)>Jy&&n.push(X(`large_html`,s,`${Math.round((s.sizeBytes??0)/1024)} KB`,{sizeBytes:s.sizeBytes,thresholdBytes:Jy})),Hy(s)&&!s.compression&&(s.sizeBytes??0)>Yy&&n.push(X(`no_compression`,s,void 0,{sizeBytes:s.sizeBytes,thresholdBytes:Yy,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??[])Qy(e.status)&&n.push(X(`broken_external_link`,s,e.url,{url:e.url,status:e.status,error:e.error}));s.indexable&&(s.internalInlinkCount??0)===0&&!Wy(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&&eb(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})),nm(s.xRobotsTag,`noindex`)&&n.push(X(`x_robots_noindex`,s,s.xRobotsTag,{xRobotsTag:s.xRobotsTag}));let l=nm(s.xRobotsTag,`nofollow`);if(l&&n.push(X(`nofollow`,s,s.xRobotsTag,{xRobotsTag:s.xRobotsTag})),!Uy(s))continue;let u=s.title?.trim(),d=u?y_(u):void 0,f=r.get(tb(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(tb(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&&!Gy(s.canonicalRaw)&&n.push(X(`canonical_non_absolute`,s,s.canonicalRaw,{canonicalRaw:s.canonicalRaw,canonical:s.canonical}));let e=o.get(Ky(s.canonical)??``);e?.canonical&&!Wy(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]}))}tm(s.metaRobots,`noindex`)&&n.push(X(`noindex`,s,s.indexability,{metaRobots:s.metaRobots})),tm(s.metaRobots,`nofollow`)&&!l&&n.push(X(`nofollow`,s,s.metaRobots,{metaRobots:s.metaRobots})),s.canonical&&!Wy(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&&!Wy(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 _=ob(s);_&&_.coverage<Xy&&n.push(X(`query_coverage_missing`,s,_.query,{query:_.query,impressions:s.topQuery?.impressions,matchedTerms:_.matchedTerms,missingTerms:_.missingTerms,coverage:_.coverage,threshold:Xy})),(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=>!$y(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=>Wy(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 lb=new Set([`authorization`,`cookie`,`proxy-authorization`,`set-cookie`,`www-authenticate`,`x-api-key`,`api-key`,`access-token`,`refresh-token`]),ub=/^(access_?token|auth|api_?key|client_?secret|code|jwt|key|password|refresh_?token|secret|session|sig|signature|token)$/i;function db(e=[]){return[...new Set(e.filter(Boolean))].sort()}function fb(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 pb(e){return{url:new URL(e.url).toString(),mode:e.mode??`site`,urls:db(e.urls??[]),maxPages:e.maxPages??500,maxDepth:e.maxDepth??16,concurrency:e.concurrency??8,timeoutMs:e.timeoutMs??2e4,include:db(e.include??[]),exclude:db(e.exclude??[]),respectRobots:e.respectRobots??!0,useSitemap:e.useSitemap??!0,checkExternal:e.checkExternal??!0,js:Eg(e.js),refresh:e.refresh??!1,fetchRate:fb(e)}}function mb(e){let t=pb(e);return _(`sha256`).update(JSON.stringify(t)).digest(`hex`).slice(0,16)}function hb(e){let t={config:pb(e.config),site:e.site??null,ga4PropertyId:e.ga4PropertyId??null};return`crawl_def_${_(`sha256`).update(JSON.stringify(t)).digest(`hex`).slice(0,20)}`}function gb(){return`crawl_${v().replaceAll(`-`,``)}`}function _b(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 vb(e){let t=e.toLowerCase();return lb.has(t)||ub.test(t.replace(/-/g,`_`))}function yb(e){try{let t=new URL(e);for(let e of[...t.searchParams.keys()])ub.test(e)&&t.searchParams.set(e,`[redacted]`);return t.toString()}catch{return e}}function bb(e){return e.replace(/https?:\/\/[^\s"'<>`]+/g,e=>yb(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 xb(e,t){return t&&vb(t)?`[redacted]`:typeof e==`string`?bb(e):Array.isArray(e)?e.map(e=>xb(e)):!e||typeof e!=`object`?e:Object.fromEntries(Object.entries(e).map(([e,t])=>[e,xb(t,e)]))}function Sb(e){return xb(e)}function Cb(e){return xb(e).map(({seoScore:e,geoScore:t,...n})=>n)}function wb(e){return xb(e)}function Tb(e=[]){return e.map(e=>bb(e))}function Eb(e,t){return e<t?-1:+(e>t)}function Db(e){return[...e].sort((e,t)=>Eb(e.url,t.url))}function Ob(e){return[...e].sort((e,t)=>Eb(e.requestedUrl,t.requestedUrl)||Eb(e.outcome,t.outcome)||Eb(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 kb(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;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,...Ab(e.pages,e.stats),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 Ab(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}return{discoveredUrls:t.discoveredUrls??n.size,queuedUrls:t.queuedUrls??e.length,crawledUrls:t.crawledUrls??e.length,skippedUrls:t.skippedUrls??0,failedUrls:t.failedUrls??e.filter(e=>e.status===0||e.status>=400).length,observedInternalLinks:t.observedInternalLinks??r,pageLimitReached:t.pageLimitReached??!1}}function jb(e){try{let t=new URL(e);return t.hash=``,t.toString()}catch{return}}function Mb(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=jb(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=jb(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 Nb(e){let t=Sb(pb(e.config)),n=hb({config:t,site:e.site,ga4PropertyId:e.ga4PropertyId}),r=Db(Cb(Mb(e.pages??[],e.linkGraph))),i=Ob(xb(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=wb(e.issues??[...sb(i),...cb(r,{startUrl:t.url})]),s=r;return{id:e.id??gb(),definitionId:n,projectId:e.projectId,site:e.site,ga4PropertyId:e.ga4PropertyId,generatedAt:e.generatedAt??new Date().toISOString(),status:e.status??`completed`,configHash:mb(t),config:t,summary:kb({pages:s,requests:i,requestEvidenceStatus:a,issues:o,stats:e.stats}),requestEvidenceStatus:a,requests:i,pages:s,issues:o,issueGroups:_b(o),...e.dataSources?{dataSources:xb(e.dataSources)}:{},...e.ai?{ai:xb(e.ai)}:{},...e.sitemapDiscovery?{sitemapDiscovery:xb(e.sitemapDiscovery)}:{},...e.externalLinkVerification?{externalLinkVerification:xb(e.externalLinkVerification)}:{},warnings:Tb(e.warnings).sort(Eb),caveats:Tb(e.caveats).sort(Eb)}}function Pb(e){let t=e.requestEvidenceStatus??(Object.hasOwn(e,`requests`)?`available`:`unavailable`),n=wb(e.issues??[]),r=Ob(xb(e.requests??[])??[]),i=Db(Cb(Mb(e.pages??[])));return{...xb(e),requestEvidenceStatus:t,requests:r,definitionId:e.definitionId??hb({config:e.config,site:e.site,ga4PropertyId:e.ga4PropertyId}),summary:kb({pages:i,requests:r,requestEvidenceStatus:t,issues:n,stats:e.summary}),pages:i,issues:n,issueGroups:_b(n),warnings:Tb(e.warnings).sort(Eb),caveats:Tb(e.caveats).sort(Eb)}}function Fb(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:Lb(e.report_json)}}function Ib(e){let t={};return e.projectId&&(t.projectId=e.projectId),e.site&&(t.site=e.site),{kind:`seo.crawl_report`,version:5,savedAt:new Date().toISOString(),...t.projectId||t.site?{tenant:t}:{},report:e}}function Lb(e){try{let t=JSON.parse(e);return t.kind===`seo.crawl_report`&&typeof t.version==`number`?t.version:0}catch{return 0}}function Rb(e){let t=JSON.parse(e);return t&&typeof t==`object`&&`kind`in t&&t.kind===`seo.crawl_report`&&`report`in t?Pb(t.report):Pb(t)}function zb(e){if(j().prepare(`INSERT INTO crawl_reports
488
+ 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 t_(e,t,n=28){let{startDate:r,endDate:i}=Qg(n),a=await e_(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 n_(e,t,n=28){return(await r_(e,t,n)).values}async function r_(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}=$g(n),s=await e_(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 i_(e,t,n=28){let{startDate:r,endDate:i}=Qg(n),a=(await e_(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 a_(e,t,n=28){return(await o_(e,t,n)).values}async function o_(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}=$g(n),s=await e_(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 s_(e=!1){let{client:t}=await Fg(),n=await Pg(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=j().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 c_(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 l_(e,t){return c_(e)===c_(t)}function u_(e,t){try{return new URL(e,t).toString()}catch{return e}}function d_(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&&!l_(e.url,e.page.finalUrl))&&t.push(`redirected`),tm(e.page?.metaRobots,`noindex`)&&t.push(`meta-noindex`),nm(e.page?.xRobotsTag,`noindex`)&&t.push(`x-robots-noindex`),e.page?.canonical&&!l_(e.page.finalUrl,u_(e.page.canonical,e.page.finalUrl))&&t.push(`canonical-mismatch`),t}const f_={id:`arial-20-v1`,fontFamily:`Arial`,fontSizePixels:20,fontWeight:400},p_=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]))),m_=new Map([[`\xA0`,5.6],[`‘`,4.4],[`’`,4.4],[`“`,7.1],[`”`,7.1],[`–`,11.1],[`—`,20],[`…`,16.7],[`•`,7]]),h_=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]]),g_=new Intl.Segmenter(`und`,{granularity:`grapheme`});function __(e,t){let n={high:0,medium:1,low:2};return n[t]>n[e]?t:e}function v_(e){let t=p_.get(e);if(t!==void 0)return{confidence:`high`,kerningCharacter:e,width:t};let n=m_.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?p_.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 y_(e){let t=e.trim().replace(/\s+/gu,` `),n=[...g_.segment(t)].map(e=>e.segment),r=[],i=`high`,a,o=0;for(let e of n){let t=v_(e);i=__(i,t.confidence),t.confidence===`low`&&!r.includes(e)&&r.push(e),a&&t.kerningCharacter&&(o+=h_.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:f_,graphemeCount:n.length,fallbackGraphemes:r.slice(0,10)}}const b_={fetchPage:Ag,queryPageMetrics:t_,now:()=>new Date};function x_(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 S_(e,t=b_){let n=await t.fetchPage(e.url,{js:e.js,refresh:e.refresh}),r=await Th(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=y_(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?l_(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=x_(c),c}const C_=new Set([`ac`,`co`,`com`,`edu`,`gov`,`net`,`org`]);function w_(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 T_(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&&C_.has(i)?2:1;return n.at(-(a+1))??n[0]}function E_(e){return[...new Set(e.map(w_).filter(Boolean))]}function D_(e){return e.length>3&&e.endsWith(`s`)?e.slice(0,-1):e}function O_(e,t){return e===t||D_(e)===D_(t)}function k_(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)=>O_(e[n+r]??``,t)))return!0;return!1}function A_(e){let t=T_(e.siteUrl);return E_([e.name,e.id,t].filter(e=>!!e).flatMap(e=>{let t=w_(e);return[t,t.replace(/\s+/g,``)]}))}function j_(e,t){let n=w_(e);if(!n||!t.length)return!1;let r=n.split(` `),i=n.replace(/\s+/g,``);return t.some(e=>{let t=w_(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=>O_(e,t))||o:k_(r,s)||o})}function M_(e){if(e.includeBrand)return!1;let t=e.brandTerms?.length?e.brandTerms:A_({siteUrl:e.siteUrl});return j_(e.query,t)}function N_(e=28){return Zg(e)}function P_(e,t,n){let r=w_(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 F_(e,t){let n=w_(e),r=n.replace(/\s+/g,``);return t.some(e=>{let t=w_(e),i=t.replace(/\s+/g,``);return n===t||r===i})}async function I_(e){let t=A_({id:e.id,name:e.name,siteUrl:e.site}),n=N_(e.days??28),{rows:r}=await e_(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||!j_(n,t))continue;let r={query:n,clicks:e.clicks,impressions:e.impressions,ctr:e.ctr,position:e.position},o=w_(n);o.split(` `).length<=4&&(F_(n,t)||n.includes(`.`))&&P_(a,o,r);for(let e of t)j_(n,[e])&&P_(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 L_={1:.27,2:.15,3:.1,4:.07,5:.05,6:.035,7:.025,8:.02,9:.015,10:.012};function R_(e){return e.normalize(`NFKD`).replace(/\p{Diacritic}/gu,``).replace(/[’']/g,`'`).toLowerCase().replace(/\s+/g,` `).trim()}function z_(e){let t=R_(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 B_(e){return z_(e)}function V_(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 H_(e=28,t=new Date){return Zg(e,t)}const U_=[{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 W_(e){let t=R_(e),n=U_.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 A(`INVALID_INPUT`,`${e.label} must be a whole number between ${e.minimum} and ${e.maximum}.`);return e.value}function G_(e,t){return Zg(e,t)}function K_(e,t){if(!e.startDate&&!e.endDate)return;if(!e.startDate||!e.endDate)throw new A(`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 A(`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 A(`INVALID_INPUT`,`Date range must not exceed ${t} days.`);return{range:{startDate:e.startDate,endDate:e.endDate},days:a}}function q_(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 J_(e,t){if(!e.site.trim())throw new A(`INVALID_INPUT`,`site must not be empty.`);let n=Y({value:e.days,fallback:28,minimum:1,maximum:548,label:`days`}),r=K_(e,548);if(r&&e.days!==void 0)throw new A(`INVALID_INPUT`,`Use either days or an explicit startDate/endDate range, not both.`);let i=r?.range??G_(n,t),a=G_(1,t).endDate,o=new Date(`${a}T00:00:00.000Z`);o.setUTCDate(o.getUTCDate()+4);let s=q_(o.toISOString().slice(0,10),16);if(i.endDate>a)throw new A(`INVALID_INPUT`,`endDate must be on or before the latest finalized GSC date, ${a}.`);if(i.startDate<s)throw new A(`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 Y_(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 X_(e,t){return t.impressions-e.impressions||t.clicks-e.clicks||e.position-t.position||Y_(e.query,t.query)}function Z_(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 Q_(e){return`${e.clicks}\u0000${e.impressions}\u0000${e.ctr}\u0000${e.position}`}function $_(e,t){let n=new Map;for(let r of e){let e=r.keys[0]?.trim()??``;if(!Z_(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(Q_)).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 ev={searchAnalytics:e_,now:()=>new Date};async function tv(e,t=ev){let n=t.now(),r=J_(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=$_(i.rows,a).filter(t=>t.impressions<r.minImpressions?(a.belowMinimumRows++,!1):M_({query:t.query,siteUrl:e.site,brandTerms:e.brandTerms,includeBrand:e.includeBrand})?(a.brandRows++,!1):!0).sort(X_);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 nv(e){return e.sourceRows===0?`empty`:e.partial?`partial`:e.classifiedRows===0?`filtered`:`available`}async function rv(e,t=ev){let n=await tv(e,t),r=n.rows.flatMap(e=>{let t=W_(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=nv({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 iv=new Set([`a`,`an`,`and`,`are`,`as`,`at`,`be`,`by`,`for`,`from`,`how`,`in`,`is`,`of`,`on`,`or`,`the`,`to`,`with`]);function av(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 ov(e){return[...e].some(e=>(e.codePointAt(0)??0)>127)}function sv(e){let t=av(e).split(` `).filter(e=>e.length>1||ov(e)),n=t.filter(e=>(e.length>2||ov(e))&&!iv.has(e));return[...new Set(n.length?n:t)]}function cv(e,t){let n=av(e),r=av(t);if(!n||!r)return 0;let i=RegExp(`(?:^|\\s)${n.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}(?=\\s|$)`,`g`);return[...r.matchAll(i)].length}function lv(e,t=``){let n=sv(e),r=av(t),i=n.filter(e=>r.split(` `).some(t=>t===e||t.startsWith(e))),a=n.filter(e=>!i.includes(e));return{phraseCount:cv(e,t),matchedTerms:i,missingTerms:a,termCoverage:n.length?i.length/n.length:0}}function uv(e){let t=e.query.trim();return t.length<=80?t:e.queryTerms.slice(0,7).join(` `)||`${t.slice(0,77)}...`}function dv(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 fv(e){return e===1?`does`:`do`}function pv(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 mv(e){return e.mainContent.termCoverage<.8?`content-gap`:e.title.termCoverage<.8||e.metaDescription.termCoverage<.8||e.h1.termCoverage<.8?`serp-framing`:`covered`}function hv(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:mv(e.fields),signals:t}}function gv(e){let t=uv(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=dv(e);return`The main content covers the important terms for "${t}", but the ${n.length?n.join(`, `):`title and meta`} ${fv(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 _v(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 vv(e){let t=yv(e.query,e.page),n=hv({technicalSignals:d_({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:pv(t),queryTerms:sv(e.query),fields:t,classification:n.classification,signals:n.signals,recommendation:``,summary:``};return{...r,recommendation:gv(r),summary:_v(r)}}function yv(e,t){return{title:lv(e,t.title),h1:lv(e,t.headings.filter(e=>e.level===1).map(e=>e.text).join(` `)),metaDescription:lv(e,t.metaDescription),mainContent:lv(e,t.contentText)}}async function bv(e){let t;try{t=await(e.fetch??Ag)(e.url,{js:e.js??`auto`,refresh:e.refresh,rate:e.rate});let n=await(e.extract??Th)(t,`defuddle`);return vv({query:e.query,url:e.url,page:n,fetchDiagnostics:t.diagnostics,httpStatus:t.status,warnings:t.warnings,verifiedAt:e.verifiedAt})}catch(n){let r=d_({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:sv(e.query),fields:{title:lv(e.query),h1:lv(e.query),metaDescription:lv(e.query),mainContent:lv(e.query)},classification:`fetch-failed`,signals:r,recommendation:`Verification failed; inspect fetch diagnostics before making content calls.`,summary:`Content verification failed.`}}}function xv(e){return Math.max(1,Math.min(10,Math.round(e)))}function Sv(e){return L_[xv(e)]??.01}function Cv(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 wv(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=Sv(e.position),u=e.samplePopulation??`positive_url_samples`,d=c?Cv(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 Tv(e){let t={},n=1/0;for(let r of Object.keys(L_).map(Number)){let i=e[r]??wv({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 Ev(e,t={}){let n=new Map;for(let t of e){let e=xv(t.position);n.set(e,[...n.get(e)??[],t])}let r={};for(let e of Object.keys(L_).map(Number))r[e]=wv({rows:n.get(e)??[],position:e,samplePopulation:t.samplePopulation,fallbackSource:t.fallbackSource});let i=Tv(r);function a(e,r,a){let o=xv(e.position),s=new Set(r),c={};for(let e of Object.keys(L_).map(Number))c[e]=wv({rows:n.get(e)??[],position:e,excludedRows:s,sourceSuffix:a,samplePopulation:t.samplePopulation,fallbackSource:t.fallbackSource});return Tv(c)[String(o)]??i[String(o)]??wv({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=>l_(e.keys?.[1]??``,n)):[t],`_leave_target_url_out`)},forAggregate(e,t=[]){return a(e,t,t.length?`_leave_group_out`:void 0)}}}function Dv(e){let t=d_(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:yv(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 Ov(e){if(e.verification.status===`technical-check`)return`technical-check`;if(e.verification.status===`verified`&&e.verification.fields){let t=mv(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 kv(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 Av(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 jv(e){return/\p{L}/u.test(e)}function Mv(e){return/\d/.test(e)}function Nv(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 Pv(e){let t=e.toLowerCase();return/(?:^|\s)-?(?:site|filetype|inurl|intitle|intext):\S+/.test(t)}function Fv(e){let t=e.toLowerCase();return t.includes(`ranking/`)||/\bmoved up\b/.test(t)}function Iv(e){return/^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/i.test(e.trim())}function Lv(e){return!B_(e).length||Pv(e)||Nv(e)||Fv(e)||Iv(e)}function Rv(e){let t=B_(e);if(!t.length||!jv(e)||Lv(e))return!0;let n=t.filter(jv);return!!(t.length===1&&Mv(t[0]??``)||n.length<=1&&t.some(Mv))}function zv(e,t,n,r){return e===void 0||!Number.isFinite(e)?t:Math.min(r,Math.max(n,Math.floor(e)))}function Bv(e){try{let t=new URL(e);return t.protocol===`http:`||t.protocol===`https:`}catch{return!1}}function Vv(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 Hv(e){return M_({query:e.query,siteUrl:e.site,brandTerms:e.brandTerms,includeBrand:e.includeBrand})}function Uv(e){return{minImpressions:zv(e.minImpressions,10,1,2**53-1),limit:zv(e.limit,25,1,100)}}function Wv(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||!Bv(a)||!Vv(r)){t.invalidRows++;continue}if(!l_(a,e.url)){t.wrongPageRows++;continue}if(r.impressions<e.minImpressions){t.belowMinimumRows++;continue}if(Rv(i)){t.lowActionabilityRows++;continue}if(Hv({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 Gv(e){return e.rows.filter(t=>{let n=t.keys[0]?.trim()??``;return t.keys.length>=2&&Bv(t.keys[1]??``)&&Vv(t)&&t.position>=1&&t.position<=10&&!!n&&!Rv(n)&&!Hv({query:n,site:e.site,brandTerms:e.brandTerms,includeBrand:e.includeBrand})})}function Kv(e,t){return e<t?-1:+(e>t)}function qv(e,t){return(t.estimatedCtrClickShortfall??-1)-(e.estimatedCtrClickShortfall??-1)||t.impressions-e.impressions||e.position-t.position||Kv(e.query,t.query)||Kv(e.url,t.url)}function Jv(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=Dv({query:t,url:e.reportInput.url,page:e.reportInput.page,fetchDiagnostics:e.reportInput.fetchDiagnostics,httpStatus:e.reportInput.httpStatus}),o=Ov({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:Av(n,e.excludedTargetRows.length),verification:a,recommendation:kv({query:t,position:e.row.position,type:o})}}function Yv(e){let{minImpressions:t,limit:n}=Uv(e),{eligibleRows:r,selection:i}=Wv({rows:e.targetRows,site:e.site,url:e.url,minImpressions:t,brandTerms:e.brandTerms,includeBrand:e.includeBrand}),a=Gv({rows:e.benchmarkRows,site:e.site,brandTerms:e.brandTerms,includeBrand:e.includeBrand}),o=a.filter(t=>l_(t.keys[1]??``,e.url)),s=Ev(a),c=r.map(t=>Jv({row:t,reportInput:e,benchmarkContext:s,excludedTargetRows:o})).sort(qv).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 Xv=new Map([`technical-check`,`content-gap`,`serp-framing`,`ranking`,`ctr`,`unverified`,`covered`].map((e,t)=>[e,t]));function Zv(e,t,n){return e===1?t:n}function Qv(e){return e.opportunityType!==`covered`&&e.opportunityType!==`unverified`}function $v(e,t){return(Xv.get(e.opportunityType)??99)-(Xv.get(t.opportunityType)??99)||(t.estimatedCtrClickShortfall??-1)-(e.estimatedCtrClickShortfall??-1)||t.impressions-e.impressions||(e.query<t.query?-1:+(e.query>t.query))}function ey(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||(Xv.get(e)??99)-(Xv.get(n)??99))[0]?.[0]??`no-data`}function ty(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 ny(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.${ty(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} ${Zv(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 ry(e){let t=e.items.filter(Qv).sort($v),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} ${Zv(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 iy=1e5,ay={searchAnalytics:e_,fetch:Ag,extract:Th,now:()=>new Date};function oy(e){return e instanceof Error?e.message:String(e)}function sy(e){return[...new Set(e.filter(Boolean))]}function cy(e){if(e===void 0)return 28;if(!Number.isInteger(e)||e<1||e>548)throw new A(`INVALID_INPUT`,`Days must be a whole number between 1 and 548.`);return e}async function ly(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: ${oy(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:sy(t)}}catch(e){let r=`Page extraction failed: ${oy(e)}`;return{fetched:n,verification:{status:`failed`,reason:r},warnings:sy([...t,r])}}}function uy(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 ${iy.toLocaleString(`en-US`)}-row cap, so provenance is complete but the peer sample is truncated.`:``].filter(Boolean)}async function dy(e,t=ay){let n=Ng(e.site,e.url),r=cy(e.days),i=H_(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=Yv({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?ly({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:iy},{refresh:e.refresh}):Promise.resolve({rows:[],calls:0,rowsFetched:0}),[l,u]=await Promise.all([s,c]),d=Yv({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=ey(d.items),p=ny({analysis:d,focus:f}),m=u.rowsFetched>=iy;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:iy,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:uy({reportInput:e,analysis:d,range:i,benchmarkTruncated:m,verification:l.verification}),recommendations:ry(d)}}function fy(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 py(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 my(e){return e.split(/\s+/).filter(Boolean).map(e=>`${e[0]?.toUpperCase()??``}${e.slice(1)}`).join(` `)}function hy(e){return e.estimatedCtrClickShortfall??e.estimatedClickLift??0}function gy(e,t){return e===`comparison`?`${my(t)}: what to choose`:e===`how-to`?`How to ${t.replace(/^how to\s+/i,``)}`:e===`definition`?`${my(t)}, in plain English`:e===`commercial`?`${my(t)}: costs and trade-offs`:my(t)}function _y(e){let t=new Map;for(let n of e.items){let e=fy(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 vy(e){let t=e.items.filter(e=>e.opportunityType!==`covered`&&e.opportunityType!==`technical-check`&&e.opportunityType!==`unverified`).sort((e,t)=>hy(t)-hy(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=fy(e.query);return{heading:gy(t,e.query),why:e.recommendation,queries:[e.query,...r.filter(n=>n.query!==e.query&&fy(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 yy(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 by(e){let t=_y(e),n=t[0]?.intent??`general`,r=e.items.filter(e=>e.opportunityType!==`covered`&&e.opportunityType!==`technical-check`&&e.opportunityType!==`unverified`).sort((e,t)=>hy(t)-hy(e))[0]?.query??e.items[0]?.query;return{site:e.site,url:e.url,generatedAt:e.generatedAt,sourceReport:e,summary:{score:py(e),primaryIntent:n,primaryQuery:r,queries:e.summary.queries,opportunities:e.summary.opportunities,estimatedClickLift:e.summary.estimatedClickLift,verdict:e.summary.verdict},intentMix:t,brief:vy(e),topActions:yy(e),caveats:e.caveats}}async function xy(e){return by(await dy(e))}const Sy={high:3,medium:2,low:1};function Cy(e,t={}){let n=new Map(e.pages.flatMap(e=>[[e.url,e],[e.finalUrl,e]])),r=e.issues.filter(e=>wy(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)=>Sy[t.severity]-Sy[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 wy(e,t){return!(t.ruleId&&e.ruleId!==t.ruleId||t.category&&e.category!==t.category||t.severity&&e.severity!==t.severity)}function Ty(e,t){return t<=0?0:Math.round(e/t*100)}function Ey(e,t){return Math.round(Math.max(0,Math.min(100,e))/100*t)}function Dy(e,t){if(t===0)return`info`;let n=t?e/t:0;return n>=.85?`pass`:n>=.5?`warning`:`fail`}function Oy(e,t,n){return{id:e,title:t,checks:n}}function ky(e){let{score:t,maxScore:n,...r}=e,i=e.evaluated??!0;return{...r,evaluated:i,status:i?Dy(t,n):`unknown`}}function Ay(e){return e.pages.filter(e=>e.indexable&&e.status<400)}function jy(e,t){return e.filter(t).slice(0,10).map(e=>e.finalUrl)}function My(e){let t=Ay(e),n=t.length,r=n>0,i=e.summary.pageLimitReached,a=e.ai?.robotsTxt,o=a?.botAccess??[],s=o.filter(e=>e.allowed===!1),c=o.filter(e=>e.allowed===null),l=a?.availability===`rate-limited`||a?.availability===`unreachable`,u=a!==void 0,d=u&&o.length>0,f=d&&!l,p=u&&!l,m=l||!u||!d,h=o.filter(e=>e.declared||e.coveredByWildcard),g=e.ai?.agentResources?.filter(e=>e.exists),_=e.pages.filter(e=>e.structuredDataFormats?.includes(`json-ld`)||(e.invalidJsonLdCount??0)>0),v=_.filter(e=>(e.invalidJsonLdCount??0)>0),y=t.filter(e=>e.geo?.answerable),b=t.filter(e=>e.geo?.semanticHtml),x=t.filter(e=>e.title&&e.h1),ee=t.filter(e=>e.metaDescription),te=t.filter(e=>e.lang),S=t.filter(e=>e.schemaTypes?.some(e=>/^(Organization|LocalBusiness|Person|Product|WebSite)$/i.test(e))||(e.schemaSameAs?.length??0)>0||(e.socialProfileLinks?.length??0)>0),C=t.map(e=>({url:e.finalUrl,control:am({metaRobots:e.metaRobots,xRobotsTag:e.xRobotsTag})})).filter(({control:e})=>e.status!==`not-restricted`),w=C.filter(({control:e})=>e.status===`blocked`),T=C.filter(({control:e})=>e.status===`limited`),ne=[Oy(`agent-access`,`Agent access`,[ky({id:`robots-ai-bots`,section:`agent-access`,maxScore:20,evaluated:f,score:l?0:o.length?s.length?Ey(Ty(o.length-s.length,o.length),20):20:8,title:l?`robots.txt crawler-policy evidence is unavailable`:d?s.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:l?`The robots.txt request failed, so this report cannot say whether selected crawler tokens are allowed at the start URL.`:d?s.length?`robots.txt policy blocks ${s.length} selected crawler ${s.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:l?`Restore a stable robots.txt response, then rerun this report before making crawler-policy claims.`:d?s.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:s,unknownBots:c,declaredBots:h.length,availability:a?.availability,status:a?.status,error:a?.error,scope:`start-url-robots-policy`,startUrl:e.config.url}}),ky({id:`robots-sitemap`,section:`agent-access`,maxScore:0,evaluated:p,score:0,title:p?`robots.txt sitemap declarations were checked`:`robots.txt sitemap declarations were not evaluated`,plainEnglish:l?`The robots.txt response was unavailable, so sitemap declarations could not be checked.`:u?a.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:l?`Restore robots.txt availability and rerun before evaluating sitemap declarations.`:u?`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:a?.sitemapUrls??[]}}),ky({id:`agent-resources`,section:`agent-access`,maxScore:0,score:0,title:`Agent descriptor files are discoverable`,plainEnglish:g?.length?`${g.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??[]}})]),Oy(`machine-readable`,`Machine-readable signals`,[ky({id:`llms-txt`,section:`machine-readable`,maxScore:0,score:0,title:`llms.txt is optional`,plainEnglish:e.ai?.llmsTxt?.exists?`An optional llms.txt file was found. 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,googleSearchImpact:`none`,guidanceUrl:`https://developers.google.com/search/updates#clarifying-guidance-on-llms-txt-files`}}),ky({id:`valid-json-ld`,section:`machine-readable`,maxScore:8,evaluated:_.length>0,score:v.length?3:8,title:_.length?`Observed JSON-LD parses cleanly`:`JSON-LD syntax was not evaluated`,plainEnglish:_.length?v.length?`${v.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:v.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:v.slice(0,10).map(e=>e.finalUrl)})]),Oy(`content-clarity`,`Content clarity`,[ky({id:`answerable-content`,section:`content-clarity`,maxScore:0,evaluated:r,score:0,title:`Short opening paragraphs are an unscored observation`,plainEnglish:r?`${Ty(y.length,n)}% 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:jy(t,e=>!e.geo?.answerable),evidence:{observedPages:y.length,evaluatedPages:n,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`}}),ky({id:`semantic-html`,section:`content-clarity`,maxScore:0,evaluated:r,score:0,title:`Semantic HTML coverage observed`,plainEnglish:r?`${Ty(b.length,n)}% 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.`}),ky({id:`titles-headings-meta`,section:`content-clarity`,maxScore:0,evaluated:r,score:0,title:`Title, H1, and description coverage observed`,plainEnglish:r?`${Ty(x.length,n)}% of indexable pages have title/H1 coverage and ${Ty(ee.length,n)}% 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.`}),ky({id:`language`,section:`content-clarity`,maxScore:0,evaluated:r,score:0,title:`HTML language declaration coverage observed`,plainEnglish:r?`${Ty(te.length,n)}% 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.`})]),Oy(`entity-signals`,`Entity signals`,[ky({id:`entity-schema`,section:`entity-signals`,maxScore:0,evaluated:r,score:0,title:`Entity-signal coverage observed`,plainEnglish:r?`${Ty(S.length,n)}% 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:jy(t,e=>!S.includes(e))}),ky({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.`})]),Oy(`technical-ux`,`Technical UX`,[ky({id:`snippet-controls`,section:`technical-ux`,maxScore:0,evaluated:r,score:0,title:r?C.length?`Page-level snippet restrictions observed`:`No page-level snippet restriction detected`:`Snippet controls were not evaluated`,plainEnglish:r?C.length?`${w.length} indexable ${w.length===1?`page has`:`pages have`} snippets blocked and ${T.length} ${T.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:t.length,blockedPages:w.length,limitedPages:T.length,restrictions:C.slice(0,10)}}),ky({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.`}),ky({id:`status-indexability`,section:`technical-ux`,maxScore:12,evaluated:e.pages.length>0,score:Ey(Ty(t.length,e.pages.length||1),12),title:e.pages.length?`Crawled-page indexability observed`:`Crawled-page indexability was not evaluated`,plainEnglish:e.pages.length?`${Ty(t.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.`})]),Oy(`crawl-completeness`,`Crawl completeness`,[ky({id:`crawl-depth`,section:`crawl-completeness`,maxScore:8,score:i?3:8,title:i?`The crawl hit the page cap`:`The crawl finished without hitting the page cap`,plainEnglish:i?`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.`}),ky({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.`})])],re=ne.flatMap(e=>e.checks),E=e.status===`completed`&&!m?`complete`:`partial`,ie=re.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:E,assessment:`evidence-only`,headline:E===`partial`?`AI-search evidence is incomplete. Fix the collection gaps before drawing site-wide conclusions.`:ie.length?`${ie.length} evidence-backed technical ${ie.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:ne,checks:re,topActions:ie,botAccess:o,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.`,...a?l?[`robots.txt was unavailable, so crawler access and sitemap declaration checks are inconclusive.`]:d?[]:[`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.`]}}function Ny(e){return[...new Set(e.filter(e=>!!e))]}function Py(e,t){return t?Math.round(e/t*100):0}function Fy(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(e=>/^(Organization|LocalBusiness|Person|Product|WebSite)$/i.test(e))),a=t.flatMap(e=>e.schemaSameAsEvidence??[]).filter(e=>e.subjectTypes.some(e=>/^(Organization|LocalBusiness|Person)$/i.test(e))),o=a.filter(e=>e.subjectTypes.some(e=>/^(Organization|LocalBusiness)$/i.test(e))),s=Ny(a.map(e=>e.url)),c={};for(let e of a)for(let t of e.subjectTypes)/^(Organization|LocalBusiness|Person)$/i.test(t)&&(c[t]=Ny([...c[t]??[],e.url]));let l=Ny(t.flatMap(e=>e.socialProfileLinks??[])),u=Ny(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:Py(i.length,n),check:{id:`entity-schema`,title:`Entity schema is present`,plainEnglish:`${Py(i.length,n)}% of evaluated indexable pages include Organization, LocalBusiness, Person, Product, or WebSite schema.`,action:`Add accurate entity schema to the homepage and key product, author, organization, and local pages.`,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:Ny(o.map(e=>e.url)),sameAsEvidence:a,unclassifiedSocialLinks:l}}},{coverage:Math.round((Py(d.length,n)+Py(f.length,n))/2),check:{id:`authority-freshness`,title:`Author and date signals observed`,plainEnglish:`${Py(d.length,n)}% of evaluated indexable pages expose an author signal and ${Py(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:Py(d.length,n),dateCoverage:Py(f.length,n),authors:u}}},{coverage:Py(p.length,n),check:{id:`entity-naming`,title:`Title and H1 coverage observed`,plainEnglish:`${Py(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}}}const Iy=new Set([`connection_error`,`client_error`,`server_error`,`robots_blocked`,`noindex`,`x_robots_noindex`,`canonicalized_page`]);function Ly(e){let t=new Map;for(let n of e.issues){if(!Iy.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=am({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 Ry(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.skippedUrls>0&&t.push(`skipped-urls`),e.summary.failedRequests>0&&t.push(`failed-requests`),e.summary.extractionFailures>0&&t.push(`extraction-failures`),t}function zy(e,t={}){let n=Ly(e),r=t.limit??50,i=n.slice(0,r),a=Ry(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,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 By(e){return Math.ceil(e.length/4)}function Vy(e){return new URL(e).hostname.replace(/^www\./,``)}function Hy(e){return e.title??e.h1??(new URL(e.finalUrl).pathname||e.finalUrl)}function Uy(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 Wy(e){return/\/(login|logout|cart|checkout|account|admin|wp-admin|search|tag|privacy|terms|cookie|legal)\b/i.test(new URL(e).pathname)}function Gy(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 Ky(e,t={}){return e.pages.filter(e=>e.indexable&&e.status<400).filter(e=>!Wy(e.finalUrl)).filter(e=>!Gy(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 qy(e){let t=e.ai?.llmsTxt,n=Ky(e),r=[];return n.length<3&&r.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:n.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?`An optional llms.txt file is present. It has no Google Search visibility impact.`:`No llms.txt file was found. This is not an SEO issue and requires no action.`,issues:r,recommendedPages:n.slice(0,25).map(e=>({url:e.finalUrl,title:e.title,section:Uy(e),reason:(e.internalLinkAuthorityScore??0)>0?`High internal link authority in this crawl.`:`Indexable page with useful content signals.`}))}}function Jy(e,t={}){let n=t.maxUrls??250,r=t.tokenBudget??12e3,i=t.title??Vy(e.config.url),a=t.description??`Curated entry points for agents reading ${Vy(e.config.url)}.`,o=Ky(e,t).slice(0,n),s=new Map;for(let e of o){let t=Uy(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=Hy(e).replace(/\]/g,`\\]`),a=e.metaDescription?` - ${e.metaDescription.replace(/\s+/g,` `).slice(0,160)}`:``,o=`- [${t}](${e.finalUrl})${a}`;if(By([...c,...n,o,``].join(`
489
+ `))>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(`
490
+ `)}\n`;return{content:d,includedUrls:l,estimatedTokens:By(d),sections:u}}const Yy={pass:1,warn:.5,fail:0},Xy={"ai-bot-access":20,https:10,"indexable-pages":15,"structured-data":15,"valid-json-ld":10,"entity-identity":20,"answerable-content":10},Zy=[`ai-bot-access`,`https`,`indexable-pages`,`structured-data`,`valid-json-ld`,`entity-identity`,`answerable-content`],Qy=/^(Organization|LocalBusiness|Person|Product|WebSite)$/i,$y=/^(Organization|LocalBusiness)$/i;function eb(e){return e.pages.filter(e=>e.indexable&&e.status<400)}function tb(e,t){return t>0?Math.round(e/t*100):0}function nb(e){return e.slice(0,10).map(e=>e.finalUrl)}function rb(e,t,n){return t===0?`unknown`:e/t>=n?`pass`:e>0?`warn`:`fail`}function ib(e){let t={id:`ai-bot-access`,label:`AI crawler access in the start-URL robots policy`,weight:Xy[`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 ab(e){let t={id:`https`,label:`Secure transport on evaluated pages`,weight:Xy.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=rb(r.length,e.length,1);return{...t,status:i,observed:{evaluatedPages:e.length,httpsPages:r.length,coveragePercent:tb(r.length,e.length),insecureSample:nb(e.filter(e=>e.isHttps!==!0))},finding:i===`pass`?`Every evaluated indexable page was served over HTTPS.`:`${tb(r.length,e.length)}% of evaluated indexable pages were served over HTTPS.`,verification:n}}function ob(e){let t={id:`indexable-pages`,label:`Indexable 2xx share of crawled pages`,weight:Xy[`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=eb(e).length,a=rb(i,r,.85);return{...t,status:a,observed:{crawledPages:r,indexablePages:i,coveragePercent:tb(i,r)},finding:`${tb(i,r)}% of crawled pages are indexable 2xx pages. Non-indexable states can be intentional; this check flags share, not intent.`,verification:n}}function sb(e){let t={id:`structured-data`,label:`Structured-data coverage on evaluated pages`,weight:Xy[`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=rb(r.length,e.length,.5);return{...t,status:i,observed:{evaluatedPages:e.length,pagesWithStructuredData:r.length,coveragePercent:tb(r.length,e.length),missingSample:nb(e.filter(e=>!r.includes(e)))},finding:`${tb(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 cb(e){let t={id:`valid-json-ld`,label:`JSON-LD parses cleanly where present`,weight:Xy[`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:nb(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 lb(e){let t={id:`entity-identity`,label:`Entity identity connected with sameAs`,weight:Xy[`entity-identity`]},n=`Confirm the homepage and key pages carry accurate Organization, Person, or LocalBusiness schema with sameAs links to profiles you control.`,r=Fy(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(e=>Qy.test(e)),a=Object.entries(r.entities.sameAsByType).filter(([e])=>$y.test(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, or WebSite schema was found on the evaluated pages.`,verification:n}}function ub(e){let t={id:`answerable-content`,label:`Substantive opening content (heuristic)`,weight:Xy[`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=rb(r.length,e.length,.5);return{...t,status:i,observed:{evaluatedPages:e.length,answerablePages:r.length,coveragePercent:tb(r.length,e.length),heuristic:`one-of-first-three-paragraphs-has-at-least-25-words`,missingSample:nb(e.filter(e=>!e.geo?.answerable))},finding:`${tb(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 db(e){let t=[],n=qy(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=eb(e).map(e=>({url:e.finalUrl,control:am({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 fb(e){return e===null?`unscored`:e>=80?`strong`:e>=50?`moderate`:`weak`}function pb(e){let t=eb(e),n=zy(e),r=n.dataStatus===`complete`,i={"ai-bot-access":ib(e),https:ab(t),"indexable-pages":ob(e),"structured-data":sb(t),"valid-json-ld":cb(e),"entity-identity":lb(e),"answerable-content":ub(t)},a=Zy.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*Yy[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=db(e),h=fb(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:Yy,weights:{...Xy},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 mb=[{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 external link no longer appears in externalLinkChecks as broken.`},{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.`}],hb={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`},gb=new Map(mb.map(e=>[e.id,{...e,recommendation:hb[e.id]??`fix`}]));function _b(){return[...gb.values()]}function vb(e){return gb.get(e)}function yb(e){return vb(e)?.recommendation??`fix`}function bb(e){if(e)try{let t=new URL(e);return t.hash=``,t.toString()}catch{return e}}function xb(e){let t=bb(e.url),n=bb(e.finalUrl);return!!(t&&n&&t!==n)}function Sb(e){return e.status>=200&&e.status<300}function Cb(e){return/^\s*(?:text\/html|application\/xhtml\+xml)\b/i.test(e.contentType??``)}function wb(e){let t=e.contentAuditAllowed??(!e.blocked&&e.robotsTxt?.allowed!==!1);return Sb(e)&&Cb(e)&&!xb(e)&&t}function Tb(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 Eb(e){try{let t=new URL(e??``);return t.protocol===`http:`||t.protocol===`https:`}catch{return!1}}function Db(e){if(e)try{let t=new URL(e);return t.hash=``,t.toString()}catch{return}}const Ob=2e3,kb=2*1024*1024,Ab=4*1024,jb=.6;function X(e,t,n,r){let i=vb(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 Mb(e,t,n,r){let i=vb(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 Nb(e){return e===0||e===404||e===410||(e??0)>=500}function Pb(e){let t=e.trim().toLowerCase();return t===`x-default`||/^[a-z]{2}(?:-[a-z]{4})?(?:-[a-z]{2})?$/.test(t)}function Fb(e){return(e.searchMetrics?.clicks??0)>0||(e.searchMetrics?.impressions??0)>=100||(e.analytics?.sessions??0)>=25}function Ib(e){return e?.trim().replace(/\s+/g,` `).toLowerCase()||void 0}function Lb(e,t){let n=new Map;for(let r of e){if(!wb(r))continue;let e=t(r)?.trim(),i=Ib(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 Rb(e){let t=new Map;for(let n of e){if(!wb(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 zb=new Set([`a`,`an`,`and`,`are`,`for`,`from`,`how`,`in`,`is`,`of`,`on`,`or`,`the`,`to`,`vs`,`what`,`with`]);function Bb(e){return[...new Set(e.toLowerCase().split(/[^a-z0-9]+/).filter(e=>e.length>2&&!zb.has(e)))]}function Vb(e){let t=e.topQuery?.query;if(!t||(e.topQuery?.impressions??0)<50)return;let n=Bb(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 Hb(e){let t=[];for(let n of e){if(n.outcome===`failure`){if(n.failureKind===`aborted`)continue;t.push(Mb(`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||!Tb(n.requestedUrl,n.finalUrl))&&t.push(Mb(`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(Mb(`redirect_chain`,n,`${e.length} hops`,{redirectChain:e,hops:e.length}))}return t}function Ub(e,t={}){let n=[],r=Lb(e,e=>e.title),i=Lb(e,e=>e.metaDescription),a=Rb(e),o=new Map;for(let t of e)if(wb(t))for(let e of[t.url,t.finalUrl]){let n=Db(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})),Nb(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=xb(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)>Ob&&n.push(X(`slow_response`,s,`${s.responseTimeMs}ms`,{responseTimeMs:s.responseTimeMs,thresholdMs:Ob})),e)continue;Cb(s)&&(s.sizeBytes??0)>kb&&n.push(X(`large_html`,s,`${Math.round((s.sizeBytes??0)/1024)} KB`,{sizeBytes:s.sizeBytes,thresholdBytes:kb})),Cb(s)&&!s.compression&&(s.sizeBytes??0)>Ab&&n.push(X(`no_compression`,s,void 0,{sizeBytes:s.sizeBytes,thresholdBytes:Ab,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??[])Nb(e.status)&&n.push(X(`broken_external_link`,s,e.url,{url:e.url,status:e.status,error:e.error}));s.indexable&&(s.internalInlinkCount??0)===0&&!Tb(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&&Fb(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})),nm(s.xRobotsTag,`noindex`)&&n.push(X(`x_robots_noindex`,s,s.xRobotsTag,{xRobotsTag:s.xRobotsTag}));let l=nm(s.xRobotsTag,`nofollow`);if(l&&n.push(X(`nofollow`,s,s.xRobotsTag,{xRobotsTag:s.xRobotsTag})),!wb(s))continue;let u=s.title?.trim(),d=u?y_(u):void 0,f=r.get(Ib(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(Ib(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&&!Eb(s.canonicalRaw)&&n.push(X(`canonical_non_absolute`,s,s.canonicalRaw,{canonicalRaw:s.canonicalRaw,canonical:s.canonical}));let e=o.get(Db(s.canonical)??``);e?.canonical&&!Tb(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]}))}tm(s.metaRobots,`noindex`)&&n.push(X(`noindex`,s,s.indexability,{metaRobots:s.metaRobots})),tm(s.metaRobots,`nofollow`)&&!l&&n.push(X(`nofollow`,s,s.metaRobots,{metaRobots:s.metaRobots})),s.canonical&&!Tb(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&&!Tb(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 _=Vb(s);_&&_.coverage<jb&&n.push(X(`query_coverage_missing`,s,_.query,{query:_.query,impressions:s.topQuery?.impressions,matchedTerms:_.matchedTerms,missingTerms:_.missingTerms,coverage:_.coverage,threshold:jb})),(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=>!Pb(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=>Tb(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 Wb=new Set([`authorization`,`cookie`,`proxy-authorization`,`set-cookie`,`www-authenticate`,`x-api-key`,`api-key`,`access-token`,`refresh-token`]),Gb=/^(access_?token|auth|api_?key|client_?secret|code|jwt|key|password|refresh_?token|secret|session|sig|signature|token)$/i;function Kb(e=[]){return[...new Set(e.filter(Boolean))].sort()}function qb(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 Jb(e){return{url:new URL(e.url).toString(),mode:e.mode??`site`,urls:Kb(e.urls??[]),maxPages:e.maxPages??500,maxDepth:e.maxDepth??16,concurrency:e.concurrency??8,timeoutMs:e.timeoutMs??2e4,include:Kb(e.include??[]),exclude:Kb(e.exclude??[]),respectRobots:e.respectRobots??!0,useSitemap:e.useSitemap??!0,checkExternal:e.checkExternal??!0,js:Eg(e.js),refresh:e.refresh??!1,fetchRate:qb(e)}}function Yb(e){let t=Jb(e);return _(`sha256`).update(JSON.stringify(t)).digest(`hex`).slice(0,16)}function Xb(e){let t={config:Jb(e.config),site:e.site??null,ga4PropertyId:e.ga4PropertyId??null};return`crawl_def_${_(`sha256`).update(JSON.stringify(t)).digest(`hex`).slice(0,20)}`}function Zb(){return`crawl_${v().replaceAll(`-`,``)}`}function Qb(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 $b(e){let t=e.toLowerCase();return Wb.has(t)||Gb.test(t.replace(/-/g,`_`))}function ex(e){try{let t=new URL(e);for(let e of[...t.searchParams.keys()])Gb.test(e)&&t.searchParams.set(e,`[redacted]`);return t.toString()}catch{return e}}function tx(e){return e.replace(/https?:\/\/[^\s"'<>`]+/g,e=>ex(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 nx(e,t){return t&&$b(t)?`[redacted]`:typeof e==`string`?tx(e):Array.isArray(e)?e.map(e=>nx(e)):!e||typeof e!=`object`?e:Object.fromEntries(Object.entries(e).map(([e,t])=>[e,nx(t,e)]))}function rx(e){return nx(e)}function ix(e){return nx(e).map(({seoScore:e,geoScore:t,...n})=>n)}function ax(e){return nx(e)}function ox(e=[]){return e.map(e=>tx(e))}function sx(e,t){return e<t?-1:+(e>t)}function cx(e){return[...e].sort((e,t)=>sx(e.url,t.url))}function lx(e){return[...e].sort((e,t)=>sx(e.requestedUrl,t.requestedUrl)||sx(e.outcome,t.outcome)||sx(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 ux(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;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,...dx(e.pages,e.stats),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 dx(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}return{discoveredUrls:t.discoveredUrls??n.size,queuedUrls:t.queuedUrls??e.length,crawledUrls:t.crawledUrls??e.length,skippedUrls:t.skippedUrls??0,failedUrls:t.failedUrls??e.filter(e=>e.status===0||e.status>=400).length,observedInternalLinks:t.observedInternalLinks??r,pageLimitReached:t.pageLimitReached??!1}}function fx(e){try{let t=new URL(e);return t.hash=``,t.toString()}catch{return}}function px(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=fx(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=fx(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 mx(e){let t=rx(Jb(e.config)),n=Xb({config:t,site:e.site,ga4PropertyId:e.ga4PropertyId}),r=cx(ix(px(e.pages??[],e.linkGraph))),i=lx(nx(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=ax(e.issues??[...Hb(i),...Ub(r,{startUrl:t.url})]),s=r;return{id:e.id??Zb(),definitionId:n,projectId:e.projectId,site:e.site,ga4PropertyId:e.ga4PropertyId,generatedAt:e.generatedAt??new Date().toISOString(),status:e.status??`completed`,configHash:Yb(t),config:t,summary:ux({pages:s,requests:i,requestEvidenceStatus:a,issues:o,stats:e.stats}),requestEvidenceStatus:a,requests:i,pages:s,issues:o,issueGroups:Qb(o),...e.dataSources?{dataSources:nx(e.dataSources)}:{},...e.ai?{ai:nx(e.ai)}:{},...e.sitemapDiscovery?{sitemapDiscovery:nx(e.sitemapDiscovery)}:{},...e.externalLinkVerification?{externalLinkVerification:nx(e.externalLinkVerification)}:{},warnings:ox(e.warnings).sort(sx),caveats:ox(e.caveats).sort(sx)}}function hx(e){let t=e.requestEvidenceStatus??(Object.hasOwn(e,`requests`)?`available`:`unavailable`),n=ax(e.issues??[]),r=lx(nx(e.requests??[])??[]),i=cx(ix(px(e.pages??[])));return{...nx(e),requestEvidenceStatus:t,requests:r,definitionId:e.definitionId??Xb({config:e.config,site:e.site,ga4PropertyId:e.ga4PropertyId}),summary:ux({pages:i,requests:r,requestEvidenceStatus:t,issues:n,stats:e.summary}),pages:i,issues:n,issueGroups:Qb(n),warnings:ox(e.warnings).sort(sx),caveats:ox(e.caveats).sort(sx)}}function gx(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:vx(e.report_json)}}function _x(e){let t={};return e.projectId&&(t.projectId=e.projectId),e.site&&(t.site=e.site),{kind:`seo.crawl_report`,version:5,savedAt:new Date().toISOString(),...t.projectId||t.site?{tenant:t}:{},report:e}}function vx(e){try{let t=JSON.parse(e);return t.kind===`seo.crawl_report`&&typeof t.version==`number`?t.version:0}catch{return 0}}function yx(e){let t=JSON.parse(e);return t&&typeof t==`object`&&`kind`in t&&t.kind===`seo.crawl_report`&&`report`in t?hx(t.report):hx(t)}function bx(e){if(j().prepare(`INSERT INTO crawl_reports
489
491
  (id, config_hash, site_url, url, status, total_pages, issue_count, created_at, report_json)
490
492
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
491
- ON CONFLICT(id) DO NOTHING`).run(e.id,e.configHash,e.site??null,e.config.url,e.status,e.summary.totalPages,e.issues.length,Date.parse(e.generatedAt),JSON.stringify(Ib(e))).changes===0){let t=j().prepare(`SELECT * FROM crawl_reports WHERE id = ?`).get(e.id);if(t)return Fb(t)}return{id:e.id,configHash:e.configHash,site:e.site,url:e.config.url,status:e.status,totalPages:e.summary.totalPages,issueCount:e.issues.length,createdAt:e.generatedAt,storageVersion:5}}function Bb(e={}){let t=e.limit??20;return(e.site?j().prepare(`SELECT * FROM crawl_reports
493
+ ON CONFLICT(id) DO NOTHING`).run(e.id,e.configHash,e.site??null,e.config.url,e.status,e.summary.totalPages,e.issues.length,Date.parse(e.generatedAt),JSON.stringify(_x(e))).changes===0){let t=j().prepare(`SELECT * FROM crawl_reports WHERE id = ?`).get(e.id);if(t)return gx(t)}return{id:e.id,configHash:e.configHash,site:e.site,url:e.config.url,status:e.status,totalPages:e.summary.totalPages,issueCount:e.issues.length,createdAt:e.generatedAt,storageVersion:5}}function xx(e={}){let t=e.limit??20;return(e.site?j().prepare(`SELECT * FROM crawl_reports
492
494
  WHERE site_url = ?
493
495
  ORDER BY created_at DESC, id DESC
494
496
  LIMIT ?`).all(e.site,t):j().prepare(`SELECT * FROM crawl_reports
495
497
  ORDER BY created_at DESC, id DESC
496
- LIMIT ?`).all(t)).map(Fb)}function Vb(e){let t=j().prepare(`SELECT report_json FROM crawl_reports WHERE id = ?`).get(e);if(t)return Rb(t.report_json)}function Hb(e){return j().prepare(`DELETE FROM crawl_reports WHERE id = ?`).run(e).changes>0}function Ub(e){let t=e?j().prepare(`SELECT report_json FROM crawl_reports
498
+ LIMIT ?`).all(t)).map(gx)}function Sx(e){let t=j().prepare(`SELECT report_json FROM crawl_reports WHERE id = ?`).get(e);if(t)return yx(t.report_json)}function Cx(e){return j().prepare(`DELETE FROM crawl_reports WHERE id = ?`).run(e).changes>0}function wx(e){let t=e?j().prepare(`SELECT report_json FROM crawl_reports
497
499
  WHERE site_url = ?
498
500
  ORDER BY created_at DESC, id DESC
499
501
  LIMIT 1`).get(e):j().prepare(`SELECT report_json FROM crawl_reports
500
502
  ORDER BY created_at DESC, id DESC
501
- LIMIT 1`).get();if(t)return Rb(t.report_json)}const Wb={save:zb,list:Bb,load:Vb,delete:Hb,latest:Ub};function Gb(e,t=Wb){return t.save(e)}function Kb(e={},t=Wb){return t.list(e)}function qb(e,t=Wb){return t.load(e)}function Jb(e,t=Wb){return t.delete(e)}function Yb(e,t=Wb){return t.latest(e)}function Xb(e){let t=e.cancelled?[`Crawl cancelled before all queued URLs finished.`]:e.pageLimitReached?[`Stopped after reaching maxPages (${e.maxPages}).`]:[];return e.queueSafetySkippedUrls>0&&t.push(`Left ${e.queueSafetySkippedUrls} eligible same-origin URLs unqueued to keep this crawl bounded. Increase --max-pages to inspect more.`),e.originBackpressureSkippedUrls>0&&t.push(`Stopped ${e.originBackpressureSkippedUrls} queued URL${e.originBackpressureSkippedUrls===1?``:`s`} because the origin stayed slow. This is incomplete crawl evidence, not a failed URL check.`),t}const Zb=Symbol(`crawl-cancelled`);function Qb(e){let t=new AbortController,n=()=>t.abort(),r=setTimeout(n,Math.min(e.timeoutMs,5e3));return e.signal?.aborted?t.abort():e.signal?.addEventListener(`abort`,n,{once:!0}),{signal:t.signal,cleanup:()=>{clearTimeout(r),e.signal?.removeEventListener(`abort`,n)}}}function $b(e){return e.aborted?Promise.resolve(Zb):new Promise(t=>{e.addEventListener(`abort`,()=>t(Zb),{once:!0})})}function ex(e){let t=2166136261;for(let n of e)t=Math.imul(t^(n.codePointAt(0)??0),16777619);return t>>>0}function tx(e,t){let n=ex(e),r=ex(t);return n===r?e<t?-1:+(e>t):n-r}function nx(e){let t=new Map,n=0,r=0;for(let i of e){let e=[...new Set(i.sampleExternalLinks??[])];n+=i.outgoingExternalCount??e.length,r+=e.length;for(let n of e){let e=t.get(n);e||(e=new Set,t.set(n,e)),e.add(i.url)}}return{candidates:[...t].map(([e,t])=>({url:e,sourcePages:[...t].sort()})).sort((e,t)=>tx(e.url,t.url)),discoveredLinkOccurrences:n,retainedLinkOccurrences:r}}function rx(e,t){let n=new Map;for(let t of e)for(let e of t.sourcePages){let r=n.get(e)??[];r.push(t.url),n.set(e,r)}for(let e of n.values())e.sort(tx);let r=[...n.keys()].sort(tx),i=new Map(r.map(e=>[e,0])),a=new Set;for(;a.size<t;){let e=!1;for(let o of r){let r=n.get(o)??[],s=i.get(o)??0;for(;s<r.length;){let e=r[s];if(!e||!a.has(e))break;s+=1}i.set(o,s+1);let c=r[s];if(c&&(a.add(c),e=!0,a.size===t))break}if(!e)break}return[...a]}async function ix(e,t,n,r){let i=Qb({timeoutMs:t,signal:r});try{let t=await n(e,{method:`HEAD`,redirect:`follow`,signal:i.signal});return await t.body?.cancel().catch(()=>void 0),{url:e,status:t.status}}catch(t){return{url:e,status:0,error:t instanceof Error?t.message:String(t)}}finally{i.cleanup()}}async function ax(e){let{candidates:t,discoveredLinkOccurrences:n,retainedLinkOccurrences:r}=nx(e.pages),i=e.signal?.aborted?[]:rx(t,200),a=new Map;for(let t=0;t<i.length&&!e.signal?.aborted;t+=8){let n=i.slice(t,t+8),r=await Promise.all(n.map(t=>ix(t,e.timeoutMs,e.fetch,e.signal)));for(let e of r)a.set(e.url,e)}for(let t of e.pages){let e=(t.sampleExternalLinks??[]).map(e=>a.get(e)).filter(e=>!!e);e.length&&(t.externalLinkChecks=e)}let o=[...a.values()].filter(e=>typeof e.status==`number`&&e.status>0).length,s=a.size-o,c=t.length-a.size,l=[];return n>r&&l.push(`Retained ${r} sampled external link occurrence${r===1?``:`s`} from ${n} observed occurrence${n===1?``:`s`}.`),t.length>i.length&&l.push(`Selected ${i.length} of ${t.length} retained external URLs using the 200-URL verification limit.`),a.size<i.length&&l.push(`Stopped before checking ${i.length-a.size} selected external URL${i.length-a.size===1?``:`s`}.`),s&&l.push(`${s} external URL request${s===1?``:`s`} failed before a response was received.`),{dataStatus:n>r||c>0||s>0?`partial`:`complete`,discoveredLinkOccurrences:n,retainedUrls:t.length,selectedUrls:i.length,fetchedUrls:o,failedUrls:s,deferredUrls:c,limit:200,warnings:l}}function ox(e,t){let n=new Set,r=e;for(;!n.has(r);){n.add(r);let e=t.get(r);if(!e)return r;r=e}return r}function sx(e,t){let n={};for(let[r,i]of Object.entries(e)){let e=ox(r,t),a=n[e];a||(a=new Set,n[e]=a);for(let e of i)a.add(ox(e,t))}return Object.fromEntries(Object.entries(n).map(([e,t])=>[e,[...t].sort((e,t)=>e<t?-1:+(e>t))]))}function cx(e,t){let n={requestedUrl:e,outcome:`response`,finalUrl:t.finalUrl,status:t.status,contentType:t.contentType,durationMs:t.responseTimeMs,redirectChain:t.fetchDiagnostics?.redirectChain};return t.extractionStatus===`failed`?{...n,extraction:`failed`,extractionError:t.extractionError??`Content extraction failed without a recorded error.`}:{...n,extraction:t.extractionStatus??`complete`}}function lx(e){return{...G_(28,e),days:28}}async function ux(e){let t;try{t=await e.fetchLandingPageValues({propertyId:e.propertyId,startDate:e.window.startDate,endDate:e.window.endDate,limit:e.limit})}catch(t){let n=`GA4 metrics unavailable: ${t instanceof Error?t.message:String(t)}`;return e.warnings.push(n),{status:`unavailable`,window:e.window,totalPages:e.pages.length,queriedPages:e.pages.length,joinedPages:0,retainedRowLimit:e.limit,warning:n}}if(t.warning){let n=`GA4 metrics unavailable: ${t.warning}`;return e.warnings.push(n),{status:`unavailable`,window:e.window,totalPages:e.pages.length,queriedPages:e.pages.length,joinedPages:0,returnedRows:t.source?.returnedRows,availableRows:t.source?.availableRows,retainedRowLimit:t.source?.retainedRowLimit??e.limit,...t.source?{retainedRowLimitReached:t.source.retainedRowLimitReached}:{},warning:n}}let n=0;for(let r of e.pages){let i=e.landingValueForUrl(t.values,r.finalUrl);i&&(r.analytics=i,n+=1)}let r=t.source?.dataStatus!==void 0,i=t.source?.retainedRowLimitReached,a=t.source?.qualityWarnings??[],o=r?i?`GA4 retained-row limit reached; missing page metrics are not reliable zero-traffic evidence.`:a.length?`${a.join(` `)} Missing page metrics and relative analytics value are partial evidence.`:e.pages.length&&n===0?`GA4 metrics joined for 0 crawled pages.`:void 0:`GA4 metrics joined for ${n} of ${e.pages.length} crawled pages, but the provider did not expose row completeness; missing page metrics are not reliable zero-traffic evidence.`;return o&&e.warnings.push(o),{status:!r||t.source?.dataStatus===`partial`?`partial`:n?`joined`:`none`,window:e.window,totalPages:e.pages.length,queriedPages:e.pages.length,joinedPages:n,returnedRows:t.source?.returnedRows,availableRows:t.source?.availableRows,retainedRowLimit:t.source?.retainedRowLimit??e.limit,...i===void 0?{}:{retainedRowLimitReached:i},...o?{warning:o}:{}}}async function dx(e){let t=[...e.pages].sort((e,t)=>e.finalUrl<t.finalUrl?-1:e.finalUrl>t.finalUrl?1:e.url<t.url?-1:+(e.url>t.url)).slice(0,e.limit),n=t.length<e.pages.length,r={startDate:e.window.startDate,endDate:e.window.endDate},i=0,a=0,o,s,c=!!(e.queryPagesMetrics&&!e.queryPagesMetricsBatch||e.queryPagesTopQueries&&!e.queryPagesTopQueriesBatch),l=!!(e.queryPagesMetricsBatch||e.queryPagesTopQueriesBatch),u=!!(e.queryPagesMetricsBatch||e.queryPagesMetrics),d=!!(e.queryPagesTopQueriesBatch||e.queryPagesTopQueries),f=(u||d)&&u!==d,p=[];try{if(e.queryPagesMetricsBatch||e.queryPagesTopQueriesBatch||e.queryPagesMetrics||e.queryPagesTopQueries){let n=t.map(e=>e.finalUrl);try{o=e.queryPagesMetricsBatch?await e.queryPagesMetricsBatch(e.site,n,r):e.queryPagesMetrics?{values:await e.queryPagesMetrics(e.site,n,e.window.days)}:void 0}catch(e){p.push(`page metrics failed: ${e instanceof Error?e.message:String(e)}`)}try{s=e.queryPagesTopQueriesBatch?await e.queryPagesTopQueriesBatch(e.site,n,r):e.queryPagesTopQueries?{values:await e.queryPagesTopQueries(e.site,n,e.window.days)}:void 0}catch(e){p.push(`top queries failed: ${e instanceof Error?e.message:String(e)}`)}for(let e of t){let t=o?.values.get(e.finalUrl);t&&(e.searchMetrics=t,i+=1);let n=s?.values.get(e.finalUrl);n&&(e.topQuery=n,a+=1)}}else for(let n of t){let t=await e.queryPageMetrics(e.site,n.finalUrl,e.window.days);t&&(n.searchMetrics=t,i+=1);let r=await e.queryPageTopQuery(e.site,n.finalUrl,e.window.days);r&&(n.topQuery=r,a+=1)}}catch(r){let c=`GSC metrics unavailable: ${r instanceof Error?r.message:String(r)}`;return e.warnings.push(c),{status:i||a?`partial`:`unavailable`,...l?{window:e.window}:{},totalPages:e.pages.length,queriedPages:t.length,joinedMetricPages:i,joinedQueryPages:a,pageLimit:e.limit,pageLimitReached:n,metricRowsReturned:o?.returnedRows,queryRowsReturned:s?.returnedRows,retainedRowLimit:o?.retainedRowLimit??s?.retainedRowLimit,warning:c}}if(p.length>0){let r=`GSC metrics partially unavailable: ${p.join(`; `)}`;return e.warnings.push(r),{status:i||a?`partial`:`unavailable`,...l?{window:e.window}:{},totalPages:e.pages.length,queriedPages:t.length,joinedMetricPages:i,joinedQueryPages:a,pageLimit:e.limit,pageLimitReached:n,metricRowsReturned:o?.returnedRows,queryRowsReturned:s?.returnedRows,retainedRowLimit:o?.retainedRowLimit??s?.retainedRowLimit,warning:r}}let m=!!(o?.retainedRowLimitReached||s?.retainedRowLimitReached),h=t.filter(e=>e.searchMetrics||e.topQuery).length,g=n||m||c||f||!l,_=f?`GSC provider did not supply both page metrics and top-query evidence; missing evidence is not a reliable zero.`:c&&!l?`GSC legacy provider did not expose retained-row completeness or an exact date window; missing evidence is not a reliable zero.`:c?`GSC provider did not expose retained-row completeness; missing page metrics and queries are not reliable zero-visibility evidence.`:l?m?`GSC retained-row limit reached; missing page metrics and queries are not reliable zero-visibility evidence.`:n?`GSC metrics queried for ${t.length} of ${e.pages.length} crawled pages because the page limit was reached.`:h<t.length?`GSC metrics joined for ${h} of ${t.length} crawled pages.`:void 0:`GSC metrics joined for ${h} of ${t.length} crawled pages through a legacy relative-day provider; exact queried dates are not verifiable.`;return _&&e.warnings.push(_),{status:g?`partial`:i||a?`joined`:`none`,...l?{window:e.window}:{},totalPages:e.pages.length,queriedPages:t.length,joinedMetricPages:i,joinedQueryPages:a,pageLimit:e.limit,pageLimitReached:n,metricRowsReturned:o?.returnedRows,queryRowsReturned:s?.returnedRows,retainedRowLimit:o?.retainedRowLimit??s?.retainedRowLimit,...c?{}:{retainedRowLimitReached:m},..._?{warning:_}:{}}}function fx(e){let t=(e,t,n)=>{let r=e??t;if(!Number.isInteger(r)||r<1)throw Error(`${n} must be a positive whole number.`);return r};return{searchMetricsLimit:t(e.searchMetricsLimit,5e3,`searchMetricsLimit`),analyticsLimit:t(e.analyticsLimit,5e3,`analyticsLimit`)}}async function px(e){let t=lx(e.now()),n={searchConsole:{status:`skipped`,totalPages:e.pages.length,queriedPages:0,joinedMetricPages:0,joinedQueryPages:0,pageLimit:e.searchMetricsLimit,pageLimitReached:!1,retainedRowLimitReached:!1,warning:e.cancelled?`Search Console join skipped because the crawl was cancelled.`:`Search Console join skipped because no property was selected.`},analytics:{status:`skipped`,totalPages:e.pages.length,queriedPages:0,joinedPages:0,retainedRowLimit:e.analyticsLimit,retainedRowLimitReached:!1,warning:e.cancelled?`GA4 join skipped because the crawl was cancelled.`:`GA4 join skipped because no property was selected.`}};return!e.cancelled&&e.site&&(n.searchConsole=await dx({site:e.site,pages:e.pages,warnings:e.warnings,limit:e.searchMetricsLimit,window:t,queryPageMetrics:e.queryPageMetrics,queryPageTopQuery:e.queryPageTopQuery,queryPagesMetrics:e.queryPagesMetrics,queryPagesTopQueries:e.queryPagesTopQueries,queryPagesMetricsBatch:e.queryPagesMetricsBatch,queryPagesTopQueriesBatch:e.queryPagesTopQueriesBatch})),!e.cancelled&&e.ga4PropertyId&&(n.analytics=await ux({propertyId:e.ga4PropertyId,pages:e.pages,warnings:e.warnings,limit:e.analyticsLimit,window:t,fetchLandingPageValues:e.fetchLandingPageValues,landingValueForUrl:e.landingValueForUrl})),n}function mx(e,t){try{let n=new URL(e,t);return n.hash=``,n.origin!==t.origin||![`http:`,`https:`].includes(n.protocol)?void 0:n.toString()}catch{return}}function hx(e,t){if(e)try{let n=new URL(e,t);return[`http:`,`https:`].includes(n.protocol)?n.toString():void 0}catch{return}}function gx(e){return _(`sha256`).update(e).digest(`hex`)}function _x(e,t){let n=t.toLowerCase();return e[t]??e[n]??Object.entries(e).find(([e])=>e.toLowerCase()===n)?.[1]}function vx(e){let t=new Set([`authorization`,`cookie`,`proxy-authorization`,`set-cookie`,`www-authenticate`]);return Object.fromEntries(Object.entries(e).filter(([e])=>!t.has(e.toLowerCase())))}function yx(e,t){return e.length>t?e.slice(0,t).trimEnd():e}function bx(e,t){let n=new Set,r=[];for(let i of e){if(i.internal!==t)continue;let e=yx(i.text.replace(/\s+/g,` `).trim(),120);if(!e)continue;let a=`${i.href}\n${e}`;if(!n.has(a)&&(n.add(a),r.push({href:i.href,text:e}),r.length>=25))break}return r}function xx(e){let t=[`facebook.com`,`instagram.com`,`linkedin.com`,`pinterest.com`,`tiktok.com`,`twitter.com`,`x.com`,`youtube.com`],n=new Set;for(let r of e)if(!r.internal)try{let e=new URL(r.href),i=e.hostname.replace(/^www\./,``).toLowerCase();t.some(e=>i===e||i.endsWith(`.${e}`))&&(e.hash=``,n.add(e.toString()))}catch{}return[...n].slice(0,50)}function Sx(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 Cx(e){return/^\s*(?:text\/html|application\/xhtml\+xml)\b/i.test(e??``)}function wx(e,t){let n=e,r=String(n?.code??n?.cause?.code??``).toUpperCase(),i=e instanceof Error?e.message:String(e);return t?.aborted?`aborted`:/TOO MANY REDIRECTS/i.test(i)?`redirect-limit`:[`ENOTFOUND`,`EAI_AGAIN`,`EAI_FAIL`].includes(r)?`dns`:r.includes(`CERT`)||r.includes(`TLS`)||r.includes(`SSL`)||/CERTIFICATE|\bTLS\b|\bSSL\b/i.test(i)?`tls`:r===`ETIMEDOUT`||/TIMED?\s*OUT|TIMEOUT|ABORT(?:ED|ERROR)/i.test(i)?`timeout`:`unknown`}function Tx(e,t,n,r){let i={requestedUrl:e,outcome:`response`,finalUrl:t.finalUrl,status:t.status,contentType:_x(t.headers,`content-type`),durationMs:t.diagnostics.durationMs,redirectChain:t.diagnostics.redirectChain};if(n===`failed`){if(!r)throw Error(`Failed extraction observations require an error.`);return{...i,extraction:n,extractionError:r}}return{...i,extraction:n}}function Ex(e,t){let n=_x(e.headers,`x-robots-tag`),r=mm(e.headers,e.finalUrl),i=nm(n,`noindex`),a=!!t;return{url:e.finalUrl,finalUrl:e.finalUrl,status:e.status,contentType:t,responseHeaders:vx(e.headers),responseTimeMs:e.diagnostics.durationMs,sizeBytes:Buffer.byteLength(e.html),usedJs:e.usedJs,fetchSource:e.diagnostics.source,cacheState:e.diagnostics.cache,fetchDiagnostics:e.diagnostics,blocked:e.diagnostics.blocked,robotsTxt:e.robotsTxt,xRobotsTag:n,canonical:r.selectedUrl,canonicalRaw:r.selectedRaw??r.candidates[0]?.raw,canonicalStatus:r.status,canonicalCandidates:r.candidates,indexable:e.status>=200&&e.status<300&&!i,indexability:i?`X-Robots-Tag noindex`:a?`Non-HTML response`:`Unknown response media type`,declaredIndexability:i?`noindex`:a?`not-html`:`unknown`,extractionStatus:a?`not-applicable`:`unknown-media-type`,wordCount:0,contentHash:gx(e.html),outgoingInternalCount:0,outgoingExternalCount:0,isHttps:new URL(e.finalUrl).protocol===`https:`,hasHsts:!!_x(e.headers,`strict-transport-security`),compression:_x(e.headers,`content-encoding`)}}function Dx(e){if(e.status<200||e.status>=300)return`Status ${e.status}`;if(tm(e.metaRobots,`noindex`))return`Meta robots noindex`;if(nm(e.xRobotsTag,`noindex`))return`X-Robots-Tag noindex`}async function Ox(e,t={}){let n=Date.now(),r;try{r=await Ag(e,t)}catch(r){if(r instanceof jh)return{request:{requestedUrl:e,outcome:`skipped`,reason:`origin-backpressure`,error:r.message,extraction:`not-applicable`},urls:[]};if(r instanceof Wh)return{request:{requestedUrl:e,outcome:`skipped`,reason:r.reason,robotsTxt:{url:r.evidence.url,allowed:r.evidence.allowed,availability:r.evidence.availability,status:r.evidence.status,error:r.evidence.error,matchedLine:r.evidence.matchedLine},extraction:`not-applicable`},urls:[]};let i=r instanceof Error?r.message:String(r);return{request:{requestedUrl:e,outcome:`failure`,durationMs:Date.now()-n,failureKind:wx(r,t.signal),error:i,extraction:`not-applicable`},urls:[],warning:`${e}: ${i}`}}let i=_x(r.headers,`content-type`);if(!Cx(i))return{request:Tx(e,r,i?`not-applicable`:`unknown-media-type`),page:Ex(r,i),urls:[]};try{let t=await Th(r),n=new URL(t.finalUrl),a=t.links.map(e=>mx(e.href,n)).filter(e=>!!e),o=t.links.filter(e=>!e.internal).map(e=>e.href).filter(e=>/^https?:\/\//.test(e)),s=t.headings.find(e=>e.level===1)?.text,c=[...new Set(a)],l=[...new Set(o)],u=t.canonicalEvidence?.selectedUrl??hx(t.canonical,t.finalUrl),d=ah(t.googleRichResults??[]),f={url:t.finalUrl,finalUrl:t.finalUrl,status:r.status,contentType:i,responseHeaders:vx(r.headers),responseTimeMs:r.diagnostics.durationMs,sizeBytes:Buffer.byteLength(r.html),usedJs:r.usedJs,fetchSource:r.diagnostics.source,cacheState:r.diagnostics.cache,fetchDiagnostics:r.diagnostics,blocked:r.diagnostics.blocked,robotsTxt:r.robotsTxt,title:t.title,metaDescription:t.metaDescription,canonical:u,canonicalRaw:t.canonicalEvidence?.selectedRaw??t.canonicalEvidence?.candidates[0]?.raw??t.canonical,canonicalStatus:t.canonicalEvidence?.status,canonicalCandidates:t.canonicalEvidence?.candidates,metaRobots:t.metaRobots,xRobotsTag:t.xRobotsTag,h1:s,h1Count:t.headings.filter(e=>e.level===1).length,h2Count:t.headings.filter(e=>e.level===2).length,h3Count:t.headings.filter(e=>e.level===3).length,indexable:!1,extractionStatus:`complete`,wordCount:t.wordCount,contentExtraction:t.contentExtraction,warnings:[...t.warnings,...t.canonicalEvidence?.status===`invalid`||t.canonical&&!u?[`Ignored a malformed canonical URL.`]:[],...t.canonicalEvidence?.status===`conflicting`?[`Found conflicting canonical declarations.`]:[],...t.canonicalEvidence?.status===`outside-head-only`?[`Ignored canonical link elements outside the document head because Google does not accept them.`]:[]],contentHash:gx([t.title,t.metaDescription,s,t.canonical,t.contentText].join(`
502
- `)),mainContentHash:gx(t.contentText),textRatio:r.html.length?Math.min(1,t.contentText.length/r.html.length):void 0,contentSample:yx(t.contentText,300),lang:t.lang,hasViewport:t.hasViewport,isHttps:new URL(t.finalUrl).protocol===`https:`,hasHsts:!!_x(r.headers,`strict-transport-security`),compression:_x(r.headers,`content-encoding`),hreflang:t.hreflang,mixedContentCount:t.mixedContentUrls.length,mixedContentSamples:t.mixedContentUrls.slice(0,25),imagesTotal:t.imagesTotal,imagesMissingAlt:t.imagesMissingAlt,oversizedImageCandidates:t.oversizedImageCandidates,outgoingInternalCount:c.length,outgoingExternalCount:l.length,sampleInternalLinks:c.slice(0,25),sampleExternalLinks:l.slice(0,25),internalAnchorSamples:bx(t.links,!0),externalAnchorSamples:bx(t.links,!1),schemaTypes:t.schemaTypes,structuredDataFormats:t.structuredDataFormats??[],googleRichResults:d.assessments,googleRichResultsSelection:d.selection,schemaSameAs:[...new Set((t.schemaSameAsEvidence??[]).map(e=>e.url))].slice(0,50),schemaSameAsEvidence:(t.schemaSameAsEvidence??[]).slice(0,50),invalidSchemaSameAs:(t.invalidSchemaSameAs??[]).slice(0,25),socialProfileLinks:xx(t.links),invalidJsonLdCount:t.invalidJsonLdCount,invalidJsonLdSamples:t.invalidJsonLdSamples,unrecognizedJsonLdTypes:t.unrecognizedJsonLdTypes??[],openGraphTitle:t.openGraph[`og:title`],openGraphDescription:t.openGraph[`og:description`],openGraphImage:t.openGraph[`og:image`],twitterCard:t.twitter[`twitter:card`],author:t.author,hasDate:t.hasDate,geo:{semanticHtml:t.semanticHtml,structuredData:(t.structuredDataFormats?.length??0)>0,hasAuthor:t.hasAuthor,hasDate:t.hasDate,questionHeadings:t.questionHeadings,listCount:t.listCount,tableCount:t.tableCount,structuredBlocks:t.structuredBlocks,answerable:t.answerable,hasFaqSchema:t.schemaTypes.includes(`FAQPage`),hasQapageSchema:t.schemaTypes.includes(`QAPage`)}};return f.indexability=Dx(f),f.indexable=!f.indexability,f.declaredIndexability=tm(f.metaRobots,`noindex`)||nm(f.xRobotsTag,`noindex`)?`noindex`:f.robotsTxt?.allowed===!1?`robots-blocked`:f.canonicalStatus===`conflicting`?`canonical-conflict`:f.canonical&&!Sx(f.canonical,f.finalUrl)?`canonical-hint-other`:`indexable-candidate`,{request:Tx(e,r,`complete`),page:f,urls:c}}catch(t){let n=t instanceof Error?t.message:String(t);return{request:Tx(e,r,`failed`,n),urls:[],warning:`${e}: content extraction failed: ${n}`}}}const kx=52428800,Ax=/^(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.\d+)?)?(Z|[+-]\d{2}:\d{2}))?$/;function jx(e){try{let t=new URL(e.trim());return[`http:`,`https:`].includes(t.protocol)?t.toString():void 0}catch{return}}function Mx(e,t){if(!Number.isSafeInteger(e)||e<1)throw Error(`${t} must be a positive integer.`);return e}function Nx(e){let t=Ax.exec(e);if(!t)return;let[,n,r,i,a,o,s,c]=t,l=Number(n),u=Number(r),d=Number(i),f=a===void 0?0:Number(a),p=o===void 0?0:Number(o),m=s===void 0?0:Number(s),h=u===2?l%4==0&&(l%100!=0||l%400==0)?29:28:[4,6,9,11].includes(u)?30:31,g=c?.match(/^([+-])(\d{2}):(\d{2})$/);if(u<1||u>12||d<1||d>h||f>23||p>59||m>59||g&&(Number(g[2])>23||Number(g[3])>59))return;let _=a?new Date(e):new Date(`${e}T00:00:00Z`);return Number.isNaN(_.getTime())?void 0:_}function Px(e){return e[0]===31&&e[1]===139}function Fx(e,t){if(!e)return!0;let n=e.split(`;`,1)[0]?.trim().toLowerCase();return!!(n&&(t===`gzip`?n===`application/gzip`||n===`application/x-gzip`:n===`application/xml`||n===`text/xml`||n.endsWith(`+xml`)))}async function Ix(e){let t=Number(e.headers.get(`content-length`));if(Number.isFinite(t)&&t>kx)throw Error(`The response exceeds the ${kx}-byte sitemap limit.`);let n=e.body?.getReader();if(!n)return new Uint8Array;let r=[],i=0;try{for(;;){let{done:e,value:t}=await n.read();if(e)break;if(i+=t.byteLength,i>kx)throw await n.cancel(),Error(`The response exceeds the ${kx}-byte sitemap limit.`);r.push(t)}}finally{n.releaseLock()}let a=new Uint8Array(i),o=0;for(let e of r)a.set(e,o),o+=e.byteLength;return a}function Lx(e){let t=[],n,r=0;for(;r<e.length;){let i=e.indexOf(`<`,r);if(i===-1)break;if(e.startsWith(`<!--`,i)){let t=e.indexOf(`-->`,i+4);if(t===-1)return{error:`an unclosed XML comment`};r=t+3;continue}if(e.startsWith(`<![CDATA[`,i)){let t=e.indexOf(`]]>`,i+9);if(t===-1)return{error:`an unclosed CDATA section`};r=t+3;continue}if(e.startsWith(`<?`,i)){let t=e.indexOf(`?>`,i+2);if(t===-1)return{error:`an unclosed processing instruction`};r=t+2;continue}let a=i+1,o;for(;a<e.length;){let t=e[a];if(o)t===o&&(o=void 0);else if(t===`"`||t===`'`)o=t;else if(t===`>`)break;a+=1}if(a>=e.length||o)return{error:`an unclosed XML tag`};let s=e.slice(i+1,a).trim();if(r=a+1,!s||s.startsWith(`!`))continue;if(s.startsWith(`/`)){let e=s.slice(1).trim(),n=t.pop();if(!n||e!==n)return{error:`a mismatched closing tag </${e}>`};continue}let c=s.match(/^([A-Za-z_][A-Za-z0-9_.:-]*)/);if(!c?.[1])return{error:`an invalid XML tag name`};if(t.length===0){if(n)return{error:`more than one XML root element`};n=c[1]}/\/$/.test(s)||t.push(c[1])}return t.length>0?{error:`an unclosed <${t.at(-1)}> tag`}:n===`urlset`||n===`sitemapindex`?{root:n}:{error:`expected a <urlset> or <sitemapindex> root${n?`, found <${n}>`:``}`}}async function Rx(e){try{let t=await Oh(e,{profile:`browser`}),n=t.headers.get(`content-type`)??void 0,r=jx(t.url),i=t.redirected&&r?{finalUrl:r,redirected:!0}:{};if(!t.ok)return{document:{url:e,...i,dataStatus:`unavailable`,status:t.status,contentType:n,compression:`none`,warning:`Sitemap fetch failed for ${e}: HTTP ${t.status}.`}};let a;try{a=await Ix(t)}catch(r){return{document:{url:e,...i,dataStatus:`partial`,status:t.status,contentType:n,compression:`none`,warning:`Could not read sitemap data: ${r instanceof Error?r.message:String(r)}`}}}let o=Px(a)?`gzip`:`none`,s=a;if(o===`gzip`)try{s=ae(a,{maxOutputLength:kx})}catch(r){return{document:{url:e,...i,dataStatus:`partial`,status:t.status,contentType:n,compression:o,bytes:a.byteLength,warning:`Could not decompress sitemap gzip data: ${r instanceof Error?r.message:String(r)}`}}}let c=new TextDecoder().decode(s),l=Lx(c),u=Fx(n,o)?void 0:`Sitemap returned content type ${n??`unknown`}, not an XML type.`;return`error`in l?{document:{url:e,...i,dataStatus:`partial`,status:t.status,contentType:n,compression:o,bytes:a.byteLength,uncompressedBytes:s.byteLength,warning:`Sitemap XML is invalid: ${l.error}.`}}:{document:{url:e,...i,dataStatus:u?`partial`:`complete`,status:t.status,contentType:n,compression:o,bytes:a.byteLength,uncompressedBytes:s.byteLength,root:l.root,...u?{warning:u}:{}},xml:c}}catch(t){return{document:{url:e,dataStatus:`unavailable`,compression:`none`,warning:`Sitemap fetch failed for ${e}: ${t instanceof Error?t.message:String(t)}`}}}}function zx(e,t){let n=Mx(t,`limit`),r=[...new Set(e.flatMap(e=>e.urls))],i=e.some(e=>e.truncation.possiblyTruncated),a=r.length>n;return{urls:r.slice(0,n),truncation:{possiblyTruncated:i||a,sourceTruncated:i,inventoryLimitExceeded:a,omittedUrlsAtLeast:Math.max(0,r.length-n),limit:n}}}async function Bx(e){let t=[],n=[],r=new Set,i=[],a=new Set,o=new Set,s=[],c=[],l=[],u=[],d=[],f=Mx(e.limit??5e4,`limit`),p=Mx(e.maxNested??50,`maxNested`),m=jx(e.sitemapUrl);if(!m)throw Error(`sitemapUrl must be an absolute HTTP or HTTPS URL.`);o.add(m),d.push(m);let h=0,g=0,_=0,v=0,y=0,b=0,x=0,ee=0,te=0,S=0,C=0,w=!1,T=!1;function ne(e){b+=1,s.length<10&&s.push(e)}function re(e){x+=1;let t=Nx(e.value);if(!t){te+=1,c.length<10&&c.push(e);return}ee+=1,t.getTime()>Date.now()&&(S+=1,l.length<10&&l.push(e))}for(;d.length;){let e=d.shift();if(!e)continue;let s=await Rx(e);if(u.push(s.document),s.document.warning&&t.push(s.document.warning),s.document.dataStatus!==`unavailable`&&(h+=1),!s.xml||!s.document.root)continue;let c=pp(s.xml,{xmlMode:!0});c(s.document.root===`urlset`?`urlset > url`:``).each((t,i)=>{g+=1;let a=c(i),o=a.children(`loc`).first().text().trim(),s=a.children(`lastmod`).first();s.length&&re({sitemapUrl:e,kind:`url`,loc:o,value:s.text().trim()});let l=jx(o);if(!l){ne({sitemapUrl:e,kind:`url`,value:o});return}if(r.has(l)){v+=1;return}if(r.add(l),n.length<f){n.push(l);return}w=!0,C+=1}),c(s.document.root===`sitemapindex`?`sitemapindex > sitemap`:``).each((t,n)=>{_+=1;let r=c(n),s=r.children(`loc`).first().text().trim(),l=r.children(`lastmod`).first();l.length&&re({sitemapUrl:e,kind:`sitemap`,loc:s,value:l.text().trim()});let u=jx(s);if(!u){ne({sitemapUrl:e,kind:`sitemap`,value:s});return}if(a.has(u)||o.has(u)){y+=1;return}a.add(u),i.push(u),o.size<p?(o.add(u),d.push(u)):T=!0})}let E=i.filter(e=>!o.has(e)).length,ie=w||T||E>0;return b&&t.push(`Ignored ${b} invalid sitemap <loc> ${b===1?`entry`:`entries`}.`),ie&&t.push(`Sitemap discovery exceeded a configured URL or sitemap boundary; the returned inventory is incomplete.`),{sitemapUrl:m,dataStatus:t.length||b||ie?`partial`:`complete`,urls:n,nestedSitemaps:i,source:{sitemapsFetched:h,urlLocs:g,sitemapLocs:_,duplicateUrlLocs:v,duplicateSitemapLocs:y,invalidLocs:{count:b,samples:s},lastmods:{trust:`unverified`,observed:x,parseable:ee,malformed:{count:te,samples:c},future:{count:S,samples:l}},documents:u},truncation:{possiblyTruncated:ie,urlLimitExceeded:w,nestedSitemapLimitExceeded:T,omittedUrlsAtLeast:C,unprocessedSitemaps:E,limits:{urls:f,sitemaps:p}},warnings:t}}const Vx={runGa4Report:dn};function Hx(e){if(!e||e===`(not set)`)return``;let[t=``]=e.split(`?`);return t.replace(/\/$/,``)||`/`}function Ux(e){try{return Hx(new URL(e).pathname)}catch{return``}}function Wx(e,t){return e<t?-1:+(e>t)}function Gx(e){let t=[...e].sort((e,t)=>Wx(Hx(e.landingPagePlusQueryString??``),Hx(t.landingPagePlusQueryString??``))||Wx(e.landingPagePlusQueryString??``,t.landingPagePlusQueryString??``)||Wx(e.sessions??``,t.sessions??``)||Wx(e.totalUsers??``,t.totalUsers??``)||Wx(e.conversions??``,t.conversions??``)),n=new Map;for(let e of t){let t=Hx(e.landingPagePlusQueryString??``);if(!t)continue;let r=n.get(t)??{sessions:0,totalUsers:0,conversions:0};n.set(t,{sessions:r.sessions+Number(e.sessions??0),totalUsers:r.totalUsers+Number(e.totalUsers??0),conversions:r.conversions+Number(e.conversions??0)})}return n}function Kx(e,t){return e.get(Ux(t))}function qx(e){return e?.dataStatus===`complete`}function Jx(e){if(!e.propertyId)return{canRank:!1,warnings:[]};let t=`Observed landing-page values remain visible but do not affect priority scores.`,n=[...e.warning?[`GA4: ${e.warning}`]:[],...e.source?.retainedRowLimitReached?[`GA4: the retained-row limit was reached. ${t}`]:[],...e.source?.qualityWarnings?.map(e=>`${e} ${t}`)??[]];return!e.source?.dataStatus&&!e.warning&&n.push(`GA4: landing-page completeness was not reported. ${t}`),{canRank:!e.warning&&n.length===0&&qx(e.source),warnings:n}}async function Yx(e,t=Vx){let n=e.limit??5e3;if(!e.propertyId)return{values:new Map,source:{dataStatus:`complete`,returnedRows:0,retainedRowLimit:n,retainedRowLimitReached:!1,qualityWarnings:[]}};try{let r=await t.runGa4Report(e.propertyId,{dateRanges:[{startDate:e.startDate,endDate:e.endDate}],dimensions:[{name:`landingPagePlusQueryString`}],metrics:[{name:`sessions`},{name:`totalUsers`},{name:`conversions`}],orderBys:[{metric:{metricName:`sessions`},desc:!0}],limit:n}),i=hn(r),a=Gx(i),o=r.rowCount,s=o!==void 0&&o>i.length||i.length>=n,c=gn(r,`GA4 landing-page report`);return{values:a,source:{dataStatus:s||c.length?`partial`:`complete`,returnedRows:i.length,...o===void 0?{}:{availableRows:o},retainedRowLimit:n,retainedRowLimitReached:s,qualityWarnings:c}}}catch(e){return{values:new Map,source:{dataStatus:`partial`,returnedRows:0,retainedRowLimit:n,retainedRowLimitReached:!1,qualityWarnings:[]},warning:e instanceof Error?e.message:String(e)}}}function Xx(e={}){let t=!!(e.queryPageMetrics||e.queryPageTopQuery),n=!!(e.queryPagesMetrics||e.queryPagesTopQueries),r=e.queryPagesMetricsBatch||e.queryPagesTopQueriesBatch?`batch`:n?`legacy-bulk`:t?`per-page`:void 0;return{fetchPage:e.fetchPage??Ox,fetchSitemapUrls:e.fetchSitemapUrls??Bx,fetch:e.fetch??Oh,queryPageMetrics:(r===`per-page`?e.queryPageMetrics:void 0)??(r?async()=>void 0:t_),queryPageTopQuery:(r===`per-page`?e.queryPageTopQuery:void 0)??(r?async()=>void 0:i_),queryPagesMetrics:r===`legacy-bulk`?e.queryPagesMetrics:r?void 0:n_,queryPagesTopQueries:r===`legacy-bulk`?e.queryPagesTopQueries:r?void 0:a_,queryPagesMetricsBatch:r===`batch`?e.queryPagesMetricsBatch:r?void 0:r_,queryPagesTopQueriesBatch:r===`batch`?e.queryPagesTopQueriesBatch:r?void 0:o_,fetchLandingPageValues:e.fetchLandingPageValues??Yx,landingValueForUrl:e.landingValueForUrl??Kx,now:e.now??(()=>new Date)}}function Zx(e,t){try{return new URL(e).origin===t}catch{return!1}}function Qx(e){let t=new URL(e.url).origin,n=e.declaredSitemapUrls.filter(e=>Zx(e,t)),r=e.declaredSitemapUrls.length-n.length;r>0&&e.warnings.push(`Ignored ${r} robots.txt sitemap declaration${r===1?``:`s`} outside the crawl origin.`);let i=[...new Set(n)].map(e=>({url:e,source:`robots-txt`}));return i.length>20&&e.warnings.push(`Used the first 20 robots.txt sitemap declarations to keep discovery bounded.`),i.length>0?i.slice(0,20):[{url:new URL(`/sitemap.xml`,e.url).toString(),source:`default-path`}]}function $x(e){return e.some(e=>e.dataStatus!==`unavailable`)?e.every(e=>e.dataStatus===`complete`)?`complete`:`partial`:`unavailable`}async function eS(e){let t=[],n=new Set,r=Qx(e);for(let i of r){let r=await e.fetchSitemapUrls({sitemapUrl:i.url,limit:e.maxPages});e.warnings.push(...r.warnings);for(let e of r.urls)n.add(e);t.push({url:r.sitemapUrl,source:i.source,dataStatus:r.source.sitemapsFetched>0?r.dataStatus:`unavailable`,urlsReturned:r.urls.length,sitemapsFetched:r.source.sitemapsFetched,lastmods:r.source.lastmods,documents:r.source.documents,possiblyTruncated:r.truncation.possiblyTruncated,warnings:r.warnings})}if(n.size===0&&r.some(e=>e.source===`robots-txt`)){let r=new URL(`/sitemap.xml`,e.url).toString();if(!t.some(e=>e.url===r)){e.warnings.push(`No robots.txt sitemap declaration returned URLs, so the crawler also tried /sitemap.xml.`);let i=await e.fetchSitemapUrls({sitemapUrl:r,limit:e.maxPages});e.warnings.push(...i.warnings);for(let e of i.urls)n.add(e);t.push({url:i.sitemapUrl,source:`default-path`,dataStatus:i.source.sitemapsFetched>0?i.dataStatus:`unavailable`,urlsReturned:i.urls.length,sitemapsFetched:i.source.sitemapsFetched,lastmods:i.source.lastmods,documents:i.source.documents,possiblyTruncated:i.truncation.possiblyTruncated,warnings:i.warnings})}}return{urls:[...n],discovery:{dataStatus:$x(t),urlsReturned:n.size,roots:t}}}const tS=[`GPTBot`,`OAI-SearchBot`,`ChatGPT-User`,`ClaudeBot`,`PerplexityBot`,`Google-Extended`,`CCBot`,`Applebot`,`Bingbot`],nS=[`/.well-known/agent.json`,`/agent.json`,`/.well-known/mcp.json`,`/.well-known/ai-plugin.json`,`/.well-known/openapi.json`,`/openapi.json`];function rS(e,t){try{let n=t?new URL(e,t):new URL(e);return n.hash=``,[`http:`,`https:`].includes(n.protocol)?n.toString():void 0}catch{return}}function iS(e,t){try{return new URL(e).origin===t}catch{return!1}}function aS(e){let t=new URL(e).pathname.toLowerCase();return/\.(avif|bmp|css|csv|dmg|eot|gif|gz|ico|jpeg|jpg|js|json|map|mov|mp3|mp4|ogg|otf|pdf|png|svg|tar|ttf|wav|webm|webp|woff|woff2|zip)$/.test(t)}function oS(e,t){if(!e.includes(`*`))return t.includes(e);let n=e.split(`*`),r=0;for(let e of n){if(!e)continue;let n=t.slice(r).indexOf(e);if(n===-1)return!1;r+=n+e.length}return!0}function sS(e,t,n){return(!t.length||t.some(t=>oS(t,e)))&&!n.some(t=>oS(t,e))}async function cS(e){let t=new URL(`/llms.txt`,e.url).toString(),n=Qb({timeoutMs:e.timeoutMs,signal:e.signal});try{let r=await e.fetch(t,{profile:`bot`,redirect:`follow`,signal:n.signal}),i=r.headers.get(`content-type`)??``,a=r.status>=200&&r.status<300&&!/\btext\/html\b/i.test(i);return await r.body?.cancel().catch(()=>void 0),{url:r.url||t,exists:a,status:r.status}}catch{return{url:t,exists:!1}}finally{n.cleanup()}}function lS(e,t){return E(e,t)}function uS(e){let t=new Set;for(let n of e.matchAll(/^\s*user-agent\s*:\s*(.+?)\s*$/gim)){let e=n[1]?.trim().toLowerCase();e&&t.add(e)}return t}function dS(e){let t=new Set;for(let n of e.matchAll(/^\s*sitemap\s*:\s*(\S+)\s*$/gim)){let e=n[1]?.trim();if(e)try{t.add(new URL(e).toString())}catch{}}return[...t]}async function fS(e){let t=new URL(`/robots.txt`,e.url).toString(),n=Qb({timeoutMs:e.timeoutMs,signal:e.signal});try{let r=await e.fetch(t,{profile:`bot`,redirect:`follow`,signal:n.signal}),i=await r.text(),a=r.status>=200&&r.status<300,o=r.status>=400&&r.status<500&&r.status!==429,s=a?`available`:[401,403].includes(r.status)?`access-blocked`:r.status===429?`rate-limited`:o?`absent`:`unreachable`,c=a,l=c?uS(i):new Set,u=lS(t,c?i:``);return{url:r.url||t,exists:c,availability:s,status:r.status,...[`rate-limited`,`unreachable`].includes(s)?{error:`robots.txt returned HTTP ${r.status}.`}:{},sitemapUrls:c?dS(i):[],botAccess:tS.map(t=>{let n=t.toLowerCase();return{userAgent:t,allowed:s===`rate-limited`||s===`unreachable`?null:u.isAllowed(e.url,t)??!0,declared:l.has(n),coveredByWildcard:l.has(`*`)}})}}catch(e){return{url:t,exists:!1,availability:`unreachable`,error:e instanceof Error?e.message:String(e),sitemapUrls:[],botAccess:tS.map(e=>({userAgent:e,allowed:null,declared:!1,coveredByWildcard:!1}))}}finally{n.cleanup()}}async function pS(e){let t=new URL(e.path,e.baseUrl).toString(),n=Qb({timeoutMs:e.timeoutMs,signal:e.signal});try{let r=await e.fetch(t,{profile:`bot`,redirect:`follow`,signal:n.signal}),i=r.headers.get(`content-type`)??``,a=await r.text(),o=r.status>=200&&r.status<300,s;if(o&&/\bjson\b/i.test(i))try{JSON.parse(a),s=!0}catch{s=!1}return{url:r.url||t,exists:o,status:r.status,contentType:i,...s===void 0?{}:{validJson:s}}}catch{return{url:t,exists:!1}}finally{n.cleanup()}}async function mS(e){let t=[];for(let n of nS){if(e.signal?.aborted)break;t.push(await pS({baseUrl:e.url,path:n,timeoutMs:e.timeoutMs,fetch:e.fetch,signal:e.signal}))}return t}async function hS(e,t){let n=Xx(t),r=pb(e),{searchMetricsLimit:i,analyticsLimit:a}=fx(e),o=e.signal,s=!!o?.aborted,c=()=>s||!!o?.aborted,l=e.site,u=new URL(r.url).origin,d=[],f=[],p=new Set,m=new Set,h=new Set,g={},_=new Map,v=[],y=[],b=new Map,x=new Set,ee=r.mode===`site`,te=0,S=0,C=0,w=0,T=0,ne=0,re=0,E,ie,ae=Promise.resolve(),D=(t,i={})=>{if(!e.onStatus)return;let a={type:`crawl_status`,phase:t,generatedAt:n.now().toISOString(),discoveredUrls:h.size,queuedUrls:te,pendingUrls:f.length,inFlightUrls:x.size,crawledUrls:v.length,skippedUrls:S,failedUrls:ne,observedInternalLinks:re,maxPages:r.maxPages,...i};ae=ae.then(()=>e.onStatus?.(a)).catch(()=>void 0)},oe=async()=>{await ae},se=r.js===`off`?void 0:hg({concurrency:r.concurrency});try{D(`started`,{url:r.url,message:`Started crawl for ${r.url}.`});let t=c()?void 0:await Promise.all([cS({url:r.url,timeoutMs:r.timeoutMs,fetch:n.fetch,signal:o}),fS({url:r.url,timeoutMs:r.timeoutMs,fetch:n.fetch,signal:o}),mS({url:r.url,timeoutMs:r.timeoutMs,fetch:n.fetch,signal:o})]),ae=t?.[0]??{url:new URL(`/llms.txt`,r.url).toString(),exists:!1},ce=t?.[1],le=t?.[2];s=c();let O=(e,t)=>{let n=rS(e,r.url);if(!n){S+=1,D(`url_skipped`,{url:e,depth:t,reason:`invalid_url`});return}if(h.add(n),!p.has(n)){if(m.has(n)){let e=f.find(e=>e.url===n);e&&t<e.depth&&(e.depth=t),f.sort((e,t)=>e.depth-t.depth||(e.url<t.url?-1:+(e.url>t.url)));return}if(!iS(n,u)){S+=1,D(`url_skipped`,{url:n,depth:t,reason:`off_origin`});return}if(aS(n)){S+=1,D(`url_skipped`,{url:n,depth:t,reason:`asset_url`});return}if(!sS(n,r.include,r.exclude)){S+=1,D(`url_skipped`,{url:n,depth:t,reason:`filtered_url`});return}if(f.length+p.size+x.size>=r.maxPages*5){S+=1,T+=1,D(`url_skipped`,{url:n,depth:t,reason:`queue_safety_limit`});return}m.add(n),te+=1,f.push({url:n,depth:t}),f.sort((e,t)=>e.depth-t.depth||(e.url<t.url?-1:+(e.url>t.url))),D(`url_queued`,{url:n,depth:t})}};if(r.mode!==`sitemap`){let e=r.mode===`list`?r.urls:[r.url];for(let t of e)O(t,0)}if(!c()&&r.useSitemap&&(r.mode===`site`||r.mode===`sitemap`)){let e=await eS({url:r.url,maxPages:r.maxPages,declaredSitemapUrls:ce?.sitemapUrls??[],warnings:d,fetchSitemapUrls:n.fetchSitemapUrls});for(let t of e.urls)O(t,0);E=e.discovery}let ue=()=>{for(;f.length;){let e=f.shift();if(!e)continue;let t=rS(e.url,r.url);if(t&&!p.has(t))return p.add(t),{url:t,depth:e.depth}}},de=e=>{let t;t={...e,promise:n.fetchPage(e.url,{js:r.js,refresh:r.refresh,timeoutMs:r.timeoutMs,rate:r.fetchRate,signal:o,respectRobots:r.respectRobots,renderer:se}).then(e=>({task:t,result:e})).catch(n=>({task:t,result:{request:{requestedUrl:e.url,outcome:`failure`,failureKind:`unknown`,error:n instanceof Error?n.message:String(n),extraction:`not-applicable`},urls:[],warning:`${e.url}: ${n instanceof Error?n.message:String(n)}`}}))},x.add(t),D(`page_started`,{url:e.url,depth:e.depth})};for(;!c()&&(f.length||x.size)&&v.length<r.maxPages;){for(;!c()&&x.size<r.concurrency&&v.length+x.size<r.maxPages;){let e=ue();if(!e)break;de(e)}if(!x.size)break;let e=await Promise.race([...[...x].map(e=>e.promise),...o?[$b(o)]:[]]);if(e===Zb){s=!0;break}let{task:t,result:n}=e;x.delete(t);let i=n.request??(n.page?cx(t.url,n.page):{requestedUrl:t.url,outcome:`failure`,failureKind:`unknown`,error:n.warning??`Crawler returned no page snapshot.`,extraction:`not-applicable`});if(y.push(i),i.outcome===`skipped`){S+=1,i.reason===`robots-deferred`&&(C+=1,d.push(`${t.url}: crawl deferred because robots.txt availability is unknown.`)),i.reason===`origin-backpressure`&&(w+=1),D(`page_skipped`,{url:t.url,depth:t.depth,reason:i.reason});continue}if(n.warning){d.push(n.warning),ne+=1,D(`page_failed`,{url:t.url,depth:t.depth,reason:n.warning});continue}if(!n.page){ne+=1,D(`page_failed`,{url:t.url,depth:t.depth,reason:`missing_page_snapshot`});continue}let a=n.page,l=rS(a.finalUrl,r.url);if(!l){ne+=1,d.push(`${t.url}: invalid final URL ${a.finalUrl}`),D(`page_failed`,{url:t.url,depth:t.depth,reason:`invalid_final_url`});continue}a.url=l,a.finalUrl=l,a.crawlDepth=t.depth;let u=rS(t.url,r.url);u&&u!==l&&_.set(u,l),a.contentAuditAllowed=!r.respectRobots||a.robotsTxt?.allowed!==!1;let f=e=>{let n=rS(t.url,r.url)??t.url,i=n===l,o=e.map(e=>rS(e,l)).filter(e=>!!e),s=(e,t)=>{let n=[...t].sort((e,t)=>e<t?-1:+(e>t));e.outgoingInternalCount=n.length,e.sampleInternalLinks=n.slice(0,25)},c=b.get(l);if(c){let e=new Set([...c.links,...o]),r=Math.min(c.minDepth,t.depth),u=!c.directlyRequested&&i||c.directlyRequested===i&&n<c.sourceRequest;if(u)a.crawlDepth=r,v[c.index]=a;else{let e=v[c.index];e&&(e.crawlDepth=r)}let d=v[c.index];d&&s(d,e),re+=e.size-c.links.size,b.set(l,{...c,directlyRequested:c.directlyRequested||i,sourceRequest:u?n:c.sourceRequest,links:e,minDepth:r}),g[l]=[...e].sort((e,t)=>e<t?-1:+(e>t))}else{let e=new Set(o);s(a,e),v.push(a),re+=e.size,b.set(l,{index:v.length-1,directlyRequested:i,sourceRequest:n,links:e,minDepth:t.depth}),g[l]=[...e].sort((e,t)=>e<t?-1:+(e>t))}};if(r.respectRobots&&a.robotsTxt?.allowed===!1){a.blocked=!0,a.contentAuditAllowed=!1,a.indexable=!1,a.indexability=`Robots.txt disallowed`,a.declaredIndexability=`robots-blocked`,f([]),d.push(`${a.url}: skipped because robots.txt disallows it`),S+=1,D(`page_skipped`,{url:a.url,depth:t.depth,statusCode:a.status,reason:`robots_txt_disallowed`});continue}if(f(n.urls),D(`page_completed`,{url:t.url,depth:t.depth,statusCode:a.status}),ee&&t.depth<r.maxDepth)for(let e of n.urls)O(e,t.depth+1)}let fe=sx(g,_),pe=new Map(v.map((e,t)=>[e.url,t])),me=v.map(e=>e.crawlDepth??0);for(let e=0;e<v.length;e+=1){let e=!1;for(let[t,n]of Object.entries(fe)){let r=pe.get(t);if(r!==void 0)for(let t of n){let n=pe.get(t);if(n===void 0)continue;let i=(me[r]??0)+1;i>=(me[n]??1/0)||(me[n]=i,e=!0)}}if(!e)break}for(let[e,t]of v.entries())t.crawlDepth=me[e];s=c(),s&&(d.push(`Crawl cancelled; returning a partial report.`),D(`cancelled`,{url:r.url,message:`Crawl cancelled; returning a partial report.`}));let he=v.length>=r.maxPages&&(f.length>0||x.size>0),k=E?.dataStatus===`partial`,ge=s||ne>0||he||C>0||w>0||T>0||f.length>0||x.size>0||k,_e=!s&&v.length===0&&y.length>0&&y.every(e=>e.outcome===`failure`),ve=s&&x.size>0?`partial`:`available`,ye=await px({cancelled:s,site:l,ga4PropertyId:e.ga4PropertyId,pages:v,warnings:d,searchMetricsLimit:i,analyticsLimit:a,now:n.now,queryPageMetrics:n.queryPageMetrics,queryPageTopQuery:n.queryPageTopQuery,queryPagesMetrics:n.queryPagesMetrics,queryPagesTopQueries:n.queryPagesTopQueries,queryPagesMetricsBatch:n.queryPagesMetricsBatch,queryPagesTopQueriesBatch:n.queryPagesTopQueriesBatch,fetchLandingPageValues:n.fetchLandingPageValues,landingValueForUrl:n.landingValueForUrl}),be=[ye.searchConsole.status,ye.analytics.status].some(e=>e===`partial`||e===`unavailable`),A=_e?`failed`:ge||be?`partial`:`completed`;!s&&r.checkExternal&&(D(`external_links_started`,{url:r.url,message:`Started external link checks.`}),ie=await ax({pages:v,timeoutMs:r.timeoutMs,fetch:n.fetch,signal:o}),d.push(...ie.warnings),D(`external_links_completed`,{url:r.url,message:`Finished external link checks.`}));for(let e of v)e.geo&&={...e.geo,hasLlmsTxt:ae.exists,llmsTxtUrl:ae.url,llmsTxtStatus:ae.status};let xe=Nb({config:r,projectId:e.projectId,site:l,ga4PropertyId:e.ga4PropertyId,dataSources:ye,pages:v,requests:y,requestEvidenceStatus:ve,linkGraph:fe,ai:{llmsTxt:ae,...ce?{robotsTxt:ce}:{},...le?{agentResources:le}:{}},...E?{sitemapDiscovery:E}:{},...ie?{externalLinkVerification:ie}:{},status:A,warnings:d,caveats:Xb({cancelled:s,pageLimitReached:he,maxPages:r.maxPages,queueSafetySkippedUrls:T,originBackpressureSkippedUrls:w}),stats:{discoveredUrls:h.size,queuedUrls:te,crawledUrls:v.length,skippedUrls:S,failedUrls:ne,observedInternalLinks:re,pageLimitReached:he}});return D(`completed`,{url:r.url,reportId:xe.id,reportStatus:xe.status,message:`Crawl ${xe.status} with ${xe.summary.totalPages} crawled pages.`}),await oe(),xe}finally{await se?.close()}}const gS={latestCrawlReport:Yb,crawlSite:hS,saveCrawlReport:Gb,now:()=>new Date};function _S(e){return e.endsWith(`/`)?e:`${e}/`}function vS(e,t){try{let n=new URL(e),r=new URL(t);return n.origin===r.origin&&_S(r.pathname).startsWith(_S(n.pathname))}catch{return!1}}function yS(e,t){let n=t.maxPages??100,r=t.maxDepth??4;return(e.status===`completed`||e.status===`partial`)&&e.config.mode===`site`&&e.config.maxPages>=n&&e.config.maxDepth>=r&&e.config.include.length===0&&e.config.exclude.length===0&&e.config.useSitemap&&e.config.respectRobots&&(!t.url||vS(e.config.url,t.url))}function bS(e,t){let n=Date.parse(e.generatedAt),r=t.maxAgeMs??6048e5,i=t.now.getTime()-n;return Number.isFinite(n)&&i>=0&&i<=r}function xS(e){let t=e.warnings[0]??e.caveats[0];return t?`The technical crawl did not complete: ${t}`:`The technical crawl did not complete.`}function SS(e){return{url:e.url,...e.searchSite?{site:e.searchSite}:{},projectId:e.projectId,ga4PropertyId:e.ga4PropertyId,mode:`site`,maxPages:e.maxPages,maxDepth:e.maxDepth,respectRobots:!0,useSitemap:!0,checkExternal:!1,js:!1,refresh:e.refresh??!1}}async function CS(e,t=gS){if(e.crawl===!1)return{status:`skipped`,reason:`Technical crawl evidence was skipped with --no-crawl.`};let n=e.maxPages??100,r=e.maxDepth??4,i=t.latestCrawlReport(e.site);if(!e.refresh&&i&&yS(i,{...e,maxPages:n,maxDepth:r})&&bS(i,{...e,now:t.now()}))return{status:`reused`,report:i};if(!e.url)return{status:`unavailable`,reason:"No crawl URL is saved for this site. Add one to the project profile or run `seo crawl --url <url> --save`."};try{let i=await t.crawlSite(SS({...e,url:e.url,maxPages:n,maxDepth:r}));return i.status===`failed`?{status:`unavailable`,reason:xS(i)}:(t.saveCrawlReport(i),{status:e.refresh?`refreshed`:`created`,report:i,...i.status===`partial`?{reason:`The technical crawl completed with partial coverage.`}:{}})}catch(e){return{status:`unavailable`,reason:`The technical crawl could not run: ${e instanceof Error?e.message:String(e)}`}}}function wS(e){return[...new Set(e.filter(e=>!!e))]}function TS(e,t){return t?Math.round(e/t*100):0}function ES(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(e=>/^(Organization|LocalBusiness|Person|Product|WebSite)$/i.test(e))),a=t.flatMap(e=>e.schemaSameAsEvidence??[]).filter(e=>e.subjectTypes.some(e=>/^(Organization|LocalBusiness|Person)$/i.test(e))),o=a.filter(e=>e.subjectTypes.some(e=>/^(Organization|LocalBusiness)$/i.test(e))),s=wS(a.map(e=>e.url)),c={};for(let e of a)for(let t of e.subjectTypes)/^(Organization|LocalBusiness|Person)$/i.test(t)&&(c[t]=wS([...c[t]??[],e.url]));let l=wS(t.flatMap(e=>e.socialProfileLinks??[])),u=wS(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:TS(i.length,n),check:{id:`entity-schema`,title:`Entity schema is present`,plainEnglish:`${TS(i.length,n)}% of evaluated indexable pages include Organization, LocalBusiness, Person, Product, or WebSite schema.`,action:`Add accurate entity schema to the homepage and key product, author, organization, and local pages.`,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:wS(o.map(e=>e.url)),sameAsEvidence:a,unclassifiedSocialLinks:l}}},{coverage:Math.round((TS(d.length,n)+TS(f.length,n))/2),check:{id:`authority-freshness`,title:`Author and date signals observed`,plainEnglish:`${TS(d.length,n)}% of evaluated indexable pages expose an author signal and ${TS(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:TS(d.length,n),dateCoverage:TS(f.length,n),authors:u}}},{coverage:TS(p.length,n),check:{id:`entity-naming`,title:`Title and H1 coverage observed`,plainEnglish:`${TS(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}}}const DS=[`page`,`query`,`device`,`country`],OS=[`rank`,`dimension`,`data_status`,`evidence_scope`,`key`,`before_clicks`,`after_clicks`,`click_delta`,`before_impressions`,`after_impressions`,`impression_delta`,`before_position`,`after_position`,`position_delta`],kS=[`rank`,`dimension`,`data_status`,`key`,`retained_in`,`clicks`,`impressions`,`position`,`reason`];function AS(e){return[{filename:`segment-status.csv`,headers:[`dimension`,`data_status`,`matched_rows`,`unmatched_rows`,`before_rows_fetched`,`after_rows_fetched`,`possibly_truncated`,`verdict`,`warnings`],rows:DS.map(t=>{let n=e.segments[t];return{dimension:t,data_status:n.dataStatus,matched_rows:n.summary.matchedRows,unmatched_rows:n.summary.unmatchedRows,before_rows_fetched:n.source.before.rowsFetched,after_rows_fetched:n.source.after.rowsFetched,possibly_truncated:n.source.before.possiblyTruncated||n.source.after.possiblyTruncated,verdict:n.summary.verdict,warnings:n.warnings.join(`; `)}})},...DS.map(t=>({filename:`segment-${t}.csv`,headers:OS,rows:e.segments[t].items.map((n,r)=>({rank:r+1,dimension:t,data_status:e.segments[t].dataStatus,evidence_scope:n.evidenceScope,key:n.key,before_clicks:n.beforeClicks,after_clicks:n.afterClicks,click_delta:n.clickDelta,before_impressions:n.beforeImpressions,after_impressions:n.afterImpressions,impression_delta:n.impressionDelta,before_position:n.beforePosition,after_position:n.afterPosition,position_delta:n.positionDelta}))})),...DS.map(t=>({filename:`segment-${t}-unmatched.csv`,headers:kS,rows:e.segments[t].unmatchedSegments.map((n,r)=>({rank:r+1,dimension:t,data_status:e.segments[t].dataStatus,key:n.key,retained_in:n.retainedIn,clicks:n.clicks,impressions:n.impressions,position:n.position,reason:n.reason}))}))]}const jS={"priorities.csv":[`rank`,`label`,`confidence`,`reason`,`action`],"anomalies.csv":[`metric`,`direction`,`significant`,`baseline_start`,`baseline_end`,`comparison_start`,`comparison_end`,`baseline_mean`,`comparison_mean`,`baseline_total`,`comparison_total`,`percent_change`,`z_score`],"decay.csv":[`rank`,`query`,`url`,`template`,`diagnosis`,`signals`,`evidence_scope`,`click_loss`,`drop_pct`,`previous_clicks`,`current_clicks`,`previous_position`,`current_position`,`action`],"decay-clusters.csv":[`rank`,`template`,`diagnosis`,`findings`,`observed_click_decline`,`previous_clicks`,`average_drop_pct`,`sample_queries`,`sample_urls`,`action`],"cannibalisation.csv":[`rank`,`query`,`owner_url`,`url_count`,`hhi`,`template`,`urls`,`clicks`,`impressions`,`action`],"cannibalisation-suppressed.csv":[`rank`,`query`,`reason`,`url_count`,`template`,`evidence`],"striking-distance.csv":[`rank`,`query`,`url`,`template`,`clicks`,`impressions`,`ctr`,`position`,`opportunity_score`,`coverage`,`action`],"quick-wins.csv":[`rank`,`query`,`url`,`template`,`position`,`clicks`,`impressions`,`ctr`,`target_ctr`,`benchmark_source`,`benchmark_sample_population`,`benchmark_leave_out`,`benchmark_confidence`,`benchmark_peer_rows`,`benchmark_qualified_impressions`,`estimated_ctr_click_shortfall`,`heuristic`,`traffic_forecast`,`coverage`,`verification_status`,`verification_error`,`verification_signals`,`confidence`,`evidence`,`action`],"quick-win-groups.csv":[`rank`,`label`,`query`,`template`,`row_count`,`url_count`,`estimated_ctr_click_shortfall`,`impressions`,`sample_urls`,`action`],"report-summary.csv":[`site`,`generated_at`,`period_start`,`period_end`,`period_days`,`headline`],"report-sections.csv":[`section`,`rank`,`bullet`],"report-caveats.csv":[`rank`,`caveat`],"report-priorities.csv":[`rank`,`title`,`confidence`,`action`],"change-measurements.csv":[`change_id`,`title`,`scope`,`target`,`changed_at`,`verdict`,`confidence`,`data_status`,`error_code`,`error_retryable`,`error_message`,`before_start`,`before_end`,`after_start`,`after_end`,`before_clicks`,`after_clicks`,`click_delta`,`click_pct`,`impression_delta`,`ctr_delta`,`position_delta`,`note`],"monitoring-crawls.csv":[`id`,`site`,`start_url`,`created_at`,`limit`,`url_count`,`status_errors`,`non_indexable`,`recommendations`,`high_priority_recommendations`,`top_recommendation_url`,`top_recommendation_severity`,`top_recommendation_title`,`top_recommendation_action`],"monitoring-index-watch.csv":[`inspected_urls`,`latest_inspected_at`,`non_pass`,`blocked`,`earliest_inspected_at`,`current_issues`,`failed`],"monitoring-link-recover.csv":[`id`,`site`,`created_at`,`start_date`,`end_date`,`days`,`checked`,`recoverable`,`high`,`medium`,`low`,`clicks_at_risk`,`impressions_at_risk`,`top_issue`,`top_url`,`top_action`,`repeated_urls`,`repeated_top_url`],"priority-queue.csv":[`rank`,`source`,`category`,`score`,`impact`,`impact_kind`,`confidence`,`findings`,`title`,`target`,`template`,`template_count`,`ga4_sessions`,`ga4_total_users`,`evidence`,`action`],"priority-score-breakdown.csv":[`rank`,`target`,`source`,`score`,`impact_score`,`source_weight`,`confidence_weight`,`effort_weight`,`verification_weight`,`template_weight`,`analytics_weight`],"priority-grouped-findings.csv":[`queue_rank`,`finding_rank`,`source`,`category`,`score`,`impact`,`impact_kind`,`confidence`,`title`,`target`,`template`,`ga4_sessions`,`evidence`,`action`],"workflow-steps.csv":[`rank`,`tool`,`status`,`summary`],"workflow-actions.csv":[`rank`,`title`,`confidence`,`action`],"warnings.csv":[`rank`,`warning`],"templates.csv":[`rank`,`template`,`verdict`,`confidence`,`urls`,`clicks`,`impressions`,`ctr`,`position`,`entity_fit_impression_share`,`discovered_urls`,`gsc_visible_urls`,`untested_urls`,`crawl_attempts`,`crawled_urls`,`crawl_blocked_or_failed`,`inspection_attempts`,`inspected_urls`,`not_indexed`,`inspection_unknown`,`evidence`,`action`],"pseo-caveats.csv":[`rank`,`caveat`],"template-queries.csv":[`template`,`rank`,`query`,`clicks`,`impressions`,`position`],"demand-patterns.csv":[`template`,`rank`,`label`,`impressions`,`clicks`,`sample_queries`],"sample-coverage.csv":[`template`,`url`,`query`,`classification`,`title_coverage`,`h1_coverage`,`body_coverage`,`missing_terms`],"weak-entity-fit.csv":[`template`,`query`,`url`,`path_terms`],"crawl-samples.csv":[`template`,`url`,`status`,`final_url`,`title`,`meta_description`,`h1`,`word_count`,`technical_status`,`canonical`,`meta_robots`,`x_robots_tag`,`warning`,`blocked`,`rendered`,`cache`],"inspection-samples.csv":[`template`,`url`,`verdict`,`index_status`,`coverage_state`,`indexing_state`,`page_fetch_state`,`google_canonical`,`user_canonical`,`last_crawl_time`,`warning`]};function MS(e){return e.startsWith(`diagnosis-`)?e.slice(10):e}function NS(e){return e.map(e=>({...e,headers:e.headers??jS[MS(e.filename)]}))}function PS(e){return e.startsWith(`theme: `)?`${e.slice(7)}-related`:e===`general`?`broad`:e}function FS(e){if(e==null)return``;let t=String(e);return/[",\n\r]/.test(t)?`"${t.replaceAll(`"`,`""`)}"`:t}function IS(e,t){let n=t??[...new Set(e.flatMap(e=>Object.keys(e)))];return`${[n.map(FS).join(`,`),...e.map(e=>n.map(t=>FS(e[t])).join(`,`))].join(`
503
- `)}\n`}function LS(e){return e.items.map((e,t)=>({rank:t+1,query:e.query,url:e.url,template:e.template.label,position:e.position,clicks:e.clicks,impressions:e.impressions,ctr:e.ctr,target_ctr:e.targetCtr,benchmark_source:e.benchmark.source,benchmark_sample_population:e.benchmark.samplePopulation,benchmark_leave_out:e.benchmark.leaveOut,benchmark_confidence:e.benchmark.confidence,benchmark_peer_rows:e.benchmark.peerRows,benchmark_qualified_impressions:e.benchmark.qualifiedPeerImpressions,estimated_ctr_click_shortfall:e.estimatedCtrClickShortfall,heuristic:e.priority.heuristic,traffic_forecast:e.priority.estimatedClickLift,coverage:e.contentVerification?.classification,verification_status:e.contentVerification?.status,verification_error:e.contentVerification?.error,verification_signals:e.contentVerification?.signals.join(`; `),confidence:e.recommendation.confidence,evidence:e.recommendation.evidenceRef,action:e.recommendation.action}))}function RS(e){return e.changeMeasurementAttempts.map(e=>{let t=e.status===`measured`?e.measurement:void 0;return{change_id:e.change.id,title:e.change.title,scope:e.change.scope,target:e.change.target,changed_at:e.change.changedAt,verdict:t?.verdict,confidence:t?.confidence,data_status:e.dataStatus,error_code:e.status===`failed`?e.error.code:void 0,error_retryable:e.status===`failed`?e.error.retryable:void 0,error_message:e.status===`failed`?e.error.message:void 0,before_start:t?.before.startDate,before_end:t?.before.endDate,after_start:t?.after.startDate,after_end:t?.after.endDate,before_clicks:t?.before.metrics?.clicks,after_clicks:t?.after.metrics?.clicks,click_delta:t?.delta.clicks,click_pct:t?.delta.clickPct,impression_delta:t?.delta.impressions,ctr_delta:t?.delta.ctr,position_delta:t?.delta.position,note:t?.note}})}function zS(e){return NS([{filename:`priorities.csv`,rows:e.priorities.map((e,t)=>({rank:t+1,label:e.label,confidence:e.confidence,reason:e.reason,action:e.action}))},{filename:`anomalies.csv`,rows:e.anomaly.anomalies.map(e=>({metric:e.metric,direction:e.direction,significant:e.significant,baseline_start:e.baselineStart,baseline_end:e.baselineEnd,comparison_start:e.comparisonStart,comparison_end:e.comparisonEnd,baseline_mean:e.baselineMean,comparison_mean:e.comparisonMean,baseline_total:e.baselineTotal,comparison_total:e.comparisonTotal,percent_change:e.percentChange,z_score:e.zScore}))},...AS(e),{filename:`decay.csv`,rows:e.decay.items.map((e,t)=>({rank:t+1,query:e.query,url:e.url,template:e.template.label,diagnosis:e.diagnosis,signals:e.signals.join(`; `),evidence_scope:e.evidenceScope,click_loss:e.clickLoss,drop_pct:e.dropPct,previous_clicks:e.previous.clicks,current_clicks:e.current.clicks,previous_position:e.previous.position,current_position:e.current.position,action:e.recommendation.action}))},{filename:`decay-clusters.csv`,rows:e.decay.groups.map((e,t)=>({rank:t+1,template:e.template.label,diagnosis:e.diagnosis,findings:e.count,observed_click_decline:e.totalClickLoss,previous_clicks:e.totalPreviousClicks,average_drop_pct:e.averageDropPct,sample_queries:e.sampleQueries.join(`; `),sample_urls:e.sampleUrls.join(`; `),action:e.recommendation}))},{filename:`cannibalisation.csv`,rows:e.cannibalization.items.map((e,t)=>({rank:t+1,query:e.query,suggested_owner_url:e.suggestedOwnerUrl,url_count:e.pages.length,hhi:e.hhi,property_impressions:e.propertyImpressions,page_exposure_impressions:e.pageExposureImpressions,observed_page_exposure_ratio:e.observedPageExposureRatio,template:e.template?.label,urls:e.pages.map(e=>e.url).join(`; `),clicks:e.pages.reduce((e,t)=>e+t.clicks,0),review_context:e.reviewContext.join(`; `),action:e.recommendation.action}))},{filename:`cannibalisation-suppressed.csv`,rows:e.cannibalization.suppressed.map((e,t)=>({rank:t+1,query:e.query,reason:e.reason,url_count:e.urlCount,template:e.template?.label,evidence:e.evidenceRef}))},{filename:`striking-distance.csv`,rows:e.strikingDistance.items.map((e,t)=>({rank:t+1,query:e.query,url:e.url,template:e.template.label,clicks:e.clicks,impressions:e.impressions,ctr:e.ctr,position:e.position,priority_method:e.priority.method,priority_score:e.priority.score,estimated_click_lift:e.priority.estimatedClickLift,coverage:e.contentVerification?.classification,action_type:e.recommendation.type,confidence:e.recommendation.confidence,evidence:e.recommendation.evidence,action:e.recommendation.action}))},{filename:`quick-wins.csv`,rows:LS(e.quickWins)},{filename:`quick-win-groups.csv`,rows:e.quickWins.groups.map((e,t)=>({rank:t+1,label:e.label,query:e.query,template:e.template.label,row_count:e.rowCount,url_count:e.urlCount,estimated_ctr_click_shortfall:e.totalEstimatedCtrClickShortfall,impressions:e.totalImpressions,sample_urls:e.sampleUrls.join(`; `),action:e.recommendation}))}])}function BS(e,t){return t.map(t=>({...t,filename:`${e}-${t.filename}`}))}function VS(e){let t=e.monitoring.linkRecover;return NS([{filename:`report-summary.csv`,rows:[{site:e.site,generated_at:e.generatedAt,period_start:e.period.startDate,period_end:e.period.endDate,period_days:e.periodDays,headline:e.headline}]},{filename:`report-sections.csv`,rows:e.sections.flatMap(e=>e.bullets.map((t,n)=>({section:e.title,rank:n+1,bullet:t})))},{filename:`report-caveats.csv`,rows:e.caveats.map((e,t)=>({rank:t+1,caveat:e}))},{filename:`report-priorities.csv`,rows:e.priorities.map((e,t)=>({rank:t+1,title:e.title,confidence:e.confidence,action:e.action}))},{filename:`change-measurements.csv`,rows:RS(e)},{filename:`monitoring-crawls.csv`,rows:e.monitoring.crawlRuns.map(e=>({id:e.id,site:e.site,start_url:e.startUrl,created_at:e.createdAt,limit:e.limit,url_count:e.urlCount,status_errors:e.statusErrors,non_indexable:e.nonIndexable,recommendations:e.recommendations,high_priority_recommendations:e.highPriorityRecommendations,top_recommendation_url:e.topRecommendation?.url,top_recommendation_severity:e.topRecommendation?.severity,top_recommendation_title:e.topRecommendation?.title,top_recommendation_action:e.topRecommendation?.action}))},{filename:`monitoring-index-watch.csv`,rows:[{inspected_urls:e.monitoring.indexWatch.inspectedUrls,latest_inspected_at:e.monitoring.indexWatch.latestInspectedAt,non_pass:e.monitoring.indexWatch.nonPass,blocked:e.monitoring.indexWatch.blocked,earliest_inspected_at:e.monitoring.indexWatch.earliestInspectedAt,current_issues:e.monitoring.indexWatch.currentIssues,failed:e.monitoring.indexWatch.failed}]},{filename:`monitoring-link-recover.csv`,rows:t?[{id:t.id,site:t.site,created_at:t.createdAt,start_date:t.range.startDate,end_date:t.range.endDate,days:t.range.days,checked:t.checked,recoverable:t.recoverable,high:t.high,medium:t.medium,low:t.low,clicks_at_risk:t.clicksAtRisk,impressions_at_risk:t.impressionsAtRisk,top_issue:t.topIssue,top_url:t.topUrl,top_action:t.topAction,repeated_urls:t.repeatedUrls,repeated_top_url:t.repeatedTopUrl}]:[]},...BS(`diagnosis`,zS(e.diagnosis))])}function HS(e){return NS([{filename:`priority-queue.csv`,rows:e.output.queue.map((e,t)=>({rank:t+1,source:e.source,category:e.category,score:e.score,impact:e.impact,impact_kind:e.impactKind,confidence:e.confidence,findings:e.grouped?.count??1,title:e.title,target:e.target,template:e.template?.label,template_count:e.template?.count,ga4_sessions:e.analytics?.sessions,ga4_total_users:e.analytics?.totalUsers,evidence:e.evidence,action:e.action}))},{filename:`priority-score-breakdown.csv`,rows:e.output.queue.map((e,t)=>({rank:t+1,target:e.target,source:e.source,score:e.scoreBreakdown.final,impact_score:e.scoreBreakdown.impact,source_weight:e.scoreBreakdown.source,confidence_weight:e.scoreBreakdown.confidence,effort_weight:e.scoreBreakdown.effort,verification_weight:e.scoreBreakdown.verification,template_weight:e.scoreBreakdown.template,analytics_weight:e.scoreBreakdown.analytics}))},{filename:`priority-grouped-findings.csv`,rows:e.output.queue.flatMap((e,t)=>(e.grouped?.findings??[]).map((e,n)=>({queue_rank:t+1,finding_rank:n+1,source:e.source,category:e.category,score:e.score,impact:e.impact,impact_kind:e.impactKind,confidence:e.confidence,title:e.title,target:e.target,template:e.template?.label,ga4_sessions:e.analytics?.sessions,evidence:e.evidence,action:e.action})))},{filename:`workflow-steps.csv`,rows:e.steps.map((e,t)=>({rank:t+1,tool:e.tool,status:e.status,summary:e.summary}))},{filename:`workflow-actions.csv`,rows:e.actions.map((e,t)=>({rank:t+1,title:e.title,confidence:e.confidence,action:e.action}))},{filename:`warnings.csv`,rows:e.output.warnings.map((e,t)=>({rank:t+1,warning:e}))},...BS(`diagnosis`,zS(e.output.diagnosis))])}function US(e){return NS([{filename:`pseo-caveats.csv`,rows:e.caveats.map((e,t)=>({rank:t+1,caveat:e}))},{filename:`templates.csv`,rows:e.templates.map((e,t)=>({rank:t+1,template:e.signature,verdict:e.verdict,confidence:e.confidence,urls:e.urlCount,clicks:e.metrics.clicks,impressions:e.metrics.impressions,ctr:e.metrics.ctr,position:e.metrics.position,entity_fit_impression_share:e.metrics.entityFit.impressionShare,discovered_urls:e.population.discoveredUrls,gsc_visible_urls:e.population.gscVisibleUrls,untested_urls:e.population.untestedUrls,crawl_attempts:e.crawl.attempted,crawled_urls:e.crawl.usable,crawl_blocked_or_failed:e.crawl.blockedOrFailed,inspection_attempts:e.inspection.attempted,inspected_urls:e.inspection.succeeded,not_indexed:e.inspection.notIndexed,inspection_unknown:e.inspection.unknown,evidence:e.evidence.join(`; `),action:e.recommendation}))},{filename:`template-queries.csv`,rows:e.templates.flatMap(e=>e.metrics.topQueries.map((t,n)=>({template:e.signature,rank:n+1,query:t.query,clicks:t.clicks,impressions:t.impressions,position:t.position})))},{filename:`demand-patterns.csv`,rows:e.templates.flatMap(e=>e.metrics.queryPatterns.map((t,n)=>({template:e.signature,rank:n+1,label:PS(t.label),impressions:t.impressions,clicks:t.clicks,sample_queries:t.examples.join(`; `)})))},{filename:`sample-coverage.csv`,rows:e.templates.flatMap(e=>e.crawl.samples.flatMap(t=>t.queryCoverage?[{template:e.signature,url:t.url,query:t.queryCoverage.query,classification:t.queryCoverage.classification,title_coverage:t.queryCoverage.titleCoverage,h1_coverage:t.queryCoverage.h1Coverage,body_coverage:t.queryCoverage.bodyCoverage,missing_terms:t.queryCoverage.missingTerms.join(`; `)}]:[]))},{filename:`weak-entity-fit.csv`,rows:e.templates.flatMap(e=>e.metrics.entityFit.weakExamples.map(t=>({template:e.signature,query:t.query,url:t.url,path_terms:t.pathTerms.join(`; `)})))},{filename:`crawl-samples.csv`,rows:e.templates.flatMap(e=>e.crawl.samples.map(t=>({template:e.signature,url:t.url,status:t.status,final_url:t.finalUrl,title:t.title,meta_description:t.metaDescription,h1:t.h1,word_count:t.wordCount,technical_status:t.technicalStatus,canonical:t.canonical,meta_robots:t.metaRobots,x_robots_tag:t.xRobotsTag,warning:t.warning,blocked:t.fetchDiagnostics?.blocked,rendered:t.fetchDiagnostics?.rendered,cache:t.fetchDiagnostics?.cache})))},{filename:`inspection-samples.csv`,rows:e.templates.flatMap(e=>e.inspection.samples.map(t=>({template:e.signature,url:t.url,verdict:t.verdict,index_status:t.indexStatus,coverage_state:t.coverageState,indexing_state:t.indexingState,page_fetch_state:t.pageFetchState,google_canonical:t.googleCanonical,user_canonical:t.userCanonical,last_crawl_time:t.lastCrawlTime,warning:t.warning})))}])}const WS={high:5e3,medium:1200,low:100},GS={canonical:`medium`,content:`high`,geo:`medium`,headings:`low`,images:`medium`,indexability:`medium`,international:`low`,metadata:`low`,mobile:`low`,response:`medium`,security:`medium`,social:`low`,"structured-data":`medium`},KS={low:60,medium:30,high:0},qS={high:25,medium:15,low:2},JS={high:1,medium:.75,low:.15};function YS(e,t){if(!e.includes(`*`))return t.includes(e);let n=e.split(`*`),r=0;for(let e of n){if(!e)continue;let n=t.slice(r).indexOf(e);if(n===-1)return!1;r+=n+e.length}return!0}function XS(e,t){return t.projectId&&e.projectId!==t.projectId?[]:e.issues.filter(e=>!(t.category&&e.category!==t.category||t.severity&&e.severity!==t.severity||t.url&&!YS(t.url,e.url)||(t.recommendation??`fix`)!==Ry(e.ruleId)))}function ZS(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)}return[...t.values()]}function QS(e,t){let n=new Set(t),r=e.pages.filter(e=>n.has(e.url)||n.has(e.finalUrl)),i=r.map(e=>e.searchMetrics).filter(e=>!!e),a=i.reduce((e,t)=>e+t.clicks,0),o=i.reduce((e,t)=>e+t.impressions,0),s=r.reduce((e,t)=>e+(t.analytics?.sessions??0),0),c=r.reduce((e,t)=>e+(t.analytics?.totalUsers??0),0),l=r.reduce((e,t)=>e+(t.analytics?.conversions??0),0),u=i.length?i.reduce((e,t)=>e+t.position,0)/i.length:void 0;return{searchVisibleUrls:i.length,clicks:a,impressions:o,sessions:s,totalUsers:c,conversions:l,avgPosition:u}}function $S(e,t,n){return`${e.searchVisibleUrls?`${e.searchVisibleUrls} affected URLs have GSC visibility (${e.clicks} clicks, ${e.impressions} impressions).`:n&&![`joined`,`none`].includes(n)?`No joined GSC visibility is available for these affected URLs; Search Console evidence is ${n}.`:`No affected URL has joined GSC visibility yet.`}${e.sessions?` GA4 adds ${e.sessions} sessions and ${e.conversions} conversions from affected landing pages.`:``} ${t===`review`?`This is a review observation. Confirm it before scheduling implementation work.`:`This ranks as a fix. Severity is ${e.severity}; ${e.affectedUrls} URL${e.affectedUrls===1?` is`:`s are`} affected; estimated effort is ${e.effort}.`}`}function eC(e,t){return`seo crawl ${e.config.url} --severity ${t} --max-pages ${Math.min(e.config.maxPages,100)}`}function tC(e,t={}){return ZS(XS(e,t)).map(t=>{let n=QS(e,t.sampleUrls),r=GS[t.category]??`medium`,i={severity:WS[t.severity],affectedUrls:t.count,searchVisibleUrls:n.searchVisibleUrls,clicks:n.clicks,impressions:n.impressions,sessions:n.sessions,totalUsers:n.totalUsers,conversions:n.conversions,avgPosition:n.avgPosition,effort:r,effortScore:KS[r]},a=JS[t.severity],o=i.severity+i.affectedUrls*qS[t.severity]+i.searchVisibleUrls*100*a+i.clicks*20*a+Math.min(i.impressions,1e4)/25*a+i.sessions*2*a+i.conversions*100*a+i.effortScore,s=Ly(t.ruleId),c=Ry(t.ruleId);return{...t,recommendation:c,score:Math.round(o),scoreFactors:i,whyThisRanks:$S(i,c,e.dataSources?.searchConsole.status),howToFix:s?.howToFix??``,howToVerify:s?.howToVerify??``,verification:{command:eC(e,t.severity),expected:s?.howToVerify??``}}}).sort((e,t)=>t.score-e.score||WS[t.severity]-WS[e.severity]||t.count-e.count||e.ruleId.localeCompare(t.ruleId)).slice(0,t.limit??10)}function nC(e,t={}){return tC(e,{...t,recommendation:`review`})}const rC=[`rule_id`,`title`,`category`,`severity`,`url`,`detail`,`clicks`,`impressions`,`position`],iC=[`url`,`final_url`,`status`,`indexable`,`title`,`meta_description`,`canonical`,`word_count`,`content_extractor`,`content_extractor_type`,`content_extraction_fallback`,`word_count_source`,`internal_links`,`internal_inlinks`,`internal_authority_score`,`external_links`,`images_total`,`images_missing_alt`,`oversized_image_candidates`,`schema_types`,`clicks`,`impressions`,`sessions`];function aC(e){return IS(e.issues.map(e=>({rule_id:e.ruleId,title:e.title,category:e.category,severity:e.severity,url:e.url,detail:e.detail,clicks:e.searchMetrics?.clicks,impressions:e.searchMetrics?.impressions,position:e.searchMetrics?.position})),rC)}function oC(e){return IS(e.pages.map(e=>({url:e.url,final_url:e.finalUrl,status:e.status,indexable:e.indexable,title:e.title,meta_description:e.metaDescription,canonical:e.canonical,word_count:e.wordCount,content_extractor:e.contentExtraction?.used,content_extractor_type:e.contentExtraction?.extractorType,content_extraction_fallback:e.contentExtraction?.fallback,word_count_source:e.contentExtraction?.wordCountSource,internal_links:e.outgoingInternalCount,internal_inlinks:e.internalInlinkCount,internal_authority_score:e.internalLinkAuthorityScore,external_links:e.outgoingExternalCount,images_total:e.imagesTotal,images_missing_alt:e.imagesMissingAlt,oversized_image_candidates:e.oversizedImageCandidates?.length??0,schema_types:e.schemaTypes?.join(`|`),clicks:e.searchMetrics?.clicks,impressions:e.searchMetrics?.impressions,sessions:e.analytics?.sessions})),iC)}function sC(e,t=tC(e)){let n=nC(e),r=e.requestEvidenceStatus===`available`?`${e.requests.length} requests`:e.requestEvidenceStatus===`partial`?`${e.requests.length} observed requests; some started requests were still in flight when the crawl stopped`:`request evidence unavailable for this legacy report`,i=[`Crawl report for ${e.config.url}`,``,`Status: ${e.status}`,`Documents: ${e.summary.totalPages} retained; ${r}, ${e.summary.discoveredUrls} URLs discovered, ${e.summary.failedUrls} failed, ${e.summary.skippedUrls} skipped`,`Issues: ${e.issues.length} (${e.summary.highIssues} high, ${e.summary.mediumIssues} medium, ${e.summary.lowIssues} low)`];if(e.externalLinkVerification){let t=e.externalLinkVerification;i.push(`External links: ${t.fetchedUrls} fetched, ${t.failedUrls} request failure${t.failedUrls===1?``:`s`}, ${t.deferredUrls} deferred (${t.dataStatus})`)}if(t.length){i.push(``,`Prioritised fixes`);for(let e of t.slice(0,5))i.push(`- ${e.title} (${e.severity}, ${e.count} URL${e.count===1?``:`s`}): ${e.howToFix}`),e.sampleUrls[0]&&i.push(` First URL: ${e.sampleUrls[0]}`)}else i.push(``,`No prioritised fixes found.`);if(n.length){i.push(``,`Review observations (check before scheduling work)`);for(let e of n.slice(0,5))i.push(`- ${e.title} (${e.severity}, ${e.count} URL${e.count===1?``:`s`}): ${e.howToFix}`),e.sampleUrls[0]&&i.push(` First URL: ${e.sampleUrls[0]}`)}i.push(``,`Next commands`,`- seo crawl ${e.config.url} --json`);for(let e of[...new Set(t.slice(0,3).map(e=>e.verification.command))])i.push(`- ${e}`);return e.warnings.length&&i.push(``,`Warnings`,...e.warnings.map(e=>`- ${e}`)),e.caveats.length&&i.push(``,`Caveats`,...e.caveats.map(e=>`- ${e}`)),`${i.join(`
504
- `)}\n`}function cC(e,t=tC(e)){let n=nC(e),r=e.issueGroups.map(e=>{let t=Ly(e.ruleId);return`<tr><td>${uC(e.severity)}</td><td>${uC(e.title)}</td><td>${e.count}</td><td>${uC(t?.howToFix??``)}</td><td>${uC(e.sampleUrls[0]??``)}</td></tr>`}).join(`
505
- `),i=t.slice(0,10).map(e=>`<li><strong>${uC(e.title)}</strong><br>${uC(e.howToFix)}<br><span>Verify: ${uC(e.howToVerify)}</span><br><code>${uC(e.verification.command)}</code></li>`).join(`
506
- `),a=n.slice(0,10).map(e=>`<li><strong>${uC(e.title)}</strong><br>${uC(e.howToFix)}<br><span>Check before scheduling work. ${uC(e.howToVerify)}</span></li>`).join(`
507
- `),o=e.caveats.map(e=>`<li>${uC(e)}</li>`).join(`
508
- `),s=e.warnings.map(e=>`<li>${uC(e)}</li>`).join(`
503
+ LIMIT 1`).get();if(t)return yx(t.report_json)}const Tx={save:bx,list:xx,load:Sx,delete:Cx,latest:wx};function Ex(e,t=Tx){return t.save(e)}function Dx(e={},t=Tx){return t.list(e)}function Ox(e,t=Tx){return t.load(e)}function kx(e,t=Tx){return t.delete(e)}function Ax(e,t=Tx){return t.latest(e)}function jx(e){let t=e.cancelled?[`Crawl cancelled before all queued URLs finished.`]:e.pageLimitReached?[`Stopped after reaching maxPages (${e.maxPages}).`]:[];return e.queueSafetySkippedUrls>0&&t.push(`Left ${e.queueSafetySkippedUrls} eligible same-origin URLs unqueued to keep this crawl bounded. Increase --max-pages to inspect more.`),e.originBackpressureSkippedUrls>0&&t.push(`Stopped ${e.originBackpressureSkippedUrls} queued URL${e.originBackpressureSkippedUrls===1?``:`s`} because the origin stayed slow. This is incomplete crawl evidence, not a failed URL check.`),t}const Mx=Symbol(`crawl-cancelled`);function Nx(e){let t=new AbortController,n=()=>t.abort(),r=setTimeout(n,Math.min(e.timeoutMs,5e3));return e.signal?.aborted?t.abort():e.signal?.addEventListener(`abort`,n,{once:!0}),{signal:t.signal,cleanup:()=>{clearTimeout(r),e.signal?.removeEventListener(`abort`,n)}}}function Px(e){return e.aborted?Promise.resolve(Mx):new Promise(t=>{e.addEventListener(`abort`,()=>t(Mx),{once:!0})})}function Fx(e){let t=2166136261;for(let n of e)t=Math.imul(t^(n.codePointAt(0)??0),16777619);return t>>>0}function Ix(e,t){let n=Fx(e),r=Fx(t);return n===r?e<t?-1:+(e>t):n-r}function Lx(e){let t=new Map,n=0,r=0;for(let i of e){let e=[...new Set(i.sampleExternalLinks??[])];n+=i.outgoingExternalCount??e.length,r+=e.length;for(let n of e){let e=t.get(n);e||(e=new Set,t.set(n,e)),e.add(i.url)}}return{candidates:[...t].map(([e,t])=>({url:e,sourcePages:[...t].sort()})).sort((e,t)=>Ix(e.url,t.url)),discoveredLinkOccurrences:n,retainedLinkOccurrences:r}}function Rx(e,t){let n=new Map;for(let t of e)for(let e of t.sourcePages){let r=n.get(e)??[];r.push(t.url),n.set(e,r)}for(let e of n.values())e.sort(Ix);let r=[...n.keys()].sort(Ix),i=new Map(r.map(e=>[e,0])),a=new Set;for(;a.size<t;){let e=!1;for(let o of r){let r=n.get(o)??[],s=i.get(o)??0;for(;s<r.length;){let e=r[s];if(!e||!a.has(e))break;s+=1}i.set(o,s+1);let c=r[s];if(c&&(a.add(c),e=!0,a.size===t))break}if(!e)break}return[...a]}async function zx(e,t,n,r){let i=Nx({timeoutMs:t,signal:r});try{let t=await n(e,{method:`HEAD`,redirect:`follow`,signal:i.signal});return await t.body?.cancel().catch(()=>void 0),{url:e,status:t.status}}catch(t){return{url:e,status:0,error:t instanceof Error?t.message:String(t)}}finally{i.cleanup()}}async function Bx(e){let{candidates:t,discoveredLinkOccurrences:n,retainedLinkOccurrences:r}=Lx(e.pages),i=e.signal?.aborted?[]:Rx(t,200),a=new Map;for(let t=0;t<i.length&&!e.signal?.aborted;t+=8){let n=i.slice(t,t+8),r=await Promise.all(n.map(t=>zx(t,e.timeoutMs,e.fetch,e.signal)));for(let e of r)a.set(e.url,e)}for(let t of e.pages){let e=(t.sampleExternalLinks??[]).map(e=>a.get(e)).filter(e=>!!e);e.length&&(t.externalLinkChecks=e)}let o=[...a.values()].filter(e=>typeof e.status==`number`&&e.status>0).length,s=a.size-o,c=t.length-a.size,l=[];return n>r&&l.push(`Retained ${r} sampled external link occurrence${r===1?``:`s`} from ${n} observed occurrence${n===1?``:`s`}.`),t.length>i.length&&l.push(`Selected ${i.length} of ${t.length} retained external URLs using the 200-URL verification limit.`),a.size<i.length&&l.push(`Stopped before checking ${i.length-a.size} selected external URL${i.length-a.size===1?``:`s`}.`),s&&l.push(`${s} external URL request${s===1?``:`s`} failed before a response was received.`),{dataStatus:n>r||c>0||s>0?`partial`:`complete`,discoveredLinkOccurrences:n,retainedUrls:t.length,selectedUrls:i.length,fetchedUrls:o,failedUrls:s,deferredUrls:c,limit:200,warnings:l}}function Vx(e,t){let n=new Set,r=e;for(;!n.has(r);){n.add(r);let e=t.get(r);if(!e)return r;r=e}return r}function Hx(e,t){let n={};for(let[r,i]of Object.entries(e)){let e=Vx(r,t),a=n[e];a||(a=new Set,n[e]=a);for(let e of i)a.add(Vx(e,t))}return Object.fromEntries(Object.entries(n).map(([e,t])=>[e,[...t].sort((e,t)=>e<t?-1:+(e>t))]))}function Ux(e,t){let n={requestedUrl:e,outcome:`response`,finalUrl:t.finalUrl,status:t.status,contentType:t.contentType,durationMs:t.responseTimeMs,redirectChain:t.fetchDiagnostics?.redirectChain};return t.extractionStatus===`failed`?{...n,extraction:`failed`,extractionError:t.extractionError??`Content extraction failed without a recorded error.`}:{...n,extraction:t.extractionStatus??`complete`}}function Wx(e){return{...G_(28,e),days:28}}async function Gx(e){let t;try{t=await e.fetchLandingPageValues({propertyId:e.propertyId,startDate:e.window.startDate,endDate:e.window.endDate,limit:e.limit})}catch(t){let n=`GA4 metrics unavailable: ${t instanceof Error?t.message:String(t)}`;return e.warnings.push(n),{status:`unavailable`,window:e.window,totalPages:e.pages.length,queriedPages:e.pages.length,joinedPages:0,retainedRowLimit:e.limit,warning:n}}if(t.warning){let n=`GA4 metrics unavailable: ${t.warning}`;return e.warnings.push(n),{status:`unavailable`,window:e.window,totalPages:e.pages.length,queriedPages:e.pages.length,joinedPages:0,returnedRows:t.source?.returnedRows,availableRows:t.source?.availableRows,retainedRowLimit:t.source?.retainedRowLimit??e.limit,...t.source?{retainedRowLimitReached:t.source.retainedRowLimitReached}:{},warning:n}}let n=0;for(let r of e.pages){let i=e.landingValueForUrl(t.values,r.finalUrl);i&&(r.analytics=i,n+=1)}let r=t.source?.dataStatus!==void 0,i=t.source?.retainedRowLimitReached,a=t.source?.qualityWarnings??[],o=r?i?`GA4 retained-row limit reached; missing page metrics are not reliable zero-traffic evidence.`:a.length?`${a.join(` `)} Missing page metrics and relative analytics value are partial evidence.`:e.pages.length&&n===0?`GA4 metrics joined for 0 crawled pages.`:void 0:`GA4 metrics joined for ${n} of ${e.pages.length} crawled pages, but the provider did not expose row completeness; missing page metrics are not reliable zero-traffic evidence.`;return o&&e.warnings.push(o),{status:!r||t.source?.dataStatus===`partial`?`partial`:n?`joined`:`none`,window:e.window,totalPages:e.pages.length,queriedPages:e.pages.length,joinedPages:n,returnedRows:t.source?.returnedRows,availableRows:t.source?.availableRows,retainedRowLimit:t.source?.retainedRowLimit??e.limit,...i===void 0?{}:{retainedRowLimitReached:i},...o?{warning:o}:{}}}async function Kx(e){let t=[...e.pages].sort((e,t)=>e.finalUrl<t.finalUrl?-1:e.finalUrl>t.finalUrl?1:e.url<t.url?-1:+(e.url>t.url)).slice(0,e.limit),n=t.length<e.pages.length,r={startDate:e.window.startDate,endDate:e.window.endDate},i=0,a=0,o,s,c=!!(e.queryPagesMetrics&&!e.queryPagesMetricsBatch||e.queryPagesTopQueries&&!e.queryPagesTopQueriesBatch),l=!!(e.queryPagesMetricsBatch||e.queryPagesTopQueriesBatch),u=!!(e.queryPagesMetricsBatch||e.queryPagesMetrics),d=!!(e.queryPagesTopQueriesBatch||e.queryPagesTopQueries),f=(u||d)&&u!==d,p=[];try{if(e.queryPagesMetricsBatch||e.queryPagesTopQueriesBatch||e.queryPagesMetrics||e.queryPagesTopQueries){let n=t.map(e=>e.finalUrl);try{o=e.queryPagesMetricsBatch?await e.queryPagesMetricsBatch(e.site,n,r):e.queryPagesMetrics?{values:await e.queryPagesMetrics(e.site,n,e.window.days)}:void 0}catch(e){p.push(`page metrics failed: ${e instanceof Error?e.message:String(e)}`)}try{s=e.queryPagesTopQueriesBatch?await e.queryPagesTopQueriesBatch(e.site,n,r):e.queryPagesTopQueries?{values:await e.queryPagesTopQueries(e.site,n,e.window.days)}:void 0}catch(e){p.push(`top queries failed: ${e instanceof Error?e.message:String(e)}`)}for(let e of t){let t=o?.values.get(e.finalUrl);t&&(e.searchMetrics=t,i+=1);let n=s?.values.get(e.finalUrl);n&&(e.topQuery=n,a+=1)}}else for(let n of t){let t=await e.queryPageMetrics(e.site,n.finalUrl,e.window.days);t&&(n.searchMetrics=t,i+=1);let r=await e.queryPageTopQuery(e.site,n.finalUrl,e.window.days);r&&(n.topQuery=r,a+=1)}}catch(r){let c=`GSC metrics unavailable: ${r instanceof Error?r.message:String(r)}`;return e.warnings.push(c),{status:i||a?`partial`:`unavailable`,...l?{window:e.window}:{},totalPages:e.pages.length,queriedPages:t.length,joinedMetricPages:i,joinedQueryPages:a,pageLimit:e.limit,pageLimitReached:n,metricRowsReturned:o?.returnedRows,queryRowsReturned:s?.returnedRows,retainedRowLimit:o?.retainedRowLimit??s?.retainedRowLimit,warning:c}}if(p.length>0){let r=`GSC metrics partially unavailable: ${p.join(`; `)}`;return e.warnings.push(r),{status:i||a?`partial`:`unavailable`,...l?{window:e.window}:{},totalPages:e.pages.length,queriedPages:t.length,joinedMetricPages:i,joinedQueryPages:a,pageLimit:e.limit,pageLimitReached:n,metricRowsReturned:o?.returnedRows,queryRowsReturned:s?.returnedRows,retainedRowLimit:o?.retainedRowLimit??s?.retainedRowLimit,warning:r}}let m=!!(o?.retainedRowLimitReached||s?.retainedRowLimitReached),h=t.filter(e=>e.searchMetrics||e.topQuery).length,g=n||m||c||f||!l,_=f?`GSC provider did not supply both page metrics and top-query evidence; missing evidence is not a reliable zero.`:c&&!l?`GSC legacy provider did not expose retained-row completeness or an exact date window; missing evidence is not a reliable zero.`:c?`GSC provider did not expose retained-row completeness; missing page metrics and queries are not reliable zero-visibility evidence.`:l?m?`GSC retained-row limit reached; missing page metrics and queries are not reliable zero-visibility evidence.`:n?`GSC metrics queried for ${t.length} of ${e.pages.length} crawled pages because the page limit was reached.`:h<t.length?`GSC metrics joined for ${h} of ${t.length} crawled pages.`:void 0:`GSC metrics joined for ${h} of ${t.length} crawled pages through a legacy relative-day provider; exact queried dates are not verifiable.`;return _&&e.warnings.push(_),{status:g?`partial`:i||a?`joined`:`none`,...l?{window:e.window}:{},totalPages:e.pages.length,queriedPages:t.length,joinedMetricPages:i,joinedQueryPages:a,pageLimit:e.limit,pageLimitReached:n,metricRowsReturned:o?.returnedRows,queryRowsReturned:s?.returnedRows,retainedRowLimit:o?.retainedRowLimit??s?.retainedRowLimit,...c?{}:{retainedRowLimitReached:m},..._?{warning:_}:{}}}function qx(e){let t=(e,t,n)=>{let r=e??t;if(!Number.isInteger(r)||r<1)throw Error(`${n} must be a positive whole number.`);return r};return{searchMetricsLimit:t(e.searchMetricsLimit,5e3,`searchMetricsLimit`),analyticsLimit:t(e.analyticsLimit,5e3,`analyticsLimit`)}}async function Jx(e){let t=Wx(e.now()),n={searchConsole:{status:`skipped`,totalPages:e.pages.length,queriedPages:0,joinedMetricPages:0,joinedQueryPages:0,pageLimit:e.searchMetricsLimit,pageLimitReached:!1,retainedRowLimitReached:!1,warning:e.cancelled?`Search Console join skipped because the crawl was cancelled.`:`Search Console join skipped because no property was selected.`},analytics:{status:`skipped`,totalPages:e.pages.length,queriedPages:0,joinedPages:0,retainedRowLimit:e.analyticsLimit,retainedRowLimitReached:!1,warning:e.cancelled?`GA4 join skipped because the crawl was cancelled.`:`GA4 join skipped because no property was selected.`}};return!e.cancelled&&e.site&&(n.searchConsole=await Kx({site:e.site,pages:e.pages,warnings:e.warnings,limit:e.searchMetricsLimit,window:t,queryPageMetrics:e.queryPageMetrics,queryPageTopQuery:e.queryPageTopQuery,queryPagesMetrics:e.queryPagesMetrics,queryPagesTopQueries:e.queryPagesTopQueries,queryPagesMetricsBatch:e.queryPagesMetricsBatch,queryPagesTopQueriesBatch:e.queryPagesTopQueriesBatch})),!e.cancelled&&e.ga4PropertyId&&(n.analytics=await Gx({propertyId:e.ga4PropertyId,pages:e.pages,warnings:e.warnings,limit:e.analyticsLimit,window:t,fetchLandingPageValues:e.fetchLandingPageValues,landingValueForUrl:e.landingValueForUrl})),n}function Yx(e,t){try{let n=new URL(e,t);return n.hash=``,n.origin!==t.origin||![`http:`,`https:`].includes(n.protocol)?void 0:n.toString()}catch{return}}function Xx(e,t){if(e)try{let n=new URL(e,t);return[`http:`,`https:`].includes(n.protocol)?n.toString():void 0}catch{return}}function Zx(e){return _(`sha256`).update(e).digest(`hex`)}function Qx(e,t){let n=t.toLowerCase();return e[t]??e[n]??Object.entries(e).find(([e])=>e.toLowerCase()===n)?.[1]}function $x(e){let t=new Set([`authorization`,`cookie`,`proxy-authorization`,`set-cookie`,`www-authenticate`]);return Object.fromEntries(Object.entries(e).filter(([e])=>!t.has(e.toLowerCase())))}function eS(e,t){return e.length>t?e.slice(0,t).trimEnd():e}function tS(e,t){let n=new Set,r=[];for(let i of e){if(i.internal!==t)continue;let e=eS(i.text.replace(/\s+/g,` `).trim(),120);if(!e)continue;let a=`${i.href}\n${e}`;if(!n.has(a)&&(n.add(a),r.push({href:i.href,text:e}),r.length>=25))break}return r}function nS(e){let t=[`facebook.com`,`instagram.com`,`linkedin.com`,`pinterest.com`,`tiktok.com`,`twitter.com`,`x.com`,`youtube.com`],n=new Set;for(let r of e)if(!r.internal)try{let e=new URL(r.href),i=e.hostname.replace(/^www\./,``).toLowerCase();t.some(e=>i===e||i.endsWith(`.${e}`))&&(e.hash=``,n.add(e.toString()))}catch{}return[...n].slice(0,50)}function rS(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 iS(e){return/^\s*(?:text\/html|application\/xhtml\+xml)\b/i.test(e??``)}function aS(e,t){let n=e,r=String(n?.code??n?.cause?.code??``).toUpperCase(),i=e instanceof Error?e.message:String(e);return t?.aborted?`aborted`:/TOO MANY REDIRECTS/i.test(i)?`redirect-limit`:[`ENOTFOUND`,`EAI_AGAIN`,`EAI_FAIL`].includes(r)?`dns`:r.includes(`CERT`)||r.includes(`TLS`)||r.includes(`SSL`)||/CERTIFICATE|\bTLS\b|\bSSL\b/i.test(i)?`tls`:r===`ETIMEDOUT`||/TIMED?\s*OUT|TIMEOUT|ABORT(?:ED|ERROR)/i.test(i)?`timeout`:`unknown`}function oS(e,t,n,r){let i={requestedUrl:e,outcome:`response`,finalUrl:t.finalUrl,status:t.status,contentType:Qx(t.headers,`content-type`),durationMs:t.diagnostics.durationMs,redirectChain:t.diagnostics.redirectChain};if(n===`failed`){if(!r)throw Error(`Failed extraction observations require an error.`);return{...i,extraction:n,extractionError:r}}return{...i,extraction:n}}function sS(e,t){let n=Qx(e.headers,`x-robots-tag`),r=mm(e.headers,e.finalUrl),i=nm(n,`noindex`),a=!!t;return{url:e.finalUrl,finalUrl:e.finalUrl,status:e.status,contentType:t,responseHeaders:$x(e.headers),responseTimeMs:e.diagnostics.durationMs,sizeBytes:Buffer.byteLength(e.html),usedJs:e.usedJs,fetchSource:e.diagnostics.source,cacheState:e.diagnostics.cache,fetchDiagnostics:e.diagnostics,blocked:e.diagnostics.blocked,robotsTxt:e.robotsTxt,xRobotsTag:n,canonical:r.selectedUrl,canonicalRaw:r.selectedRaw??r.candidates[0]?.raw,canonicalStatus:r.status,canonicalCandidates:r.candidates,indexable:e.status>=200&&e.status<300&&!i,indexability:i?`X-Robots-Tag noindex`:a?`Non-HTML response`:`Unknown response media type`,declaredIndexability:i?`noindex`:a?`not-html`:`unknown`,extractionStatus:a?`not-applicable`:`unknown-media-type`,wordCount:0,contentHash:Zx(e.html),outgoingInternalCount:0,outgoingExternalCount:0,isHttps:new URL(e.finalUrl).protocol===`https:`,hasHsts:!!Qx(e.headers,`strict-transport-security`),compression:Qx(e.headers,`content-encoding`)}}function cS(e){if(e.status<200||e.status>=300)return`Status ${e.status}`;if(tm(e.metaRobots,`noindex`))return`Meta robots noindex`;if(nm(e.xRobotsTag,`noindex`))return`X-Robots-Tag noindex`}async function lS(e,t={}){let n=Date.now(),r;try{r=await Ag(e,t)}catch(r){if(r instanceof jh)return{request:{requestedUrl:e,outcome:`skipped`,reason:`origin-backpressure`,error:r.message,extraction:`not-applicable`},urls:[]};if(r instanceof Wh)return{request:{requestedUrl:e,outcome:`skipped`,reason:r.reason,robotsTxt:{url:r.evidence.url,allowed:r.evidence.allowed,availability:r.evidence.availability,status:r.evidence.status,error:r.evidence.error,matchedLine:r.evidence.matchedLine},extraction:`not-applicable`},urls:[]};let i=r instanceof Error?r.message:String(r);return{request:{requestedUrl:e,outcome:`failure`,durationMs:Date.now()-n,failureKind:aS(r,t.signal),error:i,extraction:`not-applicable`},urls:[],warning:`${e}: ${i}`}}let i=Qx(r.headers,`content-type`);if(!iS(i))return{request:oS(e,r,i?`not-applicable`:`unknown-media-type`),page:sS(r,i),urls:[]};try{let t=await Th(r),n=new URL(t.finalUrl),a=t.links.map(e=>Yx(e.href,n)).filter(e=>!!e),o=t.links.filter(e=>!e.internal).map(e=>e.href).filter(e=>/^https?:\/\//.test(e)),s=t.headings.find(e=>e.level===1)?.text,c=[...new Set(a)],l=[...new Set(o)],u=t.canonicalEvidence?.selectedUrl??Xx(t.canonical,t.finalUrl),d=ah(t.googleRichResults??[]),f={url:t.finalUrl,finalUrl:t.finalUrl,status:r.status,contentType:i,responseHeaders:$x(r.headers),responseTimeMs:r.diagnostics.durationMs,sizeBytes:Buffer.byteLength(r.html),usedJs:r.usedJs,fetchSource:r.diagnostics.source,cacheState:r.diagnostics.cache,fetchDiagnostics:r.diagnostics,blocked:r.diagnostics.blocked,robotsTxt:r.robotsTxt,title:t.title,metaDescription:t.metaDescription,canonical:u,canonicalRaw:t.canonicalEvidence?.selectedRaw??t.canonicalEvidence?.candidates[0]?.raw??t.canonical,canonicalStatus:t.canonicalEvidence?.status,canonicalCandidates:t.canonicalEvidence?.candidates,metaRobots:t.metaRobots,xRobotsTag:t.xRobotsTag,h1:s,h1Count:t.headings.filter(e=>e.level===1).length,h2Count:t.headings.filter(e=>e.level===2).length,h3Count:t.headings.filter(e=>e.level===3).length,indexable:!1,extractionStatus:`complete`,wordCount:t.wordCount,contentExtraction:t.contentExtraction,warnings:[...t.warnings,...t.canonicalEvidence?.status===`invalid`||t.canonical&&!u?[`Ignored a malformed canonical URL.`]:[],...t.canonicalEvidence?.status===`conflicting`?[`Found conflicting canonical declarations.`]:[],...t.canonicalEvidence?.status===`outside-head-only`?[`Ignored canonical link elements outside the document head because Google does not accept them.`]:[]],contentHash:Zx([t.title,t.metaDescription,s,t.canonical,t.contentText].join(`
504
+ `)),mainContentHash:Zx(t.contentText),textRatio:r.html.length?Math.min(1,t.contentText.length/r.html.length):void 0,contentSample:eS(t.contentText,300),lang:t.lang,hasViewport:t.hasViewport,isHttps:new URL(t.finalUrl).protocol===`https:`,hasHsts:!!Qx(r.headers,`strict-transport-security`),compression:Qx(r.headers,`content-encoding`),hreflang:t.hreflang,mixedContentCount:t.mixedContentUrls.length,mixedContentSamples:t.mixedContentUrls.slice(0,25),imagesTotal:t.imagesTotal,imagesMissingAlt:t.imagesMissingAlt,oversizedImageCandidates:t.oversizedImageCandidates,outgoingInternalCount:c.length,outgoingExternalCount:l.length,sampleInternalLinks:c.slice(0,25),sampleExternalLinks:l.slice(0,25),internalAnchorSamples:tS(t.links,!0),externalAnchorSamples:tS(t.links,!1),schemaTypes:t.schemaTypes,structuredDataFormats:t.structuredDataFormats??[],googleRichResults:d.assessments,googleRichResultsSelection:d.selection,schemaSameAs:[...new Set((t.schemaSameAsEvidence??[]).map(e=>e.url))].slice(0,50),schemaSameAsEvidence:(t.schemaSameAsEvidence??[]).slice(0,50),invalidSchemaSameAs:(t.invalidSchemaSameAs??[]).slice(0,25),socialProfileLinks:nS(t.links),invalidJsonLdCount:t.invalidJsonLdCount,invalidJsonLdSamples:t.invalidJsonLdSamples,unrecognizedJsonLdTypes:t.unrecognizedJsonLdTypes??[],openGraphTitle:t.openGraph[`og:title`],openGraphDescription:t.openGraph[`og:description`],openGraphImage:t.openGraph[`og:image`],twitterCard:t.twitter[`twitter:card`],author:t.author,hasDate:t.hasDate,geo:{semanticHtml:t.semanticHtml,structuredData:(t.structuredDataFormats?.length??0)>0,hasAuthor:t.hasAuthor,hasDate:t.hasDate,questionHeadings:t.questionHeadings,listCount:t.listCount,tableCount:t.tableCount,structuredBlocks:t.structuredBlocks,answerable:t.answerable,hasFaqSchema:t.schemaTypes.includes(`FAQPage`),hasQapageSchema:t.schemaTypes.includes(`QAPage`)}};return f.indexability=cS(f),f.indexable=!f.indexability,f.declaredIndexability=tm(f.metaRobots,`noindex`)||nm(f.xRobotsTag,`noindex`)?`noindex`:f.robotsTxt?.allowed===!1?`robots-blocked`:f.canonicalStatus===`conflicting`?`canonical-conflict`:f.canonical&&!rS(f.canonical,f.finalUrl)?`canonical-hint-other`:`indexable-candidate`,{request:oS(e,r,`complete`),page:f,urls:c}}catch(t){let n=t instanceof Error?t.message:String(t);return{request:oS(e,r,`failed`,n),urls:[],warning:`${e}: content extraction failed: ${n}`}}}const uS=52428800,dS=/^(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.\d+)?)?(Z|[+-]\d{2}:\d{2}))?$/;function fS(e){try{let t=new URL(e.trim());return[`http:`,`https:`].includes(t.protocol)?t.toString():void 0}catch{return}}function pS(e,t){if(!Number.isSafeInteger(e)||e<1)throw Error(`${t} must be a positive integer.`);return e}function mS(e){let t=dS.exec(e);if(!t)return;let[,n,r,i,a,o,s,c]=t,l=Number(n),u=Number(r),d=Number(i),f=a===void 0?0:Number(a),p=o===void 0?0:Number(o),m=s===void 0?0:Number(s),h=u===2?l%4==0&&(l%100!=0||l%400==0)?29:28:[4,6,9,11].includes(u)?30:31,g=c?.match(/^([+-])(\d{2}):(\d{2})$/);if(u<1||u>12||d<1||d>h||f>23||p>59||m>59||g&&(Number(g[2])>23||Number(g[3])>59))return;let _=a?new Date(e):new Date(`${e}T00:00:00Z`);return Number.isNaN(_.getTime())?void 0:_}function hS(e){return e[0]===31&&e[1]===139}function gS(e,t){if(!e)return!0;let n=e.split(`;`,1)[0]?.trim().toLowerCase();return!!(n&&(t===`gzip`?n===`application/gzip`||n===`application/x-gzip`:n===`application/xml`||n===`text/xml`||n.endsWith(`+xml`)))}async function _S(e){let t=Number(e.headers.get(`content-length`));if(Number.isFinite(t)&&t>uS)throw Error(`The response exceeds the ${uS}-byte sitemap limit.`);let n=e.body?.getReader();if(!n)return new Uint8Array;let r=[],i=0;try{for(;;){let{done:e,value:t}=await n.read();if(e)break;if(i+=t.byteLength,i>uS)throw await n.cancel(),Error(`The response exceeds the ${uS}-byte sitemap limit.`);r.push(t)}}finally{n.releaseLock()}let a=new Uint8Array(i),o=0;for(let e of r)a.set(e,o),o+=e.byteLength;return a}function vS(e){let t=[],n,r=0;for(;r<e.length;){let i=e.indexOf(`<`,r);if(i===-1)break;if(e.startsWith(`<!--`,i)){let t=e.indexOf(`-->`,i+4);if(t===-1)return{error:`an unclosed XML comment`};r=t+3;continue}if(e.startsWith(`<![CDATA[`,i)){let t=e.indexOf(`]]>`,i+9);if(t===-1)return{error:`an unclosed CDATA section`};r=t+3;continue}if(e.startsWith(`<?`,i)){let t=e.indexOf(`?>`,i+2);if(t===-1)return{error:`an unclosed processing instruction`};r=t+2;continue}let a=i+1,o;for(;a<e.length;){let t=e[a];if(o)t===o&&(o=void 0);else if(t===`"`||t===`'`)o=t;else if(t===`>`)break;a+=1}if(a>=e.length||o)return{error:`an unclosed XML tag`};let s=e.slice(i+1,a).trim();if(r=a+1,!s||s.startsWith(`!`))continue;if(s.startsWith(`/`)){let e=s.slice(1).trim(),n=t.pop();if(!n||e!==n)return{error:`a mismatched closing tag </${e}>`};continue}let c=s.match(/^([A-Za-z_][A-Za-z0-9_.:-]*)/);if(!c?.[1])return{error:`an invalid XML tag name`};if(t.length===0){if(n)return{error:`more than one XML root element`};n=c[1]}/\/$/.test(s)||t.push(c[1])}return t.length>0?{error:`an unclosed <${t.at(-1)}> tag`}:n===`urlset`||n===`sitemapindex`?{root:n}:{error:`expected a <urlset> or <sitemapindex> root${n?`, found <${n}>`:``}`}}async function yS(e){try{let t=await Oh(e,{profile:`browser`}),n=t.headers.get(`content-type`)??void 0,r=fS(t.url),i=t.redirected&&r?{finalUrl:r,redirected:!0}:{};if(!t.ok)return{document:{url:e,...i,dataStatus:`unavailable`,status:t.status,contentType:n,compression:`none`,warning:`Sitemap fetch failed for ${e}: HTTP ${t.status}.`}};let a;try{a=await _S(t)}catch(r){return{document:{url:e,...i,dataStatus:`partial`,status:t.status,contentType:n,compression:`none`,warning:`Could not read sitemap data: ${r instanceof Error?r.message:String(r)}`}}}let o=hS(a)?`gzip`:`none`,s=a;if(o===`gzip`)try{s=ae(a,{maxOutputLength:uS})}catch(r){return{document:{url:e,...i,dataStatus:`partial`,status:t.status,contentType:n,compression:o,bytes:a.byteLength,warning:`Could not decompress sitemap gzip data: ${r instanceof Error?r.message:String(r)}`}}}let c=new TextDecoder().decode(s),l=vS(c),u=gS(n,o)?void 0:`Sitemap returned content type ${n??`unknown`}, not an XML type.`;return`error`in l?{document:{url:e,...i,dataStatus:`partial`,status:t.status,contentType:n,compression:o,bytes:a.byteLength,uncompressedBytes:s.byteLength,warning:`Sitemap XML is invalid: ${l.error}.`}}:{document:{url:e,...i,dataStatus:u?`partial`:`complete`,status:t.status,contentType:n,compression:o,bytes:a.byteLength,uncompressedBytes:s.byteLength,root:l.root,...u?{warning:u}:{}},xml:c}}catch(t){return{document:{url:e,dataStatus:`unavailable`,compression:`none`,warning:`Sitemap fetch failed for ${e}: ${t instanceof Error?t.message:String(t)}`}}}}function bS(e,t){let n=pS(t,`limit`),r=[...new Set(e.flatMap(e=>e.urls))],i=e.some(e=>e.truncation.possiblyTruncated),a=r.length>n;return{urls:r.slice(0,n),truncation:{possiblyTruncated:i||a,sourceTruncated:i,inventoryLimitExceeded:a,omittedUrlsAtLeast:Math.max(0,r.length-n),limit:n}}}async function xS(e){let t=[],n=[],r=new Set,i=[],a=new Set,o=new Set,s=[],c=[],l=[],u=[],d=[],f=pS(e.limit??5e4,`limit`),p=pS(e.maxNested??50,`maxNested`),m=fS(e.sitemapUrl);if(!m)throw Error(`sitemapUrl must be an absolute HTTP or HTTPS URL.`);o.add(m),d.push(m);let h=0,g=0,_=0,v=0,y=0,b=0,x=0,ee=0,te=0,S=0,C=0,w=!1,T=!1;function ne(e){b+=1,s.length<10&&s.push(e)}function re(e){x+=1;let t=mS(e.value);if(!t){te+=1,c.length<10&&c.push(e);return}ee+=1,t.getTime()>Date.now()&&(S+=1,l.length<10&&l.push(e))}for(;d.length;){let e=d.shift();if(!e)continue;let s=await yS(e);if(u.push(s.document),s.document.warning&&t.push(s.document.warning),s.document.dataStatus!==`unavailable`&&(h+=1),!s.xml||!s.document.root)continue;let c=pp(s.xml,{xmlMode:!0});c(s.document.root===`urlset`?`urlset > url`:``).each((t,i)=>{g+=1;let a=c(i),o=a.children(`loc`).first().text().trim(),s=a.children(`lastmod`).first();s.length&&re({sitemapUrl:e,kind:`url`,loc:o,value:s.text().trim()});let l=fS(o);if(!l){ne({sitemapUrl:e,kind:`url`,value:o});return}if(r.has(l)){v+=1;return}if(r.add(l),n.length<f){n.push(l);return}w=!0,C+=1}),c(s.document.root===`sitemapindex`?`sitemapindex > sitemap`:``).each((t,n)=>{_+=1;let r=c(n),s=r.children(`loc`).first().text().trim(),l=r.children(`lastmod`).first();l.length&&re({sitemapUrl:e,kind:`sitemap`,loc:s,value:l.text().trim()});let u=fS(s);if(!u){ne({sitemapUrl:e,kind:`sitemap`,value:s});return}if(a.has(u)||o.has(u)){y+=1;return}a.add(u),i.push(u),o.size<p?(o.add(u),d.push(u)):T=!0})}let E=i.filter(e=>!o.has(e)).length,ie=w||T||E>0;return b&&t.push(`Ignored ${b} invalid sitemap <loc> ${b===1?`entry`:`entries`}.`),ie&&t.push(`Sitemap discovery exceeded a configured URL or sitemap boundary; the returned inventory is incomplete.`),{sitemapUrl:m,dataStatus:t.length||b||ie?`partial`:`complete`,urls:n,nestedSitemaps:i,source:{sitemapsFetched:h,urlLocs:g,sitemapLocs:_,duplicateUrlLocs:v,duplicateSitemapLocs:y,invalidLocs:{count:b,samples:s},lastmods:{trust:`unverified`,observed:x,parseable:ee,malformed:{count:te,samples:c},future:{count:S,samples:l}},documents:u},truncation:{possiblyTruncated:ie,urlLimitExceeded:w,nestedSitemapLimitExceeded:T,omittedUrlsAtLeast:C,unprocessedSitemaps:E,limits:{urls:f,sitemaps:p}},warnings:t}}const SS={runGa4Report:dn};function CS(e){if(!e||e===`(not set)`)return``;let[t=``]=e.split(`?`);return t.replace(/\/$/,``)||`/`}function wS(e){try{return CS(new URL(e).pathname)}catch{return``}}function TS(e,t){return e<t?-1:+(e>t)}function ES(e){let t=[...e].sort((e,t)=>TS(CS(e.landingPagePlusQueryString??``),CS(t.landingPagePlusQueryString??``))||TS(e.landingPagePlusQueryString??``,t.landingPagePlusQueryString??``)||TS(e.sessions??``,t.sessions??``)||TS(e.totalUsers??``,t.totalUsers??``)||TS(e.conversions??``,t.conversions??``)),n=new Map;for(let e of t){let t=CS(e.landingPagePlusQueryString??``);if(!t)continue;let r=n.get(t)??{sessions:0,totalUsers:0,conversions:0};n.set(t,{sessions:r.sessions+Number(e.sessions??0),totalUsers:r.totalUsers+Number(e.totalUsers??0),conversions:r.conversions+Number(e.conversions??0)})}return n}function DS(e,t){return e.get(wS(t))}function OS(e){return e?.dataStatus===`complete`}function kS(e){if(!e.propertyId)return{canRank:!1,warnings:[]};let t=`Observed landing-page values remain visible but do not affect priority scores.`,n=[...e.warning?[`GA4: ${e.warning}`]:[],...e.source?.retainedRowLimitReached?[`GA4: the retained-row limit was reached. ${t}`]:[],...e.source?.qualityWarnings?.map(e=>`${e} ${t}`)??[]];return!e.source?.dataStatus&&!e.warning&&n.push(`GA4: landing-page completeness was not reported. ${t}`),{canRank:!e.warning&&n.length===0&&OS(e.source),warnings:n}}async function AS(e,t=SS){let n=e.limit??5e3;if(!e.propertyId)return{values:new Map,source:{dataStatus:`complete`,returnedRows:0,retainedRowLimit:n,retainedRowLimitReached:!1,qualityWarnings:[]}};try{let r=await t.runGa4Report(e.propertyId,{dateRanges:[{startDate:e.startDate,endDate:e.endDate}],dimensions:[{name:`landingPagePlusQueryString`}],metrics:[{name:`sessions`},{name:`totalUsers`},{name:`conversions`}],orderBys:[{metric:{metricName:`sessions`},desc:!0}],limit:n}),i=hn(r),a=ES(i),o=r.rowCount,s=o!==void 0&&o>i.length||i.length>=n,c=gn(r,`GA4 landing-page report`);return{values:a,source:{dataStatus:s||c.length?`partial`:`complete`,returnedRows:i.length,...o===void 0?{}:{availableRows:o},retainedRowLimit:n,retainedRowLimitReached:s,qualityWarnings:c}}}catch(e){return{values:new Map,source:{dataStatus:`partial`,returnedRows:0,retainedRowLimit:n,retainedRowLimitReached:!1,qualityWarnings:[]},warning:e instanceof Error?e.message:String(e)}}}function jS(e={}){let t=!!(e.queryPageMetrics||e.queryPageTopQuery),n=!!(e.queryPagesMetrics||e.queryPagesTopQueries),r=e.queryPagesMetricsBatch||e.queryPagesTopQueriesBatch?`batch`:n?`legacy-bulk`:t?`per-page`:void 0;return{fetchPage:e.fetchPage??lS,fetchSitemapUrls:e.fetchSitemapUrls??xS,fetch:e.fetch??Oh,queryPageMetrics:(r===`per-page`?e.queryPageMetrics:void 0)??(r?async()=>void 0:t_),queryPageTopQuery:(r===`per-page`?e.queryPageTopQuery:void 0)??(r?async()=>void 0:i_),queryPagesMetrics:r===`legacy-bulk`?e.queryPagesMetrics:r?void 0:n_,queryPagesTopQueries:r===`legacy-bulk`?e.queryPagesTopQueries:r?void 0:a_,queryPagesMetricsBatch:r===`batch`?e.queryPagesMetricsBatch:r?void 0:r_,queryPagesTopQueriesBatch:r===`batch`?e.queryPagesTopQueriesBatch:r?void 0:o_,fetchLandingPageValues:e.fetchLandingPageValues??AS,landingValueForUrl:e.landingValueForUrl??DS,now:e.now??(()=>new Date)}}function MS(e,t){try{return new URL(e).origin===t}catch{return!1}}function NS(e){let t=new URL(e.url).origin,n=e.declaredSitemapUrls.filter(e=>MS(e,t)),r=e.declaredSitemapUrls.length-n.length;r>0&&e.warnings.push(`Ignored ${r} robots.txt sitemap declaration${r===1?``:`s`} outside the crawl origin.`);let i=[...new Set(n)].map(e=>({url:e,source:`robots-txt`}));return i.length>20&&e.warnings.push(`Used the first 20 robots.txt sitemap declarations to keep discovery bounded.`),i.length>0?i.slice(0,20):[{url:new URL(`/sitemap.xml`,e.url).toString(),source:`default-path`}]}function PS(e){return e.some(e=>e.dataStatus!==`unavailable`)?e.every(e=>e.dataStatus===`complete`)?`complete`:`partial`:`unavailable`}async function FS(e){let t=[],n=new Set,r=NS(e);for(let i of r){let r=await e.fetchSitemapUrls({sitemapUrl:i.url,limit:e.maxPages});e.warnings.push(...r.warnings);for(let e of r.urls)n.add(e);t.push({url:r.sitemapUrl,source:i.source,dataStatus:r.source.sitemapsFetched>0?r.dataStatus:`unavailable`,urlsReturned:r.urls.length,sitemapsFetched:r.source.sitemapsFetched,lastmods:r.source.lastmods,documents:r.source.documents,possiblyTruncated:r.truncation.possiblyTruncated,warnings:r.warnings})}if(n.size===0&&r.some(e=>e.source===`robots-txt`)){let r=new URL(`/sitemap.xml`,e.url).toString();if(!t.some(e=>e.url===r)){e.warnings.push(`No robots.txt sitemap declaration returned URLs, so the crawler also tried /sitemap.xml.`);let i=await e.fetchSitemapUrls({sitemapUrl:r,limit:e.maxPages});e.warnings.push(...i.warnings);for(let e of i.urls)n.add(e);t.push({url:i.sitemapUrl,source:`default-path`,dataStatus:i.source.sitemapsFetched>0?i.dataStatus:`unavailable`,urlsReturned:i.urls.length,sitemapsFetched:i.source.sitemapsFetched,lastmods:i.source.lastmods,documents:i.source.documents,possiblyTruncated:i.truncation.possiblyTruncated,warnings:i.warnings})}}return{urls:[...n],discovery:{dataStatus:PS(t),urlsReturned:n.size,roots:t}}}const IS=[`GPTBot`,`OAI-SearchBot`,`ChatGPT-User`,`ClaudeBot`,`PerplexityBot`,`Google-Extended`,`CCBot`,`Applebot`,`Bingbot`],LS=[`/.well-known/agent.json`,`/agent.json`,`/.well-known/mcp.json`,`/.well-known/ai-plugin.json`,`/.well-known/openapi.json`,`/openapi.json`];function RS(e,t){try{let n=t?new URL(e,t):new URL(e);return n.hash=``,[`http:`,`https:`].includes(n.protocol)?n.toString():void 0}catch{return}}function zS(e,t){try{return new URL(e).origin===t}catch{return!1}}function BS(e){let t=new URL(e).pathname.toLowerCase();return/\.(avif|bmp|css|csv|dmg|eot|gif|gz|ico|jpeg|jpg|js|json|map|mov|mp3|mp4|ogg|otf|pdf|png|svg|tar|ttf|wav|webm|webp|woff|woff2|zip)$/.test(t)}function VS(e,t){if(!e.includes(`*`))return t.includes(e);let n=e.split(`*`),r=0;for(let e of n){if(!e)continue;let n=t.slice(r).indexOf(e);if(n===-1)return!1;r+=n+e.length}return!0}function HS(e,t,n){return(!t.length||t.some(t=>VS(t,e)))&&!n.some(t=>VS(t,e))}async function US(e){let t=new URL(`/llms.txt`,e.url).toString(),n=Nx({timeoutMs:e.timeoutMs,signal:e.signal});try{let r=await e.fetch(t,{profile:`bot`,redirect:`follow`,signal:n.signal}),i=r.headers.get(`content-type`)??``,a=r.status>=200&&r.status<300&&!/\btext\/html\b/i.test(i);return await r.body?.cancel().catch(()=>void 0),{url:r.url||t,exists:a,status:r.status}}catch{return{url:t,exists:!1}}finally{n.cleanup()}}function WS(e,t){return E(e,t)}function GS(e){let t=new Set;for(let n of e.matchAll(/^\s*user-agent\s*:\s*(.+?)\s*$/gim)){let e=n[1]?.trim().toLowerCase();e&&t.add(e)}return t}function KS(e){let t=new Set;for(let n of e.matchAll(/^\s*sitemap\s*:\s*(\S+)\s*$/gim)){let e=n[1]?.trim();if(e)try{t.add(new URL(e).toString())}catch{}}return[...t]}async function qS(e){let t=new URL(`/robots.txt`,e.url).toString(),n=Nx({timeoutMs:e.timeoutMs,signal:e.signal});try{let r=await e.fetch(t,{profile:`bot`,redirect:`follow`,signal:n.signal}),i=await r.text(),a=r.status>=200&&r.status<300,o=r.status>=400&&r.status<500&&r.status!==429,s=a?`available`:[401,403].includes(r.status)?`access-blocked`:r.status===429?`rate-limited`:o?`absent`:`unreachable`,c=a,l=c?GS(i):new Set,u=WS(t,c?i:``);return{url:r.url||t,exists:c,availability:s,status:r.status,...[`rate-limited`,`unreachable`].includes(s)?{error:`robots.txt returned HTTP ${r.status}.`}:{},sitemapUrls:c?KS(i):[],botAccess:IS.map(t=>{let n=t.toLowerCase();return{userAgent:t,allowed:s===`rate-limited`||s===`unreachable`?null:u.isAllowed(e.url,t)??!0,declared:l.has(n),coveredByWildcard:l.has(`*`)}})}}catch(e){return{url:t,exists:!1,availability:`unreachable`,error:e instanceof Error?e.message:String(e),sitemapUrls:[],botAccess:IS.map(e=>({userAgent:e,allowed:null,declared:!1,coveredByWildcard:!1}))}}finally{n.cleanup()}}async function JS(e){let t=new URL(e.path,e.baseUrl).toString(),n=Nx({timeoutMs:e.timeoutMs,signal:e.signal});try{let r=await e.fetch(t,{profile:`bot`,redirect:`follow`,signal:n.signal}),i=r.headers.get(`content-type`)??``,a=await r.text(),o=r.status>=200&&r.status<300,s;if(o&&/\bjson\b/i.test(i))try{JSON.parse(a),s=!0}catch{s=!1}return{url:r.url||t,exists:o,status:r.status,contentType:i,...s===void 0?{}:{validJson:s}}}catch{return{url:t,exists:!1}}finally{n.cleanup()}}async function YS(e){let t=[];for(let n of LS){if(e.signal?.aborted)break;t.push(await JS({baseUrl:e.url,path:n,timeoutMs:e.timeoutMs,fetch:e.fetch,signal:e.signal}))}return t}async function XS(e,t){let n=jS(t),r=Jb(e),{searchMetricsLimit:i,analyticsLimit:a}=qx(e),o=e.signal,s=!!o?.aborted,c=()=>s||!!o?.aborted,l=e.site,u=new URL(r.url).origin,d=[],f=[],p=new Set,m=new Set,h=new Set,g={},_=new Map,v=[],y=[],b=new Map,x=new Set,ee=r.mode===`site`,te=0,S=0,C=0,w=0,T=0,ne=0,re=0,E,ie,ae=Promise.resolve(),D=(t,i={})=>{if(!e.onStatus)return;let a={type:`crawl_status`,phase:t,generatedAt:n.now().toISOString(),discoveredUrls:h.size,queuedUrls:te,pendingUrls:f.length,inFlightUrls:x.size,crawledUrls:v.length,skippedUrls:S,failedUrls:ne,observedInternalLinks:re,maxPages:r.maxPages,...i};ae=ae.then(()=>e.onStatus?.(a)).catch(()=>void 0)},oe=async()=>{await ae},se=r.js===`off`?void 0:hg({concurrency:r.concurrency});try{D(`started`,{url:r.url,message:`Started crawl for ${r.url}.`});let t=c()?void 0:await Promise.all([US({url:r.url,timeoutMs:r.timeoutMs,fetch:n.fetch,signal:o}),qS({url:r.url,timeoutMs:r.timeoutMs,fetch:n.fetch,signal:o}),YS({url:r.url,timeoutMs:r.timeoutMs,fetch:n.fetch,signal:o})]),ae=t?.[0]??{url:new URL(`/llms.txt`,r.url).toString(),exists:!1},ce=t?.[1],le=t?.[2];s=c();let O=(e,t)=>{let n=RS(e,r.url);if(!n){S+=1,D(`url_skipped`,{url:e,depth:t,reason:`invalid_url`});return}if(h.add(n),!p.has(n)){if(m.has(n)){let e=f.find(e=>e.url===n);e&&t<e.depth&&(e.depth=t),f.sort((e,t)=>e.depth-t.depth||(e.url<t.url?-1:+(e.url>t.url)));return}if(!zS(n,u)){S+=1,D(`url_skipped`,{url:n,depth:t,reason:`off_origin`});return}if(BS(n)){S+=1,D(`url_skipped`,{url:n,depth:t,reason:`asset_url`});return}if(!HS(n,r.include,r.exclude)){S+=1,D(`url_skipped`,{url:n,depth:t,reason:`filtered_url`});return}if(f.length+p.size+x.size>=r.maxPages*5){S+=1,T+=1,D(`url_skipped`,{url:n,depth:t,reason:`queue_safety_limit`});return}m.add(n),te+=1,f.push({url:n,depth:t}),f.sort((e,t)=>e.depth-t.depth||(e.url<t.url?-1:+(e.url>t.url))),D(`url_queued`,{url:n,depth:t})}};if(r.mode!==`sitemap`){let e=r.mode===`list`?r.urls:[r.url];for(let t of e)O(t,0)}if(!c()&&r.useSitemap&&(r.mode===`site`||r.mode===`sitemap`)){let e=await FS({url:r.url,maxPages:r.maxPages,declaredSitemapUrls:ce?.sitemapUrls??[],warnings:d,fetchSitemapUrls:n.fetchSitemapUrls});for(let t of e.urls)O(t,0);E=e.discovery}let ue=()=>{for(;f.length;){let e=f.shift();if(!e)continue;let t=RS(e.url,r.url);if(t&&!p.has(t))return p.add(t),{url:t,depth:e.depth}}},de=e=>{let t;t={...e,promise:n.fetchPage(e.url,{js:r.js,refresh:r.refresh,timeoutMs:r.timeoutMs,rate:r.fetchRate,signal:o,respectRobots:r.respectRobots,renderer:se}).then(e=>({task:t,result:e})).catch(n=>({task:t,result:{request:{requestedUrl:e.url,outcome:`failure`,failureKind:`unknown`,error:n instanceof Error?n.message:String(n),extraction:`not-applicable`},urls:[],warning:`${e.url}: ${n instanceof Error?n.message:String(n)}`}}))},x.add(t),D(`page_started`,{url:e.url,depth:e.depth})};for(;!c()&&(f.length||x.size)&&v.length<r.maxPages;){for(;!c()&&x.size<r.concurrency&&v.length+x.size<r.maxPages;){let e=ue();if(!e)break;de(e)}if(!x.size)break;let e=await Promise.race([...[...x].map(e=>e.promise),...o?[Px(o)]:[]]);if(e===Mx){s=!0;break}let{task:t,result:n}=e;x.delete(t);let i=n.request??(n.page?Ux(t.url,n.page):{requestedUrl:t.url,outcome:`failure`,failureKind:`unknown`,error:n.warning??`Crawler returned no page snapshot.`,extraction:`not-applicable`});if(y.push(i),i.outcome===`skipped`){S+=1,i.reason===`robots-deferred`&&(C+=1,d.push(`${t.url}: crawl deferred because robots.txt availability is unknown.`)),i.reason===`origin-backpressure`&&(w+=1),D(`page_skipped`,{url:t.url,depth:t.depth,reason:i.reason});continue}if(n.warning){d.push(n.warning),ne+=1,D(`page_failed`,{url:t.url,depth:t.depth,reason:n.warning});continue}if(!n.page){ne+=1,D(`page_failed`,{url:t.url,depth:t.depth,reason:`missing_page_snapshot`});continue}let a=n.page,l=RS(a.finalUrl,r.url);if(!l){ne+=1,d.push(`${t.url}: invalid final URL ${a.finalUrl}`),D(`page_failed`,{url:t.url,depth:t.depth,reason:`invalid_final_url`});continue}a.url=l,a.finalUrl=l,a.crawlDepth=t.depth;let u=RS(t.url,r.url);u&&u!==l&&_.set(u,l),a.contentAuditAllowed=!r.respectRobots||a.robotsTxt?.allowed!==!1;let f=e=>{let n=RS(t.url,r.url)??t.url,i=n===l,o=e.map(e=>RS(e,l)).filter(e=>!!e),s=(e,t)=>{let n=[...t].sort((e,t)=>e<t?-1:+(e>t));e.outgoingInternalCount=n.length,e.sampleInternalLinks=n.slice(0,25)},c=b.get(l);if(c){let e=new Set([...c.links,...o]),r=Math.min(c.minDepth,t.depth),u=!c.directlyRequested&&i||c.directlyRequested===i&&n<c.sourceRequest;if(u)a.crawlDepth=r,v[c.index]=a;else{let e=v[c.index];e&&(e.crawlDepth=r)}let d=v[c.index];d&&s(d,e),re+=e.size-c.links.size,b.set(l,{...c,directlyRequested:c.directlyRequested||i,sourceRequest:u?n:c.sourceRequest,links:e,minDepth:r}),g[l]=[...e].sort((e,t)=>e<t?-1:+(e>t))}else{let e=new Set(o);s(a,e),v.push(a),re+=e.size,b.set(l,{index:v.length-1,directlyRequested:i,sourceRequest:n,links:e,minDepth:t.depth}),g[l]=[...e].sort((e,t)=>e<t?-1:+(e>t))}};if(r.respectRobots&&a.robotsTxt?.allowed===!1){a.blocked=!0,a.contentAuditAllowed=!1,a.indexable=!1,a.indexability=`Robots.txt disallowed`,a.declaredIndexability=`robots-blocked`,f([]),d.push(`${a.url}: skipped because robots.txt disallows it`),S+=1,D(`page_skipped`,{url:a.url,depth:t.depth,statusCode:a.status,reason:`robots_txt_disallowed`});continue}if(f(n.urls),D(`page_completed`,{url:t.url,depth:t.depth,statusCode:a.status}),ee&&t.depth<r.maxDepth)for(let e of n.urls)O(e,t.depth+1)}let fe=Hx(g,_),pe=new Map(v.map((e,t)=>[e.url,t])),me=v.map(e=>e.crawlDepth??0);for(let e=0;e<v.length;e+=1){let e=!1;for(let[t,n]of Object.entries(fe)){let r=pe.get(t);if(r!==void 0)for(let t of n){let n=pe.get(t);if(n===void 0)continue;let i=(me[r]??0)+1;i>=(me[n]??1/0)||(me[n]=i,e=!0)}}if(!e)break}for(let[e,t]of v.entries())t.crawlDepth=me[e];s=c(),s&&(d.push(`Crawl cancelled; returning a partial report.`),D(`cancelled`,{url:r.url,message:`Crawl cancelled; returning a partial report.`}));let he=v.length>=r.maxPages&&(f.length>0||x.size>0),k=E?.dataStatus===`partial`,ge=s||ne>0||he||C>0||w>0||T>0||f.length>0||x.size>0||k,_e=!s&&v.length===0&&y.length>0&&y.every(e=>e.outcome===`failure`),ve=s&&x.size>0?`partial`:`available`,ye=await Jx({cancelled:s,site:l,ga4PropertyId:e.ga4PropertyId,pages:v,warnings:d,searchMetricsLimit:i,analyticsLimit:a,now:n.now,queryPageMetrics:n.queryPageMetrics,queryPageTopQuery:n.queryPageTopQuery,queryPagesMetrics:n.queryPagesMetrics,queryPagesTopQueries:n.queryPagesTopQueries,queryPagesMetricsBatch:n.queryPagesMetricsBatch,queryPagesTopQueriesBatch:n.queryPagesTopQueriesBatch,fetchLandingPageValues:n.fetchLandingPageValues,landingValueForUrl:n.landingValueForUrl}),be=[ye.searchConsole.status,ye.analytics.status].some(e=>e===`partial`||e===`unavailable`),A=_e?`failed`:ge||be?`partial`:`completed`;!s&&r.checkExternal&&(D(`external_links_started`,{url:r.url,message:`Started external link checks.`}),ie=await Bx({pages:v,timeoutMs:r.timeoutMs,fetch:n.fetch,signal:o}),d.push(...ie.warnings),D(`external_links_completed`,{url:r.url,message:`Finished external link checks.`}));for(let e of v)e.geo&&={...e.geo,hasLlmsTxt:ae.exists,llmsTxtUrl:ae.url,llmsTxtStatus:ae.status};let xe=mx({config:r,projectId:e.projectId,site:l,ga4PropertyId:e.ga4PropertyId,dataSources:ye,pages:v,requests:y,requestEvidenceStatus:ve,linkGraph:fe,ai:{llmsTxt:ae,...ce?{robotsTxt:ce}:{},...le?{agentResources:le}:{}},...E?{sitemapDiscovery:E}:{},...ie?{externalLinkVerification:ie}:{},status:A,warnings:d,caveats:jx({cancelled:s,pageLimitReached:he,maxPages:r.maxPages,queueSafetySkippedUrls:T,originBackpressureSkippedUrls:w}),stats:{discoveredUrls:h.size,queuedUrls:te,crawledUrls:v.length,skippedUrls:S,failedUrls:ne,observedInternalLinks:re,pageLimitReached:he}});return D(`completed`,{url:r.url,reportId:xe.id,reportStatus:xe.status,message:`Crawl ${xe.status} with ${xe.summary.totalPages} crawled pages.`}),await oe(),xe}finally{await se?.close()}}const ZS={latestCrawlReport:Ax,crawlSite:XS,saveCrawlReport:Ex,now:()=>new Date};function QS(e){return e.endsWith(`/`)?e:`${e}/`}function $S(e,t){try{let n=new URL(e),r=new URL(t);return n.origin===r.origin&&QS(r.pathname).startsWith(QS(n.pathname))}catch{return!1}}function eC(e,t){let n=t.maxPages??100,r=t.maxDepth??4;return(e.status===`completed`||e.status===`partial`)&&e.config.mode===`site`&&e.config.maxPages>=n&&e.config.maxDepth>=r&&e.config.include.length===0&&e.config.exclude.length===0&&e.config.useSitemap&&e.config.respectRobots&&(!t.url||$S(e.config.url,t.url))}function tC(e,t){let n=Date.parse(e.generatedAt),r=t.maxAgeMs??6048e5,i=t.now.getTime()-n;return Number.isFinite(n)&&i>=0&&i<=r}function nC(e){let t=e.warnings[0]??e.caveats[0];return t?`The technical crawl did not complete: ${t}`:`The technical crawl did not complete.`}function rC(e){return{url:e.url,...e.searchSite?{site:e.searchSite}:{},projectId:e.projectId,ga4PropertyId:e.ga4PropertyId,mode:`site`,maxPages:e.maxPages,maxDepth:e.maxDepth,respectRobots:!0,useSitemap:!0,checkExternal:!1,js:!1,refresh:e.refresh??!1}}async function iC(e,t=ZS){if(e.crawl===!1)return{status:`skipped`,reason:`Technical crawl evidence was skipped with --no-crawl.`};let n=e.maxPages??100,r=e.maxDepth??4,i=t.latestCrawlReport(e.site);if(!e.refresh&&i&&eC(i,{...e,maxPages:n,maxDepth:r})&&tC(i,{...e,now:t.now()}))return{status:`reused`,report:i};if(!e.url)return{status:`unavailable`,reason:"No crawl URL is saved for this site. Add one to the project profile or run `seo crawl --url <url> --save`."};try{let i=await t.crawlSite(rC({...e,url:e.url,maxPages:n,maxDepth:r}));return i.status===`failed`?{status:`unavailable`,reason:nC(i)}:(t.saveCrawlReport(i),{status:e.refresh?`refreshed`:`created`,report:i,...i.status===`partial`?{reason:`The technical crawl completed with partial coverage.`}:{}})}catch(e){return{status:`unavailable`,reason:`The technical crawl could not run: ${e instanceof Error?e.message:String(e)}`}}}const aC=[`page`,`query`,`device`,`country`],oC=[`rank`,`dimension`,`data_status`,`evidence_scope`,`key`,`before_clicks`,`after_clicks`,`click_delta`,`before_impressions`,`after_impressions`,`impression_delta`,`before_position`,`after_position`,`position_delta`],sC=[`rank`,`dimension`,`data_status`,`key`,`retained_in`,`clicks`,`impressions`,`position`,`reason`];function cC(e){return[{filename:`segment-status.csv`,headers:[`dimension`,`data_status`,`matched_rows`,`unmatched_rows`,`before_rows_fetched`,`after_rows_fetched`,`possibly_truncated`,`verdict`,`warnings`],rows:aC.map(t=>{let n=e.segments[t];return{dimension:t,data_status:n.dataStatus,matched_rows:n.summary.matchedRows,unmatched_rows:n.summary.unmatchedRows,before_rows_fetched:n.source.before.rowsFetched,after_rows_fetched:n.source.after.rowsFetched,possibly_truncated:n.source.before.possiblyTruncated||n.source.after.possiblyTruncated,verdict:n.summary.verdict,warnings:n.warnings.join(`; `)}})},...aC.map(t=>({filename:`segment-${t}.csv`,headers:oC,rows:e.segments[t].items.map((n,r)=>({rank:r+1,dimension:t,data_status:e.segments[t].dataStatus,evidence_scope:n.evidenceScope,key:n.key,before_clicks:n.beforeClicks,after_clicks:n.afterClicks,click_delta:n.clickDelta,before_impressions:n.beforeImpressions,after_impressions:n.afterImpressions,impression_delta:n.impressionDelta,before_position:n.beforePosition,after_position:n.afterPosition,position_delta:n.positionDelta}))})),...aC.map(t=>({filename:`segment-${t}-unmatched.csv`,headers:sC,rows:e.segments[t].unmatchedSegments.map((n,r)=>({rank:r+1,dimension:t,data_status:e.segments[t].dataStatus,key:n.key,retained_in:n.retainedIn,clicks:n.clicks,impressions:n.impressions,position:n.position,reason:n.reason}))}))]}const lC={"priorities.csv":[`rank`,`label`,`confidence`,`reason`,`action`],"anomalies.csv":[`metric`,`direction`,`significant`,`baseline_start`,`baseline_end`,`comparison_start`,`comparison_end`,`baseline_mean`,`comparison_mean`,`baseline_total`,`comparison_total`,`percent_change`,`z_score`],"decay.csv":[`rank`,`query`,`url`,`template`,`diagnosis`,`signals`,`evidence_scope`,`click_loss`,`drop_pct`,`previous_clicks`,`current_clicks`,`previous_position`,`current_position`,`action`],"decay-clusters.csv":[`rank`,`template`,`diagnosis`,`findings`,`observed_click_decline`,`previous_clicks`,`average_drop_pct`,`sample_queries`,`sample_urls`,`action`],"cannibalisation.csv":[`rank`,`query`,`owner_url`,`url_count`,`hhi`,`template`,`urls`,`clicks`,`impressions`,`action`],"cannibalisation-suppressed.csv":[`rank`,`query`,`reason`,`url_count`,`template`,`evidence`],"striking-distance.csv":[`rank`,`query`,`url`,`template`,`clicks`,`impressions`,`ctr`,`position`,`opportunity_score`,`coverage`,`action`],"quick-wins.csv":[`rank`,`query`,`url`,`template`,`position`,`clicks`,`impressions`,`ctr`,`target_ctr`,`benchmark_source`,`benchmark_sample_population`,`benchmark_leave_out`,`benchmark_confidence`,`benchmark_peer_rows`,`benchmark_qualified_impressions`,`estimated_ctr_click_shortfall`,`heuristic`,`traffic_forecast`,`coverage`,`verification_status`,`verification_error`,`verification_signals`,`confidence`,`evidence`,`action`],"quick-win-groups.csv":[`rank`,`label`,`query`,`template`,`row_count`,`url_count`,`estimated_ctr_click_shortfall`,`impressions`,`sample_urls`,`action`],"report-summary.csv":[`site`,`generated_at`,`period_start`,`period_end`,`period_days`,`headline`],"report-sections.csv":[`section`,`rank`,`bullet`],"report-caveats.csv":[`rank`,`caveat`],"report-priorities.csv":[`rank`,`title`,`confidence`,`action`],"change-measurements.csv":[`change_id`,`title`,`scope`,`target`,`changed_at`,`verdict`,`confidence`,`data_status`,`error_code`,`error_retryable`,`error_message`,`before_start`,`before_end`,`after_start`,`after_end`,`before_clicks`,`after_clicks`,`click_delta`,`click_pct`,`impression_delta`,`ctr_delta`,`position_delta`,`note`],"monitoring-crawls.csv":[`id`,`site`,`start_url`,`created_at`,`limit`,`url_count`,`status_errors`,`non_indexable`,`recommendations`,`high_priority_recommendations`,`top_recommendation_url`,`top_recommendation_severity`,`top_recommendation_title`,`top_recommendation_action`],"monitoring-index-watch.csv":[`inspected_urls`,`latest_inspected_at`,`non_pass`,`blocked`,`earliest_inspected_at`,`current_issues`,`failed`],"monitoring-link-recover.csv":[`id`,`site`,`created_at`,`start_date`,`end_date`,`days`,`checked`,`recoverable`,`high`,`medium`,`low`,`clicks_at_risk`,`impressions_at_risk`,`top_issue`,`top_url`,`top_action`,`repeated_urls`,`repeated_top_url`],"priority-queue.csv":[`rank`,`source`,`category`,`score`,`impact`,`impact_kind`,`confidence`,`findings`,`title`,`target`,`template`,`template_count`,`ga4_sessions`,`ga4_total_users`,`evidence`,`action`],"priority-score-breakdown.csv":[`rank`,`target`,`source`,`score`,`impact_score`,`source_weight`,`confidence_weight`,`effort_weight`,`verification_weight`,`template_weight`,`analytics_weight`],"priority-grouped-findings.csv":[`queue_rank`,`finding_rank`,`source`,`category`,`score`,`impact`,`impact_kind`,`confidence`,`title`,`target`,`template`,`ga4_sessions`,`evidence`,`action`],"workflow-steps.csv":[`rank`,`tool`,`status`,`summary`],"workflow-actions.csv":[`rank`,`title`,`confidence`,`action`],"warnings.csv":[`rank`,`warning`],"templates.csv":[`rank`,`template`,`verdict`,`confidence`,`urls`,`clicks`,`impressions`,`ctr`,`position`,`entity_fit_impression_share`,`discovered_urls`,`gsc_visible_urls`,`untested_urls`,`crawl_attempts`,`crawled_urls`,`crawl_blocked_or_failed`,`inspection_attempts`,`inspected_urls`,`not_indexed`,`inspection_unknown`,`evidence`,`action`],"pseo-caveats.csv":[`rank`,`caveat`],"template-queries.csv":[`template`,`rank`,`query`,`clicks`,`impressions`,`position`],"demand-patterns.csv":[`template`,`rank`,`label`,`impressions`,`clicks`,`sample_queries`],"sample-coverage.csv":[`template`,`url`,`query`,`classification`,`title_coverage`,`h1_coverage`,`body_coverage`,`missing_terms`],"weak-entity-fit.csv":[`template`,`query`,`url`,`path_terms`],"crawl-samples.csv":[`template`,`url`,`status`,`final_url`,`title`,`meta_description`,`h1`,`word_count`,`technical_status`,`canonical`,`meta_robots`,`x_robots_tag`,`warning`,`blocked`,`rendered`,`cache`],"inspection-samples.csv":[`template`,`url`,`verdict`,`index_status`,`coverage_state`,`indexing_state`,`page_fetch_state`,`google_canonical`,`user_canonical`,`last_crawl_time`,`warning`]};function uC(e){return e.startsWith(`diagnosis-`)?e.slice(10):e}function dC(e){return e.map(e=>({...e,headers:e.headers??lC[uC(e.filename)]}))}function fC(e){return e.startsWith(`theme: `)?`${e.slice(7)}-related`:e===`general`?`broad`:e}function pC(e){if(e==null)return``;let t=String(e);return/[",\n\r]/.test(t)?`"${t.replaceAll(`"`,`""`)}"`:t}function mC(e,t){let n=t??[...new Set(e.flatMap(e=>Object.keys(e)))];return`${[n.map(pC).join(`,`),...e.map(e=>n.map(t=>pC(e[t])).join(`,`))].join(`
505
+ `)}\n`}function hC(e){return e.items.map((e,t)=>({rank:t+1,query:e.query,url:e.url,template:e.template.label,position:e.position,clicks:e.clicks,impressions:e.impressions,ctr:e.ctr,target_ctr:e.targetCtr,benchmark_source:e.benchmark.source,benchmark_sample_population:e.benchmark.samplePopulation,benchmark_leave_out:e.benchmark.leaveOut,benchmark_confidence:e.benchmark.confidence,benchmark_peer_rows:e.benchmark.peerRows,benchmark_qualified_impressions:e.benchmark.qualifiedPeerImpressions,estimated_ctr_click_shortfall:e.estimatedCtrClickShortfall,heuristic:e.priority.heuristic,traffic_forecast:e.priority.estimatedClickLift,coverage:e.contentVerification?.classification,verification_status:e.contentVerification?.status,verification_error:e.contentVerification?.error,verification_signals:e.contentVerification?.signals.join(`; `),confidence:e.recommendation.confidence,evidence:e.recommendation.evidenceRef,action:e.recommendation.action}))}function gC(e){return e.changeMeasurementAttempts.map(e=>{let t=e.status===`measured`?e.measurement:void 0;return{change_id:e.change.id,title:e.change.title,scope:e.change.scope,target:e.change.target,changed_at:e.change.changedAt,verdict:t?.verdict,confidence:t?.confidence,data_status:e.dataStatus,error_code:e.status===`failed`?e.error.code:void 0,error_retryable:e.status===`failed`?e.error.retryable:void 0,error_message:e.status===`failed`?e.error.message:void 0,before_start:t?.before.startDate,before_end:t?.before.endDate,after_start:t?.after.startDate,after_end:t?.after.endDate,before_clicks:t?.before.metrics?.clicks,after_clicks:t?.after.metrics?.clicks,click_delta:t?.delta.clicks,click_pct:t?.delta.clickPct,impression_delta:t?.delta.impressions,ctr_delta:t?.delta.ctr,position_delta:t?.delta.position,note:t?.note}})}function _C(e){return dC([{filename:`priorities.csv`,rows:e.priorities.map((e,t)=>({rank:t+1,label:e.label,confidence:e.confidence,reason:e.reason,action:e.action}))},{filename:`anomalies.csv`,rows:e.anomaly.anomalies.map(e=>({metric:e.metric,direction:e.direction,significant:e.significant,baseline_start:e.baselineStart,baseline_end:e.baselineEnd,comparison_start:e.comparisonStart,comparison_end:e.comparisonEnd,baseline_mean:e.baselineMean,comparison_mean:e.comparisonMean,baseline_total:e.baselineTotal,comparison_total:e.comparisonTotal,percent_change:e.percentChange,z_score:e.zScore}))},...cC(e),{filename:`decay.csv`,rows:e.decay.items.map((e,t)=>({rank:t+1,query:e.query,url:e.url,template:e.template.label,diagnosis:e.diagnosis,signals:e.signals.join(`; `),evidence_scope:e.evidenceScope,click_loss:e.clickLoss,drop_pct:e.dropPct,previous_clicks:e.previous.clicks,current_clicks:e.current.clicks,previous_position:e.previous.position,current_position:e.current.position,action:e.recommendation.action}))},{filename:`decay-clusters.csv`,rows:e.decay.groups.map((e,t)=>({rank:t+1,template:e.template.label,diagnosis:e.diagnosis,findings:e.count,observed_click_decline:e.totalClickLoss,previous_clicks:e.totalPreviousClicks,average_drop_pct:e.averageDropPct,sample_queries:e.sampleQueries.join(`; `),sample_urls:e.sampleUrls.join(`; `),action:e.recommendation}))},{filename:`cannibalisation.csv`,rows:e.cannibalization.items.map((e,t)=>({rank:t+1,query:e.query,suggested_owner_url:e.suggestedOwnerUrl,url_count:e.pages.length,hhi:e.hhi,property_impressions:e.propertyImpressions,page_exposure_impressions:e.pageExposureImpressions,observed_page_exposure_ratio:e.observedPageExposureRatio,template:e.template?.label,urls:e.pages.map(e=>e.url).join(`; `),clicks:e.pages.reduce((e,t)=>e+t.clicks,0),review_context:e.reviewContext.join(`; `),action:e.recommendation.action}))},{filename:`cannibalisation-suppressed.csv`,rows:e.cannibalization.suppressed.map((e,t)=>({rank:t+1,query:e.query,reason:e.reason,url_count:e.urlCount,template:e.template?.label,evidence:e.evidenceRef}))},{filename:`striking-distance.csv`,rows:e.strikingDistance.items.map((e,t)=>({rank:t+1,query:e.query,url:e.url,template:e.template.label,clicks:e.clicks,impressions:e.impressions,ctr:e.ctr,position:e.position,priority_method:e.priority.method,priority_score:e.priority.score,estimated_click_lift:e.priority.estimatedClickLift,coverage:e.contentVerification?.classification,action_type:e.recommendation.type,confidence:e.recommendation.confidence,evidence:e.recommendation.evidence,action:e.recommendation.action}))},{filename:`quick-wins.csv`,rows:hC(e.quickWins)},{filename:`quick-win-groups.csv`,rows:e.quickWins.groups.map((e,t)=>({rank:t+1,label:e.label,query:e.query,template:e.template.label,row_count:e.rowCount,url_count:e.urlCount,estimated_ctr_click_shortfall:e.totalEstimatedCtrClickShortfall,impressions:e.totalImpressions,sample_urls:e.sampleUrls.join(`; `),action:e.recommendation}))}])}function vC(e,t){return t.map(t=>({...t,filename:`${e}-${t.filename}`}))}function yC(e){let t=e.monitoring.linkRecover;return dC([{filename:`report-summary.csv`,rows:[{site:e.site,generated_at:e.generatedAt,period_start:e.period.startDate,period_end:e.period.endDate,period_days:e.periodDays,headline:e.headline}]},{filename:`report-sections.csv`,rows:e.sections.flatMap(e=>e.bullets.map((t,n)=>({section:e.title,rank:n+1,bullet:t})))},{filename:`report-caveats.csv`,rows:e.caveats.map((e,t)=>({rank:t+1,caveat:e}))},{filename:`report-priorities.csv`,rows:e.priorities.map((e,t)=>({rank:t+1,title:e.title,confidence:e.confidence,action:e.action}))},{filename:`change-measurements.csv`,rows:gC(e)},{filename:`monitoring-crawls.csv`,rows:e.monitoring.crawlRuns.map(e=>({id:e.id,site:e.site,start_url:e.startUrl,created_at:e.createdAt,limit:e.limit,url_count:e.urlCount,status_errors:e.statusErrors,non_indexable:e.nonIndexable,recommendations:e.recommendations,high_priority_recommendations:e.highPriorityRecommendations,top_recommendation_url:e.topRecommendation?.url,top_recommendation_severity:e.topRecommendation?.severity,top_recommendation_title:e.topRecommendation?.title,top_recommendation_action:e.topRecommendation?.action}))},{filename:`monitoring-index-watch.csv`,rows:[{inspected_urls:e.monitoring.indexWatch.inspectedUrls,latest_inspected_at:e.monitoring.indexWatch.latestInspectedAt,non_pass:e.monitoring.indexWatch.nonPass,blocked:e.monitoring.indexWatch.blocked,earliest_inspected_at:e.monitoring.indexWatch.earliestInspectedAt,current_issues:e.monitoring.indexWatch.currentIssues,failed:e.monitoring.indexWatch.failed}]},{filename:`monitoring-link-recover.csv`,rows:t?[{id:t.id,site:t.site,created_at:t.createdAt,start_date:t.range.startDate,end_date:t.range.endDate,days:t.range.days,checked:t.checked,recoverable:t.recoverable,high:t.high,medium:t.medium,low:t.low,clicks_at_risk:t.clicksAtRisk,impressions_at_risk:t.impressionsAtRisk,top_issue:t.topIssue,top_url:t.topUrl,top_action:t.topAction,repeated_urls:t.repeatedUrls,repeated_top_url:t.repeatedTopUrl}]:[]},...vC(`diagnosis`,_C(e.diagnosis))])}function bC(e){return dC([{filename:`priority-queue.csv`,rows:e.output.queue.map((e,t)=>({rank:t+1,source:e.source,category:e.category,score:e.score,impact:e.impact,impact_kind:e.impactKind,confidence:e.confidence,findings:e.grouped?.count??1,title:e.title,target:e.target,template:e.template?.label,template_count:e.template?.count,ga4_sessions:e.analytics?.sessions,ga4_total_users:e.analytics?.totalUsers,evidence:e.evidence,action:e.action}))},{filename:`priority-score-breakdown.csv`,rows:e.output.queue.map((e,t)=>({rank:t+1,target:e.target,source:e.source,score:e.scoreBreakdown.final,impact_score:e.scoreBreakdown.impact,source_weight:e.scoreBreakdown.source,confidence_weight:e.scoreBreakdown.confidence,effort_weight:e.scoreBreakdown.effort,verification_weight:e.scoreBreakdown.verification,template_weight:e.scoreBreakdown.template,analytics_weight:e.scoreBreakdown.analytics}))},{filename:`priority-grouped-findings.csv`,rows:e.output.queue.flatMap((e,t)=>(e.grouped?.findings??[]).map((e,n)=>({queue_rank:t+1,finding_rank:n+1,source:e.source,category:e.category,score:e.score,impact:e.impact,impact_kind:e.impactKind,confidence:e.confidence,title:e.title,target:e.target,template:e.template?.label,ga4_sessions:e.analytics?.sessions,evidence:e.evidence,action:e.action})))},{filename:`workflow-steps.csv`,rows:e.steps.map((e,t)=>({rank:t+1,tool:e.tool,status:e.status,summary:e.summary}))},{filename:`workflow-actions.csv`,rows:e.actions.map((e,t)=>({rank:t+1,title:e.title,confidence:e.confidence,action:e.action}))},{filename:`warnings.csv`,rows:e.output.warnings.map((e,t)=>({rank:t+1,warning:e}))},...vC(`diagnosis`,_C(e.output.diagnosis))])}function xC(e){return dC([{filename:`pseo-caveats.csv`,rows:e.caveats.map((e,t)=>({rank:t+1,caveat:e}))},{filename:`templates.csv`,rows:e.templates.map((e,t)=>({rank:t+1,template:e.signature,verdict:e.verdict,confidence:e.confidence,urls:e.urlCount,clicks:e.metrics.clicks,impressions:e.metrics.impressions,ctr:e.metrics.ctr,position:e.metrics.position,entity_fit_impression_share:e.metrics.entityFit.impressionShare,discovered_urls:e.population.discoveredUrls,gsc_visible_urls:e.population.gscVisibleUrls,untested_urls:e.population.untestedUrls,crawl_attempts:e.crawl.attempted,crawled_urls:e.crawl.usable,crawl_blocked_or_failed:e.crawl.blockedOrFailed,inspection_attempts:e.inspection.attempted,inspected_urls:e.inspection.succeeded,not_indexed:e.inspection.notIndexed,inspection_unknown:e.inspection.unknown,evidence:e.evidence.join(`; `),action:e.recommendation}))},{filename:`template-queries.csv`,rows:e.templates.flatMap(e=>e.metrics.topQueries.map((t,n)=>({template:e.signature,rank:n+1,query:t.query,clicks:t.clicks,impressions:t.impressions,position:t.position})))},{filename:`demand-patterns.csv`,rows:e.templates.flatMap(e=>e.metrics.queryPatterns.map((t,n)=>({template:e.signature,rank:n+1,label:fC(t.label),impressions:t.impressions,clicks:t.clicks,sample_queries:t.examples.join(`; `)})))},{filename:`sample-coverage.csv`,rows:e.templates.flatMap(e=>e.crawl.samples.flatMap(t=>t.queryCoverage?[{template:e.signature,url:t.url,query:t.queryCoverage.query,classification:t.queryCoverage.classification,title_coverage:t.queryCoverage.titleCoverage,h1_coverage:t.queryCoverage.h1Coverage,body_coverage:t.queryCoverage.bodyCoverage,missing_terms:t.queryCoverage.missingTerms.join(`; `)}]:[]))},{filename:`weak-entity-fit.csv`,rows:e.templates.flatMap(e=>e.metrics.entityFit.weakExamples.map(t=>({template:e.signature,query:t.query,url:t.url,path_terms:t.pathTerms.join(`; `)})))},{filename:`crawl-samples.csv`,rows:e.templates.flatMap(e=>e.crawl.samples.map(t=>({template:e.signature,url:t.url,status:t.status,final_url:t.finalUrl,title:t.title,meta_description:t.metaDescription,h1:t.h1,word_count:t.wordCount,technical_status:t.technicalStatus,canonical:t.canonical,meta_robots:t.metaRobots,x_robots_tag:t.xRobotsTag,warning:t.warning,blocked:t.fetchDiagnostics?.blocked,rendered:t.fetchDiagnostics?.rendered,cache:t.fetchDiagnostics?.cache})))},{filename:`inspection-samples.csv`,rows:e.templates.flatMap(e=>e.inspection.samples.map(t=>({template:e.signature,url:t.url,verdict:t.verdict,index_status:t.indexStatus,coverage_state:t.coverageState,indexing_state:t.indexingState,page_fetch_state:t.pageFetchState,google_canonical:t.googleCanonical,user_canonical:t.userCanonical,last_crawl_time:t.lastCrawlTime,warning:t.warning})))}])}const SC={high:5e3,medium:1200,low:100},CC={canonical:`medium`,content:`high`,geo:`medium`,headings:`low`,images:`medium`,indexability:`medium`,international:`low`,metadata:`low`,mobile:`low`,response:`medium`,security:`medium`,social:`low`,"structured-data":`medium`},wC={low:60,medium:30,high:0},TC={high:25,medium:15,low:2},EC={high:1,medium:.75,low:.15};function DC(e,t){if(!e.includes(`*`))return t.includes(e);let n=e.split(`*`),r=0;for(let e of n){if(!e)continue;let n=t.slice(r).indexOf(e);if(n===-1)return!1;r+=n+e.length}return!0}function OC(e,t){return t.projectId&&e.projectId!==t.projectId?[]:e.issues.filter(e=>!(t.category&&e.category!==t.category||t.severity&&e.severity!==t.severity||t.url&&!DC(t.url,e.url)||(t.recommendation??`fix`)!==yb(e.ruleId)))}function kC(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)}return[...t.values()]}function AC(e,t){let n=new Set(t),r=e.pages.filter(e=>n.has(e.url)||n.has(e.finalUrl)),i=r.map(e=>e.searchMetrics).filter(e=>!!e),a=i.reduce((e,t)=>e+t.clicks,0),o=i.reduce((e,t)=>e+t.impressions,0),s=r.reduce((e,t)=>e+(t.analytics?.sessions??0),0),c=r.reduce((e,t)=>e+(t.analytics?.totalUsers??0),0),l=r.reduce((e,t)=>e+(t.analytics?.conversions??0),0),u=i.length?i.reduce((e,t)=>e+t.position,0)/i.length:void 0;return{searchVisibleUrls:i.length,clicks:a,impressions:o,sessions:s,totalUsers:c,conversions:l,avgPosition:u}}function jC(e,t,n){return`${e.searchVisibleUrls?`${e.searchVisibleUrls} affected URLs have GSC visibility (${e.clicks} clicks, ${e.impressions} impressions).`:n&&![`joined`,`none`].includes(n)?`No joined GSC visibility is available for these affected URLs; Search Console evidence is ${n}.`:`No affected URL has joined GSC visibility yet.`}${e.sessions?` GA4 adds ${e.sessions} sessions and ${e.conversions} conversions from affected landing pages.`:``} ${t===`review`?`This is a review observation. Confirm it before scheduling implementation work.`:`This ranks as a fix. Severity is ${e.severity}; ${e.affectedUrls} URL${e.affectedUrls===1?` is`:`s are`} affected; estimated effort is ${e.effort}.`}`}function MC(e,t){return`seo crawl ${e.config.url} --severity ${t} --max-pages ${Math.min(e.config.maxPages,100)}`}function NC(e,t={}){return kC(OC(e,t)).map(t=>{let n=AC(e,t.sampleUrls),r=CC[t.category]??`medium`,i={severity:SC[t.severity],affectedUrls:t.count,searchVisibleUrls:n.searchVisibleUrls,clicks:n.clicks,impressions:n.impressions,sessions:n.sessions,totalUsers:n.totalUsers,conversions:n.conversions,avgPosition:n.avgPosition,effort:r,effortScore:wC[r]},a=EC[t.severity],o=i.severity+i.affectedUrls*TC[t.severity]+i.searchVisibleUrls*100*a+i.clicks*20*a+Math.min(i.impressions,1e4)/25*a+i.sessions*2*a+i.conversions*100*a+i.effortScore,s=vb(t.ruleId),c=yb(t.ruleId);return{...t,recommendation:c,score:Math.round(o),scoreFactors:i,whyThisRanks:jC(i,c,e.dataSources?.searchConsole.status),howToFix:s?.howToFix??``,howToVerify:s?.howToVerify??``,verification:{command:MC(e,t.severity),expected:s?.howToVerify??``}}}).sort((e,t)=>t.score-e.score||SC[t.severity]-SC[e.severity]||t.count-e.count||e.ruleId.localeCompare(t.ruleId)).slice(0,t.limit??10)}function PC(e,t={}){return NC(e,{...t,recommendation:`review`})}const FC=[`rule_id`,`title`,`category`,`severity`,`url`,`detail`,`clicks`,`impressions`,`position`],IC=[`url`,`final_url`,`status`,`indexable`,`title`,`meta_description`,`canonical`,`word_count`,`content_extractor`,`content_extractor_type`,`content_extraction_fallback`,`word_count_source`,`internal_links`,`internal_inlinks`,`internal_authority_score`,`external_links`,`images_total`,`images_missing_alt`,`oversized_image_candidates`,`schema_types`,`clicks`,`impressions`,`sessions`];function LC(e){return mC(e.issues.map(e=>({rule_id:e.ruleId,title:e.title,category:e.category,severity:e.severity,url:e.url,detail:e.detail,clicks:e.searchMetrics?.clicks,impressions:e.searchMetrics?.impressions,position:e.searchMetrics?.position})),FC)}function RC(e){return mC(e.pages.map(e=>({url:e.url,final_url:e.finalUrl,status:e.status,indexable:e.indexable,title:e.title,meta_description:e.metaDescription,canonical:e.canonical,word_count:e.wordCount,content_extractor:e.contentExtraction?.used,content_extractor_type:e.contentExtraction?.extractorType,content_extraction_fallback:e.contentExtraction?.fallback,word_count_source:e.contentExtraction?.wordCountSource,internal_links:e.outgoingInternalCount,internal_inlinks:e.internalInlinkCount,internal_authority_score:e.internalLinkAuthorityScore,external_links:e.outgoingExternalCount,images_total:e.imagesTotal,images_missing_alt:e.imagesMissingAlt,oversized_image_candidates:e.oversizedImageCandidates?.length??0,schema_types:e.schemaTypes?.join(`|`),clicks:e.searchMetrics?.clicks,impressions:e.searchMetrics?.impressions,sessions:e.analytics?.sessions})),IC)}function zC(e,t=NC(e)){let n=PC(e),r=e.requestEvidenceStatus===`available`?`${e.requests.length} requests`:e.requestEvidenceStatus===`partial`?`${e.requests.length} observed requests; some started requests were still in flight when the crawl stopped`:`request evidence unavailable for this legacy report`,i=[`Crawl report for ${e.config.url}`,``,`Status: ${e.status}`,`Documents: ${e.summary.totalPages} retained; ${r}, ${e.summary.discoveredUrls} URLs discovered, ${e.summary.failedUrls} failed, ${e.summary.skippedUrls} skipped`,`Issues: ${e.issues.length} (${e.summary.highIssues} high, ${e.summary.mediumIssues} medium, ${e.summary.lowIssues} low)`];if(e.externalLinkVerification){let t=e.externalLinkVerification;i.push(`External links: ${t.fetchedUrls} fetched, ${t.failedUrls} request failure${t.failedUrls===1?``:`s`}, ${t.deferredUrls} deferred (${t.dataStatus})`)}if(t.length){i.push(``,`Prioritised fixes`);for(let e of t.slice(0,5))i.push(`- ${e.title} (${e.severity}, ${e.count} URL${e.count===1?``:`s`}): ${e.howToFix}`),e.sampleUrls[0]&&i.push(` First URL: ${e.sampleUrls[0]}`)}else i.push(``,`No prioritised fixes found.`);if(n.length){i.push(``,`Review observations (check before scheduling work)`);for(let e of n.slice(0,5))i.push(`- ${e.title} (${e.severity}, ${e.count} URL${e.count===1?``:`s`}): ${e.howToFix}`),e.sampleUrls[0]&&i.push(` First URL: ${e.sampleUrls[0]}`)}i.push(``,`Next commands`,`- seo crawl ${e.config.url} --json`);for(let e of[...new Set(t.slice(0,3).map(e=>e.verification.command))])i.push(`- ${e}`);return e.warnings.length&&i.push(``,`Warnings`,...e.warnings.map(e=>`- ${e}`)),e.caveats.length&&i.push(``,`Caveats`,...e.caveats.map(e=>`- ${e}`)),`${i.join(`
506
+ `)}\n`}function BC(e,t=NC(e)){let n=PC(e),r=e.issueGroups.map(e=>{let t=vb(e.ruleId);return`<tr><td>${HC(e.severity)}</td><td>${HC(e.title)}</td><td>${e.count}</td><td>${HC(t?.howToFix??``)}</td><td>${HC(e.sampleUrls[0]??``)}</td></tr>`}).join(`
507
+ `),i=t.slice(0,10).map(e=>`<li><strong>${HC(e.title)}</strong><br>${HC(e.howToFix)}<br><span>Verify: ${HC(e.howToVerify)}</span><br><code>${HC(e.verification.command)}</code></li>`).join(`
508
+ `),a=n.slice(0,10).map(e=>`<li><strong>${HC(e.title)}</strong><br>${HC(e.howToFix)}<br><span>Check before scheduling work. ${HC(e.howToVerify)}</span></li>`).join(`
509
+ `),o=e.caveats.map(e=>`<li>${HC(e)}</li>`).join(`
510
+ `),s=e.warnings.map(e=>`<li>${HC(e)}</li>`).join(`
509
511
  `);return`<!doctype html>
510
512
  <html lang="en">
511
513
  <head>
512
514
  <meta charset="utf-8">
513
515
  <meta name="viewport" content="width=device-width, initial-scale=1">
514
- <title>Crawl report for ${uC(e.config.url)}</title>
516
+ <title>Crawl report for ${HC(e.config.url)}</title>
515
517
  <style>
516
518
  body { color: #17202a; font: 15px/1.5 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; margin: 32px; max-width: 1120px; }
517
519
  h1, h2 { line-height: 1.2; }
@@ -527,9 +529,9 @@ CREATE INDEX IF NOT EXISTS idx_performance_reports_url ON performance_reports(ur
527
529
  </head>
528
530
  <body>
529
531
  <h1>Crawl report</h1>
530
- <p>${uC(e.config.url)}</p>
532
+ <p>${HC(e.config.url)}</p>
531
533
  <section class="summary">
532
- <div class="metric"><span>Status</span><strong>${uC(e.status)}</strong></div>
534
+ <div class="metric"><span>Status</span><strong>${HC(e.status)}</strong></div>
533
535
  <div class="metric"><span>Documents</span><strong>${e.summary.totalPages}</strong></div>
534
536
  <div class="metric"><span>Requests</span><strong>${e.requestEvidenceStatus===`unavailable`?`n/a`:e.requests.length}${e.requestEvidenceStatus===`partial`?` (partial)`:``}</strong></div>
535
537
  <div class="metric"><span>Discovered</span><strong>${e.summary.discoveredUrls}</strong></div>
@@ -556,24 +558,22 @@ CREATE INDEX IF NOT EXISTS idx_performance_reports_url ON performance_reports(ur
556
558
  </table>
557
559
  </body>
558
560
  </html>
559
- `}function lC(e,t=tC(e)){let n=nC(e),r=[`# Crawl Implementation Tickets`,``,`Source: ${e.config.url}`,`Report: ${e.id}`,`Status: ${e.status}`,``];e.caveats.length&&r.push(`## Caveats`,``,...e.caveats.map(e=>`- ${e}`),``),e.warnings.length&&r.push(`## Warnings`,``,...e.warnings.map(e=>`- ${e}`),``);for(let[e,n]of t.entries())r.push(`## ${e+1}. ${n.title}`,``,`- [ ] Fix ${n.count} affected URL${n.count===1?``:`s`}`,`- Severity: ${n.severity}`,`- Rule: ${n.ruleId}`,`- Why this ranks: ${n.whyThisRanks}`,`- Plain-English fix: ${n.howToFix}`,`- Verify: ${n.howToVerify}`,`- Command: ${n.verification.command}`,``,`Affected URLs:`,...n.sampleUrls.slice(0,10).map(e=>`- ${e}`),``);return t.length||r.push(`No implementation tickets were generated.`),n.length&&r.push(`## Review observations`,``,`These are not implementation tickets. Check the evidence before deciding whether to work on them.`,``,...n.slice(0,10).flatMap(e=>[`- ${e.title} (${e.severity}, ${e.count} URL${e.count===1?``:`s`}): ${e.howToFix}`,` - First URL: ${e.sampleUrls[0]??`No sample URL recorded`}`]),``),`${r.join(`
560
- `)}\n`}function uC(e){return e.replaceAll(`&`,`&amp;`).replaceAll(`<`,`&lt;`).replaceAll(`>`,`&gt;`).replaceAll(`"`,`&quot;`).replaceAll(`'`,`&#39;`)}const dC=new Set([`connection_error`,`client_error`,`server_error`,`robots_blocked`,`noindex`,`x_robots_noindex`,`canonicalized_page`]);function fC(e){let t=new Map;for(let n of e.issues){if(!dC.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=am({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 pC(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.skippedUrls>0&&t.push(`skipped-urls`),e.summary.failedRequests>0&&t.push(`failed-requests`),e.summary.extractionFailures>0&&t.push(`extraction-failures`),t}function mC(e,t={}){let n=fC(e),r=t.limit??50,i=n.slice(0,r),a=pC(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,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 hC(e){return{maxPages:e.config.maxPages,maxDepth:e.config.maxDepth,timeoutMs:e.config.timeoutMs,searchConsolePageLimit:e.dataSources?.searchConsole.pageLimit,searchConsoleRetainedRowLimit:e.dataSources?.searchConsole.retainedRowLimit,analyticsRetainedRowLimit:e.dataSources?.analytics.retainedRowLimit}}function gC(e){return{startUrl:e.config.url,mode:e.config.mode,explicitUrls:e.config.urls,include:e.config.include,exclude:e.config.exclude,respectRobots:e.config.respectRobots,useSitemap:e.config.useSitemap,checkExternal:e.config.checkExternal,js:e.config.js}}function _C(e){return!!(e.dataSources?.searchConsole.pageLimitReached||e.dataSources?.searchConsole.retainedRowLimitReached||e.dataSources?.analytics.retainedRowLimitReached)}function vC(e){let t=[];return e.status!==`completed`&&t.push(`report-status-${e.status}`),e.summary.pageLimitReached&&t.push(`crawl-page-limit-reached`),e.requestEvidenceStatus!==`available`&&t.push(`request-evidence-${e.requestEvidenceStatus}`),e.summary.skippedUrls>0&&t.push(`urls-skipped`),e.summary.failedRequests>0&&t.push(`requests-failed`),e.summary.abortedRequests>0&&t.push(`requests-aborted`),e.summary.extractionFailures>0&&t.push(`document-extraction-failed`),e.dataSources?.searchConsole.pageLimitReached&&t.push(`search-console-page-limit-reached`),e.dataSources?.searchConsole.retainedRowLimitReached&&t.push(`search-console-row-limit-reached`),e.dataSources?.analytics.retainedRowLimitReached&&t.push(`analytics-row-limit-reached`),(e.dataSources?.searchConsole.status===`partial`||e.dataSources?.searchConsole.status===`unavailable`)&&t.push(`search-console-${e.dataSources.searchConsole.status}`),(e.dataSources?.analytics.status===`partial`||e.dataSources?.analytics.status===`unavailable`)&&t.push(`analytics-${e.dataSources.analytics.status}`),t}function yC(e){let t=vC(e);return{status:e.status===`failed`?`failed`:t.length?`partial`:`complete`,truncated:e.summary.pageLimitReached||_C(e),reasons:t,requestEvidenceStatus:e.requestEvidenceStatus,pageLimitReached:e.summary.pageLimitReached,skippedUrls:e.summary.skippedUrls,failedRequests:e.summary.failedRequests,abortedRequests:e.summary.abortedRequests,extractionFailures:e.summary.extractionFailures,dataSources:e.dataSources}}function bC(e){let t=yC(e);return{id:e.id,definitionId:e.definitionId,generatedAt:e.generatedAt,projectId:e.projectId,site:e.site,ga4PropertyId:e.ga4PropertyId,url:e.config.url,status:e.status,configHash:e.configHash,config:e.config,caps:hC(e),requestScope:gC(e),requestEvidenceStatus:e.requestEvidenceStatus,dataSources:e.dataSources,warnings:e.warnings,caveats:e.caveats,completeness:t,summary:e.summary}}function xC(e,t){return JSON.stringify(e)===JSON.stringify(t)}function SC(e){let t=[];return e.comparability.sameSite||t.push(`The reports use different site properties.`),e.comparability.sameRequestScope||t.push(`The reports use different crawl request scopes; added and removed pages may reflect scope changes.`),e.comparability.sameCaps||t.push(`The reports use different crawl or provider caps; compare retained coverage before interpreting deltas.`),!e.comparability.sameConfigHash&&e.comparability.sameRequestScope&&e.comparability.sameCaps&&t.push(`The crawl configurations differ outside request scope and caps; inspect both config objects before attribution.`),e.comparability.sameDefinitionId||t.push(`The reports use different crawl definitions; do not attribute every delta to the site or a release.`),e.before.completeness.status!==`complete`&&t.push(`The baseline report is ${e.before.completeness.status}: ${e.before.completeness.reasons.join(`, `)}.`),e.after.completeness.status!==`complete`&&t.push(`The newer report is ${e.after.completeness.status}: ${e.after.completeness.reasons.join(`, `)}.`),(e.before.completeness.truncated||e.after.completeness.truncated)&&t.push(`At least one report was truncated; page and issue deltas describe retained evidence only.`),e.after.generatedAt<=e.before.generatedAt&&t.push(`The report labelled after is not newer than the report labelled before.`),(e.before.warnings.length||e.after.warnings.length||e.before.caveats.length||e.after.caveats.length)&&t.push(`One or both saved reports contain source warnings or caveats; inspect them before acting.`),t}function CC(e){let t=bC(e.before),n=bC(e.after),r={sameDefinitionId:t.definitionId===n.definitionId,sameConfigHash:t.configHash===n.configHash,sameSite:t.site===n.site,sameStartUrl:t.url===n.url,sameMode:t.config.mode===n.config.mode,sameRequestScope:xC(t.requestScope,n.requestScope),sameCaps:xC(t.caps,n.caps)},i=Object.values(r).every(Boolean),a=t.completeness.status===`complete`&&n.completeness.status===`complete`,o={status:i&&a?`comparable`:`review-required`,...r};return{before:t,after:n,comparability:o,completeness:{status:t.completeness.status===`failed`||n.completeness.status===`failed`?`failed`:t.completeness.status===`partial`||n.completeness.status===`partial`?`partial`:`complete`,truncated:t.completeness.truncated||n.completeness.truncated,before:t.completeness,after:n.completeness},caveats:SC({before:t,after:n,comparability:o})}}function wC(e){return{url:e.url,finalUrl:e.finalUrl,status:e.status,title:e.title,metaDescription:e.metaDescription,canonical:e.canonical,h1:e.h1,indexable:e.indexable,wordCount:e.wordCount}}function TC(e,t){return(e?.status??200)<400&&(t?.status??200)>=400}function EC(e,t){return(e?.status??200)>=400&&(t?.status??500)<400}function DC(e,t){let n=[];return e.finalUrl!==t.finalUrl&&n.push(`final_url`),e.status!==t.status&&n.push(`status`),e.title!==t.title&&n.push(`title`),e.metaDescription!==t.metaDescription&&n.push(`meta_description`),e.canonical!==t.canonical&&n.push(`canonical`),e.h1!==t.h1&&n.push(`h1`),e.indexable!==t.indexable&&n.push(`indexability`),e.contentHash!==t.contentHash&&n.push(`content`),e.wordCount!==t.wordCount&&n.push(`word_count`),n}function OC(e,t){let n=new Map(e.pages.map(e=>[e.url,e])),r=new Map(t.pages.map(e=>[e.url,e])),i=[];for(let[e,t]of r){let r=n.get(e);if(!r){i.push({url:e,kind:`added`,changes:[`url_added`],after:wC(t)});continue}let a=DC(r,t);a.length&&i.push({url:e,kind:`changed`,changes:a,before:wC(r),after:wC(t)})}for(let[e,t]of n)r.has(e)||i.push({url:e,kind:`removed`,changes:[`url_removed`],before:wC(t)});return i.sort((e,t)=>{let n={changed:0,added:1,removed:2};return n[e.kind]-n[t.kind]||e.url.localeCompare(t.url)})}function kC(e){return new Map(e.map(e=>[e.ruleId,e]))}function AC(e,t){let n=kC(e.issueGroups),r=kC(t.issueGroups),i=new Set([...n.keys(),...r.keys()]),a=[];for(let e of i){let t=n.get(e),i=r.get(e),o=t?.count??0,s=i?.count??0;if(o===s)continue;let c=i??t;c&&a.push({ruleId:e,title:c.title,severity:c.severity,category:c.category,before:o,after:s,delta:s-o})}return a.sort((e,t)=>Math.abs(t.delta)-Math.abs(e.delta)||e.ruleId.localeCompare(t.ruleId))}function jC(e){return e.addedPages===0&&e.removedPages===0&&e.changedPages===0&&e.issueDelta===0?`No material crawl changes were found between these saved reports.`:e.newStatusErrors>0||e.issueGroupsWorse>e.issueGroupsBetter?`The newer crawl has regressions that should be reviewed before more SEO work.`:e.fixedStatusErrors>0||e.issueGroupsBetter>e.issueGroupsWorse?`The newer crawl looks healthier, with some technical issues reduced or fixed.`:`The newer crawl changed, but the overall technical picture is broadly similar.`}function MC(e){let t=[],n=e.changes.filter(e=>TC(e.before,e.after));n.length&&t.push({title:`New status errors appeared`,plainEnglish:`${n.length} URL now returns a 4xx or 5xx status when it did not before.`,action:`Check deploys, redirects, routing, and canonical URL changes before changing content.`,urls:n.slice(0,10).map(e=>e.url)});let r=e.changes.filter(e=>e.changes.includes(`indexability`));r.length&&t.push({title:`Indexability changed`,plainEnglish:`${r.length} URL changed indexability state.`,action:`Confirm noindex, robots, canonical, auth, and rendering changes were intentional.`,urls:r.slice(0,10).map(e=>e.url)});let i=e.issues.filter(e=>e.delta>0);if(i.length){let e=i[0];t.push({title:e?.title??`Crawl issues increased`,plainEnglish:`${e?.ruleId??`A rule`} increased by ${e?.delta??0} affected pages.`,action:`Review the affected rule and compare changed templates before shipping more pages.`})}let a=e.changes.filter(e=>e.changes.includes(`title`));return a.length&&t.push({title:`Titles changed`,plainEnglish:`${a.length} page title changed between snapshots.`,action:`Compare title changes against top GSC queries before treating traffic movement as content decay.`,urls:a.slice(0,10).map(e=>e.url)}),!t.length&&e.summary.changedPages>0&&t.push({title:`Review changed pages`,plainEnglish:`${e.summary.changedPages} page changed without an obvious technical regression.`,action:`Use the JSON output to inspect changed fields, then decide whether to annotate the change as an SEO test.`}),t}function NC(e){let t=CC(e),n=OC(e.before,e.after),r=AC(e.before,e.after),i={pageDelta:e.after.summary.totalPages-e.before.summary.totalPages,issueDelta:e.after.issues.length-e.before.issues.length,addedPages:n.filter(e=>e.kind===`added`).length,removedPages:n.filter(e=>e.kind===`removed`).length,changedPages:n.filter(e=>e.kind===`changed`).length,newStatusErrors:n.filter(e=>TC(e.before,e.after)).length,fixedStatusErrors:n.filter(e=>EC(e.before,e.after)).length,indexabilityFlips:n.filter(e=>e.changes.includes(`indexability`)).length,titleChanges:n.filter(e=>e.changes.includes(`title`)).length,contentChanges:n.filter(e=>e.changes.includes(`content`)).length,issueGroupsWorse:r.filter(e=>e.delta>0).length,issueGroupsBetter:r.filter(e=>e.delta<0).length};return{schemaVersion:1,...t,summary:i,headline:jC(i),topActions:MC({changes:n,issues:r,summary:i}),pageChanges:n,issueChanges:r}}function PC(e){return Math.ceil(e.length/4)}function FC(e){return new URL(e).hostname.replace(/^www\./,``)}function IC(e){return e.title??e.h1??(new URL(e.finalUrl).pathname||e.finalUrl)}function LC(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 RC(e){return/\/(login|logout|cart|checkout|account|admin|wp-admin|search|tag|privacy|terms|cookie|legal)\b/i.test(new URL(e).pathname)}function zC(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 BC(e,t={}){return e.pages.filter(e=>e.indexable&&e.status<400).filter(e=>!RC(e.finalUrl)).filter(e=>!zC(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 VC(e){let t=e.ai?.llmsTxt,n=BC(e),r=[];return n.length<3&&r.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:n.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?`An optional llms.txt file is present. It has no Google Search visibility impact.`:`No llms.txt file was found. This is not an SEO issue and requires no action.`,issues:r,recommendedPages:n.slice(0,25).map(e=>({url:e.finalUrl,title:e.title,section:LC(e),reason:(e.internalLinkAuthorityScore??0)>0?`High internal link authority in this crawl.`:`Indexable page with useful content signals.`}))}}function HC(e,t={}){let n=t.maxUrls??250,r=t.tokenBudget??12e3,i=t.title??FC(e.config.url),a=t.description??`Curated entry points for agents reading ${FC(e.config.url)}.`,o=BC(e,t).slice(0,n),s=new Map;for(let e of o){let t=LC(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=IC(e).replace(/\]/g,`\\]`),a=e.metaDescription?` - ${e.metaDescription.replace(/\s+/g,` `).slice(0,160)}`:``,o=`- [${t}](${e.finalUrl})${a}`;if(PC([...c,...n,o,``].join(`
561
- `))>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(`
562
- `)}\n`;return{content:d,includedUrls:l,estimatedTokens:PC(d),sections:u}}function UC(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)return t}return n.length-r.length}function WC(e){return`concepts/${e.toLowerCase().replace(/^https?:\/\//,``).replace(/[^a-z0-9]+/g,`-`).replace(/^-+|-+$/g,``).slice(0,62)||`page`}-${_(`sha256`).update(e).digest(`hex`).slice(0,12)}.md`}function GC(e){let t=[`---`];for(let[n,r]of Object.entries(e))if(r!==void 0)if(Array.isArray(r)){if(!r.length)continue;t.push(`${n}:`);for(let e of r)t.push(` - ${JSON.stringify(e)}`)}else t.push(`${n}: ${JSON.stringify(r)}`);return t.push(`---`,``),t.join(`
563
- `)}function KC(e){return[...e].map(e=>{let t=e.codePointAt(0)??0;return t<32||t===127?` `:e}).join(``).replace(/\s+/g,` `).trim()}function qC(e){return KC(e.title??e.h1??(new URL(e.finalUrl).pathname||e.finalUrl))}function JC(e){let t=new URL(e.finalUrl).pathname.toLowerCase();return e.schemaTypes?.includes(`Article`)||/\/(blog|article|news)\b/.test(t)?`article`:e.schemaTypes?.some(e=>/Product|Service|SoftwareApplication/i.test(e))?`product`:/\/(about|company|team|contact)\b/.test(t)?`organization`:`webpage`}function YC(e){let t=qC(e),n=[GC({type:JC(e),title:t,url:e.finalUrl,status:e.status,indexable:e.indexable,canonical:e.canonical,schemaTypes:e.schemaTypes,sameAs:e.schemaSameAs}),`# ${t}`,``,e.metaDescription?`Summary: ${KC(e.metaDescription)}`:void 0,e.contentSample?`Extract: ${KC(e.contentSample)}`:void 0,``,`## Signals`,``,`- URL: ${e.finalUrl}`,`- Status: ${e.status}`,`- Indexable: ${e.indexable?`yes`:`no`}`,`- Word count: ${e.wordCount}`,`- Structured data: ${(e.schemaTypes??[]).join(`, `)||`none detected`}`,`- Internal inlinks: ${e.internalInlinkCount??0}`,``,`# Citations`,``,`- [Source page](<${e.finalUrl}>)`,``].filter(e=>e!==void 0);return{path:WC(e.finalUrl),content:n.join(`
564
- `)}}function XC(e){if(e===void 0)return 500;if(!Number.isInteger(e)||e<1||e>5e3)throw new A(`INVALID_INPUT`,`maxConcepts must be a whole number between 1 and 5000.`);return e}function ZC(e,t){let n=KC(e??new URL(t.config.url).hostname.replace(/^www\./,``));if(!n||n.length>200)throw new A(`INVALID_INPUT`,`OKF title must contain 1 to 200 characters on one line.`);return n}function QC(e,t){let n=XC(t),r=e.pages.filter(e=>e.indexable&&e.status>=200&&e.status<300&&!e.blocked&&!e.error).sort((e,t)=>(t.searchMetrics?.clicks??0)-(e.searchMetrics?.clicks??0)||(t.searchMetrics?.impressions??0)-(e.searchMetrics?.impressions??0)||(t.internalLinkAuthorityScore??0)-(e.internalLinkAuthorityScore??0)||(t.internalInlinkCount??0)-(e.internalInlinkCount??0)||UC(e.finalUrl,t.finalUrl)),i=new Map;for(let e of r)i.has(e.finalUrl)||i.set(e.finalUrl,e);let a=[...i.values()];return{pages:a.slice(0,n),sourcePages:e.pages.length,eligiblePages:a.length,duplicateFinalUrls:r.length-a.length,limit:n}}function $C(e,t={}){let n=ZC(t.title,e),r=QC(e,t.maxConcepts),i=r.pages,a=[...e.caveats,...e.status===`completed`?[]:[`Source crawl status is ${e.status}; the bundle may not represent the complete site inventory.`],...r.duplicateFinalUrls?[`${r.duplicateFinalUrls} duplicate final URL${r.duplicateFinalUrls===1?` was`:`s were`} deduplicated before concept export.`]:[],...r.eligiblePages>i.length?[`OKF selected ${i.length} of ${r.eligiblePages} eligible 2xx indexable pages using observed search clicks, impressions, internal authority, inlinks, and a stable URL tie-break.`]:[]],o=i.map(YC),s=[{path:`index.md`,content:[GC({okf:`0.1`,type:`index`,title:n}),`# ${n}`,``,`Source crawl: ${e.id}`,`Generated from: ${e.config.url}`,``,`## Contents`,``,`- [Concepts](concepts/index.md)`,`- [Inventory](inventory/pages.md)`,`- [Graph](graph/links.md)`,`- [Caveats](caveats.md)`,`- [Log](log.md)`,``].join(`
561
+ `}function VC(e,t=NC(e)){let n=PC(e),r=[`# Crawl Implementation Tickets`,``,`Source: ${e.config.url}`,`Report: ${e.id}`,`Status: ${e.status}`,``];e.caveats.length&&r.push(`## Caveats`,``,...e.caveats.map(e=>`- ${e}`),``),e.warnings.length&&r.push(`## Warnings`,``,...e.warnings.map(e=>`- ${e}`),``);for(let[e,n]of t.entries())r.push(`## ${e+1}. ${n.title}`,``,`- [ ] Fix ${n.count} affected URL${n.count===1?``:`s`}`,`- Severity: ${n.severity}`,`- Rule: ${n.ruleId}`,`- Why this ranks: ${n.whyThisRanks}`,`- Plain-English fix: ${n.howToFix}`,`- Verify: ${n.howToVerify}`,`- Command: ${n.verification.command}`,``,`Affected URLs:`,...n.sampleUrls.slice(0,10).map(e=>`- ${e}`),``);return t.length||r.push(`No implementation tickets were generated.`),n.length&&r.push(`## Review observations`,``,`These are not implementation tickets. Check the evidence before deciding whether to work on them.`,``,...n.slice(0,10).flatMap(e=>[`- ${e.title} (${e.severity}, ${e.count} URL${e.count===1?``:`s`}): ${e.howToFix}`,` - First URL: ${e.sampleUrls[0]??`No sample URL recorded`}`]),``),`${r.join(`
562
+ `)}\n`}function HC(e){return e.replaceAll(`&`,`&amp;`).replaceAll(`<`,`&lt;`).replaceAll(`>`,`&gt;`).replaceAll(`"`,`&quot;`).replaceAll(`'`,`&#39;`)}function UC(e){return{maxPages:e.config.maxPages,maxDepth:e.config.maxDepth,timeoutMs:e.config.timeoutMs,searchConsolePageLimit:e.dataSources?.searchConsole.pageLimit,searchConsoleRetainedRowLimit:e.dataSources?.searchConsole.retainedRowLimit,analyticsRetainedRowLimit:e.dataSources?.analytics.retainedRowLimit}}function WC(e){return{startUrl:e.config.url,mode:e.config.mode,explicitUrls:e.config.urls,include:e.config.include,exclude:e.config.exclude,respectRobots:e.config.respectRobots,useSitemap:e.config.useSitemap,checkExternal:e.config.checkExternal,js:e.config.js}}function GC(e){return!!(e.dataSources?.searchConsole.pageLimitReached||e.dataSources?.searchConsole.retainedRowLimitReached||e.dataSources?.analytics.retainedRowLimitReached)}function KC(e){let t=[];return e.status!==`completed`&&t.push(`report-status-${e.status}`),e.summary.pageLimitReached&&t.push(`crawl-page-limit-reached`),e.requestEvidenceStatus!==`available`&&t.push(`request-evidence-${e.requestEvidenceStatus}`),e.summary.skippedUrls>0&&t.push(`urls-skipped`),e.summary.failedRequests>0&&t.push(`requests-failed`),e.summary.abortedRequests>0&&t.push(`requests-aborted`),e.summary.extractionFailures>0&&t.push(`document-extraction-failed`),e.dataSources?.searchConsole.pageLimitReached&&t.push(`search-console-page-limit-reached`),e.dataSources?.searchConsole.retainedRowLimitReached&&t.push(`search-console-row-limit-reached`),e.dataSources?.analytics.retainedRowLimitReached&&t.push(`analytics-row-limit-reached`),(e.dataSources?.searchConsole.status===`partial`||e.dataSources?.searchConsole.status===`unavailable`)&&t.push(`search-console-${e.dataSources.searchConsole.status}`),(e.dataSources?.analytics.status===`partial`||e.dataSources?.analytics.status===`unavailable`)&&t.push(`analytics-${e.dataSources.analytics.status}`),t}function qC(e){let t=KC(e);return{status:e.status===`failed`?`failed`:t.length?`partial`:`complete`,truncated:e.summary.pageLimitReached||GC(e),reasons:t,requestEvidenceStatus:e.requestEvidenceStatus,pageLimitReached:e.summary.pageLimitReached,skippedUrls:e.summary.skippedUrls,failedRequests:e.summary.failedRequests,abortedRequests:e.summary.abortedRequests,extractionFailures:e.summary.extractionFailures,dataSources:e.dataSources}}function JC(e){let t=qC(e);return{id:e.id,definitionId:e.definitionId,generatedAt:e.generatedAt,projectId:e.projectId,site:e.site,ga4PropertyId:e.ga4PropertyId,url:e.config.url,status:e.status,configHash:e.configHash,config:e.config,caps:UC(e),requestScope:WC(e),requestEvidenceStatus:e.requestEvidenceStatus,dataSources:e.dataSources,warnings:e.warnings,caveats:e.caveats,completeness:t,summary:e.summary}}function YC(e,t){return JSON.stringify(e)===JSON.stringify(t)}function XC(e){let t=[];return e.comparability.sameSite||t.push(`The reports use different site properties.`),e.comparability.sameRequestScope||t.push(`The reports use different crawl request scopes; added and removed pages may reflect scope changes.`),e.comparability.sameCaps||t.push(`The reports use different crawl or provider caps; compare retained coverage before interpreting deltas.`),!e.comparability.sameConfigHash&&e.comparability.sameRequestScope&&e.comparability.sameCaps&&t.push(`The crawl configurations differ outside request scope and caps; inspect both config objects before attribution.`),e.comparability.sameDefinitionId||t.push(`The reports use different crawl definitions; do not attribute every delta to the site or a release.`),e.before.completeness.status!==`complete`&&t.push(`The baseline report is ${e.before.completeness.status}: ${e.before.completeness.reasons.join(`, `)}.`),e.after.completeness.status!==`complete`&&t.push(`The newer report is ${e.after.completeness.status}: ${e.after.completeness.reasons.join(`, `)}.`),(e.before.completeness.truncated||e.after.completeness.truncated)&&t.push(`At least one report was truncated; page and issue deltas describe retained evidence only.`),e.after.generatedAt<=e.before.generatedAt&&t.push(`The report labelled after is not newer than the report labelled before.`),(e.before.warnings.length||e.after.warnings.length||e.before.caveats.length||e.after.caveats.length)&&t.push(`One or both saved reports contain source warnings or caveats; inspect them before acting.`),t}function ZC(e){let t=JC(e.before),n=JC(e.after),r={sameDefinitionId:t.definitionId===n.definitionId,sameConfigHash:t.configHash===n.configHash,sameSite:t.site===n.site,sameStartUrl:t.url===n.url,sameMode:t.config.mode===n.config.mode,sameRequestScope:YC(t.requestScope,n.requestScope),sameCaps:YC(t.caps,n.caps)},i=Object.values(r).every(Boolean),a=t.completeness.status===`complete`&&n.completeness.status===`complete`,o={status:i&&a?`comparable`:`review-required`,...r};return{before:t,after:n,comparability:o,completeness:{status:t.completeness.status===`failed`||n.completeness.status===`failed`?`failed`:t.completeness.status===`partial`||n.completeness.status===`partial`?`partial`:`complete`,truncated:t.completeness.truncated||n.completeness.truncated,before:t.completeness,after:n.completeness},caveats:XC({before:t,after:n,comparability:o})}}function QC(e){return{url:e.url,finalUrl:e.finalUrl,status:e.status,title:e.title,metaDescription:e.metaDescription,canonical:e.canonical,h1:e.h1,indexable:e.indexable,wordCount:e.wordCount}}function $C(e,t){return(e?.status??200)<400&&(t?.status??200)>=400}function ew(e,t){return(e?.status??200)>=400&&(t?.status??500)<400}function tw(e,t){let n=[];return e.finalUrl!==t.finalUrl&&n.push(`final_url`),e.status!==t.status&&n.push(`status`),e.title!==t.title&&n.push(`title`),e.metaDescription!==t.metaDescription&&n.push(`meta_description`),e.canonical!==t.canonical&&n.push(`canonical`),e.h1!==t.h1&&n.push(`h1`),e.indexable!==t.indexable&&n.push(`indexability`),e.contentHash!==t.contentHash&&n.push(`content`),e.wordCount!==t.wordCount&&n.push(`word_count`),n}function nw(e,t){let n=new Map(e.pages.map(e=>[e.url,e])),r=new Map(t.pages.map(e=>[e.url,e])),i=[];for(let[e,t]of r){let r=n.get(e);if(!r){i.push({url:e,kind:`added`,changes:[`url_added`],after:QC(t)});continue}let a=tw(r,t);a.length&&i.push({url:e,kind:`changed`,changes:a,before:QC(r),after:QC(t)})}for(let[e,t]of n)r.has(e)||i.push({url:e,kind:`removed`,changes:[`url_removed`],before:QC(t)});return i.sort((e,t)=>{let n={changed:0,added:1,removed:2};return n[e.kind]-n[t.kind]||e.url.localeCompare(t.url)})}function rw(e){return new Map(e.map(e=>[e.ruleId,e]))}function iw(e,t){let n=rw(e.issueGroups),r=rw(t.issueGroups),i=new Set([...n.keys(),...r.keys()]),a=[];for(let e of i){let t=n.get(e),i=r.get(e),o=t?.count??0,s=i?.count??0;if(o===s)continue;let c=i??t;c&&a.push({ruleId:e,title:c.title,severity:c.severity,category:c.category,before:o,after:s,delta:s-o})}return a.sort((e,t)=>Math.abs(t.delta)-Math.abs(e.delta)||e.ruleId.localeCompare(t.ruleId))}function aw(e){return e.addedPages===0&&e.removedPages===0&&e.changedPages===0&&e.issueDelta===0?`No material crawl changes were found between these saved reports.`:e.newStatusErrors>0||e.issueGroupsWorse>e.issueGroupsBetter?`The newer crawl has regressions that should be reviewed before more SEO work.`:e.fixedStatusErrors>0||e.issueGroupsBetter>e.issueGroupsWorse?`The newer crawl looks healthier, with some technical issues reduced or fixed.`:`The newer crawl changed, but the overall technical picture is broadly similar.`}function ow(e){let t=[],n=e.changes.filter(e=>$C(e.before,e.after));n.length&&t.push({title:`New status errors appeared`,plainEnglish:`${n.length} URL now returns a 4xx or 5xx status when it did not before.`,action:`Check deploys, redirects, routing, and canonical URL changes before changing content.`,urls:n.slice(0,10).map(e=>e.url)});let r=e.changes.filter(e=>e.changes.includes(`indexability`));r.length&&t.push({title:`Indexability changed`,plainEnglish:`${r.length} URL changed indexability state.`,action:`Confirm noindex, robots, canonical, auth, and rendering changes were intentional.`,urls:r.slice(0,10).map(e=>e.url)});let i=e.issues.filter(e=>e.delta>0);if(i.length){let e=i[0];t.push({title:e?.title??`Crawl issues increased`,plainEnglish:`${e?.ruleId??`A rule`} increased by ${e?.delta??0} affected pages.`,action:`Review the affected rule and compare changed templates before shipping more pages.`})}let a=e.changes.filter(e=>e.changes.includes(`title`));return a.length&&t.push({title:`Titles changed`,plainEnglish:`${a.length} page title changed between snapshots.`,action:`Compare title changes against top GSC queries before treating traffic movement as content decay.`,urls:a.slice(0,10).map(e=>e.url)}),!t.length&&e.summary.changedPages>0&&t.push({title:`Review changed pages`,plainEnglish:`${e.summary.changedPages} page changed without an obvious technical regression.`,action:`Use the JSON output to inspect changed fields, then decide whether to annotate the change as an SEO test.`}),t}function sw(e){let t=ZC(e),n=nw(e.before,e.after),r=iw(e.before,e.after),i={pageDelta:e.after.summary.totalPages-e.before.summary.totalPages,issueDelta:e.after.issues.length-e.before.issues.length,addedPages:n.filter(e=>e.kind===`added`).length,removedPages:n.filter(e=>e.kind===`removed`).length,changedPages:n.filter(e=>e.kind===`changed`).length,newStatusErrors:n.filter(e=>$C(e.before,e.after)).length,fixedStatusErrors:n.filter(e=>ew(e.before,e.after)).length,indexabilityFlips:n.filter(e=>e.changes.includes(`indexability`)).length,titleChanges:n.filter(e=>e.changes.includes(`title`)).length,contentChanges:n.filter(e=>e.changes.includes(`content`)).length,issueGroupsWorse:r.filter(e=>e.delta>0).length,issueGroupsBetter:r.filter(e=>e.delta<0).length};return{schemaVersion:1,...t,summary:i,headline:aw(i),topActions:ow({changes:n,issues:r,summary:i}),pageChanges:n,issueChanges:r}}function cw(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)return t}return n.length-r.length}function lw(e){return`concepts/${e.toLowerCase().replace(/^https?:\/\//,``).replace(/[^a-z0-9]+/g,`-`).replace(/^-+|-+$/g,``).slice(0,62)||`page`}-${_(`sha256`).update(e).digest(`hex`).slice(0,12)}.md`}function uw(e){let t=[`---`];for(let[n,r]of Object.entries(e))if(r!==void 0)if(Array.isArray(r)){if(!r.length)continue;t.push(`${n}:`);for(let e of r)t.push(` - ${JSON.stringify(e)}`)}else t.push(`${n}: ${JSON.stringify(r)}`);return t.push(`---`,``),t.join(`
563
+ `)}function dw(e){return[...e].map(e=>{let t=e.codePointAt(0)??0;return t<32||t===127?` `:e}).join(``).replace(/\s+/g,` `).trim()}function fw(e){return dw(e.title??e.h1??(new URL(e.finalUrl).pathname||e.finalUrl))}function pw(e){let t=new URL(e.finalUrl).pathname.toLowerCase();return e.schemaTypes?.includes(`Article`)||/\/(blog|article|news)\b/.test(t)?`article`:e.schemaTypes?.some(e=>/Product|Service|SoftwareApplication/i.test(e))?`product`:/\/(about|company|team|contact)\b/.test(t)?`organization`:`webpage`}function mw(e){let t=fw(e),n=[uw({type:pw(e),title:t,url:e.finalUrl,status:e.status,indexable:e.indexable,canonical:e.canonical,schemaTypes:e.schemaTypes,sameAs:e.schemaSameAs}),`# ${t}`,``,e.metaDescription?`Summary: ${dw(e.metaDescription)}`:void 0,e.contentSample?`Extract: ${dw(e.contentSample)}`:void 0,``,`## Signals`,``,`- URL: ${e.finalUrl}`,`- Status: ${e.status}`,`- Indexable: ${e.indexable?`yes`:`no`}`,`- Word count: ${e.wordCount}`,`- Structured data: ${(e.schemaTypes??[]).join(`, `)||`none detected`}`,`- Internal inlinks: ${e.internalInlinkCount??0}`,``,`# Citations`,``,`- [Source page](<${e.finalUrl}>)`,``].filter(e=>e!==void 0);return{path:lw(e.finalUrl),content:n.join(`
564
+ `)}}function hw(e){if(e===void 0)return 500;if(!Number.isInteger(e)||e<1||e>5e3)throw new A(`INVALID_INPUT`,`maxConcepts must be a whole number between 1 and 5000.`);return e}function gw(e,t){let n=dw(e??new URL(t.config.url).hostname.replace(/^www\./,``));if(!n||n.length>200)throw new A(`INVALID_INPUT`,`OKF title must contain 1 to 200 characters on one line.`);return n}function _w(e,t){let n=hw(t),r=e.pages.filter(e=>e.indexable&&e.status>=200&&e.status<300&&!e.blocked&&!e.error).sort((e,t)=>(t.searchMetrics?.clicks??0)-(e.searchMetrics?.clicks??0)||(t.searchMetrics?.impressions??0)-(e.searchMetrics?.impressions??0)||(t.internalLinkAuthorityScore??0)-(e.internalLinkAuthorityScore??0)||(t.internalInlinkCount??0)-(e.internalInlinkCount??0)||cw(e.finalUrl,t.finalUrl)),i=new Map;for(let e of r)i.has(e.finalUrl)||i.set(e.finalUrl,e);let a=[...i.values()];return{pages:a.slice(0,n),sourcePages:e.pages.length,eligiblePages:a.length,duplicateFinalUrls:r.length-a.length,limit:n}}function vw(e,t={}){let n=gw(t.title,e),r=_w(e,t.maxConcepts),i=r.pages,a=[...e.caveats,...e.status===`completed`?[]:[`Source crawl status is ${e.status}; the bundle may not represent the complete site inventory.`],...r.duplicateFinalUrls?[`${r.duplicateFinalUrls} duplicate final URL${r.duplicateFinalUrls===1?` was`:`s were`} deduplicated before concept export.`]:[],...r.eligiblePages>i.length?[`OKF selected ${i.length} of ${r.eligiblePages} eligible 2xx indexable pages using observed search clicks, impressions, internal authority, inlinks, and a stable URL tie-break.`]:[]],o=i.map(mw),s=[{path:`index.md`,content:[uw({okf:`0.1`,type:`index`,title:n}),`# ${n}`,``,`Source crawl: ${e.id}`,`Generated from: ${e.config.url}`,``,`## Contents`,``,`- [Concepts](concepts/index.md)`,`- [Inventory](inventory/pages.md)`,`- [Graph](graph/links.md)`,`- [Caveats](caveats.md)`,`- [Log](log.md)`,``].join(`
565
565
  `)},{path:`log.md`,content:[`# Log`,``,`- ${e.generatedAt}: Generated OKF bundle from crawl report ${e.id}.`,``].join(`
566
566
  `)},{path:`concepts/index.md`,content:[`# Concepts`,``,...o.map(e=>`- [${e.path.replace(/^concepts\//,``)}](${e.path.replace(/^concepts\//,``)})`),``].join(`
567
- `)},{path:`inventory/pages.md`,content:[GC({type:`inventory`,title:`Selected concept pages`}),`# Selected Concept Pages`,``,`| URL | Status | Indexable | Title |`,`| --- | ---: | --- | --- |`,...i.map(e=>`| ${e.finalUrl} | ${e.status} | ${e.indexable?`yes`:`no`} | ${KC(e.title??``).replace(/\|/g,`\\|`)} |`),``,`# Citations`,``,`- [Crawl start URL](${e.config.url})`,``].join(`
568
- `)},{path:`graph/links.md`,content:[GC({type:`graph`,title:`Internal link graph`}),`# Internal Link Graph`,``,...i.flatMap(e=>[`## ${qC(e)}`,``,`Source: ${e.finalUrl}`,``,...(e.sampleInternalLinks??[]).slice(0,25).map(e=>`- ${e}`),``]),`# Citations`,``,`- [Crawl start URL](${e.config.url})`,``].join(`
569
- `)},{path:`caveats.md`,content:[GC({type:`caveats`,title:`Caveats`}),`# Caveats`,``,...(a.length?a:[`No crawl caveats were reported.`]).map(e=>`- ${e}`),...e.warnings.length?[``,`## Warnings`,``,...e.warnings.map(e=>`- ${e}`)]:[],``,`# Citations`,``,`- [Crawl start URL](${e.config.url})`,``].join(`
570
- `)},...o];return{schemaVersion:1,reportId:e.id,sourceUrl:e.config.url,generatedAt:e.generatedAt,crawlStatus:e.status,rootTitle:n,files:s,conceptCount:o.length,selection:{sourcePages:r.sourcePages,eligiblePages:r.eligiblePages,duplicateFinalUrls:r.duplicateFinalUrls,selectedPages:i.length,limitedPages:r.eligiblePages-i.length,limit:r.limit,order:`search-clicks-impressions-internal-authority-inlinks-url`},caveats:a,warnings:e.warnings}}function ew(e){return/^---\n[\s\S]+?\n---\n/.test(e)}function tw(e){return!!e.match(/^---\n([\s\S]+?)\n---\n/)?.[1]?.match(/^type:\s*.+$/m)}function nw(e,t){let n=e.match(RegExp(`^${t}:\\s*(.+)$`,`m`))?.[1];if(n)try{let e=JSON.parse(n);return typeof e==`string`?e:void 0}catch{return}}function rw(e){return e.endsWith(`/index.md`)||e===`index.md`||e.endsWith(`/log.md`)||e===`log.md`}function iw(e){let t=[],n=new Map;for(let r of e)n.set(r.path,(n.get(r.path)??0)+1),(!r.path||r.path.startsWith(`/`)||r.path.split(`/`).some(e=>e===`..`||e===``))&&t.push({path:r.path||`(empty)`,severity:`error`,message:`File paths must be safe relative paths.`});for(let[e,r]of n)r>1&&t.push({path:e,severity:`error`,message:`File path is duplicated ${r} times.`});let r=new Map(e.map(e=>[e.path,e]));for(let e of[`index.md`,`log.md`,`concepts/index.md`,`inventory/pages.md`,`graph/links.md`,`caveats.md`])r.has(e)||t.push({path:e,severity:`error`,message:`Required seo OKF bundle file is missing.`});let i=r.get(`index.md`);i&&(nw(i.content,`okf`)!==`0.1`||nw(i.content,`type`)!==`index`)&&t.push({path:`index.md`,severity:`error`,message:`Root frontmatter must declare okf "0.1" and type "index".`});for(let n of e.filter(e=>e.path.endsWith(`.md`)))if(n.content.trim()||t.push({path:n.path,severity:`error`,message:`Markdown files must not be empty.`}),!rw(n.path)){if(ew(n.content)?tw(n.content)||t.push({path:n.path,severity:`error`,message:`Concept frontmatter needs a non-empty type field.`}):t.push({path:n.path,severity:`error`,message:`Concept files need YAML frontmatter.`}),n.path.startsWith(`concepts/`)){let e=nw(n.content,`url`),r=!1;try{r=!!(e&&[`http:`,`https:`].includes(new URL(e).protocol))}catch{r=!1}r||t.push({path:n.path,severity:`error`,message:`Concept frontmatter needs a valid HTTP(S) url.`})}/\n# Citations\n/i.test(n.content)||t.push({path:n.path,severity:`warning`,message:`Files with claims should include a # Citations section.`})}let a=r.get(`concepts/index.md`)?.content??``;for(let n of e.filter(e=>e.path.startsWith(`concepts/`)&&!rw(e.path))){let e=n.path.replace(/^concepts\//,``);a.includes(`](${e})`)||t.push({path:n.path,severity:`error`,message:`Concept is missing from concepts/index.md.`})}let o=e.filter(e=>e.path.startsWith(`concepts/`)&&!rw(e.path)).length;return{valid:!t.some(e=>e.severity===`error`),files:e.length,concepts:o,issues:t}}function aw(e){let t=e.issues.filter(e=>e.severity===`error`),n=e.issues.filter(e=>e.severity===`warning`);return{title:`OKF bundle`,valid:e.valid,summary:e.valid?`This bundle passes seo OKF checks with ${e.concepts} concept files.`:`This OKF bundle has ${t.length} validation error${t.length===1?``:`s`}.`,files:e.files,concepts:e.concepts,errors:t.length,warnings:n.length,nextActions:e.valid?[`Review concept quality and publish the bundle where agents can fetch it.`]:t.slice(0,5).map(e=>`${e.path}: ${e.message}`)}}const ow=m.enum([`low`,`medium`,`high`]),sw=m.enum([`fix`,`review`]),cw=m.enum([`canonical`,`content`,`response`,`headings`,`images`,`indexability`,`international`,`links`,`metadata`,`mobile`,`performance`,`security`,`social`,`structured-data`,`geo`]),lw=m.enum(Iy().map(e=>e.id)),uw=m.object({id:lw,title:m.string(),category:cw,defaultSeverity:ow,recommendation:sw,whyItMatters:m.string(),howToFix:m.string(),impactIfIgnored:m.string(),howToVerify:m.string(),agentHints:m.object({evidenceFields:m.array(m.string()).optional(),suggestedCommands:m.array(m.string()).optional()}).optional()}),dw=m.object({clicks:m.number(),impressions:m.number(),ctr:m.number(),position:m.number()}),fw=m.object({sessions:m.number(),totalUsers:m.number(),conversions:m.number()}),pw=m.object({semanticHtml:m.boolean(),structuredData:m.boolean(),hasAuthor:m.boolean(),hasDate:m.boolean(),questionHeadings:m.number().int(),listCount:m.number().int().optional(),tableCount:m.number().int().optional(),structuredBlocks:m.number().int(),answerable:m.boolean(),hasFaqSchema:m.boolean().optional(),hasQapageSchema:m.boolean().optional(),hasLlmsTxt:m.boolean().optional(),llmsTxtUrl:m.string().url().optional(),llmsTxtStatus:m.number().int().optional()}),mw=m.enum([`no-required-properties`,`required-properties-observed`,`missing-required-properties`,`retired`,`not-assessed`]),hw=m.object({"no-required-properties":m.number().int().nonnegative(),"required-properties-observed":m.number().int().nonnegative(),"missing-required-properties":m.number().int().nonnegative(),retired:m.number().int().nonnegative(),"not-assessed":m.number().int().nonnegative()}),gw=m.object({format:m.enum([`json-ld`,`microdata`,`rdfa`]),block:m.number().int().nonnegative().optional(),path:m.string(),schemaType:m.enum([`Article`,`BlogPosting`,`NewsArticle`,`Product`,`BreadcrumbList`,`FAQPage`]),feature:m.enum([`article`,`product-snippet`,`breadcrumb`,`faq`]),status:mw,observedProperties:m.array(m.string()),missingRequiredProperties:m.array(m.string()),limitations:m.array(m.string()),documentationUrl:m.string().url()}),_w=m.enum([`missing`,`single`,`duplicate`,`conflicting`,`outside-head-only`,`invalid`]),vw=m.object({title:m.string().optional(),metaDescription:m.string().optional(),canonical:m.object({status:_w,url:m.string().url().optional()}),robots:m.object({meta:m.string().optional(),googlebot:m.string().optional(),http:m.string().optional()}),headings:m.array(m.object({level:m.number().int(),text:m.string()})),links:m.object({total:m.number().int().nonnegative(),internal:m.number().int().nonnegative(),external:m.number().int().nonnegative(),fingerprint:m.string()}),content:m.object({characters:m.number().int().nonnegative(),wordCount:m.number().int().nonnegative(),fingerprint:m.string()}),structuredData:m.object({blocks:m.number().int().nonnegative(),formats:m.array(m.enum([`json-ld`,`microdata`,`rdfa`])),schemaTypes:m.array(m.string())})}),yw=m.object({raw:vw,rendered:vw,changed:m.array(m.enum([`title`,`metaDescription`,`canonical`,`robots`,`headings`,`links`,`content`,`structuredData`]))}),bw=m.object({url:m.string().url(),finalUrl:m.string().url(),status:m.number().int(),contentType:m.string().optional(),responseHeaders:m.record(m.string(),m.string()).optional(),responseTimeMs:m.number().optional(),sizeBytes:m.number().int().optional(),usedJs:m.boolean().optional(),fetchSource:m.enum([`cache`,`network`,`rendered`]).optional(),cacheState:m.enum([`hit`,`miss`,`bypass`]).optional(),fetchDiagnostics:m.object({source:m.enum([`cache`,`network`,`rendered`]),cache:m.enum([`hit`,`miss`,`bypass`]),fetched:m.boolean(),rendered:m.boolean(),blocked:m.boolean(),durationMs:m.number(),retries:m.number().int(),rateLimit:m.object({host:m.string(),concurrency:m.number().int(),intervalCap:m.number().int(),intervalMs:m.number().int()}),backpressure:m.object({host:m.string(),status:m.enum([`ok`,`slowed`,`stopped`]),reason:m.string().optional(),delayMs:m.number(),cooldownUntil:m.string().optional(),consecutiveSlow:m.number().int(),consecutiveBlocked:m.number().int(),consecutiveErrors:m.number().int(),recentP95Ms:m.number().optional()}).optional(),robotsTxt:m.object({url:m.string().url(),cache:m.enum([`hit`,`miss`,`bypass`]),allowed:m.boolean().nullable(),availability:m.enum([`available`,`absent`,`access-blocked`,`rate-limited`,`unreachable`]),status:m.number().int().optional(),error:m.string().optional()}).optional(),redirectChain:m.array(m.object({url:m.string().url(),status:m.number().int(),location:m.string().url().optional()})).optional(),rendering:m.object({mode:m.enum([`auto`,`on`,`off`]),status:m.enum([`not-requested`,`not-needed`,`skipped`,`rendered`,`unavailable`,`failed`]),raw:m.object({source:m.enum([`cache`,`network`]),cache:m.enum([`hit`,`miss`,`bypass`]),url:m.string().url(),finalUrl:m.string().url(),status:m.number().int()}).optional(),documentDifference:yw.optional(),browser:m.object({source:m.enum([`environment`,`playwright-cache`,`system`]),product:m.string(),version:m.string().optional()}).optional(),navigation:m.object({waitUntil:m.literal(`domcontentloaded`),networkIdleTimeoutMs:m.number().int().nonnegative(),networkIdleReached:m.boolean()}).optional(),consoleErrors:m.array(m.string()).optional(),pageErrors:m.array(m.string()).optional(),failedRequests:m.array(m.object({url:m.string(),resourceType:m.string(),error:m.string()})).optional(),securityObservations:m.array(m.object({kind:m.enum([`content-security-policy`,`cors`,`mixed-content`]),message:m.string()})).optional(),error:m.string().optional()}).optional()}).optional(),blocked:m.boolean().optional(),contentAuditAllowed:m.boolean().optional(),crawlDepth:m.number().int().optional(),error:m.string().optional(),robotsTxt:m.object({url:m.string().url(),allowed:m.boolean().nullable(),availability:m.enum([`available`,`absent`,`access-blocked`,`rate-limited`,`unreachable`]),status:m.number().int().optional(),error:m.string().optional(),matchedLine:m.string().optional()}).optional(),title:m.string().optional(),metaDescription:m.string().optional(),canonical:m.string().url().optional(),canonicalRaw:m.string().optional(),canonicalStatus:m.enum([`missing`,`single`,`duplicate`,`conflicting`,`outside-head-only`,`invalid`]).optional(),canonicalCandidates:m.array(m.object({source:m.enum([`html-head`,`html-body`,`http-header`]),raw:m.string(),resolved:m.string().url().optional(),ignoredReason:m.enum([`outside-head`,`alternate-qualifier`,`fragment`,`invalid-url`,`non-http-url`]).optional()})).optional(),metaRobots:m.string().optional(),xRobotsTag:m.string().optional(),h1:m.string().optional(),h1Count:m.number().int().optional(),h2Count:m.number().int().optional(),h3Count:m.number().int().optional(),indexable:m.boolean(),indexability:m.string().optional(),declaredIndexability:m.enum([`indexable-candidate`,`noindex`,`robots-blocked`,`canonical-conflict`,`canonical-hint-other`,`not-html`,`unknown`]).optional(),extractionStatus:m.enum([`complete`,`failed`,`not-applicable`,`unknown-media-type`]).optional(),extractionError:m.string().optional(),wordCount:m.number().int(),contentExtraction:m.object({requested:m.enum([`defuddle`,`readability`]),used:m.enum([`defuddle`,`readability`]),fallback:m.boolean(),fallbackReason:m.enum([`defuddle_error`,`defuddle_empty`]).optional(),fallbackDetail:m.string().optional(),wordCountSource:m.enum([`defuddle`,`local_cjk_aware`]),baseUrl:m.string().url(),extractorType:m.string().optional()}).optional(),warnings:m.array(m.string()).optional(),contentHash:m.string(),mainContentHash:m.string().optional(),textRatio:m.number().min(0).max(1).optional(),contentSample:m.string().optional(),lang:m.string().optional(),hasViewport:m.boolean().optional(),isHttps:m.boolean().optional(),hasHsts:m.boolean().optional(),compression:m.string().optional(),hreflang:m.array(m.object({hreflang:m.string(),href:m.string().url()})).optional(),mixedContentCount:m.number().int().optional(),mixedContentSamples:m.array(m.string().url()).optional(),imagesTotal:m.number().int().optional(),imagesMissingAlt:m.number().int().optional(),oversizedImageCandidates:m.array(m.object({src:m.string().url(),width:m.number().int().optional(),height:m.number().int().optional(),detectedFrom:m.string()})).optional(),outgoingInternalCount:m.number().int(),outgoingExternalCount:m.number().int().optional(),internalInlinkCount:m.number().int().optional(),internalLinkAuthorityScore:m.number().int().min(0).max(100).optional(),sampleInternalLinks:m.array(m.string().url()).optional(),sampleExternalLinks:m.array(m.string().url()).optional(),internalAnchorSamples:m.array(m.object({href:m.string().url(),text:m.string()})).optional(),externalAnchorSamples:m.array(m.object({href:m.string().url(),text:m.string()})).optional(),externalLinkChecks:m.array(m.object({url:m.string().url(),status:m.number().int().optional(),error:m.string().optional()})).optional(),schemaTypes:m.array(m.string()).optional(),structuredDataFormats:m.array(m.enum([`json-ld`,`microdata`,`rdfa`])).optional(),googleRichResults:m.array(gw).optional(),googleRichResultsSelection:m.object({limit:m.number().int().nonnegative(),eligible:m.number().int().nonnegative(),returned:m.number().int().nonnegative(),omitted:m.number().int().nonnegative(),partial:m.boolean(),eligibleByStatus:hw,returnedByStatus:hw,omittedByStatus:hw}).optional(),schemaSameAs:m.array(m.string().url()).optional(),schemaSameAsEvidence:m.array(m.object({url:m.string().url(),block:m.number().int().nonnegative(),path:m.string(),subjectId:m.string().url().optional(),subjectTypes:m.array(m.string())})).optional(),invalidSchemaSameAs:m.array(m.object({block:m.number().int().nonnegative(),path:m.string(),value:m.string()})).optional(),socialProfileLinks:m.array(m.string().url()).optional(),invalidJsonLdCount:m.number().int().optional(),invalidJsonLdSamples:m.array(m.object({snippet:m.string(),error:m.string()})).optional(),unrecognizedJsonLdTypes:m.array(m.object({block:m.number().int().nonnegative(),path:m.string(),value:m.string(),reason:m.enum([`missing-schema-context`,`unresolved-context`,`unsupported-vocabulary`])})).optional(),openGraphTitle:m.string().optional(),openGraphDescription:m.string().optional(),openGraphImage:m.string().optional(),twitterCard:m.string().optional(),author:m.string().optional(),hasDate:m.boolean().optional(),geo:pw.optional(),searchMetrics:dw.optional(),topQuery:dw.extend({query:m.string()}).optional(),analytics:fw.optional()}),xw=m.object({requestedUrl:m.string().url(),outcome:m.literal(`response`),finalUrl:m.string().url(),status:m.number().int(),contentType:m.string().optional(),durationMs:m.number().optional(),redirectChain:m.array(m.object({url:m.string().url(),status:m.number().int(),location:m.string().url().optional()})).optional()}),Sw=m.union([xw.extend({extraction:m.enum([`complete`,`not-applicable`,`unknown-media-type`])}),xw.extend({extraction:m.literal(`failed`),extractionError:m.string()}),m.object({requestedUrl:m.string().url(),outcome:m.literal(`skipped`),durationMs:m.number().optional(),reason:m.enum([`robots-disallowed`,`robots-deferred`]),robotsTxt:m.object({url:m.string().url(),allowed:m.boolean().nullable(),availability:m.enum([`available`,`absent`,`access-blocked`,`rate-limited`,`unreachable`]),status:m.number().int().optional(),error:m.string().optional(),matchedLine:m.string().optional()}),extraction:m.literal(`not-applicable`)}),m.object({requestedUrl:m.string().url(),outcome:m.literal(`skipped`),durationMs:m.number().optional(),reason:m.literal(`origin-backpressure`),error:m.string(),extraction:m.literal(`not-applicable`)}),m.object({requestedUrl:m.string().url(),outcome:m.literal(`failure`),durationMs:m.number().optional(),failureKind:m.enum([`dns`,`tls`,`timeout`,`redirect-limit`,`aborted`,`unknown`]),error:m.string(),extraction:m.literal(`not-applicable`)})]),Cw=m.object({ruleId:lw,title:m.string(),category:cw,severity:ow,url:m.string().url(),detail:m.string().optional(),evidence:m.record(m.string(),m.unknown()).optional(),searchMetrics:dw.optional()}),ww=m.object({ruleId:lw,title:m.string(),category:cw,severity:ow,count:m.number().int(),sampleUrls:m.array(m.string().url())}),Tw=m.object({robotsTxt:m.object({url:m.string().url(),exists:m.boolean(),availability:m.enum([`available`,`absent`,`access-blocked`,`rate-limited`,`unreachable`]),status:m.number().int().optional(),error:m.string().optional(),sitemapUrls:m.array(m.string().url()),botAccess:m.array(m.object({userAgent:m.string(),allowed:m.boolean().nullable(),declared:m.boolean(),coveredByWildcard:m.boolean()}))}).optional(),llmsTxt:m.object({url:m.string().url(),exists:m.boolean(),status:m.number().int().optional()}).optional(),agentResources:m.array(m.object({url:m.string().url(),exists:m.boolean(),status:m.number().int().optional(),contentType:m.string().optional(),validJson:m.boolean().optional()})).optional()}),Ew=ww.extend({recommendation:sw,score:m.number(),scoreFactors:m.object({severity:m.number(),affectedUrls:m.number().int(),searchVisibleUrls:m.number().int(),clicks:m.number(),impressions:m.number(),sessions:m.number(),totalUsers:m.number(),conversions:m.number(),avgPosition:m.number().optional(),effort:m.enum([`low`,`medium`,`high`]),effortScore:m.number()}),whyThisRanks:m.string(),howToFix:m.string(),howToVerify:m.string(),verification:m.object({command:m.string(),expected:m.string()})}),Dw=m.object({dataStatus:m.enum([`complete`,`partial`,`unavailable`]),urlsReturned:m.number().int().nonnegative(),roots:m.array(m.object({url:m.string().url(),source:m.enum([`robots-txt`,`default-path`]),dataStatus:m.enum([`complete`,`partial`,`unavailable`]),urlsReturned:m.number().int().nonnegative(),sitemapsFetched:m.number().int().nonnegative(),lastmods:m.object({trust:m.literal(`unverified`),observed:m.number().int().nonnegative(),parseable:m.number().int().nonnegative(),malformed:m.object({count:m.number().int().nonnegative(),samples:m.array(m.object({sitemapUrl:m.string().url(),kind:m.enum([`url`,`sitemap`]),loc:m.string(),value:m.string()}))}),future:m.object({count:m.number().int().nonnegative(),samples:m.array(m.object({sitemapUrl:m.string().url(),kind:m.enum([`url`,`sitemap`]),loc:m.string(),value:m.string()}))})}),documents:m.array(m.object({url:m.string().url(),finalUrl:m.string().url().optional(),redirected:m.literal(!0).optional(),dataStatus:m.enum([`complete`,`partial`,`unavailable`]),status:m.number().int().optional(),contentType:m.string().optional(),compression:m.enum([`gzip`,`none`]),bytes:m.number().int().nonnegative().optional(),uncompressedBytes:m.number().int().nonnegative().optional(),root:m.enum([`urlset`,`sitemapindex`]).optional(),warning:m.string().optional()})),possiblyTruncated:m.boolean(),warnings:m.array(m.string())}))}),Ow=m.object({dataStatus:m.enum([`complete`,`partial`,`unavailable`]),discoveredLinkOccurrences:m.number().int().nonnegative(),retainedUrls:m.number().int().nonnegative(),selectedUrls:m.number().int().nonnegative(),fetchedUrls:m.number().int().nonnegative(),failedUrls:m.number().int().nonnegative(),deferredUrls:m.number().int().nonnegative(),limit:m.number().int().positive(),warnings:m.array(m.string())}),kw=m.object({url:m.string().url(),mode:m.enum([`site`,`page`,`list`,`sitemap`]),urls:m.array(m.string().url()),maxPages:m.number().int(),maxDepth:m.number().int(),concurrency:m.number().int(),timeoutMs:m.number().int(),include:m.array(m.string()),exclude:m.array(m.string()),respectRobots:m.boolean(),useSitemap:m.boolean(),checkExternal:m.boolean(),js:m.union([m.enum([`auto`,`on`,`off`]),m.boolean()]),refresh:m.boolean(),fetchRate:m.object({concurrency:m.number().int(),intervalCap:m.number().int().optional(),intervalMs:m.number().int().optional(),backpressure:m.object({slowMs:m.number().int().optional(),verySlowMs:m.number().int().optional(),maxConsecutiveSlow:m.number().int().optional(),maxConsecutiveBlocked:m.number().int().optional(),maxConsecutiveErrors:m.number().int().optional(),cooldownMs:m.number().int().optional(),retryAfterCapMs:m.number().int().optional()}).optional()})}),Aw=m.object({totalPages:m.number().int(),indexablePages:m.number().int(),nonIndexablePages:m.number().int(),statusErrors:m.number().int(),discoveredUrls:m.number().int(),queuedUrls:m.number().int(),crawledUrls:m.number().int(),skippedUrls:m.number().int(),failedUrls:m.number().int(),observedInternalLinks:m.number().int(),pageLimitReached:m.boolean().default(!1),attemptedRequests:m.number().int(),responseRequests:m.number().int(),failedRequests:m.number().int(),abortedRequests:m.number().int(),extractionFailures:m.number().int(),requestByStatus:m.record(m.string(),m.number().int()),avgRequestMs:m.number().int().optional(),highIssues:m.number().int(),mediumIssues:m.number().int(),lowIssues:m.number().int(),avgResponseMs:m.number().int().optional(),byStatus:m.record(m.string(),m.number().int()),byCategory:m.record(m.string(),m.number().int())}),jw=m.enum([`joined`,`partial`,`none`,`skipped`,`unavailable`]),Mw=m.object({startDate:m.string().date(),endDate:m.string().date(),days:m.number().int().positive()}),Nw=m.object({status:jw,window:Mw.optional(),totalPages:m.number().int().nonnegative(),queriedPages:m.number().int().nonnegative(),joinedMetricPages:m.number().int().nonnegative(),joinedQueryPages:m.number().int().nonnegative(),pageLimit:m.number().int().positive(),pageLimitReached:m.boolean(),metricRowsReturned:m.number().int().nonnegative().optional(),queryRowsReturned:m.number().int().nonnegative().optional(),retainedRowLimit:m.number().int().positive().optional(),retainedRowLimitReached:m.boolean().optional(),warning:m.string().optional()}),Pw=m.object({status:jw,window:Mw.optional(),totalPages:m.number().int().nonnegative(),queriedPages:m.number().int().nonnegative(),joinedPages:m.number().int().nonnegative(),returnedRows:m.number().int().nonnegative().optional(),availableRows:m.number().int().nonnegative().optional(),retainedRowLimit:m.number().int().positive().optional(),retainedRowLimitReached:m.boolean().optional(),warning:m.string().optional()}),Fw=m.object({searchConsole:Nw,analytics:Pw}),Iw=m.object({id:m.string(),definitionId:m.string(),projectId:m.string().optional(),site:m.string().optional(),ga4PropertyId:m.string().optional(),generatedAt:m.string().datetime(),status:m.enum([`completed`,`partial`,`failed`]),configHash:m.string(),config:kw,summary:Aw,pages:m.array(bw),issues:m.array(Cw),issueGroups:m.array(ww),dataSources:Fw.optional(),ai:Tw.optional(),sitemapDiscovery:Dw.optional(),externalLinkVerification:Ow.optional(),warnings:m.array(m.string()),caveats:m.array(m.string())}),Lw=m.discriminatedUnion(`requestEvidenceStatus`,[Iw.extend({requestEvidenceStatus:m.literal(`available`),requests:m.array(Sw)}),Iw.extend({requestEvidenceStatus:m.literal(`partial`),requests:m.array(Sw)}),Iw.extend({requestEvidenceStatus:m.literal(`unavailable`),requests:m.array(Sw).max(0)})]);m.toJSONSchema(Lw),m.toJSONSchema(ww),m.toJSONSchema(Ew),m.toJSONSchema(uw),m.toJSONSchema(Sw),m.toJSONSchema(bw);const Rw=1e9;function zw(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 Bw(e,t,n,r){return e===void 0||!Number.isFinite(e)?t:Math.min(r,Math.max(n,Math.floor(e)))}function Vw(e){return e.normalize(`NFKC`).toLowerCase().replace(/\s+/g,` `).trim()}function Hw(e){try{let t=new URL(e);return t.protocol!==`http:`&&t.protocol!==`https:`?void 0:(t.hash=``,t.toString())}catch{return}}function Uw(e){return Number.isFinite(e.clicks)&&e.clicks>=0&&Number.isFinite(e.impressions)&&e.impressions>0&&e.clicks<=e.impressions&&Number.isFinite(e.ctr)&&e.ctr>=0&&e.ctr<=1&&Number.isFinite(e.position)&&e.position>0}function Ww(e,t){let n=new Map;for(let r of e){let e=r.keys[0]?.trim()??``,i=Hw(r.keys[1]?.trim()??``);if(r.keys.length!==2||!e||!i||!Uw(r)){t.invalidRows++;continue}t.validRows++;let a=`${Vw(e)}\u0000${i}`,o=n.get(a)??{query:e,url:i,clicks:0,impressions:0,weightedPosition:0,sourceRows:0};zw(e,o.query)<0&&(o.query=e),o.clicks+=r.clicks,o.impressions+=r.impressions,o.weightedPosition+=r.position*r.impressions,o.sourceRows++,n.set(a,o)}let r=[...n.values()].map(e=>({keys:[e.query,e.url],clicks:e.clicks,impressions:e.impressions,ctr:e.clicks/e.impressions,position:e.weightedPosition/e.impressions,sourceRows:e.sourceRows}));return t.duplicateRows=r.reduce((e,t)=>e+t.sourceRows-1,0),t.aggregatedRows=r.length,r}function Gw(e){return{sourceRows:e,invalidRows:0,validRows:0,duplicateRows:0,aggregatedRows:0,outsidePageOneRows:0,lowActionabilityRows:0,brandRows:0,benchmarkRows:0,belowMinimumRows:0,evaluatedRows:0,eligibleUnderperformers:0,returnedUnderperformers:0,limitedUnderperformers:0}}function Kw(e){let t=Bw(e.minImpressions,200,1,Rw),n=Bw(e.limit,25,1,100),r=Gw(e.rows.length),i=Ww(e.rows,r).filter(t=>{let n=t.keys[0]??``;return t.position<1||t.position>10?(r.outsidePageOneRows++,!1):Rv(n)?(r.lowActionabilityRows++,!1):M_({query:n,siteUrl:e.site,brandTerms:e.brandTerms,includeBrand:e.includeBrand})?(r.brandRows++,!1):!0});r.benchmarkRows=i.length;let a=Ev(i),o=i.filter(e=>e.impressions<t?(r.belowMinimumRows++,!1):!0);r.evaluatedRows=o.length;let s=o.map(e=>{let t=xv(e.position),n=a.forUrl(e),r=n.ctr*e.impressions,i=Math.max(0,r-e.clicks);return{query:e.keys[0]??``,url:e.keys[1]??``,position:e.position,impressions:e.impressions,actualCtr:e.ctr,expectedCtr:n.ctr,clicks:e.clicks,expectedClicks:r,clickShortfall:i,benchmark:{expectedCtr:n.ctr,source:n.source,peerRows:n.rows,peerImpressions:n.impressions,qualifiedPeerImpressions:n.qualifiedImpressions,urlSamples:n.urlSamples,positiveUrlSamples:n.positiveUrlSamples},recommendation:{principle:`C.3`,evidenceRef:`Query "${e.keys[0]}" has observed CTR ${e.ctr.toFixed(3)} versus a heuristic ${n.ctr.toFixed(3)} benchmark at average position ${t}, producing a calculated ${i.toFixed(0)}-click shortfall for this window.`,action:`Review the live SERP, query intent, displayed snippet, title, and meta description for "${e.keys[0]}". Test SERP copy only when that review finds a mismatch; the CTR gap alone does not identify the cause.`,effort:`S`,confidence:`low`}}}).filter(e=>e.actualCtr<e.expectedCtr*.6).sort((e,t)=>t.clickShortfall-e.clickShortfall||zw(e.query,t.query)||zw(e.url,t.url)),c=s.slice(0,n);return r.eligibleUnderperformers=s.length,r.returnedUnderperformers=c.length,r.limitedUnderperformers=s.length-c.length,{items:c,totalClickShortfall:s.reduce((e,t)=>e+t.clickShortfall,0),returnedClickShortfall:c.reduce((e,t)=>e+t.clickShortfall,0),minImpressions:t,limit:n,selection:r}}const qw=1e5;function Jw(e,t,n=`${t}s`){return e===1?t:n}const Yw={searchAnalytics:e_,now:()=>new Date};async function Xw(e,t=Yw){let n=Y({value:e.minImpressions,fallback:200,minimum:1,maximum:Rw,label:`minImpressions`}),r=Y({value:e.limit,fallback:25,minimum:1,maximum:100,label:`limit`}),i=t.now(),a=H_(28,i),o=await t.searchAnalytics(e.site,{...a,dimensions:[`query`,`page`],type:`web`,dataState:`final`,maxRows:qw},{refresh:e.refresh}),{items:s,totalClickShortfall:c,returnedClickShortfall:l,selection:u}=Kw({rows:o.rows,site:e.site,minImpressions:n,limit:r,brandTerms:e.brandTerms,includeBrand:e.includeBrand}),d=s[0],f=o.rowsFetched>=qw,p=u.invalidRows>0,m=u.sourceRows===0?`empty`:f||p?`partial`:u.eligibleUnderperformers===0?`filtered`:`complete`,h=f&&p?`partial-and-possibly-truncated`:f?`possibly-truncated`:p?`partial`:`complete`;return{schemaVersion:1,site:e.site,range:a,generatedAt:i.toISOString(),dataStatus:m,source:{provider:`google-search-console`,dimensions:[`query`,`page`],searchType:`web`,dataState:`final`,rowsFetched:o.rowsFetched,calls:o.calls,maxRows:qw,possiblyTruncated:f,completeness:h,validation:{retainedRows:u.validRows,invalidRows:u.invalidRows,aggregatedRows:u.aggregatedRows,duplicateRows:u.duplicateRows}},selection:u,summary:{underperformers:u.eligibleUnderperformers,returnedUnderperformers:u.returnedUnderperformers,estimatedClickShortfall:c,returnedClickShortfall:l,minImpressions:n,limit:r,brandFiltering:e.includeBrand?`included`:`excluded`,verdict:d?`${u.eligibleUnderperformers} CTR ${Jw(u.eligibleUnderperformers,`underperformer`)} found in retained rows, with a calculated heuristic shortfall of ${c.toFixed(0)} clicks for this window. Start review with "${d.query}" because it has the largest gap.`:m===`partial`?`No material CTR underperformers remained in the validated retained rows, but partial evidence prevents an all-clear.`:`No high-impression page-one queries in the retained rows are materially below the heuristic CTR benchmark.`},items:s,caveats:[`Date window: ${a.startDate} to ${a.endDate} (28 days), using final GSC data where available.`,`Brand queries: ${e.includeBrand?`included`:`excluded`}.`,`Only queries ranking position 1-10 with at least ${n} impressions were checked.`,`Expected CTR uses a robust site-aware position benchmark when enough peer data exists, otherwise the fallback position curve.`,`CTR benchmarks and calculated click shortfalls are directional heuristics, not forecasts or proof that clicks are available. Validate the search intent and live SERP before editing.`,...u.invalidRows?[`${u.invalidRows} invalid provider ${Jw(u.invalidRows,`row`)} ${u.invalidRows===1?`was`:`were`} excluded, so this report is partial.`]:[],...u.duplicateRows?[`${u.duplicateRows} repeated query/page ${Jw(u.duplicateRows,`row`)} ${u.duplicateRows===1?`was`:`were`} aggregated before CTR and average position were calculated.`]:[],...f?[`The retained-row limit of ${qw} was reached, so lower-ranked source rows may be absent.`]:[],...u.limitedUnderperformers?[`${u.limitedUnderperformers} eligible ${Jw(u.limitedUnderperformers,`underperformer`)} ${u.limitedUnderperformers===1?`was`:`were`} omitted by the output limit.`]:[]],recommendations:d?[`Review the live SERP and displayed snippet for "${d.query}" first. Test the title or meta description only if the observed framing does not match the query intent.`,`Prioritise rows with high impressions and clear intent, but treat the benchmark gap as a hypothesis rather than a diagnosed copy defect.`,`After changing SERP copy, annotate the change and compare the next full 28-day period before making more edits.`]:m===`partial`?[`No CTR-only action is recommended from the validated retained rows. Refresh or inspect the provider evidence before treating this as an all-clear.`]:[`No CTR-only action is recommended from this report. Use striking-distance or page-opportunities if you want more growth ideas.`]}}function Zw(e,t,n=`${t}s`){return e===1?t:n}function Z(e,t,n=`${t}s`){return`${e.toLocaleString(`en-GB`)} ${Zw(e,t,n)}`}function Qw(e){let t=e.items[0];if(!t)return`No segment movement found.`;let n=t.clickDelta<0?`lost`:`gained`;return`${t.key} ${n} ${Math.abs(t.clickDelta)} clicks.`}function $w(e){let t=[];e.anomaly.anomalies.some(e=>e.significant)&&e.update.overlappingUpdates.length&&t.push({label:`Review movement during update overlap`,reason:`${Z(e.update.overlappingUpdates.length,`official update window`)} ${e.update.overlappingUpdates.length===1?`overlaps`:`overlap`} the significant movement comparison window.`,action:`Do not assume the update caused the movement. First compare winning and losing templates, then test site changes, indexability, intent, and SERP changes as separate explanations.`,confidence:`low`});let n=e.page.items[0];if(n&&t.push({label:`Investigate largest page movement`,reason:Qw(e.page),action:`Open the page and check the queries that moved, whether the URL is still canonical/indexable, and whether the title, H1, or body changed recently.`,confidence:e.page.dataStatus===`partial`?`low`:Math.abs(n.clickDelta)>50?`high`:`medium`}),e.decay.selection.eligibleRows){let n=e.decay.groups[0];t.push({label:`Refresh decaying content`,reason:n?`${e.decay.selection.eligibleRows} observed retained query/page declines found; ${n.label} declined by ${n.totalClickLoss.toFixed(0)} clicks.`:`${e.decay.selection.eligibleRows} observed retained query/page declines found.`,action:n?n.recommendation:`Start with declines that continued outside the update window. Check indexability first, then ranking and CTR causes.`,confidence:e.decay.dataStatus===`partial`?`low`:`medium`})}e.cannibal.items.length&&t.push({label:`Review multi-URL query candidates`,reason:`${e.cannibal.selection.eligibleClusters} multi-URL query candidates found.`,action:`Confirm whether each URL set satisfies the same intent and inspect technical state. Consolidate only verified duplicate or same-intent pages; otherwise clarify the distinction.`,confidence:`low`});let r=e.quickWins.items[0];if(r){let n=e.quickWins.source.possiblyTruncated||e.quickWins.verification.requested&&e.quickWins.verification.failed>0;t.push({label:`Review CTR-target opportunity`,reason:`${Z(e.quickWins.items.length,`returned query/page candidate`)} fell below ${e.quickWins.items.length===1?`its`:`their`} deterministic, versioned heuristic CTR target.`,action:r.recommendation.action,confidence:n?`low`:r.recommendation.confidence})}if(e.striking.items.length){let n=e.striking.items[0];t.push({label:`Investigate striking-distance candidates`,reason:`${e.striking.items.length} query/page rows have an average GSC position above 10 and at most 20.`,action:n?.recommendation.action??`Check technical state, query coverage, competing URLs, and relevant internal links before choosing a change.`,confidence:e.striking.source.possiblyTruncated?`low`:n?.recommendation.confidence??`low`})}return t.slice(0,6)}function eT(e){let t=new Set(e.skippedSections?.map(e=>e.section)),n=[`page`,`query`,`device`,`country`].flatMap(n=>{let r=e.segments[n];return r.dataStatus===`complete`||t.has(`${n} movement segments`)?[]:[{section:`${n} movement segments`,reason:`${r.summary.verdict} Inspect its retained-row source evidence and warnings.`}]});return[...e.anomaly.coverage?.status===`partial`?[{section:`traffic anomaly`,reason:e.anomaly.coverage.caveats.join(` `)||`The requested Search Console calendar window has incomplete daily evidence.`}]:[],...n,...e.decay.dataStatus===`partial`?[{section:`decay analysis`,reason:`The decay comparison used incomplete source evidence; inspect its source completeness, warnings, and caveats.`}]:[],...e.cannibalization.dataStatus===`partial`?[{section:`cannibalisation analysis`,reason:`The multi-URL query analysis used incomplete source evidence; inspect its source completeness and caveats.`}]:[],...e.strikingDistance.source.possiblyTruncated?[{section:`striking-distance opportunities`,reason:`The retained Search Console response reached its row cap, so additional candidates may exist.`}]:[],...e.strikingDistance.verification.requested&&e.strikingDistance.verification.failed>0?[{section:`striking-distance content verification`,reason:`${e.strikingDistance.verification.failed} of ${e.strikingDistance.verification.attempted} attempted candidate verifications failed, so page evidence is incomplete.`}]:[],...e.quickWins.source.possiblyTruncated?[{section:`quick-win opportunities`,reason:`The retained Search Console response reached its row cap, so benchmarks and candidates may be incomplete.`}]:[],...e.quickWins.verification.requested&&e.quickWins.verification.failed>0?[{section:`quick-win content verification`,reason:`${e.quickWins.verification.failed} of ${e.quickWins.verification.attemptedRows} attempted candidate verifications failed, so page evidence is incomplete.`}]:[]]}function tT(e){return e.criticalStatuses.length>0&&e.criticalStatuses.every(e=>e===`skipped`)?`unavailable`:e.skippedSections||e.partialReasons?`partial`:`complete`}function nT(e){return Number(e.toFixed(3))}function rT(e,t){return e<t?-1:+(e>t)}function iT(e){return Number.isFinite(e)&&e>=0}function aT(e){return Number.isInteger(e)&&e>=0}function oT(e){let t=new Map,n=new Set,r=0,i=0,a=0;for(let o of e){let e=o.keys[0];if(o.keys.length!==1||!e?.trim()||!aT(o.clicks)||!aT(o.impressions)||!iT(o.position)||o.impressions>0&&o.position<=0){r+=1;continue}let s={key:e,clicks:o.clicks,impressions:o.impressions,position:o.impressions>0?nT(o.position):null},c=t.get(e);if(c){c.clicks===s.clicks&&c.impressions===s.impressions&&c.position===s.position?i+=1:(a+=1,t.delete(e),n.add(e));continue}n.has(e)||t.set(e,s)}return{rows:t,invalidRows:r,duplicateRows:i,conflictingRows:a}}function sT(e,t){let n=e.position===null||t.position===null?null:nT(t.position-e.position);return{key:e.key,evidenceScope:`matched-retained-segment`,beforeClicks:e.clicks,afterClicks:t.clicks,clickDelta:nT(t.clicks-e.clicks),beforeImpressions:e.impressions,afterImpressions:t.impressions,impressionDelta:nT(t.impressions-e.impressions),beforePosition:e.position,afterPosition:t.position,positionDelta:n}}function cT(e,t){return{...e,retainedIn:t,reason:`not-retained-in-other-window`}}function lT(e){let t=oT(e.beforeRows),n=oT(e.afterRows),r=[],i=[];for(let[e,a]of t.rows){let t=n.rows.get(e);t?r.push(sT(a,t)):i.push(cT(a,`before`))}for(let[e,r]of n.rows)t.rows.has(e)||i.push(cT(r,`after`));return r.sort((e,t)=>Math.abs(t.clickDelta)-Math.abs(e.clickDelta)||rT(e.key,t.key)),i.sort((e,t)=>t.clicks-e.clicks||rT(e.retainedIn,t.retainedIn)||rT(e.key,t.key)),{before:t,after:n,matched:r,unmatched:i}}function uT(e){if(e.status===`empty`)return`Search Console returned no retained segment rows for either window.`;if(!e.matchedRows)return`No segment was retained in both windows, so no movement delta was inferred.`;let t=e.unmatchedRows?` ${e.unmatchedRows} one-window rows were kept as unmatched evidence and not converted to zero.`:``;return`${e.returnedRows} of ${e.matchedRows} matched retained segments returned.${t}`}function dT(e){if(!Number.isInteger(e.beforeDays)||!Number.isInteger(e.afterDays)||e.beforeDays<1||e.afterDays<1)throw new A(`INVALID_INPUT`,`Segment ranges must contain days.`);if(e.beforeDays!==e.afterDays)throw new A(`INVALID_INPUT`,`Segment impact requires adjacent equal-length comparison windows.`);let{before:t,after:n,matched:r,unmatched:i}=lT(e),a=e.beforeRowsFetched??e.beforeRows.length,o=e.afterRowsFetched??e.afterRows.length,s=a>=e.maxRows,c=o>=e.maxRows,l=i.filter(e=>e.retainedIn===`before`).length,u=i.length-l,d={beforeSourceRows:e.beforeRows.length,afterSourceRows:e.afterRows.length,beforeInvalidRows:t.invalidRows,afterInvalidRows:n.invalidRows,beforeDuplicateRows:t.duplicateRows,afterDuplicateRows:n.duplicateRows,beforeConflictingRows:t.conflictingRows,afterConflictingRows:n.conflictingRows,matchedRows:r.length,unmatchedBeforeRows:l,unmatchedAfterRows:u,returnedRows:Math.min(r.length,e.limit),limitedRows:Math.max(0,r.length-e.limit),returnedUnmatchedRows:Math.min(i.length,e.unmatchedLimit),limitedUnmatchedRows:Math.max(0,i.length-e.unmatchedLimit)},f=s||c||t.invalidRows>0||n.invalidRows>0||t.duplicateRows>0||n.duplicateRows>0||t.conflictingRows>0||n.conflictingRows>0||i.length>0||a===0||o===0,p=a===0&&o===0?`empty`:a===0||o===0||r.length===0?`unavailable`:f?`partial`:`complete`,m=[...s||c?[`At least one GSC window reached the ${e.maxRows.toLocaleString(`en-GB`)}-row safety cap. Results may be truncated.`]:[],...i.length?[`${i.length} segments appeared in only one retained response. They are not treated as zero traffic or ranked as winners or losers.`]:[],...t.invalidRows||n.invalidRows?[`${t.invalidRows} before and ${n.invalidRows} after rows had invalid dimensions or metrics and were excluded.`]:[],...t.duplicateRows||n.duplicateRows?[`${t.duplicateRows} before and ${n.duplicateRows} after identical duplicate segment rows were deduplicated.`]:[],...t.conflictingRows||n.conflictingRows?[`${t.conflictingRows} before and ${n.conflictingRows} after conflicting duplicate segment rows were excluded.`]:[]];return{schemaVersion:2,site:e.site,dimension:e.dimension,before:e.before,after:e.after,rangeDays:{before:e.beforeDays,after:e.afterDays},generatedAt:e.generatedAt,dataStatus:p,source:{provider:`google-search-console`,dimension:e.dimension,aggregationType:`auto`,searchType:`web`,dataState:`final`,before:{rowsFetched:a,calls:e.beforeCalls??0,maxRows:e.maxRows,possiblyTruncated:s},after:{rowsFetched:o,calls:e.afterCalls??0,maxRows:e.maxRows,possiblyTruncated:c},completeness:s||c?`possibly-truncated`:`retained-rows-only`},methodology:{id:`gsc_matched_retained_segment_impact`,version:2,missingRowsTreatedAsZero:!1,equalLengthAdjacentWindows:!0,clickDeltaBasis:`after-minus-before`,positionDeltaDefinition:`after-minus-before-positive-is-worse`,causeLanguage:`signals-not-attribution`},filters:{limit:e.limit,unmatchedLimit:e.unmatchedLimit,maxRows:e.maxRows},selection:d,summary:{matchedRows:r.length,returnedRows:d.returnedRows,unmatchedRows:i.length,verdict:uT({status:p,matchedRows:r.length,returnedRows:d.returnedRows,unmatchedRows:i.length})},items:r.slice(0,e.limit),unmatchedSegments:i.slice(0,e.unmatchedLimit),warnings:m,caveats:[`Only segments retained in both GSC responses receive movement deltas. Search Console can omit anonymized and lower-volume rows, so one-window absence is not proof of zero traffic.`,`Average position is impression-weighted provider data and is only compared when both retained rows have impressions.`,`Search Console segment data is observational. Movement can guide investigation but does not establish the cause of a ranking, demand, or CTR change.`]}}function fT(e,t){let n=new Date(`${e}T00:00:00.000Z`);return n.setUTCDate(n.getUTCDate()-t),n.toISOString().slice(0,10)}function pT(e){return Math.floor((Date.parse(`${e.endDate}T00:00:00.000Z`)-Date.parse(`${e.startDate}T00:00:00.000Z`))/864e5)+1}function mT(e,t){let n=fT(e.startDate,1);return{startDate:fT(n,t-1),endDate:n}}function hT(e){let t=Y({value:e.days,fallback:28,minimum:1,maximum:240,label:`days`}),n=K_(e,240)?.range??G_(t,e.now),r=Y({value:e.compareDays,fallback:pT(n),minimum:1,maximum:240,label:`compareDays`}),i=pT(n);if(r!==i)throw new A(`INVALID_INPUT`,`compareDays must equal the ${i}-day current window. Segment impact requires adjacent equal-length periods.`);return{before:mT(n,r),after:n}}function gT(e){if(!e.trim())throw new A(`INVALID_INPUT`,`site must not be empty.`)}function _T(e){let t=e.value??e.fallback;if(!Number.isFinite(t)||t<e.minimum||t>e.maximum)throw new A(`INVALID_INPUT`,`${e.label} must be between ${e.minimum} and ${e.maximum}.`);return t}function vT(e){return Math.floor((new Date(`${e.endDate}T00:00:00.000Z`).getTime()-new Date(`${e.startDate}T00:00:00.000Z`).getTime())/864e5)+1}function yT(e){return e.toISOString().slice(0,10)}function bT(e,t){let n=new Date(`${e}T00:00:00.000Z`);return n.setUTCDate(n.getUTCDate()-t),yT(n)}function xT(e){let t=new Date(`${e}T00:00:00.000Z`),n=t.getUTCFullYear()-1,r=t.getUTCMonth(),i=t.getUTCDate(),a=new Date(Date.UTC(n,r,i));return a.getUTCMonth()===r?yT(a):yT(new Date(Date.UTC(n,r+1,0)))}function ST(e,t){let n=e.getUTCDate(),r=new Date(Date.UTC(e.getUTCFullYear(),e.getUTCMonth()-t,1)),i=new Date(Date.UTC(r.getUTCFullYear(),r.getUTCMonth()+1,0)).getUTCDate();return r.setUTCDate(Math.min(n,i)),yT(r)}function CT(e){let t=G_(1,e.now).endDate;if(e.current.endDate>t)throw new A(`INVALID_INPUT`,`Decay end date must be ${t} or earlier so both GSC windows use final data.`);let n=new Date(`${t}T00:00:00.000Z`);n.setUTCDate(n.getUTCDate()+4);let r=ST(n,16);if(e.previous.startDate<r)throw new A(`INVALID_INPUT`,`Comparison range starts before Search Console's rolling 16-month API history (${r}). Choose a shorter range or use previously stored data.`)}function wT(e,t=`previous-period`){let n=vT(e);if(t===`year-over-year`){let t=xT(e.endDate);return{startDate:bT(t,n-1),endDate:t}}let r=bT(e.startDate,1);return{startDate:bT(r,n-1),endDate:r}}function TT(e){let t=pT(e.after),n=mT(e.after,t);return{schemaVersion:2,site:e.site,dimension:e.dimension,before:n,after:e.after,rangeDays:{before:t,after:t},generatedAt:e.generatedAt??new Date().toISOString(),dataStatus:`unavailable`,source:{provider:`google-search-console`,dimension:e.dimension,aggregationType:`auto`,searchType:`web`,dataState:`final`,before:{rowsFetched:0,calls:0,maxRows:1e5,possiblyTruncated:!1},after:{rowsFetched:0,calls:0,maxRows:1e5,possiblyTruncated:!1},completeness:`unavailable`},methodology:{id:`gsc_matched_retained_segment_impact`,version:2,missingRowsTreatedAsZero:!1,equalLengthAdjacentWindows:!0,clickDeltaBasis:`after-minus-before`,positionDeltaDefinition:`after-minus-before-positive-is-worse`,causeLanguage:`signals-not-attribution`},filters:{limit:0,unmatchedLimit:0,maxRows:1e5},selection:{beforeSourceRows:0,afterSourceRows:0,beforeInvalidRows:0,afterInvalidRows:0,beforeDuplicateRows:0,afterDuplicateRows:0,beforeConflictingRows:0,afterConflictingRows:0,matchedRows:0,unmatchedBeforeRows:0,unmatchedAfterRows:0,returnedRows:0,limitedRows:0,returnedUnmatchedRows:0,limitedUnmatchedRows:0},summary:{matchedRows:0,returnedRows:0,unmatchedRows:0,verdict:`Segment movement could not be compared for this run.`},items:[],unmatchedSegments:[],warnings:[e.reason],caveats:[`No segment movement conclusion was produced because the provider evidence was unavailable.`]}}const ET={searchAnalytics:e_,now:()=>new Date};async function DT(e,t=ET){gT(e.site);let n=t.now(),r=hT({...e,now:n});CT({current:r.after,previous:r.before,now:n});let i=Y({value:e.limit,fallback:25,minimum:1,maximum:100,label:`limit`}),a=Y({value:e.unmatchedLimit,fallback:25,minimum:0,maximum:100,label:`unmatchedLimit`}),o=Y({value:e.maxRows,fallback:1e5,minimum:1,maximum:25e4,label:`maxRows`}),s=e.dimension??`page`,c={dimensions:[s],type:`web`,dataState:`final`,aggregationType:`auto`,maxRows:o},[l,u]=await Promise.all([t.searchAnalytics(e.site,{...r.before,...c},{refresh:e.refresh}),t.searchAnalytics(e.site,{...r.after,...c},{refresh:e.refresh})]);return dT({site:e.site,dimension:s,before:r.before,after:r.after,beforeDays:pT(r.before),afterDays:pT(r.after),beforeRows:l.rows,afterRows:u.rows,beforeRowsFetched:l.rowsFetched,afterRowsFetched:u.rowsFetched,beforeCalls:l.calls,afterCalls:u.calls,limit:i,unmatchedLimit:a,maxRows:o,generatedAt:n.toISOString()})}var OT=class{entries=new Map;gscCalls=0;gscRows=0;addUsage(e){let t=this.entries.get(e.provider);if(!t){this.entries.set(e.provider,{...e});return}t.units+=e.units,t.calls+=e.calls,t.cacheHits=(t.cacheHits??0)+(e.cacheHits??0),t.estimatedUsd=(t.estimatedUsd??0)+(e.estimatedUsd??0)}addGsc(e,t){this.gscCalls+=e,this.gscRows+=t}summary(){let e=[...this.entries.values()].map(e=>{let t=e.estimatedUsd?` (~$${e.estimatedUsd.toFixed(2)})`:``,n=e.cacheHits?` · Cache hits: ${e.cacheHits}`:``;return`${e.provider}: ${e.calls} calls, ${e.units} ${e.unitLabel}${t}${n}`});return e.push(`GSC: ${this.gscCalls} calls, ${this.gscRows} rows`),e.join(` · `)}};const kT=[`near me`,`today`,`tonight`,`calendar`,`schedule`,`time`,`times`,`high tide`,`low tide`,`tide chart`,`tide times`,`salary in`,`average salary`];function AT(e){try{return new URL(e)}catch{return}}function jT(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,` `).replace(/\s+/g,` `).trim()}function MT(e){return(AT(e)?.pathname??``).split(`/`).map(e=>e.trim()).filter(Boolean)}function NT(e){let t=MT(e),n=`/${t.join(`/`)}/`;if(n.includes(`/last-names/`)&&n.endsWith(`-surname-popularity/`))return{id:`surname-entity`,label:`Surname entity page`,confidence:`high`};if(n.includes(`/last-names/`))return{id:`last-name-list`,label:`Last-name list page`,confidence:`high`};if(n.includes(`/first-names/`)&&n.endsWith(`-meaning-and-history/`))return{id:`first-name-entity`,label:`First-name entity page`,confidence:`high`};if(n.includes(`/first-names/`))return{id:`first-name-list`,label:`First-name list page`,confidence:`high`};if(/\/average-[a-z0-9-]+-salary-in-[a-z0-9-]+\//.test(n)){let e=t.length>=2?`city`:`country`;return{id:`${e}-salary`,label:`${e===`city`?`City`:`Country`} salary page`,confidence:`high`}}return n.includes(`/salary-calculator/`)?{id:`salary-calculator`,label:`Salary calculator page`,confidence:`high`}:t.length>=3&&/\b(tide|tides|harbour|harbor|marina|beach)\b/.test(jT(n))?{id:`location-schedule`,label:`Location schedule page`,confidence:`medium`}:n.startsWith(`/tools/`)?{id:`tool-page`,label:`Tool page`,confidence:`medium`}:n.startsWith(`/alternatives/`)?{id:`alternative-page`,label:`Alternative page`,confidence:`medium`}:n.startsWith(`/compare/`)?{id:`comparison-page`,label:`Comparison page`,confidence:`medium`}:{id:`other`,label:`Other page`,confidence:`low`}}function PT(e,t=5){let n=new Map;for(let t of e){let e=t.template??NT(t.url??``),r=n.get(e.id)??{id:e.id,label:e.label,count:0,sampleUrls:[]};r.count+=1,t.url&&r.sampleUrls.length<3&&r.sampleUrls.push(t.url),n.set(e.id,r)}return[...n.values()].sort((e,t)=>t.count-e.count).slice(0,t)}function FT(e){return/["“”]/.test(e)}function IT(e){let t=jT(e);return kT.some(e=>t.includes(e))}function LT(e){let t=new Map;for(let n of e){let e=NT(n.keys[1]??``),r=t.get(e.id)??{template:e,count:0};r.count+=1,t.set(e.id,r)}let n=[...t.values()].sort((e,t)=>t.count-e.count)[0];return n?{template:n.template,share:n.count/e.length}:{template:NT(``),share:0}}function RT(e){return e.id.endsWith(`-salary`)?`salary-page`:e.id}function zT(e){return e.id.endsWith(`-salary`)?`Salary page`:e.label}function BT(e){let t=new Map;for(let n of e){let e=NT(n.keys[1]??``),r=RT(e),i=t.get(r)??{id:r,label:zT(e),count:0,share:0,template:e};i.count+=1,t.set(r,i)}let n=[...t.values()].sort((e,t)=>t.count-e.count)[0];return n?{...n,share:n.count/e.length}:{id:`other`,label:`Other page`,count:0,share:0,template:NT(``)}}function VT(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)return t}return n.length-r.length}function HT(e){return e.normalize(`NFKC`).toLowerCase().replace(/\s+/g,` `).trim()}function UT(e){try{let t=new URL(e);return t.protocol!==`http:`&&t.protocol!==`https:`?void 0:(t.hash=``,t.toString())}catch{return}}function WT(e){return Number.isFinite(e.clicks)&&e.clicks>=0&&Number.isFinite(e.impressions)&&e.impressions>0&&e.clicks<=e.impressions&&Number.isFinite(e.ctr)&&e.ctr>=0&&e.ctr<=1&&Number.isFinite(e.position)&&e.position>0}function GT(e,t,n){return!!(t&&n)&&WT(e)}function KT(e,t){return!!t&&WT(e)}function qT(e,t,n){let r=e.pages.get(t),i=(r?.clicks??0)+n.clicks,a=(r?.impressions??0)+n.impressions,o=(r?.position??0)*(r?.impressions??0)+n.position*n.impressions;e.pages.set(t,{url:t,clicks:i,impressions:a,ctr:i/a,position:o/a})}function JT(e,t){let n=[];FT(e)&&n.push(`quoted-query`);let r=BT(t),i=r.share>=.8&&r.id!==`other`;return i&&n.push(`same-template-family`),i&&IT(e)&&n.push(`local-or-entity-intent`),n}function YT(e){return`Query "${e}" was excluded as branded.`}function XT(e,t){return{keys:[t,e.url],clicks:e.clicks,impressions:e.impressions,ctr:e.ctr,position:e.position}}function ZT(e,t){return t.clicks-e.clicks||t.impressions-e.impressions||e.position-t.position||VT(e.url,t.url)}function QT(e){let{query:t,pages:n}=e,r=n.reduce((e,t)=>e+t.clicks,0),i=n.reduce((e,t)=>e+t.impressions,0),a=[...n].sort(ZT)[0],o=n.reduce((e,t)=>e+t.impressionShare**2,0),s=Math.max(...n.map(e=>e.impressionShare)),c=1-s,l=e.propertyImpressions??e.pageExposureImpressions,u=LT(n.map(e=>XT(e,t))),d=a?.url??n[0]?.url??``;return{query:t,pages:[...n].sort((e,t)=>t.impressionShare-e.impressionShare||ZT(e,t)),pageCount:n.length,materialPageClicks:Number(r.toFixed(3)),materialPageExposureImpressions:Number(i.toFixed(3)),pageExposureImpressions:Number(e.pageExposureImpressions.toFixed(3)),propertyImpressions:e.propertyImpressions,observedPageExposureRatio:e.propertyImpressions&&e.propertyImpressions>0?Number((e.pageExposureImpressions/e.propertyImpressions).toFixed(4)):void 0,additionalUrlExposures:e.propertyImpressions===void 0?void 0:Number(Math.max(0,e.pageExposureImpressions-e.propertyImpressions).toFixed(3)),hhi:Number(o.toFixed(4)),splitScore:Number((1-o).toFixed(4)),largestPageShare:Number(s.toFixed(4)),secondaryExposureShare:Number(c.toFixed(4)),reviewContext:e.context,suggestedOwnerUrl:d,ownerSelection:{method:`clicks_then_impressions_then_position`,confidence:`low`,requiresIntentReview:!0},priority:{method:`demand_impressions_x_secondary_exposure`,score:Number((l*c).toFixed(3)),demandImpressions:Number(l.toFixed(3)),secondaryExposureShare:Number(c.toFixed(4)),heuristic:!0,estimatedClickLift:!1},template:u.share>=.8?u.template:void 0,recommendation:{principle:`C.6`,evidenceRef:`GSC retained ${n.length} material URLs for "${t}" across ${e.pageExposureImpressions.toFixed(0)} page exposures${e.propertyImpressions===void 0?``:` and ${e.propertyImpressions.toFixed(0)} property impressions`}; the largest material URL share was ${(s*100).toFixed(1)}%.`,action:`Compare the intent and technical state of these URLs. If they satisfy the same intent, choose a preferred page and align internal links, canonicals, and on-page focus. If they satisfy different intents, keep them separate and clarify the distinction. Treat ${d} only as the first review candidate based on observed clicks, impressions, and position.`,effort:`M`,confidence:`low`}}}function $T(e,t){return t.priority.score-e.priority.score||t.priority.demandImpressions-e.priority.demandImpressions||VT(e.query,t.query)}function eE(e,t,n,r){return e===void 0||!Number.isFinite(e)?t:Math.min(r,Math.max(n,Math.floor(e)))}function tE(e){let t=eE(e.minImpressions,50,0,1e9),n=eE(e.limit,25,1,100),r={sourceRows:e.rows.length,invalidRows:0,validRows:0,propertySourceRows:e.propertyRows?.length??0,propertyInvalidRows:0,propertyQueryGroups:0,queryGroups:0,lowActionabilityQueries:0,brandQueries:0,belowMinimumQueries:0,singlePageQueries:0,incidentalPages:0,dominantQueries:0,missingPropertyQueries:0,suppressedQueries:0,eligibleClusters:0,returnedClusters:0,limitedClusters:0,returnedSuppressions:0,limitedSuppressions:0},i=new Map,a=new Map;for(let t of e.propertyRows??[]){let e=t.keys[0]?.trim()??``;if(!KT(t,e)){r.propertyInvalidRows++;continue}let n=HT(e);a.set(n,(a.get(n)??0)+t.impressions)}r.propertyQueryGroups=a.size;for(let t of e.rows){let e=t.keys[0]?.trim()??``,n=UT(t.keys[1]?.trim()??``);if(!GT(t,e,n)){r.invalidRows++;continue}let a=HT(e),o=i.get(a)??{query:e,pages:new Map};VT(e,o.query)<0&&(o.query=e),qT(o,n??``,t),i.set(a,o),r.validRows++}r.queryGroups=i.size;let o=[],s=[];for(let[n,c]of i.entries()){let i=[...c.pages.values()].reduce((e,t)=>e+t.impressions,0),l=a.get(n);if(Rv(c.query)){r.lowActionabilityQueries++;continue}if(M_({query:c.query,siteUrl:e.site,brandTerms:e.brandTerms,includeBrand:e.includeBrand})){r.brandQueries++,c.pages.size>=2&&s.push({query:c.query,reason:`brand_query`,urlCount:c.pages.size,evidenceRef:YT(c.query)});continue}if((l??i)<t){r.belowMinimumQueries++;continue}if(c.pages.size<2){r.singlePageQueries++;continue}let u=[...c.pages.values()].map(e=>({...e,clicks:Number(e.clicks.toFixed(3)),impressions:Number(e.impressions.toFixed(3)),ctr:Number(e.ctr.toFixed(4)),position:Number(e.position.toFixed(2)),impressionShare:Number((e.impressions/i).toFixed(4)),template:NT(e.url)})),d=u.filter(e=>e.impressions>=10&&e.impressionShare>=.1);if(r.incidentalPages+=u.length-d.length,d.length<2){r.dominantQueries++;continue}let f=d.reduce((e,t)=>e+t.impressions,0),p=d.map(e=>({...e,impressionShare:Number((e.impressions/f).toFixed(4))}));if(Math.max(...p.map(e=>e.impressionShare))>.8){r.dominantQueries++;continue}let m=p.map(e=>XT(e,c.query));l===void 0&&r.missingPropertyQueries++,o.push(QT({query:c.query,pages:p,pageExposureImpressions:i,propertyImpressions:l,context:JT(c.query,m)}))}let c=o.sort($T),l=c.slice(0,n),u=s.sort((e,t)=>VT(e.query,t.query)||VT(e.reason,t.reason)).slice(0,100);return r.suppressedQueries=s.length,r.eligibleClusters=c.length,r.returnedClusters=l.length,r.limitedClusters=c.length-l.length,r.returnedSuppressions=u.length,r.limitedSuppressions=s.length-u.length,{filters:{minImpressions:t,limit:n,brand:e.includeBrand?`included`:`excluded`},selection:r,items:l,suppressed:u,suppressionSummary:s.reduce((e,t)=>(e[t.reason]=(e[t.reason]??0)+1,e),{}),templates:PT(l.flatMap(e=>e.pages))}}const nE=1e5,rE={searchAnalytics:e_,now:()=>new Date};function iE(e){if(e.status===`empty`)return`Search Console returned no retained query/page rows for this window.`;if(!e.eligible)return e.status===`partial`?`No material multi-URL query candidates remained in the validated retained rows, but partial evidence prevents an all-clear.`:`No material multi-URL query candidates remained after validation and filtering.`;let t=`${e.returned} of ${e.eligible} multi-URL query candidate${e.eligible===1?``:`s`} returned for intent and technical review.`;return e.status===`partial`?`${t} Provider evidence is partial.`:t}async function aE(e,t=rE){let n=Y({value:e.days,fallback:28,minimum:1,maximum:548,label:`days`}),r=Y({value:e.limit,fallback:25,minimum:1,maximum:100,label:`limit`}),i=Y({value:e.minImpressions,fallback:50,minimum:0,maximum:1e9,label:`minImpressions`}),a=t.now(),o=K_(e,548),s=o?.range??G_(n,a),c=o?.days??n,l={...s,type:`web`,dataState:`final`,maxRows:nE},[u,d]=await Promise.all([t.searchAnalytics(e.site,{...l,dimensions:[`query`,`page`],aggregationType:`auto`},{refresh:e.refresh}),t.searchAnalytics(e.site,{...l,dimensions:[`query`],aggregationType:`byProperty`},{refresh:e.refresh})]),f=new OT;f.addGsc(u.calls,u.rowsFetched),f.addGsc(d.calls,d.rowsFetched);let p=tE({site:e.site,rows:u.rows,propertyRows:d.rows,minImpressions:i,limit:r,brandTerms:e.brandTerms,includeBrand:e.includeBrand}),m=u.rowsFetched>=nE,h=d.rowsFetched>=nE,g=m||h,_=p.selection.invalidRows+p.selection.propertyInvalidRows>0,v=g&&_?`partial-and-possibly-truncated`:g?`possibly-truncated`:_?`partial`:`complete`,y=p.selection.sourceRows===0?`empty`:g||_||p.selection.missingPropertyQueries>0?`partial`:p.selection.eligibleClusters===0?`filtered`:`complete`;return{schemaVersion:1,site:e.site,generatedAt:a.toISOString(),range:s,rangeDays:c,dataStatus:y,source:{provider:`google-search-console`,searchType:`web`,dataState:`final`,pageExposure:{dimensions:[`query`,`page`],aggregationType:`auto`,rowsFetched:u.rowsFetched,validation:{retainedRows:p.selection.validRows,invalidRows:p.selection.invalidRows},calls:u.calls,maxRows:nE,possiblyTruncated:m},propertyDemand:{dimensions:[`query`],aggregationType:`byProperty`,rowsFetched:d.rowsFetched,validation:{retainedRows:p.selection.propertySourceRows-p.selection.propertyInvalidRows,invalidRows:p.selection.propertyInvalidRows},calls:d.calls,maxRows:nE,possiblyTruncated:h},completeness:v},methodology:{id:`gsc_url_overlap_v2`,version:2,minimumPageImpressions:10,minimumPageImpressionShare:.1,maximumDominantPageShare:.8,matching:`normalized_exact_query`,finding:`url-overlap-candidate`,requiresIntentReview:!0},verification:{status:`not-requested`,technicalStateChecked:!1,searchIntentChecked:!1},filters:p.filters,selection:p.selection,summary:{eligibleClusters:p.selection.eligibleClusters,returnedClusters:p.selection.returnedClusters,suppressedQueries:p.selection.suppressedQueries,brandFiltering:e.includeBrand?`included`:`excluded`,verdict:iE({status:y,eligible:p.selection.eligibleClusters,returned:p.selection.returnedClusters})},templates:p.templates,suppressed:p.suppressed,suppressionSummary:p.suppressionSummary,items:p.items,caveats:[`Date window: ${s.startDate} to ${s.endDate} (${c} days), using final GSC data where available.`,`This report finds queries with material exposure across multiple URLs. It does not prove that the URLs satisfy the same intent or that consolidation is correct.`,`Page exposure impressions can count more than one URL for a property impression. Property-level query impressions are reported separately instead of calling page exposure split demand.`,`Search Console retains top rows and omits anonymized queries, so absent rows are not proof of absent traffic.`,..._?[`${p.selection.invalidRows} invalid page-exposure row${p.selection.invalidRows===1?``:`s`} and ${p.selection.propertyInvalidRows} invalid property-demand row${p.selection.propertyInvalidRows===1?``:`s`} were excluded. Findings use validated retained rows only.`]:[],`The suggested owner is only a deterministic review starting point based on clicks, impressions, and position. Verify intent, indexability, redirects, and canonicals before changing URLs.`],recommendations:p.items.length?[`Review the highest-exposure candidates first. Confirm whether URLs serve the same intent and inspect technical state before changing canonicals, redirects, or content.`]:_?[`No URL-overlap action is recommended from the validated retained rows. Inspect or refresh the provider data before treating this result as an all-clear.`]:[`No URL-overlap action is recommended from the retained rows and current filters.`],ledgerSummary:f.summary()}}function oE(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)return t}return n.length-r.length}function sE(e){return e.normalize(`NFKC`).toLowerCase().replace(/\s+/g,` `).trim()}function cE(e){try{let t=new URL(e);return t.protocol!==`http:`&&t.protocol!==`https:`?void 0:(t.hash=``,t.toString())}catch{return}}function lE(e,t,n){return!!(t&&n)&&Number.isFinite(e.clicks)&&e.clicks>=0&&Number.isFinite(e.impressions)&&e.impressions>0&&e.clicks<=e.impressions&&Number.isFinite(e.ctr)&&e.ctr>=0&&e.ctr<=1&&Number.isFinite(e.position)&&e.position>0}function uE(e){let t=new Map,n=0;for(let r of e){let e=r.keys[0]?.trim()??``,i=cE(r.keys[1]?.trim()??``);if(!lE(r,e,i)){n++;continue}let a=`${sE(e)}\u0000${i}`,o=t.get(a),s=(o?.clicks??0)+r.clicks,c=(o?.impressions??0)+r.impressions,l=((o?.position??0)*(o?.impressions??0)+r.position*r.impressions)/c;t.set(a,{keys:[o&&oE(o.keys[0],e)<0?o.keys[0]:e,i??``],clicks:s,impressions:c,ctr:s/c,position:l})}return{rows:[...t.values()],invalid:n}}function dE(e){return{clicks:Number(e.clicks.toFixed(3)),impressions:Number(e.impressions.toFixed(3)),ctr:Number(e.ctr.toFixed(4)),position:Number(e.position.toFixed(2))}}function fE(e){let t=NT(e);if(t.id!==`other`)return t;let n=new URL(e).pathname.split(`/`).filter(Boolean)[0];return n?{id:`path:${n}`,label:`/${n}/ pages`,confidence:`low`}:{id:`path:root`,label:`Homepage`,confidence:`high`}}function pE(e,t){let n=[`click_decline`];return e.position>t.position+1&&n.push(`position_decline`),e.impressions>=t.impressions*.9&&e.ctr<t.ctr*.8&&n.push(`ctr_decline`),e.impressions<t.impressions*.9&&n.push(`impression_decline`),{diagnosis:n.includes(`position_decline`)?`lost_position`:n.includes(`ctr_decline`)?`lost_ctr`:n.includes(`impression_decline`)?`lost_impressions`:`lost_clicks`,signals:n}}function mE(e){let t=e.diagnosis===`lost_position`?`Check indexability, canonical state, competing URLs, SERP intent, content changes, and internal links before refreshing the page.`:e.diagnosis===`lost_ctr`?`Inspect the live SERP and query intent, then review title and meta framing before changing page content.`:e.diagnosis===`lost_impressions`?`Check seasonality, query demand, SERP features, page indexability, and URL shifts before attributing this to content quality.`:`Clicks declined without one strong position, CTR, or impression signal. Segment by device, country, and search appearance before choosing a fix.`;return{principle:e.diagnosis===`lost_ctr`?`C.3`:`C.8`,evidenceRef:`Retained GSC row for "${e.query}" on ${e.url}: ${e.previous.clicks.toFixed(1)} to ${e.current.clicks.toFixed(1)} clicks (${e.dropPct.toFixed(1)}% decline). Signals: ${e.signals.join(`, `)}.`,action:t,effort:e.diagnosis===`lost_ctr`?`S`:`M`,confidence:`medium`}}function hE(e){let t=new Map;for(let n of e){let e=`${n.template.id}:${n.diagnosis}`,r=t.get(e)??{group:{id:e,label:`${n.template.label} - ${n.diagnosis.replaceAll(`_`,` `)}`,diagnosis:n.diagnosis,template:n.template,count:0,urlCount:0,totalClickLoss:0,totalPreviousClicks:0,averageDropPct:0,sampleQueries:[],sampleUrls:[],recommendation:``},urls:new Set},{group:i}=r;i.count++,i.totalClickLoss+=n.clickLoss,i.totalPreviousClicks+=n.previous.clicks,r.urls.add(n.url),!i.sampleQueries.includes(n.query)&&i.sampleQueries.length<5&&i.sampleQueries.push(n.query),!i.sampleUrls.includes(n.url)&&i.sampleUrls.length<3&&i.sampleUrls.push(n.url),t.set(e,r)}return[...t.values()].map(({group:e,urls:t})=>({...e,urlCount:t.size,totalClickLoss:Number(e.totalClickLoss.toFixed(3)),totalPreviousClicks:Number(e.totalPreviousClicks.toFixed(3)),averageDropPct:Number((e.totalClickLoss/e.totalPreviousClicks*100).toFixed(1)),recommendation:`Review the shared ${e.template.label} pattern, but verify technical state, seasonality, and SERP intent before applying one change across ${e.count} retained query/page findings.`})).sort((e,t)=>t.totalClickLoss-e.totalClickLoss||oE(e.id,t.id))}function gE(e,t,n,r){return e===void 0||!Number.isFinite(e)?t:Math.min(r,Math.max(n,e))}function _E(e){let t=gE(e.minDropPct,20,0,100),n=gE(e.minPreviousClicks,2,0,1e9),r=gE(e.minClickLoss,1,0,1e9),i=Math.floor(gE(e.limit,25,1,100)),a=uE(e.currentRows),o=uE(e.previousRows),s={currentSourceRows:e.currentRows.length,previousSourceRows:e.previousRows.length,currentInvalidRows:a.invalid,previousInvalidRows:o.invalid,currentAggregatedRows:a.rows.length,previousAggregatedRows:o.rows.length,lowEvidenceRows:0,lowActionabilityRows:0,brandRows:0,currentRowNotRetained:0,urlShiftRows:0,belowClickLossRows:0,belowDropRows:0,eligibleRows:0,returnedRows:0,limitedRows:0,eligibleGroups:0,returnedGroups:0,limitedGroups:0},c=new Map(a.rows.map(e=>[`${sE(e.keys[0])}\u0000${e.keys[1]}`,e])),l=new Map;for(let e of a.rows){let t=sE(e.keys[0]),n=l.get(t)??new Set;n.add(e.keys[1]),l.set(t,n)}let u=[];for(let i of o.rows){let a=i.keys[0],o=i.keys[1];if(i.clicks<=0||i.clicks<n){s.lowEvidenceRows++;continue}if(Lv(a)){s.lowActionabilityRows++;continue}if(M_({query:a,siteUrl:e.site,brandTerms:e.brandTerms,includeBrand:e.includeBrand})){s.brandRows++;continue}let d=`${sE(a)}\u0000${o}`,f=c.get(d);if(!f){l.get(sE(a))?.size?s.urlShiftRows++:s.currentRowNotRetained++;continue}let p=i.clicks-f.clicks;if(p<=0||p<r){s.belowClickLossRows++;continue}let m=p/i.clicks*100;if(m<t){s.belowDropRows++;continue}let h=pE(f,i),g={query:a,url:o,template:fE(o),clickLoss:Number(p.toFixed(3)),dropPct:Number(m.toFixed(1)),current:dE(f),previous:dE(i),diagnosis:h.diagnosis,signals:h.signals,evidenceScope:`retained-query-page-row`};u.push({...g,recommendation:mE(g)})}let d=u.sort((e,t)=>t.clickLoss-e.clickLoss||t.previous.clicks-e.previous.clicks||oE(e.query,t.query)||oE(e.url,t.url)),f=d.slice(0,i),p=Number(d.reduce((e,t)=>e+t.clickLoss,0).toFixed(3)),m=Number(f.reduce((e,t)=>e+t.clickLoss,0).toFixed(3));s.eligibleRows=d.length,s.returnedRows=f.length,s.limitedRows=d.length-f.length;let h=hE(d),g=h.slice(0,100);return s.eligibleGroups=h.length,s.returnedGroups=g.length,s.limitedGroups=h.length-g.length,{selection:s,totals:{eligibleObservedRetainedQueryClickLoss:p,returnedObservedRetainedQueryClickLoss:m},items:f,groups:g,templates:PT(f)}}const vE=1e5,yE={searchAnalytics:e_,now:()=>new Date};function bE(e){return e.dataStatus===`empty`?`Search Console returned no retained query/page rows for either comparison window.`:e.dataStatus===`partial`&&e.selection.currentAggregatedRows===0?`The current retained-row window is empty, so no query/page losses were inferred.`:e.dataStatus===`partial`&&e.selection.previousAggregatedRows===0?`The comparison retained-row window is empty, so query/page decay could not be measured.`:e.selection.eligibleRows?`${e.selection.returnedRows} of ${e.selection.eligibleRows} observed retained query/page declines returned for review.`:`No observed retained query/page declines matched the report filters.`}async function xE(e,t=yE){if(e.comparison!==void 0&&e.comparison!==`previous-period`&&e.comparison!==`year-over-year`)throw new A(`INVALID_INPUT`,`comparison must be previous-period or year-over-year.`);if(e.brandTerms&&(e.brandTerms.length>20||e.brandTerms.some(e=>!e.trim()||e.trim().length>200)))throw new A(`INVALID_INPUT`,`brandTerms must contain at most 20 non-empty terms of 200 characters or fewer.`);let n=Y({value:e.days,fallback:28,minimum:1,maximum:548,label:`days`}),r=Y({value:e.limit,fallback:25,minimum:1,maximum:100,label:`limit`}),i=_T({value:e.minDropPct,fallback:20,minimum:0,maximum:100,label:`minDropPct`}),a=_T({value:e.minPreviousClicks,fallback:2,minimum:0,maximum:1e9,label:`minPreviousClicks`}),o=_T({value:e.minClickLoss,fallback:1,minimum:0,maximum:1e9,label:`minClickLoss`}),s=t.now(),c=K_(e,548),l=c?.range??G_(n,s),u=e.comparison??`previous-period`,d=wT(l,u);CT({current:l,previous:d,now:s});let f={dimensions:[`query`,`page`],aggregationType:`auto`,type:`web`,dataState:`final`,maxRows:vE},[p,m]=await Promise.all([t.searchAnalytics(e.site,{...f,...l},{refresh:e.refresh}),t.searchAnalytics(e.site,{...f,...d},{refresh:e.refresh})]),h=new OT;h.addGsc(p.calls,p.rowsFetched),h.addGsc(m.calls,m.rowsFetched);let g=_E({site:e.site,currentRows:p.rows,previousRows:m.rows,minDropPct:i,minPreviousClicks:a,minClickLoss:o,limit:r,brandTerms:e.brandTerms,includeBrand:e.includeBrand}),_=p.rowsFetched>=vE,v=m.rowsFetched>=vE,y=_||v,b=p.rowsFetched===0&&m.rowsFetched===0?`empty`:p.rowsFetched===0||m.rowsFetched===0||y||g.selection.currentInvalidRows>0||g.selection.previousInvalidRows>0||g.selection.currentRowNotRetained>0?`partial`:g.selection.eligibleRows===0?`filtered`:`complete`,x={dataStatus:b,selection:g.selection};return{schemaVersion:1,site:e.site,generatedAt:s.toISOString(),comparison:u,ranges:{current:l,previous:d},rangeDays:c?.days??n,dataStatus:b,source:{provider:`google-search-console`,dimensions:[`query`,`page`],aggregationType:`auto`,searchType:`web`,dataState:`final`,current:{rowsFetched:p.rowsFetched,calls:p.calls,maxRows:vE,possiblyTruncated:_},previous:{rowsFetched:m.rowsFetched,calls:m.calls,maxRows:vE,possiblyTruncated:v},completeness:y?`possibly-truncated`:`retained-query-rows-only`},methodology:{id:`gsc_retained_query_page_decay_v2`,version:2,gscHistoryMonths:16,missingRowsTreatedAsZero:!1,urlShiftsExcluded:!0,causeLanguage:`signals-not-attribution`},filters:{minDropPct:i,minPreviousClicks:a,minClickLoss:o,limit:r,brand:e.includeBrand?`included`:`excluded`},selection:g.selection,summary:{eligibleRows:g.selection.eligibleRows,returnedRows:g.selection.returnedRows,groups:g.selection.eligibleGroups,observedRetainedQueryClickLoss:g.totals.eligibleObservedRetainedQueryClickLoss,returnedObservedRetainedQueryClickLoss:g.totals.returnedObservedRetainedQueryClickLoss,brandFiltering:e.includeBrand?`included`:`excluded`,verdict:bE(x)},caveats:[`Current window: ${l.startDate} to ${l.endDate}; comparison: ${d.startDate} to ${d.endDate}.`,`Only declines observed in retained GSC query/page rows are counted. Missing current rows are reported in selection and never converted to zero traffic.`,`Queries retained on a different current URL are counted as URL shifts and excluded from content-decay actions.`,`Search Console API history is limited to a rolling 16 months. Ranges outside that horizon are rejected instead of compared partially.`,`Search Console omits anonymized and lower-volume rows and does not guarantee every row. Signals do not establish cause.`],recommendations:g.groups.length?g.groups.slice(0,5).map(e=>e.recommendation):[`No content-decay action is recommended from the observed retained rows.`],items:g.items,groups:g.groups,templates:g.templates,ledgerSummary:h.summary(),warnings:[...y?[`At least one GSC window reached the 100,000-row safety cap. Results may be truncated.`]:[],...g.selection.currentRowNotRetained?[`${g.selection.currentRowNotRetained} previous query/page rows were not retained in the current response and were excluded rather than treated as zero.`]:[],...g.selection.currentInvalidRows||g.selection.previousInvalidRows?[`${g.selection.currentInvalidRows} current and ${g.selection.previousInvalidRows} comparison rows had invalid dimensions or metrics and were excluded.`]:[],...m.rowsFetched===0&&p.rowsFetched>0?[`The comparison window returned no retained query/page rows, so decay could not be measured.`]:[],...g.selection.urlShiftRows?[`${g.selection.urlShiftRows} previous query/page rows now appear under another URL and were excluded from content-decay actions.`]:[]]}}function SE(e){return e.normalize(`NFKC`).toLocaleLowerCase(`und`).replace(/\s+/gu,` `).trim()}function CE(e){return`${e.template.id}:${SE(e.query)}`}function wE(e,t){return e<t?-1:+(e>t)}function TE(e){return e.technicalUrls>0?`${e.technicalUrls} of ${e.group.urlCount} affected URLs have verified technical evidence. Fix those HTTP, indexability, redirect, or canonical issues before testing shared search-framing changes.`:`${e.group.urlCount} distinct ${e.group.template.label.toLowerCase()} URLs share the query pattern "${e.group.query}". Compare their live search results and verified page evidence before testing a shared title, snippet, or heading change.`}function EE(e){let t=new Map;for(let n of e){let e=CE(n);t.set(e,[...t.get(e)??[],n])}return[...t.entries()].map(([e,t])=>{let n=t[0];if(!n||n.template.confidence===`low`)return;let r=[...new Set(t.map(e=>e.url))];if(r.length<2)return;let i=new Set(t.filter(e=>e.contentVerification?.classification===`technical-check`).map(e=>e.url)).size,a={id:e,label:`${n.template.label} - ${n.query}`,query:n.query,template:n.template,rowCount:t.length,urlCount:r.length,totalEstimatedCtrClickShortfall:Number(t.reduce((e,t)=>e+t.estimatedCtrClickShortfall,0).toFixed(2)),totalImpressions:Number(t.reduce((e,t)=>e+t.impressions,0).toFixed(0)),sampleUrls:r.slice(0,3),recommendation:``};return a.recommendation=TE({group:a,technicalUrls:i}),a}).filter(e=>e!==void 0).sort((e,t)=>t.totalEstimatedCtrClickShortfall-e.totalEstimatedCtrClickShortfall||t.totalImpressions-e.totalImpressions||wE(e.id,t.id))}function DE(e,t,n,r){return e===void 0||!Number.isFinite(e)?t:Math.min(r,Math.max(n,Math.floor(e)))}function OE(e){try{let t=new URL(e);return t.protocol===`http:`||t.protocol===`https:`}catch{return!1}}function kE(e,t,n){return e.keys.length>=2&&!!t&&OE(n)&&Number.isFinite(e.clicks)&&e.clicks>=0&&Number.isFinite(e.impressions)&&e.impressions>0&&e.clicks<=e.impressions&&Number.isFinite(e.ctr)&&e.ctr>=0&&e.ctr<=1&&Number.isFinite(e.position)&&e.position>=1}function AE(e){let t={sourceRows:e.rows.length,invalidRows:0,outsideBenchmarkPositionRows:0,lowActionabilityRows:0,brandRows:0,benchmarkRows:0,outsideCandidatePositionRows:0,belowMinimumRows:0,atOrAboveTargetRows:0,eligibleRows:0,returnedRows:0,limitedRows:0},n=[];for(let r of e.rows){let i=r.keys[0]?.trim()??``,a=r.keys[1]?.trim()??``;kE(r,i,a)?r.position>10?t.outsideBenchmarkPositionRows++:Rv(i)?t.lowActionabilityRows++:M_({query:i,siteUrl:e.site,brandTerms:e.brandTerms,includeBrand:e.includeBrand})?t.brandRows++:(n.push({...r,keys:[i,a]}),t.benchmarkRows++):t.invalidRows++}return{rows:n,selection:t}}function jE(e){return{targetCtr:e.benchmark.ctr,source:e.benchmark.source,samplePopulation:`all_qualified_url_samples`,peerRows:e.benchmark.rows,peerImpressions:e.benchmark.impressions,qualifiedPeerImpressions:e.benchmark.qualifiedImpressions,urlSamples:e.benchmark.urlSamples,positiveUrlSamples:e.benchmark.positiveUrlSamples,excludedTargetRows:e.excludedTargetRows,leaveOut:`target_url`,confidence:e.benchmark.source.startsWith(`site_gsc_`)?`site-data`:`fallback`,heuristic:!0}}function ME(e){let t=e.row.keys[0]??``,n=e.row.keys[1]??``,r=Number((Math.max(0,e.benchmark.ctr-e.row.ctr)*e.row.impressions).toFixed(2)),i=jE({benchmark:e.benchmark,excludedTargetRows:e.excludedTargetRows});return{query:t,url:n,template:NT(n),position:e.row.position,clicks:e.row.clicks,impressions:e.row.impressions,ctr:e.row.ctr,targetCtr:e.benchmark.ctr,benchmark:i,estimatedCtrClickShortfall:r,priority:{method:`impressions_x_target_ctr_shortfall`,score:r,heuristic:!0,estimatedClickLift:!1},finding:`ctr-target-shortfall`,recommendation:{principle:`C.3`,evidenceRef:`GSC average position is ${e.row.position.toFixed(1)} with ${e.row.impressions} impressions and CTR ${e.row.ctr.toFixed(4)} versus a heuristic target of ${e.benchmark.ctr.toFixed(4)} from ${i.source}.`,action:`Inspect the live search result, query intent, title, snippet, device/country mix, and page evidence before choosing an edit.`,effort:`S`,confidence:`low`,impactEstimate:`${r.toFixed(2)} calculated CTR click shortfall in this window if observed CTR matched the heuristic target; this is not a traffic forecast.`}}}function NE(e,t){return e<t?-1:+(e>t)}function PE(e,t){return t.estimatedCtrClickShortfall-e.estimatedCtrClickShortfall||t.impressions-e.impressions||NE(e.query,t.query)||NE(e.url,t.url)}function FE(e){return Number(e.reduce((e,t)=>e+t,0).toFixed(2))}function IE(e){let t=DE(e.minImpressions,200,0,1e9),n=DE(e.limit,25,1,100),r=AE(e),i=Ev(r.rows,{samplePopulation:`all_qualified_url_samples`,fallbackSource:`builtin_position_ctr_curve_v1`}),a=[];for(let e of r.rows){if(e.position<4){r.selection.outsideCandidatePositionRows++;continue}if(e.impressions<t){r.selection.belowMinimumRows++;continue}let n=e.keys[1]??``,o=r.rows.filter(e=>l_(e.keys[1]??``,n)).length,s=i.forUrl(e);if(e.ctr>=s.ctr){r.selection.atOrAboveTargetRows++;continue}a.push(ME({row:e,benchmark:s,excludedTargetRows:o})),r.selection.eligibleRows++}a.sort(PE);let o=a.slice(0,n);return r.selection.returnedRows=o.length,r.selection.limitedRows=a.length-o.length,{site:e.site,minImpressions:t,limit:n,dataStatus:e.rows.length===0?`empty`:a.length===0?`filtered`:`available`,selection:r.selection,methodology:{id:`gsc_quick_wins_v2`,source:`google_search_console_query_page_rows`,position:{metric:`gsc_average_position`,minimumInclusive:4,maximumInclusive:10},benchmark:{method:`position_bucket_url_p75_v2`,samplePopulation:`all_qualified_url_samples`,leaveOut:`target_url`,minimumUrlImpressions:30,minimumQualifiedImpressions:1e3,minimumUrlSamples:5,minimumPositiveUrlSamples:3,fallback:{id:`seo_builtin_position_ctr`,version:1,kind:`built_in_heuristic`,curve:Object.fromEntries(Object.entries(L_).map(([e,t])=>[e,t]))},heuristic:!0},priority:{method:`impressions_x_target_ctr_shortfall`,formula:`impressions * max(0, target CTR - observed CTR)`,heuristic:!0,estimatedClickLift:!1}},provenance:{inputScope:`provided_rows`,selectionOrder:[`valid_row`,`benchmark_position`,`query_quality`,`brand`,`candidate_position`,`minimum_impressions`,`target_ctr_shortfall`,`limit`],selection:r.selection},summary:{eligibleRows:a.length,returnedRows:o.length,eligibleImpressions:FE(a.map(e=>e.impressions)),returnedImpressions:FE(o.map(e=>e.impressions)),eligibleEstimatedCtrClickShortfall:FE(a.map(e=>e.estimatedCtrClickShortfall)),returnedEstimatedCtrClickShortfall:FE(o.map(e=>e.estimatedCtrClickShortfall)),uniqueEligibleUrls:new Set(a.map(e=>e.url)).size,uniqueEligibleQueries:new Set(a.map(e=>e.query)).size},items:o,eligibleItems:a,benchmarkByPosition:i.byPosition}}function LE(e,t){return e<t?-1:+(e>t)}function RE(e){return[...new Set(e.filter(Boolean))]}function zE(e){let t=new Map;for(let n of e)t.set(n.template.id,[...t.get(n.template.id)??[],n]);return[...t.entries()].map(([e,t])=>{let n=t[0];if(!n||n.template.confidence===`low`)return;let r=[...new Set(t.map(e=>e.url))];if(r.length<2)return;let i=new Set(t.filter(e=>e.contentVerification?.classification===`technical-check`).map(e=>e.url)).size,a=RE(t.map(e=>e.query)).slice(0,3);return{templateId:e,templateLabel:n.template.label,rowCount:t.length,urlCount:r.length,totalEstimatedCtrClickShortfall:Number(t.reduce((e,t)=>e+t.estimatedCtrClickShortfall,0).toFixed(2)),totalImpressions:Number(t.reduce((e,t)=>e+t.impressions,0).toFixed(0)),action:i>0?`Fix verified technical issues on ${i} affected URLs before testing shared template changes.`:`Compare live results and verified page evidence for ${r.length} affected URLs before testing a shared title, snippet, or heading change.`,evidence:`${t.length} eligible rows across ${r.length} distinct URLs share this recognised template. Example queries: ${a.join(`; `)}.`}}).filter(e=>e!==void 0).sort((e,t)=>t.totalEstimatedCtrClickShortfall-e.totalEstimatedCtrClickShortfall||t.totalImpressions-e.totalImpressions||LE(e.templateId,t.templateId))}function BE(e){let t=new Map;for(let n of e){let e=t.get(n.template.id)??{label:n.template.label,rows:0,urls:new Set};e.rows++,e.urls.add(n.url),t.set(n.template.id,e)}return[...t.entries()].map(([e,t])=>({id:e,label:t.label,rowCount:t.rows,urlCount:t.urls.size,sampleUrls:[...t.urls].sort(LE).slice(0,3)})).sort((e,t)=>t.urlCount-e.urlCount||t.rowCount-e.rowCount||LE(e.id,t.id)).slice(0,5)}const VE=1e5,HE={searchAnalytics:e_,fetch:Ag,extract:Th,now:()=>new Date};function UE(e){return[...new Set(e.filter(Boolean))]}function WE(e,t){e.contentVerification=t,e.finding=t.classification,e.recommendation={...e.recommendation,action:gv(t),confidence:t.status===`verified`&&t.classification!==`covered`?`medium`:`low`,evidenceRef:`${e.recommendation.evidenceRef} ${t.summary}`}}async function GE(e,t=HE){let n=K_(e,548),r=n?.days??Y({value:e.days,fallback:28,minimum:1,maximum:548,label:`Days`}),i=Y({value:e.verifyLimit,fallback:5,minimum:0,maximum:100,label:`Verification limit`}),a=t.now().toISOString(),o=n?.range??G_(r,new Date(a)),s=await t.searchAnalytics(e.site,{...o,dimensions:[`query`,`page`],type:`web`,dataState:`final`,maxRows:VE},{refresh:e.refresh}),c=new OT;c.addGsc(s.calls,s.rowsFetched);let l=IE({rows:s.rows,site:e.site,minImpressions:e.minImpressions,limit:e.limit,brandTerms:e.brandTerms,includeBrand:e.includeBrand}),u=e.verifyContent===!0||e.verifyLimit!==void 0,d=u?l.items.slice(0,i):[],f=new Map,p=(e,n)=>{let r=f.get(e);if(r)return r;let i=t.fetch(e,n);return f.set(e,i),i};for(let n of d)WE(n,await bv({query:n.query,url:n.url,js:e.js,refresh:e.refresh,rate:e.rate,verifiedAt:a,fetch:p,extract:t.extract}));let m=EE(l.eligibleItems),h=BE(l.eligibleItems),g=zE(l.eligibleItems),_=d.filter(e=>e.contentVerification?.status===`verified`).length,v=d.filter(e=>e.contentVerification?.status===`failed`).length,y=d.filter(e=>e.contentVerification?.classification===`technical-check`).length,b=UE([...g.slice(0,3).map(e=>e.action),...m.slice(0,3).map(e=>e.recommendation),l.items[0]?.recommendation.action??``]).slice(0,5),x=d.flatMap(e=>{let t=e.contentVerification;return t?[...(t.warnings??[]).map(t=>({stage:`verification`,url:e.url,code:`page-warning`,message:t})),...t.error?[{stage:`verification`,url:e.url,code:`verification-failed`,message:t.error}]:[]]:[]});return{site:e.site,generatedAt:a,range:o,rangeDays:r,source:{provider:`google-search-console`,dimensions:[`query`,`page`],searchType:`web`,dataState:`final`,rowsFetched:s.rowsFetched,calls:s.calls,maxRows:VE,possiblyTruncated:s.rowsFetched>=VE,completeness:`retained-query-rows-only`},dataStatus:l.dataStatus,selection:l.selection,methodology:l.methodology,provenance:{...l.provenance,verification:{optional:!0,population:`returned_rows_in_priority_order`,fetchDeduplication:`exact_url`}},benchmark:{method:l.methodology.benchmark.method,peerRows:l.selection.benchmarkRows,byPosition:l.benchmarkByPosition},verification:u?{requested:!0,limit:i,attemptedRows:d.length,attemptedUrls:f.size,verified:_,technical:y,failed:v}:{requested:!1,attemptedRows:0,attemptedUrls:0,verified:0,technical:0,failed:0},summary:{...l.summary,repeatedQueryGroups:m.length,templatePatterns:g.length,brandFiltering:e.includeBrand?`included`:`excluded`,verdict:l.dataStatus===`empty`?`Search Console returned no retained query/page rows for this date window.`:l.dataStatus===`filtered`?`No retained rows met the quick-win report criteria.`:`${l.summary.eligibleRows} eligible CTR-target rows found; ${l.summary.returnedRows} returned for review.`},caveats:[`Date window: ${o.startDate} to ${o.endDate}, using final GSC data where available.`,`Selection: ${l.selection.sourceRows} source rows, ${l.selection.benchmarkRows} benchmark rows, ${l.selection.eligibleRows} eligible rows, ${l.selection.returnedRows} returned.`,`Position is GSC average position across impressions; a value from 4 to 10 does not mean the URL ranked on page one for every impression.`,`Target CTR is a deterministic site-peer or versioned built-in heuristic. The calculated CTR click shortfall is for prioritisation and is not a traffic forecast.`,`Search Analytics exposes top retained query rows only. Anonymized and lower-value queries can be absent even after pagination is exhausted.`,s.rowsFetched>=VE?`The report reached its ${VE.toLocaleString(`en-US`)}-row safety cap.`:``,u?`Page evidence was checked for ${d.length} returned rows across ${f.size} distinct URLs.`:`Page evidence was not requested; CTR shortfalls do not establish why CTR differs.`].filter(Boolean),recommendations:b.length?b:[l.dataStatus===`empty`?`Choose a longer window only if this property should have Search Console data.`:`Adjust the minimum impressions only if you intentionally want a broader, lower-confidence review.`],templates:h,templateRecommendations:g,groups:m,items:l.items,ledgerSummary:c.summary(),warnings:x}}function KE(e,t){let n=[...e].map(e=>e.codePointAt(0)??0),r=[...t].map(e=>e.codePointAt(0)??0),i=Math.min(n.length,r.length);for(let e=0;e<i;e++){let t=(n[e]??0)-(r[e]??0);if(t!==0)return t}return n.length-r.length}function qE(e){try{let t=new URL(e);return t.protocol===`http:`||t.protocol===`https:`}catch{return!1}}function JE(e,t,n){return!!t&&qE(n)&&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 YE(e){return M_({query:e.query,siteUrl:e.site,brandTerms:e.brandTerms,includeBrand:e.includeBrand})}function XE(e){return{type:`investigate-ranking`,confidence:`low`,evidence:`GSC reports ${e.impressions} impressions at average position ${e.position.toFixed(2)} for "${e.query}" on ${e.url}.`,action:`Inspect indexability, canonical state, current query coverage, competing URLs, and relevant internal links for ${e.url} before choosing a change.`}}function ZE(e){let t=e.keys[0]?.trim()??``,n=e.keys[1]?.trim()??``,r=Number(Math.min(1,Math.max(.1,(21-e.position)/10)).toFixed(4));return{query:t,url:n,template:NT(n),clicks:Number(e.clicks.toFixed(3)),impressions:Number(e.impressions.toFixed(3)),ctr:Number(e.ctr.toFixed(4)),position:Number(e.position.toFixed(2)),priority:{method:`impressions_x_position_proximity`,score:Number((e.impressions*r).toFixed(2)),demandImpressions:Number(e.impressions.toFixed(3)),positionProximity:r,heuristic:!0,estimatedClickLift:!1},recommendation:XE({query:t,url:n,impressions:e.impressions,position:e.position})}}function QE(e,t){return t.priority.score-e.priority.score||t.impressions-e.impressions||e.position-t.position||KE(e.query,t.query)||KE(e.url,t.url)}function $E(e){let t=new Map;for(let n of e){let e=t.get(n.template.id)??[];e.push(n),t.set(n.template.id,e)}return[...t.entries()].map(([e,t])=>{let n=t[0];if(!n)return;let r=[...new Set(t.map(e=>e.url))],i=[...new Set(t.map(e=>e.query))],a=t.reduce((e,t)=>e+t.impressions,0),o=n.template.confidence!==`low`&&r.length>=2,s=`${t.length} eligible query/page rows represent ${r.length} unique URLs and ${i.length} unique queries.`;return{id:e,label:n.template.label,template:n.template,rowCount:t.length,uniqueUrls:r.length,uniqueQueries:i.length,totalImpressions:Number(a.toFixed(3)),bestPosition:Math.min(...t.map(e=>e.position)),impressionWeightedPosition:Number((t.reduce((e,t)=>e+t.position*t.impressions,0)/a).toFixed(2)),sampleQueries:i.slice(0,5),sampleUrls:r.slice(0,3),actionScope:o?`shared-template-candidate`:`page-level-review`,recommendation:{type:`investigate-ranking`,confidence:`low`,evidence:s,action:o?`Review the sampled ${n.template.label.toLowerCase()} URLs for a recurring technical, content, or internal-link pattern before changing the shared template.`:`Review these URLs individually; the current evidence does not support a shared-template change.`}}}).filter(e=>e!==void 0).sort((e,t)=>t.totalImpressions-e.totalImpressions||t.uniqueUrls-e.uniqueUrls||KE(e.id,t.id))}function eD(e,t,n,r){return e===void 0||!Number.isFinite(e)?t:Math.min(r,Math.max(n,Math.floor(e)))}function tD(e){let t={sourceRows:e.rows.length,invalidRows:0,outsidePositionRows: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()??``;JE(r,i,r.keys[1]?.trim()??``)?r.position<=10||r.position>20?t.outsidePositionRows++:r.impressions<e.minImpressions?t.belowMinimumRows++:Rv(i)?t.lowActionabilityRows++:YE({query:i,site:e.site,brandTerms:e.brandTerms,includeBrand:e.includeBrand})?t.brandRows++:(n.push(r),t.eligibleRows++):t.invalidRows++}return{rows:n,selection:t}}function nD(e){let t=eD(e.minImpressions,100,0,1e9),n=eD(e.limit,25,1,100),r=tD({...e,minImpressions:t}),i=r.rows.map(ZE).sort(QE),a=i.slice(0,n);r.selection.returnedRows=a.length,r.selection.limitedRows=i.length-a.length;let o=$E(i),s=i.reduce((e,t)=>e+t.impressions,0),c=a.reduce((e,t)=>e+t.impressions,0),l=e.rows.length===0?`empty`:i.length===0?`filtered`:`available`;return{site:e.site,minImpressions:t,limit:n,dataStatus:l,selection:r.selection,methodology:{id:`gsc_striking_distance_v2`,source:`google_search_console_query_page_rows`,position:{minimumExclusive:10,maximumInclusive:20},ctrEligibilityFilter:!1,priority:{method:`impressions_x_position_proximity`,formula:`impressions * clamp((21 - position) / 10, 0.1, 1)`,heuristic:!0,estimatedClickLift:!1},grouping:{population:`all_eligible_rows_before_limit`,sharedTemplateMinimumUniqueUrls:2,lowConfidenceTemplatesAreShared:!1}},provenance:{inputScope:`provided_rows`,selectionOrder:[`valid_row`,`position`,`minimum_impressions`,`query_quality`,`brand`],selection:r.selection},summary:{eligibleRows:i.length,returnedRows:a.length,eligibleImpressions:Number(s.toFixed(3)),returnedImpressions:Number(c.toFixed(3)),uniqueEligibleUrls:new Set(i.map(e=>e.url)).size,uniqueEligibleQueries:new Set(i.map(e=>e.query)).size,groups:o.length},groups:o,items:a}}const rD=1e5,iD={searchAnalytics:e_,verifyContent:bv,now:()=>new Date};function aD(e,t,n,r){return e===void 0||!Number.isFinite(e)?t:Math.min(r,Math.max(n,Math.floor(e)))}function oD(e){if(e===void 0)return 28;if(!Number.isInteger(e)||e<1||e>548)throw new A(`INVALID_INPUT`,`Days must be a whole number between 1 and 548.`);return e}function sD(e,t){return Zg(e,t)}function cD(e){return e.status===`failed`?{type:`verification-failed`,confidence:`low`,evidence:e.error??`The page could not be verified.`,action:gv(e)}:e.classification===`technical-check`?{type:`fix-technical`,confidence:`medium`,evidence:`Verified technical signals: ${e.signals.join(`, `)}.`,action:gv(e)}:{type:e.classification===`covered`?`investigate-ranking`:`review-page-evidence`,confidence:`low`,evidence:e.summary,action:gv(e)}}function lD(e){return e.slice(0,5).map(e=>({id:e.id,label:e.label,count:e.rowCount,sampleUrls:e.sampleUrls}))}function uD(e){return e.dataStatus===`empty`?`Search Console returned no retained query/page rows for this date window.`:e.dataStatus===`filtered`?`No retained query/page rows met the report filters.`:`${e.summary.eligibleRows} eligible query/page rows found; ${e.summary.returnedRows} returned for review.`}async function dD(e,t=iD){let n=oD(e.days),r=t.now().toISOString(),i=sD(n,new Date(r)),a=await t.searchAnalytics(e.site,{...i,dimensions:[`query`,`page`],type:`web`,dataState:`final`,maxRows:rD},{refresh:e.refresh}),o=nD({rows:a.rows,site:e.site,minImpressions:e.minImpressions,limit:e.limit,brandTerms:e.brandTerms,includeBrand:e.includeBrand}),s=aD(e.verifyLimit,5,0,o.limit),c=e.verifyContent===!0||e.verifyLimit!==void 0,l=c?Math.min(s,o.items.length):0,u=await Promise.all(o.items.map(async(n,i)=>{if(i>=l)return n;let a=await t.verifyContent({query:n.query,url:n.url,js:e.js,refresh:e.refresh,rate:e.rate,verifiedAt:r});return{...n,contentVerification:a,recommendation:cD(a)}})),d=u.slice(0,l).map(e=>e.contentVerification).filter(e=>!!e),f=a.rowsFetched>=rD;return{site:e.site,generatedAt:r,range:i,rangeDays:n,source:{provider:`google-search-console`,dimensions:[`query`,`page`],searchType:`web`,dataState:`final`,rowsFetched:a.rowsFetched,calls:a.calls,maxRows:rD,possiblyTruncated:f,completeness:`retained-query-rows-only`},dataStatus:o.dataStatus,selection:o.selection,methodology:o.methodology,verification:c?{requested:!0,limit:s,attempted:l,verified:d.filter(e=>e.status===`verified`).length,technical:d.filter(e=>e.classification===`technical-check`).length,failed:d.filter(e=>e.status===`failed`).length}:{requested:!1,attempted:0,verified:0,technical:0,failed:0},items:u,templates:lD(o.groups),groups:o.groups,summary:{...o.summary,brandFiltering:e.includeBrand?`included`:`excluded`,verdict:uD(o)},caveats:[`Date window: ${i.startDate} to ${i.endDate}, using final GSC data where available.`,`Selection: ${o.selection.sourceRows} source rows, ${o.selection.eligibleRows} eligible, ${o.selection.returnedRows} returned.`,`Position is GSC average position for the property's topmost result per impression; it does not prove a URL ranked on a literal second results page every time.`,`Search Analytics exposes retained query rows only. Anonymized and lower-value queries can be absent even when pagination is exhausted.`,`Priority is a demand-and-position heuristic, not an estimated click lift or proof that a change will improve rankings.`,`Content verification: ${c?`${l} ${l===1?`row`:`rows`} attempted`:`not run`}.`,f?`The report reached its ${rD.toLocaleString(`en-US`)}-row safety cap.`:``].filter(Boolean),recommendations:o.groups.length?o.groups.slice(0,5).map(e=>e.recommendation.action):[`No action is recommended from this report. Lower the minimum impressions or widen the date window if you need long-tail inspection.`]}}function fD(e,t){let n=`${e} ${t}`.toLowerCase();return n.includes(`core`)?`core`:n.includes(`spam`)?`spam`:n.includes(`discover`)?`discover`:n.includes(`ranking`)?`ranking`:n.includes(`index`)?`indexing`:n.includes(`serving`)?`serving`:`other`}async function pD(){let e=await n(`https://status.search.google.com/incidents.json`);if(!e.ok)throw new A(`OPTIONAL_PROVIDER_UNAVAILABLE`,`Google Search Status fetch failed with ${e.status}.`);return await e.json()}async function mD(e={}){let t=await pD(),n=e.product?.toLowerCase();return t.filter(e=>!n||e.service_name.toLowerCase()===n).map(e=>{let t=e.end?`complete`:e.status_impact===`SERVICE_INFORMATION`?`rolling-out`:`incident`;return{id:e.id,name:e.external_desc,type:fD(e.external_desc,e.service_name),product:e.service_name,start:e.begin,end:e.end,status:t,sourceUrl:`https://status.search.google.com/${e.uri}`}}).slice(0,e.limit??25)}function hD(e){let t=(e.paddingDays??3)*864e5,n=new Date(e.startDate).getTime()-t,r=new Date(e.endDate).getTime()+t;return e.updates.filter(e=>{let t=new Date(e.start).getTime(),i=e.end?new Date(e.end).getTime():Date.now();return t<=r&&i>=n})}function gD(e){return new Date(e).toISOString()}function _D(e){return{id:e.id,site:e.site_url,name:e.name,dimension:e.dimension,matchType:e.match_type,pattern:e.pattern,createdAt:gD(e.created_at)}}function vD(e){return{id:e.id,site:e.site_url,scope:e.scope,target:e.target,title:e.title,description:e.description??void 0,changedAt:e.changed_at,createdAt:gD(e.created_at)}}function yD(e){let t={id:v(),site:e.site,scope:e.scope,target:e.target,title:e.title,description:e.description,changedAt:e.changedAt,createdAt:new Date().toISOString()};return j().prepare(`INSERT INTO seo_changes
567
+ `)},{path:`inventory/pages.md`,content:[uw({type:`inventory`,title:`Selected concept pages`}),`# Selected Concept Pages`,``,`| URL | Status | Indexable | Title |`,`| --- | ---: | --- | --- |`,...i.map(e=>`| ${e.finalUrl} | ${e.status} | ${e.indexable?`yes`:`no`} | ${dw(e.title??``).replace(/\|/g,`\\|`)} |`),``,`# Citations`,``,`- [Crawl start URL](${e.config.url})`,``].join(`
568
+ `)},{path:`graph/links.md`,content:[uw({type:`graph`,title:`Internal link graph`}),`# Internal Link Graph`,``,...i.flatMap(e=>[`## ${fw(e)}`,``,`Source: ${e.finalUrl}`,``,...(e.sampleInternalLinks??[]).slice(0,25).map(e=>`- ${e}`),``]),`# Citations`,``,`- [Crawl start URL](${e.config.url})`,``].join(`
569
+ `)},{path:`caveats.md`,content:[uw({type:`caveats`,title:`Caveats`}),`# Caveats`,``,...(a.length?a:[`No crawl caveats were reported.`]).map(e=>`- ${e}`),...e.warnings.length?[``,`## Warnings`,``,...e.warnings.map(e=>`- ${e}`)]:[],``,`# Citations`,``,`- [Crawl start URL](${e.config.url})`,``].join(`
570
+ `)},...o];return{schemaVersion:1,reportId:e.id,sourceUrl:e.config.url,generatedAt:e.generatedAt,crawlStatus:e.status,rootTitle:n,files:s,conceptCount:o.length,selection:{sourcePages:r.sourcePages,eligiblePages:r.eligiblePages,duplicateFinalUrls:r.duplicateFinalUrls,selectedPages:i.length,limitedPages:r.eligiblePages-i.length,limit:r.limit,order:`search-clicks-impressions-internal-authority-inlinks-url`},caveats:a,warnings:e.warnings}}function yw(e){return/^---\n[\s\S]+?\n---\n/.test(e)}function bw(e){return!!e.match(/^---\n([\s\S]+?)\n---\n/)?.[1]?.match(/^type:\s*.+$/m)}function xw(e,t){let n=e.match(RegExp(`^${t}:\\s*(.+)$`,`m`))?.[1];if(n)try{let e=JSON.parse(n);return typeof e==`string`?e:void 0}catch{return}}function Sw(e){return e.endsWith(`/index.md`)||e===`index.md`||e.endsWith(`/log.md`)||e===`log.md`}function Cw(e){let t=[],n=new Map;for(let r of e)n.set(r.path,(n.get(r.path)??0)+1),(!r.path||r.path.startsWith(`/`)||r.path.split(`/`).some(e=>e===`..`||e===``))&&t.push({path:r.path||`(empty)`,severity:`error`,message:`File paths must be safe relative paths.`});for(let[e,r]of n)r>1&&t.push({path:e,severity:`error`,message:`File path is duplicated ${r} times.`});let r=new Map(e.map(e=>[e.path,e]));for(let e of[`index.md`,`log.md`,`concepts/index.md`,`inventory/pages.md`,`graph/links.md`,`caveats.md`])r.has(e)||t.push({path:e,severity:`error`,message:`Required seo OKF bundle file is missing.`});let i=r.get(`index.md`);i&&(xw(i.content,`okf`)!==`0.1`||xw(i.content,`type`)!==`index`)&&t.push({path:`index.md`,severity:`error`,message:`Root frontmatter must declare okf "0.1" and type "index".`});for(let n of e.filter(e=>e.path.endsWith(`.md`)))if(n.content.trim()||t.push({path:n.path,severity:`error`,message:`Markdown files must not be empty.`}),!Sw(n.path)){if(yw(n.content)?bw(n.content)||t.push({path:n.path,severity:`error`,message:`Concept frontmatter needs a non-empty type field.`}):t.push({path:n.path,severity:`error`,message:`Concept files need YAML frontmatter.`}),n.path.startsWith(`concepts/`)){let e=xw(n.content,`url`),r=!1;try{r=!!(e&&[`http:`,`https:`].includes(new URL(e).protocol))}catch{r=!1}r||t.push({path:n.path,severity:`error`,message:`Concept frontmatter needs a valid HTTP(S) url.`})}/\n# Citations\n/i.test(n.content)||t.push({path:n.path,severity:`warning`,message:`Files with claims should include a # Citations section.`})}let a=r.get(`concepts/index.md`)?.content??``;for(let n of e.filter(e=>e.path.startsWith(`concepts/`)&&!Sw(e.path))){let e=n.path.replace(/^concepts\//,``);a.includes(`](${e})`)||t.push({path:n.path,severity:`error`,message:`Concept is missing from concepts/index.md.`})}let o=e.filter(e=>e.path.startsWith(`concepts/`)&&!Sw(e.path)).length;return{valid:!t.some(e=>e.severity===`error`),files:e.length,concepts:o,issues:t}}function ww(e){let t=e.issues.filter(e=>e.severity===`error`),n=e.issues.filter(e=>e.severity===`warning`);return{title:`OKF bundle`,valid:e.valid,summary:e.valid?`This bundle passes seo OKF checks with ${e.concepts} concept files.`:`This OKF bundle has ${t.length} validation error${t.length===1?``:`s`}.`,files:e.files,concepts:e.concepts,errors:t.length,warnings:n.length,nextActions:e.valid?[`Review concept quality and publish the bundle where agents can fetch it.`]:t.slice(0,5).map(e=>`${e.path}: ${e.message}`)}}const Tw=m.enum([`low`,`medium`,`high`]),Ew=m.enum([`fix`,`review`]),Dw=m.enum([`canonical`,`content`,`response`,`headings`,`images`,`indexability`,`international`,`links`,`metadata`,`mobile`,`performance`,`security`,`social`,`structured-data`,`geo`]),Ow=m.enum(_b().map(e=>e.id)),kw=m.object({id:Ow,title:m.string(),category:Dw,defaultSeverity:Tw,recommendation:Ew,whyItMatters:m.string(),howToFix:m.string(),impactIfIgnored:m.string(),howToVerify:m.string(),agentHints:m.object({evidenceFields:m.array(m.string()).optional(),suggestedCommands:m.array(m.string()).optional()}).optional()}),Aw=m.object({clicks:m.number(),impressions:m.number(),ctr:m.number(),position:m.number()}),jw=m.object({sessions:m.number(),totalUsers:m.number(),conversions:m.number()}),Mw=m.object({semanticHtml:m.boolean(),structuredData:m.boolean(),hasAuthor:m.boolean(),hasDate:m.boolean(),questionHeadings:m.number().int(),listCount:m.number().int().optional(),tableCount:m.number().int().optional(),structuredBlocks:m.number().int(),answerable:m.boolean(),hasFaqSchema:m.boolean().optional(),hasQapageSchema:m.boolean().optional(),hasLlmsTxt:m.boolean().optional(),llmsTxtUrl:m.string().url().optional(),llmsTxtStatus:m.number().int().optional()}),Nw=m.enum([`no-required-properties`,`required-properties-observed`,`missing-required-properties`,`retired`,`not-assessed`]),Pw=m.object({"no-required-properties":m.number().int().nonnegative(),"required-properties-observed":m.number().int().nonnegative(),"missing-required-properties":m.number().int().nonnegative(),retired:m.number().int().nonnegative(),"not-assessed":m.number().int().nonnegative()}),Fw=m.object({format:m.enum([`json-ld`,`microdata`,`rdfa`]),block:m.number().int().nonnegative().optional(),path:m.string(),schemaType:m.enum([`Article`,`BlogPosting`,`NewsArticle`,`Product`,`BreadcrumbList`,`FAQPage`]),feature:m.enum([`article`,`product-snippet`,`breadcrumb`,`faq`]),status:Nw,observedProperties:m.array(m.string()),missingRequiredProperties:m.array(m.string()),limitations:m.array(m.string()),documentationUrl:m.string().url()}),Iw=m.enum([`missing`,`single`,`duplicate`,`conflicting`,`outside-head-only`,`invalid`]),Lw=m.object({title:m.string().optional(),metaDescription:m.string().optional(),canonical:m.object({status:Iw,url:m.string().url().optional()}),robots:m.object({meta:m.string().optional(),googlebot:m.string().optional(),http:m.string().optional()}),headings:m.array(m.object({level:m.number().int(),text:m.string()})),links:m.object({total:m.number().int().nonnegative(),internal:m.number().int().nonnegative(),external:m.number().int().nonnegative(),fingerprint:m.string()}),content:m.object({characters:m.number().int().nonnegative(),wordCount:m.number().int().nonnegative(),fingerprint:m.string()}),structuredData:m.object({blocks:m.number().int().nonnegative(),formats:m.array(m.enum([`json-ld`,`microdata`,`rdfa`])),schemaTypes:m.array(m.string())})}),Rw=m.object({raw:Lw,rendered:Lw,changed:m.array(m.enum([`title`,`metaDescription`,`canonical`,`robots`,`headings`,`links`,`content`,`structuredData`]))}),zw=m.object({url:m.string().url(),finalUrl:m.string().url(),status:m.number().int(),contentType:m.string().optional(),responseHeaders:m.record(m.string(),m.string()).optional(),responseTimeMs:m.number().optional(),sizeBytes:m.number().int().optional(),usedJs:m.boolean().optional(),fetchSource:m.enum([`cache`,`network`,`rendered`]).optional(),cacheState:m.enum([`hit`,`miss`,`bypass`]).optional(),fetchDiagnostics:m.object({source:m.enum([`cache`,`network`,`rendered`]),cache:m.enum([`hit`,`miss`,`bypass`]),fetched:m.boolean(),rendered:m.boolean(),blocked:m.boolean(),durationMs:m.number(),retries:m.number().int(),rateLimit:m.object({host:m.string(),concurrency:m.number().int(),intervalCap:m.number().int(),intervalMs:m.number().int()}),backpressure:m.object({host:m.string(),status:m.enum([`ok`,`slowed`,`stopped`]),reason:m.string().optional(),delayMs:m.number(),cooldownUntil:m.string().optional(),consecutiveSlow:m.number().int(),consecutiveBlocked:m.number().int(),consecutiveErrors:m.number().int(),recentP95Ms:m.number().optional()}).optional(),robotsTxt:m.object({url:m.string().url(),cache:m.enum([`hit`,`miss`,`bypass`]),allowed:m.boolean().nullable(),availability:m.enum([`available`,`absent`,`access-blocked`,`rate-limited`,`unreachable`]),status:m.number().int().optional(),error:m.string().optional()}).optional(),redirectChain:m.array(m.object({url:m.string().url(),status:m.number().int(),location:m.string().url().optional()})).optional(),rendering:m.object({mode:m.enum([`auto`,`on`,`off`]),status:m.enum([`not-requested`,`not-needed`,`skipped`,`rendered`,`unavailable`,`failed`]),raw:m.object({source:m.enum([`cache`,`network`]),cache:m.enum([`hit`,`miss`,`bypass`]),url:m.string().url(),finalUrl:m.string().url(),status:m.number().int()}).optional(),documentDifference:Rw.optional(),browser:m.object({source:m.enum([`environment`,`playwright-cache`,`system`]),product:m.string(),version:m.string().optional()}).optional(),navigation:m.object({waitUntil:m.literal(`domcontentloaded`),networkIdleTimeoutMs:m.number().int().nonnegative(),networkIdleReached:m.boolean()}).optional(),consoleErrors:m.array(m.string()).optional(),pageErrors:m.array(m.string()).optional(),failedRequests:m.array(m.object({url:m.string(),resourceType:m.string(),error:m.string()})).optional(),securityObservations:m.array(m.object({kind:m.enum([`content-security-policy`,`cors`,`mixed-content`]),message:m.string()})).optional(),error:m.string().optional()}).optional()}).optional(),blocked:m.boolean().optional(),contentAuditAllowed:m.boolean().optional(),crawlDepth:m.number().int().optional(),error:m.string().optional(),robotsTxt:m.object({url:m.string().url(),allowed:m.boolean().nullable(),availability:m.enum([`available`,`absent`,`access-blocked`,`rate-limited`,`unreachable`]),status:m.number().int().optional(),error:m.string().optional(),matchedLine:m.string().optional()}).optional(),title:m.string().optional(),metaDescription:m.string().optional(),canonical:m.string().url().optional(),canonicalRaw:m.string().optional(),canonicalStatus:m.enum([`missing`,`single`,`duplicate`,`conflicting`,`outside-head-only`,`invalid`]).optional(),canonicalCandidates:m.array(m.object({source:m.enum([`html-head`,`html-body`,`http-header`]),raw:m.string(),resolved:m.string().url().optional(),ignoredReason:m.enum([`outside-head`,`alternate-qualifier`,`fragment`,`invalid-url`,`non-http-url`]).optional()})).optional(),metaRobots:m.string().optional(),xRobotsTag:m.string().optional(),h1:m.string().optional(),h1Count:m.number().int().optional(),h2Count:m.number().int().optional(),h3Count:m.number().int().optional(),indexable:m.boolean(),indexability:m.string().optional(),declaredIndexability:m.enum([`indexable-candidate`,`noindex`,`robots-blocked`,`canonical-conflict`,`canonical-hint-other`,`not-html`,`unknown`]).optional(),extractionStatus:m.enum([`complete`,`failed`,`not-applicable`,`unknown-media-type`]).optional(),extractionError:m.string().optional(),wordCount:m.number().int(),contentExtraction:m.object({requested:m.enum([`defuddle`,`readability`]),used:m.enum([`defuddle`,`readability`]),fallback:m.boolean(),fallbackReason:m.enum([`defuddle_error`,`defuddle_empty`]).optional(),fallbackDetail:m.string().optional(),wordCountSource:m.enum([`defuddle`,`local_cjk_aware`]),baseUrl:m.string().url(),extractorType:m.string().optional()}).optional(),warnings:m.array(m.string()).optional(),contentHash:m.string(),mainContentHash:m.string().optional(),textRatio:m.number().min(0).max(1).optional(),contentSample:m.string().optional(),lang:m.string().optional(),hasViewport:m.boolean().optional(),isHttps:m.boolean().optional(),hasHsts:m.boolean().optional(),compression:m.string().optional(),hreflang:m.array(m.object({hreflang:m.string(),href:m.string().url()})).optional(),mixedContentCount:m.number().int().optional(),mixedContentSamples:m.array(m.string().url()).optional(),imagesTotal:m.number().int().optional(),imagesMissingAlt:m.number().int().optional(),oversizedImageCandidates:m.array(m.object({src:m.string().url(),width:m.number().int().optional(),height:m.number().int().optional(),detectedFrom:m.string()})).optional(),outgoingInternalCount:m.number().int(),outgoingExternalCount:m.number().int().optional(),internalInlinkCount:m.number().int().optional(),internalLinkAuthorityScore:m.number().int().min(0).max(100).optional(),sampleInternalLinks:m.array(m.string().url()).optional(),sampleExternalLinks:m.array(m.string().url()).optional(),internalAnchorSamples:m.array(m.object({href:m.string().url(),text:m.string()})).optional(),externalAnchorSamples:m.array(m.object({href:m.string().url(),text:m.string()})).optional(),externalLinkChecks:m.array(m.object({url:m.string().url(),status:m.number().int().optional(),error:m.string().optional()})).optional(),schemaTypes:m.array(m.string()).optional(),structuredDataFormats:m.array(m.enum([`json-ld`,`microdata`,`rdfa`])).optional(),googleRichResults:m.array(Fw).optional(),googleRichResultsSelection:m.object({limit:m.number().int().nonnegative(),eligible:m.number().int().nonnegative(),returned:m.number().int().nonnegative(),omitted:m.number().int().nonnegative(),partial:m.boolean(),eligibleByStatus:Pw,returnedByStatus:Pw,omittedByStatus:Pw}).optional(),schemaSameAs:m.array(m.string().url()).optional(),schemaSameAsEvidence:m.array(m.object({url:m.string().url(),block:m.number().int().nonnegative(),path:m.string(),subjectId:m.string().url().optional(),subjectTypes:m.array(m.string())})).optional(),invalidSchemaSameAs:m.array(m.object({block:m.number().int().nonnegative(),path:m.string(),value:m.string()})).optional(),socialProfileLinks:m.array(m.string().url()).optional(),invalidJsonLdCount:m.number().int().optional(),invalidJsonLdSamples:m.array(m.object({snippet:m.string(),error:m.string()})).optional(),unrecognizedJsonLdTypes:m.array(m.object({block:m.number().int().nonnegative(),path:m.string(),value:m.string(),reason:m.enum([`missing-schema-context`,`unresolved-context`,`unsupported-vocabulary`])})).optional(),openGraphTitle:m.string().optional(),openGraphDescription:m.string().optional(),openGraphImage:m.string().optional(),twitterCard:m.string().optional(),author:m.string().optional(),hasDate:m.boolean().optional(),geo:Mw.optional(),searchMetrics:Aw.optional(),topQuery:Aw.extend({query:m.string()}).optional(),analytics:jw.optional()}),Bw=m.object({requestedUrl:m.string().url(),outcome:m.literal(`response`),finalUrl:m.string().url(),status:m.number().int(),contentType:m.string().optional(),durationMs:m.number().optional(),redirectChain:m.array(m.object({url:m.string().url(),status:m.number().int(),location:m.string().url().optional()})).optional()}),Vw=m.union([Bw.extend({extraction:m.enum([`complete`,`not-applicable`,`unknown-media-type`])}),Bw.extend({extraction:m.literal(`failed`),extractionError:m.string()}),m.object({requestedUrl:m.string().url(),outcome:m.literal(`skipped`),durationMs:m.number().optional(),reason:m.enum([`robots-disallowed`,`robots-deferred`]),robotsTxt:m.object({url:m.string().url(),allowed:m.boolean().nullable(),availability:m.enum([`available`,`absent`,`access-blocked`,`rate-limited`,`unreachable`]),status:m.number().int().optional(),error:m.string().optional(),matchedLine:m.string().optional()}),extraction:m.literal(`not-applicable`)}),m.object({requestedUrl:m.string().url(),outcome:m.literal(`skipped`),durationMs:m.number().optional(),reason:m.literal(`origin-backpressure`),error:m.string(),extraction:m.literal(`not-applicable`)}),m.object({requestedUrl:m.string().url(),outcome:m.literal(`failure`),durationMs:m.number().optional(),failureKind:m.enum([`dns`,`tls`,`timeout`,`redirect-limit`,`aborted`,`unknown`]),error:m.string(),extraction:m.literal(`not-applicable`)})]),Hw=m.object({ruleId:Ow,title:m.string(),category:Dw,severity:Tw,url:m.string().url(),detail:m.string().optional(),evidence:m.record(m.string(),m.unknown()).optional(),searchMetrics:Aw.optional()}),Uw=m.object({ruleId:Ow,title:m.string(),category:Dw,severity:Tw,count:m.number().int(),sampleUrls:m.array(m.string().url())}),Ww=m.object({robotsTxt:m.object({url:m.string().url(),exists:m.boolean(),availability:m.enum([`available`,`absent`,`access-blocked`,`rate-limited`,`unreachable`]),status:m.number().int().optional(),error:m.string().optional(),sitemapUrls:m.array(m.string().url()),botAccess:m.array(m.object({userAgent:m.string(),allowed:m.boolean().nullable(),declared:m.boolean(),coveredByWildcard:m.boolean()}))}).optional(),llmsTxt:m.object({url:m.string().url(),exists:m.boolean(),status:m.number().int().optional()}).optional(),agentResources:m.array(m.object({url:m.string().url(),exists:m.boolean(),status:m.number().int().optional(),contentType:m.string().optional(),validJson:m.boolean().optional()})).optional()}),Gw=Uw.extend({recommendation:Ew,score:m.number(),scoreFactors:m.object({severity:m.number(),affectedUrls:m.number().int(),searchVisibleUrls:m.number().int(),clicks:m.number(),impressions:m.number(),sessions:m.number(),totalUsers:m.number(),conversions:m.number(),avgPosition:m.number().optional(),effort:m.enum([`low`,`medium`,`high`]),effortScore:m.number()}),whyThisRanks:m.string(),howToFix:m.string(),howToVerify:m.string(),verification:m.object({command:m.string(),expected:m.string()})}),Kw=m.object({dataStatus:m.enum([`complete`,`partial`,`unavailable`]),urlsReturned:m.number().int().nonnegative(),roots:m.array(m.object({url:m.string().url(),source:m.enum([`robots-txt`,`default-path`]),dataStatus:m.enum([`complete`,`partial`,`unavailable`]),urlsReturned:m.number().int().nonnegative(),sitemapsFetched:m.number().int().nonnegative(),lastmods:m.object({trust:m.literal(`unverified`),observed:m.number().int().nonnegative(),parseable:m.number().int().nonnegative(),malformed:m.object({count:m.number().int().nonnegative(),samples:m.array(m.object({sitemapUrl:m.string().url(),kind:m.enum([`url`,`sitemap`]),loc:m.string(),value:m.string()}))}),future:m.object({count:m.number().int().nonnegative(),samples:m.array(m.object({sitemapUrl:m.string().url(),kind:m.enum([`url`,`sitemap`]),loc:m.string(),value:m.string()}))})}),documents:m.array(m.object({url:m.string().url(),finalUrl:m.string().url().optional(),redirected:m.literal(!0).optional(),dataStatus:m.enum([`complete`,`partial`,`unavailable`]),status:m.number().int().optional(),contentType:m.string().optional(),compression:m.enum([`gzip`,`none`]),bytes:m.number().int().nonnegative().optional(),uncompressedBytes:m.number().int().nonnegative().optional(),root:m.enum([`urlset`,`sitemapindex`]).optional(),warning:m.string().optional()})),possiblyTruncated:m.boolean(),warnings:m.array(m.string())}))}),qw=m.object({dataStatus:m.enum([`complete`,`partial`,`unavailable`]),discoveredLinkOccurrences:m.number().int().nonnegative(),retainedUrls:m.number().int().nonnegative(),selectedUrls:m.number().int().nonnegative(),fetchedUrls:m.number().int().nonnegative(),failedUrls:m.number().int().nonnegative(),deferredUrls:m.number().int().nonnegative(),limit:m.number().int().positive(),warnings:m.array(m.string())}),Jw=m.object({url:m.string().url(),mode:m.enum([`site`,`page`,`list`,`sitemap`]),urls:m.array(m.string().url()),maxPages:m.number().int(),maxDepth:m.number().int(),concurrency:m.number().int(),timeoutMs:m.number().int(),include:m.array(m.string()),exclude:m.array(m.string()),respectRobots:m.boolean(),useSitemap:m.boolean(),checkExternal:m.boolean(),js:m.union([m.enum([`auto`,`on`,`off`]),m.boolean()]),refresh:m.boolean(),fetchRate:m.object({concurrency:m.number().int(),intervalCap:m.number().int().optional(),intervalMs:m.number().int().optional(),backpressure:m.object({slowMs:m.number().int().optional(),verySlowMs:m.number().int().optional(),maxConsecutiveSlow:m.number().int().optional(),maxConsecutiveBlocked:m.number().int().optional(),maxConsecutiveErrors:m.number().int().optional(),cooldownMs:m.number().int().optional(),retryAfterCapMs:m.number().int().optional()}).optional()})}),Yw=m.object({totalPages:m.number().int(),indexablePages:m.number().int(),nonIndexablePages:m.number().int(),statusErrors:m.number().int(),discoveredUrls:m.number().int(),queuedUrls:m.number().int(),crawledUrls:m.number().int(),skippedUrls:m.number().int(),failedUrls:m.number().int(),observedInternalLinks:m.number().int(),pageLimitReached:m.boolean().default(!1),attemptedRequests:m.number().int(),responseRequests:m.number().int(),failedRequests:m.number().int(),abortedRequests:m.number().int(),extractionFailures:m.number().int(),requestByStatus:m.record(m.string(),m.number().int()),avgRequestMs:m.number().int().optional(),highIssues:m.number().int(),mediumIssues:m.number().int(),lowIssues:m.number().int(),avgResponseMs:m.number().int().optional(),byStatus:m.record(m.string(),m.number().int()),byCategory:m.record(m.string(),m.number().int())}),Xw=m.enum([`joined`,`partial`,`none`,`skipped`,`unavailable`]),Zw=m.object({startDate:m.string().date(),endDate:m.string().date(),days:m.number().int().positive()}),Qw=m.object({status:Xw,window:Zw.optional(),totalPages:m.number().int().nonnegative(),queriedPages:m.number().int().nonnegative(),joinedMetricPages:m.number().int().nonnegative(),joinedQueryPages:m.number().int().nonnegative(),pageLimit:m.number().int().positive(),pageLimitReached:m.boolean(),metricRowsReturned:m.number().int().nonnegative().optional(),queryRowsReturned:m.number().int().nonnegative().optional(),retainedRowLimit:m.number().int().positive().optional(),retainedRowLimitReached:m.boolean().optional(),warning:m.string().optional()}),$w=m.object({status:Xw,window:Zw.optional(),totalPages:m.number().int().nonnegative(),queriedPages:m.number().int().nonnegative(),joinedPages:m.number().int().nonnegative(),returnedRows:m.number().int().nonnegative().optional(),availableRows:m.number().int().nonnegative().optional(),retainedRowLimit:m.number().int().positive().optional(),retainedRowLimitReached:m.boolean().optional(),warning:m.string().optional()}),eT=m.object({searchConsole:Qw,analytics:$w}),tT=m.object({id:m.string(),definitionId:m.string(),projectId:m.string().optional(),site:m.string().optional(),ga4PropertyId:m.string().optional(),generatedAt:m.string().datetime(),status:m.enum([`completed`,`partial`,`failed`]),configHash:m.string(),config:Jw,summary:Yw,pages:m.array(zw),issues:m.array(Hw),issueGroups:m.array(Uw),dataSources:eT.optional(),ai:Ww.optional(),sitemapDiscovery:Kw.optional(),externalLinkVerification:qw.optional(),warnings:m.array(m.string()),caveats:m.array(m.string())}),nT=m.discriminatedUnion(`requestEvidenceStatus`,[tT.extend({requestEvidenceStatus:m.literal(`available`),requests:m.array(Vw)}),tT.extend({requestEvidenceStatus:m.literal(`partial`),requests:m.array(Vw)}),tT.extend({requestEvidenceStatus:m.literal(`unavailable`),requests:m.array(Vw).max(0)})]);m.toJSONSchema(nT),m.toJSONSchema(Uw),m.toJSONSchema(Gw),m.toJSONSchema(kw),m.toJSONSchema(Vw),m.toJSONSchema(zw);const rT=1e9;function iT(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 aT(e,t,n,r){return e===void 0||!Number.isFinite(e)?t:Math.min(r,Math.max(n,Math.floor(e)))}function oT(e){return e.normalize(`NFKC`).toLowerCase().replace(/\s+/g,` `).trim()}function sT(e){try{let t=new URL(e);return t.protocol!==`http:`&&t.protocol!==`https:`?void 0:(t.hash=``,t.toString())}catch{return}}function cT(e){return Number.isFinite(e.clicks)&&e.clicks>=0&&Number.isFinite(e.impressions)&&e.impressions>0&&e.clicks<=e.impressions&&Number.isFinite(e.ctr)&&e.ctr>=0&&e.ctr<=1&&Number.isFinite(e.position)&&e.position>0}function lT(e,t){let n=new Map;for(let r of e){let e=r.keys[0]?.trim()??``,i=sT(r.keys[1]?.trim()??``);if(r.keys.length!==2||!e||!i||!cT(r)){t.invalidRows++;continue}t.validRows++;let a=`${oT(e)}\u0000${i}`,o=n.get(a)??{query:e,url:i,clicks:0,impressions:0,weightedPosition:0,sourceRows:0};iT(e,o.query)<0&&(o.query=e),o.clicks+=r.clicks,o.impressions+=r.impressions,o.weightedPosition+=r.position*r.impressions,o.sourceRows++,n.set(a,o)}let r=[...n.values()].map(e=>({keys:[e.query,e.url],clicks:e.clicks,impressions:e.impressions,ctr:e.clicks/e.impressions,position:e.weightedPosition/e.impressions,sourceRows:e.sourceRows}));return t.duplicateRows=r.reduce((e,t)=>e+t.sourceRows-1,0),t.aggregatedRows=r.length,r}function uT(e){return{sourceRows:e,invalidRows:0,validRows:0,duplicateRows:0,aggregatedRows:0,outsidePageOneRows:0,lowActionabilityRows:0,brandRows:0,benchmarkRows:0,belowMinimumRows:0,evaluatedRows:0,eligibleUnderperformers:0,returnedUnderperformers:0,limitedUnderperformers:0}}function dT(e){let t=aT(e.minImpressions,200,1,rT),n=aT(e.limit,25,1,100),r=uT(e.rows.length),i=lT(e.rows,r).filter(t=>{let n=t.keys[0]??``;return t.position<1||t.position>10?(r.outsidePageOneRows++,!1):Rv(n)?(r.lowActionabilityRows++,!1):M_({query:n,siteUrl:e.site,brandTerms:e.brandTerms,includeBrand:e.includeBrand})?(r.brandRows++,!1):!0});r.benchmarkRows=i.length;let a=Ev(i),o=i.filter(e=>e.impressions<t?(r.belowMinimumRows++,!1):!0);r.evaluatedRows=o.length;let s=o.map(e=>{let t=xv(e.position),n=a.forUrl(e),r=n.ctr*e.impressions,i=Math.max(0,r-e.clicks);return{query:e.keys[0]??``,url:e.keys[1]??``,position:e.position,impressions:e.impressions,actualCtr:e.ctr,expectedCtr:n.ctr,clicks:e.clicks,expectedClicks:r,clickShortfall:i,benchmark:{expectedCtr:n.ctr,source:n.source,peerRows:n.rows,peerImpressions:n.impressions,qualifiedPeerImpressions:n.qualifiedImpressions,urlSamples:n.urlSamples,positiveUrlSamples:n.positiveUrlSamples},recommendation:{principle:`C.3`,evidenceRef:`Query "${e.keys[0]}" has observed CTR ${e.ctr.toFixed(3)} versus a heuristic ${n.ctr.toFixed(3)} benchmark at average position ${t}, producing a calculated ${i.toFixed(0)}-click shortfall for this window.`,action:`Review the live SERP, query intent, displayed snippet, title, and meta description for "${e.keys[0]}". Test SERP copy only when that review finds a mismatch; the CTR gap alone does not identify the cause.`,effort:`S`,confidence:`low`}}}).filter(e=>e.actualCtr<e.expectedCtr*.6).sort((e,t)=>t.clickShortfall-e.clickShortfall||iT(e.query,t.query)||iT(e.url,t.url)),c=s.slice(0,n);return r.eligibleUnderperformers=s.length,r.returnedUnderperformers=c.length,r.limitedUnderperformers=s.length-c.length,{items:c,totalClickShortfall:s.reduce((e,t)=>e+t.clickShortfall,0),returnedClickShortfall:c.reduce((e,t)=>e+t.clickShortfall,0),minImpressions:t,limit:n,selection:r}}const fT=1e5;function pT(e,t,n=`${t}s`){return e===1?t:n}const mT={searchAnalytics:e_,now:()=>new Date};async function hT(e,t=mT){let n=Y({value:e.minImpressions,fallback:200,minimum:1,maximum:rT,label:`minImpressions`}),r=Y({value:e.limit,fallback:25,minimum:1,maximum:100,label:`limit`}),i=t.now(),a=H_(28,i),o=await t.searchAnalytics(e.site,{...a,dimensions:[`query`,`page`],type:`web`,dataState:`final`,maxRows:fT},{refresh:e.refresh}),{items:s,totalClickShortfall:c,returnedClickShortfall:l,selection:u}=dT({rows:o.rows,site:e.site,minImpressions:n,limit:r,brandTerms:e.brandTerms,includeBrand:e.includeBrand}),d=s[0],f=o.rowsFetched>=fT,p=u.invalidRows>0,m=u.sourceRows===0?`empty`:f||p?`partial`:u.eligibleUnderperformers===0?`filtered`:`complete`,h=f&&p?`partial-and-possibly-truncated`:f?`possibly-truncated`:p?`partial`:`complete`;return{schemaVersion:1,site:e.site,range:a,generatedAt:i.toISOString(),dataStatus:m,source:{provider:`google-search-console`,dimensions:[`query`,`page`],searchType:`web`,dataState:`final`,rowsFetched:o.rowsFetched,calls:o.calls,maxRows:fT,possiblyTruncated:f,completeness:h,validation:{retainedRows:u.validRows,invalidRows:u.invalidRows,aggregatedRows:u.aggregatedRows,duplicateRows:u.duplicateRows}},selection:u,summary:{underperformers:u.eligibleUnderperformers,returnedUnderperformers:u.returnedUnderperformers,estimatedClickShortfall:c,returnedClickShortfall:l,minImpressions:n,limit:r,brandFiltering:e.includeBrand?`included`:`excluded`,verdict:d?`${u.eligibleUnderperformers} CTR ${pT(u.eligibleUnderperformers,`underperformer`)} found in retained rows, with a calculated heuristic shortfall of ${c.toFixed(0)} clicks for this window. Start review with "${d.query}" because it has the largest gap.`:m===`partial`?`No material CTR underperformers remained in the validated retained rows, but partial evidence prevents an all-clear.`:`No high-impression page-one queries in the retained rows are materially below the heuristic CTR benchmark.`},items:s,caveats:[`Date window: ${a.startDate} to ${a.endDate} (28 days), using final GSC data where available.`,`Brand queries: ${e.includeBrand?`included`:`excluded`}.`,`Only queries ranking position 1-10 with at least ${n} impressions were checked.`,`Expected CTR uses a robust site-aware position benchmark when enough peer data exists, otherwise the fallback position curve.`,`CTR benchmarks and calculated click shortfalls are directional heuristics, not forecasts or proof that clicks are available. Validate the search intent and live SERP before editing.`,...u.invalidRows?[`${u.invalidRows} invalid provider ${pT(u.invalidRows,`row`)} ${u.invalidRows===1?`was`:`were`} excluded, so this report is partial.`]:[],...u.duplicateRows?[`${u.duplicateRows} repeated query/page ${pT(u.duplicateRows,`row`)} ${u.duplicateRows===1?`was`:`were`} aggregated before CTR and average position were calculated.`]:[],...f?[`The retained-row limit of ${fT} was reached, so lower-ranked source rows may be absent.`]:[],...u.limitedUnderperformers?[`${u.limitedUnderperformers} eligible ${pT(u.limitedUnderperformers,`underperformer`)} ${u.limitedUnderperformers===1?`was`:`were`} omitted by the output limit.`]:[]],recommendations:d?[`Review the live SERP and displayed snippet for "${d.query}" first. Test the title or meta description only if the observed framing does not match the query intent.`,`Prioritise rows with high impressions and clear intent, but treat the benchmark gap as a hypothesis rather than a diagnosed copy defect.`,`After changing SERP copy, annotate the change and compare the next full 28-day period before making more edits.`]:m===`partial`?[`No CTR-only action is recommended from the validated retained rows. Refresh or inspect the provider evidence before treating this as an all-clear.`]:[`No CTR-only action is recommended from this report. Use striking-distance or page-opportunities if you want more growth ideas.`]}}function gT(e,t,n=`${t}s`){return e===1?t:n}function Z(e,t,n=`${t}s`){return`${e.toLocaleString(`en-GB`)} ${gT(e,t,n)}`}function _T(e){let t=e.items[0];if(!t)return`No segment movement found.`;let n=t.clickDelta<0?`lost`:`gained`;return`${t.key} ${n} ${Math.abs(t.clickDelta)} clicks.`}function vT(e){let t=[];e.anomaly.anomalies.some(e=>e.significant)&&e.update.overlappingUpdates.length&&t.push({label:`Review movement during update overlap`,reason:`${Z(e.update.overlappingUpdates.length,`official update window`)} ${e.update.overlappingUpdates.length===1?`overlaps`:`overlap`} the significant movement comparison window.`,action:`Do not assume the update caused the movement. First compare winning and losing templates, then test site changes, indexability, intent, and SERP changes as separate explanations.`,confidence:`low`});let n=e.page.items[0];if(n&&t.push({label:`Investigate largest page movement`,reason:_T(e.page),action:`Open the page and check the queries that moved, whether the URL is still canonical/indexable, and whether the title, H1, or body changed recently.`,confidence:e.page.dataStatus===`partial`?`low`:Math.abs(n.clickDelta)>50?`high`:`medium`}),e.decay.selection.eligibleRows){let n=e.decay.groups[0];t.push({label:`Refresh decaying content`,reason:n?`${e.decay.selection.eligibleRows} observed retained query/page declines found; ${n.label} declined by ${n.totalClickLoss.toFixed(0)} clicks.`:`${e.decay.selection.eligibleRows} observed retained query/page declines found.`,action:n?n.recommendation:`Start with declines that continued outside the update window. Check indexability first, then ranking and CTR causes.`,confidence:e.decay.dataStatus===`partial`?`low`:`medium`})}e.cannibal.items.length&&t.push({label:`Review multi-URL query candidates`,reason:`${e.cannibal.selection.eligibleClusters} multi-URL query candidates found.`,action:`Confirm whether each URL set satisfies the same intent and inspect technical state. Consolidate only verified duplicate or same-intent pages; otherwise clarify the distinction.`,confidence:`low`});let r=e.quickWins.items[0];if(r){let n=e.quickWins.source.possiblyTruncated||e.quickWins.verification.requested&&e.quickWins.verification.failed>0;t.push({label:`Review CTR-target opportunity`,reason:`${Z(e.quickWins.items.length,`returned query/page candidate`)} fell below ${e.quickWins.items.length===1?`its`:`their`} deterministic, versioned heuristic CTR target.`,action:r.recommendation.action,confidence:n?`low`:r.recommendation.confidence})}if(e.striking.items.length){let n=e.striking.items[0];t.push({label:`Investigate striking-distance candidates`,reason:`${e.striking.items.length} query/page rows have an average GSC position above 10 and at most 20.`,action:n?.recommendation.action??`Check technical state, query coverage, competing URLs, and relevant internal links before choosing a change.`,confidence:e.striking.source.possiblyTruncated?`low`:n?.recommendation.confidence??`low`})}return t.slice(0,6)}function yT(e){let t=new Set(e.skippedSections?.map(e=>e.section)),n=[`page`,`query`,`device`,`country`].flatMap(n=>{let r=e.segments[n];return r.dataStatus===`complete`||t.has(`${n} movement segments`)?[]:[{section:`${n} movement segments`,reason:`${r.summary.verdict} Inspect its retained-row source evidence and warnings.`}]});return[...e.anomaly.coverage?.status===`partial`?[{section:`traffic anomaly`,reason:e.anomaly.coverage.caveats.join(` `)||`The requested Search Console calendar window has incomplete daily evidence.`}]:[],...n,...e.decay.dataStatus===`partial`?[{section:`decay analysis`,reason:`The decay comparison used incomplete source evidence; inspect its source completeness, warnings, and caveats.`}]:[],...e.cannibalization.dataStatus===`partial`?[{section:`cannibalisation analysis`,reason:`The multi-URL query analysis used incomplete source evidence; inspect its source completeness and caveats.`}]:[],...e.strikingDistance.source.possiblyTruncated?[{section:`striking-distance opportunities`,reason:`The retained Search Console response reached its row cap, so additional candidates may exist.`}]:[],...e.strikingDistance.verification.requested&&e.strikingDistance.verification.failed>0?[{section:`striking-distance content verification`,reason:`${e.strikingDistance.verification.failed} of ${e.strikingDistance.verification.attempted} attempted candidate verifications failed, so page evidence is incomplete.`}]:[],...e.quickWins.source.possiblyTruncated?[{section:`quick-win opportunities`,reason:`The retained Search Console response reached its row cap, so benchmarks and candidates may be incomplete.`}]:[],...e.quickWins.verification.requested&&e.quickWins.verification.failed>0?[{section:`quick-win content verification`,reason:`${e.quickWins.verification.failed} of ${e.quickWins.verification.attemptedRows} attempted candidate verifications failed, so page evidence is incomplete.`}]:[]]}function bT(e){return e.criticalStatuses.length>0&&e.criticalStatuses.every(e=>e===`skipped`)?`unavailable`:e.skippedSections||e.partialReasons?`partial`:`complete`}function xT(e){return Number(e.toFixed(3))}function ST(e,t){return e<t?-1:+(e>t)}function CT(e){return Number.isFinite(e)&&e>=0}function wT(e){return Number.isInteger(e)&&e>=0}function TT(e){let t=new Map,n=new Set,r=0,i=0,a=0;for(let o of e){let e=o.keys[0];if(o.keys.length!==1||!e?.trim()||!wT(o.clicks)||!wT(o.impressions)||!CT(o.position)||o.impressions>0&&o.position<=0){r+=1;continue}let s={key:e,clicks:o.clicks,impressions:o.impressions,position:o.impressions>0?xT(o.position):null},c=t.get(e);if(c){c.clicks===s.clicks&&c.impressions===s.impressions&&c.position===s.position?i+=1:(a+=1,t.delete(e),n.add(e));continue}n.has(e)||t.set(e,s)}return{rows:t,invalidRows:r,duplicateRows:i,conflictingRows:a}}function ET(e,t){let n=e.position===null||t.position===null?null:xT(t.position-e.position);return{key:e.key,evidenceScope:`matched-retained-segment`,beforeClicks:e.clicks,afterClicks:t.clicks,clickDelta:xT(t.clicks-e.clicks),beforeImpressions:e.impressions,afterImpressions:t.impressions,impressionDelta:xT(t.impressions-e.impressions),beforePosition:e.position,afterPosition:t.position,positionDelta:n}}function DT(e,t){return{...e,retainedIn:t,reason:`not-retained-in-other-window`}}function OT(e){let t=TT(e.beforeRows),n=TT(e.afterRows),r=[],i=[];for(let[e,a]of t.rows){let t=n.rows.get(e);t?r.push(ET(a,t)):i.push(DT(a,`before`))}for(let[e,r]of n.rows)t.rows.has(e)||i.push(DT(r,`after`));return r.sort((e,t)=>Math.abs(t.clickDelta)-Math.abs(e.clickDelta)||ST(e.key,t.key)),i.sort((e,t)=>t.clicks-e.clicks||ST(e.retainedIn,t.retainedIn)||ST(e.key,t.key)),{before:t,after:n,matched:r,unmatched:i}}function kT(e){if(e.status===`empty`)return`Search Console returned no retained segment rows for either window.`;if(!e.matchedRows)return`No segment was retained in both windows, so no movement delta was inferred.`;let t=e.unmatchedRows?` ${e.unmatchedRows} one-window rows were kept as unmatched evidence and not converted to zero.`:``;return`${e.returnedRows} of ${e.matchedRows} matched retained segments returned.${t}`}function AT(e){if(!Number.isInteger(e.beforeDays)||!Number.isInteger(e.afterDays)||e.beforeDays<1||e.afterDays<1)throw new A(`INVALID_INPUT`,`Segment ranges must contain days.`);if(e.beforeDays!==e.afterDays)throw new A(`INVALID_INPUT`,`Segment impact requires adjacent equal-length comparison windows.`);let{before:t,after:n,matched:r,unmatched:i}=OT(e),a=e.beforeRowsFetched??e.beforeRows.length,o=e.afterRowsFetched??e.afterRows.length,s=a>=e.maxRows,c=o>=e.maxRows,l=i.filter(e=>e.retainedIn===`before`).length,u=i.length-l,d={beforeSourceRows:e.beforeRows.length,afterSourceRows:e.afterRows.length,beforeInvalidRows:t.invalidRows,afterInvalidRows:n.invalidRows,beforeDuplicateRows:t.duplicateRows,afterDuplicateRows:n.duplicateRows,beforeConflictingRows:t.conflictingRows,afterConflictingRows:n.conflictingRows,matchedRows:r.length,unmatchedBeforeRows:l,unmatchedAfterRows:u,returnedRows:Math.min(r.length,e.limit),limitedRows:Math.max(0,r.length-e.limit),returnedUnmatchedRows:Math.min(i.length,e.unmatchedLimit),limitedUnmatchedRows:Math.max(0,i.length-e.unmatchedLimit)},f=s||c||t.invalidRows>0||n.invalidRows>0||t.duplicateRows>0||n.duplicateRows>0||t.conflictingRows>0||n.conflictingRows>0||i.length>0||a===0||o===0,p=a===0&&o===0?`empty`:a===0||o===0||r.length===0?`unavailable`:f?`partial`:`complete`,m=[...s||c?[`At least one GSC window reached the ${e.maxRows.toLocaleString(`en-GB`)}-row safety cap. Results may be truncated.`]:[],...i.length?[`${i.length} segments appeared in only one retained response. They are not treated as zero traffic or ranked as winners or losers.`]:[],...t.invalidRows||n.invalidRows?[`${t.invalidRows} before and ${n.invalidRows} after rows had invalid dimensions or metrics and were excluded.`]:[],...t.duplicateRows||n.duplicateRows?[`${t.duplicateRows} before and ${n.duplicateRows} after identical duplicate segment rows were deduplicated.`]:[],...t.conflictingRows||n.conflictingRows?[`${t.conflictingRows} before and ${n.conflictingRows} after conflicting duplicate segment rows were excluded.`]:[]];return{schemaVersion:2,site:e.site,dimension:e.dimension,before:e.before,after:e.after,rangeDays:{before:e.beforeDays,after:e.afterDays},generatedAt:e.generatedAt,dataStatus:p,source:{provider:`google-search-console`,dimension:e.dimension,aggregationType:`auto`,searchType:`web`,dataState:`final`,before:{rowsFetched:a,calls:e.beforeCalls??0,maxRows:e.maxRows,possiblyTruncated:s},after:{rowsFetched:o,calls:e.afterCalls??0,maxRows:e.maxRows,possiblyTruncated:c},completeness:s||c?`possibly-truncated`:`retained-rows-only`},methodology:{id:`gsc_matched_retained_segment_impact`,version:2,missingRowsTreatedAsZero:!1,equalLengthAdjacentWindows:!0,clickDeltaBasis:`after-minus-before`,positionDeltaDefinition:`after-minus-before-positive-is-worse`,causeLanguage:`signals-not-attribution`},filters:{limit:e.limit,unmatchedLimit:e.unmatchedLimit,maxRows:e.maxRows},selection:d,summary:{matchedRows:r.length,returnedRows:d.returnedRows,unmatchedRows:i.length,verdict:kT({status:p,matchedRows:r.length,returnedRows:d.returnedRows,unmatchedRows:i.length})},items:r.slice(0,e.limit),unmatchedSegments:i.slice(0,e.unmatchedLimit),warnings:m,caveats:[`Only segments retained in both GSC responses receive movement deltas. Search Console can omit anonymized and lower-volume rows, so one-window absence is not proof of zero traffic.`,`Average position is impression-weighted provider data and is only compared when both retained rows have impressions.`,`Search Console segment data is observational. Movement can guide investigation but does not establish the cause of a ranking, demand, or CTR change.`]}}function jT(e,t){let n=new Date(`${e}T00:00:00.000Z`);return n.setUTCDate(n.getUTCDate()-t),n.toISOString().slice(0,10)}function MT(e){return Math.floor((Date.parse(`${e.endDate}T00:00:00.000Z`)-Date.parse(`${e.startDate}T00:00:00.000Z`))/864e5)+1}function NT(e,t){let n=jT(e.startDate,1);return{startDate:jT(n,t-1),endDate:n}}function PT(e){let t=Y({value:e.days,fallback:28,minimum:1,maximum:240,label:`days`}),n=K_(e,240)?.range??G_(t,e.now),r=Y({value:e.compareDays,fallback:MT(n),minimum:1,maximum:240,label:`compareDays`}),i=MT(n);if(r!==i)throw new A(`INVALID_INPUT`,`compareDays must equal the ${i}-day current window. Segment impact requires adjacent equal-length periods.`);return{before:NT(n,r),after:n}}function FT(e){if(!e.trim())throw new A(`INVALID_INPUT`,`site must not be empty.`)}function IT(e){let t=e.value??e.fallback;if(!Number.isFinite(t)||t<e.minimum||t>e.maximum)throw new A(`INVALID_INPUT`,`${e.label} must be between ${e.minimum} and ${e.maximum}.`);return t}function LT(e){return Math.floor((new Date(`${e.endDate}T00:00:00.000Z`).getTime()-new Date(`${e.startDate}T00:00:00.000Z`).getTime())/864e5)+1}function RT(e){return e.toISOString().slice(0,10)}function zT(e,t){let n=new Date(`${e}T00:00:00.000Z`);return n.setUTCDate(n.getUTCDate()-t),RT(n)}function BT(e){let t=new Date(`${e}T00:00:00.000Z`),n=t.getUTCFullYear()-1,r=t.getUTCMonth(),i=t.getUTCDate(),a=new Date(Date.UTC(n,r,i));return a.getUTCMonth()===r?RT(a):RT(new Date(Date.UTC(n,r+1,0)))}function VT(e,t){let n=e.getUTCDate(),r=new Date(Date.UTC(e.getUTCFullYear(),e.getUTCMonth()-t,1)),i=new Date(Date.UTC(r.getUTCFullYear(),r.getUTCMonth()+1,0)).getUTCDate();return r.setUTCDate(Math.min(n,i)),RT(r)}function HT(e){let t=G_(1,e.now).endDate;if(e.current.endDate>t)throw new A(`INVALID_INPUT`,`Decay end date must be ${t} or earlier so both GSC windows use final data.`);let n=new Date(`${t}T00:00:00.000Z`);n.setUTCDate(n.getUTCDate()+4);let r=VT(n,16);if(e.previous.startDate<r)throw new A(`INVALID_INPUT`,`Comparison range starts before Search Console's rolling 16-month API history (${r}). Choose a shorter range or use previously stored data.`)}function UT(e,t=`previous-period`){let n=LT(e);if(t===`year-over-year`){let t=BT(e.endDate);return{startDate:zT(t,n-1),endDate:t}}let r=zT(e.startDate,1);return{startDate:zT(r,n-1),endDate:r}}function WT(e){let t=MT(e.after),n=NT(e.after,t);return{schemaVersion:2,site:e.site,dimension:e.dimension,before:n,after:e.after,rangeDays:{before:t,after:t},generatedAt:e.generatedAt??new Date().toISOString(),dataStatus:`unavailable`,source:{provider:`google-search-console`,dimension:e.dimension,aggregationType:`auto`,searchType:`web`,dataState:`final`,before:{rowsFetched:0,calls:0,maxRows:1e5,possiblyTruncated:!1},after:{rowsFetched:0,calls:0,maxRows:1e5,possiblyTruncated:!1},completeness:`unavailable`},methodology:{id:`gsc_matched_retained_segment_impact`,version:2,missingRowsTreatedAsZero:!1,equalLengthAdjacentWindows:!0,clickDeltaBasis:`after-minus-before`,positionDeltaDefinition:`after-minus-before-positive-is-worse`,causeLanguage:`signals-not-attribution`},filters:{limit:0,unmatchedLimit:0,maxRows:1e5},selection:{beforeSourceRows:0,afterSourceRows:0,beforeInvalidRows:0,afterInvalidRows:0,beforeDuplicateRows:0,afterDuplicateRows:0,beforeConflictingRows:0,afterConflictingRows:0,matchedRows:0,unmatchedBeforeRows:0,unmatchedAfterRows:0,returnedRows:0,limitedRows:0,returnedUnmatchedRows:0,limitedUnmatchedRows:0},summary:{matchedRows:0,returnedRows:0,unmatchedRows:0,verdict:`Segment movement could not be compared for this run.`},items:[],unmatchedSegments:[],warnings:[e.reason],caveats:[`No segment movement conclusion was produced because the provider evidence was unavailable.`]}}const GT={searchAnalytics:e_,now:()=>new Date};async function KT(e,t=GT){FT(e.site);let n=t.now(),r=PT({...e,now:n});HT({current:r.after,previous:r.before,now:n});let i=Y({value:e.limit,fallback:25,minimum:1,maximum:100,label:`limit`}),a=Y({value:e.unmatchedLimit,fallback:25,minimum:0,maximum:100,label:`unmatchedLimit`}),o=Y({value:e.maxRows,fallback:1e5,minimum:1,maximum:25e4,label:`maxRows`}),s=e.dimension??`page`,c={dimensions:[s],type:`web`,dataState:`final`,aggregationType:`auto`,maxRows:o},[l,u]=await Promise.all([t.searchAnalytics(e.site,{...r.before,...c},{refresh:e.refresh}),t.searchAnalytics(e.site,{...r.after,...c},{refresh:e.refresh})]);return AT({site:e.site,dimension:s,before:r.before,after:r.after,beforeDays:MT(r.before),afterDays:MT(r.after),beforeRows:l.rows,afterRows:u.rows,beforeRowsFetched:l.rowsFetched,afterRowsFetched:u.rowsFetched,beforeCalls:l.calls,afterCalls:u.calls,limit:i,unmatchedLimit:a,maxRows:o,generatedAt:n.toISOString()})}var qT=class{entries=new Map;gscCalls=0;gscRows=0;addUsage(e){let t=this.entries.get(e.provider);if(!t){this.entries.set(e.provider,{...e});return}t.units+=e.units,t.calls+=e.calls,t.cacheHits=(t.cacheHits??0)+(e.cacheHits??0),t.estimatedUsd=(t.estimatedUsd??0)+(e.estimatedUsd??0)}addGsc(e,t){this.gscCalls+=e,this.gscRows+=t}summary(){let e=[...this.entries.values()].map(e=>{let t=e.estimatedUsd?` (~$${e.estimatedUsd.toFixed(2)})`:``,n=e.cacheHits?` · Cache hits: ${e.cacheHits}`:``;return`${e.provider}: ${e.calls} calls, ${e.units} ${e.unitLabel}${t}${n}`});return e.push(`GSC: ${this.gscCalls} calls, ${this.gscRows} rows`),e.join(` · `)}};const JT=[`near me`,`today`,`tonight`,`calendar`,`schedule`,`time`,`times`,`high tide`,`low tide`,`tide chart`,`tide times`,`salary in`,`average salary`];function YT(e){try{return new URL(e)}catch{return}}function XT(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,` `).replace(/\s+/g,` `).trim()}function ZT(e){return(YT(e)?.pathname??``).split(`/`).map(e=>e.trim()).filter(Boolean)}function QT(e){let t=ZT(e),n=`/${t.join(`/`)}/`;if(n.includes(`/last-names/`)&&n.endsWith(`-surname-popularity/`))return{id:`surname-entity`,label:`Surname entity page`,confidence:`high`};if(n.includes(`/last-names/`))return{id:`last-name-list`,label:`Last-name list page`,confidence:`high`};if(n.includes(`/first-names/`)&&n.endsWith(`-meaning-and-history/`))return{id:`first-name-entity`,label:`First-name entity page`,confidence:`high`};if(n.includes(`/first-names/`))return{id:`first-name-list`,label:`First-name list page`,confidence:`high`};if(/\/average-[a-z0-9-]+-salary-in-[a-z0-9-]+\//.test(n)){let e=t.length>=2?`city`:`country`;return{id:`${e}-salary`,label:`${e===`city`?`City`:`Country`} salary page`,confidence:`high`}}return n.includes(`/salary-calculator/`)?{id:`salary-calculator`,label:`Salary calculator page`,confidence:`high`}:t.length>=3&&/\b(tide|tides|harbour|harbor|marina|beach)\b/.test(XT(n))?{id:`location-schedule`,label:`Location schedule page`,confidence:`medium`}:n.startsWith(`/tools/`)?{id:`tool-page`,label:`Tool page`,confidence:`medium`}:n.startsWith(`/alternatives/`)?{id:`alternative-page`,label:`Alternative page`,confidence:`medium`}:n.startsWith(`/compare/`)?{id:`comparison-page`,label:`Comparison page`,confidence:`medium`}:{id:`other`,label:`Other page`,confidence:`low`}}function $T(e,t=5){let n=new Map;for(let t of e){let e=t.template??QT(t.url??``),r=n.get(e.id)??{id:e.id,label:e.label,count:0,sampleUrls:[]};r.count+=1,t.url&&r.sampleUrls.length<3&&r.sampleUrls.push(t.url),n.set(e.id,r)}return[...n.values()].sort((e,t)=>t.count-e.count).slice(0,t)}function eE(e){return/["“”]/.test(e)}function tE(e){let t=XT(e);return JT.some(e=>t.includes(e))}function nE(e){let t=new Map;for(let n of e){let e=QT(n.keys[1]??``),r=t.get(e.id)??{template:e,count:0};r.count+=1,t.set(e.id,r)}let n=[...t.values()].sort((e,t)=>t.count-e.count)[0];return n?{template:n.template,share:n.count/e.length}:{template:QT(``),share:0}}function rE(e){return e.id.endsWith(`-salary`)?`salary-page`:e.id}function iE(e){return e.id.endsWith(`-salary`)?`Salary page`:e.label}function aE(e){let t=new Map;for(let n of e){let e=QT(n.keys[1]??``),r=rE(e),i=t.get(r)??{id:r,label:iE(e),count:0,share:0,template:e};i.count+=1,t.set(r,i)}let n=[...t.values()].sort((e,t)=>t.count-e.count)[0];return n?{...n,share:n.count/e.length}:{id:`other`,label:`Other page`,count:0,share:0,template:QT(``)}}function oE(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)return t}return n.length-r.length}function sE(e){return e.normalize(`NFKC`).toLowerCase().replace(/\s+/g,` `).trim()}function cE(e){try{let t=new URL(e);return t.protocol!==`http:`&&t.protocol!==`https:`?void 0:(t.hash=``,t.toString())}catch{return}}function lE(e){return Number.isFinite(e.clicks)&&e.clicks>=0&&Number.isFinite(e.impressions)&&e.impressions>0&&e.clicks<=e.impressions&&Number.isFinite(e.ctr)&&e.ctr>=0&&e.ctr<=1&&Number.isFinite(e.position)&&e.position>0}function uE(e,t,n){return!!(t&&n)&&lE(e)}function dE(e,t){return!!t&&lE(e)}function fE(e,t,n){let r=e.pages.get(t),i=(r?.clicks??0)+n.clicks,a=(r?.impressions??0)+n.impressions,o=(r?.position??0)*(r?.impressions??0)+n.position*n.impressions;e.pages.set(t,{url:t,clicks:i,impressions:a,ctr:i/a,position:o/a})}function pE(e,t){let n=[];eE(e)&&n.push(`quoted-query`);let r=aE(t),i=r.share>=.8&&r.id!==`other`;return i&&n.push(`same-template-family`),i&&tE(e)&&n.push(`local-or-entity-intent`),n}function mE(e){return`Query "${e}" was excluded as branded.`}function hE(e,t){return{keys:[t,e.url],clicks:e.clicks,impressions:e.impressions,ctr:e.ctr,position:e.position}}function gE(e,t){return t.clicks-e.clicks||t.impressions-e.impressions||e.position-t.position||oE(e.url,t.url)}function _E(e){let{query:t,pages:n}=e,r=n.reduce((e,t)=>e+t.clicks,0),i=n.reduce((e,t)=>e+t.impressions,0),a=[...n].sort(gE)[0],o=n.reduce((e,t)=>e+t.impressionShare**2,0),s=Math.max(...n.map(e=>e.impressionShare)),c=1-s,l=e.propertyImpressions??e.pageExposureImpressions,u=nE(n.map(e=>hE(e,t))),d=a?.url??n[0]?.url??``;return{query:t,pages:[...n].sort((e,t)=>t.impressionShare-e.impressionShare||gE(e,t)),pageCount:n.length,materialPageClicks:Number(r.toFixed(3)),materialPageExposureImpressions:Number(i.toFixed(3)),pageExposureImpressions:Number(e.pageExposureImpressions.toFixed(3)),propertyImpressions:e.propertyImpressions,observedPageExposureRatio:e.propertyImpressions&&e.propertyImpressions>0?Number((e.pageExposureImpressions/e.propertyImpressions).toFixed(4)):void 0,additionalUrlExposures:e.propertyImpressions===void 0?void 0:Number(Math.max(0,e.pageExposureImpressions-e.propertyImpressions).toFixed(3)),hhi:Number(o.toFixed(4)),splitScore:Number((1-o).toFixed(4)),largestPageShare:Number(s.toFixed(4)),secondaryExposureShare:Number(c.toFixed(4)),reviewContext:e.context,suggestedOwnerUrl:d,ownerSelection:{method:`clicks_then_impressions_then_position`,confidence:`low`,requiresIntentReview:!0},priority:{method:`demand_impressions_x_secondary_exposure`,score:Number((l*c).toFixed(3)),demandImpressions:Number(l.toFixed(3)),secondaryExposureShare:Number(c.toFixed(4)),heuristic:!0,estimatedClickLift:!1},template:u.share>=.8?u.template:void 0,recommendation:{principle:`C.6`,evidenceRef:`GSC retained ${n.length} material URLs for "${t}" across ${e.pageExposureImpressions.toFixed(0)} page exposures${e.propertyImpressions===void 0?``:` and ${e.propertyImpressions.toFixed(0)} property impressions`}; the largest material URL share was ${(s*100).toFixed(1)}%.`,action:`Compare the intent and technical state of these URLs. If they satisfy the same intent, choose a preferred page and align internal links, canonicals, and on-page focus. If they satisfy different intents, keep them separate and clarify the distinction. Treat ${d} only as the first review candidate based on observed clicks, impressions, and position.`,effort:`M`,confidence:`low`}}}function vE(e,t){return t.priority.score-e.priority.score||t.priority.demandImpressions-e.priority.demandImpressions||oE(e.query,t.query)}function yE(e,t,n,r){return e===void 0||!Number.isFinite(e)?t:Math.min(r,Math.max(n,Math.floor(e)))}function bE(e){let t=yE(e.minImpressions,50,0,1e9),n=yE(e.limit,25,1,100),r={sourceRows:e.rows.length,invalidRows:0,validRows:0,propertySourceRows:e.propertyRows?.length??0,propertyInvalidRows:0,propertyQueryGroups:0,queryGroups:0,lowActionabilityQueries:0,brandQueries:0,belowMinimumQueries:0,singlePageQueries:0,incidentalPages:0,dominantQueries:0,missingPropertyQueries:0,suppressedQueries:0,eligibleClusters:0,returnedClusters:0,limitedClusters:0,returnedSuppressions:0,limitedSuppressions:0},i=new Map,a=new Map;for(let t of e.propertyRows??[]){let e=t.keys[0]?.trim()??``;if(!dE(t,e)){r.propertyInvalidRows++;continue}let n=sE(e);a.set(n,(a.get(n)??0)+t.impressions)}r.propertyQueryGroups=a.size;for(let t of e.rows){let e=t.keys[0]?.trim()??``,n=cE(t.keys[1]?.trim()??``);if(!uE(t,e,n)){r.invalidRows++;continue}let a=sE(e),o=i.get(a)??{query:e,pages:new Map};oE(e,o.query)<0&&(o.query=e),fE(o,n??``,t),i.set(a,o),r.validRows++}r.queryGroups=i.size;let o=[],s=[];for(let[n,c]of i.entries()){let i=[...c.pages.values()].reduce((e,t)=>e+t.impressions,0),l=a.get(n);if(Rv(c.query)){r.lowActionabilityQueries++;continue}if(M_({query:c.query,siteUrl:e.site,brandTerms:e.brandTerms,includeBrand:e.includeBrand})){r.brandQueries++,c.pages.size>=2&&s.push({query:c.query,reason:`brand_query`,urlCount:c.pages.size,evidenceRef:mE(c.query)});continue}if((l??i)<t){r.belowMinimumQueries++;continue}if(c.pages.size<2){r.singlePageQueries++;continue}let u=[...c.pages.values()].map(e=>({...e,clicks:Number(e.clicks.toFixed(3)),impressions:Number(e.impressions.toFixed(3)),ctr:Number(e.ctr.toFixed(4)),position:Number(e.position.toFixed(2)),impressionShare:Number((e.impressions/i).toFixed(4)),template:QT(e.url)})),d=u.filter(e=>e.impressions>=10&&e.impressionShare>=.1);if(r.incidentalPages+=u.length-d.length,d.length<2){r.dominantQueries++;continue}let f=d.reduce((e,t)=>e+t.impressions,0),p=d.map(e=>({...e,impressionShare:Number((e.impressions/f).toFixed(4))}));if(Math.max(...p.map(e=>e.impressionShare))>.8){r.dominantQueries++;continue}let m=p.map(e=>hE(e,c.query));l===void 0&&r.missingPropertyQueries++,o.push(_E({query:c.query,pages:p,pageExposureImpressions:i,propertyImpressions:l,context:pE(c.query,m)}))}let c=o.sort(vE),l=c.slice(0,n),u=s.sort((e,t)=>oE(e.query,t.query)||oE(e.reason,t.reason)).slice(0,100);return r.suppressedQueries=s.length,r.eligibleClusters=c.length,r.returnedClusters=l.length,r.limitedClusters=c.length-l.length,r.returnedSuppressions=u.length,r.limitedSuppressions=s.length-u.length,{filters:{minImpressions:t,limit:n,brand:e.includeBrand?`included`:`excluded`},selection:r,items:l,suppressed:u,suppressionSummary:s.reduce((e,t)=>(e[t.reason]=(e[t.reason]??0)+1,e),{}),templates:$T(l.flatMap(e=>e.pages))}}const xE=1e5,SE={searchAnalytics:e_,now:()=>new Date};function CE(e){if(e.status===`empty`)return`Search Console returned no retained query/page rows for this window.`;if(!e.eligible)return e.status===`partial`?`No material multi-URL query candidates remained in the validated retained rows, but partial evidence prevents an all-clear.`:`No material multi-URL query candidates remained after validation and filtering.`;let t=`${e.returned} of ${e.eligible} multi-URL query candidate${e.eligible===1?``:`s`} returned for intent and technical review.`;return e.status===`partial`?`${t} Provider evidence is partial.`:t}async function wE(e,t=SE){let n=Y({value:e.days,fallback:28,minimum:1,maximum:548,label:`days`}),r=Y({value:e.limit,fallback:25,minimum:1,maximum:100,label:`limit`}),i=Y({value:e.minImpressions,fallback:50,minimum:0,maximum:1e9,label:`minImpressions`}),a=t.now(),o=K_(e,548),s=o?.range??G_(n,a),c=o?.days??n,l={...s,type:`web`,dataState:`final`,maxRows:xE},[u,d]=await Promise.all([t.searchAnalytics(e.site,{...l,dimensions:[`query`,`page`],aggregationType:`auto`},{refresh:e.refresh}),t.searchAnalytics(e.site,{...l,dimensions:[`query`],aggregationType:`byProperty`},{refresh:e.refresh})]),f=new qT;f.addGsc(u.calls,u.rowsFetched),f.addGsc(d.calls,d.rowsFetched);let p=bE({site:e.site,rows:u.rows,propertyRows:d.rows,minImpressions:i,limit:r,brandTerms:e.brandTerms,includeBrand:e.includeBrand}),m=u.rowsFetched>=xE,h=d.rowsFetched>=xE,g=m||h,_=p.selection.invalidRows+p.selection.propertyInvalidRows>0,v=g&&_?`partial-and-possibly-truncated`:g?`possibly-truncated`:_?`partial`:`complete`,y=p.selection.sourceRows===0?`empty`:g||_||p.selection.missingPropertyQueries>0?`partial`:p.selection.eligibleClusters===0?`filtered`:`complete`;return{schemaVersion:1,site:e.site,generatedAt:a.toISOString(),range:s,rangeDays:c,dataStatus:y,source:{provider:`google-search-console`,searchType:`web`,dataState:`final`,pageExposure:{dimensions:[`query`,`page`],aggregationType:`auto`,rowsFetched:u.rowsFetched,validation:{retainedRows:p.selection.validRows,invalidRows:p.selection.invalidRows},calls:u.calls,maxRows:xE,possiblyTruncated:m},propertyDemand:{dimensions:[`query`],aggregationType:`byProperty`,rowsFetched:d.rowsFetched,validation:{retainedRows:p.selection.propertySourceRows-p.selection.propertyInvalidRows,invalidRows:p.selection.propertyInvalidRows},calls:d.calls,maxRows:xE,possiblyTruncated:h},completeness:v},methodology:{id:`gsc_url_overlap_v2`,version:2,minimumPageImpressions:10,minimumPageImpressionShare:.1,maximumDominantPageShare:.8,matching:`normalized_exact_query`,finding:`url-overlap-candidate`,requiresIntentReview:!0},verification:{status:`not-requested`,technicalStateChecked:!1,searchIntentChecked:!1},filters:p.filters,selection:p.selection,summary:{eligibleClusters:p.selection.eligibleClusters,returnedClusters:p.selection.returnedClusters,suppressedQueries:p.selection.suppressedQueries,brandFiltering:e.includeBrand?`included`:`excluded`,verdict:CE({status:y,eligible:p.selection.eligibleClusters,returned:p.selection.returnedClusters})},templates:p.templates,suppressed:p.suppressed,suppressionSummary:p.suppressionSummary,items:p.items,caveats:[`Date window: ${s.startDate} to ${s.endDate} (${c} days), using final GSC data where available.`,`This report finds queries with material exposure across multiple URLs. It does not prove that the URLs satisfy the same intent or that consolidation is correct.`,`Page exposure impressions can count more than one URL for a property impression. Property-level query impressions are reported separately instead of calling page exposure split demand.`,`Search Console retains top rows and omits anonymized queries, so absent rows are not proof of absent traffic.`,..._?[`${p.selection.invalidRows} invalid page-exposure row${p.selection.invalidRows===1?``:`s`} and ${p.selection.propertyInvalidRows} invalid property-demand row${p.selection.propertyInvalidRows===1?``:`s`} were excluded. Findings use validated retained rows only.`]:[],`The suggested owner is only a deterministic review starting point based on clicks, impressions, and position. Verify intent, indexability, redirects, and canonicals before changing URLs.`],recommendations:p.items.length?[`Review the highest-exposure candidates first. Confirm whether URLs serve the same intent and inspect technical state before changing canonicals, redirects, or content.`]:_?[`No URL-overlap action is recommended from the validated retained rows. Inspect or refresh the provider data before treating this result as an all-clear.`]:[`No URL-overlap action is recommended from the retained rows and current filters.`],ledgerSummary:f.summary()}}function TE(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)return t}return n.length-r.length}function EE(e){return e.normalize(`NFKC`).toLowerCase().replace(/\s+/g,` `).trim()}function DE(e){try{let t=new URL(e);return t.protocol!==`http:`&&t.protocol!==`https:`?void 0:(t.hash=``,t.toString())}catch{return}}function OE(e,t,n){return!!(t&&n)&&Number.isFinite(e.clicks)&&e.clicks>=0&&Number.isFinite(e.impressions)&&e.impressions>0&&e.clicks<=e.impressions&&Number.isFinite(e.ctr)&&e.ctr>=0&&e.ctr<=1&&Number.isFinite(e.position)&&e.position>0}function kE(e){let t=new Map,n=0;for(let r of e){let e=r.keys[0]?.trim()??``,i=DE(r.keys[1]?.trim()??``);if(!OE(r,e,i)){n++;continue}let a=`${EE(e)}\u0000${i}`,o=t.get(a),s=(o?.clicks??0)+r.clicks,c=(o?.impressions??0)+r.impressions,l=((o?.position??0)*(o?.impressions??0)+r.position*r.impressions)/c;t.set(a,{keys:[o&&TE(o.keys[0],e)<0?o.keys[0]:e,i??``],clicks:s,impressions:c,ctr:s/c,position:l})}return{rows:[...t.values()],invalid:n}}function AE(e){return{clicks:Number(e.clicks.toFixed(3)),impressions:Number(e.impressions.toFixed(3)),ctr:Number(e.ctr.toFixed(4)),position:Number(e.position.toFixed(2))}}function jE(e){let t=QT(e);if(t.id!==`other`)return t;let n=new URL(e).pathname.split(`/`).filter(Boolean)[0];return n?{id:`path:${n}`,label:`/${n}/ pages`,confidence:`low`}:{id:`path:root`,label:`Homepage`,confidence:`high`}}function ME(e,t){let n=[`click_decline`];return e.position>t.position+1&&n.push(`position_decline`),e.impressions>=t.impressions*.9&&e.ctr<t.ctr*.8&&n.push(`ctr_decline`),e.impressions<t.impressions*.9&&n.push(`impression_decline`),{diagnosis:n.includes(`position_decline`)?`lost_position`:n.includes(`ctr_decline`)?`lost_ctr`:n.includes(`impression_decline`)?`lost_impressions`:`lost_clicks`,signals:n}}function NE(e){let t=e.diagnosis===`lost_position`?`Check indexability, canonical state, competing URLs, SERP intent, content changes, and internal links before refreshing the page.`:e.diagnosis===`lost_ctr`?`Inspect the live SERP and query intent, then review title and meta framing before changing page content.`:e.diagnosis===`lost_impressions`?`Check seasonality, query demand, SERP features, page indexability, and URL shifts before attributing this to content quality.`:`Clicks declined without one strong position, CTR, or impression signal. Segment by device, country, and search appearance before choosing a fix.`;return{principle:e.diagnosis===`lost_ctr`?`C.3`:`C.8`,evidenceRef:`Retained GSC row for "${e.query}" on ${e.url}: ${e.previous.clicks.toFixed(1)} to ${e.current.clicks.toFixed(1)} clicks (${e.dropPct.toFixed(1)}% decline). Signals: ${e.signals.join(`, `)}.`,action:t,effort:e.diagnosis===`lost_ctr`?`S`:`M`,confidence:`medium`}}function PE(e){let t=new Map;for(let n of e){let e=`${n.template.id}:${n.diagnosis}`,r=t.get(e)??{group:{id:e,label:`${n.template.label} - ${n.diagnosis.replaceAll(`_`,` `)}`,diagnosis:n.diagnosis,template:n.template,count:0,urlCount:0,totalClickLoss:0,totalPreviousClicks:0,averageDropPct:0,sampleQueries:[],sampleUrls:[],recommendation:``},urls:new Set},{group:i}=r;i.count++,i.totalClickLoss+=n.clickLoss,i.totalPreviousClicks+=n.previous.clicks,r.urls.add(n.url),!i.sampleQueries.includes(n.query)&&i.sampleQueries.length<5&&i.sampleQueries.push(n.query),!i.sampleUrls.includes(n.url)&&i.sampleUrls.length<3&&i.sampleUrls.push(n.url),t.set(e,r)}return[...t.values()].map(({group:e,urls:t})=>({...e,urlCount:t.size,totalClickLoss:Number(e.totalClickLoss.toFixed(3)),totalPreviousClicks:Number(e.totalPreviousClicks.toFixed(3)),averageDropPct:Number((e.totalClickLoss/e.totalPreviousClicks*100).toFixed(1)),recommendation:`Review the shared ${e.template.label} pattern, but verify technical state, seasonality, and SERP intent before applying one change across ${e.count} retained query/page findings.`})).sort((e,t)=>t.totalClickLoss-e.totalClickLoss||TE(e.id,t.id))}function FE(e,t,n,r){return e===void 0||!Number.isFinite(e)?t:Math.min(r,Math.max(n,e))}function IE(e){let t=FE(e.minDropPct,20,0,100),n=FE(e.minPreviousClicks,2,0,1e9),r=FE(e.minClickLoss,1,0,1e9),i=Math.floor(FE(e.limit,25,1,100)),a=kE(e.currentRows),o=kE(e.previousRows),s={currentSourceRows:e.currentRows.length,previousSourceRows:e.previousRows.length,currentInvalidRows:a.invalid,previousInvalidRows:o.invalid,currentAggregatedRows:a.rows.length,previousAggregatedRows:o.rows.length,lowEvidenceRows:0,lowActionabilityRows:0,brandRows:0,currentRowNotRetained:0,urlShiftRows:0,belowClickLossRows:0,belowDropRows:0,eligibleRows:0,returnedRows:0,limitedRows:0,eligibleGroups:0,returnedGroups:0,limitedGroups:0},c=new Map(a.rows.map(e=>[`${EE(e.keys[0])}\u0000${e.keys[1]}`,e])),l=new Map;for(let e of a.rows){let t=EE(e.keys[0]),n=l.get(t)??new Set;n.add(e.keys[1]),l.set(t,n)}let u=[];for(let i of o.rows){let a=i.keys[0],o=i.keys[1];if(i.clicks<=0||i.clicks<n){s.lowEvidenceRows++;continue}if(Lv(a)){s.lowActionabilityRows++;continue}if(M_({query:a,siteUrl:e.site,brandTerms:e.brandTerms,includeBrand:e.includeBrand})){s.brandRows++;continue}let d=`${EE(a)}\u0000${o}`,f=c.get(d);if(!f){l.get(EE(a))?.size?s.urlShiftRows++:s.currentRowNotRetained++;continue}let p=i.clicks-f.clicks;if(p<=0||p<r){s.belowClickLossRows++;continue}let m=p/i.clicks*100;if(m<t){s.belowDropRows++;continue}let h=ME(f,i),g={query:a,url:o,template:jE(o),clickLoss:Number(p.toFixed(3)),dropPct:Number(m.toFixed(1)),current:AE(f),previous:AE(i),diagnosis:h.diagnosis,signals:h.signals,evidenceScope:`retained-query-page-row`};u.push({...g,recommendation:NE(g)})}let d=u.sort((e,t)=>t.clickLoss-e.clickLoss||t.previous.clicks-e.previous.clicks||TE(e.query,t.query)||TE(e.url,t.url)),f=d.slice(0,i),p=Number(d.reduce((e,t)=>e+t.clickLoss,0).toFixed(3)),m=Number(f.reduce((e,t)=>e+t.clickLoss,0).toFixed(3));s.eligibleRows=d.length,s.returnedRows=f.length,s.limitedRows=d.length-f.length;let h=PE(d),g=h.slice(0,100);return s.eligibleGroups=h.length,s.returnedGroups=g.length,s.limitedGroups=h.length-g.length,{selection:s,totals:{eligibleObservedRetainedQueryClickLoss:p,returnedObservedRetainedQueryClickLoss:m},items:f,groups:g,templates:$T(f)}}const LE=1e5,RE={searchAnalytics:e_,now:()=>new Date};function zE(e){return e.dataStatus===`empty`?`Search Console returned no retained query/page rows for either comparison window.`:e.dataStatus===`partial`&&e.selection.currentAggregatedRows===0?`The current retained-row window is empty, so no query/page losses were inferred.`:e.dataStatus===`partial`&&e.selection.previousAggregatedRows===0?`The comparison retained-row window is empty, so query/page decay could not be measured.`:e.selection.eligibleRows?`${e.selection.returnedRows} of ${e.selection.eligibleRows} observed retained query/page declines returned for review.`:`No observed retained query/page declines matched the report filters.`}async function BE(e,t=RE){if(e.comparison!==void 0&&e.comparison!==`previous-period`&&e.comparison!==`year-over-year`)throw new A(`INVALID_INPUT`,`comparison must be previous-period or year-over-year.`);if(e.brandTerms&&(e.brandTerms.length>20||e.brandTerms.some(e=>!e.trim()||e.trim().length>200)))throw new A(`INVALID_INPUT`,`brandTerms must contain at most 20 non-empty terms of 200 characters or fewer.`);let n=Y({value:e.days,fallback:28,minimum:1,maximum:548,label:`days`}),r=Y({value:e.limit,fallback:25,minimum:1,maximum:100,label:`limit`}),i=IT({value:e.minDropPct,fallback:20,minimum:0,maximum:100,label:`minDropPct`}),a=IT({value:e.minPreviousClicks,fallback:2,minimum:0,maximum:1e9,label:`minPreviousClicks`}),o=IT({value:e.minClickLoss,fallback:1,minimum:0,maximum:1e9,label:`minClickLoss`}),s=t.now(),c=K_(e,548),l=c?.range??G_(n,s),u=e.comparison??`previous-period`,d=UT(l,u);HT({current:l,previous:d,now:s});let f={dimensions:[`query`,`page`],aggregationType:`auto`,type:`web`,dataState:`final`,maxRows:LE},[p,m]=await Promise.all([t.searchAnalytics(e.site,{...f,...l},{refresh:e.refresh}),t.searchAnalytics(e.site,{...f,...d},{refresh:e.refresh})]),h=new qT;h.addGsc(p.calls,p.rowsFetched),h.addGsc(m.calls,m.rowsFetched);let g=IE({site:e.site,currentRows:p.rows,previousRows:m.rows,minDropPct:i,minPreviousClicks:a,minClickLoss:o,limit:r,brandTerms:e.brandTerms,includeBrand:e.includeBrand}),_=p.rowsFetched>=LE,v=m.rowsFetched>=LE,y=_||v,b=p.rowsFetched===0&&m.rowsFetched===0?`empty`:p.rowsFetched===0||m.rowsFetched===0||y||g.selection.currentInvalidRows>0||g.selection.previousInvalidRows>0||g.selection.currentRowNotRetained>0?`partial`:g.selection.eligibleRows===0?`filtered`:`complete`,x={dataStatus:b,selection:g.selection};return{schemaVersion:1,site:e.site,generatedAt:s.toISOString(),comparison:u,ranges:{current:l,previous:d},rangeDays:c?.days??n,dataStatus:b,source:{provider:`google-search-console`,dimensions:[`query`,`page`],aggregationType:`auto`,searchType:`web`,dataState:`final`,current:{rowsFetched:p.rowsFetched,calls:p.calls,maxRows:LE,possiblyTruncated:_},previous:{rowsFetched:m.rowsFetched,calls:m.calls,maxRows:LE,possiblyTruncated:v},completeness:y?`possibly-truncated`:`retained-query-rows-only`},methodology:{id:`gsc_retained_query_page_decay_v2`,version:2,gscHistoryMonths:16,missingRowsTreatedAsZero:!1,urlShiftsExcluded:!0,causeLanguage:`signals-not-attribution`},filters:{minDropPct:i,minPreviousClicks:a,minClickLoss:o,limit:r,brand:e.includeBrand?`included`:`excluded`},selection:g.selection,summary:{eligibleRows:g.selection.eligibleRows,returnedRows:g.selection.returnedRows,groups:g.selection.eligibleGroups,observedRetainedQueryClickLoss:g.totals.eligibleObservedRetainedQueryClickLoss,returnedObservedRetainedQueryClickLoss:g.totals.returnedObservedRetainedQueryClickLoss,brandFiltering:e.includeBrand?`included`:`excluded`,verdict:zE(x)},caveats:[`Current window: ${l.startDate} to ${l.endDate}; comparison: ${d.startDate} to ${d.endDate}.`,`Only declines observed in retained GSC query/page rows are counted. Missing current rows are reported in selection and never converted to zero traffic.`,`Queries retained on a different current URL are counted as URL shifts and excluded from content-decay actions.`,`Search Console API history is limited to a rolling 16 months. Ranges outside that horizon are rejected instead of compared partially.`,`Search Console omits anonymized and lower-volume rows and does not guarantee every row. Signals do not establish cause.`],recommendations:g.groups.length?g.groups.slice(0,5).map(e=>e.recommendation):[`No content-decay action is recommended from the observed retained rows.`],items:g.items,groups:g.groups,templates:g.templates,ledgerSummary:h.summary(),warnings:[...y?[`At least one GSC window reached the 100,000-row safety cap. Results may be truncated.`]:[],...g.selection.currentRowNotRetained?[`${g.selection.currentRowNotRetained} previous query/page rows were not retained in the current response and were excluded rather than treated as zero.`]:[],...g.selection.currentInvalidRows||g.selection.previousInvalidRows?[`${g.selection.currentInvalidRows} current and ${g.selection.previousInvalidRows} comparison rows had invalid dimensions or metrics and were excluded.`]:[],...m.rowsFetched===0&&p.rowsFetched>0?[`The comparison window returned no retained query/page rows, so decay could not be measured.`]:[],...g.selection.urlShiftRows?[`${g.selection.urlShiftRows} previous query/page rows now appear under another URL and were excluded from content-decay actions.`]:[]]}}function VE(e){return e.normalize(`NFKC`).toLocaleLowerCase(`und`).replace(/\s+/gu,` `).trim()}function HE(e){return`${e.template.id}:${VE(e.query)}`}function UE(e,t){return e<t?-1:+(e>t)}function WE(e){return e.technicalUrls>0?`${e.technicalUrls} of ${e.group.urlCount} affected URLs have verified technical evidence. Fix those HTTP, indexability, redirect, or canonical issues before testing shared search-framing changes.`:`${e.group.urlCount} distinct ${e.group.template.label.toLowerCase()} URLs share the query pattern "${e.group.query}". Compare their live search results and verified page evidence before testing a shared title, snippet, or heading change.`}function GE(e){let t=new Map;for(let n of e){let e=HE(n);t.set(e,[...t.get(e)??[],n])}return[...t.entries()].map(([e,t])=>{let n=t[0];if(!n||n.template.confidence===`low`)return;let r=[...new Set(t.map(e=>e.url))];if(r.length<2)return;let i=new Set(t.filter(e=>e.contentVerification?.classification===`technical-check`).map(e=>e.url)).size,a={id:e,label:`${n.template.label} - ${n.query}`,query:n.query,template:n.template,rowCount:t.length,urlCount:r.length,totalEstimatedCtrClickShortfall:Number(t.reduce((e,t)=>e+t.estimatedCtrClickShortfall,0).toFixed(2)),totalImpressions:Number(t.reduce((e,t)=>e+t.impressions,0).toFixed(0)),sampleUrls:r.slice(0,3),recommendation:``};return a.recommendation=WE({group:a,technicalUrls:i}),a}).filter(e=>e!==void 0).sort((e,t)=>t.totalEstimatedCtrClickShortfall-e.totalEstimatedCtrClickShortfall||t.totalImpressions-e.totalImpressions||UE(e.id,t.id))}function KE(e,t,n,r){return e===void 0||!Number.isFinite(e)?t:Math.min(r,Math.max(n,Math.floor(e)))}function qE(e){try{let t=new URL(e);return t.protocol===`http:`||t.protocol===`https:`}catch{return!1}}function JE(e,t,n){return e.keys.length>=2&&!!t&&qE(n)&&Number.isFinite(e.clicks)&&e.clicks>=0&&Number.isFinite(e.impressions)&&e.impressions>0&&e.clicks<=e.impressions&&Number.isFinite(e.ctr)&&e.ctr>=0&&e.ctr<=1&&Number.isFinite(e.position)&&e.position>=1}function YE(e){let t={sourceRows:e.rows.length,invalidRows:0,outsideBenchmarkPositionRows:0,lowActionabilityRows:0,brandRows:0,benchmarkRows:0,outsideCandidatePositionRows:0,belowMinimumRows:0,atOrAboveTargetRows:0,eligibleRows:0,returnedRows:0,limitedRows:0},n=[];for(let r of e.rows){let i=r.keys[0]?.trim()??``,a=r.keys[1]?.trim()??``;JE(r,i,a)?r.position>10?t.outsideBenchmarkPositionRows++:Rv(i)?t.lowActionabilityRows++:M_({query:i,siteUrl:e.site,brandTerms:e.brandTerms,includeBrand:e.includeBrand})?t.brandRows++:(n.push({...r,keys:[i,a]}),t.benchmarkRows++):t.invalidRows++}return{rows:n,selection:t}}function XE(e){return{targetCtr:e.benchmark.ctr,source:e.benchmark.source,samplePopulation:`all_qualified_url_samples`,peerRows:e.benchmark.rows,peerImpressions:e.benchmark.impressions,qualifiedPeerImpressions:e.benchmark.qualifiedImpressions,urlSamples:e.benchmark.urlSamples,positiveUrlSamples:e.benchmark.positiveUrlSamples,excludedTargetRows:e.excludedTargetRows,leaveOut:`target_url`,confidence:e.benchmark.source.startsWith(`site_gsc_`)?`site-data`:`fallback`,heuristic:!0}}function ZE(e){let t=e.row.keys[0]??``,n=e.row.keys[1]??``,r=Number((Math.max(0,e.benchmark.ctr-e.row.ctr)*e.row.impressions).toFixed(2)),i=XE({benchmark:e.benchmark,excludedTargetRows:e.excludedTargetRows});return{query:t,url:n,template:QT(n),position:e.row.position,clicks:e.row.clicks,impressions:e.row.impressions,ctr:e.row.ctr,targetCtr:e.benchmark.ctr,benchmark:i,estimatedCtrClickShortfall:r,priority:{method:`impressions_x_target_ctr_shortfall`,score:r,heuristic:!0,estimatedClickLift:!1},finding:`ctr-target-shortfall`,recommendation:{principle:`C.3`,evidenceRef:`GSC average position is ${e.row.position.toFixed(1)} with ${e.row.impressions} impressions and CTR ${e.row.ctr.toFixed(4)} versus a heuristic target of ${e.benchmark.ctr.toFixed(4)} from ${i.source}.`,action:`Inspect the live search result, query intent, title, snippet, device/country mix, and page evidence before choosing an edit.`,effort:`S`,confidence:`low`,impactEstimate:`${r.toFixed(2)} calculated CTR click shortfall in this window if observed CTR matched the heuristic target; this is not a traffic forecast.`}}}function QE(e,t){return e<t?-1:+(e>t)}function $E(e,t){return t.estimatedCtrClickShortfall-e.estimatedCtrClickShortfall||t.impressions-e.impressions||QE(e.query,t.query)||QE(e.url,t.url)}function eD(e){return Number(e.reduce((e,t)=>e+t,0).toFixed(2))}function tD(e){let t=KE(e.minImpressions,200,0,1e9),n=KE(e.limit,25,1,100),r=YE(e),i=Ev(r.rows,{samplePopulation:`all_qualified_url_samples`,fallbackSource:`builtin_position_ctr_curve_v1`}),a=[];for(let e of r.rows){if(e.position<4){r.selection.outsideCandidatePositionRows++;continue}if(e.impressions<t){r.selection.belowMinimumRows++;continue}let n=e.keys[1]??``,o=r.rows.filter(e=>l_(e.keys[1]??``,n)).length,s=i.forUrl(e);if(e.ctr>=s.ctr){r.selection.atOrAboveTargetRows++;continue}a.push(ZE({row:e,benchmark:s,excludedTargetRows:o})),r.selection.eligibleRows++}a.sort($E);let o=a.slice(0,n);return r.selection.returnedRows=o.length,r.selection.limitedRows=a.length-o.length,{site:e.site,minImpressions:t,limit:n,dataStatus:e.rows.length===0?`empty`:a.length===0?`filtered`:`available`,selection:r.selection,methodology:{id:`gsc_quick_wins_v2`,source:`google_search_console_query_page_rows`,position:{metric:`gsc_average_position`,minimumInclusive:4,maximumInclusive:10},benchmark:{method:`position_bucket_url_p75_v2`,samplePopulation:`all_qualified_url_samples`,leaveOut:`target_url`,minimumUrlImpressions:30,minimumQualifiedImpressions:1e3,minimumUrlSamples:5,minimumPositiveUrlSamples:3,fallback:{id:`seo_builtin_position_ctr`,version:1,kind:`built_in_heuristic`,curve:Object.fromEntries(Object.entries(L_).map(([e,t])=>[e,t]))},heuristic:!0},priority:{method:`impressions_x_target_ctr_shortfall`,formula:`impressions * max(0, target CTR - observed CTR)`,heuristic:!0,estimatedClickLift:!1}},provenance:{inputScope:`provided_rows`,selectionOrder:[`valid_row`,`benchmark_position`,`query_quality`,`brand`,`candidate_position`,`minimum_impressions`,`target_ctr_shortfall`,`limit`],selection:r.selection},summary:{eligibleRows:a.length,returnedRows:o.length,eligibleImpressions:eD(a.map(e=>e.impressions)),returnedImpressions:eD(o.map(e=>e.impressions)),eligibleEstimatedCtrClickShortfall:eD(a.map(e=>e.estimatedCtrClickShortfall)),returnedEstimatedCtrClickShortfall:eD(o.map(e=>e.estimatedCtrClickShortfall)),uniqueEligibleUrls:new Set(a.map(e=>e.url)).size,uniqueEligibleQueries:new Set(a.map(e=>e.query)).size},items:o,eligibleItems:a,benchmarkByPosition:i.byPosition}}function nD(e,t){return e<t?-1:+(e>t)}function rD(e){return[...new Set(e.filter(Boolean))]}function iD(e){let t=new Map;for(let n of e)t.set(n.template.id,[...t.get(n.template.id)??[],n]);return[...t.entries()].map(([e,t])=>{let n=t[0];if(!n||n.template.confidence===`low`)return;let r=[...new Set(t.map(e=>e.url))];if(r.length<2)return;let i=new Set(t.filter(e=>e.contentVerification?.classification===`technical-check`).map(e=>e.url)).size,a=rD(t.map(e=>e.query)).slice(0,3);return{templateId:e,templateLabel:n.template.label,rowCount:t.length,urlCount:r.length,totalEstimatedCtrClickShortfall:Number(t.reduce((e,t)=>e+t.estimatedCtrClickShortfall,0).toFixed(2)),totalImpressions:Number(t.reduce((e,t)=>e+t.impressions,0).toFixed(0)),action:i>0?`Fix verified technical issues on ${i} affected URLs before testing shared template changes.`:`Compare live results and verified page evidence for ${r.length} affected URLs before testing a shared title, snippet, or heading change.`,evidence:`${t.length} eligible rows across ${r.length} distinct URLs share this recognised template. Example queries: ${a.join(`; `)}.`}}).filter(e=>e!==void 0).sort((e,t)=>t.totalEstimatedCtrClickShortfall-e.totalEstimatedCtrClickShortfall||t.totalImpressions-e.totalImpressions||nD(e.templateId,t.templateId))}function aD(e){let t=new Map;for(let n of e){let e=t.get(n.template.id)??{label:n.template.label,rows:0,urls:new Set};e.rows++,e.urls.add(n.url),t.set(n.template.id,e)}return[...t.entries()].map(([e,t])=>({id:e,label:t.label,rowCount:t.rows,urlCount:t.urls.size,sampleUrls:[...t.urls].sort(nD).slice(0,3)})).sort((e,t)=>t.urlCount-e.urlCount||t.rowCount-e.rowCount||nD(e.id,t.id)).slice(0,5)}const oD=1e5,sD={searchAnalytics:e_,fetch:Ag,extract:Th,now:()=>new Date};function cD(e){return[...new Set(e.filter(Boolean))]}function lD(e,t){e.contentVerification=t,e.finding=t.classification,e.recommendation={...e.recommendation,action:gv(t),confidence:t.status===`verified`&&t.classification!==`covered`?`medium`:`low`,evidenceRef:`${e.recommendation.evidenceRef} ${t.summary}`}}async function uD(e,t=sD){let n=K_(e,548),r=n?.days??Y({value:e.days,fallback:28,minimum:1,maximum:548,label:`Days`}),i=Y({value:e.verifyLimit,fallback:5,minimum:0,maximum:100,label:`Verification limit`}),a=t.now().toISOString(),o=n?.range??G_(r,new Date(a)),s=await t.searchAnalytics(e.site,{...o,dimensions:[`query`,`page`],type:`web`,dataState:`final`,maxRows:oD},{refresh:e.refresh}),c=new qT;c.addGsc(s.calls,s.rowsFetched);let l=tD({rows:s.rows,site:e.site,minImpressions:e.minImpressions,limit:e.limit,brandTerms:e.brandTerms,includeBrand:e.includeBrand}),u=e.verifyContent===!0||e.verifyLimit!==void 0,d=u?l.items.slice(0,i):[],f=new Map,p=(e,n)=>{let r=f.get(e);if(r)return r;let i=t.fetch(e,n);return f.set(e,i),i};for(let n of d)lD(n,await bv({query:n.query,url:n.url,js:e.js,refresh:e.refresh,rate:e.rate,verifiedAt:a,fetch:p,extract:t.extract}));let m=GE(l.eligibleItems),h=aD(l.eligibleItems),g=iD(l.eligibleItems),_=d.filter(e=>e.contentVerification?.status===`verified`).length,v=d.filter(e=>e.contentVerification?.status===`failed`).length,y=d.filter(e=>e.contentVerification?.classification===`technical-check`).length,b=cD([...g.slice(0,3).map(e=>e.action),...m.slice(0,3).map(e=>e.recommendation),l.items[0]?.recommendation.action??``]).slice(0,5),x=d.flatMap(e=>{let t=e.contentVerification;return t?[...(t.warnings??[]).map(t=>({stage:`verification`,url:e.url,code:`page-warning`,message:t})),...t.error?[{stage:`verification`,url:e.url,code:`verification-failed`,message:t.error}]:[]]:[]});return{site:e.site,generatedAt:a,range:o,rangeDays:r,source:{provider:`google-search-console`,dimensions:[`query`,`page`],searchType:`web`,dataState:`final`,rowsFetched:s.rowsFetched,calls:s.calls,maxRows:oD,possiblyTruncated:s.rowsFetched>=oD,completeness:`retained-query-rows-only`},dataStatus:l.dataStatus,selection:l.selection,methodology:l.methodology,provenance:{...l.provenance,verification:{optional:!0,population:`returned_rows_in_priority_order`,fetchDeduplication:`exact_url`}},benchmark:{method:l.methodology.benchmark.method,peerRows:l.selection.benchmarkRows,byPosition:l.benchmarkByPosition},verification:u?{requested:!0,limit:i,attemptedRows:d.length,attemptedUrls:f.size,verified:_,technical:y,failed:v}:{requested:!1,attemptedRows:0,attemptedUrls:0,verified:0,technical:0,failed:0},summary:{...l.summary,repeatedQueryGroups:m.length,templatePatterns:g.length,brandFiltering:e.includeBrand?`included`:`excluded`,verdict:l.dataStatus===`empty`?`Search Console returned no retained query/page rows for this date window.`:l.dataStatus===`filtered`?`No retained rows met the quick-win report criteria.`:`${l.summary.eligibleRows} eligible CTR-target rows found; ${l.summary.returnedRows} returned for review.`},caveats:[`Date window: ${o.startDate} to ${o.endDate}, using final GSC data where available.`,`Selection: ${l.selection.sourceRows} source rows, ${l.selection.benchmarkRows} benchmark rows, ${l.selection.eligibleRows} eligible rows, ${l.selection.returnedRows} returned.`,`Position is GSC average position across impressions; a value from 4 to 10 does not mean the URL ranked on page one for every impression.`,`Target CTR is a deterministic site-peer or versioned built-in heuristic. The calculated CTR click shortfall is for prioritisation and is not a traffic forecast.`,`Search Analytics exposes top retained query rows only. Anonymized and lower-value queries can be absent even after pagination is exhausted.`,s.rowsFetched>=oD?`The report reached its ${oD.toLocaleString(`en-US`)}-row safety cap.`:``,u?`Page evidence was checked for ${d.length} returned rows across ${f.size} distinct URLs.`:`Page evidence was not requested; CTR shortfalls do not establish why CTR differs.`].filter(Boolean),recommendations:b.length?b:[l.dataStatus===`empty`?`Choose a longer window only if this property should have Search Console data.`:`Adjust the minimum impressions only if you intentionally want a broader, lower-confidence review.`],templates:h,templateRecommendations:g,groups:m,items:l.items,ledgerSummary:c.summary(),warnings:x}}function dD(e,t){let n=[...e].map(e=>e.codePointAt(0)??0),r=[...t].map(e=>e.codePointAt(0)??0),i=Math.min(n.length,r.length);for(let e=0;e<i;e++){let t=(n[e]??0)-(r[e]??0);if(t!==0)return t}return n.length-r.length}function fD(e){try{let t=new URL(e);return t.protocol===`http:`||t.protocol===`https:`}catch{return!1}}function pD(e,t,n){return!!t&&fD(n)&&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 mD(e){return M_({query:e.query,siteUrl:e.site,brandTerms:e.brandTerms,includeBrand:e.includeBrand})}function hD(e){return{type:`investigate-ranking`,confidence:`low`,evidence:`GSC reports ${e.impressions} impressions at average position ${e.position.toFixed(2)} for "${e.query}" on ${e.url}.`,action:`Inspect indexability, canonical state, current query coverage, competing URLs, and relevant internal links for ${e.url} before choosing a change.`}}function gD(e){let t=e.keys[0]?.trim()??``,n=e.keys[1]?.trim()??``,r=Number(Math.min(1,Math.max(.1,(21-e.position)/10)).toFixed(4));return{query:t,url:n,template:QT(n),clicks:Number(e.clicks.toFixed(3)),impressions:Number(e.impressions.toFixed(3)),ctr:Number(e.ctr.toFixed(4)),position:Number(e.position.toFixed(2)),priority:{method:`impressions_x_position_proximity`,score:Number((e.impressions*r).toFixed(2)),demandImpressions:Number(e.impressions.toFixed(3)),positionProximity:r,heuristic:!0,estimatedClickLift:!1},recommendation:hD({query:t,url:n,impressions:e.impressions,position:e.position})}}function _D(e,t){return t.priority.score-e.priority.score||t.impressions-e.impressions||e.position-t.position||dD(e.query,t.query)||dD(e.url,t.url)}function vD(e){let t=new Map;for(let n of e){let e=t.get(n.template.id)??[];e.push(n),t.set(n.template.id,e)}return[...t.entries()].map(([e,t])=>{let n=t[0];if(!n)return;let r=[...new Set(t.map(e=>e.url))],i=[...new Set(t.map(e=>e.query))],a=t.reduce((e,t)=>e+t.impressions,0),o=n.template.confidence!==`low`&&r.length>=2,s=`${t.length} eligible query/page rows represent ${r.length} unique URLs and ${i.length} unique queries.`;return{id:e,label:n.template.label,template:n.template,rowCount:t.length,uniqueUrls:r.length,uniqueQueries:i.length,totalImpressions:Number(a.toFixed(3)),bestPosition:Math.min(...t.map(e=>e.position)),impressionWeightedPosition:Number((t.reduce((e,t)=>e+t.position*t.impressions,0)/a).toFixed(2)),sampleQueries:i.slice(0,5),sampleUrls:r.slice(0,3),actionScope:o?`shared-template-candidate`:`page-level-review`,recommendation:{type:`investigate-ranking`,confidence:`low`,evidence:s,action:o?`Review the sampled ${n.template.label.toLowerCase()} URLs for a recurring technical, content, or internal-link pattern before changing the shared template.`:`Review these URLs individually; the current evidence does not support a shared-template change.`}}}).filter(e=>e!==void 0).sort((e,t)=>t.totalImpressions-e.totalImpressions||t.uniqueUrls-e.uniqueUrls||dD(e.id,t.id))}function yD(e,t,n,r){return e===void 0||!Number.isFinite(e)?t:Math.min(r,Math.max(n,Math.floor(e)))}function bD(e){let t={sourceRows:e.rows.length,invalidRows:0,outsidePositionRows: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()??``;pD(r,i,r.keys[1]?.trim()??``)?r.position<=10||r.position>20?t.outsidePositionRows++:r.impressions<e.minImpressions?t.belowMinimumRows++:Rv(i)?t.lowActionabilityRows++:mD({query:i,site:e.site,brandTerms:e.brandTerms,includeBrand:e.includeBrand})?t.brandRows++:(n.push(r),t.eligibleRows++):t.invalidRows++}return{rows:n,selection:t}}function xD(e){let t=yD(e.minImpressions,100,0,1e9),n=yD(e.limit,25,1,100),r=bD({...e,minImpressions:t}),i=r.rows.map(gD).sort(_D),a=i.slice(0,n);r.selection.returnedRows=a.length,r.selection.limitedRows=i.length-a.length;let o=vD(i),s=i.reduce((e,t)=>e+t.impressions,0),c=a.reduce((e,t)=>e+t.impressions,0),l=e.rows.length===0?`empty`:i.length===0?`filtered`:`available`;return{site:e.site,minImpressions:t,limit:n,dataStatus:l,selection:r.selection,methodology:{id:`gsc_striking_distance_v2`,source:`google_search_console_query_page_rows`,position:{minimumExclusive:10,maximumInclusive:20},ctrEligibilityFilter:!1,priority:{method:`impressions_x_position_proximity`,formula:`impressions * clamp((21 - position) / 10, 0.1, 1)`,heuristic:!0,estimatedClickLift:!1},grouping:{population:`all_eligible_rows_before_limit`,sharedTemplateMinimumUniqueUrls:2,lowConfidenceTemplatesAreShared:!1}},provenance:{inputScope:`provided_rows`,selectionOrder:[`valid_row`,`position`,`minimum_impressions`,`query_quality`,`brand`],selection:r.selection},summary:{eligibleRows:i.length,returnedRows:a.length,eligibleImpressions:Number(s.toFixed(3)),returnedImpressions:Number(c.toFixed(3)),uniqueEligibleUrls:new Set(i.map(e=>e.url)).size,uniqueEligibleQueries:new Set(i.map(e=>e.query)).size,groups:o.length},groups:o,items:a}}const SD=1e5,CD={searchAnalytics:e_,verifyContent:bv,now:()=>new Date};function wD(e,t,n,r){return e===void 0||!Number.isFinite(e)?t:Math.min(r,Math.max(n,Math.floor(e)))}function TD(e){if(e===void 0)return 28;if(!Number.isInteger(e)||e<1||e>548)throw new A(`INVALID_INPUT`,`Days must be a whole number between 1 and 548.`);return e}function ED(e,t){return Zg(e,t)}function DD(e){return e.status===`failed`?{type:`verification-failed`,confidence:`low`,evidence:e.error??`The page could not be verified.`,action:gv(e)}:e.classification===`technical-check`?{type:`fix-technical`,confidence:`medium`,evidence:`Verified technical signals: ${e.signals.join(`, `)}.`,action:gv(e)}:{type:e.classification===`covered`?`investigate-ranking`:`review-page-evidence`,confidence:`low`,evidence:e.summary,action:gv(e)}}function OD(e){return e.slice(0,5).map(e=>({id:e.id,label:e.label,count:e.rowCount,sampleUrls:e.sampleUrls}))}function kD(e){return e.dataStatus===`empty`?`Search Console returned no retained query/page rows for this date window.`:e.dataStatus===`filtered`?`No retained query/page rows met the report filters.`:`${e.summary.eligibleRows} eligible query/page rows found; ${e.summary.returnedRows} returned for review.`}async function AD(e,t=CD){let n=TD(e.days),r=t.now().toISOString(),i=ED(n,new Date(r)),a=await t.searchAnalytics(e.site,{...i,dimensions:[`query`,`page`],type:`web`,dataState:`final`,maxRows:SD},{refresh:e.refresh}),o=xD({rows:a.rows,site:e.site,minImpressions:e.minImpressions,limit:e.limit,brandTerms:e.brandTerms,includeBrand:e.includeBrand}),s=wD(e.verifyLimit,5,0,o.limit),c=e.verifyContent===!0||e.verifyLimit!==void 0,l=c?Math.min(s,o.items.length):0,u=await Promise.all(o.items.map(async(n,i)=>{if(i>=l)return n;let a=await t.verifyContent({query:n.query,url:n.url,js:e.js,refresh:e.refresh,rate:e.rate,verifiedAt:r});return{...n,contentVerification:a,recommendation:DD(a)}})),d=u.slice(0,l).map(e=>e.contentVerification).filter(e=>!!e),f=a.rowsFetched>=SD;return{site:e.site,generatedAt:r,range:i,rangeDays:n,source:{provider:`google-search-console`,dimensions:[`query`,`page`],searchType:`web`,dataState:`final`,rowsFetched:a.rowsFetched,calls:a.calls,maxRows:SD,possiblyTruncated:f,completeness:`retained-query-rows-only`},dataStatus:o.dataStatus,selection:o.selection,methodology:o.methodology,verification:c?{requested:!0,limit:s,attempted:l,verified:d.filter(e=>e.status===`verified`).length,technical:d.filter(e=>e.classification===`technical-check`).length,failed:d.filter(e=>e.status===`failed`).length}:{requested:!1,attempted:0,verified:0,technical:0,failed:0},items:u,templates:OD(o.groups),groups:o.groups,summary:{...o.summary,brandFiltering:e.includeBrand?`included`:`excluded`,verdict:kD(o)},caveats:[`Date window: ${i.startDate} to ${i.endDate}, using final GSC data where available.`,`Selection: ${o.selection.sourceRows} source rows, ${o.selection.eligibleRows} eligible, ${o.selection.returnedRows} returned.`,`Position is GSC average position for the property's topmost result per impression; it does not prove a URL ranked on a literal second results page every time.`,`Search Analytics exposes retained query rows only. Anonymized and lower-value queries can be absent even when pagination is exhausted.`,`Priority is a demand-and-position heuristic, not an estimated click lift or proof that a change will improve rankings.`,`Content verification: ${c?`${l} ${l===1?`row`:`rows`} attempted`:`not run`}.`,f?`The report reached its ${SD.toLocaleString(`en-US`)}-row safety cap.`:``].filter(Boolean),recommendations:o.groups.length?o.groups.slice(0,5).map(e=>e.recommendation.action):[`No action is recommended from this report. Lower the minimum impressions or widen the date window if you need long-tail inspection.`]}}function jD(e,t){let n=`${e} ${t}`.toLowerCase();return n.includes(`core`)?`core`:n.includes(`spam`)?`spam`:n.includes(`discover`)?`discover`:n.includes(`ranking`)?`ranking`:n.includes(`index`)?`indexing`:n.includes(`serving`)?`serving`:`other`}async function MD(){let e=await n(`https://status.search.google.com/incidents.json`);if(!e.ok)throw new A(`OPTIONAL_PROVIDER_UNAVAILABLE`,`Google Search Status fetch failed with ${e.status}.`);return await e.json()}async function ND(e={}){let t=await MD(),n=e.product?.toLowerCase();return t.filter(e=>!n||e.service_name.toLowerCase()===n).map(e=>{let t=e.end?`complete`:e.status_impact===`SERVICE_INFORMATION`?`rolling-out`:`incident`;return{id:e.id,name:e.external_desc,type:jD(e.external_desc,e.service_name),product:e.service_name,start:e.begin,end:e.end,status:t,sourceUrl:`https://status.search.google.com/${e.uri}`}}).slice(0,e.limit??25)}function PD(e){let t=(e.paddingDays??3)*864e5,n=new Date(e.startDate).getTime()-t,r=new Date(e.endDate).getTime()+t;return e.updates.filter(e=>{let t=new Date(e.start).getTime(),i=e.end?new Date(e.end).getTime():Date.now();return t<=r&&i>=n})}function FD(e){return new Date(e).toISOString()}function ID(e){return{id:e.id,site:e.site_url,name:e.name,dimension:e.dimension,matchType:e.match_type,pattern:e.pattern,createdAt:FD(e.created_at)}}function LD(e){return{id:e.id,site:e.site_url,scope:e.scope,target:e.target,title:e.title,description:e.description??void 0,changedAt:e.changed_at,createdAt:FD(e.created_at)}}function RD(e){let t={id:v(),site:e.site,scope:e.scope,target:e.target,title:e.title,description:e.description,changedAt:e.changedAt,createdAt:new Date().toISOString()};return j().prepare(`INSERT INTO seo_changes
571
571
  (id, site_url, scope, target, title, description, changed_at, created_at)
572
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(t.id,t.site,t.scope,t.target,t.title,t.description??null,t.changedAt,Date.parse(t.createdAt)),t}function bD(e={}){let t=e.limit??25;return(e.site?j().prepare(`SELECT * FROM seo_changes WHERE site_url = ? ORDER BY changed_at DESC, created_at DESC LIMIT ?`).all(e.site,t):j().prepare(`SELECT * FROM seo_changes ORDER BY changed_at DESC, created_at DESC LIMIT ?`).all(t)).map(vD)}function xD(e){let t=j().prepare(`SELECT * FROM seo_changes WHERE id = ?`).get(e);return t?vD(t):void 0}function SD(e){return j().prepare(`DELETE FROM seo_changes WHERE id = ?`).run(e).changes>0}function CD(e){return e.reduce((e,t)=>e+t,0)/Math.max(1,e.length)}function wD(e){let t=CD(e),n=CD(e.map(e=>(e-t)**2));return Math.sqrt(n)}function TD(e,t=3){return Number(e.toFixed(t))}function ED(e,t){return e?(t-e)/e*100:t?null:0}function DD(e){return`${Math.abs(e)>=10?e.toFixed(0):e.toFixed(1)}%`}function OD(e){return e===`clicks`?`Clicks`:`Impressions`}function kD(e){let t=e.direction===`drop`?`fell`:`rose`,n=e.percentChange===null?null:e.direction===`drop`?Math.abs(e.percentChange):e.percentChange,r=n===null?`from a zero baseline; percentage change is undefined`:`${DD(n)} vs baseline`;return e.direction===`normal`?`${OD(e.metric)} changed ${r} (${e.baselineMean.toLocaleString(`en-GB`)} -> ${e.comparisonMean.toLocaleString(`en-GB`)} per observed day${e.zScore===null?`; the baseline had no observed variance`:`, z ${e.zScore}`}).`:`${OD(e.metric)} ${t} ${r} (${e.baselineMean.toLocaleString(`en-GB`)} -> ${e.comparisonMean.toLocaleString(`en-GB`)} per observed day${e.zScore===null?`; outside the flat observed baseline`:`, z ${e.zScore}`}).`}function AD(e,t,n=`${t}s`){return e===1?t:n}function jD(e){return[...e].sort((e,t)=>e.significant===t.significant?Math.abs(t.zScore??0)-Math.abs(e.zScore??0)||Math.abs(t.percentChange??0)-Math.abs(e.percentChange??0)||(e.metric<t.metric?-1:+(e.metric>t.metric)):e.significant?-1:1)[0]}function MD(e){return new Date(e).getTime()}function ND(e){if(!e.startDate||!e.endDate)return[];let t=e.paddingDays*864e5,n=MD(e.startDate)-t,r=MD(e.endDate)+t;return e.changes.filter(e=>{let t=MD(e.changedAt);return t>=n&&t<=r})}function PD(e){let t=e.filter(e=>e.significant);if(t.length<2)return!1;let n=new Set(t.map(e=>e.direction));if(n.size!==1||n.has(`normal`))return!1;let r=t.find(e=>e.metric===`clicks`),i=t.find(e=>e.metric===`impressions`);if(!r||!i||r.percentChange===null||i.percentChange===null)return!1;let a=Math.abs(r.percentChange),o=Math.abs(i.percentChange),s=Math.max(...t.map(e=>Math.abs(e.zScore??0))),c=t.some(e=>e.significanceMethod===`outside-flat-baseline`);return(s>=3||c)&&Math.min(a,o)>=45&&Math.max(a,o)>=60}function FD(e){return`${e.date?`${e.date}: `:``}${e.scope?`${e.scope} `:``}${e.title}`}function ID(e){let t=e.anomalies.filter(e=>e.significant),n=jD(e.anomalies),r=e.overlappingUpdates.map(e=>e.name),i=e.overlappingUpdates.length>0,a=e.confounders??[],o=a.length>0,s=PD(e.anomalies),c=t.length>0&&i?`significant-movement-with-update-overlap`:i?`update-overlap-without-significant-movement`:`no-update-overlap`,l=n?.direction??`normal`,u=l===`drop`?s?`collapsed`:`dropped`:l===`spike`?s?`surged`:`grew`:`did not move enough to call an anomaly`,d=a.length===1?`1 known site change also overlaps`:`${a.length} known site changes also overlap`,f=t.length&&i?`${e.site} ${u} during a period that overlaps ${r.join(`, `)}.${o?` ${d} this window.`:``} The overlap is timing context and does not establish what caused the movement.`:i?`${e.site} overlaps ${r.join(`, `)}, but no significant GSC movement was detected in this window.`:`${e.site} had no official Google Ranking update overlap in the recent comparison window.`,p=t.length&&i&&o?[`Start with the overlapping site changes. Test whether pruning, blocking, redirects, deploys, or tracking changes explain the observed movement.`,`Run segment-impact for page and query and compare affected sections against the saved change targets.`,`Compare changed and unchanged sections, then record which explanation the segment evidence supports.`]:t.length&&i?[`Run update-postmortem and segment-impact next to find the winning or losing templates, queries, countries, and devices without assuming a cause.`,l===`drop`?`For a collapse, avoid broad rewrites first. Identify the templates that lost impressions, then check indexability, intent fit, and SERP changes.`:`For a surge, document the winning templates and reinforce them with internal links before changing pages that improved.`,`Add any missed deploy, pruning, blocking, or content changes to change-log so later comparisons include that evidence.`]:i?[`No significant movement was detected in this comparison. Keep the overlap as context and avoid an update-impact conclusion.`]:[`If traffic moved, investigate site changes, seasonality, indexing, and segment-level movement before looking for update causes.`];return{classification:c,attribution:`not-established`,confidence:`none`,confounders:a,summary:f,evidence:[...e.anomalies.map(kD),i?`${e.overlappingUpdates.length} official Google Ranking update ${AD(e.overlappingUpdates.length,`window`)} overlapped the comparison period: ${r.join(`, `)}.`:`No official Google Ranking update windows overlapped the comparison period.`,s?`Both clicks and impressions moved sharply in the same direction.`:`Clicks and impressions did not both meet the sharp-movement evidence threshold.`,o?`Known overlapping site changes: ${a.map(FD).join(`; `)}.`:`No saved or manually supplied overlapping site changes were found.`],caveats:[`GSC window: ${e.days} days; recent comparison window: ${e.recentDays} days.`,`Update overlap padding: ${e.paddingDays} ${AD(e.paddingDays,`day`)} either side of the comparison window.`,e.refresh?`Data freshness: cache bypassed for GSC data.`:`Data freshness: local GSC cache may have been used.`,`Source: official Google Search Status Dashboard Ranking incidents only; third-party volatility and unconfirmed chatter are not included.`,`An update-window overlap cannot establish causation, even when movement is large and no known site change was recorded.`,`Attribution remains not established. Use segment-impact and update-postmortem to describe which templates or intents moved, then test possible explanations separately.`],actions:p,source:{name:`Google Search Status Dashboard incidents feed`,url:`https://status.search.google.com/incidents.json`,product:`Ranking`}}}function LD(e){if(!/^\d{4}-\d{2}-\d{2}$/.test(e))return!1;let t=new Date(`${e}T00:00:00.000Z`);return!Number.isNaN(t.getTime())&&t.toISOString().slice(0,10)===e}function RD(e,t){let n=Date.parse(`${e}T00:00:00.000Z`),r=Date.parse(`${t}T00:00:00.000Z`);return Math.floor((r-n)/864e5)+1}function zD(e){return e.keys.length===1&&Number.isFinite(e.clicks)&&e.clicks>=0&&Number.isFinite(e.impressions)&&e.impressions>=0}function BD(e){let t=new Map,n=0,r=0;for(let i of e.rows){let a=i.keys[0];if(!a||!LD(a)||a<e.startDate||a>e.endDate||!zD(i)){n+=1;continue}let o=t.get(a);if(o){r+=1,o.clicks+=i.clicks,o.impressions+=i.impressions;continue}t.set(a,{...i,keys:[a]})}return{rows:[...t.values()].sort((e,t)=>{let n=e.keys[0]??``,r=t.keys[0]??``;return n<r?-1:+(n>r)}),invalidRows:n,duplicateRows:r}}function VD(e){if(!LD(e.startDate)||!LD(e.endDate)||e.startDate>e.endDate)throw new A(`INVALID_INPUT`,`Pass a valid Search Console date range.`);if(!Number.isInteger(e.recentDays)||e.recentDays<1)throw new A(`INVALID_INPUT`,`recentDays must be a positive integer.`);let t=RD(e.startDate,e.endDate),n=Yg(e.endDate,-(e.recentDays-1)),r=Yg(n,-1),i=RD(e.startDate,r);if(n<e.startDate||i<14)throw new A(`INSUFFICIENT_DATA`,`The requested calendar range does not contain at least 14 baseline days before the comparison window.`);let a=BD({rows:e.rows,startDate:e.startDate,endDate:e.endDate}),o=a.rows.filter(e=>(e.keys[0]??``)<=r),s=a.rows.filter(e=>(e.keys[0]??``)>=n);if(o.length<14||s.length<3)throw new A(`INSUFFICIENT_DATA`,`Not enough observed daily GSC data for anomaly detection (${o.length} baseline days and ${s.length} comparison days).`);let c=[`clicks`,`impressions`].map(t=>{let i=o.map(e=>e[t]),a=s.map(e=>e[t]),c=CD(i),l=CD(a),u=i.reduce((e,t)=>e+t,0),d=a.reduce((e,t)=>e+t,0),f=wD(i)/Math.sqrt(a.length),p=f>0?(l-c)/f:null,m=p===null&&l!==c,h=m||p!==null&&Math.abs(p)>=2,g=h?l<c?`drop`:`spike`:`normal`,_=ED(c,l);return{site:e.site,metric:t,baselineStart:e.startDate,baselineEnd:r,comparisonStart:n,comparisonEnd:e.endDate,baselineMean:TD(c),comparisonMean:TD(l),baselineTotal:TD(u),comparisonTotal:TD(d),percentChange:_===null?null:TD(_),zScore:p===null?null:TD(p),significanceMethod:h?m?`outside-flat-baseline`:`z-score`:`none`,direction:g,significant:h}}),l=i-o.length,u=e.recentDays-s.length,d=t-a.rows.length,f=[...d>0?[`${d} requested calendar ${AD(d,`day`)} had no returned date aggregate and ${d===1?`was`:`were`} not filled with zeros.`]:[],...a.invalidRows>0?[`${a.invalidRows} invalid provider ${AD(a.invalidRows,`row`)} ${a.invalidRows===1?`was`:`were`} excluded.`]:[],...a.duplicateRows>0?[`${a.duplicateRows} duplicate date ${AD(a.duplicateRows,`row`)} ${a.duplicateRows===1?`was`:`were`} aggregated before analysis.`]:[]];return{anomalies:c,coverage:{status:d>0||a.invalidRows>0?`partial`:`complete`,requestedStart:e.startDate,requestedEnd:e.endDate,returnedRows:e.rows.length,observedDays:a.rows.length,invalidRows:a.invalidRows,duplicateRows:a.duplicateRows,expectedDays:t,missingDays:d,baseline:{start:e.startDate,end:r,expectedDays:i,observedDays:o.length,missingDays:l},comparison:{start:n,end:e.endDate,expectedDays:e.recentDays,observedDays:s.length,missingDays:u},caveats:f}}}async function HD(e){let t=e.days??90,n=e.recentDays??7;if(e.startDate&&!e.endDate||!e.startDate&&e.endDate)throw new A(`INVALID_INPUT`,`Pass both startDate and endDate, or neither.`);let r=e.startDate&&e.endDate?{startDate:e.startDate,endDate:e.endDate}:H_(t),i=await e_(e.site,{...r,dimensions:[`date`],type:`web`,dataState:`final`},{refresh:e.refresh}),a=VD({site:e.site,rows:i.rows,startDate:r.startDate,endDate:r.endDate,recentDays:n});return{site:e.site,generatedAt:new Date().toISOString(),anomalies:a.anomalies,rows:i.rows.length,coverage:a.coverage}}async function UD(e){let t=e.days??90,n=e.recentDays??7,r=e.paddingDays??3,i=e.trafficAnomalies??(await HD(e)).anomalies,a=i[0]?.comparisonStart,o=i[0]?.comparisonEnd,s=await mD({product:`Ranking`,limit:50}),c=a&&o?hD({updates:s,startDate:a,endDate:o,paddingDays:r}):[],l=e.includeChangeLog===!1?[]:ND({changes:bD({site:e.site,limit:100}),startDate:a,endDate:o,paddingDays:r}).map(e=>({source:`change-log`,title:e.title,date:e.changedAt,scope:e.scope,target:e.target,description:e.description})),u=(e.knownConfounders??[]).map(e=>({source:`manual`,title:e})),d=[...l,...u];return{site:e.site,generatedAt:new Date().toISOString(),anomalies:i,overlappingUpdates:c,...ID({site:e.site,anomalies:i,overlappingUpdates:c,confounders:d,days:t,recentDays:n,paddingDays:r,refresh:e.refresh})}}function WD(e){return e instanceof Error?e.message:String(e)}function GD(e){return e.startDate&&e.endDate?{startDate:e.startDate,endDate:e.endDate}:H_(e.days??28)}function KD(e){return{site:e.site,generatedAt:new Date().toISOString(),anomalies:[],rows:0}}function qD(e,t){let n=t?`Update correlation was unavailable: ${t.reason}`:`The traffic anomaly section was skipped or returned no daily rows.`;return{site:e.site,generatedAt:new Date().toISOString(),anomalies:[],overlappingUpdates:[],classification:`insufficient-data`,attribution:`not-established`,confidence:`none`,confounders:[],summary:t?`${e.site} update correlation was unavailable for this run.`:`${e.site} does not have enough daily GSC data for update correlation yet.`,evidence:[n],caveats:[`GSC window: ${e.days??90} days; recent comparison window: ${e.recentDays??7} days.`,...t?[`No update-correlation conclusion was produced.`]:[`New or sparse properties need more daily GSC data before update correlation is useful.`]],actions:[t?.source===`search-status`?`Retry update correlation when the Search Status provider is available; use the canonical traffic anomalies and other report evidence in the meantime.`:t?.source===`traffic-anomaly`?`Restore traffic anomaly evidence, then retry update correlation; use other report evidence in the meantime.`:`Use quick wins, second-page opportunities, page audit, and technical checks until enough daily GSC data exists.`],source:{name:`Google Search Status Dashboard incidents feed`,url:`https://status.search.google.com/incidents.json`,product:`Ranking`}}}function JD(e,t=`Segment evidence was unavailable.`){let n=GD(e);return TT({site:e.site,dimension:e.dimension,after:n,reason:t})}function YD(e){let t=pT(GD(e));return t>240?`The ${t}-day diagnosis window is too long for an adjacent segment comparison inside Search Console's rolling 16-month API history. Use 240 days or fewer for segment movement.`:void 0}function XD(e){let t=GD(e),n=wT(t),r=_E({site:e.site,currentRows:[],previousRows:[],limit:e.limit,includeBrand:e.includeBrand});return{schemaVersion:1,site:e.site,generatedAt:new Date().toISOString(),comparison:`previous-period`,ranges:{current:t,previous:n},rangeDays:Math.floor((new Date(`${t.endDate}T00:00:00.000Z`).getTime()-new Date(`${t.startDate}T00:00:00.000Z`).getTime())/864e5)+1,dataStatus:`unavailable`,source:{provider:`google-search-console`,dimensions:[`query`,`page`],aggregationType:`auto`,searchType:`web`,dataState:`final`,current:{rowsFetched:0,calls:0,maxRows:1e5,possiblyTruncated:!1},previous:{rowsFetched:0,calls:0,maxRows:1e5,possiblyTruncated:!1},completeness:`unavailable`},methodology:{id:`gsc_retained_query_page_decay_v2`,version:2,gscHistoryMonths:16,missingRowsTreatedAsZero:!1,urlShiftsExcluded:!0,causeLanguage:`signals-not-attribution`},filters:{minDropPct:20,minPreviousClicks:2,minClickLoss:1,limit:e.limit??25,brand:e.includeBrand?`included`:`excluded`},selection:r.selection,summary:{eligibleRows:0,returnedRows:0,groups:0,observedRetainedQueryClickLoss:0,returnedObservedRetainedQueryClickLoss:0,brandFiltering:e.includeBrand?`included`:`excluded`,verdict:`Decay analysis was skipped.`},caveats:[`Decay analysis was skipped because required GSC data was unavailable.`],recommendations:[`Run this section again after the property has enough query/page history.`],items:r.items,groups:r.groups,templates:r.templates,ledgerSummary:`GSC: 0 calls, 0 rows.`,warnings:[]}}function ZD(e){let t=GD(e),n=Math.floor((new Date(`${t.endDate}T00:00:00.000Z`).getTime()-new Date(`${t.startDate}T00:00:00.000Z`).getTime())/864e5)+1,r=tE({site:e.site,rows:[]});return{schemaVersion:1,site:e.site,generatedAt:new Date().toISOString(),range:t,rangeDays:n,dataStatus:`unavailable`,source:{provider:`google-search-console`,searchType:`web`,dataState:`final`,pageExposure:{dimensions:[`query`,`page`],aggregationType:`auto`,rowsFetched:0,validation:{retainedRows:0,invalidRows:0},calls:0,maxRows:1e5,possiblyTruncated:!1},propertyDemand:{dimensions:[`query`],aggregationType:`byProperty`,rowsFetched:0,validation:{retainedRows:0,invalidRows:0},calls:0,maxRows:1e5,possiblyTruncated:!1},completeness:`unavailable`},methodology:{id:`gsc_url_overlap_v2`,version:2,minimumPageImpressions:10,minimumPageImpressionShare:.1,maximumDominantPageShare:.8,matching:`normalized_exact_query`,finding:`url-overlap-candidate`,requiresIntentReview:!0},verification:{status:`not-requested`,technicalStateChecked:!1,searchIntentChecked:!1},filters:r.filters,selection:r.selection,summary:{eligibleClusters:0,returnedClusters:0,suppressedQueries:0,brandFiltering:`excluded`,verdict:`Multi-URL query analysis was unavailable.`},templates:[],suppressed:[],suppressionSummary:{},items:[],caveats:[`Multi-URL query analysis was skipped because required GSC data was unavailable.`],recommendations:[`Run this section again after Search Console query/page data is available.`],ledgerSummary:`GSC: 0 calls, 0 rows.`}}function QD(e){let t=nD({rows:[],site:e.site});return{site:e.site,generatedAt:new Date().toISOString(),range:H_(e.days??28),rangeDays:e.days??28,source:{provider:`google-search-console`,dimensions:[`query`,`page`],searchType:`web`,dataState:`final`,rowsFetched:0,calls:0,maxRows:1e5,possiblyTruncated:!1,completeness:`retained-query-rows-only`},dataStatus:t.dataStatus,selection:t.selection,methodology:t.methodology,verification:{requested:!1,attempted:0,verified:0,technical:0,failed:0},items:[],templates:[],groups:[],summary:{...t.summary,brandFiltering:`excluded`,verdict:`Striking-distance analysis was skipped.`},caveats:[`Striking-distance analysis was skipped because required GSC data was unavailable.`],recommendations:[`Run this section again after the property has enough query/page history.`]}}function $D(e){let t=IE({rows:[],site:e.site}),n=GD(e);return{site:e.site,generatedAt:new Date().toISOString(),range:n,rangeDays:e.days??28,source:{provider:`google-search-console`,dimensions:[`query`,`page`],searchType:`web`,dataState:`final`,rowsFetched:0,calls:0,maxRows:1e5,possiblyTruncated:!1,completeness:`retained-query-rows-only`},dataStatus:t.dataStatus,selection:t.selection,methodology:t.methodology,provenance:{...t.provenance,verification:{optional:!0,population:`returned_rows_in_priority_order`,fetchDeduplication:`exact_url`}},benchmark:{method:t.methodology.benchmark.method,peerRows:0,byPosition:t.benchmarkByPosition},verification:{requested:!1,attemptedRows:0,attemptedUrls:0,verified:0,technical:0,failed:0},summary:{...t.summary,repeatedQueryGroups:0,templatePatterns:0,brandFiltering:`excluded`,verdict:`Quick-win analysis was skipped.`},caveats:[`Quick-win analysis was skipped because required GSC data was unavailable.`],recommendations:[`Run this section again after the property has enough query/page history.`],templates:[],templateRecommendations:[],groups:[],items:[],ledgerSummary:`No provider calls recorded.`,warnings:[]}}const eO={trafficAnomaly:HD,updateCorrelation:UD,segmentImpact:DT,decayingReport:xE,cannibalReport:aE,strikingDistance:dD,quickWinsReport:GE};async function tO(e,t={}){let n={...eO,...t},r=e.limit??10,i=async(t,n,r)=>{e.progress?.(`Running ${t}`);try{let r=await n();return e.progress?.(`Finished ${t}`),{status:`completed`,value:r}}catch(n){if(!Ce(n))throw n;let i=WD(n);return e.progress?.(`Skipped ${t}: ${i}`),{status:`skipped`,value:r(n),skipped:{section:t,reason:i}}}},a=(t,n,r)=>(e.progress?.(`Skipped ${t}: ${r}`),{status:`skipped`,value:n,skipped:{section:t,reason:r}}),o=e.skipSearchData?`Search Console data was skipped because no Search Console property was selected.`:void 0,s=(e,t,n)=>o?Promise.resolve(a(e,t(),o)):i(e,n,t),c=await s(`traffic anomaly`,()=>KD(e),()=>n.trafficAnomaly(e)),l=o?Promise.resolve(a(`update correlation`,qD(e,{reason:o,source:`traffic-anomaly`}),o)):c.status===`skipped`?Promise.resolve(a(`update correlation`,qD(e,{reason:`Traffic anomaly evidence was unavailable: ${c.skipped.reason}`,source:`traffic-anomaly`}),`Traffic anomaly evidence was unavailable: ${c.skipped.reason}`)):i(`update correlation`,()=>n.updateCorrelation({...e,trafficAnomalies:c.value.anomalies}),t=>qD(e,{reason:WD(t),source:`search-status`})),u=YD(e),d=(t,i)=>o?Promise.resolve(a(t,JD({...e,dimension:i},o),o)):u?Promise.resolve(a(t,JD({...e,dimension:i},u),u)):s(t,t=>JD({...e,dimension:i},WD(t)),()=>n.segmentImpact({...e,dimension:i,limit:r})),[f,p,m,h,g,_,v,y,b]=await Promise.all([l,d(`page movement segments`,`page`),d(`query movement segments`,`query`),d(`device movement segments`,`device`),d(`country movement segments`,`country`),s(`decay analysis`,()=>XD({...e,limit:r}),()=>n.decayingReport({site:e.site,days:e.days,startDate:e.startDate,endDate:e.endDate,limit:r,brandTerms:e.brandTerms,includeBrand:e.includeBrand,refresh:e.refresh})),s(`cannibalisation analysis`,()=>ZD(e),()=>n.cannibalReport({site:e.site,days:e.days,startDate:e.startDate,endDate:e.endDate,limit:r,brandTerms:e.brandTerms,includeBrand:e.includeBrand,refresh:e.refresh})),s(`striking-distance opportunities`,()=>QD(e),()=>n.strikingDistance({...e,limit:r})),s(`quick-win opportunities`,()=>$D(e),()=>n.quickWinsReport({site:e.site,days:e.days,startDate:e.startDate,endDate:e.endDate,limit:r,brandTerms:e.brandTerms,includeBrand:e.includeBrand,verifyContent:e.verifyContent,verifyLimit:e.verifyLimit,js:e.js,rate:e.rate,refresh:e.refresh}))]);e.progress?.(`Building priority list`);let x=c.value,ee=f.value,te=p.value,S=m.value,C=h.value,w=g.value,T=_.value,ne=v.value,re=y.value,E=b.value,ie=[c,f,p,m,h,g,_,v,y,b].flatMap(e=>e.status===`skipped`?[e.skipped]:[]),ae=$w({anomaly:x,update:ee,page:te,decay:T,cannibal:ne,striking:re,quickWins:E}),D=eT({skippedSections:ie,anomaly:x,segments:{page:te,query:S,device:C,country:w},decay:T,cannibalization:ne,strikingDistance:re,quickWins:E}),oe=tT({criticalStatuses:[c,f,p,_,v,y,b].map(e=>e.status),skippedSections:ie.length,partialReasons:D.length}),se=f.status===`skipped`?`unavailable`:`available`;return{site:e.site,generatedAt:new Date().toISOString(),dataStatus:oe,summary:{updateAttribution:se===`available`?ee.classification:`unavailable`,updateAttributionStatus:se,classification:ee.classification,significantAnomalies:x.anomalies.filter(e=>e.significant).length,updateMatches:ee.overlappingUpdates.length,largestPageMovements:te.items.length,decayItems:T.selection.eligibleRows,cannibalItems:ne.selection.eligibleClusters,strikingDistanceItems:re.items.length,quickWinItems:E.items.length},skippedSections:ie,partialReasons:D,priorities:ae,anomaly:x,updateCorrelation:ee,segments:{page:te,query:S,device:C,country:w},decay:T,cannibalization:ne,strikingDistance:re,quickWins:E}}function nO(e){let t={id:v(),site:e.site,name:e.name,dimension:e.dimension??`page`,matchType:e.matchType??`contains`,pattern:e.pattern,createdAt:new Date().toISOString()};return j().prepare(`INSERT INTO content_groups
572
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(t.id,t.site,t.scope,t.target,t.title,t.description??null,t.changedAt,Date.parse(t.createdAt)),t}function zD(e={}){let t=e.limit??25;return(e.site?j().prepare(`SELECT * FROM seo_changes WHERE site_url = ? ORDER BY changed_at DESC, created_at DESC LIMIT ?`).all(e.site,t):j().prepare(`SELECT * FROM seo_changes ORDER BY changed_at DESC, created_at DESC LIMIT ?`).all(t)).map(LD)}function BD(e){let t=j().prepare(`SELECT * FROM seo_changes WHERE id = ?`).get(e);return t?LD(t):void 0}function VD(e){return j().prepare(`DELETE FROM seo_changes WHERE id = ?`).run(e).changes>0}function HD(e){return e.reduce((e,t)=>e+t,0)/Math.max(1,e.length)}function UD(e){let t=HD(e),n=HD(e.map(e=>(e-t)**2));return Math.sqrt(n)}function WD(e,t=3){return Number(e.toFixed(t))}function GD(e,t){return e?(t-e)/e*100:t?null:0}function KD(e){return`${Math.abs(e)>=10?e.toFixed(0):e.toFixed(1)}%`}function qD(e){return e===`clicks`?`Clicks`:`Impressions`}function JD(e){let t=e.direction===`drop`?`fell`:`rose`,n=e.percentChange===null?null:e.direction===`drop`?Math.abs(e.percentChange):e.percentChange,r=n===null?`from a zero baseline; percentage change is undefined`:`${KD(n)} vs baseline`;return e.direction===`normal`?`${qD(e.metric)} changed ${r} (${e.baselineMean.toLocaleString(`en-GB`)} -> ${e.comparisonMean.toLocaleString(`en-GB`)} per observed day${e.zScore===null?`; the baseline had no observed variance`:`, z ${e.zScore}`}).`:`${qD(e.metric)} ${t} ${r} (${e.baselineMean.toLocaleString(`en-GB`)} -> ${e.comparisonMean.toLocaleString(`en-GB`)} per observed day${e.zScore===null?`; outside the flat observed baseline`:`, z ${e.zScore}`}).`}function YD(e,t,n=`${t}s`){return e===1?t:n}function XD(e){return[...e].sort((e,t)=>e.significant===t.significant?Math.abs(t.zScore??0)-Math.abs(e.zScore??0)||Math.abs(t.percentChange??0)-Math.abs(e.percentChange??0)||(e.metric<t.metric?-1:+(e.metric>t.metric)):e.significant?-1:1)[0]}function ZD(e){return new Date(e).getTime()}function QD(e){if(!e.startDate||!e.endDate)return[];let t=e.paddingDays*864e5,n=ZD(e.startDate)-t,r=ZD(e.endDate)+t;return e.changes.filter(e=>{let t=ZD(e.changedAt);return t>=n&&t<=r})}function $D(e){let t=e.filter(e=>e.significant);if(t.length<2)return!1;let n=new Set(t.map(e=>e.direction));if(n.size!==1||n.has(`normal`))return!1;let r=t.find(e=>e.metric===`clicks`),i=t.find(e=>e.metric===`impressions`);if(!r||!i||r.percentChange===null||i.percentChange===null)return!1;let a=Math.abs(r.percentChange),o=Math.abs(i.percentChange),s=Math.max(...t.map(e=>Math.abs(e.zScore??0))),c=t.some(e=>e.significanceMethod===`outside-flat-baseline`);return(s>=3||c)&&Math.min(a,o)>=45&&Math.max(a,o)>=60}function eO(e){return`${e.date?`${e.date}: `:``}${e.scope?`${e.scope} `:``}${e.title}`}function tO(e){let t=e.anomalies.filter(e=>e.significant),n=XD(e.anomalies),r=e.overlappingUpdates.map(e=>e.name),i=e.overlappingUpdates.length>0,a=e.confounders??[],o=a.length>0,s=$D(e.anomalies),c=t.length>0&&i?`significant-movement-with-update-overlap`:i?`update-overlap-without-significant-movement`:`no-update-overlap`,l=n?.direction??`normal`,u=l===`drop`?s?`collapsed`:`dropped`:l===`spike`?s?`surged`:`grew`:`did not move enough to call an anomaly`,d=a.length===1?`1 known site change also overlaps`:`${a.length} known site changes also overlap`,f=t.length&&i?`${e.site} ${u} during a period that overlaps ${r.join(`, `)}.${o?` ${d} this window.`:``} The overlap is timing context and does not establish what caused the movement.`:i?`${e.site} overlaps ${r.join(`, `)}, but no significant GSC movement was detected in this window.`:`${e.site} had no official Google Ranking update overlap in the recent comparison window.`,p=t.length&&i&&o?[`Start with the overlapping site changes. Test whether pruning, blocking, redirects, deploys, or tracking changes explain the observed movement.`,`Run segment-impact for page and query and compare affected sections against the saved change targets.`,`Compare changed and unchanged sections, then record which explanation the segment evidence supports.`]:t.length&&i?[`Run update-postmortem and segment-impact next to find the winning or losing templates, queries, countries, and devices without assuming a cause.`,l===`drop`?`For a collapse, avoid broad rewrites first. Identify the templates that lost impressions, then check indexability, intent fit, and SERP changes.`:`For a surge, document the winning templates and reinforce them with internal links before changing pages that improved.`,`Add any missed deploy, pruning, blocking, or content changes to change-log so later comparisons include that evidence.`]:i?[`No significant movement was detected in this comparison. Keep the overlap as context and avoid an update-impact conclusion.`]:[`If traffic moved, investigate site changes, seasonality, indexing, and segment-level movement before looking for update causes.`];return{classification:c,attribution:`not-established`,confidence:`none`,confounders:a,summary:f,evidence:[...e.anomalies.map(JD),i?`${e.overlappingUpdates.length} official Google Ranking update ${YD(e.overlappingUpdates.length,`window`)} overlapped the comparison period: ${r.join(`, `)}.`:`No official Google Ranking update windows overlapped the comparison period.`,s?`Both clicks and impressions moved sharply in the same direction.`:`Clicks and impressions did not both meet the sharp-movement evidence threshold.`,o?`Known overlapping site changes: ${a.map(eO).join(`; `)}.`:`No saved or manually supplied overlapping site changes were found.`],caveats:[`GSC window: ${e.days} days; recent comparison window: ${e.recentDays} days.`,`Update overlap padding: ${e.paddingDays} ${YD(e.paddingDays,`day`)} either side of the comparison window.`,e.refresh?`Data freshness: cache bypassed for GSC data.`:`Data freshness: local GSC cache may have been used.`,`Source: official Google Search Status Dashboard Ranking incidents only; third-party volatility and unconfirmed chatter are not included.`,`An update-window overlap cannot establish causation, even when movement is large and no known site change was recorded.`,`Attribution remains not established. Use segment-impact and update-postmortem to describe which templates or intents moved, then test possible explanations separately.`],actions:p,source:{name:`Google Search Status Dashboard incidents feed`,url:`https://status.search.google.com/incidents.json`,product:`Ranking`}}}function nO(e){if(!/^\d{4}-\d{2}-\d{2}$/.test(e))return!1;let t=new Date(`${e}T00:00:00.000Z`);return!Number.isNaN(t.getTime())&&t.toISOString().slice(0,10)===e}function rO(e,t){let n=Date.parse(`${e}T00:00:00.000Z`),r=Date.parse(`${t}T00:00:00.000Z`);return Math.floor((r-n)/864e5)+1}function iO(e){return e.keys.length===1&&Number.isFinite(e.clicks)&&e.clicks>=0&&Number.isFinite(e.impressions)&&e.impressions>=0}function aO(e){let t=new Map,n=0,r=0;for(let i of e.rows){let a=i.keys[0];if(!a||!nO(a)||a<e.startDate||a>e.endDate||!iO(i)){n+=1;continue}let o=t.get(a);if(o){r+=1,o.clicks+=i.clicks,o.impressions+=i.impressions;continue}t.set(a,{...i,keys:[a]})}return{rows:[...t.values()].sort((e,t)=>{let n=e.keys[0]??``,r=t.keys[0]??``;return n<r?-1:+(n>r)}),invalidRows:n,duplicateRows:r}}function oO(e){if(!nO(e.startDate)||!nO(e.endDate)||e.startDate>e.endDate)throw new A(`INVALID_INPUT`,`Pass a valid Search Console date range.`);if(!Number.isInteger(e.recentDays)||e.recentDays<1)throw new A(`INVALID_INPUT`,`recentDays must be a positive integer.`);let t=rO(e.startDate,e.endDate),n=Yg(e.endDate,-(e.recentDays-1)),r=Yg(n,-1),i=rO(e.startDate,r);if(n<e.startDate||i<14)throw new A(`INSUFFICIENT_DATA`,`The requested calendar range does not contain at least 14 baseline days before the comparison window.`);let a=aO({rows:e.rows,startDate:e.startDate,endDate:e.endDate}),o=a.rows.filter(e=>(e.keys[0]??``)<=r),s=a.rows.filter(e=>(e.keys[0]??``)>=n);if(o.length<14||s.length<3)throw new A(`INSUFFICIENT_DATA`,`Not enough observed daily GSC data for anomaly detection (${o.length} baseline days and ${s.length} comparison days).`);let c=[`clicks`,`impressions`].map(t=>{let i=o.map(e=>e[t]),a=s.map(e=>e[t]),c=HD(i),l=HD(a),u=i.reduce((e,t)=>e+t,0),d=a.reduce((e,t)=>e+t,0),f=UD(i)/Math.sqrt(a.length),p=f>0?(l-c)/f:null,m=p===null&&l!==c,h=m||p!==null&&Math.abs(p)>=2,g=h?l<c?`drop`:`spike`:`normal`,_=GD(c,l);return{site:e.site,metric:t,baselineStart:e.startDate,baselineEnd:r,comparisonStart:n,comparisonEnd:e.endDate,baselineMean:WD(c),comparisonMean:WD(l),baselineTotal:WD(u),comparisonTotal:WD(d),percentChange:_===null?null:WD(_),zScore:p===null?null:WD(p),significanceMethod:h?m?`outside-flat-baseline`:`z-score`:`none`,direction:g,significant:h}}),l=i-o.length,u=e.recentDays-s.length,d=t-a.rows.length,f=[...d>0?[`${d} requested calendar ${YD(d,`day`)} had no returned date aggregate and ${d===1?`was`:`were`} not filled with zeros.`]:[],...a.invalidRows>0?[`${a.invalidRows} invalid provider ${YD(a.invalidRows,`row`)} ${a.invalidRows===1?`was`:`were`} excluded.`]:[],...a.duplicateRows>0?[`${a.duplicateRows} duplicate date ${YD(a.duplicateRows,`row`)} ${a.duplicateRows===1?`was`:`were`} aggregated before analysis.`]:[]];return{anomalies:c,coverage:{status:d>0||a.invalidRows>0?`partial`:`complete`,requestedStart:e.startDate,requestedEnd:e.endDate,returnedRows:e.rows.length,observedDays:a.rows.length,invalidRows:a.invalidRows,duplicateRows:a.duplicateRows,expectedDays:t,missingDays:d,baseline:{start:e.startDate,end:r,expectedDays:i,observedDays:o.length,missingDays:l},comparison:{start:n,end:e.endDate,expectedDays:e.recentDays,observedDays:s.length,missingDays:u},caveats:f}}}async function sO(e){let t=e.days??90,n=e.recentDays??7;if(e.startDate&&!e.endDate||!e.startDate&&e.endDate)throw new A(`INVALID_INPUT`,`Pass both startDate and endDate, or neither.`);let r=e.startDate&&e.endDate?{startDate:e.startDate,endDate:e.endDate}:H_(t),i=await e_(e.site,{...r,dimensions:[`date`],type:`web`,dataState:`final`},{refresh:e.refresh}),a=oO({site:e.site,rows:i.rows,startDate:r.startDate,endDate:r.endDate,recentDays:n});return{site:e.site,generatedAt:new Date().toISOString(),anomalies:a.anomalies,rows:i.rows.length,coverage:a.coverage}}async function cO(e){let t=e.days??90,n=e.recentDays??7,r=e.paddingDays??3,i=e.trafficAnomalies??(await sO(e)).anomalies,a=i[0]?.comparisonStart,o=i[0]?.comparisonEnd,s=await ND({product:`Ranking`,limit:50}),c=a&&o?PD({updates:s,startDate:a,endDate:o,paddingDays:r}):[],l=e.includeChangeLog===!1?[]:QD({changes:zD({site:e.site,limit:100}),startDate:a,endDate:o,paddingDays:r}).map(e=>({source:`change-log`,title:e.title,date:e.changedAt,scope:e.scope,target:e.target,description:e.description})),u=(e.knownConfounders??[]).map(e=>({source:`manual`,title:e})),d=[...l,...u];return{site:e.site,generatedAt:new Date().toISOString(),anomalies:i,overlappingUpdates:c,...tO({site:e.site,anomalies:i,overlappingUpdates:c,confounders:d,days:t,recentDays:n,paddingDays:r,refresh:e.refresh})}}function lO(e){return e instanceof Error?e.message:String(e)}function uO(e){return e.startDate&&e.endDate?{startDate:e.startDate,endDate:e.endDate}:H_(e.days??28)}function dO(e){return{site:e.site,generatedAt:new Date().toISOString(),anomalies:[],rows:0}}function fO(e,t){let n=t?`Update correlation was unavailable: ${t.reason}`:`The traffic anomaly section was skipped or returned no daily rows.`;return{site:e.site,generatedAt:new Date().toISOString(),anomalies:[],overlappingUpdates:[],classification:`insufficient-data`,attribution:`not-established`,confidence:`none`,confounders:[],summary:t?`${e.site} update correlation was unavailable for this run.`:`${e.site} does not have enough daily GSC data for update correlation yet.`,evidence:[n],caveats:[`GSC window: ${e.days??90} days; recent comparison window: ${e.recentDays??7} days.`,...t?[`No update-correlation conclusion was produced.`]:[`New or sparse properties need more daily GSC data before update correlation is useful.`]],actions:[t?.source===`search-status`?`Retry update correlation when the Search Status provider is available; use the canonical traffic anomalies and other report evidence in the meantime.`:t?.source===`traffic-anomaly`?`Restore traffic anomaly evidence, then retry update correlation; use other report evidence in the meantime.`:`Use quick wins, second-page opportunities, page audit, and technical checks until enough daily GSC data exists.`],source:{name:`Google Search Status Dashboard incidents feed`,url:`https://status.search.google.com/incidents.json`,product:`Ranking`}}}function pO(e,t=`Segment evidence was unavailable.`){let n=uO(e);return WT({site:e.site,dimension:e.dimension,after:n,reason:t})}function mO(e){let t=MT(uO(e));return t>240?`The ${t}-day diagnosis window is too long for an adjacent segment comparison inside Search Console's rolling 16-month API history. Use 240 days or fewer for segment movement.`:void 0}function hO(e){let t=uO(e),n=UT(t),r=IE({site:e.site,currentRows:[],previousRows:[],limit:e.limit,includeBrand:e.includeBrand});return{schemaVersion:1,site:e.site,generatedAt:new Date().toISOString(),comparison:`previous-period`,ranges:{current:t,previous:n},rangeDays:Math.floor((new Date(`${t.endDate}T00:00:00.000Z`).getTime()-new Date(`${t.startDate}T00:00:00.000Z`).getTime())/864e5)+1,dataStatus:`unavailable`,source:{provider:`google-search-console`,dimensions:[`query`,`page`],aggregationType:`auto`,searchType:`web`,dataState:`final`,current:{rowsFetched:0,calls:0,maxRows:1e5,possiblyTruncated:!1},previous:{rowsFetched:0,calls:0,maxRows:1e5,possiblyTruncated:!1},completeness:`unavailable`},methodology:{id:`gsc_retained_query_page_decay_v2`,version:2,gscHistoryMonths:16,missingRowsTreatedAsZero:!1,urlShiftsExcluded:!0,causeLanguage:`signals-not-attribution`},filters:{minDropPct:20,minPreviousClicks:2,minClickLoss:1,limit:e.limit??25,brand:e.includeBrand?`included`:`excluded`},selection:r.selection,summary:{eligibleRows:0,returnedRows:0,groups:0,observedRetainedQueryClickLoss:0,returnedObservedRetainedQueryClickLoss:0,brandFiltering:e.includeBrand?`included`:`excluded`,verdict:`Decay analysis was skipped.`},caveats:[`Decay analysis was skipped because required GSC data was unavailable.`],recommendations:[`Run this section again after the property has enough query/page history.`],items:r.items,groups:r.groups,templates:r.templates,ledgerSummary:`GSC: 0 calls, 0 rows.`,warnings:[]}}function gO(e){let t=uO(e),n=Math.floor((new Date(`${t.endDate}T00:00:00.000Z`).getTime()-new Date(`${t.startDate}T00:00:00.000Z`).getTime())/864e5)+1,r=bE({site:e.site,rows:[]});return{schemaVersion:1,site:e.site,generatedAt:new Date().toISOString(),range:t,rangeDays:n,dataStatus:`unavailable`,source:{provider:`google-search-console`,searchType:`web`,dataState:`final`,pageExposure:{dimensions:[`query`,`page`],aggregationType:`auto`,rowsFetched:0,validation:{retainedRows:0,invalidRows:0},calls:0,maxRows:1e5,possiblyTruncated:!1},propertyDemand:{dimensions:[`query`],aggregationType:`byProperty`,rowsFetched:0,validation:{retainedRows:0,invalidRows:0},calls:0,maxRows:1e5,possiblyTruncated:!1},completeness:`unavailable`},methodology:{id:`gsc_url_overlap_v2`,version:2,minimumPageImpressions:10,minimumPageImpressionShare:.1,maximumDominantPageShare:.8,matching:`normalized_exact_query`,finding:`url-overlap-candidate`,requiresIntentReview:!0},verification:{status:`not-requested`,technicalStateChecked:!1,searchIntentChecked:!1},filters:r.filters,selection:r.selection,summary:{eligibleClusters:0,returnedClusters:0,suppressedQueries:0,brandFiltering:`excluded`,verdict:`Multi-URL query analysis was unavailable.`},templates:[],suppressed:[],suppressionSummary:{},items:[],caveats:[`Multi-URL query analysis was skipped because required GSC data was unavailable.`],recommendations:[`Run this section again after Search Console query/page data is available.`],ledgerSummary:`GSC: 0 calls, 0 rows.`}}function _O(e){let t=xD({rows:[],site:e.site});return{site:e.site,generatedAt:new Date().toISOString(),range:H_(e.days??28),rangeDays:e.days??28,source:{provider:`google-search-console`,dimensions:[`query`,`page`],searchType:`web`,dataState:`final`,rowsFetched:0,calls:0,maxRows:1e5,possiblyTruncated:!1,completeness:`retained-query-rows-only`},dataStatus:t.dataStatus,selection:t.selection,methodology:t.methodology,verification:{requested:!1,attempted:0,verified:0,technical:0,failed:0},items:[],templates:[],groups:[],summary:{...t.summary,brandFiltering:`excluded`,verdict:`Striking-distance analysis was skipped.`},caveats:[`Striking-distance analysis was skipped because required GSC data was unavailable.`],recommendations:[`Run this section again after the property has enough query/page history.`]}}function vO(e){let t=tD({rows:[],site:e.site}),n=uO(e);return{site:e.site,generatedAt:new Date().toISOString(),range:n,rangeDays:e.days??28,source:{provider:`google-search-console`,dimensions:[`query`,`page`],searchType:`web`,dataState:`final`,rowsFetched:0,calls:0,maxRows:1e5,possiblyTruncated:!1,completeness:`retained-query-rows-only`},dataStatus:t.dataStatus,selection:t.selection,methodology:t.methodology,provenance:{...t.provenance,verification:{optional:!0,population:`returned_rows_in_priority_order`,fetchDeduplication:`exact_url`}},benchmark:{method:t.methodology.benchmark.method,peerRows:0,byPosition:t.benchmarkByPosition},verification:{requested:!1,attemptedRows:0,attemptedUrls:0,verified:0,technical:0,failed:0},summary:{...t.summary,repeatedQueryGroups:0,templatePatterns:0,brandFiltering:`excluded`,verdict:`Quick-win analysis was skipped.`},caveats:[`Quick-win analysis was skipped because required GSC data was unavailable.`],recommendations:[`Run this section again after the property has enough query/page history.`],templates:[],templateRecommendations:[],groups:[],items:[],ledgerSummary:`No provider calls recorded.`,warnings:[]}}const yO={trafficAnomaly:sO,updateCorrelation:cO,segmentImpact:KT,decayingReport:BE,cannibalReport:wE,strikingDistance:AD,quickWinsReport:uD};async function bO(e,t={}){let n={...yO,...t},r=e.limit??10,i=async(t,n,r)=>{e.progress?.(`Running ${t}`);try{let r=await n();return e.progress?.(`Finished ${t}`),{status:`completed`,value:r}}catch(n){if(!Ce(n))throw n;let i=lO(n);return e.progress?.(`Skipped ${t}: ${i}`),{status:`skipped`,value:r(n),skipped:{section:t,reason:i}}}},a=(t,n,r)=>(e.progress?.(`Skipped ${t}: ${r}`),{status:`skipped`,value:n,skipped:{section:t,reason:r}}),o=e.skipSearchData?`Search Console data was skipped because no Search Console property was selected.`:void 0,s=(e,t,n)=>o?Promise.resolve(a(e,t(),o)):i(e,n,t),c=await s(`traffic anomaly`,()=>dO(e),()=>n.trafficAnomaly(e)),l=o?Promise.resolve(a(`update correlation`,fO(e,{reason:o,source:`traffic-anomaly`}),o)):c.status===`skipped`?Promise.resolve(a(`update correlation`,fO(e,{reason:`Traffic anomaly evidence was unavailable: ${c.skipped.reason}`,source:`traffic-anomaly`}),`Traffic anomaly evidence was unavailable: ${c.skipped.reason}`)):i(`update correlation`,()=>n.updateCorrelation({...e,trafficAnomalies:c.value.anomalies}),t=>fO(e,{reason:lO(t),source:`search-status`})),u=mO(e),d=(t,i)=>o?Promise.resolve(a(t,pO({...e,dimension:i},o),o)):u?Promise.resolve(a(t,pO({...e,dimension:i},u),u)):s(t,t=>pO({...e,dimension:i},lO(t)),()=>n.segmentImpact({...e,dimension:i,limit:r})),[f,p,m,h,g,_,v,y,b]=await Promise.all([l,d(`page movement segments`,`page`),d(`query movement segments`,`query`),d(`device movement segments`,`device`),d(`country movement segments`,`country`),s(`decay analysis`,()=>hO({...e,limit:r}),()=>n.decayingReport({site:e.site,days:e.days,startDate:e.startDate,endDate:e.endDate,limit:r,brandTerms:e.brandTerms,includeBrand:e.includeBrand,refresh:e.refresh})),s(`cannibalisation analysis`,()=>gO(e),()=>n.cannibalReport({site:e.site,days:e.days,startDate:e.startDate,endDate:e.endDate,limit:r,brandTerms:e.brandTerms,includeBrand:e.includeBrand,refresh:e.refresh})),s(`striking-distance opportunities`,()=>_O(e),()=>n.strikingDistance({...e,limit:r})),s(`quick-win opportunities`,()=>vO(e),()=>n.quickWinsReport({site:e.site,days:e.days,startDate:e.startDate,endDate:e.endDate,limit:r,brandTerms:e.brandTerms,includeBrand:e.includeBrand,verifyContent:e.verifyContent,verifyLimit:e.verifyLimit,js:e.js,rate:e.rate,refresh:e.refresh}))]);e.progress?.(`Building priority list`);let x=c.value,ee=f.value,te=p.value,S=m.value,C=h.value,w=g.value,T=_.value,ne=v.value,re=y.value,E=b.value,ie=[c,f,p,m,h,g,_,v,y,b].flatMap(e=>e.status===`skipped`?[e.skipped]:[]),ae=vT({anomaly:x,update:ee,page:te,decay:T,cannibal:ne,striking:re,quickWins:E}),D=yT({skippedSections:ie,anomaly:x,segments:{page:te,query:S,device:C,country:w},decay:T,cannibalization:ne,strikingDistance:re,quickWins:E}),oe=bT({criticalStatuses:[c,f,p,_,v,y,b].map(e=>e.status),skippedSections:ie.length,partialReasons:D.length}),se=f.status===`skipped`?`unavailable`:`available`;return{site:e.site,generatedAt:new Date().toISOString(),dataStatus:oe,summary:{updateAttribution:se===`available`?ee.classification:`unavailable`,updateAttributionStatus:se,classification:ee.classification,significantAnomalies:x.anomalies.filter(e=>e.significant).length,updateMatches:ee.overlappingUpdates.length,largestPageMovements:te.items.length,decayItems:T.selection.eligibleRows,cannibalItems:ne.selection.eligibleClusters,strikingDistanceItems:re.items.length,quickWinItems:E.items.length},skippedSections:ie,partialReasons:D,priorities:ae,anomaly:x,updateCorrelation:ee,segments:{page:te,query:S,device:C,country:w},decay:T,cannibalization:ne,strikingDistance:re,quickWins:E}}function xO(e){let t={id:v(),site:e.site,name:e.name,dimension:e.dimension??`page`,matchType:e.matchType??`contains`,pattern:e.pattern,createdAt:new Date().toISOString()};return j().prepare(`INSERT INTO content_groups
573
573
  (id, site_url, name, dimension, match_type, pattern, created_at)
574
- VALUES (?, ?, ?, ?, ?, ?, ?)`).run(t.id,t.site,t.name,t.dimension,t.matchType,t.pattern,Date.parse(t.createdAt)),t}function rO(e){let t=e?`SELECT * FROM content_groups WHERE site_url = ? ORDER BY created_at DESC`:`SELECT * FROM content_groups ORDER BY created_at DESC`;return(e?j().prepare(t).all(e):j().prepare(t).all()).map(_D)}function iO(e){let t=j().prepare(`SELECT * FROM content_groups WHERE id = ?`).get(e);return t?_D(t):void 0}function aO(e){return j().prepare(`DELETE FROM content_groups WHERE id = ?`).run(e).changes>0}function oO(e,t){return t===0?e===0?0:null:Number(((e-t)/t*100).toFixed(2))}function sO(e,t=3){return Number(e.toFixed(t))}function cO(e){let t=e.reduce((e,t)=>e+t.clicks,0),n=e.reduce((e,t)=>e+t.impressions,0),r=n>0?t/n:0,i=n>0?e.reduce((e,t)=>e+t.position*t.impressions,0)/n:0;return{clicks:sO(t),impressions:sO(n),ctr:sO(r,4),position:sO(i)}}function lO(e){try{let t=new URL(e);return`${t.pathname}${t.search}`}catch{return e}}function uO(e){return e===`regex`?`PARTIAL_REGEXP`:e===`equals`?`EXACT`:`CONTAINS`}function dO(e,t){if(e.scope!==`site`&&e.scope!==`query`)return e.scope===`group`?t?.dimension===`page`?{filter:{fieldName:`landingPagePlusQueryString`,stringFilter:{matchType:uO(t.matchType),value:lO(t.pattern),caseSensitive:!1}}}:void 0:{filter:{fieldName:`landingPagePlusQueryString`,stringFilter:{matchType:`EXACT`,value:lO(e.target),caseSensitive:!1}}}}function fO(e){return e.reduce((e,t)=>({sessions:sO(e.sessions+(Number(t.sessions)||0)),engagedSessions:sO(e.engagedSessions+(Number(t.engagedSessions)||0)),conversions:sO(e.conversions+(Number(t.conversions)||0)),totalRevenue:sO(e.totalRevenue+(Number(t.totalRevenue)||0))}),{sessions:0,engagedSessions:0,conversions:0,totalRevenue:0})}function pO(e){return{sessions:sO(e.after.sessions-e.before.sessions),sessionPct:oO(e.after.sessions,e.before.sessions),engagedSessions:sO(e.after.engagedSessions-e.before.engagedSessions),engagedSessionPct:oO(e.after.engagedSessions,e.before.engagedSessions),conversions:sO(e.after.conversions-e.before.conversions),conversionPct:oO(e.after.conversions,e.before.conversions),totalRevenue:sO(e.after.totalRevenue-e.before.totalRevenue),revenuePct:oO(e.after.totalRevenue,e.before.totalRevenue)}}function mO(e){if(e.before.impressions+e.after.impressions<100)return{verdict:`not-enough-data`,confidence:`low`,note:`The comparison windows have fewer than 100 impressions total.`};let t=e.positionDelta<-.5,n=e.positionDelta>.5;if(e.clickPct===null&&e.before.clicks===0&&e.after.clicks>0&&e.after.ctr>e.before.ctr)return{verdict:`positive`,confidence:`low`,note:`Clicks and CTR increased from a zero-click baseline. Treat the direction as early evidence because percentage growth is undefined.`};let r=e.clickPct??0,i=e.clickDelta>0&&(r>=10||t),a=e.clickDelta<0&&(r<=-10||n),o=Math.abs(e.clickDelta)>=50&&Math.abs(r)>=30?`high`:Math.abs(e.clickDelta)>=10&&Math.abs(r)>=10?`medium`:`low`;return i?{verdict:`positive`,confidence:o,note:`Clicks improved after the change. Confirm with segment breakdown before rolling out widely.`}:a?{verdict:`negative`,confidence:o,note:`Clicks declined after the change. Check query mix, ranking movement, and indexability before reverting.`}:t||n?{verdict:`mixed`,confidence:o,note:`Ranking movement and click movement disagree. Inspect SERP layout, CTR, and query demand.`}:{verdict:`flat`,confidence:o,note:`No material movement detected in this window.`}}async function hO(e){let t=await e.ga4Report(e.propertyId,{dateRanges:[e.window],dimensions:[{name:`date`}],metrics:[{name:`sessions`},{name:`engagedSessions`},{name:`conversions`},{name:`totalRevenue`}],...e.filter?{dimensionFilter:e.filter}:{},limit:1e4},{refresh:e.refresh}),n=hn(t);return{metrics:fO(n),source:{rows:n.length,rowCount:t.rowCount??n.length,timeZone:t.metadata?.timeZone,currencyCode:t.metadata?.currencyCode},warnings:gn(t,e.label)}}async function gO(e){let[t,n]=await Promise.all([hO({...e,window:e.before,label:`GA4 before window`}),hO({...e,window:e.after,label:`GA4 after window`})]),r=[];return t.source.timeZone&&n.source.timeZone&&t.source.timeZone!==n.source.timeZone&&r.push(`GA4 returned different property timezones across the two windows.`),t.source.currencyCode&&n.source.currencyCode&&t.source.currencyCode!==n.source.currencyCode&&r.push(`GA4 returned different currency codes across the two windows.`),{report:{propertyId:e.propertyId,before:{...e.before,metrics:t.metrics},after:{...e.after,metrics:n.metrics},delta:pO({before:t.metrics,after:n.metrics}),note:`GA4 attribution is landing-page based. Query-level tests use GSC only.`},source:{before:t.source,after:n.source},warnings:[...new Set([...t.warnings,...n.warnings,...r])]}}function _O(e){return e===`regex`?`includingRegex`:e===`contains`?`contains`:`equals`}function vO(e,t){if(e.scope===`site`)return;let n=e.scope===`group`?t?.dimension:e.scope,r=e.scope===`group`?t?.pattern:e.target;if(!(!n||!r))return[{groupType:`and`,filters:[{dimension:n,operator:e.scope===`group`&&t?_O(t.matchType):`equals`,expression:r}]}]}function yO(e,t){let n=e.keys[0];if(!n)return!1;let r=new Date(`${n}T00:00:00.000Z`);return e.keys.length===1&&/^\d{4}-\d{2}-\d{2}$/.test(n)&&!Number.isNaN(r.getTime())&&r.toISOString().slice(0,10)===n&&n>=t.startDate&&n<=t.endDate&&[e.clicks,e.impressions,e.ctr,e.position].every(Number.isFinite)&&e.clicks>=0&&e.impressions>=0&&e.clicks<=e.impressions&&e.ctr>=0&&e.ctr<=1&&(e.impressions===0?e.position>=0:e.position>=1)}function bO(e,t){let n=new Map,r=new Set,i=0,a=0;for(let o of e){if(!yO(o,t)){i+=1;continue}let e=o.keys[0]??``;if(r.has(e)){i+=1;continue}let s=n.get(e);if(!s){n.set(e,o);continue}if(s.clicks===o.clicks&&s.impressions===o.impressions&&s.ctr===o.ctr&&s.position===o.position){a+=1;continue}n.delete(e),r.add(e),i+=2}return{rows:[...n.values()].sort((e,t)=>{let n=e.keys[0]??``,r=t.keys[0]??``;return n<r?-1:+(n>r)}),invalidRows:i,duplicateRows:a}}async function xO(e){let t=await e.searchAnalytics(e.site,{...e.window,dimensions:[`date`],type:`web`,dataState:`final`,dimensionFilterGroups:e.filters},{refresh:e.refresh}),n=bO(t.rows,e.window);return{metrics:cO(n.rows),source:{calls:t.calls,rowsFetched:t.rowsFetched,returnedRows:n.rows.length,invalidRows:n.invalidRows,duplicateRows:n.duplicateRows}}}function SO(e){return{clicks:sO(e.after.clicks-e.before.clicks),clickPct:oO(e.after.clicks,e.before.clicks),impressions:sO(e.after.impressions-e.before.impressions),impressionPct:oO(e.after.impressions,e.before.impressions),ctr:sO(e.after.ctr-e.before.ctr,4),position:e.before.impressions>0&&e.after.impressions>0?sO(e.after.position-e.before.position):null}}async function CO(e){let t=vO(e.change,e.group),n=e.change.scope===`query`||e.group?.dimension===`query`,[r,i]=await Promise.all([xO({site:e.change.site,window:e.before,filters:t,refresh:e.refresh,searchAnalytics:e.searchAnalytics}),xO({site:e.change.site,window:e.after,filters:t,refresh:e.refresh,searchAnalytics:e.searchAnalytics})]),a=[],o=r.source.invalidRows+i.source.invalidRows;o&&a.push(`Excluded ${o} invalid or conflicting Search Console date rows.`),n&&a.push(`Query-scoped Search Console evidence contains retained query data and can omit anonymized queries.`);let s=r.source.rowsFetched>0&&r.source.returnedRows===0&&r.source.invalidRows>0,c=i.source.rowsFetched>0&&i.source.returnedRows===0&&i.source.invalidRows>0,l=!s&&!c&&(!n||r.source.returnedRows>0&&i.source.returnedRows>0);l||a.push(n?`A query-scoped window returned no retained rows; absence was not converted into zero movement.`:`A Search Console window contained provider rows but none were valid; the window was not converted into zero movement.`);let u=s||n&&r.source.returnedRows===0?null:r.metrics,d=c||n&&i.source.returnedRows===0?null:i.metrics;return{before:{...e.before,metrics:u},after:{...e.after,metrics:d},delta:u&&d?SO({before:u,after:d}):{clicks:null,clickPct:null,impressions:null,impressionPct:null,ctr:null,position:null},status:n||o>0?`partial`:`complete`,completeness:n?`retained-query-date-aggregates`:`date-aggregates`,comparable:l,warnings:a,source:{before:r.source,after:i.source}}}function wO(e,t){let n=new Date(`${e}T00:00:00.000Z`);return n.setUTCDate(n.getUTCDate()+t),n.toISOString().slice(0,10)}function TO(e,t){let n=/^\d{4}-\d{2}-\d{2}$/,r=new Date(`${e}T00:00:00.000Z`);if(!n.test(e)||Number.isNaN(r.getTime())||r.toISOString().slice(0,10)!==e)throw new A(`INVALID_INPUT`,`${t} must be a valid YYYY-MM-DD date.`);return e}function EO(e,t,n){let r=e??t;if(!Number.isInteger(r)||r<1||r>548)throw new A(`INVALID_INPUT`,`${n} must be a whole number between 1 and 548.`);return r}function DO(e,t){let n=new Date(`${e}T00:00:00.000Z`).getTime(),r=new Date(`${t}T00:00:00.000Z`).getTime();return Math.floor((r-n)/864e5)+1}function OO(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 kO(e){let t=TO(e.changedAt,`changedAt`),n=EO(e.beforeDays,28,`beforeDays`),r=EO(e.afterDays,n,`afterDays`);if(n!==r)throw new A(`INVALID_INPUT`,`beforeDays and afterDays must match so raw totals use equal calendar windows.`);let i=G_(1,e.now).endDate,a=OO(wO(i,4),16);if(t>i)throw new A(`INSUFFICIENT_DATA`,`changedAt must be on or before the latest finalized GSC date, ${i}.`);let o=wO(t,r-1),s=o>i?i:o,c=DO(t,s),l=wO(t,-1),u=wO(l,-(c-1));if(u<a)throw new A(`INVALID_INPUT`,`The equal before window starts before ${a}, the current 16-month GSC retention boundary.`);return{before:{startDate:u,endDate:l},after:{startDate:t,endDate:s},requestedDays:n,effectiveDays:c,afterWindowTruncated:c<r,availableDateWindow:{earliestDate:a,latestFinalDate:i}}}const AO={searchAnalytics:e_,ga4Report:dn,contentGroup:iO,now:()=>new Date};function jO(e,t){if(e.scope!==`group`)return;let n=t(e.target);if(!n)throw new A(`INVALID_INPUT`,`Content group ${e.target} was not found.`);if(n.site!==e.site)throw new A(`INVALID_INPUT`,`Content group ${e.target} belongs to ${n.site}, not ${e.site}.`);return n}function MO(e){if(e.controlBefore<=0)return null;let t=e.treatmentBefore*(e.controlAfter/e.controlBefore);return sO(e.treatmentAfter-t)}async function NO(e,t=AO){let n=t.now(),r=(e.id?xD(e.id):void 0)??{id:`adhoc`,site:e.site,scope:e.scope,target:e.target,title:e.title??`Ad hoc change measurement`,changedAt:e.changedAt,createdAt:n.toISOString()};if(!r.site?.trim()||!r.scope||!r.target?.trim()||!r.changedAt)throw new A(`INVALID_INPUT`,`Pass a change id, or provide site, scope, target, and changedAt.`);if(!!e.controlScope!=!!e.controlTarget?.trim())throw new A(`INVALID_INPUT`,`controlScope and controlTarget must be provided together.`);let i=kO({changedAt:r.changedAt,beforeDays:e.beforeDays,afterDays:e.afterDays,now:n}),{before:a,after:o}=i,s=jO(r,t.contentGroup),c=e.controlScope&&e.controlTarget?{id:`control`,site:r.site,scope:e.controlScope,target:e.controlTarget,title:e.controlTitle??`Control group`,changedAt:r.changedAt,createdAt:n.toISOString()}:void 0,l=c?jO(c,t.contentGroup):void 0,u=await CO({change:r,before:a,after:o,group:s,refresh:e.refresh,searchAnalytics:t.searchAnalytics}),d=c?await CO({change:c,before:a,after:o,group:l,refresh:e.refresh,searchAnalytics:t.searchAnalytics}):void 0,f=u.delta.clickPct,p=i.effectiveDays<7,m=i.afterWindowTruncated?`Only ${i.effectiveDays} of ${i.requestedDays} requested finalized after-days are available.`:u.comparable?p?`Only ${i.effectiveDays} finalized day${i.effectiveDays===1?``:`s`} are available in each window.`:void 0:`A Search Console window has no comparable metrics.`,h=u.before.metrics&&u.after.metrics&&u.delta.clicks!==null&&u.delta.position!==null?mO({before:u.before.metrics,after:u.after.metrics,clickPct:f,clickDelta:u.delta.clicks,positionDelta:u.delta.position}):{verdict:`not-enough-data`,confidence:`low`,note:`Comparable Search Console metrics are unavailable.`},g=m?{verdict:`not-enough-data`,confidence:`low`,note:`${m} The equal-window metrics are provisional and no directional verdict was assigned.`}:u.status===`partial`&&h.confidence===`high`?{...h,confidence:`medium`,note:`${h.note} Confidence is capped because Search Console evidence is partial.`}:h,_=dO(r,s),v=r.scope===`query`||s?.dimension===`query`,y=e.ga4PropertyId&&!v?await gO({propertyId:e.ga4PropertyId,before:a,after:o,filter:_,refresh:e.refresh,ga4Report:t.ga4Report}):void 0,b=[...u.warnings,...d?.warnings.map(e=>`Control: ${e}`)??[],...y?.warnings??[]];return{schemaVersion:1,methodology:`equal-finalized-calendar-windows-v1`,dataStatus:i.afterWindowTruncated||u.status===`partial`||d?.status===`partial`||y?.warnings.length?`partial`:`complete`,change:r,window:{requestedDays:i.requestedDays,effectiveDays:i.effectiveDays,afterWindowTruncated:i.afterWindowTruncated,gscTimezone:`America/Los_Angeles`,availableDateWindow:i.availableDateWindow},source:{searchAnalytics:{status:u.status,completeness:u.completeness,dimensions:[`date`],searchType:`web`,dataState:`final`,before:u.source.before,after:u.source.after,...d?{control:{status:d.status,completeness:d.completeness,before:d.source.before,after:d.source.after}}:{},warnings:[...u.warnings,...d?.warnings.map(e=>`Control: ${e}`)??[]]},...y?{analytics:{status:y.warnings.length?`partial`:`complete`,before:y.source.before,after:y.source.after,warnings:y.warnings}}:{}},before:u.before,after:u.after,delta:u.delta,...y?{analytics:y.report}:{},...d&&c?{control:{change:c,before:d.before,after:d.after,delta:d.delta,adjusted:{methodology:`control-ratio-counterfactual-v1`,clickDelta:d.comparable&&u.before.metrics&&u.after.metrics&&d.before.metrics&&d.after.metrics?MO({treatmentBefore:u.before.metrics.clicks,treatmentAfter:u.after.metrics.clicks,controlBefore:d.before.metrics.clicks,controlAfter:d.after.metrics.clicks}):null,clickPctPoints:u.delta.clickPct===null||d.delta.clickPct===null?null:sO(u.delta.clickPct-d.delta.clickPct),impressionDelta:d.comparable&&u.before.metrics&&u.after.metrics&&d.before.metrics&&d.after.metrics?MO({treatmentBefore:u.before.metrics.impressions,treatmentAfter:u.after.metrics.impressions,controlBefore:d.before.metrics.impressions,controlAfter:d.after.metrics.impressions}):null,impressionPctPoints:u.delta.impressionPct===null||d.delta.impressionPct===null?null:sO(u.delta.impressionPct-d.delta.impressionPct)},note:`Adjusted deltas compare the observed treatment result with a control-ratio counterfactual. Controls reduce some shared noise but do not prove causality.`}}:{},...g,warnings:b,caveats:[`Compared ${i.effectiveDays} finalized Search Console calendar days before and after the change in America/Los_Angeles time.`,`Search Console position is impression-weighted average position; before/after timing and controls do not prove causality.`,...i.afterWindowTruncated?[`The requested ${i.requestedDays}-day after window is incomplete, so this run is partial and has no directional verdict.`]:[],...p?[`Fewer than 7 finalized days per window is too short for a directional verdict.`]:[],...u.completeness===`retained-query-date-aggregates`?[`Query-scoped Search Console data can omit anonymized queries; an absent retained row is not zero traffic.`]:[],...y?[`GA4 attribution uses landing pages and the GA4 property timezone${y.source.before.timeZone?` (${y.source.before.timeZone})`:``}; its day boundaries may differ from Search Console Pacific dates.`]:[]]}}const PO=new Set(`a.an.and.are.at.best.by.for.free.from.guide.how.in.is.of.on.online.or.page.seo.site.the.to.tool.use.website.what.when.where.which.with`.split(`.`));function FO(e,t){return e<t?-1:+(e>t)}function IO(e){return e.normalize(`NFKC`).replace(/[’']/g,`'`).toLowerCase().replace(/[^\p{L}\p{N}']+/gu,` `).replace(/\s+/g,` `).trim()}function LO(e){try{let t=new URL(e);return t.protocol!==`http:`&&t.protocol!==`https:`?void 0:(t.hash=``,t.toString())}catch{return}}function RO(e){let[t,n]=e.keys;return!!(IO(t??``)&&LO(n??``)&&Number.isFinite(e.clicks)&&e.clicks>=0&&Number.isFinite(e.impressions)&&e.impressions>0&&e.clicks<=e.impressions&&Number.isFinite(e.ctr)&&e.ctr>=0&&e.ctr<=1&&Number.isFinite(e.position)&&e.position>0)}function zO(e){let t=IO(e),n=typeof Intl<`u`&&`Segmenter`in Intl?[...new Intl.Segmenter(`und`,{granularity:`word`}).segment(t)].filter(e=>e.isWordLike).map(e=>e.segment):t.match(/[\p{L}\p{N}]+/gu)??[];return[...new Set(n.filter(e=>!PO.has(e)&&!/^\p{N}+$/u.test(e)&&([...e].length>1||/[^\p{ASCII}]/u.test(e))))]}function BO(e){let t=new Map,n=e=>{let n=IO(e),r=t.get(n);if(r)return r;let i=zO(e);return t.set(n,i),i},r=[...new Set(e.map(IO).filter(Boolean))].map(n),i=new Map;for(let e of r)for(let t of new Set(e))i.set(t,(i.get(t)??0)+1);let a=Math.max(1,r.length),o=Math.max(3,Math.ceil(a*.1)),s=e=>Math.log((a+1)/((i.get(e)??0)+1))+1,c=e=>e.filter(e=>(i.get(e)??0)<=o);return(e,t)=>{let r=c(n(e)),i=c(n(t));if(r.length<2||i.length<2)return;let a=new Set(i),o=r.filter(e=>a.has(e)).sort(FO);if(o.length<2)return;let l=new Set([...r,...i]),u=o.reduce((e,t)=>e+s(t),0),d=i.reduce((e,t)=>e+s(t),0),f=r.reduce((e,t)=>e+s(t),0),p=[...l].reduce((e,t)=>e+s(t),0),m=u/d,h=u/f,g=u/p,_=g*.5+m*.25+h*.25;if(!(m<2/3||h<2/3||g<.6||_<.72))return{score:Number(_.toFixed(4)),weightedJaccard:Number(g.toFixed(4)),targetCoverage:Number(m.toFixed(4)),sourceCoverage:Number(h.toFixed(4)),sharedTerms:o}}}function VO(e){if(e.impressions<e.minImpressions)return`low-impressions`;if(Rv(e.query))return`low-actionability`;if(M_({query:e.query,siteUrl:e.site,brandTerms:e.brandTerms,includeBrand:e.includeBrand}))return`brand`}function HO(e,t,n,r){let i=e.get(t)??{query:n,clicks:0,impressions:0};i.clicks+=r.clicks,i.impressions+=r.impressions,FO(n,i.query)<0&&(i.query=n),e.set(t,i)}function UO(e){let t=IO(e.source.query),n=e.exactTargets.get(t);return n?{sourceQuery:e.source.query,targetQuery:n.query,kind:`exact-query`,relevanceScore:1,impressions:e.source.impressions,sharedTerms:zO(e.source.query)}:e.lexicalTargets.flatMap(t=>{let n=e.scoreLexical(e.source.query,t.query);return n?[{sourceQuery:e.source.query,targetQuery:t.query,kind:`lexical-review`,relevanceScore:n.score,impressions:e.source.impressions,sharedTerms:n.sharedTerms}]:[]}).sort((e,t)=>t.relevanceScore-e.relevanceScore||FO(e.targetQuery,t.targetQuery))[0]}function WO(e){return[...e.entries()].map(([e,t])=>{let n=[...t].sort((e,t)=>(e.kind===t.kind?0:e.kind===`exact-query`?-1:1)||t.impressions-e.impressions||t.relevanceScore-e.relevanceScore||FO(e.sourceQuery,t.sourceQuery));return{sourceUrl:e,matchedQueryImpressions:n.reduce((e,t)=>e+t.impressions,0),matchedQueries:n.length,exactQueryMatches:n.filter(e=>e.kind===`exact-query`).length,bestRelevanceScore:Math.max(...n.map(e=>e.relevanceScore)),bestMatchKind:n.some(e=>e.kind===`exact-query`)?`exact-query`:`lexical-review`,matches:n.slice(0,10)}}).sort((e,t)=>t.exactQueryMatches-e.exactQueryMatches||t.matchedQueryImpressions-e.matchedQueryImpressions||t.bestRelevanceScore-e.bestRelevanceScore||FO(e.sourceUrl,t.sourceUrl))}function GO(e){let t=new Set(e.targetAliases.map(LO).filter(e=>!!e)),n=new Map,r=e=>{n.set(e,(n.get(e)??0)+1)},i=new Map,a=0;for(let n of e.targetRows){if(!RO(n)){r(`targetInvalidRows`);continue}a+=1;let e=n.keys[0]??``,o=LO(n.keys[1]??``);if(!o||!t.has(o)){r(`targetUrlMismatchRows`);continue}HO(i,IO(e),e,n)}let o=[...i.values()].filter(t=>{let n=VO({...t,site:e.site,minImpressions:e.minImpressions,brandTerms:e.brandTerms,includeBrand:e.includeBrand});return n===`low-impressions`&&r(`targetLowImpressionQueries`),n===`low-actionability`&&r(`targetLowActionabilityQueries`),n===`brand`&&r(`targetBrandQueries`),!n}).sort((e,t)=>t.impressions-e.impressions||t.clicks-e.clicks||FO(e.query,t.query)),s=o.slice(0,50),c=new Map(o.map(e=>[IO(e.query),e])),l=new Map,u=0;for(let n of e.sourceRows){if(!RO(n)){r(`sourceInvalidRows`);continue}u+=1;let e=n.keys[0]??``,i=LO(n.keys[1]??``);if(!i)continue;if(t.has(i)){r(`sourceTargetAliasRows`);continue}let a=`${i}\u0000${IO(e)}`,o=l.get(a)??{page:i,query:e,clicks:0,impressions:0};o.clicks+=n.clicks,o.impressions+=n.impressions,FO(e,o.query)<0&&(o.query=e),l.set(a,o)}let d=[...l.values()].filter(t=>{let n=VO({...t,site:e.site,minImpressions:e.minImpressions,brandTerms:e.brandTerms,includeBrand:e.includeBrand});return n===`low-impressions`&&r(`sourceLowImpressionQueries`),n===`low-actionability`&&r(`sourceLowActionabilityQueries`),n===`brand`&&r(`sourceBrandQueries`),!n}),f=BO([...o.map(e=>e.query),...d.map(e=>e.query)]),p=new Map,m=0;for(let e of d){let t=UO({source:e,exactTargets:c,lexicalTargets:s,scoreLexical:f});if(!t){r(`sourceUnmatchedQueries`);continue}m+=1;let n=p.get(e.page)??[];n.push(t),p.set(e.page,n)}let h=WO(p),g=e=>n.get(e)??0;return{targetQueries:s,candidates:h,selection:{targetSourceRows:e.targetRows.length,targetValidRows:a,targetInvalidRows:g(`targetInvalidRows`),targetUrlMismatchRows:g(`targetUrlMismatchRows`),targetLowImpressionQueries:g(`targetLowImpressionQueries`),targetLowActionabilityQueries:g(`targetLowActionabilityQueries`),targetBrandQueries:g(`targetBrandQueries`),targetEligibleQueries:o.length,selectedLexicalTargetQueries:s.length,sourceRows:e.sourceRows.length,sourceValidRows:u,sourceInvalidRows:g(`sourceInvalidRows`),sourceTargetAliasRows:g(`sourceTargetAliasRows`),sourceLowImpressionQueries:g(`sourceLowImpressionQueries`),sourceLowActionabilityQueries:g(`sourceLowActionabilityQueries`),sourceBrandQueries:g(`sourceBrandQueries`),sourceUnmatchedQueries:g(`sourceUnmatchedQueries`),candidateQueries:m,candidateUrls:h.length}}}function KO(e){let t=e.value??e.fallback;if(!Number.isInteger(t)||t<e.minimum||t>e.maximum)throw new A(`INVALID_INPUT`,`${e.label} must be a whole number between ${e.minimum} and ${e.maximum}.`);return t}function qO(e){let t=e.value??e.fallback;if(!Number.isFinite(t)||t<e.minimum||t>e.maximum)throw new A(`INVALID_INPUT`,`${e.label} must be between ${e.minimum} and ${e.maximum}.`);return t}function JO(e,t){return Zg(e,t)}function YO(e,t){return{...e.selection,...t}}function XO(e){return e.dataStatus===`target-technical-issue`?`The target has a technical issue. Resolve it before adding internal links.`:e.dataStatus===`empty`?`Search Console returned no retained query/page rows for the target URL aliases in this window.`:e.dataStatus===`source-empty`?`Search Console returned no retained source query/page rows in this window. This does not prove that no relevant source page exists.`:e.dataStatus===`filtered`?`Target demand exists, but no precision-matched source candidates passed the report filters.`:e.returned?`${e.returned} internal-link review candidate${e.returned===1?``:`s`} remained after ${e.attempted} verification attempt${e.attempted===1?``:`s`}; ${e.checked} of ${e.candidates} matched source pages were successfully checked.`:e.dataStatus===`partial`?e.candidates===0&&e.attempted===0?`Internal-link candidates could not be evaluated because required target, provider, or row evidence was incomplete.`:`No missing contextual link was confirmed among ${e.checked} successfully checked source page${e.checked===1?``:`s`}; evidence remains incomplete with ${e.failed} failed check${e.failed===1?``:`s`} and ${e.unchecked} unattempted candidate${e.unchecked===1?``:`s`}.`:`No missing contextual link was confirmed among ${e.checked} successfully checked source page${e.checked===1?``:`s`}.`}function ZO(e){return[...new Map(e.map(e=>[`${e.stage}\u0000${e.url}\u0000${e.code}\u0000${e.message}`,e])).values()]}function QO(e){return e instanceof Error?e.message:String(e)}function $O(e,t){if(t)try{return Ng(e,t)}catch{return}}function ek(e){let t=new URL(e);return t.hash=``,t.toString()}function tk(e,t){try{return ek(e)===ek(t)}catch{return!1}}function nk(e){if(e.canonical)try{return new URL(e.canonical,e.finalUrl).toString()}catch{return}}async function rk(e){let t;try{t=await e.dependencies.fetch(e.targetUrl,{js:e.js??`auto`,refresh:e.refresh,rate:e.rate})}catch(t){return{requestedUrl:e.targetUrl,preferredUrl:e.targetUrl,aliases:[e.targetUrl],verification:`failed`,technicalSignals:[],warnings:[{stage:`target-fetch`,url:e.targetUrl,code:`fetch-failed`,message:QO(t)}]}}let n;try{n=await e.dependencies.extract(t)}catch(n){return{requestedUrl:e.targetUrl,preferredUrl:e.targetUrl,finalUrl:t.finalUrl,status:t.status,aliases:[e.targetUrl],verification:`failed`,technicalSignals:[],fetchDiagnostics:t.diagnostics,warnings:[{stage:`target-extract`,url:e.targetUrl,code:`extract-failed`,message:QO(n)}]}}let r=nk(n),i=$O(e.site,n.finalUrl),a=$O(e.site,r),o=[e.targetUrl,i,a].filter(e=>!!e).map(ek),s=[...new Set(o)],c=d_({url:e.targetUrl,page:n,fetchDiagnostics:t.diagnostics,httpStatus:t.status}),l=c.filter(e=>e!==`redirected`||!i),u=n.warnings.map(t=>({stage:`target-extract`,url:e.targetUrl,code:`extractor-warning`,message:t}));return{requestedUrl:e.targetUrl,preferredUrl:i??e.targetUrl,finalUrl:n.finalUrl,canonical:r,status:t.status,aliases:s,verification:l.length?`technical-issue`:`verified`,technicalSignals:c,fetchDiagnostics:t.diagnostics,warnings:u}}function ik(e,t){return e.links.filter(e=>t.some(t=>tk(e.href,t)))}function ak(e){return{score:Math.round(e.exactQueryMatches*100+Math.log10(e.matchedQueryImpressions+1)*10+e.bestRelevanceScore*10),heuristic:!0,components:{exactQueryMatches:e.exactQueryMatches,matchedQueryImpressions:e.matchedQueryImpressions,relevanceScore:e.bestRelevanceScore}}}async function ok(e){try{Ng(e.site,e.candidate.sourceUrl)}catch(t){return{exclusion:`failed`,warnings:[{stage:`source-fetch`,url:e.candidate.sourceUrl,code:`fetch-failed`,message:QO(t)}]}}let t;try{t=await e.dependencies.fetch(e.candidate.sourceUrl,{js:e.js??`auto`,refresh:e.refresh,rate:e.rate})}catch(t){return{exclusion:`failed`,warnings:[{stage:`source-fetch`,url:e.candidate.sourceUrl,code:`fetch-failed`,message:QO(t)}]}}let n;try{n=await e.dependencies.extract(t)}catch(t){return{exclusion:`failed`,warnings:[{stage:`source-extract`,url:e.candidate.sourceUrl,code:`extract-failed`,message:QO(t)}]}}let r=n.warnings.map(t=>({stage:`source-extract`,url:e.candidate.sourceUrl,code:`extractor-warning`,message:t}));if(e.target.aliases.some(e=>tk(n.finalUrl,e)))return{exclusion:`self-alias`,warnings:r};let i=d_({url:e.candidate.sourceUrl,page:n,fetchDiagnostics:t.diagnostics,httpStatus:t.status});if(i.length)return{exclusion:`technical`,warnings:r};let a=ik(n,e.target.aliases).map(e=>({href:e.href,text:e.text,rel:e.rel,location:e.location})),o=a.filter(e=>e.location===`main-content`);if(o.some(t=>tk(t.href,e.target.preferredUrl)))return{exclusion:`existing-link`,warnings:r};let s=o.length>0,c=s?`alias-contextual`:a.length?`non-contextual-only`:`missing`,l=e.candidate.bestMatchKind===`exact-query`&&!n.contentExtraction.fallback?`medium`:`low`,u=s?`review-alias-link`:`review-contextual-link`,d=s?`A main-content link points to a non-preferred target alias; the preferred target is ${e.target.preferredUrl}.`:`${e.candidate.sourceUrl} matched ${e.candidate.matchedQueries} retained GSC query row${e.candidate.matchedQueries===1?``:`s`}, and no main-content link to the preferred target was observed in the fetched HTML.`,f=s?`Review the existing contextual link and update it to the preferred target ${e.target.preferredUrl} if the redirect or old alias is not intentional.`:`Review whether this source page and ${e.target.preferredUrl} serve complementary intent. If the link would help a reader, add a natural main-content link without forcing exact-match anchor text.`;return{item:{...e.candidate,finalUrl:n.finalUrl,status:t.status,technicalSignals:i,fetchDiagnostics:t.diagnostics,pageWarnings:n.warnings,actionType:u,linkEvidence:{status:c,observedCount:a.length,observedLimit:20,limitedCount:Math.max(0,a.length-20),observed:a.slice(0,20)},confidence:l,priority:ak(e.candidate),recommendation:{principle:`C.6`,evidenceRef:d,action:f,effort:`S`,confidence:l}},warnings:r}}const sk=1e5,ck={searchAnalytics:e_,fetch:Ag,extract:Th,now:()=>new Date};async function lk(e,t=ck){let n=new URL(Ng(e.site,e.targetUrl));n.hash=``;let r=n.toString(),i=KO({value:e.days,fallback:28,minimum:1,maximum:548,label:`days`}),a=KO({value:e.limit,fallback:20,minimum:1,maximum:100,label:`limit`}),o=KO({value:e.checkLimit,fallback:Math.max(a,40),minimum:1,maximum:200,label:`checkLimit`}),s=qO({value:e.minImpressions,fallback:1,minimum:0,maximum:1e9,label:`minImpressions`}),c=t.now(),l=JO(i,c),u=new OT,d=await rk({site:e.site,targetUrl:r,js:e.js,refresh:e.refresh,rate:e.rate,dependencies:t}),f=await Promise.all(d.aliases.map(async n=>{let r=await t.searchAnalytics(e.site,{...l,dimensions:[`query`,`page`],type:`web`,dataState:`final`,maxRows:sk,dimensionFilterGroups:[{groupType:`and`,filters:[{dimension:`page`,operator:`equals`,expression:n}]}]},{refresh:e.refresh});return u.addGsc(r.calls,r.rowsFetched),{alias:n,result:r}})),p=f.flatMap(({result:e})=>e.rows),m=GO({targetRows:p,sourceRows:[],site:e.site,targetAliases:d.aliases,minImpressions:s,brandTerms:e.brandTerms,includeBrand:e.includeBrand}),h=d.verification===`verified`&&m.selection.targetEligibleQueries>0,g=h?await t.searchAnalytics(e.site,{...l,dimensions:[`query`,`page`],type:`web`,dataState:`final`,maxRows:sk},{refresh:e.refresh}):{rows:[],calls:0,rowsFetched:0};u.addGsc(g.calls,g.rowsFetched);let _=GO({targetRows:p,sourceRows:g.rows,site:e.site,targetAliases:d.aliases,minImpressions:s,brandTerms:e.brandTerms,includeBrand:e.includeBrand}),v=[],y=[...d.warnings],b=0,x=0,ee=0,te=0,S=0,C=0;for(let n of _.candidates){if(b>=o||v.length>=a)break;b+=1;let r=await ok({site:e.site,candidate:n,target:d,js:e.js,refresh:e.refresh,rate:e.rate,dependencies:t});y.push(...r.warnings),r.item&&v.push(r.item),r.exclusion===`existing-link`&&(ee+=1),r.exclusion===`technical`&&(te+=1),r.exclusion===`self-alias`&&(S+=1),r.exclusion===`failed`?C+=1:x+=1}let w=Math.max(0,_.candidates.length-b),T=f.some(({result:e})=>e.rowsFetched>=sk),ne=g.rowsFetched>=sk,re=_.selection.targetInvalidRows+_.selection.sourceInvalidRows,E=ZO(y),ie=d.verification===`technical-issue`?`target-technical-issue`:d.verification===`failed`?`partial`:p.length===0?`empty`:T||ne||re>0?`partial`:h&&g.rows.length===0?`source-empty`:_.selection.targetEligibleQueries===0||_.candidates.length===0?`filtered`:C>0||w>0?`partial`:`complete`,ae=YO(_,{attemptedSources:b,checkedSources:x,returnedSources:v.length,existingLinkExclusions:ee,technicalExclusions:te,selfAliasExclusions:S,failedChecks:C,uncheckedCandidates:w}),D=XO({dataStatus:ie,returned:v.length,attempted:b,checked:x,candidates:_.candidates.length,failed:C,unchecked:w});return{site:e.site,targetUrl:r,generatedAt:c.toISOString(),range:l,rangeDays:i,dataStatus:ie,source:{provider:`google-search-console`,dimensions:[`query`,`page`],searchType:`web`,dataState:`final`,target:{pageFilters:f.map(({alias:e})=>e),requests:f.map(({alias:e,result:t})=>({pageFilter:e,rowsFetched:t.rowsFetched,calls:t.calls,possiblyTruncated:t.rowsFetched>=sk})),rowsFetched:f.reduce((e,{result:t})=>e+t.rowsFetched,0),calls:f.reduce((e,{result:t})=>e+t.calls,0),maxRowsPerRequest:sk,possiblyTruncated:T},candidates:{queried:h,rowsFetched:g.rowsFetched,calls:g.calls,maxRows:sk,possiblyTruncated:ne},completeness:h?T||ne?`possibly-truncated`:`retained-rows-only`:`not-queried`},methodology:{id:`gsc_internal_link_candidates`,version:3,lexicalTargetLimit:50,matchedQueryEvidenceLimit:10,observedLinkEvidenceLimit:20,matching:`pairwise_exact_then_precision_lexical`,ranking:`exact_matches_then_matched_query_impressions_then_relevance`,contextualPlacementVerified:!0},filters:{minImpressions:s,resultLimit:a,checkLimit:o,maxGscRowsPerRequest:sk},target:{requestedUrl:d.requestedUrl,preferredUrl:d.preferredUrl,finalUrl:d.finalUrl,canonical:d.canonical,status:d.status,aliases:d.aliases,verification:d.verification,technicalSignals:d.technicalSignals,fetchDiagnostics:d.fetchDiagnostics,queries:_.targetQueries},selection:ae,summary:{targetQueries:_.selection.targetEligibleQueries,candidateSources:_.candidates.length,attemptedSources:b,checkedSources:x,returnedSources:v.length,existingLinksObserved:ee,technicalExclusions:te,failedChecks:C,uncheckedCandidates:w,matchedQueryImpressions:v.reduce((e,t)=>e+t.matchedQueryImpressions,0),brandFiltering:e.includeBrand?`included`:`excluded`,verdict:D},items:v,warnings:E,caveats:[`Date window: ${l.startDate} to ${l.endDate} (${i} days), using final GSC data where available.`,`Selection: ${_.selection.targetEligibleQueries} eligible target queries, ${_.selection.candidateUrls} matched source pages, ${b} attempted, ${x} successfully checked, ${v.length} returned.`,`Minimum retained query impressions: ${s}. Brand queries were ${e.includeBrand?`included`:`excluded when detected or configured`}.`,`GSC query rows omit anonymized queries and lower-volume rows. A 100,000-row cap is reported separately when reached.`,`Exact query overlap is medium-confidence affinity evidence; lexical matches are low-confidence review evidence. Neither predicts traffic lift or link equity.`,`The fetch checks HTML link placement, but it cannot prove editorial relevance. Review intent and reader usefulness before adding a link.`,w?`${w} matched source page${w===1?` was`:`s were`} not checked because the output or check limit was reached.`:``,C?`${C} source verification attempt${C===1?``:`s`} failed. Failed fetches or extraction attempts are not counted as checked pages and do not support a missing-link conclusion.`:``].filter(Boolean),recommendations:d.verification===`verified`?v.length?[`Review exact-query candidates first, then lexical candidates. Add or update a contextual link only when it helps a reader move between complementary intents.`]:[`No ready-to-apply link action was confirmed among the checked sources. Review completeness and unchecked counts before treating this as a site-wide absence.`]:[`Resolve the target fetch, indexability, redirect, or canonical issue before adding internal links.`],ledgerSummary:u.summary()}}function uk(e){return e.kind!==`removed`&&(e.after?.status??0)>=400&&(e.before?.status??200)<400}function dk(e){return e.kind===`changed`&&(e.before?.status??200)>=400&&(e.after?.status??0)<400}function fk(e){return e.kind===`changed`&&e.before?.indexable===!0&&e.after?.indexable===!1}function pk(e){return e.kind===`changed`&&e.changes.includes(`canonical`)&&!!e.after?.canonical&&e.after?.canonical!==e.after?.finalUrl}function mk(e){if(uk(e))return{severity:`high`,category:`status`,title:`Search-visible URL now returns an error`,action:`This URL used to be crawlable and now returns an error. Restore it if the page should still exist, or add one direct 301 to the closest live replacement before doing any content work.`,confidence:`high`};if(e.kind===`removed`)return{severity:`high`,category:`inventory`,title:`Previously crawled URL disappeared`,action:`This URL was in the previous crawl but is missing now. Confirm whether removal was intentional; if it had search traffic or links, restore it or redirect it to the best replacement.`,confidence:`medium`};if(fk(e)){let t=tm(e.after?.metaRobots,`noindex`)||nm(e.after?.xRobotsTag,`noindex`);return{severity:`high`,category:`indexability`,title:`URL became non-indexable`,action:t?`This URL now has a noindex signal. Remove it if accidental, or mark this as intentional so it does not keep appearing as a risk.`:`This URL used to be indexable and now is not. Check robots, canonical, status, and rendered HTML to find what changed.`,confidence:t?`high`:`medium`}}if(pk(e))return{severity:`medium`,category:`canonical`,title:`Canonical changed away from the final URL`,action:`Google may treat a different URL as the preferred page now. If that is not intentional, restore the self-canonical or redirect directly to the preferred URL.`,confidence:`high`};if(e.changes.includes(`title`)||e.changes.includes(`h1`))return{severity:`medium`,category:`metadata`,title:`Primary SERP/on-page targeting changed`,action:`The title or H1 changed. Compare the old and new wording against the top GSC queries for this URL before blaming traffic movement on content decay.`,confidence:`medium`};if(e.changes.includes(`meta_description`))return{severity:`low`,category:`metadata`,title:`Meta description changed`,action:`The meta description changed. Check CTR for this URL before spending time on more copy edits.`,confidence:`medium`};if(e.changes.includes(`content`))return{severity:`low`,category:`content`,title:`Main content changed`,action:`Main content changed. If traffic moved after this crawl, compare the changed section against the queries this URL ranks for.`,confidence:`medium`};if(dk(e))return{severity:`low`,category:`status`,title:`URL recovered from an error status`,action:`The URL is no longer returning an error. Keep monitoring it and run URL Inspection if it was previously deindexed.`,confidence:`medium`}}function hk(e){return e.map(e=>({...e,recommendation:mk(e)}))}function gk(e){let t={high:3,medium:2,low:1};return e.filter(e=>e.recommendation).map(e=>({...e.recommendation,url:e.url})).sort((e,n)=>t[n.severity]-t[e.severity])}function _k(e){return{id:e.id,site:e.site_url,startUrl:e.start_url,createdAt:new Date(e.created_at).toISOString(),limit:e.limit_count,urlCount:e.url_count}}function vk(e){if(!e)return{};try{let t=JSON.parse(e);return t&&typeof t==`object`?t:{}}catch{return{}}}function yk(e){return{url:e.url,finalUrl:e.final_url,status:e.status,title:e.title??void 0,metaDescription:e.meta_description??void 0,canonical:e.canonical??void 0,metaRobots:e.meta_robots??void 0,xRobotsTag:e.x_robots_tag??void 0,h1:e.h1??void 0,indexable:e.indexable===1,wordCount:e.word_count,contentHash:e.content_hash,outgoingInternalCount:e.outgoing_internal_count,...vk(e.snapshot_json),url:e.url,finalUrl:e.final_url,status:e.status,indexable:e.indexable===1,contentHash:e.content_hash,outgoingInternalCount:e.outgoing_internal_count}}function bk(e){let t=j().prepare(`SELECT * FROM crawl_runs
574
+ VALUES (?, ?, ?, ?, ?, ?, ?)`).run(t.id,t.site,t.name,t.dimension,t.matchType,t.pattern,Date.parse(t.createdAt)),t}function SO(e){let t=e?`SELECT * FROM content_groups WHERE site_url = ? ORDER BY created_at DESC`:`SELECT * FROM content_groups ORDER BY created_at DESC`;return(e?j().prepare(t).all(e):j().prepare(t).all()).map(ID)}function CO(e){let t=j().prepare(`SELECT * FROM content_groups WHERE id = ?`).get(e);return t?ID(t):void 0}function wO(e){return j().prepare(`DELETE FROM content_groups WHERE id = ?`).run(e).changes>0}function TO(e,t){return t===0?e===0?0:null:Number(((e-t)/t*100).toFixed(2))}function EO(e,t=3){return Number(e.toFixed(t))}function DO(e){let t=e.reduce((e,t)=>e+t.clicks,0),n=e.reduce((e,t)=>e+t.impressions,0),r=n>0?t/n:0,i=n>0?e.reduce((e,t)=>e+t.position*t.impressions,0)/n:0;return{clicks:EO(t),impressions:EO(n),ctr:EO(r,4),position:EO(i)}}function OO(e){try{let t=new URL(e);return`${t.pathname}${t.search}`}catch{return e}}function kO(e){return e===`regex`?`PARTIAL_REGEXP`:e===`equals`?`EXACT`:`CONTAINS`}function AO(e,t){if(e.scope!==`site`&&e.scope!==`query`)return e.scope===`group`?t?.dimension===`page`?{filter:{fieldName:`landingPagePlusQueryString`,stringFilter:{matchType:kO(t.matchType),value:OO(t.pattern),caseSensitive:!1}}}:void 0:{filter:{fieldName:`landingPagePlusQueryString`,stringFilter:{matchType:`EXACT`,value:OO(e.target),caseSensitive:!1}}}}function jO(e){return e.reduce((e,t)=>({sessions:EO(e.sessions+(Number(t.sessions)||0)),engagedSessions:EO(e.engagedSessions+(Number(t.engagedSessions)||0)),conversions:EO(e.conversions+(Number(t.conversions)||0)),totalRevenue:EO(e.totalRevenue+(Number(t.totalRevenue)||0))}),{sessions:0,engagedSessions:0,conversions:0,totalRevenue:0})}function MO(e){return{sessions:EO(e.after.sessions-e.before.sessions),sessionPct:TO(e.after.sessions,e.before.sessions),engagedSessions:EO(e.after.engagedSessions-e.before.engagedSessions),engagedSessionPct:TO(e.after.engagedSessions,e.before.engagedSessions),conversions:EO(e.after.conversions-e.before.conversions),conversionPct:TO(e.after.conversions,e.before.conversions),totalRevenue:EO(e.after.totalRevenue-e.before.totalRevenue),revenuePct:TO(e.after.totalRevenue,e.before.totalRevenue)}}function NO(e){if(e.before.impressions+e.after.impressions<100)return{verdict:`not-enough-data`,confidence:`low`,note:`The comparison windows have fewer than 100 impressions total.`};let t=e.positionDelta<-.5,n=e.positionDelta>.5;if(e.clickPct===null&&e.before.clicks===0&&e.after.clicks>0&&e.after.ctr>e.before.ctr)return{verdict:`positive`,confidence:`low`,note:`Clicks and CTR increased from a zero-click baseline. Treat the direction as early evidence because percentage growth is undefined.`};let r=e.clickPct??0,i=e.clickDelta>0&&(r>=10||t),a=e.clickDelta<0&&(r<=-10||n),o=Math.abs(e.clickDelta)>=50&&Math.abs(r)>=30?`high`:Math.abs(e.clickDelta)>=10&&Math.abs(r)>=10?`medium`:`low`;return i?{verdict:`positive`,confidence:o,note:`Clicks improved after the change. Confirm with segment breakdown before rolling out widely.`}:a?{verdict:`negative`,confidence:o,note:`Clicks declined after the change. Check query mix, ranking movement, and indexability before reverting.`}:t||n?{verdict:`mixed`,confidence:o,note:`Ranking movement and click movement disagree. Inspect SERP layout, CTR, and query demand.`}:{verdict:`flat`,confidence:o,note:`No material movement detected in this window.`}}async function PO(e){let t=await e.ga4Report(e.propertyId,{dateRanges:[e.window],dimensions:[{name:`date`}],metrics:[{name:`sessions`},{name:`engagedSessions`},{name:`conversions`},{name:`totalRevenue`}],...e.filter?{dimensionFilter:e.filter}:{},limit:1e4},{refresh:e.refresh}),n=hn(t);return{metrics:jO(n),source:{rows:n.length,rowCount:t.rowCount??n.length,timeZone:t.metadata?.timeZone,currencyCode:t.metadata?.currencyCode},warnings:gn(t,e.label)}}async function FO(e){let[t,n]=await Promise.all([PO({...e,window:e.before,label:`GA4 before window`}),PO({...e,window:e.after,label:`GA4 after window`})]),r=[];return t.source.timeZone&&n.source.timeZone&&t.source.timeZone!==n.source.timeZone&&r.push(`GA4 returned different property timezones across the two windows.`),t.source.currencyCode&&n.source.currencyCode&&t.source.currencyCode!==n.source.currencyCode&&r.push(`GA4 returned different currency codes across the two windows.`),{report:{propertyId:e.propertyId,before:{...e.before,metrics:t.metrics},after:{...e.after,metrics:n.metrics},delta:MO({before:t.metrics,after:n.metrics}),note:`GA4 attribution is landing-page based. Query-level tests use GSC only.`},source:{before:t.source,after:n.source},warnings:[...new Set([...t.warnings,...n.warnings,...r])]}}function IO(e){return e===`regex`?`includingRegex`:e===`contains`?`contains`:`equals`}function LO(e,t){if(e.scope===`site`)return;let n=e.scope===`group`?t?.dimension:e.scope,r=e.scope===`group`?t?.pattern:e.target;if(!(!n||!r))return[{groupType:`and`,filters:[{dimension:n,operator:e.scope===`group`&&t?IO(t.matchType):`equals`,expression:r}]}]}function RO(e,t){let n=e.keys[0];if(!n)return!1;let r=new Date(`${n}T00:00:00.000Z`);return e.keys.length===1&&/^\d{4}-\d{2}-\d{2}$/.test(n)&&!Number.isNaN(r.getTime())&&r.toISOString().slice(0,10)===n&&n>=t.startDate&&n<=t.endDate&&[e.clicks,e.impressions,e.ctr,e.position].every(Number.isFinite)&&e.clicks>=0&&e.impressions>=0&&e.clicks<=e.impressions&&e.ctr>=0&&e.ctr<=1&&(e.impressions===0?e.position>=0:e.position>=1)}function zO(e,t){let n=new Map,r=new Set,i=0,a=0;for(let o of e){if(!RO(o,t)){i+=1;continue}let e=o.keys[0]??``;if(r.has(e)){i+=1;continue}let s=n.get(e);if(!s){n.set(e,o);continue}if(s.clicks===o.clicks&&s.impressions===o.impressions&&s.ctr===o.ctr&&s.position===o.position){a+=1;continue}n.delete(e),r.add(e),i+=2}return{rows:[...n.values()].sort((e,t)=>{let n=e.keys[0]??``,r=t.keys[0]??``;return n<r?-1:+(n>r)}),invalidRows:i,duplicateRows:a}}async function BO(e){let t=await e.searchAnalytics(e.site,{...e.window,dimensions:[`date`],type:`web`,dataState:`final`,dimensionFilterGroups:e.filters},{refresh:e.refresh}),n=zO(t.rows,e.window);return{metrics:DO(n.rows),source:{calls:t.calls,rowsFetched:t.rowsFetched,returnedRows:n.rows.length,invalidRows:n.invalidRows,duplicateRows:n.duplicateRows}}}function VO(e){return{clicks:EO(e.after.clicks-e.before.clicks),clickPct:TO(e.after.clicks,e.before.clicks),impressions:EO(e.after.impressions-e.before.impressions),impressionPct:TO(e.after.impressions,e.before.impressions),ctr:EO(e.after.ctr-e.before.ctr,4),position:e.before.impressions>0&&e.after.impressions>0?EO(e.after.position-e.before.position):null}}async function HO(e){let t=LO(e.change,e.group),n=e.change.scope===`query`||e.group?.dimension===`query`,[r,i]=await Promise.all([BO({site:e.change.site,window:e.before,filters:t,refresh:e.refresh,searchAnalytics:e.searchAnalytics}),BO({site:e.change.site,window:e.after,filters:t,refresh:e.refresh,searchAnalytics:e.searchAnalytics})]),a=[],o=r.source.invalidRows+i.source.invalidRows;o&&a.push(`Excluded ${o} invalid or conflicting Search Console date rows.`),n&&a.push(`Query-scoped Search Console evidence contains retained query data and can omit anonymized queries.`);let s=r.source.rowsFetched>0&&r.source.returnedRows===0&&r.source.invalidRows>0,c=i.source.rowsFetched>0&&i.source.returnedRows===0&&i.source.invalidRows>0,l=!s&&!c&&(!n||r.source.returnedRows>0&&i.source.returnedRows>0);l||a.push(n?`A query-scoped window returned no retained rows; absence was not converted into zero movement.`:`A Search Console window contained provider rows but none were valid; the window was not converted into zero movement.`);let u=s||n&&r.source.returnedRows===0?null:r.metrics,d=c||n&&i.source.returnedRows===0?null:i.metrics;return{before:{...e.before,metrics:u},after:{...e.after,metrics:d},delta:u&&d?VO({before:u,after:d}):{clicks:null,clickPct:null,impressions:null,impressionPct:null,ctr:null,position:null},status:n||o>0?`partial`:`complete`,completeness:n?`retained-query-date-aggregates`:`date-aggregates`,comparable:l,warnings:a,source:{before:r.source,after:i.source}}}function UO(e,t){let n=new Date(`${e}T00:00:00.000Z`);return n.setUTCDate(n.getUTCDate()+t),n.toISOString().slice(0,10)}function WO(e,t){let n=/^\d{4}-\d{2}-\d{2}$/,r=new Date(`${e}T00:00:00.000Z`);if(!n.test(e)||Number.isNaN(r.getTime())||r.toISOString().slice(0,10)!==e)throw new A(`INVALID_INPUT`,`${t} must be a valid YYYY-MM-DD date.`);return e}function GO(e,t,n){let r=e??t;if(!Number.isInteger(r)||r<1||r>548)throw new A(`INVALID_INPUT`,`${n} must be a whole number between 1 and 548.`);return r}function KO(e,t){let n=new Date(`${e}T00:00:00.000Z`).getTime(),r=new Date(`${t}T00:00:00.000Z`).getTime();return Math.floor((r-n)/864e5)+1}function qO(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 JO(e){let t=WO(e.changedAt,`changedAt`),n=GO(e.beforeDays,28,`beforeDays`),r=GO(e.afterDays,n,`afterDays`);if(n!==r)throw new A(`INVALID_INPUT`,`beforeDays and afterDays must match so raw totals use equal calendar windows.`);let i=G_(1,e.now).endDate,a=qO(UO(i,4),16);if(t>i)throw new A(`INSUFFICIENT_DATA`,`changedAt must be on or before the latest finalized GSC date, ${i}.`);let o=UO(t,r-1),s=o>i?i:o,c=KO(t,s),l=UO(t,-1),u=UO(l,-(c-1));if(u<a)throw new A(`INVALID_INPUT`,`The equal before window starts before ${a}, the current 16-month GSC retention boundary.`);return{before:{startDate:u,endDate:l},after:{startDate:t,endDate:s},requestedDays:n,effectiveDays:c,afterWindowTruncated:c<r,availableDateWindow:{earliestDate:a,latestFinalDate:i}}}const YO={searchAnalytics:e_,ga4Report:dn,contentGroup:CO,now:()=>new Date};function XO(e,t){if(e.scope!==`group`)return;let n=t(e.target);if(!n)throw new A(`INVALID_INPUT`,`Content group ${e.target} was not found.`);if(n.site!==e.site)throw new A(`INVALID_INPUT`,`Content group ${e.target} belongs to ${n.site}, not ${e.site}.`);return n}function ZO(e){if(e.controlBefore<=0)return null;let t=e.treatmentBefore*(e.controlAfter/e.controlBefore);return EO(e.treatmentAfter-t)}async function QO(e,t=YO){let n=t.now(),r=(e.id?BD(e.id):void 0)??{id:`adhoc`,site:e.site,scope:e.scope,target:e.target,title:e.title??`Ad hoc change measurement`,changedAt:e.changedAt,createdAt:n.toISOString()};if(!r.site?.trim()||!r.scope||!r.target?.trim()||!r.changedAt)throw new A(`INVALID_INPUT`,`Pass a change id, or provide site, scope, target, and changedAt.`);if(!!e.controlScope!=!!e.controlTarget?.trim())throw new A(`INVALID_INPUT`,`controlScope and controlTarget must be provided together.`);let i=JO({changedAt:r.changedAt,beforeDays:e.beforeDays,afterDays:e.afterDays,now:n}),{before:a,after:o}=i,s=XO(r,t.contentGroup),c=e.controlScope&&e.controlTarget?{id:`control`,site:r.site,scope:e.controlScope,target:e.controlTarget,title:e.controlTitle??`Control group`,changedAt:r.changedAt,createdAt:n.toISOString()}:void 0,l=c?XO(c,t.contentGroup):void 0,u=await HO({change:r,before:a,after:o,group:s,refresh:e.refresh,searchAnalytics:t.searchAnalytics}),d=c?await HO({change:c,before:a,after:o,group:l,refresh:e.refresh,searchAnalytics:t.searchAnalytics}):void 0,f=u.delta.clickPct,p=i.effectiveDays<7,m=i.afterWindowTruncated?`Only ${i.effectiveDays} of ${i.requestedDays} requested finalized after-days are available.`:u.comparable?p?`Only ${i.effectiveDays} finalized day${i.effectiveDays===1?``:`s`} are available in each window.`:void 0:`A Search Console window has no comparable metrics.`,h=u.before.metrics&&u.after.metrics&&u.delta.clicks!==null&&u.delta.position!==null?NO({before:u.before.metrics,after:u.after.metrics,clickPct:f,clickDelta:u.delta.clicks,positionDelta:u.delta.position}):{verdict:`not-enough-data`,confidence:`low`,note:`Comparable Search Console metrics are unavailable.`},g=m?{verdict:`not-enough-data`,confidence:`low`,note:`${m} The equal-window metrics are provisional and no directional verdict was assigned.`}:u.status===`partial`&&h.confidence===`high`?{...h,confidence:`medium`,note:`${h.note} Confidence is capped because Search Console evidence is partial.`}:h,_=AO(r,s),v=r.scope===`query`||s?.dimension===`query`,y=e.ga4PropertyId&&!v?await FO({propertyId:e.ga4PropertyId,before:a,after:o,filter:_,refresh:e.refresh,ga4Report:t.ga4Report}):void 0,b=[...u.warnings,...d?.warnings.map(e=>`Control: ${e}`)??[],...y?.warnings??[]];return{schemaVersion:1,methodology:`equal-finalized-calendar-windows-v1`,dataStatus:i.afterWindowTruncated||u.status===`partial`||d?.status===`partial`||y?.warnings.length?`partial`:`complete`,change:r,window:{requestedDays:i.requestedDays,effectiveDays:i.effectiveDays,afterWindowTruncated:i.afterWindowTruncated,gscTimezone:`America/Los_Angeles`,availableDateWindow:i.availableDateWindow},source:{searchAnalytics:{status:u.status,completeness:u.completeness,dimensions:[`date`],searchType:`web`,dataState:`final`,before:u.source.before,after:u.source.after,...d?{control:{status:d.status,completeness:d.completeness,before:d.source.before,after:d.source.after}}:{},warnings:[...u.warnings,...d?.warnings.map(e=>`Control: ${e}`)??[]]},...y?{analytics:{status:y.warnings.length?`partial`:`complete`,before:y.source.before,after:y.source.after,warnings:y.warnings}}:{}},before:u.before,after:u.after,delta:u.delta,...y?{analytics:y.report}:{},...d&&c?{control:{change:c,before:d.before,after:d.after,delta:d.delta,adjusted:{methodology:`control-ratio-counterfactual-v1`,clickDelta:d.comparable&&u.before.metrics&&u.after.metrics&&d.before.metrics&&d.after.metrics?ZO({treatmentBefore:u.before.metrics.clicks,treatmentAfter:u.after.metrics.clicks,controlBefore:d.before.metrics.clicks,controlAfter:d.after.metrics.clicks}):null,clickPctPoints:u.delta.clickPct===null||d.delta.clickPct===null?null:EO(u.delta.clickPct-d.delta.clickPct),impressionDelta:d.comparable&&u.before.metrics&&u.after.metrics&&d.before.metrics&&d.after.metrics?ZO({treatmentBefore:u.before.metrics.impressions,treatmentAfter:u.after.metrics.impressions,controlBefore:d.before.metrics.impressions,controlAfter:d.after.metrics.impressions}):null,impressionPctPoints:u.delta.impressionPct===null||d.delta.impressionPct===null?null:EO(u.delta.impressionPct-d.delta.impressionPct)},note:`Adjusted deltas compare the observed treatment result with a control-ratio counterfactual. Controls reduce some shared noise but do not prove causality.`}}:{},...g,warnings:b,caveats:[`Compared ${i.effectiveDays} finalized Search Console calendar days before and after the change in America/Los_Angeles time.`,`Search Console position is impression-weighted average position; before/after timing and controls do not prove causality.`,...i.afterWindowTruncated?[`The requested ${i.requestedDays}-day after window is incomplete, so this run is partial and has no directional verdict.`]:[],...p?[`Fewer than 7 finalized days per window is too short for a directional verdict.`]:[],...u.completeness===`retained-query-date-aggregates`?[`Query-scoped Search Console data can omit anonymized queries; an absent retained row is not zero traffic.`]:[],...y?[`GA4 attribution uses landing pages and the GA4 property timezone${y.source.before.timeZone?` (${y.source.before.timeZone})`:``}; its day boundaries may differ from Search Console Pacific dates.`]:[]]}}const $O=new Set(`a.an.and.are.at.best.by.for.free.from.guide.how.in.is.of.on.online.or.page.seo.site.the.to.tool.use.website.what.when.where.which.with`.split(`.`));function ek(e,t){return e<t?-1:+(e>t)}function tk(e){return e.normalize(`NFKC`).replace(/[’']/g,`'`).toLowerCase().replace(/[^\p{L}\p{N}']+/gu,` `).replace(/\s+/g,` `).trim()}function nk(e){try{let t=new URL(e);return t.protocol!==`http:`&&t.protocol!==`https:`?void 0:(t.hash=``,t.toString())}catch{return}}function rk(e){let[t,n]=e.keys;return!!(tk(t??``)&&nk(n??``)&&Number.isFinite(e.clicks)&&e.clicks>=0&&Number.isFinite(e.impressions)&&e.impressions>0&&e.clicks<=e.impressions&&Number.isFinite(e.ctr)&&e.ctr>=0&&e.ctr<=1&&Number.isFinite(e.position)&&e.position>0)}function ik(e){let t=tk(e),n=typeof Intl<`u`&&`Segmenter`in Intl?[...new Intl.Segmenter(`und`,{granularity:`word`}).segment(t)].filter(e=>e.isWordLike).map(e=>e.segment):t.match(/[\p{L}\p{N}]+/gu)??[];return[...new Set(n.filter(e=>!$O.has(e)&&!/^\p{N}+$/u.test(e)&&([...e].length>1||/[^\p{ASCII}]/u.test(e))))]}function ak(e){let t=new Map,n=e=>{let n=tk(e),r=t.get(n);if(r)return r;let i=ik(e);return t.set(n,i),i},r=[...new Set(e.map(tk).filter(Boolean))].map(n),i=new Map;for(let e of r)for(let t of new Set(e))i.set(t,(i.get(t)??0)+1);let a=Math.max(1,r.length),o=Math.max(3,Math.ceil(a*.1)),s=e=>Math.log((a+1)/((i.get(e)??0)+1))+1,c=e=>e.filter(e=>(i.get(e)??0)<=o);return(e,t)=>{let r=c(n(e)),i=c(n(t));if(r.length<2||i.length<2)return;let a=new Set(i),o=r.filter(e=>a.has(e)).sort(ek);if(o.length<2)return;let l=new Set([...r,...i]),u=o.reduce((e,t)=>e+s(t),0),d=i.reduce((e,t)=>e+s(t),0),f=r.reduce((e,t)=>e+s(t),0),p=[...l].reduce((e,t)=>e+s(t),0),m=u/d,h=u/f,g=u/p,_=g*.5+m*.25+h*.25;if(!(m<2/3||h<2/3||g<.6||_<.72))return{score:Number(_.toFixed(4)),weightedJaccard:Number(g.toFixed(4)),targetCoverage:Number(m.toFixed(4)),sourceCoverage:Number(h.toFixed(4)),sharedTerms:o}}}function ok(e){if(e.impressions<e.minImpressions)return`low-impressions`;if(Rv(e.query))return`low-actionability`;if(M_({query:e.query,siteUrl:e.site,brandTerms:e.brandTerms,includeBrand:e.includeBrand}))return`brand`}function sk(e,t,n,r){let i=e.get(t)??{query:n,clicks:0,impressions:0};i.clicks+=r.clicks,i.impressions+=r.impressions,ek(n,i.query)<0&&(i.query=n),e.set(t,i)}function ck(e){let t=tk(e.source.query),n=e.exactTargets.get(t);return n?{sourceQuery:e.source.query,targetQuery:n.query,kind:`exact-query`,relevanceScore:1,impressions:e.source.impressions,sharedTerms:ik(e.source.query)}:e.lexicalTargets.flatMap(t=>{let n=e.scoreLexical(e.source.query,t.query);return n?[{sourceQuery:e.source.query,targetQuery:t.query,kind:`lexical-review`,relevanceScore:n.score,impressions:e.source.impressions,sharedTerms:n.sharedTerms}]:[]}).sort((e,t)=>t.relevanceScore-e.relevanceScore||ek(e.targetQuery,t.targetQuery))[0]}function lk(e){return[...e.entries()].map(([e,t])=>{let n=[...t].sort((e,t)=>(e.kind===t.kind?0:e.kind===`exact-query`?-1:1)||t.impressions-e.impressions||t.relevanceScore-e.relevanceScore||ek(e.sourceQuery,t.sourceQuery));return{sourceUrl:e,matchedQueryImpressions:n.reduce((e,t)=>e+t.impressions,0),matchedQueries:n.length,exactQueryMatches:n.filter(e=>e.kind===`exact-query`).length,bestRelevanceScore:Math.max(...n.map(e=>e.relevanceScore)),bestMatchKind:n.some(e=>e.kind===`exact-query`)?`exact-query`:`lexical-review`,matches:n.slice(0,10)}}).sort((e,t)=>t.exactQueryMatches-e.exactQueryMatches||t.matchedQueryImpressions-e.matchedQueryImpressions||t.bestRelevanceScore-e.bestRelevanceScore||ek(e.sourceUrl,t.sourceUrl))}function uk(e){let t=new Set(e.targetAliases.map(nk).filter(e=>!!e)),n=new Map,r=e=>{n.set(e,(n.get(e)??0)+1)},i=new Map,a=0;for(let n of e.targetRows){if(!rk(n)){r(`targetInvalidRows`);continue}a+=1;let e=n.keys[0]??``,o=nk(n.keys[1]??``);if(!o||!t.has(o)){r(`targetUrlMismatchRows`);continue}sk(i,tk(e),e,n)}let o=[...i.values()].filter(t=>{let n=ok({...t,site:e.site,minImpressions:e.minImpressions,brandTerms:e.brandTerms,includeBrand:e.includeBrand});return n===`low-impressions`&&r(`targetLowImpressionQueries`),n===`low-actionability`&&r(`targetLowActionabilityQueries`),n===`brand`&&r(`targetBrandQueries`),!n}).sort((e,t)=>t.impressions-e.impressions||t.clicks-e.clicks||ek(e.query,t.query)),s=o.slice(0,50),c=new Map(o.map(e=>[tk(e.query),e])),l=new Map,u=0;for(let n of e.sourceRows){if(!rk(n)){r(`sourceInvalidRows`);continue}u+=1;let e=n.keys[0]??``,i=nk(n.keys[1]??``);if(!i)continue;if(t.has(i)){r(`sourceTargetAliasRows`);continue}let a=`${i}\u0000${tk(e)}`,o=l.get(a)??{page:i,query:e,clicks:0,impressions:0};o.clicks+=n.clicks,o.impressions+=n.impressions,ek(e,o.query)<0&&(o.query=e),l.set(a,o)}let d=[...l.values()].filter(t=>{let n=ok({...t,site:e.site,minImpressions:e.minImpressions,brandTerms:e.brandTerms,includeBrand:e.includeBrand});return n===`low-impressions`&&r(`sourceLowImpressionQueries`),n===`low-actionability`&&r(`sourceLowActionabilityQueries`),n===`brand`&&r(`sourceBrandQueries`),!n}),f=ak([...o.map(e=>e.query),...d.map(e=>e.query)]),p=new Map,m=0;for(let e of d){let t=ck({source:e,exactTargets:c,lexicalTargets:s,scoreLexical:f});if(!t){r(`sourceUnmatchedQueries`);continue}m+=1;let n=p.get(e.page)??[];n.push(t),p.set(e.page,n)}let h=lk(p),g=e=>n.get(e)??0;return{targetQueries:s,candidates:h,selection:{targetSourceRows:e.targetRows.length,targetValidRows:a,targetInvalidRows:g(`targetInvalidRows`),targetUrlMismatchRows:g(`targetUrlMismatchRows`),targetLowImpressionQueries:g(`targetLowImpressionQueries`),targetLowActionabilityQueries:g(`targetLowActionabilityQueries`),targetBrandQueries:g(`targetBrandQueries`),targetEligibleQueries:o.length,selectedLexicalTargetQueries:s.length,sourceRows:e.sourceRows.length,sourceValidRows:u,sourceInvalidRows:g(`sourceInvalidRows`),sourceTargetAliasRows:g(`sourceTargetAliasRows`),sourceLowImpressionQueries:g(`sourceLowImpressionQueries`),sourceLowActionabilityQueries:g(`sourceLowActionabilityQueries`),sourceBrandQueries:g(`sourceBrandQueries`),sourceUnmatchedQueries:g(`sourceUnmatchedQueries`),candidateQueries:m,candidateUrls:h.length}}}function dk(e){let t=e.value??e.fallback;if(!Number.isInteger(t)||t<e.minimum||t>e.maximum)throw new A(`INVALID_INPUT`,`${e.label} must be a whole number between ${e.minimum} and ${e.maximum}.`);return t}function fk(e){let t=e.value??e.fallback;if(!Number.isFinite(t)||t<e.minimum||t>e.maximum)throw new A(`INVALID_INPUT`,`${e.label} must be between ${e.minimum} and ${e.maximum}.`);return t}function pk(e,t){return Zg(e,t)}function mk(e,t){return{...e.selection,...t}}function hk(e){return e.dataStatus===`target-technical-issue`?`The target has a technical issue. Resolve it before adding internal links.`:e.dataStatus===`empty`?`Search Console returned no retained query/page rows for the target URL aliases in this window.`:e.dataStatus===`source-empty`?`Search Console returned no retained source query/page rows in this window. This does not prove that no relevant source page exists.`:e.dataStatus===`filtered`?`Target demand exists, but no precision-matched source candidates passed the report filters.`:e.returned?`${e.returned} internal-link review candidate${e.returned===1?``:`s`} remained after ${e.attempted} verification attempt${e.attempted===1?``:`s`}; ${e.checked} of ${e.candidates} matched source pages were successfully checked.`:e.dataStatus===`partial`?e.candidates===0&&e.attempted===0?`Internal-link candidates could not be evaluated because required target, provider, or row evidence was incomplete.`:`No missing contextual link was confirmed among ${e.checked} successfully checked source page${e.checked===1?``:`s`}; evidence remains incomplete with ${e.failed} failed check${e.failed===1?``:`s`} and ${e.unchecked} unattempted candidate${e.unchecked===1?``:`s`}.`:`No missing contextual link was confirmed among ${e.checked} successfully checked source page${e.checked===1?``:`s`}.`}function gk(e){return[...new Map(e.map(e=>[`${e.stage}\u0000${e.url}\u0000${e.code}\u0000${e.message}`,e])).values()]}function _k(e){return e instanceof Error?e.message:String(e)}function vk(e,t){if(t)try{return Ng(e,t)}catch{return}}function yk(e){let t=new URL(e);return t.hash=``,t.toString()}function bk(e,t){try{return yk(e)===yk(t)}catch{return!1}}function xk(e){if(e.canonical)try{return new URL(e.canonical,e.finalUrl).toString()}catch{return}}async function Sk(e){let t;try{t=await e.dependencies.fetch(e.targetUrl,{js:e.js??`auto`,refresh:e.refresh,rate:e.rate})}catch(t){return{requestedUrl:e.targetUrl,preferredUrl:e.targetUrl,aliases:[e.targetUrl],verification:`failed`,technicalSignals:[],warnings:[{stage:`target-fetch`,url:e.targetUrl,code:`fetch-failed`,message:_k(t)}]}}let n;try{n=await e.dependencies.extract(t)}catch(n){return{requestedUrl:e.targetUrl,preferredUrl:e.targetUrl,finalUrl:t.finalUrl,status:t.status,aliases:[e.targetUrl],verification:`failed`,technicalSignals:[],fetchDiagnostics:t.diagnostics,warnings:[{stage:`target-extract`,url:e.targetUrl,code:`extract-failed`,message:_k(n)}]}}let r=xk(n),i=vk(e.site,n.finalUrl),a=vk(e.site,r),o=[e.targetUrl,i,a].filter(e=>!!e).map(yk),s=[...new Set(o)],c=d_({url:e.targetUrl,page:n,fetchDiagnostics:t.diagnostics,httpStatus:t.status}),l=c.filter(e=>e!==`redirected`||!i),u=n.warnings.map(t=>({stage:`target-extract`,url:e.targetUrl,code:`extractor-warning`,message:t}));return{requestedUrl:e.targetUrl,preferredUrl:i??e.targetUrl,finalUrl:n.finalUrl,canonical:r,status:t.status,aliases:s,verification:l.length?`technical-issue`:`verified`,technicalSignals:c,fetchDiagnostics:t.diagnostics,warnings:u}}function Ck(e,t){return e.links.filter(e=>t.some(t=>bk(e.href,t)))}function wk(e){return{score:Math.round(e.exactQueryMatches*100+Math.log10(e.matchedQueryImpressions+1)*10+e.bestRelevanceScore*10),heuristic:!0,components:{exactQueryMatches:e.exactQueryMatches,matchedQueryImpressions:e.matchedQueryImpressions,relevanceScore:e.bestRelevanceScore}}}async function Tk(e){try{Ng(e.site,e.candidate.sourceUrl)}catch(t){return{exclusion:`failed`,warnings:[{stage:`source-fetch`,url:e.candidate.sourceUrl,code:`fetch-failed`,message:_k(t)}]}}let t;try{t=await e.dependencies.fetch(e.candidate.sourceUrl,{js:e.js??`auto`,refresh:e.refresh,rate:e.rate})}catch(t){return{exclusion:`failed`,warnings:[{stage:`source-fetch`,url:e.candidate.sourceUrl,code:`fetch-failed`,message:_k(t)}]}}let n;try{n=await e.dependencies.extract(t)}catch(t){return{exclusion:`failed`,warnings:[{stage:`source-extract`,url:e.candidate.sourceUrl,code:`extract-failed`,message:_k(t)}]}}let r=n.warnings.map(t=>({stage:`source-extract`,url:e.candidate.sourceUrl,code:`extractor-warning`,message:t}));if(e.target.aliases.some(e=>bk(n.finalUrl,e)))return{exclusion:`self-alias`,warnings:r};let i=d_({url:e.candidate.sourceUrl,page:n,fetchDiagnostics:t.diagnostics,httpStatus:t.status});if(i.length)return{exclusion:`technical`,warnings:r};let a=Ck(n,e.target.aliases).map(e=>({href:e.href,text:e.text,rel:e.rel,location:e.location})),o=a.filter(e=>e.location===`main-content`);if(o.some(t=>bk(t.href,e.target.preferredUrl)))return{exclusion:`existing-link`,warnings:r};let s=o.length>0,c=s?`alias-contextual`:a.length?`non-contextual-only`:`missing`,l=e.candidate.bestMatchKind===`exact-query`&&!n.contentExtraction.fallback?`medium`:`low`,u=s?`review-alias-link`:`review-contextual-link`,d=s?`A main-content link points to a non-preferred target alias; the preferred target is ${e.target.preferredUrl}.`:`${e.candidate.sourceUrl} matched ${e.candidate.matchedQueries} retained GSC query row${e.candidate.matchedQueries===1?``:`s`}, and no main-content link to the preferred target was observed in the fetched HTML.`,f=s?`Review the existing contextual link and update it to the preferred target ${e.target.preferredUrl} if the redirect or old alias is not intentional.`:`Review whether this source page and ${e.target.preferredUrl} serve complementary intent. If the link would help a reader, add a natural main-content link without forcing exact-match anchor text.`;return{item:{...e.candidate,finalUrl:n.finalUrl,status:t.status,technicalSignals:i,fetchDiagnostics:t.diagnostics,pageWarnings:n.warnings,actionType:u,linkEvidence:{status:c,observedCount:a.length,observedLimit:20,limitedCount:Math.max(0,a.length-20),observed:a.slice(0,20)},confidence:l,priority:wk(e.candidate),recommendation:{principle:`C.6`,evidenceRef:d,action:f,effort:`S`,confidence:l}},warnings:r}}const Ek=1e5,Dk={searchAnalytics:e_,fetch:Ag,extract:Th,now:()=>new Date};async function Ok(e,t=Dk){let n=new URL(Ng(e.site,e.targetUrl));n.hash=``;let r=n.toString(),i=dk({value:e.days,fallback:28,minimum:1,maximum:548,label:`days`}),a=dk({value:e.limit,fallback:20,minimum:1,maximum:100,label:`limit`}),o=dk({value:e.checkLimit,fallback:Math.max(a,40),minimum:1,maximum:200,label:`checkLimit`}),s=fk({value:e.minImpressions,fallback:1,minimum:0,maximum:1e9,label:`minImpressions`}),c=t.now(),l=pk(i,c),u=new qT,d=await Sk({site:e.site,targetUrl:r,js:e.js,refresh:e.refresh,rate:e.rate,dependencies:t}),f=await Promise.all(d.aliases.map(async n=>{let r=await t.searchAnalytics(e.site,{...l,dimensions:[`query`,`page`],type:`web`,dataState:`final`,maxRows:Ek,dimensionFilterGroups:[{groupType:`and`,filters:[{dimension:`page`,operator:`equals`,expression:n}]}]},{refresh:e.refresh});return u.addGsc(r.calls,r.rowsFetched),{alias:n,result:r}})),p=f.flatMap(({result:e})=>e.rows),m=uk({targetRows:p,sourceRows:[],site:e.site,targetAliases:d.aliases,minImpressions:s,brandTerms:e.brandTerms,includeBrand:e.includeBrand}),h=d.verification===`verified`&&m.selection.targetEligibleQueries>0,g=h?await t.searchAnalytics(e.site,{...l,dimensions:[`query`,`page`],type:`web`,dataState:`final`,maxRows:Ek},{refresh:e.refresh}):{rows:[],calls:0,rowsFetched:0};u.addGsc(g.calls,g.rowsFetched);let _=uk({targetRows:p,sourceRows:g.rows,site:e.site,targetAliases:d.aliases,minImpressions:s,brandTerms:e.brandTerms,includeBrand:e.includeBrand}),v=[],y=[...d.warnings],b=0,x=0,ee=0,te=0,S=0,C=0;for(let n of _.candidates){if(b>=o||v.length>=a)break;b+=1;let r=await Tk({site:e.site,candidate:n,target:d,js:e.js,refresh:e.refresh,rate:e.rate,dependencies:t});y.push(...r.warnings),r.item&&v.push(r.item),r.exclusion===`existing-link`&&(ee+=1),r.exclusion===`technical`&&(te+=1),r.exclusion===`self-alias`&&(S+=1),r.exclusion===`failed`?C+=1:x+=1}let w=Math.max(0,_.candidates.length-b),T=f.some(({result:e})=>e.rowsFetched>=Ek),ne=g.rowsFetched>=Ek,re=_.selection.targetInvalidRows+_.selection.sourceInvalidRows,E=gk(y),ie=d.verification===`technical-issue`?`target-technical-issue`:d.verification===`failed`?`partial`:p.length===0?`empty`:T||ne||re>0?`partial`:h&&g.rows.length===0?`source-empty`:_.selection.targetEligibleQueries===0||_.candidates.length===0?`filtered`:C>0||w>0?`partial`:`complete`,ae=mk(_,{attemptedSources:b,checkedSources:x,returnedSources:v.length,existingLinkExclusions:ee,technicalExclusions:te,selfAliasExclusions:S,failedChecks:C,uncheckedCandidates:w}),D=hk({dataStatus:ie,returned:v.length,attempted:b,checked:x,candidates:_.candidates.length,failed:C,unchecked:w});return{site:e.site,targetUrl:r,generatedAt:c.toISOString(),range:l,rangeDays:i,dataStatus:ie,source:{provider:`google-search-console`,dimensions:[`query`,`page`],searchType:`web`,dataState:`final`,target:{pageFilters:f.map(({alias:e})=>e),requests:f.map(({alias:e,result:t})=>({pageFilter:e,rowsFetched:t.rowsFetched,calls:t.calls,possiblyTruncated:t.rowsFetched>=Ek})),rowsFetched:f.reduce((e,{result:t})=>e+t.rowsFetched,0),calls:f.reduce((e,{result:t})=>e+t.calls,0),maxRowsPerRequest:Ek,possiblyTruncated:T},candidates:{queried:h,rowsFetched:g.rowsFetched,calls:g.calls,maxRows:Ek,possiblyTruncated:ne},completeness:h?T||ne?`possibly-truncated`:`retained-rows-only`:`not-queried`},methodology:{id:`gsc_internal_link_candidates`,version:3,lexicalTargetLimit:50,matchedQueryEvidenceLimit:10,observedLinkEvidenceLimit:20,matching:`pairwise_exact_then_precision_lexical`,ranking:`exact_matches_then_matched_query_impressions_then_relevance`,contextualPlacementVerified:!0},filters:{minImpressions:s,resultLimit:a,checkLimit:o,maxGscRowsPerRequest:Ek},target:{requestedUrl:d.requestedUrl,preferredUrl:d.preferredUrl,finalUrl:d.finalUrl,canonical:d.canonical,status:d.status,aliases:d.aliases,verification:d.verification,technicalSignals:d.technicalSignals,fetchDiagnostics:d.fetchDiagnostics,queries:_.targetQueries},selection:ae,summary:{targetQueries:_.selection.targetEligibleQueries,candidateSources:_.candidates.length,attemptedSources:b,checkedSources:x,returnedSources:v.length,existingLinksObserved:ee,technicalExclusions:te,failedChecks:C,uncheckedCandidates:w,matchedQueryImpressions:v.reduce((e,t)=>e+t.matchedQueryImpressions,0),brandFiltering:e.includeBrand?`included`:`excluded`,verdict:D},items:v,warnings:E,caveats:[`Date window: ${l.startDate} to ${l.endDate} (${i} days), using final GSC data where available.`,`Selection: ${_.selection.targetEligibleQueries} eligible target queries, ${_.selection.candidateUrls} matched source pages, ${b} attempted, ${x} successfully checked, ${v.length} returned.`,`Minimum retained query impressions: ${s}. Brand queries were ${e.includeBrand?`included`:`excluded when detected or configured`}.`,`GSC query rows omit anonymized queries and lower-volume rows. A 100,000-row cap is reported separately when reached.`,`Exact query overlap is medium-confidence affinity evidence; lexical matches are low-confidence review evidence. Neither predicts traffic lift or link equity.`,`The fetch checks HTML link placement, but it cannot prove editorial relevance. Review intent and reader usefulness before adding a link.`,w?`${w} matched source page${w===1?` was`:`s were`} not checked because the output or check limit was reached.`:``,C?`${C} source verification attempt${C===1?``:`s`} failed. Failed fetches or extraction attempts are not counted as checked pages and do not support a missing-link conclusion.`:``].filter(Boolean),recommendations:d.verification===`verified`?v.length?[`Review exact-query candidates first, then lexical candidates. Add or update a contextual link only when it helps a reader move between complementary intents.`]:[`No ready-to-apply link action was confirmed among the checked sources. Review completeness and unchecked counts before treating this as a site-wide absence.`]:[`Resolve the target fetch, indexability, redirect, or canonical issue before adding internal links.`],ledgerSummary:u.summary()}}function kk(e){return e.kind!==`removed`&&(e.after?.status??0)>=400&&(e.before?.status??200)<400}function Ak(e){return e.kind===`changed`&&(e.before?.status??200)>=400&&(e.after?.status??0)<400}function jk(e){return e.kind===`changed`&&e.before?.indexable===!0&&e.after?.indexable===!1}function Mk(e){return e.kind===`changed`&&e.changes.includes(`canonical`)&&!!e.after?.canonical&&e.after?.canonical!==e.after?.finalUrl}function Nk(e){if(kk(e))return{severity:`high`,category:`status`,title:`Search-visible URL now returns an error`,action:`This URL used to be crawlable and now returns an error. Restore it if the page should still exist, or add one direct 301 to the closest live replacement before doing any content work.`,confidence:`high`};if(e.kind===`removed`)return{severity:`high`,category:`inventory`,title:`Previously crawled URL disappeared`,action:`This URL was in the previous crawl but is missing now. Confirm whether removal was intentional; if it had search traffic or links, restore it or redirect it to the best replacement.`,confidence:`medium`};if(jk(e)){let t=tm(e.after?.metaRobots,`noindex`)||nm(e.after?.xRobotsTag,`noindex`);return{severity:`high`,category:`indexability`,title:`URL became non-indexable`,action:t?`This URL now has a noindex signal. Remove it if accidental, or mark this as intentional so it does not keep appearing as a risk.`:`This URL used to be indexable and now is not. Check robots, canonical, status, and rendered HTML to find what changed.`,confidence:t?`high`:`medium`}}if(Mk(e))return{severity:`medium`,category:`canonical`,title:`Canonical changed away from the final URL`,action:`Google may treat a different URL as the preferred page now. If that is not intentional, restore the self-canonical or redirect directly to the preferred URL.`,confidence:`high`};if(e.changes.includes(`title`)||e.changes.includes(`h1`))return{severity:`medium`,category:`metadata`,title:`Primary SERP/on-page targeting changed`,action:`The title or H1 changed. Compare the old and new wording against the top GSC queries for this URL before blaming traffic movement on content decay.`,confidence:`medium`};if(e.changes.includes(`meta_description`))return{severity:`low`,category:`metadata`,title:`Meta description changed`,action:`The meta description changed. Check CTR for this URL before spending time on more copy edits.`,confidence:`medium`};if(e.changes.includes(`content`))return{severity:`low`,category:`content`,title:`Main content changed`,action:`Main content changed. If traffic moved after this crawl, compare the changed section against the queries this URL ranks for.`,confidence:`medium`};if(Ak(e))return{severity:`low`,category:`status`,title:`URL recovered from an error status`,action:`The URL is no longer returning an error. Keep monitoring it and run URL Inspection if it was previously deindexed.`,confidence:`medium`}}function Pk(e){return e.map(e=>({...e,recommendation:Nk(e)}))}function Fk(e){let t={high:3,medium:2,low:1};return e.filter(e=>e.recommendation).map(e=>({...e.recommendation,url:e.url})).sort((e,n)=>t[n.severity]-t[e.severity])}function Ik(e){return{id:e.id,site:e.site_url,startUrl:e.start_url,createdAt:new Date(e.created_at).toISOString(),limit:e.limit_count,urlCount:e.url_count}}function Lk(e){if(!e)return{};try{let t=JSON.parse(e);return t&&typeof t==`object`?t:{}}catch{return{}}}function Rk(e){return{url:e.url,finalUrl:e.final_url,status:e.status,title:e.title??void 0,metaDescription:e.meta_description??void 0,canonical:e.canonical??void 0,metaRobots:e.meta_robots??void 0,xRobotsTag:e.x_robots_tag??void 0,h1:e.h1??void 0,indexable:e.indexable===1,wordCount:e.word_count,contentHash:e.content_hash,outgoingInternalCount:e.outgoing_internal_count,...Lk(e.snapshot_json),url:e.url,finalUrl:e.final_url,status:e.status,indexable:e.indexable===1,contentHash:e.content_hash,outgoingInternalCount:e.outgoing_internal_count}}function zk(e){let t=j().prepare(`SELECT * FROM crawl_runs
575
575
  WHERE site_url = ? AND start_url = ? AND id != ?
576
- ORDER BY created_at DESC LIMIT 1`).get(e.site,e.startUrl,e.currentRunId);return t?_k(t):void 0}function xk(e){let t=j().prepare(`SELECT * FROM crawl_pages WHERE run_id = ?`).all(e);return new Map(t.map(e=>[e.url,yk(e)]))}function Sk(e,t,n=[]){let r=j();r.prepare(`INSERT INTO crawl_runs
576
+ ORDER BY created_at DESC LIMIT 1`).get(e.site,e.startUrl,e.currentRunId);return t?Ik(t):void 0}function Bk(e){let t=j().prepare(`SELECT * FROM crawl_pages WHERE run_id = ?`).all(e);return new Map(t.map(e=>[e.url,Rk(e)]))}function Vk(e,t,n=[]){let r=j();r.prepare(`INSERT INTO crawl_runs
577
577
  (id, site_url, start_url, created_at, limit_count, url_count)
578
578
  VALUES (?, ?, ?, ?, ?, ?)`).run(e.id,e.site,e.startUrl,Date.parse(e.createdAt),e.limit,t.length);let i=r.prepare(`INSERT INTO crawl_pages
579
579
  (run_id, url, final_url, status, title, meta_description, canonical,
@@ -581,7 +581,7 @@ CREATE INDEX IF NOT EXISTS idx_performance_reports_url ON performance_reports(ur
581
581
  outgoing_internal_count, snapshot_json)
582
582
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),a=r.prepare(`INSERT INTO crawl_recommendations
583
583
  (run_id, site_url, url, severity, category, title, action, confidence, created_at)
584
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`),o=Date.parse(e.createdAt);r.transaction(()=>{for(let n of t)i.run(e.id,n.url,n.finalUrl,n.status,n.title??null,n.metaDescription??null,n.canonical??null,n.metaRobots??null,n.xRobotsTag??null,n.h1??null,+!!n.indexable,n.wordCount,n.contentHash,n.outgoingInternalCount,JSON.stringify(n));for(let t of n)a.run(e.id,e.site,t.url,t.severity,t.category,t.title,t.action,t.confidence,o)})()}function Ck(e,t=5){return j().prepare(`WITH page_counts AS (
584
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`),o=Date.parse(e.createdAt);r.transaction(()=>{for(let n of t)i.run(e.id,n.url,n.finalUrl,n.status,n.title??null,n.metaDescription??null,n.canonical??null,n.metaRobots??null,n.xRobotsTag??null,n.h1??null,+!!n.indexable,n.wordCount,n.contentHash,n.outgoingInternalCount,JSON.stringify(n));for(let t of n)a.run(e.id,e.site,t.url,t.severity,t.category,t.title,t.action,t.confidence,o)})()}function Hk(e,t=5){return j().prepare(`WITH page_counts AS (
585
585
  SELECT
586
586
  run_id,
587
587
  COALESCE(SUM(CASE WHEN status >= 400 THEN 1 ELSE 0 END), 0) AS status_errors,
@@ -625,16 +625,16 @@ CREATE INDEX IF NOT EXISTS idx_performance_reports_url ON performance_reports(ur
625
625
  WHERE crawl_runs.site_url = ?
626
626
  GROUP BY crawl_runs.id
627
627
  ORDER BY crawl_runs.created_at DESC
628
- LIMIT ?`).all(e,t).map(e=>({..._k(e),statusErrors:e.status_errors,nonIndexable:e.non_indexable,recommendations:e.recommendation_count,highPriorityRecommendations:e.high_recommendation_count,topRecommendation:e.top_recommendation_url?{url:e.top_recommendation_url,title:e.top_recommendation_title??``,action:e.top_recommendation_action??``,severity:e.top_recommendation_severity??`medium`}:void 0}))}function wk(e){let t=new Map(e.current.map(e=>[e.url,e])),n=new Map(e.previous.map(e=>[e.url,e])),r=[];for(let[e,i]of t.entries()){let t=n.get(e);if(!t){r.push({url:e,kind:`added`,changes:[`url_added`],after:i});continue}let a=[];t.status!==i.status&&a.push(`status`),t.title!==i.title&&a.push(`title`),t.metaDescription!==i.metaDescription&&a.push(`meta_description`),t.canonical!==i.canonical&&a.push(`canonical`),t.h1!==i.h1&&a.push(`h1`),t.indexable!==i.indexable&&a.push(`indexability`),t.contentHash!==i.contentHash&&a.push(`content`),a.length&&r.push({url:e,kind:`changed`,changes:a,before:t,after:i})}for(let[e,i]of n.entries())t.has(e)||r.push({url:e,kind:`removed`,changes:[`url_removed`],before:i});return r}async function Tk(e){let t=new URL(e.startUrl).toString(),n=e.site??new URL(t).origin,r=e.limit??50,i=[t],a=new Set,o=[],s=[];for(;i.length&&o.length<r;){let t=i.shift();if(!t||a.has(t))continue;a.add(t);let n=await Ox(t,{refresh:e.refresh,js:e.js??`auto`});n.warning&&s.push(n.warning),n.page&&o.push(n.page);for(let e of n.urls)!a.has(e)&&i.length+o.length<r*3&&i.push(e)}let c={id:v(),site:n,startUrl:t,createdAt:new Date().toISOString(),limit:r,urlCount:o.length},l=bk({site:n,startUrl:t,currentRunId:c.id}),u=l?[...xk(l.id).values()]:[],d=l?hk(wk({current:o,previous:u})):[],f=gk(d);return Sk(c,o,f),{run:c,previousRun:l,summary:{crawled:o.length,added:d.filter(e=>e.kind===`added`).length,removed:d.filter(e=>e.kind===`removed`).length,changed:d.filter(e=>e.kind===`changed`).length,newErrors:d.filter(e=>e.kind!==`removed`&&(e.after?.status??0)>=400&&(e.before?.status??200)<400).length,indexabilityFlips:d.filter(e=>e.changes.includes(`indexability`)).length,highPriorityRecommendations:f.filter(e=>e.severity===`high`).length},recommendations:f,items:d,warnings:s}}const Ek=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,Dk=/^\d{4}-\d{2}-\d{2}$/;function Ok(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)return t}return n.length-r.length}function kk(e){let t=e=>{try{return decodeURIComponent(e)}catch{return e}};try{return new URL(e).pathname.split(`/`).filter(Boolean).map(t)}catch{return e.split(`?`)[0]?.split(`/`).filter(Boolean).map(t)??[]}}function Ak(e){if(/^\d+$/.test(e))return`:num`;if(Ek.test(e))return`:id`;if(Dk.test(e))return`:date`;if(/^[\p{L}\p{N}]+(?:-[\p{L}\p{N}]+)+$/u.test(e)||e.length>=12&&/^[\p{L}\p{N}]+$/u.test(e))return`:slug`}function jk(e){let t=kk(e);return t.length?`/${t.map((e,t)=>{let n=Ak(e);return n?t===0&&n===`:slug`?e.toLowerCase():n:e.toLowerCase()}).join(`/`)}`:`/`}function Mk(e){let t=kk(e);if(!t.length)return`/`;let[n,...r]=t;return n?r.length?`/${n.toLowerCase()}/${r.map(e=>Ak(e)??`:value`).join(`/`)}`:`/${Ak(n)??n.toLowerCase()}`:`/`}function Nk(e){let t=new Map;for(let n of e){let e=n.normalize(`NFKC`).toLowerCase().match(/[\p{L}\p{N}]+/gu)??[];for(let n of e.filter(e=>e.length>1||/[^\p{ASCII}]/u.test(e)))t.set(n,(t.get(n)??0)+1)}return[...t.entries()].sort((e,t)=>t[1]-e[1]||Ok(e[0],t[0])).slice(0,8).map(([e])=>e)}function Pk(e,t){let n=e.split(`/`).filter(Boolean),r=t.map(kk),i=Math.max(n.length,...r.map(e=>e.length)),a=[],o=[];for(let e=0;e<i;e+=1){let t=n[e]??`:missing`,i=r.map(t=>t[e]?.toLowerCase()).filter(e=>!!e),s=[...new Set(i)];t.startsWith(`:`)||s.length>1?o.push({index:e,placeholder:t.startsWith(`:`)?t:`:value`,distinctValues:s.length,examples:s.slice(0,5),tokenExamples:Nk(s)}):t&&a.push({index:e,value:t})}return{depth:i,staticSegments:a,variableSegments:o}}function Fk(e,t){let n=jk(e);if(t.some(e=>e.signature===n))return n;let r=Mk(e);return t.some(e=>e.signature===r)?r:n}function Ik(e,t){let n=new Map;for(let r of e){let e=t.get(r);if(!e)continue;let i=n.get(e)??[];i.push(r),n.set(e,i)}return n}function Lk(e,t={}){let n=[...new Set(e)].sort(Ok),r=t.minUrls??3,i=t.minShare??.01,a=t.sampleSize??5,o=new Map(n.map(e=>[e,jk(e)])),s=Ik(n,o),c=new Set([...s.entries()].filter(([,e])=>e.length>=r&&e.length/n.length>=i).map(([e])=>e)),l=new Map;for(let e of n){let t=o.get(e)??`/`;l.set(e,c.has(t)?t:Mk(e))}return[...Ik(n,l).entries()].map(([e,t])=>({id:e,signature:e,shape:Pk(e,t),urlCount:t.length,share:n.length?t.length/n.length:0,sampleUrls:t.sort(Ok).slice(0,a)})).filter(e=>e.urlCount>=r&&e.share>=i).sort((e,t)=>t.urlCount-e.urlCount||Ok(e.signature,t.signature)).slice(0,t.limit??50)}const Rk=.01;function zk(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+=1){let t=(n[e]??0)-(r[e]??0);if(t)return t}return n.length-r.length}function Bk(e,t,n){return e===void 0||!Number.isFinite(e)?t:Math.min(n,Math.max(1,Math.trunc(e)))}function Vk(e){try{let t=new URL(e.trim());return![`http:`,`https:`].includes(t.protocol)||t.username||t.password?void 0:(t.hash=``,t.toString())}catch{return}}function Hk(e){return e!==void 0}function Uk(e){return{...e,duplicateUrls:e.validUrls-e.uniqueUrls,invalidUrls:e.rowsReceived-e.validUrls}}function Wk(e){let t=`report`in e?e.report:void 0,n=t?.pages??(`pages`in e?e.pages:[]),r=n.map(e=>Vk(e.url)).filter(Hk),i=new Set(r),a=t?.config.maxPages??(`rowLimit`in e?e.rowLimit:void 0),o=t?.summary.pageLimitReached??(`rowLimitReached`in e?e.rowLimitReached:void 0);return{pages:n,evidence:Uk({completeness:t?t.summary.pageLimitReached?`truncated`:t.status===`completed`?`complete`:t.status===`partial`?`partial`:`unknown`:`completeness`in e?e.completeness:`unknown`,rowsReceived:n.length,validUrls:r.length,uniqueUrls:i.size,rowLimit:a,rowLimitReached:o})}}function Gk(e){if(!e)return{urls:new Set,evidence:Uk({completeness:`unavailable`,rowsReceived:0,validUrls:0,uniqueUrls:0})};let t=e.urls.map(Vk).filter(Hk),n=new Set(t);return{urls:n,evidence:Uk({completeness:e.completeness,rowsReceived:e.urls.length,validUrls:t.length,uniqueUrls:n.size,rowLimit:e.rowLimit,rowLimitReached:e.rowLimitReached})}}function Kk(e){return Number.isFinite(e)&&e>=0}function qk(e){if(!e)return{metrics:new Map,evidence:{...Uk({completeness:`unavailable`,rowsReceived:0,validUrls:0,uniqueUrls:0}),semantics:`retained-search-analytics-page-rows`,invalidMetricRows:0,zeroImpressionRows:0}};let t=e.pageKeyIndex??0,n=new Map,r=[],i=0,a=0,o=0;for(let n of e.rows){let e=Vk(n.keys[t]??``);if(!e){i+=1;continue}if(!Kk(n.clicks)||!Kk(n.impressions)||!Kk(n.position)||n.clicks>n.impressions){a+=1;continue}if(n.impressions===0){o+=1;continue}r.push({url:e,row:n})}r.sort((e,t)=>zk(e.url,t.url)||e.row.clicks-t.row.clicks||e.row.impressions-t.row.impressions||e.row.position-t.row.position||zk(JSON.stringify(e.row.keys),JSON.stringify(t.row.keys)));for(let{url:e,row:t}of r){let r=n.get(e)??{clicks:0,impressions:0,weightedPosition:0,sourceRows:0};r.clicks+=t.clicks,r.impressions+=t.impressions,r.weightedPosition+=t.position*t.impressions,r.sourceRows+=1,n.set(e,r)}let s=e.rows.length-i-a-o;return{metrics:n,evidence:{semantics:`retained-search-analytics-page-rows`,completeness:e.completeness,rowsReceived:e.rows.length,validUrls:s,uniqueUrls:n.size,duplicateUrls:s-n.size,invalidUrls:i,rowLimit:e.rowLimit,rowLimitReached:e.completeness===`truncated`,startDate:e.startDate,endDate:e.endDate,pageKeyIndex:t,invalidMetricRows:a,zeroImpressionRows:o}}}function Jk(e){let t=new Set;return(e.error||e.blocked)&&t.add(`crawl-fetch-error`),(e.status<200||e.status>=300)&&t.add(`http-status`),(e.robotsTxt?.allowed===!1||e.declaredIndexability===`robots-blocked`)&&t.add(`robots-blocked`),e.declaredIndexability===`noindex`&&t.add(`noindex`),e.declaredIndexability===`canonical-conflict`&&t.add(`canonical-conflict`),e.declaredIndexability===`canonical-hint-other`&&t.add(`canonical-hint-other`),e.declaredIndexability===`not-html`&&t.add(`not-html`),!e.indexable&&t.size===0&&t.add(`crawler-marked-non-indexable`),t.size===0&&t.add(`unknown`),[...t].sort(zk)}function Yk(e,t){let n=e.slice(0,t);return{count:e.length,returned:n.length,omitted:e.length-n.length,items:n}}function Xk(e){return{crawl:e.crawlUrls.has(e.url),sitemap:e.sitemapUrls.has(e.url),searchConsole:e.searchUrls.has(e.url)}}function Zk(e){let t=[`Retained Search Console page rows show search visibility during the selected date range. They are not a complete index inventory and do not prove current indexing.`,`A crawlable URL absent from the retained Search Console export is not proven unindexed. It may have had no retained impressions, may be outside the export limit, or may need URL Inspection.`,`Use URL Inspection when you need Google's current indexed verdict for a specific URL. This report does not infer that verdict from crawl, sitemap, or Search Analytics data.`,`Repeated URL templates are grouped for review only. A cluster does not diagnose content quality, duplication, or index coverage.`];return e.searchConsole.completeness===`unavailable`?t.push(`Search Console data was unavailable, so absence-from-search comparisons are not available.`):e.searchConsole.completeness!==`complete`&&t.push(`The Search Console source is not complete. Missing URLs are especially unsafe to interpret as absent from search.`),e.crawl.completeness!==`complete`&&t.push(`The crawl source is not complete. Crawl counts and cross-source comparisons cover only retained pages.`),e.sitemap.completeness===`unavailable`?t.push(`No sitemap inventory was supplied. Sitemap-only and sitemap membership findings are unavailable.`):e.sitemap.completeness!==`complete`&&t.push(`The sitemap source is not complete. Sitemap-only counts cover only retained URLs.`),e.sitemap.completeness!==`unavailable`&&t.push(`Sitemap inclusion is a discovery hint. It does not prove that Google crawled or indexed a URL.`),e.searchConsole.zeroImpressionRows>0&&t.push(`Rows with zero impressions were excluded from retained search visibility instead of treating them as a separate indexing state.`),e.controlledWithVisibility>0&&t.push(`Some currently blocked or non-indexable crawl URLs also had retained search visibility. The crawl observation and historical date-range visibility describe different evidence and can coexist.`),t}function Qk(e){let t=Bk(e.limits?.itemsPerSection,100,1e3),n=Bk(e.limits?.templateClusters,50,200),r=Bk(e.limits?.templateSamples,5,25),i=Bk(e.templateThresholds?.minUrls,3,2**53-1),a=e.templateThresholds?.minShare,o=Number.isFinite(a)?Math.min(1,Math.max(0,a??Rk)):Rk,s=Wk(e.crawl),c=Gk(e.sitemap),l=qk(e.searchConsole),u=new Set(l.metrics.keys()),d=l.evidence.completeness!==`unavailable`,f=new Map;for(let e of s.pages){let t=Vk(e.url);if(!t)continue;let n=f.get(t);(!n||zk(JSON.stringify(e),JSON.stringify(n))<0)&&f.set(t,e)}let p=new Set(f.keys()),m=[...l.metrics.entries()].map(([e,t])=>({url:e,clicks:t.clicks,impressions:t.impressions,ctr:t.impressions?t.clicks/t.impressions:0,position:t.impressions?t.weightedPosition/t.impressions:0,sourceRows:t.sourceRows,sources:Xk({url:e,crawlUrls:p,sitemapUrls:c.urls,searchUrls:u}),crawlIndexable:f.get(e)?.indexable})).sort((e,t)=>t.clicks-e.clicks||t.impressions-e.impressions||zk(e.url,t.url)),h=[...f.entries()].filter(([e,t])=>d&&t.indexable&&!u.has(e)).map(([e,t])=>({url:e,status:t.status,declaredIndexability:t.declaredIndexability,inSitemap:c.urls.has(e)})).sort((e,t)=>zk(e.url,t.url)),g=[...f.entries()].filter(([,e])=>!e.indexable).map(([e,t])=>({url:e,status:t.status,declaredIndexability:t.declaredIndexability,reasons:Jk(t),inSitemap:c.urls.has(e),hasRetainedSearchVisibility:u.has(e)})).sort((e,t)=>zk(e.url,t.url)),_=[...c.urls].filter(e=>!p.has(e)&&!u.has(e)).sort(zk).map(e=>({url:e,sources:Xk({url:e,crawlUrls:p,sitemapUrls:c.urls,searchUrls:u})})),v=[...u].filter(e=>!p.has(e)&&!c.urls.has(e)).sort(zk).map(e=>({url:e,sources:Xk({url:e,crawlUrls:p,sitemapUrls:c.urls,searchUrls:u})})),y=new Set([...p,...c.urls,...u]),b=Lk(h.map(e=>e.url),{minUrls:i,minShare:o,limit:2**53-1,sampleSize:r}),x=b.slice(0,n),ee=g.filter(e=>e.hasRetainedSearchVisibility).length;return{schemaVersion:1,generatedAt:e.generatedAt,limits:{itemsPerSection:t,templateClusters:n,templateSamples:r},summary:{uniqueUrlsAcrossSources:y.size,retainedSearchVisibleUrls:m.length,crawlableCandidatesWithoutRetainedSearchVisibility:h.length,blockedOrNonIndexableCrawlUrls:g.length,sitemapOnlyUrls:_.length,searchConsoleOnlyUrls:v.length,repeatedTemplateClustersForReview:b.length},sources:{crawl:{...s.evidence,semantics:`local-crawl-page-snapshots`},sitemap:{...c.evidence,semantics:`submitted-discovery-hints`},searchConsole:l.evidence},retainedSearchVisibility:Yk(m,t),crawlableWithoutRetainedSearchVisibility:Yk(h,t),blockedOrNonIndexable:Yk(g,t),sitemapOnly:Yk(_,t),searchConsoleOnly:Yk(v,t),templateReview:{scope:`crawlable-without-retained-search-visibility`,eligibleUrlCount:h.length,count:b.length,returned:x.length,omitted:b.length-x.length,thresholds:{minUrls:i,minShare:o},clusters:x},caveats:Zk({crawl:s.evidence,sitemap:c.evidence,searchConsole:l.evidence,controlledWithVisibility:ee})}}const $k={loadCrawl:qb,latestCrawl:Yb,searchAnalytics:e_,fetchSitemap:Bx,now:()=>new Date};function eA(e,t){let n=e.crawlReportId?t.loadCrawl(e.crawlReportId):t.latestCrawl(e.site);if(!n)throw new A(`INVALID_INPUT`,e.crawlReportId?`No saved crawl report found for ${e.crawlReportId}.`:`No saved crawl report found for ${e.site}. Run a site crawl first or pass crawlReportId.`);if(n.site&&n.site!==e.site)throw new A(`INVALID_INPUT`,`Crawl report ${n.id} belongs to ${n.site}, not ${e.site}.`);return n}function tA(e){if(e.explicit?.length)return{source:`explicit`,urls:[...new Set(e.explicit)]};let t=e.report.ai?.robotsTxt;return t?.availability===`available`&&t.sitemapUrls.length?{source:`crawl-robots`,urls:[...new Set(t.sitemapUrls)]}:{source:`unavailable`,urls:[]}}function nA(e){return e.inventoryTruncated?`truncated`:e.results.every(e=>e.dataStatus===`complete`)?`complete`:`partial`}async function rA(e,t=$k){let n=Y({value:e.days,fallback:90,minimum:1,maximum:548,label:`days`}),r=Y({value:e.rowLimit,fallback:1e5,minimum:1,maximum:25e4,label:`rowLimit`}),i=Y({value:e.maxSitemapUrls,fallback:1e5,minimum:1,maximum:25e4,label:`maxSitemapUrls`}),a=eA(e,t),o=tA({explicit:e.sitemaps,report:a});if(o.urls.length>20)throw new A(`INVALID_INPUT`,`Pass at most 20 sitemap URLs.`);let s=t.now(),c=Zg(n,s),[l,u]=await Promise.all([t.searchAnalytics(e.site,{...c,dimensions:[`page`],type:`web`,dataState:`final`,maxRows:r},{refresh:e.refresh}),Promise.all(o.urls.map(e=>t.fetchSitemap({sitemapUrl:e,limit:i})))]),d=u.length?zx(u,i):void 0,f=l.rows.length>=r;return{...Qk({generatedAt:s.toISOString(),crawl:{report:a},...d?{sitemap:{urls:d.urls,completeness:nA({results:u,inventoryTruncated:d.truncation.possiblyTruncated}),rowLimit:i,rowLimitReached:d.truncation.inventoryLimitExceeded}}:{},searchConsole:{rows:l.rows,...c,rowLimit:r,completeness:f?`truncated`:`complete`,pageKeyIndex:0},limits:{itemsPerSection:e.itemsPerSection,templateClusters:e.templateClusters,templateSamples:e.templateSamples}}),site:e.site,input:{crawlReport:{id:a.id,generatedAt:a.generatedAt,status:a.status,mode:a.config.mode,startUrl:a.config.url},sitemapDocuments:{...o,warnings:u.flatMap(e=>e.warnings)},searchConsole:{days:n,rowLimit:r,calls:l.calls,rowsFetched:l.rowsFetched}}}}function iA(e){try{return new URL(e)}catch{return}}function aA(e){if(e.startsWith(`sc-domain:`))return;let t=iA(e);return t?t.origin:void 0}function oA(e,t){try{return Ng(e,t),!0}catch{return!1}}function sA(e,t){return t.filter(t=>oA(t,e)).sort((e,t)=>e.startsWith(`sc-domain:`)&&!t.startsWith(`sc-domain:`)?1:!e.startsWith(`sc-domain:`)&&t.startsWith(`sc-domain:`)?-1:t.length-e.length||e.localeCompare(t))[0]}function cA(e,t){if(t.startsWith(`sc-domain:`)){let n=t.slice(10).toLowerCase();if(e.startsWith(`sc-domain:`)){let t=e.slice(10).toLowerCase();return t===n||t.endsWith(`.${n}`)}let r=iA(e)?.hostname.toLowerCase();return r===n||!!r?.endsWith(`.${n}`)}if(e.startsWith(`sc-domain:`)){let n=iA(t),r=e.slice(10).toLowerCase();return!!(n&&(n.hostname.toLowerCase()===r||n.hostname.toLowerCase().endsWith(`.${r}`)))}try{return Ng(t,e)===new URL(e).toString()}catch{return!1}}function lA(e,t){return[...new Set([e,...t])].filter(t=>cA(t,e))}function uA(e){let t=[...new Set(e.urls.map(t=>Ng(e.site,t)))],n=lA(e.site,e.accountProperties),r=new Map;for(let e of t){let t=sA(e,n);if(!t)continue;let i=r.get(t)??[];i.push(e),r.set(t,i)}return[...r.entries()].map(([e,t])=>({property:e,urls:t})).sort((e,t)=>t.urls.length-e.urls.length)}function dA(e,t){return e<=0?0:Math.ceil(e/t)}function fA(e){let t=iA(e);if(!t)return;let n=t.pathname.split(`/`).filter(Boolean)[0];if(n)return`${t.origin}/${n}/`}function pA(e,t){return aA(e)??aA(t)}function mA(e){let t=[],n=e.dailyLimit*e.targetCycleDays;for(let r of e.properties){if(r.urlCount<=n)continue;let i=e.urlsByProperty.get(r.property)??[],a=new Map;for(let t of i){let n=fA(t);if(!n||e.existingProperties.some(e=>e===n))continue;let i=pA(r.property,e.site);if(i&&!n.startsWith(i))continue;let o=a.get(n)??[];o.push(t),a.set(n,o)}for(let[i,o]of[...a.entries()].sort((e,t)=>t[1].length-e[1].length))if(!(o.length<Math.min(100,n))&&(t.push({property:i,urlCount:o.length,currentProperty:r.property,estimatedCycleDays:dA(o.length,e.dailyLimit),sampleUrls:o.slice(0,3),reason:`${o.length} URLs share this folder. A URL-prefix property would give it a separate daily inspection bucket.`}),t.length>=e.limit))return t}return t}async function hA(e){if(!e.sitemaps.length||e.sitemaps.length>20)throw new A(`INVALID_INPUT`,`Pass between 1 and 20 sitemap URLs.`);let t=Y({value:e.maxUrls,fallback:5e4,minimum:1,maximum:25e4,label:`maxUrls`}),n=[],r=e.sitemaps.map(Mg),i=await Promise.all(r.map(e=>Bx({sitemapUrl:e,limit:t}))),a=zx(i,t),o=a.urls;n.push(...i.flatMap(e=>e.warnings)),a.truncation.possiblyTruncated&&n.push(`Sitemap discovery exceeded a configured URL or nested-sitemap boundary; coverage planning may be incomplete.`);let s=e.properties?[...e.properties]:await s_(e.refresh).then(e=>e.map(e=>e.siteUrl));return gA({site:e.site,urls:o,sitemaps:r,accountProperties:s,dailyLimit:e.dailyLimit,targetCycleDays:e.targetCycleDays,suggestionLimit:e.suggestionLimit,warnings:n})}function gA(e){let t=Y({value:e.dailyLimit,fallback:2e3,minimum:1,maximum:2e3,label:`dailyLimit`}),n=Y({value:e.targetCycleDays,fallback:1,minimum:1,maximum:365,label:`targetCycleDays`}),r=Y({value:e.suggestionLimit,fallback:10,minimum:0,maximum:100,label:`suggestionLimit`}),i=[...e.warnings??[]],a=[];for(let t of[...new Set(e.urls)])try{a.push(Ng(e.site,t))}catch(e){i.push(e instanceof Error?e.message:String(e))}let o=uA({site:e.site,urls:a,accountProperties:e.accountProperties}),s=new Map(o.map(e=>[e.property,e.urls])),c=o.map(e=>({property:e.property,urlCount:e.urls.length,dailyLimit:t,cycleDays:dA(e.urls.length,t),sampleUrls:e.urls.slice(0,3)})).sort((e,t)=>t.urlCount-e.urlCount),l=c.length*t,u=dA(a.length,l||t),d=mA({properties:c,urlsByProperty:s,existingProperties:e.accountProperties,site:e.site,dailyLimit:t,targetCycleDays:n,limit:r});return{site:e.site,generatedAt:new Date().toISOString(),summary:{sitemapUrls:e.sitemaps?.length??0,urlCount:a.length,properties:c.length,dailyCapacity:l,estimatedCycleDays:u,targetCycleDays:n,suggestedProperties:d.length},properties:c,suggestions:d,warnings:i}}const _A=[`verdict`,`indexingState`,`robotsTxtState`,`pageFetchState`,`googleCanonical`,`userCanonical`];function vA(e){return e===`PASS`?`indexed`:e===`NEUTRAL`?`excluded`:e===`FAIL`?`invalid`:`unknown`}function yA(e){if(e)try{let t=new URL(e);return t.hash=``,t.toString()}catch{return e}}function bA(e){let t=[];return e.verdict===`FAIL`&&t.push(`verdict_invalid`),e.verdict===`NEUTRAL`&&t.push(`verdict_excluded`),e.robotsTxtState===`DISALLOWED`&&t.push(`robots_disallowed`),e.indexingState===`BLOCKED_BY_META_TAG`&&t.push(`indexing_blocked_meta`),e.indexingState===`BLOCKED_BY_HTTP_HEADER`&&t.push(`indexing_blocked_header`),e.pageFetchState&&![`PAGE_FETCH_STATE_UNSPECIFIED`,`SUCCESSFUL`].includes(e.pageFetchState)&&t.push(`page_fetch_failed`),e.googleCanonical&&e.userCanonical&&yA(e.googleCanonical)!==yA(e.userCanonical)&&t.push(`canonical_mismatch`),t}function xA(e,t){return e?_A.flatMap(n=>{let r=e[n],i=t[n];return(n===`googleCanonical`||n===`userCanonical`?yA(r)===yA(i):r===i)?[]:[{field:n,before:r,after:i}]}):[]}function SA(e){if(!e.previous)return`baseline`;if(!e.changes.length)return`unchanged`;let t=bA(e.previous).length>0;return!t&&e.currentIssue?`regression`:t&&!e.currentIssue?`recovery`:`changed`}function CA(e){let t=e.result.inspectionResult?.indexStatusResult,n={rootSite:e.rootSite,property:e.property,url:e.url,inspectedAt:e.inspectedAt,inspectionStatus:`succeeded`,requestSent:!0,indexStatus:vA(t?.verdict),verdict:t?.verdict,coverageState:t?.coverageState,indexingState:t?.indexingState,robotsTxtState:t?.robotsTxtState,pageFetchState:t?.pageFetchState,googleCanonical:t?.googleCanonical,userCanonical:t?.userCanonical,lastCrawlTime:t?.lastCrawlTime,previous:e.previous,issueCodes:[],currentIssue:!1,severity:`none`,changes:[],changeKind:`baseline`,changed:!1,regression:!1,recovery:!1,alert:!1};return n.issueCodes=bA(n),n.currentIssue=n.issueCodes.length>0,n.severity=n.issueCodes.some(e=>[`verdict_invalid`,`robots_disallowed`,`indexing_blocked_meta`,`indexing_blocked_header`,`page_fetch_failed`].includes(e))?`high`:n.currentIssue?`medium`:`none`,n.changes=xA(e.previous,n),n.changeKind=SA({previous:e.previous,changes:n.changes,currentIssue:n.currentIssue}),n.changed=n.changes.length>0,n.regression=n.changeKind===`regression`,n.recovery=n.changeKind===`recovery`,n.alert=n.regression||n.changeKind===`baseline`&&n.currentIssue,n}function wA(e){return{rootSite:e.rootSite,property:e.property,url:e.url,inspectedAt:e.inspectedAt,inspectionStatus:e.deferred?`deferred`:e.quotaBlocked?`quota-blocked`:`failed`,requestSent:e.requestSent??!0,indexStatus:`unknown`,issueCodes:[e.quotaBlocked?`inspection_quota_blocked`:`inspection_failed`],currentIssue:!1,severity:`none`,changes:[],changeKind:`not-comparable`,changed:!1,regression:!1,recovery:!1,alert:!1,errorCode:e.errorCode,errorMessage:e.errorMessage,retryAt:e.retryAt}}function TA(e){if(e.inspected_at)return{inspectedAt:new Date(e.inspected_at).toISOString(),verdict:e.verdict??void 0,coverageState:e.coverage_state??void 0,indexingState:e.indexing_state??void 0,robotsTxtState:e.robots_txt_state??void 0,pageFetchState:e.page_fetch_state??void 0,googleCanonical:e.google_canonical??void 0,userCanonical:e.user_canonical??void 0,lastCrawlTime:e.last_crawl_time??void 0}}const EA={inspectUrl:qg,now:()=>new Date,store:{latest(e,t){let n=j().prepare(`SELECT *
628
+ LIMIT ?`).all(e,t).map(e=>({...Ik(e),statusErrors:e.status_errors,nonIndexable:e.non_indexable,recommendations:e.recommendation_count,highPriorityRecommendations:e.high_recommendation_count,topRecommendation:e.top_recommendation_url?{url:e.top_recommendation_url,title:e.top_recommendation_title??``,action:e.top_recommendation_action??``,severity:e.top_recommendation_severity??`medium`}:void 0}))}function Uk(e){let t=new Map(e.current.map(e=>[e.url,e])),n=new Map(e.previous.map(e=>[e.url,e])),r=[];for(let[e,i]of t.entries()){let t=n.get(e);if(!t){r.push({url:e,kind:`added`,changes:[`url_added`],after:i});continue}let a=[];t.status!==i.status&&a.push(`status`),t.title!==i.title&&a.push(`title`),t.metaDescription!==i.metaDescription&&a.push(`meta_description`),t.canonical!==i.canonical&&a.push(`canonical`),t.h1!==i.h1&&a.push(`h1`),t.indexable!==i.indexable&&a.push(`indexability`),t.contentHash!==i.contentHash&&a.push(`content`),a.length&&r.push({url:e,kind:`changed`,changes:a,before:t,after:i})}for(let[e,i]of n.entries())t.has(e)||r.push({url:e,kind:`removed`,changes:[`url_removed`],before:i});return r}async function Wk(e){let t=new URL(e.startUrl).toString(),n=e.site??new URL(t).origin,r=e.limit??50,i=[t],a=new Set,o=[],s=[];for(;i.length&&o.length<r;){let t=i.shift();if(!t||a.has(t))continue;a.add(t);let n=await lS(t,{refresh:e.refresh,js:e.js??`auto`});n.warning&&s.push(n.warning),n.page&&o.push(n.page);for(let e of n.urls)!a.has(e)&&i.length+o.length<r*3&&i.push(e)}let c={id:v(),site:n,startUrl:t,createdAt:new Date().toISOString(),limit:r,urlCount:o.length},l=zk({site:n,startUrl:t,currentRunId:c.id}),u=l?[...Bk(l.id).values()]:[],d=l?Pk(Uk({current:o,previous:u})):[],f=Fk(d);return Vk(c,o,f),{run:c,previousRun:l,summary:{crawled:o.length,added:d.filter(e=>e.kind===`added`).length,removed:d.filter(e=>e.kind===`removed`).length,changed:d.filter(e=>e.kind===`changed`).length,newErrors:d.filter(e=>e.kind!==`removed`&&(e.after?.status??0)>=400&&(e.before?.status??200)<400).length,indexabilityFlips:d.filter(e=>e.changes.includes(`indexability`)).length,highPriorityRecommendations:f.filter(e=>e.severity===`high`).length},recommendations:f,items:d,warnings:s}}const Gk=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,Kk=/^\d{4}-\d{2}-\d{2}$/;function qk(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)return t}return n.length-r.length}function Jk(e){let t=e=>{try{return decodeURIComponent(e)}catch{return e}};try{return new URL(e).pathname.split(`/`).filter(Boolean).map(t)}catch{return e.split(`?`)[0]?.split(`/`).filter(Boolean).map(t)??[]}}function Yk(e){if(/^\d+$/.test(e))return`:num`;if(Gk.test(e))return`:id`;if(Kk.test(e))return`:date`;if(/^[\p{L}\p{N}]+(?:-[\p{L}\p{N}]+)+$/u.test(e)||e.length>=12&&/^[\p{L}\p{N}]+$/u.test(e))return`:slug`}function Xk(e){let t=Jk(e);return t.length?`/${t.map((e,t)=>{let n=Yk(e);return n?t===0&&n===`:slug`?e.toLowerCase():n:e.toLowerCase()}).join(`/`)}`:`/`}function Zk(e){let t=Jk(e);if(!t.length)return`/`;let[n,...r]=t;return n?r.length?`/${n.toLowerCase()}/${r.map(e=>Yk(e)??`:value`).join(`/`)}`:`/${Yk(n)??n.toLowerCase()}`:`/`}function Qk(e){let t=new Map;for(let n of e){let e=n.normalize(`NFKC`).toLowerCase().match(/[\p{L}\p{N}]+/gu)??[];for(let n of e.filter(e=>e.length>1||/[^\p{ASCII}]/u.test(e)))t.set(n,(t.get(n)??0)+1)}return[...t.entries()].sort((e,t)=>t[1]-e[1]||qk(e[0],t[0])).slice(0,8).map(([e])=>e)}function $k(e,t){let n=e.split(`/`).filter(Boolean),r=t.map(Jk),i=Math.max(n.length,...r.map(e=>e.length)),a=[],o=[];for(let e=0;e<i;e+=1){let t=n[e]??`:missing`,i=r.map(t=>t[e]?.toLowerCase()).filter(e=>!!e),s=[...new Set(i)];t.startsWith(`:`)||s.length>1?o.push({index:e,placeholder:t.startsWith(`:`)?t:`:value`,distinctValues:s.length,examples:s.slice(0,5),tokenExamples:Qk(s)}):t&&a.push({index:e,value:t})}return{depth:i,staticSegments:a,variableSegments:o}}function eA(e,t){let n=Xk(e);if(t.some(e=>e.signature===n))return n;let r=Zk(e);return t.some(e=>e.signature===r)?r:n}function tA(e,t){let n=new Map;for(let r of e){let e=t.get(r);if(!e)continue;let i=n.get(e)??[];i.push(r),n.set(e,i)}return n}function nA(e,t={}){let n=[...new Set(e)].sort(qk),r=t.minUrls??3,i=t.minShare??.01,a=t.sampleSize??5,o=new Map(n.map(e=>[e,Xk(e)])),s=tA(n,o),c=new Set([...s.entries()].filter(([,e])=>e.length>=r&&e.length/n.length>=i).map(([e])=>e)),l=new Map;for(let e of n){let t=o.get(e)??`/`;l.set(e,c.has(t)?t:Zk(e))}return[...tA(n,l).entries()].map(([e,t])=>({id:e,signature:e,shape:$k(e,t),urlCount:t.length,share:n.length?t.length/n.length:0,sampleUrls:t.sort(qk).slice(0,a)})).filter(e=>e.urlCount>=r&&e.share>=i).sort((e,t)=>t.urlCount-e.urlCount||qk(e.signature,t.signature)).slice(0,t.limit??50)}const rA=.01;function iA(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+=1){let t=(n[e]??0)-(r[e]??0);if(t)return t}return n.length-r.length}function aA(e,t,n){return e===void 0||!Number.isFinite(e)?t:Math.min(n,Math.max(1,Math.trunc(e)))}function oA(e){try{let t=new URL(e.trim());return![`http:`,`https:`].includes(t.protocol)||t.username||t.password?void 0:(t.hash=``,t.toString())}catch{return}}function sA(e){return e!==void 0}function cA(e){return{...e,duplicateUrls:e.validUrls-e.uniqueUrls,invalidUrls:e.rowsReceived-e.validUrls}}function lA(e){let t=`report`in e?e.report:void 0,n=t?.pages??(`pages`in e?e.pages:[]),r=n.map(e=>oA(e.url)).filter(sA),i=new Set(r),a=t?.config.maxPages??(`rowLimit`in e?e.rowLimit:void 0),o=t?.summary.pageLimitReached??(`rowLimitReached`in e?e.rowLimitReached:void 0);return{pages:n,evidence:cA({completeness:t?t.summary.pageLimitReached?`truncated`:t.status===`completed`?`complete`:t.status===`partial`?`partial`:`unknown`:`completeness`in e?e.completeness:`unknown`,rowsReceived:n.length,validUrls:r.length,uniqueUrls:i.size,rowLimit:a,rowLimitReached:o})}}function uA(e){if(!e)return{urls:new Set,evidence:cA({completeness:`unavailable`,rowsReceived:0,validUrls:0,uniqueUrls:0})};let t=e.urls.map(oA).filter(sA),n=new Set(t);return{urls:n,evidence:cA({completeness:e.completeness,rowsReceived:e.urls.length,validUrls:t.length,uniqueUrls:n.size,rowLimit:e.rowLimit,rowLimitReached:e.rowLimitReached})}}function dA(e){return Number.isFinite(e)&&e>=0}function fA(e){if(!e)return{metrics:new Map,evidence:{...cA({completeness:`unavailable`,rowsReceived:0,validUrls:0,uniqueUrls:0}),semantics:`retained-search-analytics-page-rows`,invalidMetricRows:0,zeroImpressionRows:0}};let t=e.pageKeyIndex??0,n=new Map,r=[],i=0,a=0,o=0;for(let n of e.rows){let e=oA(n.keys[t]??``);if(!e){i+=1;continue}if(!dA(n.clicks)||!dA(n.impressions)||!dA(n.position)||n.clicks>n.impressions){a+=1;continue}if(n.impressions===0){o+=1;continue}r.push({url:e,row:n})}r.sort((e,t)=>iA(e.url,t.url)||e.row.clicks-t.row.clicks||e.row.impressions-t.row.impressions||e.row.position-t.row.position||iA(JSON.stringify(e.row.keys),JSON.stringify(t.row.keys)));for(let{url:e,row:t}of r){let r=n.get(e)??{clicks:0,impressions:0,weightedPosition:0,sourceRows:0};r.clicks+=t.clicks,r.impressions+=t.impressions,r.weightedPosition+=t.position*t.impressions,r.sourceRows+=1,n.set(e,r)}let s=e.rows.length-i-a-o;return{metrics:n,evidence:{semantics:`retained-search-analytics-page-rows`,completeness:e.completeness,rowsReceived:e.rows.length,validUrls:s,uniqueUrls:n.size,duplicateUrls:s-n.size,invalidUrls:i,rowLimit:e.rowLimit,rowLimitReached:e.completeness===`truncated`,startDate:e.startDate,endDate:e.endDate,pageKeyIndex:t,invalidMetricRows:a,zeroImpressionRows:o}}}function pA(e){let t=new Set;return(e.error||e.blocked)&&t.add(`crawl-fetch-error`),(e.status<200||e.status>=300)&&t.add(`http-status`),(e.robotsTxt?.allowed===!1||e.declaredIndexability===`robots-blocked`)&&t.add(`robots-blocked`),e.declaredIndexability===`noindex`&&t.add(`noindex`),e.declaredIndexability===`canonical-conflict`&&t.add(`canonical-conflict`),e.declaredIndexability===`canonical-hint-other`&&t.add(`canonical-hint-other`),e.declaredIndexability===`not-html`&&t.add(`not-html`),!e.indexable&&t.size===0&&t.add(`crawler-marked-non-indexable`),t.size===0&&t.add(`unknown`),[...t].sort(iA)}function mA(e,t){let n=e.slice(0,t);return{count:e.length,returned:n.length,omitted:e.length-n.length,items:n}}function hA(e){return{crawl:e.crawlUrls.has(e.url),sitemap:e.sitemapUrls.has(e.url),searchConsole:e.searchUrls.has(e.url)}}function gA(e){let t=[`Retained Search Console page rows show search visibility during the selected date range. They are not a complete index inventory and do not prove current indexing.`,`A crawlable URL absent from the retained Search Console export is not proven unindexed. It may have had no retained impressions, may be outside the export limit, or may need URL Inspection.`,`Use URL Inspection when you need Google's current indexed verdict for a specific URL. This report does not infer that verdict from crawl, sitemap, or Search Analytics data.`,`Repeated URL templates are grouped for review only. A cluster does not diagnose content quality, duplication, or index coverage.`];return e.searchConsole.completeness===`unavailable`?t.push(`Search Console data was unavailable, so absence-from-search comparisons are not available.`):e.searchConsole.completeness!==`complete`&&t.push(`The Search Console source is not complete. Missing URLs are especially unsafe to interpret as absent from search.`),e.crawl.completeness!==`complete`&&t.push(`The crawl source is not complete. Crawl counts and cross-source comparisons cover only retained pages.`),e.sitemap.completeness===`unavailable`?t.push(`No sitemap inventory was supplied. Sitemap-only and sitemap membership findings are unavailable.`):e.sitemap.completeness!==`complete`&&t.push(`The sitemap source is not complete. Sitemap-only counts cover only retained URLs.`),e.sitemap.completeness!==`unavailable`&&t.push(`Sitemap inclusion is a discovery hint. It does not prove that Google crawled or indexed a URL.`),e.searchConsole.zeroImpressionRows>0&&t.push(`Rows with zero impressions were excluded from retained search visibility instead of treating them as a separate indexing state.`),e.controlledWithVisibility>0&&t.push(`Some currently blocked or non-indexable crawl URLs also had retained search visibility. The crawl observation and historical date-range visibility describe different evidence and can coexist.`),t}function _A(e){let t=aA(e.limits?.itemsPerSection,100,1e3),n=aA(e.limits?.templateClusters,50,200),r=aA(e.limits?.templateSamples,5,25),i=aA(e.templateThresholds?.minUrls,3,2**53-1),a=e.templateThresholds?.minShare,o=Number.isFinite(a)?Math.min(1,Math.max(0,a??rA)):rA,s=lA(e.crawl),c=uA(e.sitemap),l=fA(e.searchConsole),u=new Set(l.metrics.keys()),d=l.evidence.completeness!==`unavailable`,f=new Map;for(let e of s.pages){let t=oA(e.url);if(!t)continue;let n=f.get(t);(!n||iA(JSON.stringify(e),JSON.stringify(n))<0)&&f.set(t,e)}let p=new Set(f.keys()),m=[...l.metrics.entries()].map(([e,t])=>({url:e,clicks:t.clicks,impressions:t.impressions,ctr:t.impressions?t.clicks/t.impressions:0,position:t.impressions?t.weightedPosition/t.impressions:0,sourceRows:t.sourceRows,sources:hA({url:e,crawlUrls:p,sitemapUrls:c.urls,searchUrls:u}),crawlIndexable:f.get(e)?.indexable})).sort((e,t)=>t.clicks-e.clicks||t.impressions-e.impressions||iA(e.url,t.url)),h=[...f.entries()].filter(([e,t])=>d&&t.indexable&&!u.has(e)).map(([e,t])=>({url:e,status:t.status,declaredIndexability:t.declaredIndexability,inSitemap:c.urls.has(e)})).sort((e,t)=>iA(e.url,t.url)),g=[...f.entries()].filter(([,e])=>!e.indexable).map(([e,t])=>({url:e,status:t.status,declaredIndexability:t.declaredIndexability,reasons:pA(t),inSitemap:c.urls.has(e),hasRetainedSearchVisibility:u.has(e)})).sort((e,t)=>iA(e.url,t.url)),_=[...c.urls].filter(e=>!p.has(e)&&!u.has(e)).sort(iA).map(e=>({url:e,sources:hA({url:e,crawlUrls:p,sitemapUrls:c.urls,searchUrls:u})})),v=[...u].filter(e=>!p.has(e)&&!c.urls.has(e)).sort(iA).map(e=>({url:e,sources:hA({url:e,crawlUrls:p,sitemapUrls:c.urls,searchUrls:u})})),y=new Set([...p,...c.urls,...u]),b=nA(h.map(e=>e.url),{minUrls:i,minShare:o,limit:2**53-1,sampleSize:r}),x=b.slice(0,n),ee=g.filter(e=>e.hasRetainedSearchVisibility).length;return{schemaVersion:1,generatedAt:e.generatedAt,limits:{itemsPerSection:t,templateClusters:n,templateSamples:r},summary:{uniqueUrlsAcrossSources:y.size,retainedSearchVisibleUrls:m.length,crawlableCandidatesWithoutRetainedSearchVisibility:h.length,blockedOrNonIndexableCrawlUrls:g.length,sitemapOnlyUrls:_.length,searchConsoleOnlyUrls:v.length,repeatedTemplateClustersForReview:b.length},sources:{crawl:{...s.evidence,semantics:`local-crawl-page-snapshots`},sitemap:{...c.evidence,semantics:`submitted-discovery-hints`},searchConsole:l.evidence},retainedSearchVisibility:mA(m,t),crawlableWithoutRetainedSearchVisibility:mA(h,t),blockedOrNonIndexable:mA(g,t),sitemapOnly:mA(_,t),searchConsoleOnly:mA(v,t),templateReview:{scope:`crawlable-without-retained-search-visibility`,eligibleUrlCount:h.length,count:b.length,returned:x.length,omitted:b.length-x.length,thresholds:{minUrls:i,minShare:o},clusters:x},caveats:gA({crawl:s.evidence,sitemap:c.evidence,searchConsole:l.evidence,controlledWithVisibility:ee})}}const vA={loadCrawl:Ox,latestCrawl:Ax,searchAnalytics:e_,fetchSitemap:xS,now:()=>new Date};function yA(e,t){let n=e.crawlReportId?t.loadCrawl(e.crawlReportId):t.latestCrawl(e.site);if(!n)throw new A(`INVALID_INPUT`,e.crawlReportId?`No saved crawl report found for ${e.crawlReportId}.`:`No saved crawl report found for ${e.site}. Run a site crawl first or pass crawlReportId.`);if(n.site&&n.site!==e.site)throw new A(`INVALID_INPUT`,`Crawl report ${n.id} belongs to ${n.site}, not ${e.site}.`);return n}function bA(e){if(e.explicit?.length)return{source:`explicit`,urls:[...new Set(e.explicit)]};let t=e.report.ai?.robotsTxt;return t?.availability===`available`&&t.sitemapUrls.length?{source:`crawl-robots`,urls:[...new Set(t.sitemapUrls)]}:{source:`unavailable`,urls:[]}}function xA(e){return e.inventoryTruncated?`truncated`:e.results.every(e=>e.dataStatus===`complete`)?`complete`:`partial`}async function SA(e,t=vA){let n=Y({value:e.days,fallback:90,minimum:1,maximum:548,label:`days`}),r=Y({value:e.rowLimit,fallback:1e5,minimum:1,maximum:25e4,label:`rowLimit`}),i=Y({value:e.maxSitemapUrls,fallback:1e5,minimum:1,maximum:25e4,label:`maxSitemapUrls`}),a=yA(e,t),o=bA({explicit:e.sitemaps,report:a});if(o.urls.length>20)throw new A(`INVALID_INPUT`,`Pass at most 20 sitemap URLs.`);let s=t.now(),c=Zg(n,s),[l,u]=await Promise.all([t.searchAnalytics(e.site,{...c,dimensions:[`page`],type:`web`,dataState:`final`,maxRows:r},{refresh:e.refresh}),Promise.all(o.urls.map(e=>t.fetchSitemap({sitemapUrl:e,limit:i})))]),d=u.length?bS(u,i):void 0,f=l.rows.length>=r;return{..._A({generatedAt:s.toISOString(),crawl:{report:a},...d?{sitemap:{urls:d.urls,completeness:xA({results:u,inventoryTruncated:d.truncation.possiblyTruncated}),rowLimit:i,rowLimitReached:d.truncation.inventoryLimitExceeded}}:{},searchConsole:{rows:l.rows,...c,rowLimit:r,completeness:f?`truncated`:`complete`,pageKeyIndex:0},limits:{itemsPerSection:e.itemsPerSection,templateClusters:e.templateClusters,templateSamples:e.templateSamples}}),site:e.site,input:{crawlReport:{id:a.id,generatedAt:a.generatedAt,status:a.status,mode:a.config.mode,startUrl:a.config.url},sitemapDocuments:{...o,warnings:u.flatMap(e=>e.warnings)},searchConsole:{days:n,rowLimit:r,calls:l.calls,rowsFetched:l.rowsFetched}}}}function CA(e){try{return new URL(e)}catch{return}}function wA(e){if(e.startsWith(`sc-domain:`))return;let t=CA(e);return t?t.origin:void 0}function TA(e,t){try{return Ng(e,t),!0}catch{return!1}}function EA(e,t){return t.filter(t=>TA(t,e)).sort((e,t)=>e.startsWith(`sc-domain:`)&&!t.startsWith(`sc-domain:`)?1:!e.startsWith(`sc-domain:`)&&t.startsWith(`sc-domain:`)?-1:t.length-e.length||e.localeCompare(t))[0]}function DA(e,t){if(t.startsWith(`sc-domain:`)){let n=t.slice(10).toLowerCase();if(e.startsWith(`sc-domain:`)){let t=e.slice(10).toLowerCase();return t===n||t.endsWith(`.${n}`)}let r=CA(e)?.hostname.toLowerCase();return r===n||!!r?.endsWith(`.${n}`)}if(e.startsWith(`sc-domain:`)){let n=CA(t),r=e.slice(10).toLowerCase();return!!(n&&(n.hostname.toLowerCase()===r||n.hostname.toLowerCase().endsWith(`.${r}`)))}try{return Ng(t,e)===new URL(e).toString()}catch{return!1}}function OA(e,t){return[...new Set([e,...t])].filter(t=>DA(t,e))}function kA(e){let t=[...new Set(e.urls.map(t=>Ng(e.site,t)))],n=OA(e.site,e.accountProperties),r=new Map;for(let e of t){let t=EA(e,n);if(!t)continue;let i=r.get(t)??[];i.push(e),r.set(t,i)}return[...r.entries()].map(([e,t])=>({property:e,urls:t})).sort((e,t)=>t.urls.length-e.urls.length)}function AA(e,t){return e<=0?0:Math.ceil(e/t)}function jA(e){let t=CA(e);if(!t)return;let n=t.pathname.split(`/`).filter(Boolean)[0];if(n)return`${t.origin}/${n}/`}function MA(e,t){return wA(e)??wA(t)}function NA(e){let t=[],n=e.dailyLimit*e.targetCycleDays;for(let r of e.properties){if(r.urlCount<=n)continue;let i=e.urlsByProperty.get(r.property)??[],a=new Map;for(let t of i){let n=jA(t);if(!n||e.existingProperties.some(e=>e===n))continue;let i=MA(r.property,e.site);if(i&&!n.startsWith(i))continue;let o=a.get(n)??[];o.push(t),a.set(n,o)}for(let[i,o]of[...a.entries()].sort((e,t)=>t[1].length-e[1].length))if(!(o.length<Math.min(100,n))&&(t.push({property:i,urlCount:o.length,currentProperty:r.property,estimatedCycleDays:AA(o.length,e.dailyLimit),sampleUrls:o.slice(0,3),reason:`${o.length} URLs share this folder. A URL-prefix property would give it a separate daily inspection bucket.`}),t.length>=e.limit))return t}return t}async function PA(e){if(!e.sitemaps.length||e.sitemaps.length>20)throw new A(`INVALID_INPUT`,`Pass between 1 and 20 sitemap URLs.`);let t=Y({value:e.maxUrls,fallback:5e4,minimum:1,maximum:25e4,label:`maxUrls`}),n=[],r=e.sitemaps.map(Mg),i=await Promise.all(r.map(e=>xS({sitemapUrl:e,limit:t}))),a=bS(i,t),o=a.urls;n.push(...i.flatMap(e=>e.warnings)),a.truncation.possiblyTruncated&&n.push(`Sitemap discovery exceeded a configured URL or nested-sitemap boundary; coverage planning may be incomplete.`);let s=e.properties?[...e.properties]:await s_(e.refresh).then(e=>e.map(e=>e.siteUrl));return FA({site:e.site,urls:o,sitemaps:r,accountProperties:s,dailyLimit:e.dailyLimit,targetCycleDays:e.targetCycleDays,suggestionLimit:e.suggestionLimit,warnings:n})}function FA(e){let t=Y({value:e.dailyLimit,fallback:2e3,minimum:1,maximum:2e3,label:`dailyLimit`}),n=Y({value:e.targetCycleDays,fallback:1,minimum:1,maximum:365,label:`targetCycleDays`}),r=Y({value:e.suggestionLimit,fallback:10,minimum:0,maximum:100,label:`suggestionLimit`}),i=[...e.warnings??[]],a=[];for(let t of[...new Set(e.urls)])try{a.push(Ng(e.site,t))}catch(e){i.push(e instanceof Error?e.message:String(e))}let o=kA({site:e.site,urls:a,accountProperties:e.accountProperties}),s=new Map(o.map(e=>[e.property,e.urls])),c=o.map(e=>({property:e.property,urlCount:e.urls.length,dailyLimit:t,cycleDays:AA(e.urls.length,t),sampleUrls:e.urls.slice(0,3)})).sort((e,t)=>t.urlCount-e.urlCount),l=c.length*t,u=AA(a.length,l||t),d=NA({properties:c,urlsByProperty:s,existingProperties:e.accountProperties,site:e.site,dailyLimit:t,targetCycleDays:n,limit:r});return{site:e.site,generatedAt:new Date().toISOString(),summary:{sitemapUrls:e.sitemaps?.length??0,urlCount:a.length,properties:c.length,dailyCapacity:l,estimatedCycleDays:u,targetCycleDays:n,suggestedProperties:d.length},properties:c,suggestions:d,warnings:i}}const IA=[`verdict`,`indexingState`,`robotsTxtState`,`pageFetchState`,`googleCanonical`,`userCanonical`];function LA(e){return e===`PASS`?`indexed`:e===`NEUTRAL`?`excluded`:e===`FAIL`?`invalid`:`unknown`}function RA(e){if(e)try{let t=new URL(e);return t.hash=``,t.toString()}catch{return e}}function zA(e){let t=[];return e.verdict===`FAIL`&&t.push(`verdict_invalid`),e.verdict===`NEUTRAL`&&t.push(`verdict_excluded`),e.robotsTxtState===`DISALLOWED`&&t.push(`robots_disallowed`),e.indexingState===`BLOCKED_BY_META_TAG`&&t.push(`indexing_blocked_meta`),e.indexingState===`BLOCKED_BY_HTTP_HEADER`&&t.push(`indexing_blocked_header`),e.pageFetchState&&![`PAGE_FETCH_STATE_UNSPECIFIED`,`SUCCESSFUL`].includes(e.pageFetchState)&&t.push(`page_fetch_failed`),e.googleCanonical&&e.userCanonical&&RA(e.googleCanonical)!==RA(e.userCanonical)&&t.push(`canonical_mismatch`),t}function BA(e,t){return e?IA.flatMap(n=>{let r=e[n],i=t[n];return(n===`googleCanonical`||n===`userCanonical`?RA(r)===RA(i):r===i)?[]:[{field:n,before:r,after:i}]}):[]}function VA(e){if(!e.previous)return`baseline`;if(!e.changes.length)return`unchanged`;let t=zA(e.previous).length>0;return!t&&e.currentIssue?`regression`:t&&!e.currentIssue?`recovery`:`changed`}function HA(e){let t=e.result.inspectionResult?.indexStatusResult,n={rootSite:e.rootSite,property:e.property,url:e.url,inspectedAt:e.inspectedAt,inspectionStatus:`succeeded`,requestSent:!0,indexStatus:LA(t?.verdict),verdict:t?.verdict,coverageState:t?.coverageState,indexingState:t?.indexingState,robotsTxtState:t?.robotsTxtState,pageFetchState:t?.pageFetchState,googleCanonical:t?.googleCanonical,userCanonical:t?.userCanonical,lastCrawlTime:t?.lastCrawlTime,previous:e.previous,issueCodes:[],currentIssue:!1,severity:`none`,changes:[],changeKind:`baseline`,changed:!1,regression:!1,recovery:!1,alert:!1};return n.issueCodes=zA(n),n.currentIssue=n.issueCodes.length>0,n.severity=n.issueCodes.some(e=>[`verdict_invalid`,`robots_disallowed`,`indexing_blocked_meta`,`indexing_blocked_header`,`page_fetch_failed`].includes(e))?`high`:n.currentIssue?`medium`:`none`,n.changes=BA(e.previous,n),n.changeKind=VA({previous:e.previous,changes:n.changes,currentIssue:n.currentIssue}),n.changed=n.changes.length>0,n.regression=n.changeKind===`regression`,n.recovery=n.changeKind===`recovery`,n.alert=n.regression||n.changeKind===`baseline`&&n.currentIssue,n}function UA(e){return{rootSite:e.rootSite,property:e.property,url:e.url,inspectedAt:e.inspectedAt,inspectionStatus:e.deferred?`deferred`:e.quotaBlocked?`quota-blocked`:`failed`,requestSent:e.requestSent??!0,indexStatus:`unknown`,issueCodes:[e.quotaBlocked?`inspection_quota_blocked`:`inspection_failed`],currentIssue:!1,severity:`none`,changes:[],changeKind:`not-comparable`,changed:!1,regression:!1,recovery:!1,alert:!1,errorCode:e.errorCode,errorMessage:e.errorMessage,retryAt:e.retryAt}}function WA(e){if(e.inspected_at)return{inspectedAt:new Date(e.inspected_at).toISOString(),verdict:e.verdict??void 0,coverageState:e.coverage_state??void 0,indexingState:e.indexing_state??void 0,robotsTxtState:e.robots_txt_state??void 0,pageFetchState:e.page_fetch_state??void 0,googleCanonical:e.google_canonical??void 0,userCanonical:e.user_canonical??void 0,lastCrawlTime:e.last_crawl_time??void 0}}const GA={inspectUrl:qg,now:()=>new Date,store:{latest(e,t){let n=j().prepare(`SELECT *
629
629
  FROM index_watch_snapshots
630
630
  WHERE root_site_url = ? AND url = ? AND error_code IS NULL
631
631
  ORDER BY inspected_at DESC, id DESC
632
- LIMIT 1`).get(e,t);return n?TA(n):void 0},insert(e){j().prepare(`INSERT INTO index_watch_snapshots
632
+ LIMIT 1`).get(e,t);return n?WA(n):void 0},insert(e){j().prepare(`INSERT INTO index_watch_snapshots
633
633
  (id, site_url, root_site_url, property_site_url, url, verdict,
634
634
  coverage_state, indexing_state, robots_txt_state, page_fetch_state,
635
635
  google_canonical, user_canonical, last_crawl_time, inspection_status,
636
636
  error_code, error_message, inspected_at)
637
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(v(),e.property,e.rootSite,e.property,e.url,e.verdict??null,e.coverageState??null,e.indexingState??null,e.robotsTxtState??null,e.pageFetchState??null,e.googleCanonical??null,e.userCanonical??null,e.lastCrawlTime??null,e.inspectionStatus,e.errorCode??null,e.errorMessage??null,Date.parse(e.inspectedAt))}}};function DA(e){let t=e?.trim();if(t){if(t.length>35||!/^[A-Za-z0-9-]+$/.test(t))throw new A(`INVALID_INPUT`,`languageCode must be a valid BCP 47 tag.`);return t}}function OA(e,t){if(!t.length)throw new A(`INVALID_INPUT`,`Pass at least one URL to inspect.`);if(t.length>100)throw new A(`INVALID_INPUT`,`Direct index watch accepts at most 100 URLs per run.`);return[...new Set(t.map(t=>Ng(e,t)))]}function kA(e){return e instanceof A?e.code.toLowerCase():`inspection_failed`}function AA(e){return[`AUTH_CONFIG_REQUIRED`,`AUTH_EXPIRED`,`AUTH_REQUIRED`].includes(e.code)}async function jA(e,t=EA){let n=DA(e.languageCode),r=Y({value:e.dailyLimit,fallback:2e3,minimum:1,maximum:2e3,label:`dailyLimit`}),i=OA(e.site,e.urls),a=e.rootSite??e.site,o=[],s=[];for(let[c,l]of i.entries()){let u=t.now().toISOString();try{let i=await t.inspectUrl({siteUrl:e.site,inspectionUrl:l,languageCode:n,quotaLimit:r}),s=CA({rootSite:a,property:e.site,url:l,inspectedAt:u,result:i,previous:t.store.latest(a,l)});t.store.insert(s),o.push(s)}catch(n){let r=Se(n);if(AA(r))throw r;let d=[`ACCESS_DENIED`,`PROPERTY_NOT_FOUND`].includes(r.code);if(d&&!e.continueOnPropertyError)throw r;let f=r.code===`RATE_LIMITED`,p=r instanceof Ig?r.requestSent:!0,m=r instanceof Ig?r.resetAt:void 0,h=wA({rootSite:a,property:e.site,url:l,inspectedAt:u,errorCode:kA(r),errorMessage:r.message,quotaBlocked:f,requestSent:p,retryAt:m});if(t.store.insert(h),o.push(h),s.push(`${l}: ${r.message}`),f||d){for(let n of i.slice(c+1)){let i=wA({rootSite:a,property:e.site,url:n,inspectedAt:t.now().toISOString(),errorCode:f?`rate_limited`:r.code.toLowerCase(),errorMessage:f?`URL Inspection quota is blocked for this property; no request was sent.`:`URL Inspection stopped for this property after ${r.code}; no request was sent.`,quotaBlocked:f,deferred:!0,requestSent:!1,retryAt:m});o.push(i)}break}}}let c=o.filter(e=>e.inspectionStatus===`succeeded`).length,l=o.filter(e=>e.inspectionStatus===`failed`).length,u=o.filter(e=>e.inspectionStatus===`quota-blocked`).length,d=o.filter(e=>e.inspectionStatus===`deferred`).length,f=o.filter(e=>e.currentIssue).length;return{schemaVersion:1,methodology:`index-watch-v2`,site:a,generatedAt:t.now().toISOString(),dataStatus:l||u||d?`partial`:`complete`,source:{type:`url-inspection-indexed-snapshot`,property:e.site,dailyLimit:r,languageCode:n},summary:{requested:e.urls.length,unique:i.length,attempted:o.filter(e=>e.requestSent).length,inspected:c,failed:l,quotaBlocked:u,deferred:d,currentIssues:f,changed:o.filter(e=>e.changed).length,regressions:o.filter(e=>e.regression).length,recoveries:o.filter(e=>e.recovery).length,alerts:o.filter(e=>e.alert).length},caveats:[`URL Inspection reports Google's indexed snapshot for one URL, not a live test of the current page.`,`PASS, NEUTRAL, and FAIL map to indexed, excluded, and invalid. Translated coverage text is display evidence only.`,`An excluded URL or canonical difference may be intentional. Issue codes mean review the URL against its intended state, not that every URL is defective.`,`The local quota ledger uses a conservative UTC-day safety cap and cannot see URL Inspection calls made by other machines or Google clients.`],warnings:s,items:o}}function MA(e){let t=j().prepare(`WITH attempts AS (
637
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(v(),e.property,e.rootSite,e.property,e.url,e.verdict??null,e.coverageState??null,e.indexingState??null,e.robotsTxtState??null,e.pageFetchState??null,e.googleCanonical??null,e.userCanonical??null,e.lastCrawlTime??null,e.inspectionStatus,e.errorCode??null,e.errorMessage??null,Date.parse(e.inspectedAt))}}};function KA(e){let t=e?.trim();if(t){if(t.length>35||!/^[A-Za-z0-9-]+$/.test(t))throw new A(`INVALID_INPUT`,`languageCode must be a valid BCP 47 tag.`);return t}}function qA(e,t){if(!t.length)throw new A(`INVALID_INPUT`,`Pass at least one URL to inspect.`);if(t.length>100)throw new A(`INVALID_INPUT`,`Direct index watch accepts at most 100 URLs per run.`);return[...new Set(t.map(t=>Ng(e,t)))]}function JA(e){return e instanceof A?e.code.toLowerCase():`inspection_failed`}function YA(e){return[`AUTH_CONFIG_REQUIRED`,`AUTH_EXPIRED`,`AUTH_REQUIRED`].includes(e.code)}async function XA(e,t=GA){let n=KA(e.languageCode),r=Y({value:e.dailyLimit,fallback:2e3,minimum:1,maximum:2e3,label:`dailyLimit`}),i=qA(e.site,e.urls),a=e.rootSite??e.site,o=[],s=[];for(let[c,l]of i.entries()){let u=t.now().toISOString();try{let i=await t.inspectUrl({siteUrl:e.site,inspectionUrl:l,languageCode:n,quotaLimit:r}),s=HA({rootSite:a,property:e.site,url:l,inspectedAt:u,result:i,previous:t.store.latest(a,l)});t.store.insert(s),o.push(s)}catch(n){let r=Se(n);if(YA(r))throw r;let d=[`ACCESS_DENIED`,`PROPERTY_NOT_FOUND`].includes(r.code);if(d&&!e.continueOnPropertyError)throw r;let f=r.code===`RATE_LIMITED`,p=r instanceof Ig?r.requestSent:!0,m=r instanceof Ig?r.resetAt:void 0,h=UA({rootSite:a,property:e.site,url:l,inspectedAt:u,errorCode:JA(r),errorMessage:r.message,quotaBlocked:f,requestSent:p,retryAt:m});if(t.store.insert(h),o.push(h),s.push(`${l}: ${r.message}`),f||d){for(let n of i.slice(c+1)){let i=UA({rootSite:a,property:e.site,url:n,inspectedAt:t.now().toISOString(),errorCode:f?`rate_limited`:r.code.toLowerCase(),errorMessage:f?`URL Inspection quota is blocked for this property; no request was sent.`:`URL Inspection stopped for this property after ${r.code}; no request was sent.`,quotaBlocked:f,deferred:!0,requestSent:!1,retryAt:m});o.push(i)}break}}}let c=o.filter(e=>e.inspectionStatus===`succeeded`).length,l=o.filter(e=>e.inspectionStatus===`failed`).length,u=o.filter(e=>e.inspectionStatus===`quota-blocked`).length,d=o.filter(e=>e.inspectionStatus===`deferred`).length,f=o.filter(e=>e.currentIssue).length;return{schemaVersion:1,methodology:`index-watch-v2`,site:a,generatedAt:t.now().toISOString(),dataStatus:l||u||d?`partial`:`complete`,source:{type:`url-inspection-indexed-snapshot`,property:e.site,dailyLimit:r,languageCode:n},summary:{requested:e.urls.length,unique:i.length,attempted:o.filter(e=>e.requestSent).length,inspected:c,failed:l,quotaBlocked:u,deferred:d,currentIssues:f,changed:o.filter(e=>e.changed).length,regressions:o.filter(e=>e.regression).length,recoveries:o.filter(e=>e.recovery).length,alerts:o.filter(e=>e.alert).length},caveats:[`URL Inspection reports Google's indexed snapshot for one URL, not a live test of the current page.`,`PASS, NEUTRAL, and FAIL map to indexed, excluded, and invalid. Translated coverage text is display evidence only.`,`An excluded URL or canonical difference may be intentional. Issue codes mean review the URL against its intended state, not that every URL is defective.`,`The local quota ledger uses a conservative UTC-day safety cap and cannot see URL Inspection calls made by other machines or Google clients.`],warnings:s,items:o}}function ZA(e){let t=j().prepare(`WITH attempts AS (
638
638
  SELECT snapshot.*,
639
639
  ROW_NUMBER() OVER (
640
640
  PARTITION BY url
@@ -667,7 +667,7 @@ CREATE INDEX IF NOT EXISTS idx_performance_reports_url ON performance_reports(ur
667
667
  FROM attempts
668
668
  LEFT JOIN successes
669
669
  ON successes.url = attempts.url AND successes.success_rank = 1
670
- WHERE attempts.attempt_rank = 1`).all(e,e),n=t.map(e=>e.success_inspected_at).filter(e=>!!e),r=n.reduce((e,t)=>Math.min(e,t),1/0),i=t.filter(e=>!!e.success_inspected_at),a=i.map(e=>bA({verdict:e.verdict??void 0,indexingState:e.indexing_state??void 0,robotsTxtState:e.robots_txt_state??void 0,pageFetchState:e.page_fetch_state??void 0,googleCanonical:e.google_canonical??void 0,userCanonical:e.user_canonical??void 0}));return{inspectedUrls:t.length,earliestInspectedAt:n.length?new Date(r).toISOString():void 0,latestInspectedAt:t.length?new Date(Math.max(...t.map(e=>e.attempt_inspected_at??0))).toISOString():void 0,currentIssues:a.filter(e=>e.length>0).length,failed:t.filter(e=>!!e.latest_error_code).length,nonPass:i.filter(e=>e.verdict&&e.verdict!==`PASS`).length,blocked:a.filter(e=>e.some(e=>[`indexing_blocked_header`,`indexing_blocked_meta`,`page_fetch_failed`,`robots_disallowed`].includes(e))).length}}function NA(e,t){if(!t.length)return new Map;let n=new Map;for(let r=0;r<t.length;r+=500){let i=t.slice(r,r+500),a=i.map(()=>`?`).join(`, `),o=j().prepare(`WITH ranked AS (
670
+ WHERE attempts.attempt_rank = 1`).all(e,e),n=t.map(e=>e.success_inspected_at).filter(e=>!!e),r=n.reduce((e,t)=>Math.min(e,t),1/0),i=t.filter(e=>!!e.success_inspected_at),a=i.map(e=>zA({verdict:e.verdict??void 0,indexingState:e.indexing_state??void 0,robotsTxtState:e.robots_txt_state??void 0,pageFetchState:e.page_fetch_state??void 0,googleCanonical:e.google_canonical??void 0,userCanonical:e.user_canonical??void 0}));return{inspectedUrls:t.length,earliestInspectedAt:n.length?new Date(r).toISOString():void 0,latestInspectedAt:t.length?new Date(Math.max(...t.map(e=>e.attempt_inspected_at??0))).toISOString():void 0,currentIssues:a.filter(e=>e.length>0).length,failed:t.filter(e=>!!e.latest_error_code).length,nonPass:i.filter(e=>e.verdict&&e.verdict!==`PASS`).length,blocked:a.filter(e=>e.some(e=>[`indexing_blocked_header`,`indexing_blocked_meta`,`page_fetch_failed`,`robots_disallowed`].includes(e))).length}}function QA(e,t){if(!t.length)return new Map;let n=new Map;for(let r=0;r<t.length;r+=500){let i=t.slice(r,r+500),a=i.map(()=>`?`).join(`, `),o=j().prepare(`WITH ranked AS (
671
671
  SELECT url, inspected_at, inspection_status, error_code,
672
672
  MAX(CASE
673
673
  WHEN inspection_status = 'succeeded' AND error_code IS NULL
@@ -679,18 +679,18 @@ CREATE INDEX IF NOT EXISTS idx_performance_reports_url ON performance_reports(ur
679
679
  FROM index_watch_snapshots
680
680
  WHERE root_site_url = ? AND url IN (${a})
681
681
  )
682
- SELECT * FROM ranked WHERE attempt_rank = 1`).all(e,...i);for(let e of o)n.set(e.url,{latestAttemptAt:e.inspected_at??void 0,latestSuccessAt:e.latest_success_at??void 0,latestAttemptSucceeded:e.inspection_status===`succeeded`&&!e.error_code})}return n}function PA(e,t,n,r,i){let a=NA(e,t),o={neverAttempted:0,neverSucceeded:0,retryWaiting:0,fresh:0,stale:0},s=[];for(let e of t){let t=a.get(e);if(!t){o.neverAttempted+=1,s.push({url:e,dueReason:`never-attempted`});continue}if(!t.latestAttemptSucceeded&&t.latestAttemptAt&&t.latestAttemptAt>i){o.retryWaiting+=1;continue}if(!t.latestSuccessAt){o.neverSucceeded+=1,s.push({url:e,dueReason:`never-succeeded`});continue}if(t.latestSuccessAt<=r){o.stale+=1,s.push({url:e,lastInspectedAt:t.latestSuccessAt,dueReason:`stale`});continue}o.fresh+=1}return{counts:o,dueUrls:s.sort((e,t)=>(e.lastInspectedAt??0)-(t.lastInspectedAt??0)||(e.url<t.url?-1:+(e.url>t.url))).slice(0,n)}}function FA(e){let t=Y({value:e.dailyLimit,fallback:2e3,minimum:1,maximum:2e3,label:`dailyLimit`}),n=Y({value:e.inspectLimit,fallback:Math.min(t,100),minimum:1,maximum:100,label:`inspectLimit`}),r=Y({value:e.staleAfterDays,fallback:1,minimum:1,maximum:365,label:`staleAfterDays`}),i=Y({value:e.failureRetryHours,fallback:24,minimum:1,maximum:168,label:`failureRetryHours`}),a=(e.now??new Date).getTime(),o=a-r*864e5,s=a-i*36e5,c=uA({site:e.site,urls:e.urls,accountProperties:e.accountProperties}).map(n=>({allocation:n,...PA(e.site,n.urls,t,o,s),cursor:0})),l=[],u=n,d=Math.max(1,Math.floor(n/Math.max(1,c.length)));for(let e of c){if(u<=0)break;let t=Math.min(d,u),n=e.dueUrls.slice(0,t);e.cursor=n.length,l.push(...IA(e.allocation.property,n)),u-=n.length}for(let e of c){if(u<=0)break;let t=e.dueUrls.slice(e.cursor,e.cursor+u);l.push(...IA(e.allocation.property,t)),u-=t.length}let f=c.reduce((e,t)=>({neverAttempted:e.neverAttempted+t.counts.neverAttempted,neverSucceeded:e.neverSucceeded+t.counts.neverSucceeded,retryWaiting:e.retryWaiting+t.counts.retryWaiting,fresh:e.fresh+t.counts.fresh,stale:e.stale+t.counts.stale}),{neverAttempted:0,neverSucceeded:0,retryWaiting:0,fresh:0,stale:0}),p=f.neverAttempted+f.neverSucceeded+f.stale;return{selected:l,summary:{...f,due:p,unselectedDue:Math.max(0,p-l.length)}}}function IA(e,t){return t.map(t=>({...t,property:e}))}async function LA(e){if(!e.sitemaps.length||e.sitemaps.length>20)throw new A(`INVALID_INPUT`,`Pass between 1 and 20 sitemap URLs.`);let t=e.sitemaps.map(Mg),n=Y({value:e.dailyLimit,fallback:2e3,minimum:1,maximum:2e3,label:`dailyLimit`}),r=Y({value:e.inspectLimit,fallback:Math.min(n,100),minimum:1,maximum:100,label:`inspectLimit`}),i=Y({value:e.maxUrls,fallback:5e4,minimum:1,maximum:25e4,label:`maxUrls`}),a=await Promise.all(t.map(e=>Bx({sitemapUrl:e,limit:i}))),o=zx(a,i),s=o.urls,c=a.flatMap(e=>e.warnings),l=[],u=0;for(let t of s)try{l.push(Ng(e.site,t))}catch(e){u+=1,c.push(e instanceof Error?e.message:String(e))}let d=o.truncation.possiblyTruncated;d&&c.push(`Sitemap discovery exceeded a configured URL or nested-sitemap boundary; inventory and skipped counts may be incomplete.`);let f=e.properties?[...e.properties]:await s_(e.refresh).then(e=>e.map(e=>e.siteUrl)),p=uA({site:e.site,urls:l,accountProperties:f}),m=FA({site:e.site,urls:l,accountProperties:f,dailyLimit:n,inspectLimit:r,staleAfterDays:1,failureRetryHours:24}),h=m.selected,g=new Map;for(let e of h){let t=g.get(e.property)??[];t.push(e.url),g.set(e.property,t)}let _=[],v=[];for(let t of p){let r=g.get(t.property)??[];if(!r.length){v.push({property:t.property,inventoryUrls:t.urls.length,selectedUrls:0,attempted:0,inspected:0,failed:0,quotaBlocked:0,deferred:0,currentIssues:0,changed:0,regressions:0,recoveries:0,alerts:0,sampleUrls:t.urls.slice(0,3)});continue}let i=await jA({site:t.property,rootSite:e.site,urls:r,languageCode:e.languageCode,dailyLimit:n,continueOnPropertyError:!0});_.push(...i.items),c.push(...i.warnings),v.push({property:t.property,inventoryUrls:t.urls.length,selectedUrls:r.length,attempted:i.summary.attempted,inspected:i.summary.inspected,failed:i.summary.failed,quotaBlocked:i.summary.quotaBlocked,deferred:i.summary.deferred,currentIssues:i.summary.currentIssues,changed:i.summary.changed,regressions:i.summary.regressions,recoveries:i.summary.recoveries,alerts:i.summary.alerts,sampleUrls:r.slice(0,3)})}return{schemaVersion:1,methodology:`index-monitor-v2`,site:e.site,generatedAt:new Date().toISOString(),dataStatus:d||c.length>0||_.some(e=>e.inspectionStatus!==`succeeded`)?`partial`:`complete`,source:{type:`sitemap-url-inspection-indexed-snapshot`,sitemaps:t,maxUrls:i,dailyLimit:n,inspectLimit:r,staleAfterDays:1,failureRetryHours:24,possiblyTruncated:d,inventoryLimitExceeded:o.truncation.inventoryLimitExceeded,omittedUrlsAtLeast:o.truncation.omittedUrlsAtLeast,discoveredUrls:s.length,invalidUrls:u},summary:{inventoryUrls:l.length,properties:p.length,dailyCapacity:p.length*n,neverAttempted:m.summary.neverAttempted,neverSucceeded:m.summary.neverSucceeded,retryWaiting:m.summary.retryWaiting,fresh:m.summary.fresh,stale:m.summary.stale,due:m.summary.due,selected:h.length,unselectedDue:m.summary.unselectedDue,attempted:_.filter(e=>e.requestSent).length,inspected:_.filter(e=>e.inspectionStatus===`succeeded`).length,failed:_.filter(e=>e.inspectionStatus===`failed`).length,quotaBlocked:_.filter(e=>e.inspectionStatus===`quota-blocked`).length,deferred:_.filter(e=>e.inspectionStatus===`deferred`).length,currentIssues:_.filter(e=>e.currentIssue).length,changed:_.filter(e=>e.changed).length,regressions:_.filter(e=>e.regression).length,recoveries:_.filter(e=>e.recovery).length,alerts:_.filter(e=>e.alert).length,skipped:Math.max(0,l.length-h.length)},properties:v,items:_,caveats:[`URL Inspection reports Google's indexed snapshot for one URL, not a live test of the current page.`,`Due URLs are never-attempted, never-successful, or at least one day past their latest successful inspection. Failed attempts wait 24 hours before retry.`,`Selected URLs are a bounded oldest-first sample across matching Search Console properties; current issue counts describe the selected results, not every sitemap URL.`,`Excluded and canonical-difference issue codes require review against the intended URL state and are not automatically defects.`,`The local quota ledger uses a conservative UTC-day safety cap and cannot see URL Inspection calls made by other machines or Google clients.`],warnings:c}}function RA(e){return{id:e.id,site:e.site_url,createdAt:new Date(e.created_at).toISOString(),range:{startDate:e.start_date,endDate:e.end_date,days:e.days},checked:e.checked,recoverable:e.recoverable,high:e.high,medium:e.medium,low:e.low,clicksAtRisk:e.clicks_at_risk,impressionsAtRisk:e.impressions_at_risk,topIssue:e.top_issue??void 0,topUrl:e.top_url??void 0,topAction:e.top_action??void 0,repeatedUrls:e.repeated_urls??0,repeatedTopUrl:e.repeated_top_url??void 0}}function zA(e){let t=e.items[0],n=v(),r=j(),i=r.prepare(`INSERT INTO link_recover_runs
682
+ SELECT * FROM ranked WHERE attempt_rank = 1`).all(e,...i);for(let e of o)n.set(e.url,{latestAttemptAt:e.inspected_at??void 0,latestSuccessAt:e.latest_success_at??void 0,latestAttemptSucceeded:e.inspection_status===`succeeded`&&!e.error_code})}return n}function $A(e,t,n,r,i){let a=QA(e,t),o={neverAttempted:0,neverSucceeded:0,retryWaiting:0,fresh:0,stale:0},s=[];for(let e of t){let t=a.get(e);if(!t){o.neverAttempted+=1,s.push({url:e,dueReason:`never-attempted`});continue}if(!t.latestAttemptSucceeded&&t.latestAttemptAt&&t.latestAttemptAt>i){o.retryWaiting+=1;continue}if(!t.latestSuccessAt){o.neverSucceeded+=1,s.push({url:e,dueReason:`never-succeeded`});continue}if(t.latestSuccessAt<=r){o.stale+=1,s.push({url:e,lastInspectedAt:t.latestSuccessAt,dueReason:`stale`});continue}o.fresh+=1}return{counts:o,dueUrls:s.sort((e,t)=>(e.lastInspectedAt??0)-(t.lastInspectedAt??0)||(e.url<t.url?-1:+(e.url>t.url))).slice(0,n)}}function ej(e){let t=Y({value:e.dailyLimit,fallback:2e3,minimum:1,maximum:2e3,label:`dailyLimit`}),n=Y({value:e.inspectLimit,fallback:Math.min(t,100),minimum:1,maximum:100,label:`inspectLimit`}),r=Y({value:e.staleAfterDays,fallback:1,minimum:1,maximum:365,label:`staleAfterDays`}),i=Y({value:e.failureRetryHours,fallback:24,minimum:1,maximum:168,label:`failureRetryHours`}),a=(e.now??new Date).getTime(),o=a-r*864e5,s=a-i*36e5,c=kA({site:e.site,urls:e.urls,accountProperties:e.accountProperties}).map(n=>({allocation:n,...$A(e.site,n.urls,t,o,s),cursor:0})),l=[],u=n,d=Math.max(1,Math.floor(n/Math.max(1,c.length)));for(let e of c){if(u<=0)break;let t=Math.min(d,u),n=e.dueUrls.slice(0,t);e.cursor=n.length,l.push(...tj(e.allocation.property,n)),u-=n.length}for(let e of c){if(u<=0)break;let t=e.dueUrls.slice(e.cursor,e.cursor+u);l.push(...tj(e.allocation.property,t)),u-=t.length}let f=c.reduce((e,t)=>({neverAttempted:e.neverAttempted+t.counts.neverAttempted,neverSucceeded:e.neverSucceeded+t.counts.neverSucceeded,retryWaiting:e.retryWaiting+t.counts.retryWaiting,fresh:e.fresh+t.counts.fresh,stale:e.stale+t.counts.stale}),{neverAttempted:0,neverSucceeded:0,retryWaiting:0,fresh:0,stale:0}),p=f.neverAttempted+f.neverSucceeded+f.stale;return{selected:l,summary:{...f,due:p,unselectedDue:Math.max(0,p-l.length)}}}function tj(e,t){return t.map(t=>({...t,property:e}))}async function nj(e){if(!e.sitemaps.length||e.sitemaps.length>20)throw new A(`INVALID_INPUT`,`Pass between 1 and 20 sitemap URLs.`);let t=e.sitemaps.map(Mg),n=Y({value:e.dailyLimit,fallback:2e3,minimum:1,maximum:2e3,label:`dailyLimit`}),r=Y({value:e.inspectLimit,fallback:Math.min(n,100),minimum:1,maximum:100,label:`inspectLimit`}),i=Y({value:e.maxUrls,fallback:5e4,minimum:1,maximum:25e4,label:`maxUrls`}),a=await Promise.all(t.map(e=>xS({sitemapUrl:e,limit:i}))),o=bS(a,i),s=o.urls,c=a.flatMap(e=>e.warnings),l=[],u=0;for(let t of s)try{l.push(Ng(e.site,t))}catch(e){u+=1,c.push(e instanceof Error?e.message:String(e))}let d=o.truncation.possiblyTruncated;d&&c.push(`Sitemap discovery exceeded a configured URL or nested-sitemap boundary; inventory and skipped counts may be incomplete.`);let f=e.properties?[...e.properties]:await s_(e.refresh).then(e=>e.map(e=>e.siteUrl)),p=kA({site:e.site,urls:l,accountProperties:f}),m=ej({site:e.site,urls:l,accountProperties:f,dailyLimit:n,inspectLimit:r,staleAfterDays:1,failureRetryHours:24}),h=m.selected,g=new Map;for(let e of h){let t=g.get(e.property)??[];t.push(e.url),g.set(e.property,t)}let _=[],v=[];for(let t of p){let r=g.get(t.property)??[];if(!r.length){v.push({property:t.property,inventoryUrls:t.urls.length,selectedUrls:0,attempted:0,inspected:0,failed:0,quotaBlocked:0,deferred:0,currentIssues:0,changed:0,regressions:0,recoveries:0,alerts:0,sampleUrls:t.urls.slice(0,3)});continue}let i=await XA({site:t.property,rootSite:e.site,urls:r,languageCode:e.languageCode,dailyLimit:n,continueOnPropertyError:!0});_.push(...i.items),c.push(...i.warnings),v.push({property:t.property,inventoryUrls:t.urls.length,selectedUrls:r.length,attempted:i.summary.attempted,inspected:i.summary.inspected,failed:i.summary.failed,quotaBlocked:i.summary.quotaBlocked,deferred:i.summary.deferred,currentIssues:i.summary.currentIssues,changed:i.summary.changed,regressions:i.summary.regressions,recoveries:i.summary.recoveries,alerts:i.summary.alerts,sampleUrls:r.slice(0,3)})}return{schemaVersion:1,methodology:`index-monitor-v2`,site:e.site,generatedAt:new Date().toISOString(),dataStatus:d||c.length>0||_.some(e=>e.inspectionStatus!==`succeeded`)?`partial`:`complete`,source:{type:`sitemap-url-inspection-indexed-snapshot`,sitemaps:t,maxUrls:i,dailyLimit:n,inspectLimit:r,staleAfterDays:1,failureRetryHours:24,possiblyTruncated:d,inventoryLimitExceeded:o.truncation.inventoryLimitExceeded,omittedUrlsAtLeast:o.truncation.omittedUrlsAtLeast,discoveredUrls:s.length,invalidUrls:u},summary:{inventoryUrls:l.length,properties:p.length,dailyCapacity:p.length*n,neverAttempted:m.summary.neverAttempted,neverSucceeded:m.summary.neverSucceeded,retryWaiting:m.summary.retryWaiting,fresh:m.summary.fresh,stale:m.summary.stale,due:m.summary.due,selected:h.length,unselectedDue:m.summary.unselectedDue,attempted:_.filter(e=>e.requestSent).length,inspected:_.filter(e=>e.inspectionStatus===`succeeded`).length,failed:_.filter(e=>e.inspectionStatus===`failed`).length,quotaBlocked:_.filter(e=>e.inspectionStatus===`quota-blocked`).length,deferred:_.filter(e=>e.inspectionStatus===`deferred`).length,currentIssues:_.filter(e=>e.currentIssue).length,changed:_.filter(e=>e.changed).length,regressions:_.filter(e=>e.regression).length,recoveries:_.filter(e=>e.recovery).length,alerts:_.filter(e=>e.alert).length,skipped:Math.max(0,l.length-h.length)},properties:v,items:_,caveats:[`URL Inspection reports Google's indexed snapshot for one URL, not a live test of the current page.`,`Due URLs are never-attempted, never-successful, or at least one day past their latest successful inspection. Failed attempts wait 24 hours before retry.`,`Selected URLs are a bounded oldest-first sample across matching Search Console properties; current issue counts describe the selected results, not every sitemap URL.`,`Excluded and canonical-difference issue codes require review against the intended URL state and are not automatically defects.`,`The local quota ledger uses a conservative UTC-day safety cap and cannot see URL Inspection calls made by other machines or Google clients.`],warnings:c}}function rj(e){return{id:e.id,site:e.site_url,createdAt:new Date(e.created_at).toISOString(),range:{startDate:e.start_date,endDate:e.end_date,days:e.days},checked:e.checked,recoverable:e.recoverable,high:e.high,medium:e.medium,low:e.low,clicksAtRisk:e.clicks_at_risk,impressionsAtRisk:e.impressions_at_risk,topIssue:e.top_issue??void 0,topUrl:e.top_url??void 0,topAction:e.top_action??void 0,repeatedUrls:e.repeated_urls??0,repeatedTopUrl:e.repeated_top_url??void 0}}function ij(e){let t=e.items[0],n=v(),r=j(),i=r.prepare(`INSERT INTO link_recover_runs
683
683
  (id, site_url, created_at, start_date, end_date, days, checked,
684
684
  recoverable, high, medium, low, clicks_at_risk, impressions_at_risk,
685
685
  top_issue, top_url, top_action)
686
686
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),a=r.prepare(`INSERT INTO link_recover_items
687
687
  (run_id, site_url, url, final_url, issue, issues_json, severity,
688
688
  clicks, impressions, position, action, created_at)
689
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),o=Date.parse(e.generatedAt);return r.transaction(()=>{i.run(n,e.site,o,e.range.startDate,e.range.endDate,e.range.days,e.summary.checked,e.summary.recoverable,e.summary.high,e.summary.medium,e.summary.low,e.summary.clicksAtRisk,e.summary.impressionsAtRisk,t?.issue??null,t?.url??null,t?.recommendation.action??null);for(let t of e.items)a.run(n,e.site,t.url,t.finalUrl,t.issue,JSON.stringify(t.issues),t.severity,t.clicks,t.impressions,t.position,t.recommendation.action,o)})(),n}function BA(e){let t=j().prepare(`SELECT *
689
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),o=Date.parse(e.generatedAt);return r.transaction(()=>{i.run(n,e.site,o,e.range.startDate,e.range.endDate,e.range.days,e.summary.checked,e.summary.recoverable,e.summary.high,e.summary.medium,e.summary.low,e.summary.clicksAtRisk,e.summary.impressionsAtRisk,t?.issue??null,t?.url??null,t?.recommendation.action??null);for(let t of e.items)a.run(n,e.site,t.url,t.finalUrl,t.issue,JSON.stringify(t.issues),t.severity,t.clicks,t.impressions,t.position,t.recommendation.action,o)})(),n}function aj(e){let t=j().prepare(`SELECT *
690
690
  FROM link_recover_runs
691
691
  WHERE site_url = ?
692
692
  ORDER BY created_at DESC
693
- LIMIT 1`).get(e);if(!t)return;let n=VA(e,5);return RA({...t,repeated_urls:n.length,repeated_top_url:n[0]?.url})}function VA(e,t=5){let n=j(),r=n.prepare(`SELECT id
693
+ LIMIT 1`).get(e);if(!t)return;let n=oj(e,5);return rj({...t,repeated_urls:n.length,repeated_top_url:n[0]?.url})}function oj(e,t=5){let n=j(),r=n.prepare(`SELECT id
694
694
  FROM link_recover_runs
695
695
  WHERE site_url = ?
696
696
  ORDER BY created_at DESC
@@ -699,15 +699,15 @@ CREATE INDEX IF NOT EXISTS idx_performance_reports_url ON performance_reports(ur
699
699
  WHERE site_url = ? AND run_id IN (${i})
700
700
  GROUP BY url
701
701
  HAVING seen_count >= 2
702
- ORDER BY seen_count DESC, MAX(created_at) DESC`).all(e,...r.map(e=>e.id)).map(e=>({url:e.url,seenCount:e.seen_count}))}function HA(e,t){try{let n=new URL(e,t);return n.hash=``,n.toString()}catch{return}}function UA(e){let t=[],n=e.chain.at(-1)?.status??0,r=e.chain.some(e=>e.status>=300&&e.status<400&&!e.location);return e.stoppedForLoop&&t.push(`redirect-loop`),e.stoppedForLimit&&t.push(`too-many-redirects`),r&&t.push(`redirect-without-location`),n>=400&&n<500&&t.push(`final-4xx`),n>=500&&t.push(`final-5xx`),e.finalIndexable===!1&&t.push(`non-indexable-final`),e.canonical&&e.canonical!==e.finalUrl&&t.push(`canonical-mismatch`),[...new Set(t)]}async function WA(e){let t=e.maxHops??10,n=[],r=[],i=new Set,a=new URL(e.url).toString(),o=!1,s=!1;for(let s=0;s<=t;s+=1){if(i.has(a)){o=!0;break}i.add(a);let t=Date.now(),s=new AbortController,c=setTimeout(()=>s.abort(),e.timeoutMs??2e4);try{let e=await Oh(a,{redirect:`manual`,signal:s.signal}),n=e.headers.get(`location`)??void 0,i=n?HA(n,a):void 0;if(r.push({url:a,status:e.status,location:n,nextUrl:i,durationMs:Date.now()-t}),e.status<300||e.status>=400||!i)break;a=i}catch(e){n.push(`${a}: ${e instanceof Error?e.message:String(e)}`);break}finally{clearTimeout(c)}}let c=r.at(-1);c&&c.status>=300&&c.status<400&&r.length>t&&(s=!0);let l=c?.nextUrl??c?.url??a,u=await Ox(l,{refresh:e.refresh,js:e.js??`auto`});u.warning&&n.push(u.warning);let d=u.page?{title:u.page.title,canonical:u.page.canonical,metaRobots:u.page.metaRobots,xRobotsTag:u.page.xRobotsTag,indexable:u.page.indexable}:void 0,f=UA({chain:r,finalIndexable:d?.indexable,canonical:d?.canonical,finalUrl:l,stoppedForLoop:o,stoppedForLimit:s});return{url:e.url,finalUrl:l,generatedAt:new Date().toISOString(),summary:{hops:Math.max(0,r.length-1),finalStatus:u.page?.status??r.at(-1)?.status??0,finalIndexable:d?.indexable,issues:f},chain:r,finalPage:d,warnings:n}}function GA(e){return[`final-5xx`,`final-4xx`,`redirect-loop`,`too-many-redirects`,`non-indexable-final`,`canonical-mismatch`,`redirect-without-location`].find(t=>e.includes(t))??e[0]??`final-4xx`}function KA(e,t){return e===`final-5xx`||e===`final-4xx`?t.clicks>=10||t.impressions>=500?`high`:`medium`:e===`redirect-loop`||e===`too-many-redirects`?`high`:e===`non-indexable-final`?t.clicks>=5||t.impressions>=250?`high`:`medium`:t.clicks>=10||t.impressions>=500?`medium`:`low`}function qA(e){let t=`${Math.round(e.clicks).toLocaleString(`en-GB`)} clicks and ${Math.round(e.impressions).toLocaleString(`en-GB`)} impressions in the checked period.`;return e.issue===`final-4xx`?{principle:`Search-value URLs should not resolve to dead pages.`,evidenceRef:e.url,action:`This search-visible URL now lands on a dead page. Restore the page if it should exist, or add one direct 301 redirect to the closest live replacement.`,effort:`S`,confidence:`high`,impactEstimate:t}:e.issue===`final-5xx`?{principle:`Server errors on search-visible URLs waste existing demand.`,evidenceRef:e.url,action:`This search-visible URL ends in a server error. Fix the server response first, then rerun URL Inspection once it returns a stable 200 or an intentional 301.`,effort:`S`,confidence:`high`,impactEstimate:t}:e.issue===`redirect-loop`||e.issue===`too-many-redirects`||e.issue===`redirect-without-location`?{principle:`Redirect chains must resolve cleanly to a crawlable target.`,evidenceRef:e.url,action:`The redirect path is broken. Replace the chain with one direct 301 from the old URL to the intended live destination.`,effort:`M`,confidence:`high`,impactEstimate:t}:e.issue===`non-indexable-final`?{principle:`Recovered search-value URLs need an indexable final target.`,evidenceRef:e.finalUrl,action:`The final destination is not indexable. Remove accidental noindex/robots blocking, or redirect the old URL to an indexable equivalent.`,effort:`S`,confidence:`high`,impactEstimate:t}:{principle:`Canonicals should reinforce the final URL selected by redirects.`,evidenceRef:e.finalUrl,action:`The redirect target and canonical target disagree. Make the canonical match the final destination, or redirect the original URL directly to the canonical page.`,effort:`S`,confidence:`medium`,impactEstimate:t}}function JA(e){return{url:e.keys[0]??``,clicks:e.clicks,impressions:e.impressions,position:e.position}}async function YA(e){let t=e.days??90,n=H_(t),r=await e_(e.site,{...n,dimensions:[`page`],type:`web`,dataState:`final`},{refresh:e.refresh}),i=e.minClicks??1,a=e.minImpressions??100,o=r.rows.map(JA).filter(e=>e.url&&(e.clicks>=i||e.impressions>=a)).sort((e,t)=>t.clicks-e.clicks||t.impressions-e.impressions).slice(0,e.limit??50),s=[],c=[];for(let t of o){let n=await WA({url:t.url,refresh:e.refresh,js:e.js??`auto`});if(c.push(...n.warnings),!n.summary.issues.length)continue;let r=GA(n.summary.issues),i=KA(r,t);s.push({url:t.url,finalUrl:n.finalUrl,clicks:t.clicks,impressions:t.impressions,position:t.position,issue:r,issues:n.summary.issues,severity:i,trace:{summary:n.summary,chain:n.chain,finalPage:n.finalPage,warnings:n.warnings},recommendation:qA({issue:r,url:t.url,finalUrl:n.finalUrl,clicks:t.clicks,impressions:t.impressions})})}let l={site:e.site,generatedAt:new Date().toISOString(),range:{...n,days:t},summary:{checked:o.length,recoverable:s.length,high:s.filter(e=>e.severity===`high`).length,medium:s.filter(e=>e.severity===`medium`).length,low:s.filter(e=>e.severity===`low`).length,clicksAtRisk:s.reduce((e,t)=>e+t.clicks,0),impressionsAtRisk:s.reduce((e,t)=>e+t.impressions,0)},items:s,warnings:[...new Set(c)].slice(0,50)};return zA(l),l}function XA(e,t){if(!e)return!1;let n=Date.parse(e);return Number.isFinite(n)?Date.now()-n>t*864e5:!1}function ZA(e){return e.some(e=>e.status===`attention`)?`attention`:e.some(e=>e.status===`stale`)?`stale`:e.some(e=>e.status===`not-run`)?`not-run`:`clear`}function QA(e){let t=e.staleAfterDays??8,n=Ck(e.site,1)[0],r=MA(e.site),i=BA(e.site),a=[];if(!n)a.push({name:`crawl`,status:`not-run`,summary:`No saved crawl-diff run.`,action:`Run monitoring with a start URL to establish a crawl baseline.`});else{let e=n.highPriorityRecommendations>0;a.push({name:`crawl`,status:e?`attention`:XA(n.createdAt,t)?`stale`:`clear`,lastRunAt:n.createdAt,summary:`${Z(n.urlCount,`URL`)}, ${Z(n.statusErrors,`status error`)}, ${n.nonIndexable} non-indexable, ${Z(n.highPriorityRecommendations,`high-priority action`)}.`,action:e?n.topRecommendation?.action??`Review the saved crawl recommendations.`:void 0})}if(!r.latestInspectedAt)a.push({name:`index`,status:`not-run`,summary:`No saved URL Inspection snapshot.`,action:`Run monitoring with URLs or sitemaps to establish index status history.`});else{let e=r.currentIssues>0||r.failed>0;a.push({name:`index`,status:e?`attention`:XA(r.earliestInspectedAt,t)?`stale`:`clear`,lastRunAt:r.latestInspectedAt,summary:`${Z(r.inspectedUrls,`URL`)} tracked, ${Z(r.currentIssues,`current indexing review`)}, ${Z(r.failed,`failed inspection`)}, ${Z(r.blocked,`exact blocked state`)}.`,action:e?`Review current URL Inspection issue codes and failed checks before content changes.`:void 0})}if(!i)a.push({name:`link-recovery`,status:`not-run`,summary:`No saved link-recover run.`,action:`Run monitoring with link recovery enabled to find search-value URLs that now fail.`});else{let e=i.high>0||i.medium>0;a.push({name:`link-recovery`,status:e?`attention`:XA(i.createdAt,t)?`stale`:`clear`,lastRunAt:i.createdAt,summary:`${Z(i.checked,`URL`)} checked, ${i.recoverable} recoverable, ${i.high} high severity, ${i.clicksAtRisk.toFixed(0)} clicks at risk.`,action:e?i.topAction??`Fix recoverable search-value URL issues.`:void 0})}return{site:e.site,generatedAt:new Date().toISOString(),health:ZA(a),summary:{checks:a.length,attention:a.filter(e=>e.status===`attention`).length,stale:a.filter(e=>e.status===`stale`).length,notRun:a.filter(e=>e.status===`not-run`).length},checks:a}}const $A={CLS:{good:.1,poor:.25,unit:`score`},INP:{good:200,poor:500,unit:`milliseconds`},LCP:{good:2500,poor:4e3,unit:`milliseconds`}};function ej(e){if(typeof e==`number`&&Number.isFinite(e))return e;if(typeof e!=`string`||e.trim()===``)return;let t=Number(e);return Number.isFinite(t)?t:void 0}function tj(e,t){let n=ej(t?.percentiles?.p75);if(n===void 0||n<0)return;let r=$A[e];return{p75:n,unit:r.unit,rating:n<=r.good?`good`:n<=r.poor?`needs-work`:`poor`}}function nj(e){let t=e.record.metrics??{},n={cumulativeLayoutShift:tj(`CLS`,t.cumulative_layout_shift),interactionToNextPaint:tj(`INP`,t.interaction_to_next_paint),largestContentfulPaint:tj(`LCP`,t.largest_contentful_paint)},r=[[`CLS`,n.cumulativeLayoutShift],[`INP`,n.interactionToNextPaint],[`LCP`,n.largestContentfulPaint]],i=r.filter(e=>e[1]!==void 0),a=r.filter(e=>e[1]===void 0).map(e=>e[0]),o=i.map(e=>e[1]?.rating),s=a.length?`incomplete`:o.includes(`poor`)?`poor`:o.includes(`needs-work`)?`needs-work`:`good`;return{source:`crux`,status:`available`,scope:e.record.key?.url?`url`:`origin`,formFactor:e.requestedFormFactor,url:e.record.key?.url,origin:e.record.key?.origin,collectionPeriod:{firstDate:rj(e.record.collectionPeriod?.firstDate),lastDate:rj(e.record.collectionPeriod?.lastDate)},metrics:n,rawMetrics:t,assessment:{status:s,availableMetrics:i.length,missingMetrics:a}}}function rj(e){if(!(!e?.year||!e.month||!e.day))return`${String(e.year).padStart(4,`0`)}-${String(e.month).padStart(2,`0`)}-${String(e.day).padStart(2,`0`)}`}function ij(e){if(!e.audit)return;let t=typeof e.audit.numericValue==`number`&&Number.isFinite(e.audit.numericValue)?Math.round(e.audit.numericValue*1e3)/1e3:void 0;return{value:t,displayValue:e.audit.displayValue,score:typeof e.audit.score==`number`?e.audit.score:void 0,rating:typeof e.audit.score==`number`?e.audit.score>=.9?`good`:e.audit.score>=.5?`needs-work`:`poor`:t===void 0?`unknown`:e.rating?.(t),source:`lighthouse-lab`}}function aj(e,t,n){return e<=t?`good`:e<=n?`needs-work`:`poor`}function oj(e){return{title:e.title,plainEnglish:`${e.metric} is ${e.display} (${e.source}).`,action:e.action,evidence:{metric:e.metric,value:e.value,source:e.source}}}function sj(e){return typeof e?.value==`number`&&(e.rating===`needs-work`||e.rating===`poor`)}function cj(e){let t=[];if(e.fallbackEvidence?.blocked)return t.push({title:`Resolve crawl access first`,plainEnglish:`The fallback fetch was blocked, so it cannot support a page performance conclusion.`,action:`Check robots and local fetch access, then rerun Lighthouse before reviewing performance.`,evidence:e.fallbackEvidence}),t;if(e.fallbackEvidence&&(e.fallbackEvidence.httpStatus<200||e.fallbackEvidence.httpStatus>=300))return t.push({title:`Fix the page response first`,plainEnglish:`The fallback fetch returned HTTP ${e.fallbackEvidence.httpStatus}.`,action:`Resolve the HTTP response before drawing any performance conclusion for this URL.`,evidence:e.fallbackEvidence}),t;let n=e.fieldData?.metrics,r=n?.largestContentfulPaint;r&&r.rating!==`good`&&t.push(oj({title:`Improve the largest visible content`,metric:`LCP p75`,value:r.p75,display:`${r.p75}ms`,source:`CrUX ${e.fieldData?.scope} ${e.fieldData?.formFactor}`,action:`Optimize the LCP resource and server path, then verify the change in field data.`}));let i=e.metrics.largestContentfulPaint;sj(i)&&(!r||r.rating===`good`)&&t.push(oj({title:r?`Investigate the current lab LCP regression`:`Improve the largest visible content`,metric:`Lighthouse LCP`,value:i.value,display:i.displayValue??`${i.value}ms`,source:`Lighthouse lab run`,action:`Optimize the LCP resource, render path, and HTML delivery, then repeat controlled lab runs.`}));let a=n?.interactionToNextPaint;a&&a.rating!==`good`&&t.push(oj({title:`Improve interaction responsiveness`,metric:`INP p75`,value:a.p75,display:`${a.p75}ms`,source:`CrUX ${e.fieldData?.scope} ${e.fieldData?.formFactor}`,action:`Reduce long interaction tasks and rendering work, then verify the change in field data.`}));let o=e.metrics.totalBlockingTime;sj(o)&&t.push(oj({title:`Reduce main-thread blocking`,metric:`Lighthouse TBT`,value:o.value,display:o.displayValue??`${o.value}ms`,source:`Lighthouse lab diagnostic, not field INP`,action:`Defer non-critical JavaScript, split long tasks, and repeat controlled lab runs.`}));let s=n?.cumulativeLayoutShift;s&&s.rating!==`good`&&t.push(oj({title:`Reduce layout shifts`,metric:`CLS p75`,value:s.p75,display:String(s.p75),source:`CrUX ${e.fieldData?.scope} ${e.fieldData?.formFactor}`,action:`Reserve stable layout space and identify shifts across the full page lifecycle.`}));let c=e.metrics.cumulativeLayoutShift;sj(c)&&(!s||s.rating===`good`)&&t.push(oj({title:s?`Investigate the current lab layout shift`:`Reduce layout shifts`,metric:`Lighthouse CLS`,value:c.value,display:c.displayValue??String(c.value),source:`Lighthouse lab run`,action:`Reserve dimensions for images, ads, embeds, banners, and late-loading UI.`}));let l=e.metrics.serverResponseTime;sj(l)&&t.push(oj({title:`Reduce Lighthouse server response time`,metric:`Lighthouse server response time`,value:l.value,display:l.displayValue??`${l.value}ms`,source:`Lighthouse lab diagnostic`,action:`Check origin work and HTML caching, then validate with repeat lab runs and separate field TTFB data.`}));for(let n of(e.labInsights??[]).filter(e=>(e.estimatedSavingsMs??0)>0).slice(0,3))t.push({title:`Review Lighthouse insight: ${n.title}`,plainEnglish:`Lighthouse estimates about ${Math.round(n.estimatedSavingsMs??0)}ms of potential savings for ${n.title}.`,action:`Inspect the returned evidence, fix the measured bottleneck, and confirm it with repeat controlled lab runs.`,evidence:{insightId:n.id,estimatedSavingsMs:n.estimatedSavingsMs}});return!t.length&&e.labDataStatus?.status===`unavailable`&&!e.fieldData&&t.push({title:`Collect performance evidence`,plainEnglish:`No Lighthouse lab metrics or CrUX field Core Web Vitals were available.`,action:`Restore the local Lighthouse browser runtime or configure CrUX, then rerun before making performance claims.`}),t.length||t.push({title:`Keep monitoring performance`,plainEnglish:`No measured lab or field metric crossed its review threshold.`,action:`Re-run after deploys and avoid generalizing this single URL and measurement window to the whole site.`}),t}const lj=[`cumulative_layout_shift`,`interaction_to_next_paint`,`largest_contentful_paint`];async function uj(e){let t=e.url,r=new URL(e.url).origin,i=e.strategy===`desktop`?`DESKTOP`:`PHONE`;if(!e.apiKey)return{status:{provider:`crux`,status:`not_configured`,reason:`Chrome UX Report field data is not configured.`,checkedUrl:t,checkedOrigin:r,formFactor:i},caveat:`Field Core Web Vitals were not checked in this run.`};let a=e.fetchImpl??n,o=`https://chromeuxreport.googleapis.com/v1/records:queryRecord?key=${encodeURIComponent(e.apiKey)}`;async function s(t){return w(async()=>{let n=new AbortController,r=setTimeout(()=>n.abort(),e.timeoutMs??1e4);try{let e=await a(o,{method:`POST`,body:JSON.stringify({...t,formFactor:i,metrics:lj}),headers:{"content-type":`application/json`},signal:n.signal}),r=await e.json().catch(()=>({}));if(e.status===404)return{response:e,json:r};if(e.status===429||e.status>=500)throw Error(`CrUX transient HTTP ${e.status}`);if(!e.ok)return{response:e,json:r};if(!r.record)throw Error(`CrUX returned no record.`);return{response:e,json:r}}finally{clearTimeout(r)}},{retries:2,minTimeout:250,factor:2})}try{let e=[{scope:`url`,body:{url:t}},{scope:`origin`,body:{origin:r}}],n;for(let a of e){let{response:e,json:o}=await s(a.body);if(n=e.status,e.ok&&o.record)return{fieldData:nj({record:o.record,requestedFormFactor:i}),status:{provider:`crux`,status:`available`,reason:`Chrome UX Report ${a.scope}-level field data was available.`,checkedUrl:t,checkedOrigin:r,formFactor:i,httpStatus:e.status},caveat:`CrUX ${a.scope}-level ${i.toLowerCase()} field data covers a rolling collection period; it is not the Lighthouse run.`};if(!e.ok&&e.status!==404)return{status:{provider:`crux`,status:`request_failed`,reason:`Chrome UX Report field data could not be fetched.`,checkedUrl:t,checkedOrigin:r,formFactor:i,httpStatus:e.status},caveat:`Chrome UX Report field data could not be fetched right now.`}}return{status:{provider:`crux`,status:`unavailable_no_coverage`,reason:`Chrome UX Report does not have enough field data for this URL or origin.`,checkedUrl:t,checkedOrigin:r,formFactor:i,httpStatus:n},caveat:`Chrome UX Report does not have enough device-specific field data for this URL or origin.`}}catch{return{status:{provider:`crux`,status:`request_failed`,reason:`Chrome UX Report field data could not be fetched.`,checkedUrl:t,checkedOrigin:r,formFactor:i},caveat:`Chrome UX Report field data could not be fetched right now.`}}}const dj=se(D);function fj(e){return!e||typeof e!=`object`?{}:Object.fromEntries(Object.entries(e).filter(e=>{let t=e[1];return typeof t==`number`||typeof t==`boolean`||typeof t==`string`&&t.length<=160}).slice(0,4))}function pj(e){let t=Object.entries(e).filter(([e,t])=>{let n=e.endsWith(`-insight`),r=(t.details?.overallSavingsMs??0)>0;return(n||r)&&t.score!==1}).map(([e,t])=>{let n=[fj(t.details?.debugData),...(t.details?.items??[]).slice(0,2).map(fj)].filter(e=>Object.keys(e).length>0);return{id:e,title:t.title??e,displayValue:t.displayValue,score:t.score,estimatedSavingsMs:t.details?.overallSavingsMs,evidence:n}}).sort((e,t)=>(t.estimatedSavingsMs??0)-(e.estimatedSavingsMs??0)||e.id.localeCompare(t.id)).slice(0,8),n=[];for(let e of t){if(Buffer.byteLength(JSON.stringify([...n,e]),`utf8`)>24e3)break;n.push(e)}return n}function mj(e){let t=e[`server-response-time`];if(t?.numericValue!==void 0)return t;let n=e[`document-latency-insight`],r=n?.details?.debugData?.serverResponseTime;return typeof r==`number`?{numericValue:r,displayValue:`${Math.round(r)} ms`,score:n?.score}:void 0}function hj(){let e=oe(import.meta.resolve(`lighthouse/cli/index.js`));return{bin:process.execPath,prefixArgs:[e]}}function gj(){let e=ng(ie.executablePath());return e.status===`available`?{...process.env,CHROME_PATH:e.browser.path}:process.env}var _j=class extends Error{failureCode;constructor(e){super(e),this.failureCode=e}};function vj(e){let t=e,n=t?.message?.toLowerCase()??``;return t?.code===`ENOENT`||n.includes(`cannot find package`)?`binary_missing`:t?.code===`ETIMEDOUT`||t?.killed||t?.signal===`SIGTERM`||n.includes(`timed out`)?`timeout`:n.includes(`chrome`)&&(n.includes(`not found`)||n.includes(`no usable`)||n.includes(`install`))?`chrome_missing`:e instanceof SyntaxError?`invalid_result`:`run_failed`}function yj(e){let t=e instanceof _j?e.failureCode:vj(e);return{failureCode:t,reason:{binary_missing:`The Lighthouse runtime could not be found. Reinstall seo or remove the custom binary override.`,chrome_missing:`Lighthouse could not find a compatible local Chrome browser.`,invalid_result:`Lighthouse completed without a usable navigation performance result.`,run_failed:`Lighthouse could not complete the navigation lab run.`,timeout:`Lighthouse exceeded the bounded lab-run timeout.`}[t]}}async function bj(e){let t=e.lighthouseBin?{bin:e.lighthouseBin,prefixArgs:[]}:hj(),{stdout:n}=await dj(t.bin,[...t.prefixArgs,e.url,`--quiet`,`--output=json`,`--output-path=stdout`,`--only-categories=performance`,`--preset=${e.strategy===`desktop`?`desktop`:`perf`}`,`--chrome-flags=--headless=new`],{env:gj(),timeout:e.timeoutMs??12e4,maxBuffer:30*1024*1024}),r=n.indexOf(`{`);if(r<0)throw new _j(`invalid_result`);let i=JSON.parse(n.slice(r)),a=i.audits??{},o=i.categories?.performance?.score,s=typeof o==`number`?Math.round(o*100):void 0;if(![`first-contentful-paint`,`largest-contentful-paint`,`total-blocking-time`,`cumulative-layout-shift`].some(e=>typeof a[e]?.numericValue==`number`))throw new _j(`invalid_result`);let c={schemaVersion:1,methodology:`performance-v2`,dataStatus:`complete`,id:e.id,url:e.url,finalUrl:i.finalUrl,strategy:e.strategy,generatedAt:e.generatedAt,cache:{status:`miss`,ttlHours:24},source:`lighthouse`,score:s,grade:s===void 0?`unknown`:s>=90?`good`:s>=50?`needs-work`:`poor`,headline:s===void 0?`Lighthouse ran, but no lab performance score was returned.`:`Lighthouse lab performance score is ${s}/100.`,metrics:{firstContentfulPaint:ij({audit:a[`first-contentful-paint`],rating:e=>aj(e,1800,3e3)}),largestContentfulPaint:ij({audit:a[`largest-contentful-paint`],rating:e=>aj(e,2500,4e3)}),totalBlockingTime:ij({audit:a[`total-blocking-time`],rating:e=>aj(e,200,600)}),cumulativeLayoutShift:ij({audit:a[`cumulative-layout-shift`],rating:e=>aj(e,.1,.25)}),interactionToNextPaint:ij({audit:a[`interaction-to-next-paint`],rating:e=>aj(e,200,500)}),speedIndex:ij({audit:a[`speed-index`]}),serverResponseTime:ij({audit:mj(a),rating:e=>aj(e,600,600)})},labInsights:pj(a),labDataStatus:{provider:`lighthouse`,status:`available`,reason:`Lighthouse ${i.lighthouseVersion??`unknown version`} completed a ${e.strategy} navigation lab run.`},fieldDataStatus:{provider:`crux`,status:`not_configured`,reason:`Field Core Web Vitals were not checked yet.`,checkedUrl:e.url,checkedOrigin:new URL(e.url).origin,formFactor:e.strategy===`desktop`?`DESKTOP`:`PHONE`},caveats:[`Lighthouse is one controlled lab run; its score and timings can vary between runs and are not field Core Web Vitals.`,`Navigation Lighthouse uses Total Blocking Time as a lab responsiveness diagnostic; field INP comes from CrUX when available.`],topActions:[],...e.includeRaw?{raw:i}:{}};return i.runWarnings?.length&&(c.caveats.push(`Lighthouse returned ${i.runWarnings.length} run warning${i.runWarnings.length===1?``:`s`}; inspect the compact lab insights or rerun before relying on the score.`),c.dataStatus=`partial`),s===void 0&&(c.dataStatus=`partial`,c.caveats.push(`Lighthouse returned usable lab metrics without a numeric performance category score.`)),c}function xj(e){return`perf_${_(`sha256`).update(JSON.stringify({methodology:`performance-v2`,...e})).digest(`hex`).slice(0,20)}`}function Sj(e,t){let n=j().prepare(`SELECT report_json FROM performance_reports WHERE id = ? AND expires_at > ?`).get(e,t);if(n?.report_json)try{let e=JSON.parse(n.report_json);return Dj(e)&&Oj({report:e})?e:void 0}catch{return}}function Cj(e,t){let n={...e,raw:void 0};j().prepare(`INSERT OR REPLACE INTO performance_reports
702
+ ORDER BY seen_count DESC, MAX(created_at) DESC`).all(e,...r.map(e=>e.id)).map(e=>({url:e.url,seenCount:e.seen_count}))}function sj(e,t){try{let n=new URL(e,t);return n.hash=``,n.toString()}catch{return}}function cj(e){let t=[],n=e.chain.at(-1)?.status??0,r=e.chain.some(e=>e.status>=300&&e.status<400&&!e.location);return e.stoppedForLoop&&t.push(`redirect-loop`),e.stoppedForLimit&&t.push(`too-many-redirects`),r&&t.push(`redirect-without-location`),n>=400&&n<500&&t.push(`final-4xx`),n>=500&&t.push(`final-5xx`),e.finalIndexable===!1&&t.push(`non-indexable-final`),e.canonical&&e.canonical!==e.finalUrl&&t.push(`canonical-mismatch`),[...new Set(t)]}async function lj(e){let t=e.maxHops??10,n=[],r=[],i=new Set,a=new URL(e.url).toString(),o=!1,s=!1;for(let s=0;s<=t;s+=1){if(i.has(a)){o=!0;break}i.add(a);let t=Date.now(),s=new AbortController,c=setTimeout(()=>s.abort(),e.timeoutMs??2e4);try{let e=await Oh(a,{redirect:`manual`,signal:s.signal}),n=e.headers.get(`location`)??void 0,i=n?sj(n,a):void 0;if(r.push({url:a,status:e.status,location:n,nextUrl:i,durationMs:Date.now()-t}),e.status<300||e.status>=400||!i)break;a=i}catch(e){n.push(`${a}: ${e instanceof Error?e.message:String(e)}`);break}finally{clearTimeout(c)}}let c=r.at(-1);c&&c.status>=300&&c.status<400&&r.length>t&&(s=!0);let l=c?.nextUrl??c?.url??a,u=await lS(l,{refresh:e.refresh,js:e.js??`auto`});u.warning&&n.push(u.warning);let d=u.page?{title:u.page.title,canonical:u.page.canonical,metaRobots:u.page.metaRobots,xRobotsTag:u.page.xRobotsTag,indexable:u.page.indexable}:void 0,f=cj({chain:r,finalIndexable:d?.indexable,canonical:d?.canonical,finalUrl:l,stoppedForLoop:o,stoppedForLimit:s});return{url:e.url,finalUrl:l,generatedAt:new Date().toISOString(),summary:{hops:Math.max(0,r.length-1),finalStatus:u.page?.status??r.at(-1)?.status??0,finalIndexable:d?.indexable,issues:f},chain:r,finalPage:d,warnings:n}}function uj(e){return[`final-5xx`,`final-4xx`,`redirect-loop`,`too-many-redirects`,`non-indexable-final`,`canonical-mismatch`,`redirect-without-location`].find(t=>e.includes(t))??e[0]??`final-4xx`}function dj(e,t){return e===`final-5xx`||e===`final-4xx`?t.clicks>=10||t.impressions>=500?`high`:`medium`:e===`redirect-loop`||e===`too-many-redirects`?`high`:e===`non-indexable-final`?t.clicks>=5||t.impressions>=250?`high`:`medium`:t.clicks>=10||t.impressions>=500?`medium`:`low`}function fj(e){let t=`${Math.round(e.clicks).toLocaleString(`en-GB`)} clicks and ${Math.round(e.impressions).toLocaleString(`en-GB`)} impressions in the checked period.`;return e.issue===`final-4xx`?{principle:`Search-value URLs should not resolve to dead pages.`,evidenceRef:e.url,action:`This search-visible URL now lands on a dead page. Restore the page if it should exist, or add one direct 301 redirect to the closest live replacement.`,effort:`S`,confidence:`high`,impactEstimate:t}:e.issue===`final-5xx`?{principle:`Server errors on search-visible URLs waste existing demand.`,evidenceRef:e.url,action:`This search-visible URL ends in a server error. Fix the server response first, then rerun URL Inspection once it returns a stable 200 or an intentional 301.`,effort:`S`,confidence:`high`,impactEstimate:t}:e.issue===`redirect-loop`||e.issue===`too-many-redirects`||e.issue===`redirect-without-location`?{principle:`Redirect chains must resolve cleanly to a crawlable target.`,evidenceRef:e.url,action:`The redirect path is broken. Replace the chain with one direct 301 from the old URL to the intended live destination.`,effort:`M`,confidence:`high`,impactEstimate:t}:e.issue===`non-indexable-final`?{principle:`Recovered search-value URLs need an indexable final target.`,evidenceRef:e.finalUrl,action:`The final destination is not indexable. Remove accidental noindex/robots blocking, or redirect the old URL to an indexable equivalent.`,effort:`S`,confidence:`high`,impactEstimate:t}:{principle:`Canonicals should reinforce the final URL selected by redirects.`,evidenceRef:e.finalUrl,action:`The redirect target and canonical target disagree. Make the canonical match the final destination, or redirect the original URL directly to the canonical page.`,effort:`S`,confidence:`medium`,impactEstimate:t}}function pj(e){return{url:e.keys[0]??``,clicks:e.clicks,impressions:e.impressions,position:e.position}}async function mj(e){let t=e.days??90,n=H_(t),r=await e_(e.site,{...n,dimensions:[`page`],type:`web`,dataState:`final`},{refresh:e.refresh}),i=e.minClicks??1,a=e.minImpressions??100,o=r.rows.map(pj).filter(e=>e.url&&(e.clicks>=i||e.impressions>=a)).sort((e,t)=>t.clicks-e.clicks||t.impressions-e.impressions).slice(0,e.limit??50),s=[],c=[];for(let t of o){let n=await lj({url:t.url,refresh:e.refresh,js:e.js??`auto`});if(c.push(...n.warnings),!n.summary.issues.length)continue;let r=uj(n.summary.issues),i=dj(r,t);s.push({url:t.url,finalUrl:n.finalUrl,clicks:t.clicks,impressions:t.impressions,position:t.position,issue:r,issues:n.summary.issues,severity:i,trace:{summary:n.summary,chain:n.chain,finalPage:n.finalPage,warnings:n.warnings},recommendation:fj({issue:r,url:t.url,finalUrl:n.finalUrl,clicks:t.clicks,impressions:t.impressions})})}let l={site:e.site,generatedAt:new Date().toISOString(),range:{...n,days:t},summary:{checked:o.length,recoverable:s.length,high:s.filter(e=>e.severity===`high`).length,medium:s.filter(e=>e.severity===`medium`).length,low:s.filter(e=>e.severity===`low`).length,clicksAtRisk:s.reduce((e,t)=>e+t.clicks,0),impressionsAtRisk:s.reduce((e,t)=>e+t.impressions,0)},items:s,warnings:[...new Set(c)].slice(0,50)};return ij(l),l}function hj(e,t){if(!e)return!1;let n=Date.parse(e);return Number.isFinite(n)?Date.now()-n>t*864e5:!1}function gj(e){return e.some(e=>e.status===`attention`)?`attention`:e.some(e=>e.status===`stale`)?`stale`:e.some(e=>e.status===`not-run`)?`not-run`:`clear`}function _j(e){let t=e.staleAfterDays??8,n=Hk(e.site,1)[0],r=ZA(e.site),i=aj(e.site),a=[];if(!n)a.push({name:`crawl`,status:`not-run`,summary:`No saved crawl-diff run.`,action:`Run monitoring with a start URL to establish a crawl baseline.`});else{let e=n.highPriorityRecommendations>0;a.push({name:`crawl`,status:e?`attention`:hj(n.createdAt,t)?`stale`:`clear`,lastRunAt:n.createdAt,summary:`${Z(n.urlCount,`URL`)}, ${Z(n.statusErrors,`status error`)}, ${n.nonIndexable} non-indexable, ${Z(n.highPriorityRecommendations,`high-priority action`)}.`,action:e?n.topRecommendation?.action??`Review the saved crawl recommendations.`:void 0})}if(!r.latestInspectedAt)a.push({name:`index`,status:`not-run`,summary:`No saved URL Inspection snapshot.`,action:`Run monitoring with URLs or sitemaps to establish index status history.`});else{let e=r.currentIssues>0||r.failed>0;a.push({name:`index`,status:e?`attention`:hj(r.earliestInspectedAt,t)?`stale`:`clear`,lastRunAt:r.latestInspectedAt,summary:`${Z(r.inspectedUrls,`URL`)} tracked, ${Z(r.currentIssues,`current indexing review`)}, ${Z(r.failed,`failed inspection`)}, ${Z(r.blocked,`exact blocked state`)}.`,action:e?`Review current URL Inspection issue codes and failed checks before content changes.`:void 0})}if(!i)a.push({name:`link-recovery`,status:`not-run`,summary:`No saved link-recover run.`,action:`Run monitoring with link recovery enabled to find search-value URLs that now fail.`});else{let e=i.high>0||i.medium>0;a.push({name:`link-recovery`,status:e?`attention`:hj(i.createdAt,t)?`stale`:`clear`,lastRunAt:i.createdAt,summary:`${Z(i.checked,`URL`)} checked, ${i.recoverable} recoverable, ${i.high} high severity, ${i.clicksAtRisk.toFixed(0)} clicks at risk.`,action:e?i.topAction??`Fix recoverable search-value URL issues.`:void 0})}return{site:e.site,generatedAt:new Date().toISOString(),health:gj(a),summary:{checks:a.length,attention:a.filter(e=>e.status===`attention`).length,stale:a.filter(e=>e.status===`stale`).length,notRun:a.filter(e=>e.status===`not-run`).length},checks:a}}const vj={CLS:{good:.1,poor:.25,unit:`score`},INP:{good:200,poor:500,unit:`milliseconds`},LCP:{good:2500,poor:4e3,unit:`milliseconds`}};function yj(e){if(typeof e==`number`&&Number.isFinite(e))return e;if(typeof e!=`string`||e.trim()===``)return;let t=Number(e);return Number.isFinite(t)?t:void 0}function bj(e,t){let n=yj(t?.percentiles?.p75);if(n===void 0||n<0)return;let r=vj[e];return{p75:n,unit:r.unit,rating:n<=r.good?`good`:n<=r.poor?`needs-work`:`poor`}}function xj(e){let t=e.record.metrics??{},n={cumulativeLayoutShift:bj(`CLS`,t.cumulative_layout_shift),interactionToNextPaint:bj(`INP`,t.interaction_to_next_paint),largestContentfulPaint:bj(`LCP`,t.largest_contentful_paint)},r=[[`CLS`,n.cumulativeLayoutShift],[`INP`,n.interactionToNextPaint],[`LCP`,n.largestContentfulPaint]],i=r.filter(e=>e[1]!==void 0),a=r.filter(e=>e[1]===void 0).map(e=>e[0]),o=i.map(e=>e[1]?.rating),s=a.length?`incomplete`:o.includes(`poor`)?`poor`:o.includes(`needs-work`)?`needs-work`:`good`;return{source:`crux`,status:`available`,scope:e.record.key?.url?`url`:`origin`,formFactor:e.requestedFormFactor,url:e.record.key?.url,origin:e.record.key?.origin,collectionPeriod:{firstDate:Sj(e.record.collectionPeriod?.firstDate),lastDate:Sj(e.record.collectionPeriod?.lastDate)},metrics:n,rawMetrics:t,assessment:{status:s,availableMetrics:i.length,missingMetrics:a}}}function Sj(e){if(!(!e?.year||!e.month||!e.day))return`${String(e.year).padStart(4,`0`)}-${String(e.month).padStart(2,`0`)}-${String(e.day).padStart(2,`0`)}`}function Cj(e){if(!e.audit)return;let t=typeof e.audit.numericValue==`number`&&Number.isFinite(e.audit.numericValue)?Math.round(e.audit.numericValue*1e3)/1e3:void 0;return{value:t,displayValue:e.audit.displayValue,score:typeof e.audit.score==`number`?e.audit.score:void 0,rating:typeof e.audit.score==`number`?e.audit.score>=.9?`good`:e.audit.score>=.5?`needs-work`:`poor`:t===void 0?`unknown`:e.rating?.(t),source:`lighthouse-lab`}}function wj(e,t,n){return e<=t?`good`:e<=n?`needs-work`:`poor`}function Tj(e){return{title:e.title,plainEnglish:`${e.metric} is ${e.display} (${e.source}).`,action:e.action,evidence:{metric:e.metric,value:e.value,source:e.source}}}function Ej(e){return typeof e?.value==`number`&&(e.rating===`needs-work`||e.rating===`poor`)}function Dj(e){let t=[];if(e.fallbackEvidence?.blocked)return t.push({title:`Resolve crawl access first`,plainEnglish:`The fallback fetch was blocked, so it cannot support a page performance conclusion.`,action:`Check robots and local fetch access, then rerun Lighthouse before reviewing performance.`,evidence:e.fallbackEvidence}),t;if(e.fallbackEvidence&&(e.fallbackEvidence.httpStatus<200||e.fallbackEvidence.httpStatus>=300))return t.push({title:`Fix the page response first`,plainEnglish:`The fallback fetch returned HTTP ${e.fallbackEvidence.httpStatus}.`,action:`Resolve the HTTP response before drawing any performance conclusion for this URL.`,evidence:e.fallbackEvidence}),t;let n=e.fieldData?.metrics,r=n?.largestContentfulPaint;r&&r.rating!==`good`&&t.push(Tj({title:`Improve the largest visible content`,metric:`LCP p75`,value:r.p75,display:`${r.p75}ms`,source:`CrUX ${e.fieldData?.scope} ${e.fieldData?.formFactor}`,action:`Optimize the LCP resource and server path, then verify the change in field data.`}));let i=e.metrics.largestContentfulPaint;Ej(i)&&(!r||r.rating===`good`)&&t.push(Tj({title:r?`Investigate the current lab LCP regression`:`Improve the largest visible content`,metric:`Lighthouse LCP`,value:i.value,display:i.displayValue??`${i.value}ms`,source:`Lighthouse lab run`,action:`Optimize the LCP resource, render path, and HTML delivery, then repeat controlled lab runs.`}));let a=n?.interactionToNextPaint;a&&a.rating!==`good`&&t.push(Tj({title:`Improve interaction responsiveness`,metric:`INP p75`,value:a.p75,display:`${a.p75}ms`,source:`CrUX ${e.fieldData?.scope} ${e.fieldData?.formFactor}`,action:`Reduce long interaction tasks and rendering work, then verify the change in field data.`}));let o=e.metrics.totalBlockingTime;Ej(o)&&t.push(Tj({title:`Reduce main-thread blocking`,metric:`Lighthouse TBT`,value:o.value,display:o.displayValue??`${o.value}ms`,source:`Lighthouse lab diagnostic, not field INP`,action:`Defer non-critical JavaScript, split long tasks, and repeat controlled lab runs.`}));let s=n?.cumulativeLayoutShift;s&&s.rating!==`good`&&t.push(Tj({title:`Reduce layout shifts`,metric:`CLS p75`,value:s.p75,display:String(s.p75),source:`CrUX ${e.fieldData?.scope} ${e.fieldData?.formFactor}`,action:`Reserve stable layout space and identify shifts across the full page lifecycle.`}));let c=e.metrics.cumulativeLayoutShift;Ej(c)&&(!s||s.rating===`good`)&&t.push(Tj({title:s?`Investigate the current lab layout shift`:`Reduce layout shifts`,metric:`Lighthouse CLS`,value:c.value,display:c.displayValue??String(c.value),source:`Lighthouse lab run`,action:`Reserve dimensions for images, ads, embeds, banners, and late-loading UI.`}));let l=e.metrics.serverResponseTime;Ej(l)&&t.push(Tj({title:`Reduce Lighthouse server response time`,metric:`Lighthouse server response time`,value:l.value,display:l.displayValue??`${l.value}ms`,source:`Lighthouse lab diagnostic`,action:`Check origin work and HTML caching, then validate with repeat lab runs and separate field TTFB data.`}));for(let n of(e.labInsights??[]).filter(e=>(e.estimatedSavingsMs??0)>0).slice(0,3))t.push({title:`Review Lighthouse insight: ${n.title}`,plainEnglish:`Lighthouse estimates about ${Math.round(n.estimatedSavingsMs??0)}ms of potential savings for ${n.title}.`,action:`Inspect the returned evidence, fix the measured bottleneck, and confirm it with repeat controlled lab runs.`,evidence:{insightId:n.id,estimatedSavingsMs:n.estimatedSavingsMs}});return!t.length&&e.labDataStatus?.status===`unavailable`&&!e.fieldData&&t.push({title:`Collect performance evidence`,plainEnglish:`No Lighthouse lab metrics or CrUX field Core Web Vitals were available.`,action:`Restore the local Lighthouse browser runtime or configure CrUX, then rerun before making performance claims.`}),t.length||t.push({title:`Keep monitoring performance`,plainEnglish:`No measured lab or field metric crossed its review threshold.`,action:`Re-run after deploys and avoid generalizing this single URL and measurement window to the whole site.`}),t}const Oj=[`cumulative_layout_shift`,`interaction_to_next_paint`,`largest_contentful_paint`];async function kj(e){let t=e.url,r=new URL(e.url).origin,i=e.strategy===`desktop`?`DESKTOP`:`PHONE`;if(!e.apiKey)return{status:{provider:`crux`,status:`not_configured`,reason:`Chrome UX Report field data is not configured.`,checkedUrl:t,checkedOrigin:r,formFactor:i},caveat:`Field Core Web Vitals were not checked in this run.`};let a=e.fetchImpl??n,o=`https://chromeuxreport.googleapis.com/v1/records:queryRecord?key=${encodeURIComponent(e.apiKey)}`;async function s(t){return w(async()=>{let n=new AbortController,r=setTimeout(()=>n.abort(),e.timeoutMs??1e4);try{let e=await a(o,{method:`POST`,body:JSON.stringify({...t,formFactor:i,metrics:Oj}),headers:{"content-type":`application/json`},signal:n.signal}),r=await e.json().catch(()=>({}));if(e.status===404)return{response:e,json:r};if(e.status===429||e.status>=500)throw Error(`CrUX transient HTTP ${e.status}`);if(!e.ok)return{response:e,json:r};if(!r.record)throw Error(`CrUX returned no record.`);return{response:e,json:r}}finally{clearTimeout(r)}},{retries:2,minTimeout:250,factor:2})}try{let e=[{scope:`url`,body:{url:t}},{scope:`origin`,body:{origin:r}}],n;for(let a of e){let{response:e,json:o}=await s(a.body);if(n=e.status,e.ok&&o.record)return{fieldData:xj({record:o.record,requestedFormFactor:i}),status:{provider:`crux`,status:`available`,reason:`Chrome UX Report ${a.scope}-level field data was available.`,checkedUrl:t,checkedOrigin:r,formFactor:i,httpStatus:e.status},caveat:`CrUX ${a.scope}-level ${i.toLowerCase()} field data covers a rolling collection period; it is not the Lighthouse run.`};if(!e.ok&&e.status!==404)return{status:{provider:`crux`,status:`request_failed`,reason:`Chrome UX Report field data could not be fetched.`,checkedUrl:t,checkedOrigin:r,formFactor:i,httpStatus:e.status},caveat:`Chrome UX Report field data could not be fetched right now.`}}return{status:{provider:`crux`,status:`unavailable_no_coverage`,reason:`Chrome UX Report does not have enough field data for this URL or origin.`,checkedUrl:t,checkedOrigin:r,formFactor:i,httpStatus:n},caveat:`Chrome UX Report does not have enough device-specific field data for this URL or origin.`}}catch{return{status:{provider:`crux`,status:`request_failed`,reason:`Chrome UX Report field data could not be fetched.`,checkedUrl:t,checkedOrigin:r,formFactor:i},caveat:`Chrome UX Report field data could not be fetched right now.`}}}const Aj=se(D);function jj(e){return!e||typeof e!=`object`?{}:Object.fromEntries(Object.entries(e).filter(e=>{let t=e[1];return typeof t==`number`||typeof t==`boolean`||typeof t==`string`&&t.length<=160}).slice(0,4))}function Mj(e){let t=Object.entries(e).filter(([e,t])=>{let n=e.endsWith(`-insight`),r=(t.details?.overallSavingsMs??0)>0;return(n||r)&&t.score!==1}).map(([e,t])=>{let n=[jj(t.details?.debugData),...(t.details?.items??[]).slice(0,2).map(jj)].filter(e=>Object.keys(e).length>0);return{id:e,title:t.title??e,displayValue:t.displayValue,score:t.score,estimatedSavingsMs:t.details?.overallSavingsMs,evidence:n}}).sort((e,t)=>(t.estimatedSavingsMs??0)-(e.estimatedSavingsMs??0)||e.id.localeCompare(t.id)).slice(0,8),n=[];for(let e of t){if(Buffer.byteLength(JSON.stringify([...n,e]),`utf8`)>24e3)break;n.push(e)}return n}function Nj(e){let t=e[`server-response-time`];if(t?.numericValue!==void 0)return t;let n=e[`document-latency-insight`],r=n?.details?.debugData?.serverResponseTime;return typeof r==`number`?{numericValue:r,displayValue:`${Math.round(r)} ms`,score:n?.score}:void 0}function Pj(){let e=oe(import.meta.resolve(`lighthouse/cli/index.js`));return{bin:process.execPath,prefixArgs:[e]}}function Fj(){let e=ng(ie.executablePath());return e.status===`available`?{...process.env,CHROME_PATH:e.browser.path}:process.env}var Ij=class extends Error{failureCode;constructor(e){super(e),this.failureCode=e}};function Lj(e){let t=e,n=t?.message?.toLowerCase()??``;return t?.code===`ENOENT`||n.includes(`cannot find package`)?`binary_missing`:t?.code===`ETIMEDOUT`||t?.killed||t?.signal===`SIGTERM`||n.includes(`timed out`)?`timeout`:n.includes(`chrome`)&&(n.includes(`not found`)||n.includes(`no usable`)||n.includes(`install`))?`chrome_missing`:e instanceof SyntaxError?`invalid_result`:`run_failed`}function Rj(e){let t=e instanceof Ij?e.failureCode:Lj(e);return{failureCode:t,reason:{binary_missing:`The Lighthouse runtime could not be found. Reinstall seo or remove the custom binary override.`,chrome_missing:`Lighthouse could not find a compatible local Chrome browser.`,invalid_result:`Lighthouse completed without a usable navigation performance result.`,run_failed:`Lighthouse could not complete the navigation lab run.`,timeout:`Lighthouse exceeded the bounded lab-run timeout.`}[t]}}async function zj(e){let t=e.lighthouseBin?{bin:e.lighthouseBin,prefixArgs:[]}:Pj(),{stdout:n}=await Aj(t.bin,[...t.prefixArgs,e.url,`--quiet`,`--output=json`,`--output-path=stdout`,`--only-categories=performance`,`--preset=${e.strategy===`desktop`?`desktop`:`perf`}`,`--chrome-flags=--headless=new`],{env:Fj(),timeout:e.timeoutMs??12e4,maxBuffer:30*1024*1024}),r=n.indexOf(`{`);if(r<0)throw new Ij(`invalid_result`);let i=JSON.parse(n.slice(r)),a=i.audits??{},o=i.categories?.performance?.score,s=typeof o==`number`?Math.round(o*100):void 0;if(![`first-contentful-paint`,`largest-contentful-paint`,`total-blocking-time`,`cumulative-layout-shift`].some(e=>typeof a[e]?.numericValue==`number`))throw new Ij(`invalid_result`);let c={schemaVersion:1,methodology:`performance-v2`,dataStatus:`complete`,id:e.id,url:e.url,finalUrl:i.finalUrl,strategy:e.strategy,generatedAt:e.generatedAt,cache:{status:`miss`,ttlHours:24},source:`lighthouse`,score:s,grade:s===void 0?`unknown`:s>=90?`good`:s>=50?`needs-work`:`poor`,headline:s===void 0?`Lighthouse ran, but no lab performance score was returned.`:`Lighthouse lab performance score is ${s}/100.`,metrics:{firstContentfulPaint:Cj({audit:a[`first-contentful-paint`],rating:e=>wj(e,1800,3e3)}),largestContentfulPaint:Cj({audit:a[`largest-contentful-paint`],rating:e=>wj(e,2500,4e3)}),totalBlockingTime:Cj({audit:a[`total-blocking-time`],rating:e=>wj(e,200,600)}),cumulativeLayoutShift:Cj({audit:a[`cumulative-layout-shift`],rating:e=>wj(e,.1,.25)}),interactionToNextPaint:Cj({audit:a[`interaction-to-next-paint`],rating:e=>wj(e,200,500)}),speedIndex:Cj({audit:a[`speed-index`]}),serverResponseTime:Cj({audit:Nj(a),rating:e=>wj(e,600,600)})},labInsights:Mj(a),labDataStatus:{provider:`lighthouse`,status:`available`,reason:`Lighthouse ${i.lighthouseVersion??`unknown version`} completed a ${e.strategy} navigation lab run.`},fieldDataStatus:{provider:`crux`,status:`not_configured`,reason:`Field Core Web Vitals were not checked yet.`,checkedUrl:e.url,checkedOrigin:new URL(e.url).origin,formFactor:e.strategy===`desktop`?`DESKTOP`:`PHONE`},caveats:[`Lighthouse is one controlled lab run; its score and timings can vary between runs and are not field Core Web Vitals.`,`Navigation Lighthouse uses Total Blocking Time as a lab responsiveness diagnostic; field INP comes from CrUX when available.`],topActions:[],...e.includeRaw?{raw:i}:{}};return i.runWarnings?.length&&(c.caveats.push(`Lighthouse returned ${i.runWarnings.length} run warning${i.runWarnings.length===1?``:`s`}; inspect the compact lab insights or rerun before relying on the score.`),c.dataStatus=`partial`),s===void 0&&(c.dataStatus=`partial`,c.caveats.push(`Lighthouse returned usable lab metrics without a numeric performance category score.`)),c}function Bj(e){return`perf_${_(`sha256`).update(JSON.stringify({methodology:`performance-v2`,...e})).digest(`hex`).slice(0,20)}`}function Vj(e,t){let n=j().prepare(`SELECT report_json FROM performance_reports WHERE id = ? AND expires_at > ?`).get(e,t);if(n?.report_json)try{let e=JSON.parse(n.report_json);return Kj(e)&&qj({report:e})?e:void 0}catch{return}}function Hj(e,t){let n={...e,raw:void 0};j().prepare(`INSERT OR REPLACE INTO performance_reports
703
703
  (id, url, strategy, report_json, created_at, expires_at)
704
- VALUES (?, ?, ?, ?, ?, ?)`).run(e.id,e.url,e.strategy,JSON.stringify(n),Date.parse(e.generatedAt),t+864e5)}async function wj(e){let t=await Ag(e.url,{js:!1,refresh:!0}),n=t.diagnostics.durationMs,r={schemaVersion:1,methodology:`performance-v2`,dataStatus:`partial`,id:e.id,url:e.url,finalUrl:t.finalUrl,strategy:e.strategy,generatedAt:e.generatedAt,cache:{status:e.cacheStatus,ttlHours:24},source:`fetch-fallback`,grade:`unknown`,headline:`Lighthouse lab data was unavailable; fallback evidence is unscored HTTP transport data.`,metrics:{fallbackFetchDuration:{value:n,displayValue:`${n}ms`,source:`fetch-fallback`}},labInsights:[],labDataStatus:{provider:`lighthouse`,status:`unavailable`,reason:e.lighthouseFailure.reason,failureCode:e.lighthouseFailure.failureCode},fallbackEvidence:{requestedUrl:e.url,finalUrl:t.finalUrl,httpStatus:t.status,blocked:t.diagnostics.blocked,redirectCount:t.diagnostics.redirectChain?.length??0},fieldDataStatus:{provider:`crux`,status:`not_configured`,reason:`Field Core Web Vitals were not checked yet.`,checkedUrl:e.url,checkedOrigin:new URL(e.url).origin,formFactor:e.strategy===`desktop`?`DESKTOP`:`PHONE`},caveats:[`Fallback response duration includes the complete local fetch workflow and is not TTFB, Lighthouse, or Core Web Vitals.`,`Fallback mode does not measure JavaScript execution, LCP, CLS, TBT, or INP and never receives a performance score.`,...t.warnings],topActions:[]};return{...r,topActions:cj(r)}}function Tj(e){let t=e.fieldData;if(!t)return e.headline;let n=`${t.formFactor.toLowerCase()} ${t.scope}-level`;return t.assessment.status===`incomplete`?`CrUX ${n} field data is incomplete (${t.assessment.availableMetrics}/3 Core Web Vitals); ${e.headline}`:`CrUX ${n} Core Web Vitals are ${t.assessment.status}; ${e.headline}`}function Ej(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function Dj(e){if(!Ej(e))return!1;let t=e.cache,n=e.fieldDataStatus,r=e.labDataStatus;return e.schemaVersion===1&&e.methodology===`performance-v2`&&(e.dataStatus===`complete`||e.dataStatus===`partial`)&&typeof e.id==`string`&&typeof e.url==`string`&&(e.strategy===`mobile`||e.strategy===`desktop`)&&typeof e.generatedAt==`string`&&Ej(t)&&typeof t.ttlHours==`number`&&(e.source===`lighthouse`||e.source===`fetch-fallback`)&&typeof e.headline==`string`&&Ej(e.metrics)&&Array.isArray(e.labInsights)&&Ej(r)&&(r.status===`available`||r.status===`unavailable`)&&Ej(n)&&[`not_configured`,`available`,`unavailable_no_coverage`,`request_failed`].includes(String(n.status))&&Array.isArray(e.topActions)&&Array.isArray(e.caveats)}function Oj(e){let{report:t}=e;return!e.includeRaw&&!e.customLighthouse&&!e.customCrux&&t.dataStatus===`complete`&&t.source===`lighthouse`&&t.labDataStatus.status===`available`&&t.fieldDataStatus.status!==`request_failed`&&(t.fieldDataStatus.status!==`available`||t.fieldData?.status===`available`&&t.fieldData.assessment.status!==`incomplete`)&&t.raw===void 0}async function kj(e){let t=e.strategy??`mobile`,n=Mg(e.url),r=e.cruxApiKey??process.env.SEO_CRUX_API_KEY??process.env.CRUX_API_KEY,i=xj({url:n,strategy:t,fieldDataEnabled:!!r}),a=e.now?.()??new Date,o=!!(e.refresh||e.includeRaw||e.lighthouseBin||e.cruxFetch);if(!o){let e=Sj(i,a.getTime());if(e)return{...e,cache:{...e.cache,status:`hit`}}}let s=a.toISOString(),c=o?`bypass`:`miss`,l;try{l=await bj({id:i,url:n,strategy:t,lighthouseBin:e.lighthouseBin,timeoutMs:e.timeoutMs,generatedAt:s,includeRaw:e.includeRaw}),l.cache.status=c}catch(e){l=await wj({id:i,url:n,strategy:t,generatedAt:s,cacheStatus:c,lighthouseFailure:yj(e)})}let u=await uj({url:n,strategy:t,apiKey:r,fetchImpl:e.cruxFetch,timeoutMs:e.cruxTimeoutMs});return l={...l,...u.fieldData?{fieldData:u.fieldData}:{},fieldDataStatus:u.status,caveats:[...new Set([...l.caveats,u.caveat])]},l={...l,dataStatus:l.labDataStatus.status===`unavailable`||u.status.status===`request_failed`||u.fieldData?.assessment.status===`incomplete`?`partial`:l.dataStatus,headline:Tj(l),topActions:cj(l)},Oj({report:l,customCrux:!!e.cruxFetch,customLighthouse:!!e.lighthouseBin,includeRaw:!!e.includeRaw})&&Cj(l,a.getTime()),l}const Aj=new Set(`a.an.and.are.best.for.first.from.how.in.is.last.letter.letters.name.names.of.on.or.start.starts.starting.that.the.to.vs.with`.split(`.`)),jj=new Set([...Aj,`about`,`after`,`all`,`at`,`be`,`by`,`can`,`chart`,`check`,`day`,`days`,`did`,`do`,`does`,`each`,`ever`,`find`,`free`,`get`,`high`,`hour`,`hours`,`into`,`low`,`made`,`make`,`makes`,`many`,`month`,`months`,`much`,`near`,`new`,`page`,`pages`,`per`,`search`,`show`,`site`,`sites`,`than`,`they`,`this`,`time`,`times`,`today`,`tool`,`tools`,`top`,`what`,`when`,`where`,`year`,`years`,`you`,`your`]);function Mj(e){return e.normalize(`NFKC`).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 Nj(e){return e.length>1||/^\d$/u.test(e)||/[^\p{ASCII}]/u.test(e)}function Pj(e){let t=Mj(e).split(` `).filter(e=>Nj(e)&&!Aj.has(e));return[...new Set(t)]}function Fj(e){return/^[a-z]+$/i.test(e)?[`surname`,`surnames`].includes(e)?`name`:[`beginning`,`begins`,`started`].includes(e)?`start`:/(as|is|ss|us)$/.test(e)?e:e.length>4&&e.endsWith(`ies`)?`${e.slice(0,-3)}y`:e.length>4&&e.endsWith(`xes`)?e.slice(0,-2):e.length>4&&e.endsWith(`es`)||e.length>4&&e.endsWith(`s`)?e.slice(0,-1):e:e}function Ij(e){return Mj(e).split(` `).filter(e=>Nj(e)&&!jj.has(e))}function Lj(e){let t=Mj(e);return/\b(vs|versus|compare|comparison)\b/.test(t)?`comparison`:/\balternative|alternatives\b/.test(t)?`alternatives`:/\bwithout (account|login|signing|sign in)\b/.test(t)?`no-login modifier`:/\b(export|download|save|convert)\b/.test(t)?`workflow/action`:/\bmeaning|origin|history\b/.test(t)?`meaning/origin`:/\brare|rarity|popular|popularity\b/.test(t)?`rarity/popularity`:/\bhow many|number of|people with|people have\b/.test(t)?`count/statistic`:/\b(starting with|starts with|start with|beginning with|letter)\b/.test(t)?`list/facet`:/\b(price|pricing|cost|fee)\b/.test(t)?`pricing`:/\breview|reviews|reddit|forum|community\b/.test(t)?`reviews/community`:/\bguide|docs|documentation|api\b/.test(t)?`docs/how-to`:`general`}function Rj(e){let t=Ij(e).map(Fj),n=[];for(let e of[3,2])for(let r=0;r<=t.length-e;r+=1)n.push(t.slice(r,r+e).join(` `));return n.push(...t),[...new Set(n)]}function zj(e){let t=new Map,n=new Map,r=new Map;for(let t of e)r.set(t.query,(r.get(t.query)??0)+t.impressions);for(let[e,i]of r){let r=Rj(e);n.set(e,r);for(let e of r){let n=t.get(e)??{queryCount:0,impressions:0};n.queryCount+=1,n.impressions+=i,t.set(e,n)}}let i=[...r.values()].reduce((e,t)=>e+t,0),a=[...t.entries()].filter(([,e])=>e.queryCount>=2).filter(([e,t])=>e.includes(` `)||t.impressions>=i*.05).sort((e,t)=>t[1].impressions-e[1].impressions||t[0].length-e[0].length),o=new Map;for(let[e,t]of n){let n=a.find(([e])=>t.includes(e));n&&o.set(e,`theme: ${n[0]}`)}return o}function Bj(e){let t=new Map,n=new Map,r=zj(e);for(let i of e){let e=Lj(i.query),a=e===`general`?r.get(i.query)??e:e,o=t.get(a)??{label:a,queryCount:0,clicks:0,impressions:0,examples:[]},s=n.get(a)??new Set;s.add(i.query),n.set(a,s),o.queryCount=s.size,o.clicks+=i.clicks,o.impressions+=i.impressions,o.examples.length<3&&!o.examples.includes(i.query)&&o.examples.push(i.query),t.set(a,o)}return[...t.values()].sort((e,t)=>t.impressions-e.impressions).slice(0,5)}function Vj(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)return t}return n.length-r.length}function Hj(e){try{return[`http:`,`https:`].includes(new URL(e).protocol)}catch{return!1}}function Uj(e,t=0){return Number.isFinite(e)&&e>=t}function Wj(e){return!!e.query.trim()&&Hj(e.page)&&Uj(e.clicks)&&Uj(e.impressions)&&e.clicks<=e.impressions&&Uj(e.position,1)}function Gj(e){return Hj(e.page)&&Uj(e.clicks)&&Uj(e.impressions)&&e.clicks<=e.impressions&&Uj(e.position,1)}function Kj(e){let t=e.reduce((e,t)=>e+t.impressions,0);return t?e.reduce((e,t)=>e+t.position*t.impressions,0)/t:0}function qj(e){let t=new Map;for(let n of e){let e=t.get(n.page);t.set(n.page,{page:n.page,clicks:(e?.clicks??0)+n.clicks,impressions:(e?.impressions??0)+n.impressions,position:e?Kj([e,n]):n.position})}return[...t.values()]}function Jj(e){let t=new Map;for(let n of e){let e=`${n.query}\u0000${n.page}`,r=t.get(e);t.set(e,{query:n.query,page:n.page,clicks:(r?.clicks??0)+n.clicks,impressions:(r?.impressions??0)+n.impressions,position:r?Kj([r,n]):n.position})}return[...t.values()]}function Yj(e,t){let n=Pj(e).map(Fj);if(!n.length)return{coverage:0,missingTerms:[]};let r=new Set(Pj(Mj(t??``)).map(Fj));return{coverage:n.filter(e=>r.has(e)).length/n.length,missingTerms:n.filter(e=>!r.has(e))}}function Xj(e){let t=Yj(e.query,e.title),n=Yj(e.query,e.h1),r=Yj(e.query,e.body),i=r.coverage<.75?`body-term-review`:t.coverage<.75||n.coverage<.75?`serp-framing-review`:`covered`;return{method:`literal-query-term-presence-v1`,heuristic:!0,query:e.query,classification:i,titleCoverage:t.coverage,h1Coverage:n.coverage,bodyCoverage:r.coverage,missingTerms:r.missingTerms.slice(0,8)}}function Zj(e,t){let n=kk(e),r=t.shape.variableSegments.flatMap(e=>{let t=n[e.index];return t?Ij(t):[]});return[...new Set(r.map(Fj))]}function Qj(e,t){let n=0,r=0,i=0,a=0,o=[];for(let s of e){let e=Zj(s.page,t);if(!e.length)continue;let c=new Set(Pj(s.query).map(Fj));n+=1,i+=s.impressions,e.some(e=>c.has(e))?(r+=1,a+=s.impressions):o.push({url:s.page,query:s.query,pathTerms:e.slice(0,6),impressions:s.impressions})}return{method:`any-path-variable-term-v1`,heuristic:!0,checkedQueries:n,matchedQueries:r,impressionShare:i?a/i:0,weakExamples:o.sort((e,t)=>t.impressions-e.impressions||Vj(e.query,t.query)||Vj(e.url,t.url)).slice(0,5)}}function $j(e){let t=e.pageRows.reduce((e,t)=>e+t.clicks,0),n=e.pageRows.reduce((e,t)=>e+t.impressions,0),r=new Map;for(let t of e.queryRows){let e=r.get(t.query);r.set(t.query,{query:t.query,clicks:(e?.clicks??0)+t.clicks,impressions:(e?.impressions??0)+t.impressions,position:e?Kj([e,t]):t.position})}return{clicks:t,impressions:n,ctr:n?t/n:0,position:Kj(e.pageRows),impressionsPerUrl:e.cluster.urlCount?n/e.cluster.urlCount:0,clicksPerUrl:e.cluster.urlCount?t/e.cluster.urlCount:0,retainedQueryImpressions:e.queryRows.reduce((e,t)=>e+t.impressions,0),queryCount:r.size,pageCountWithGsc:e.pageRows.length,zeroClickImpressions:e.pageRows.filter(e=>e.clicks===0).reduce((e,t)=>e+t.impressions,0),entityFit:Qj(e.queryRows,e.cluster),queryPatterns:Bj(e.queryRows),topQueries:[...r.values()].sort((e,t)=>t.impressions-e.impressions||t.clicks-e.clicks||Vj(e.query,t.query)).slice(0,5)}}function eM(e){if(!e.length)return;let t=[...e].sort((e,t)=>e-t),n=Math.floor(t.length/2);return t.length%2?t[n]:((t[n-1]??0)+(t[n]??0))/2}function tM(e){let t=new Map;for(let n of e){let e=Mj(n??``);e&&t.set(e,(t.get(e)??0)+1)}return[...t.values()].reduce((e,t)=>e+(t>1?t:0),0)}function nM(e){return e===`PASS`?`indexed`:e===`NEUTRAL`?`excluded`:e===`FAIL`?`invalid`:`unknown`}function rM(e,t,n){if(!n)return[];let r=new Map(t.map(e=>[e.page,e])),i=[...e].sort((e,t)=>{let n=r.get(e),i=r.get(t);return(i?.clicks??0)-(n?.clicks??0)||(i?.impressions??0)-(n?.impressions??0)||Vj(e,t)}),a=[0,i.length-1,Math.floor((i.length-1)/2)],o=[];for(let e of a){let t=i[e];if(t&&!o.includes(t)&&o.push(t),o.length>=n)return o}for(let e of i)if(o.includes(e)||o.push(e),o.length>=n)break;return o}function iM(e,t){let n=e.filter(e=>e.technicalStatus!==`fetch-error`).length,r=e.filter(e=>[`ok`,`redirected`].includes(e.technicalStatus)),i=r.map(e=>e.wordCount).filter(e=>e!==void 0),a=eM(i);return{requested:t,attempted:e.length,succeeded:n,failed:e.length-n,usable:r.length,samples:e,wordCount:i.length&&a!==void 0?{min:Math.min(...i),median:a,max:Math.max(...i)}:void 0,medianWordCount:a,weakQueryCoverage:r.filter(e=>e.queryCoverage?.classification===`body-term-review`).length,duplicateTitles:tM(r.map(e=>e.title)),duplicateMetaDescriptions:tM(r.map(e=>e.metaDescription)),blockedOrFailed:e.filter(e=>![`ok`,`redirected`].includes(e.technicalStatus)).length}}function aM(e,t){let n=e.filter(e=>!e.warning),r=n.filter(e=>e.indexStatus===`indexed`).length,i=n.filter(e=>[`excluded`,`invalid`].includes(e.indexStatus)).length;return{requested:t,attempted:e.length,succeeded:n.length,failed:e.length-n.length,samples:e,indexed:r,notIndexed:i,unknown:n.length-r-i,warnings:e.length-n.length}}function oM(e){let t=e.inspection.indexed+e.inspection.notIndexed;return e.inspection.notIndexed&&(t===1||e.inspection.notIndexed/t>=.5)?{verdict:`index-risk`,confidence:t>=2?`medium`:`low`}:e.crawl.blockedOrFailed&&(e.crawl.attempted===1||e.crawl.blockedOrFailed/e.crawl.attempted>=.5)?{verdict:`crawl-risk`,confidence:e.crawl.attempted>=2?`medium`:`low`}:e.crawl.usable>=3&&(e.crawl.weakQueryCoverage/e.crawl.usable>=.5||e.crawl.duplicateTitles>=2||e.crawl.duplicateMetaDescriptions>=2)?{verdict:`content-review`,confidence:`medium`}:e.metrics.impressions>0&&(e.metrics.position>8||e.metrics.position<=10&&e.metrics.ctr<.01||e.metrics.entityFit.checkedQueries>=10&&e.metrics.entityFit.impressionShare<.35)?{verdict:`opportunity`,confidence:`low`}:t>=2&&e.inspection.indexed===t&&e.crawl.usable>=2&&e.crawl.blockedOrFailed===0?{verdict:`healthy`,confidence:`medium`}:e.metrics.impressions>0?{verdict:`inconclusive`,confidence:`low`}:{verdict:`no-data`,confidence:`low`}}function sM(e){let t=[];if(e.metrics.impressions&&t.push(`${Math.round(e.metrics.impressions).toLocaleString(`en-GB`)} impressions from ${Z(e.metrics.pageCountWithGsc,`retained GSC page row`)}`),e.inspection.attempted){let n=Math.max(0,e.population.discoveredUrls-e.inspection.attempted);t.push(`${e.inspection.indexed}/${e.inspection.attempted} sampled URLs returned exact PASS verdicts; ${n} discovered URLs were not inspected`)}if(e.crawl.attempted){let n=Math.max(0,e.population.discoveredUrls-e.crawl.attempted);t.push(`${e.crawl.usable}/${e.crawl.attempted} sampled URLs returned usable 2xx crawl evidence; ${n} discovered URLs were not sampled`)}return e.crawl.weakQueryCoverage&&t.push(`${Z(e.crawl.weakQueryCoverage,`usable sample`)} need literal query-term review`),e.crawl.duplicateTitles&&t.push(`${Z(e.crawl.duplicateTitles,`sampled URL`)} share duplicate titles`),t}function cM(e){return e.verdict===`index-risk`?`${Z(e.inspection.notIndexed,`inspected URL`)} returned an exact excluded or invalid verdict as of Google's last indexed crawl. Review their robots/noindex, canonical, fetch, and sitemap evidence; do not generalise the result to ${Z(e.population.untestedUrls,`untested URL`)}.`:e.verdict===`crawl-risk`?`${Z(e.crawl.blockedOrFailed,`sampled URL`)} returned objective HTTP, robots, noindex, canonical, or fetch failures. Fix those sampled URLs first, then rerun before assessing content.`:e.verdict===`content-review`?`Review the sampled pages for distinct utility and intent fit. Literal term coverage and repeated metadata are heuristics, not proof that pages need more words or violate Google's policies.`:e.verdict===`opportunity`?`Retained GSC page rows show search visibility but weak average position, CTR, or entity-fit heuristics. Inspect the top queries and sampled pages before changing this template.`:e.verdict===`healthy`?`No material issue was found in the bounded crawl and URL Inspection samples. Keep monitoring; this is sample evidence, not a guarantee for every URL.`:e.verdict===`inconclusive`?`Retained GSC rows show search visibility, but technical sampling is not strong enough for a health verdict. Add bounded crawl and URL Inspection samples.`:`No retained page-level Search Analytics evidence was found for this discovered template. That is not proof of zero demand or low quality; inspect representative URLs and canonical attribution.`}const lM=`page-demand-stratified-url-v1`;function uM(e){let t=e.queryPageRows.filter(e=>!Wj(e)).length,n=e.queryPageRows.filter(Wj),r=n.filter(e=>Rv(e.query)).length,i=n.filter(e=>!Rv(e.query)).map(t=>({row:t,isBrand:M_({query:t.query,siteUrl:e.site,brandTerms:e.brandTerms,includeBrand:e.includeBrand})}));return{rows:Jj(i.filter(e=>!e.isBrand).map(e=>e.row)),invalid:t,lowActionability:r,brand:i.filter(e=>e.isBrand).length}}function dM(e,t){let n=new Map;for(let r of e){let e=Fk(r.page,t);n.set(e,[...n.get(e)??[],r])}return n}function fM(e){let t=Lk(e.allUrls,{minUrls:e.dataset.minimumTemplateUrls,minShare:e.dataset.minimumTemplateShare,limit:Math.max(1,e.allUrls.length),sampleSize:0}),n=new Map;for(let r of e.allUrls){let e=Fk(r,t);n.set(e,[...n.get(e)??[],r])}let r=dM(e.pageRows,t),i=dM(e.queryRows,t),a=t.map(e=>{let t=r.get(e.signature)??[],a=i.get(e.signature)??[];return{cluster:e,urls:n.get(e.signature)??[],pageRows:t,metrics:$j({pageRows:t,queryRows:a,cluster:e})}}).filter(t=>t.metrics.impressions>=e.dataset.minimumTemplateImpressions).sort((e,t)=>t.metrics.impressions-e.metrics.impressions||t.metrics.clicks-e.metrics.clicks||t.cluster.urlCount-e.cluster.urlCount||Vj(e.cluster.signature,t.cluster.signature)),o=new Map((e.dataset.crawlSamples??[]).map(e=>[e.url,e])),s=new Map((e.dataset.inspectionSamples??[]).map(e=>[e.url,e]));return{templates:a.slice(0,e.dataset.templateLimit).map(t=>{let n=Math.max(e.dataset.crawlSamplesPerTemplate,e.dataset.inspectionSamplesPerTemplate),r=rM(t.urls,t.pageRows,n),i=iM(r.slice(0,e.dataset.crawlSamplesPerTemplate).map(e=>o.get(e)).filter(e=>!!e),e.dataset.crawlSamplesPerTemplate),a=aM(r.slice(0,e.dataset.inspectionSamplesPerTemplate).map(e=>s.get(e)).filter(e=>!!e),e.dataset.inspectionSamplesPerTemplate),c={...t.cluster,sampleUrls:r,population:{discoveredUrls:t.cluster.urlCount,gscVisibleUrls:t.pageRows.length,untestedUrls:Math.max(0,t.cluster.urlCount-Math.max(i.attempted,a.attempted)),sampleSelection:lM},metrics:t.metrics,crawl:i,inspection:a,evidence:[],verdict:`inconclusive`,confidence:`low`,recommendation:``},l={...c,...oM(c)};return{...l,evidence:sM(l),recommendation:cM(l)}}),eligibleTemplates:a.length}}function pM(e){let t=uM(e),n=e.pageRows.filter(e=>!Gj(e)).length,r=qj(e.pageRows.filter(Gj)),i=[...new Set(e.sitemapUrls??[])].filter(Hj).sort(Vj),a=[...new Set([...i,...r.map(e=>e.page),...t.rows.map(e=>e.page)])].sort(Vj),{templates:o,eligibleTemplates:s}=fM({dataset:e,allUrls:a,pageRows:r,queryRows:t.rows}),c=[...new Set(e.warnings??[])],l=e.pageRowsFetched>=e.maxRowsPerRequest,u=e.queryPageRowsFetched>=e.maxRowsPerRequest,d=o.reduce((e,t)=>e+t.crawl.attempted,0),f=o.reduce((e,t)=>e+t.crawl.usable,0),p=o.reduce((e,t)=>e+t.inspection.attempted,0),m=o.reduce((e,t)=>e+t.inspection.succeeded,0),h=e.queryPageRows.length>0||e.pageRows.length>0||i.length>0,g=l||u||t.invalid>0||n>0||c.length>0||d>f||p>m,_=h?a.length?g?`partial`:`complete`:`filtered`:`empty`,v=e.brandTerms?.length?e.brandTerms:A_({siteUrl:e.site});return{schemaVersion:1,methodology:`pseo-audit-v2`,site:e.site,generatedAt:e.generatedAt,rangeDays:e.days,range:e.range,dataStatus:_,source:{searchAnalytics:{pageRows:e.pageRowsFetched,queryPageRows:e.queryPageRowsFetched,maxRowsPerRequest:e.maxRowsPerRequest,pageRowsPossiblyTruncated:l,queryPageRowsPossiblyTruncated:u,dimensions:{page:[`page`],queryPage:[`query`,`page`]},searchType:`web`,dataState:`final`,aggregation:`auto`},sitemaps:{requested:e.sitemapsRequested,discoveredUrls:i.length,maxUrlsPerSitemap:e.maxUrlsPerSitemap}},selection:{inputQueryPageRows:e.queryPageRows.length,invalidQueryPageRows:t.invalid,lowActionabilityRows:t.lowActionability,brandRows:t.brand,retainedQueryPageRows:t.rows.length,inputPageRows:e.pageRows.length,invalidPageRows:n,retainedPageRows:r.length,discoveredUrls:a.length,eligibleTemplates:s,returnedTemplates:o.length,templateLimit:e.templateLimit,minimumTemplateUrls:e.minimumTemplateUrls,minimumTemplateShare:e.minimumTemplateShare,minimumTemplateImpressions:e.minimumTemplateImpressions,templateOrder:`page-impressions-clicks-url-count-signature-v1`},summary:{sitemapUrls:i.length,gscPages:r.length,templates:o.length,clicks:r.reduce((e,t)=>e+t.clicks,0),impressions:r.reduce((e,t)=>e+t.impressions,0),crawlAttempts:d,crawledUrls:f,crawlFailures:d-f,inspectionAttempts:p,inspectedUrls:m,inspectionFailures:p-m},caveats:[...e.caveats??[],`Search Analytics range: ${e.range.startDate} through ${e.range.endDate}, using finalized web data.`,`Clicks, impressions, CTR, and position use retained page-dimension rows; Search Console does not guarantee every row or complete totals.`,`Query patterns and examples use retained query/page rows, which omit anonymized and lower-ranked data.`,`Brand queries: ${e.includeBrand?`included`:`excluded using ${v.join(`, `)||`no terms`}`}.`,`Template detection requires at least ${e.minimumTemplateUrls} URLs and ${(e.minimumTemplateShare*100).toFixed(2)}% of discovered URLs.`,`Samples use ${lM}; observations apply to sampled URLs, not every URL in a template.`,`URL Inspection reports Google's indexed snapshot for a specific URL; an exact PASS verdict does not guarantee current search appearance.`,`Word counts are descriptive only. This report has no preferred or minimum word-count rule.`,`Literal term coverage and path entity fit are review heuristics, not Google ranking factors or scaled-content policy verdicts.`],templates:o,warnings:c}}function mM(e){return{crawl:e.templates.flatMap(e=>e.sampleUrls.slice(0,e.crawl.requested)),inspection:e.templates.flatMap(e=>e.sampleUrls.slice(0,e.inspection.requested))}}const hM=5e4,gM={searchAnalytics:e_,fetchPage:Ag,inspectUrl:qg,fetchSitemapUrls:Bx,now:()=>new Date};function _M(e){let t=e.value??e.fallback;if(!Number.isFinite(t)||t<e.minimum||t>e.maximum)throw new A(`INVALID_INPUT`,`${e.label} must be between ${e.minimum} and ${e.maximum}.`);return t}function vM(e){let t=Y({value:e.days,fallback:28,minimum:1,maximum:548,label:`days`}),n=Y({value:e.templateLimit,fallback:25,minimum:1,maximum:100,label:`templateLimit`}),r=Y({value:e.minimumTemplateUrls,fallback:3,minimum:2,maximum:100,label:`minimumTemplateUrls`}),i=Y({value:e.crawlSamples,fallback:0,minimum:0,maximum:10,label:`crawlSamples`}),a=Y({value:e.inspectSamples,fallback:0,minimum:0,maximum:10,label:`inspectSamples`}),o=Y({value:e.maxSitemapUrls,fallback:5e4,minimum:1,maximum:1e5,label:`maxSitemapUrls`}),s=_M({value:e.minimumTemplateImpressions,fallback:0,minimum:0,maximum:1e9,label:`minimumTemplateImpressions`}),c=_M({value:e.minimumTemplateShare,fallback:0,minimum:0,maximum:1,label:`minimumTemplateShare`});if((e.sitemaps?.length??0)>20)throw new A(`INVALID_INPUT`,`sitemaps must contain at most 20 URLs.`);for(let t of e.sitemaps??[])try{if(![`http:`,`https:`].includes(new URL(t).protocol))throw Error()}catch{throw new A(`INVALID_INPUT`,`Invalid sitemap URL: ${t}`)}if(e.brandTerms&&(e.brandTerms.length>20||e.brandTerms.some(e=>!e.trim()||e.length>200)))throw new A(`INVALID_INPUT`,`brandTerms must contain at most 20 non-empty terms of 200 characters or fewer.`);return{...e,days:t,templateLimit:n,minimumTemplateUrls:r,minimumTemplateShare:c,minimumTemplateImpressions:s,crawlSamples:i,inspectSamples:a,maxSitemapUrls:o}}function yM(e,t){let n=new Map;for(let r of e){if(Rv(r.query)||M_({query:r.query,siteUrl:t.site,brandTerms:t.brandTerms,includeBrand:t.includeBrand}))continue;let e=r.impressions+r.clicks*10,i=n.get(r.page);(!i||e>i.score)&&n.set(r.page,{query:r.query,score:e})}return new Map([...n].map(([e,t])=>[e,t.query]))}function bM(e,t){if(e)try{let n=new URL(e,t);return n.hash=``,n.toString()}catch{return}}function xM(e){if(e.fetched.diagnostics.blocked||e.fetched.robotsTxt?.allowed===!1)return`blocked`;if(e.fetched.status<200||e.fetched.status>=300)return`http-error`;if(rm(e).has(`noindex`))return`noindex`;let t=bM(e.canonical,e.fetched.finalUrl),n=bM(e.fetched.finalUrl,e.fetched.finalUrl);return t&&n&&t!==n?`canonical-mismatch`:e.requestedUrl!==e.fetched.finalUrl||(e.fetched.diagnostics.redirectChain?.length??0)>0?`redirected`:`ok`}async function SM(e){try{let t=await e.fetch(e.url,{refresh:e.refresh,js:e.js??`auto`,rate:e.rate}),n=await Th(t),r=n.headings.find(e=>e.level===1)?.text,i=xM({requestedUrl:e.url,fetched:t,canonical:n.canonical,metaRobots:n.metaRobots,xRobotsTag:n.xRobotsTag});return{url:e.url,finalUrl:n.finalUrl,status:t.status,title:n.title,h1:r,metaDescription:n.metaDescription,metaRobots:n.metaRobots,xRobotsTag:n.xRobotsTag,canonical:bM(n.canonical,n.finalUrl),wordCount:n.wordCount,technicalStatus:i,queryCoverage:e.topQuery&&[`ok`,`redirected`].includes(i)?Xj({query:e.topQuery,title:n.title,h1:r,body:n.contentText}):void 0,fetchDiagnostics:t.diagnostics,warnings:[...t.warnings,...n.warnings]}}catch(t){let n=t instanceof Error?t.message:String(t);return{url:e.url,technicalStatus:`fetch-error`,warnings:[n],warning:n}}}function CM(e,t){let n=t.inspectionResult?.indexStatusResult;return{url:e,indexStatus:nM(n?.verdict),verdict:n?.verdict,coverageState:n?.coverageState,indexingState:n?.indexingState,robotsTxtState:n?.robotsTxtState,pageFetchState:n?.pageFetchState,lastCrawlTime:n?.lastCrawlTime,userCanonical:n?.userCanonical,googleCanonical:n?.googleCanonical}}async function wM(e){try{return CM(e.url,await e.inspect({siteUrl:e.site,inspectionUrl:e.url}))}catch(t){return{url:e.url,indexStatus:`unknown`,warning:t instanceof Error?t.message:String(t)}}}async function TM(e,t=gM){let n=vM(e),r=t.now(),i=r.toISOString(),a=G_(n.days,r),o=[];e.progress?.(`Fetching sitemap URLs`);let s=(await Promise.all((e.sitemaps??[]).map(e=>t.fetchSitemapUrls({sitemapUrl:e,limit:n.maxSitemapUrls})))).flatMap(e=>(o.push(...e.warnings),e.urls)),c={...a,type:`web`,dataState:`final`,aggregationType:`auto`,maxRows:hM};e.progress?.(`Fetching ${n.days} days of GSC page and query/page rows`);let[l,u]=await Promise.all([t.searchAnalytics(e.site,{...c,dimensions:[`page`]},{refresh:e.refresh}),t.searchAnalytics(e.site,{...c,dimensions:[`query`,`page`]},{refresh:e.refresh})]),d=l.rows.map(e=>({page:e.keys[0]??``,clicks:e.clicks,impressions:e.impressions,position:e.position})),f=u.rows.map(e=>({query:e.keys[0]??``,page:e.keys[1]??``,clicks:e.clicks,impressions:e.impressions,position:e.position})),p=t=>pM({site:e.site,generatedAt:i,range:a,days:n.days,queryPageRows:f,pageRows:d,sitemapUrls:s,crawlSamples:t?.crawl,inspectionSamples:t?.inspection,templateLimit:n.templateLimit,minimumTemplateUrls:n.minimumTemplateUrls,minimumTemplateShare:n.minimumTemplateShare,minimumTemplateImpressions:n.minimumTemplateImpressions,crawlSamplesPerTemplate:n.crawlSamples,inspectionSamplesPerTemplate:n.inspectSamples,maxRowsPerRequest:hM,pageRowsFetched:l.rowsFetched,queryPageRowsFetched:u.rowsFetched,sitemapsRequested:e.sitemaps?.length??0,maxUrlsPerSitemap:n.maxSitemapUrls,brandTerms:e.brandTerms,includeBrand:e.includeBrand,warnings:o,caveats:[`Data freshness: ${e.refresh?`fresh fetch requested; caches bypassed where supported`:`local cache allowed; use --refresh to bypass cached provider and HTTP data`}.`,e.sitemaps?.length?`Sitemaps: ${Z(e.sitemaps.length,`sitemap URL`)} requested.`:`Sitemaps: none provided; template discovery used retained GSC page rows.`]}),m=mM(p()),h=yM(f,e),g=[];for(let[n,r]of m.crawl.entries())e.progress?.(`Crawling pSEO sample ${n+1}/${m.crawl.length}`),g.push(await SM({url:r,topQuery:h.get(r),refresh:e.refresh,js:e.js,rate:e.rate,fetch:t.fetchPage}));let _=[];for(let[n,r]of m.inspection.entries())e.progress?.(`Inspecting pSEO sample ${n+1}/${m.inspection.length}`),_.push(await wM({site:e.site,url:r,inspect:t.inspectUrl}));return e.progress?.(`Scoring pSEO template evidence`),p({crawl:g,inspection:_})}function EM(e){return String(e??`-`).replaceAll(`
705
- `,` `).replaceAll(`|`,`\\|`)}function DM(e,t=120){let n=EM(e).trim();return n.length<=t?n:`${n.slice(0,t-1)}...`}function OM(e){return e.flatMap(e=>{let t=e.columns.map(e=>EM(e.label)),n=e.columns.map(e=>e.type===`number`?`---:`:`---`);return[`## ${e.title}`,``,`| ${t.join(` | `)} |`,`| ${n.join(` | `)} |`,...e.rows.map(t=>[``,...e.columns.map(e=>DM(t[e.key])),``].join(` | `)),``]})}function kM(e){let t=e.inspection.indexed+e.inspection.notIndexed+e.inspection.unknown+e.inspection.warnings;return t?`${e.inspection.indexed}/${t} indexed`:`not checked`}function AM(e){return e.crawl.attempted?`${e.crawl.usable}/${e.crawl.attempted} usable`:`not crawled`}function jM(e){let t=e.templates.length?[{id:`template_impressions`,title:`Template Impressions`,type:`bar`,tableId:`pseo_templates`,xKey:`rank`,yKey:`impressions`},{id:`template_clicks`,title:`Template Clicks`,type:`bar`,tableId:`pseo_templates`,xKey:`rank`,yKey:`clicks`}]:[];return{tables:[{id:`pseo_summary`,title:`pSEO Summary`,columns:[{key:`metric`,label:`Metric`},{key:`value`,label:`Value`,type:`number`}],rows:[{metric:`templates`,value:e.summary.templates},{metric:`gsc_pages`,value:e.summary.gscPages},{metric:`sitemap_urls`,value:e.summary.sitemapUrls},{metric:`clicks`,value:e.summary.clicks},{metric:`impressions`,value:e.summary.impressions},{metric:`crawled_urls`,value:e.summary.crawledUrls},{metric:`crawl_attempts`,value:e.summary.crawlAttempts},{metric:`inspected_urls`,value:e.summary.inspectedUrls},{metric:`inspection_attempts`,value:e.summary.inspectionAttempts}]},{id:`pseo_templates`,title:`pSEO Templates`,columns:[{key:`rank`,label:`Rank`,type:`number`},{key:`template`,label:`Template`},{key:`verdict`,label:`Verdict`},{key:`confidence`,label:`Confidence`},{key:`urls`,label:`URLs`,type:`number`},{key:`clicks`,label:`Clicks`,type:`number`},{key:`impressions`,label:`Impressions`,type:`number`},{key:`ctr`,label:`CTR`,type:`number`},{key:`position`,label:`Position`,type:`number`},{key:`index`,label:`Index`},{key:`crawl`,label:`Crawl`},{key:`recommendation`,label:`Recommendation`}],rows:e.templates.map((e,t)=>({rank:t+1,template:e.signature,verdict:e.verdict,confidence:e.confidence,urls:e.urlCount,clicks:e.metrics.clicks,impressions:e.metrics.impressions,ctr:Number(e.metrics.ctr.toFixed(3)),position:Number(e.metrics.position.toFixed(1)),index:kM(e),crawl:AM(e),recommendation:e.recommendation}))}],charts:t}}function MM(e){let t=jM(e),n=[`# pSEO audit: ${e.site}`,``,`Generated: ${e.generatedAt}`,`Window: ${e.rangeDays} days`,``,`${Z(e.summary.templates,`template`)} audited from ${Z(e.summary.gscPages,`GSC page`)}.`,``];return e.caveats.length&&(n.push(`## Report Caveats`,``),n.push(...e.caveats.map(e=>`- ${e}`),``)),n.push(...OM(t.tables)),e.warnings.length&&(n.push(`## Warnings`,``),n.push(...e.warnings.map(e=>`- ${e}`))),n.join(`
706
- `).trim()}const NM=new Set([`and`,`are`,`best`,`can`,`for`,`from`,`how`,`near`,`the`,`to`,`vs`,`what`,`when`,`where`,`with`,`your`]);function PM(e,t){return e<t?-1:+(e>t)}function FM(e){return B_(e).filter(e=>!NM.has(e))}function IM(e){let t=new Map;for(let n of e){let e=t.get(n.query)??{query:n.query,impressions:0,clicks:0,weightedPosition:0,tokens:new Set,pages:new Map};e.impressions+=n.impressions,e.clicks+=n.clicks,e.weightedPosition+=n.position*n.impressions;for(let t of n.tokens)e.tokens.add(t);let r=e.pages.get(n.page)??{url:n.page,impressions:0,clicks:0};r.impressions+=n.impressions,r.clicks+=n.clicks,r.url&&e.pages.set(r.url,r),t.set(n.query,e)}return[...t.values()].map(e=>({query:e.query,impressions:e.impressions,clicks:e.clicks,position:e.impressions?e.weightedPosition/e.impressions:0,tokens:[...e.tokens].sort(),pages:[...e.pages.values()].sort((e,t)=>t.impressions-e.impressions||PM(e.url,t.url))})).sort((e,t)=>t.impressions-e.impressions||PM(e.query,t.query))}function LM(e){if(e.length<=1)return e.map(e=>[e]);let t=e.map(e=>({...e,tokens:FM(e.query)})),n=new Map;for(let[e,r]of t.entries())for(let t of r.tokens){let r=n.get(t)??[];r.push(e),n.set(t,r)}let r=Math.max(20,Math.min(250,Math.floor(e.length*.08))),i=new Set,a=[],o=t.map((e,t)=>({index:t,impressions:e.impressions,query:e.query})).sort((e,t)=>t.impressions-e.impressions||PM(e.query,t.query));for(let e of o){if(i.has(e.index))continue;let o=t[e.index];if(!o)continue;let s=new Set;for(let t of o.tokens){let a=n.get(t);if(!(!a||a.length>r))for(let t of a)t!==e.index&&!i.has(t)&&s.add(t)}let c=[o];i.add(e.index);let l=[...s].sort((e,n)=>{let r=t[e],i=t[n];return!r||!i?e-n:i.impressions-r.impressions||PM(r.query,i.query)});for(let e of l){let n=t[e];n&&V_(o.tokens,n.tokens)>=.5&&(c.push(n),i.add(e))}a.push(c)}return a}function RM(e){return e.includes(`"`)?`'${e}'`:`"${e}"`}function zM(e,t){let n=new Set(B_(e)),r=t?B_(t):[];return r.length&&r.every(e=>n.has(e))?`navigational`:n.has(`buy`)||n.has(`price`)||n.has(`near`)&&n.has(`me`)?`transactional`:[`vs`,`review`,`best`,`top`,`compare`].some(e=>n.has(e))?`commercial`:[`how`,`what`,`why`,`guide`].some(e=>n.has(e))?`informational`:`mixed`}function BM(e){let t=e.queries.reduce((e,t)=>({impressions:e.impressions+t.impressions,clicks:e.clicks+t.clicks,weightedPosition:e.weightedPosition+t.position*t.impressions}),{impressions:0,clicks:0,weightedPosition:0});return{impressions:t.impressions,clicks:t.clicks,averagePosition:t.impressions?t.weightedPosition/t.impressions:0,ctr:t.impressions?t.clicks/t.impressions:0}}function VM(e){return e.queries<=1||e.impressions<50?0:e.position>10?Number((e.impressions*.03).toFixed(2)):e.estimatedClickLift??0}function HM(e){let t=RM(e.label);if(e.template&&e.template.urlCount>=3&&e.template.share>=.6){let n=e.topPage&&e.template.urlCount>1?` Start with ${e.topPage.url}.`:``,r=e.template.urlCount>1?`This is template-level demand, not a one-off page edit. Tighten the shared title/H1/intro/schema/internal-link rules so each page makes the entity and query angle clear. If ${t} is broader than one entity, route it to a better hub/list page instead of letting several template URLs compete.`:`Improve the shared title, H1, intro, schema, and internal-link pattern, then spot-check the highest-impression URL.`;return`${t} maps mostly to ${e.template.signature} (${e.template.urlCount} URLs). ${r}${n}`}return e.queries===1?e.topPage?`This is a single-query cluster. Inspect ${e.topPage.url} before making a template or hub decision.`:`This is a single-query cluster. Use it as supporting detail, not as a content hub or template decision by itself.`:e.impressions>=500&&e.clicks===0?e.topPage?`This ${t} cluster has search demand but no clicks. Check ${e.topPage.url}; if it is the right target, make the exact intent obvious in the title, H1, intro, and internal links.`:`This ${t} cluster has search demand but no clicks. Check whether the ranking page actually answers the cluster intent, then improve the section/title or create a stronger dedicated page if intent is distinct.`:e.position>10?e.topPage?`This ${t} cluster is mostly outside page one. Strengthen ${e.topPage.url} around this exact intent and add internal links from related pages before treating it as a CTR problem.`:`This ${t} cluster is mostly outside page one. Tighten the page section around the shared intent and add internal links from related pages before treating this as a CTR problem.`:e.clicks/Math.max(1,e.impressions)<e.expectedCtr?`This ${t} cluster ranks below its CTR benchmark. Review SERP framing around the dominant intent (${e.intent}) before expanding content.`:`This ${t} cluster already gets clicks. Use it to refine page structure, FAQs, and internal anchors rather than creating duplicate pages.`}function UM(e){return`${e.queries} queries, ${e.impressions.toFixed(0)} impressions, ${e.clicks.toFixed(0)} clicks, average position ${e.position.toFixed(1)}.`}function WM(e){return[...e].sort((e,t)=>t.impressions-e.impressions||PM(e.query,t.query))[0]?.query??``}function GM(e){let t=new Map;for(let n of e)for(let e of n.pages){let n=t.get(e.url)??{url:e.url,impressions:0,clicks:0};n.impressions+=e.impressions,n.clicks+=e.clicks,t.set(e.url,n)}return[...t.values()].sort((e,t)=>t.impressions-e.impressions||PM(e.url,t.url))}function KM(e){let[t]=Lk(e.map(e=>e.url),{minUrls:3,minShare:.6,limit:1});return t?{signature:t.signature,urlCount:t.urlCount,share:t.share,sampleUrls:t.sampleUrls}:void 0}function qM(e){return{expectedCtr:e.ctr,source:e.source,peerRows:e.rows,peerImpressions:e.impressions,qualifiedPeerImpressions:e.qualifiedImpressions,urlSamples:e.urlSamples,positiveUrlSamples:e.positiveUrlSamples}}function JM(e){let t=Number.isFinite(e.value)?e.value??0:e.fallback;return Math.min(e.maximum??1/0,Math.max(1,Math.floor(t)))}function YM(e){let t=JM({value:e.minImpressions,fallback:25}),n=JM({value:e.limit,fallback:25,maximum:100}),r=e.rows.filter(e=>e.position>=1&&e.position<=10&&e.impressions>0&&!!e.pages[0]?.url).map(e=>({keys:[e.query,e.pages[0]?.url??``],clicks:e.clicks,impressions:e.impressions,ctr:e.clicks/e.impressions,position:e.position})),i=new Map(r.map(e=>[e.keys[0]??``,e])),a=Ev(r),o=[],s=e.rows.filter(e=>e.impressions>=t);for(let t of LM(s)){let n=WM(t),r=new Set(t.map(t=>zM(t.query,e.brand))),[s]=r,c=r.size===1&&s?s:`mixed`,l=BM({queries:t}),u=GM(t),d=u.slice(0,5),f=t.map(e=>i.get(e.query)).filter(e=>!!e),p=a.forAggregate({keys:[n,d[0]?.url??``],clicks:l.clicks,impressions:l.impressions,ctr:l.ctr,position:l.averagePosition},f),m=l.averagePosition<=10?Number((Math.max(0,p.ctr-l.ctr)*l.impressions).toFixed(2)):void 0,h=KM(u);o.push({label:n,intent:c,queries:t.map(e=>({query:e.query,impressions:e.impressions,clicks:e.clicks,position:e.position})).sort((e,t)=>t.impressions-e.impressions||PM(e.query,t.query)),topPages:d,template:h,totals:l,benchmark:qM(p),...m===void 0?{}:{estimatedClickLift:m},opportunityScore:VM({queries:t.length,impressions:l.impressions,position:l.averagePosition,estimatedClickLift:m}),summary:UM({label:n,queries:t.length,impressions:l.impressions,clicks:l.clicks,position:l.averagePosition}),recommendation:HM({label:n,intent:c,queries:t.length,impressions:l.impressions,clicks:l.clicks,position:l.averagePosition,expectedCtr:p.ctr,topPage:d[0],template:h})})}let c=o.filter(e=>e.queries.length>=2||(e.totals?.impressions??0)>=t*3).sort((e,t)=>(t.opportunityScore??0)-(e.opportunityScore??0)||(t.totals?.impressions??0)-(e.totals?.impressions??0)||t.queries.length-e.queries.length||PM(e.label,t.label)).slice(0,n);return{clusters:c,totals:c.reduce((e,t)=>({queries:e.queries+t.queries.length,impressions:e.impressions+(t.totals?.impressions??0),clicks:e.clicks+(t.totals?.clicks??0)}),{queries:0,impressions:0,clicks:0}),highOpportunityClusters:c.filter(e=>{let t=e.totals;return!!(t&&(e.opportunityScore??0)>0&&e.queries.length>1&&t.impressions>=100)}).length,minImpressions:t,limit:n}}function XM(e,t,n=`${t}s`){return e===1?t:n}function ZM(e){if(!e.clusters.length)return`No query clusters were generated for this scope.`;if(e.highOpportunityClusters>0){let t=e.highOpportunityClusters===1?`has`:`have`;return`${e.highOpportunityClusters} ${XM(e.highOpportunityClusters,`cluster`)} ${t} enough demand and weak enough performance to review first.`}return`No obvious high-opportunity clusters were found. Use this report for content structure and intent mapping rather than urgent fixes.`}async function QM(e){let t=H_(28),n=e.scope?[{groupType:`and`,filters:[{dimension:`page`,operator:`contains`,expression:e.scope}]}]:void 0,{rows:r}=await e_(e.site,{...t,dimensions:[`query`,`page`],type:`web`,dataState:`final`,dimensionFilterGroups:n},{refresh:e.refresh}),{clusters:i,totals:a,highOpportunityClusters:o,minImpressions:s,limit:c}=YM({rows:IM(r.map(e=>({query:e.keys[0]??``,impressions:e.impressions,clicks:e.clicks,position:e.position,tokens:FM(e.keys[0]??``),page:e.keys[1]??``})).filter(t=>t.query&&!Rv(t.query)&&!M_({query:t.query,siteUrl:e.site,brandTerms:e.brandTerms??(e.brand?[e.brand]:void 0),includeBrand:e.includeBrand}))),brand:e.brand,minImpressions:e.minImpressions,limit:e.limit});return{site:e.site,scope:e.scope,range:t,generatedAt:new Date().toISOString(),summary:{clusters:i.length,queries:a.queries,impressions:a.impressions,clicks:a.clicks,highOpportunityClusters:o,minImpressions:s,limit:c,brandFiltering:e.includeBrand?`included`:`excluded`,verdict:ZM({clusters:i,highOpportunityClusters:o})},clusters:i,caveats:[`Date window: ${t.startDate} to ${t.endDate} (28 days), using final GSC data where available.`,e.scope?`Scope: only pages containing "${e.scope}" were included.`:`Scope: all pages in the selected GSC property were included.`,e.brand?`Brand filtering: ${e.includeBrand?`brand queries included`:`brand queries excluded using ${e.brandTerms?.length?e.brandTerms.join(`, `):e.brand}`}.`:`Brand intent detection used only query language because no brand term was supplied.`,`Clusters are token-overlap groups. Review the top queries before creating, merging, or deleting pages.`,`Query threshold: at least ${s} impressions per query; low-volume singletons are omitted and output is capped at ${c} clusters.`,`Expected CTR is a heuristic using a leave-cluster-out site benchmark when enough peer URL data exists, otherwise the default position curve. Page-two clusters do not claim CTR-only click lift.`],recommendations:i.filter(e=>e.recommendation).slice(0,5).map(e=>e.recommendation??``)}}function $M(e){let t=Date.parse(`${e.startDate}T00:00:00Z`),n=Date.parse(`${e.endDate}T00:00:00Z`);return Math.floor((n-t)/864e5)+1}function eN(e=new Date){return Xg(e)}function tN(e,t=new Date){if(!/^\d{4}-\d{2}$/.test(e))throw Error(`Pass --month as YYYY-MM.`);let n=new Date(`${e}-01T00:00:00Z`);if(Number.isNaN(n.getTime()))throw Error(`Pass --month as YYYY-MM.`);let r=new Date(n);r.setUTCMonth(r.getUTCMonth()+1),r.setUTCDate(0);let i=r.toISOString().slice(0,10),a=eN(t),o=i<a?i:a,s=n.toISOString().slice(0,10);if(o<s)throw Error(`Final GSC data is only available through ${a}. Choose an earlier month.`);return{startDate:s,endDate:o}}async function nN(e,t={}){let n=t.measure??NO;return Promise.all(e.map(async e=>{try{let r=await n({id:e.id,refresh:t.refresh});return{status:`measured`,dataStatus:r.dataStatus,change:e,measurement:r}}catch(t){let n=Se(t);return{status:`failed`,dataStatus:`unavailable`,change:e,error:{code:n.code,message:n.message,retryable:n.retryable}}}}))}function rN(e){return e.warnings[0]??e.caveats.find(e=>/incomplete|partial|truncated/i.test(e))??`The requested window or source evidence was incomplete; inspect the structured measurement for provenance.`}function iN(e){return e.flatMap(e=>e.status===`failed`?[`Unavailable saved change measurement "${e.change.title}": ${e.error.message.replace(/[.!?]+$/,``)} (${e.error.code}).`]:e.dataStatus===`partial`?[`Partial saved change measurement "${e.change.title}": ${rN(e.measurement).replace(/[.!?]+$/,``)}.`]:[])}function aN(e,t){return e===`complete`&&t.some(e=>e.dataStatus!==`complete`)?`partial`:e}function oN(e){let t=[`# SEO report: ${e.site}`,``,e.headline,``];if(t.push(`Period: ${e.period.startDate} to ${e.period.endDate}`),t.push(``),e.caveats.length){t.push(`## Report Caveats`);for(let n of e.caveats)t.push(`- ${n}`);t.push(``)}for(let n of e.sections){t.push(`## ${n.title}`);for(let e of n.bullets)t.push(`- ${e}`);t.push(``)}t.push(`## Priorities`);for(let n of e.priorities)t.push(`- ${n.title} (${n.confidence}): ${n.action}`);return t.join(`
707
- `)}function sN(e,t){return e.skippedSections?.find(e=>e.section===t)?.reason}function cN(e,t,n){return sN(e,t)?`${n} unavailable`:void 0}function lN(e){let t=(e,t)=>{let n=new Map;for(let e of t){let t=n.get(e.reason)??[];t.push(e.section),n.set(e.reason,t)}return[...n.entries()].map(([t,n])=>n.length===1?`${e} ${n[0]}: ${t.replace(/[.!?]+$/,``)}.`:`${e} ${n.length} sections: ${t.replace(/[.!?]+$/,``)}.`)};return[...t(`Skipped`,e.skippedSections??[]),...t(`Partial`,e.partialReasons??[])]}function uN(e){if(sN(e,`traffic anomaly`))return`Traffic anomaly detection was not available for this run.`;let t=e.anomaly.anomalies.find(e=>e.significant);if(!t)return`No significant traffic anomaly signal was detected.`;let n=t.direction===`spike`?`spiked`:t.direction===`drop`?`dropped`:t.direction;return`Average daily ${t.metric} ${n} from ${t.baselineMean.toFixed(1)} to ${t.comparisonMean.toFixed(1)}.`}function dN(e,t,n=`${t}s`){return e===0?`no ${n}`:`${e} ${SN(e,t,n)}`}function fN(e){let t=[cN(e,`traffic anomaly`,`traffic anomaly analysis`)??dN(e.summary.significantAnomalies,`significant anomaly signal`),cN(e,`decay analysis`,`content decay analysis`)??dN(e.summary.decayItems,`observed retained query/page decline`),cN(e,`striking-distance opportunities`,`striking-distance analysis`)??dN(e.summary.strikingDistanceItems,`striking-distance opportunity`,`striking-distance opportunities`),cN(e,`quick-win opportunities`,`quick-win analysis`)??dN(e.summary.quickWinItems,`quick-win candidate`)];return(e.summary.cannibalItems||sN(e,`cannibalisation analysis`))&&t.push(cN(e,`cannibalisation analysis`,`multi-URL query analysis`)??dN(e.summary.cannibalItems,`multi-URL query candidate`)),`${e.dataStatus===`partial`?`Partial diagnosis; `:e.dataStatus===`unavailable`?`Diagnosis unavailable; `:``}${t.join(`; `)}.`}function pN(e){if(sN(e,`page movement segments`))return`Page movement comparison was not available for this run.`;let t=e.segments.page;if(t.dataStatus===`empty`)return`Search Console returned no retained page rows for either movement window.`;if(t.dataStatus===`unavailable`)return`No page was retained in both movement windows, so page movement could not be compared.`;let n=t.items[0];if(!n)return`No matched retained page movement stood out.`;let r=n.clickDelta<0?`lost`:`gained`;return`${n.key} ${r} ${Math.abs(n.clickDelta)} clicks compared with the previous window.`}function mN(e){if(e.summary.updateAttributionStatus===`unavailable`)return`Update correlation was not available for this run.`;let t=e.updateCorrelation.overlappingUpdates.length,n=e.anomaly.anomalies.some(e=>e.significant);return t?n?`${Z(t,`official Google update window`)} overlapped the significant movement comparison window. This timing does not establish that the update caused the movement.`:`${Z(t,`official Google update window`)} overlapped the comparison window, but no significant movement was detected.`:`No official Google update window overlapped the comparison window.`}function hN(e){let t=e.dataStatus===`partial`?`partial; `:``;if(e.delta.clicks===null)return`${e.change.title}: ${t}${e.verdict}; comparable click movement is unavailable.`;let n=e.delta.clickPct===null?``:` (${e.delta.clickPct}%)`;return`${e.change.title}: ${t}${e.verdict}, ${e.delta.clicks} clicks${n}.`}function gN(e){return e.status===`failed`?`${e.change.title}: measurement unavailable (${e.error.code}); ${e.error.message.replace(/[.!?]+$/,``)}.`:hN(e.measurement)}function _N(e){let t=[],n=e.monitoring.crawlRuns[0],r=e.monitoring.linkRecover;n?(t.push(`Latest crawl checked ${Z(n.urlCount,`URL`)} with ${Z(n.statusErrors,`status error`)}, ${Z(n.nonIndexable,`non-indexable page`)}, and ${Z(n.highPriorityRecommendations,`high-priority crawl action`)}.`),n.topRecommendation&&t.push(`Top crawl action: ${n.topRecommendation.url}. ${n.topRecommendation.action}`),(n.statusErrors||n.nonIndexable)&&!r&&t.push(`Run link-recover before content work if those URLs have GSC search value; recoverable 4xx, noindex, and canonical issues can look like content decay.`)):t.push(`No crawl-diff run is saved yet.`);let i=e.monitoring.indexWatch;return i.inspectedUrls?(t.push(`Index watch tracks ${Z(i.inspectedUrls,`URL`)}; ${Z(i.currentIssues,`current indexing review`)} and ${Z(i.failed,`failed inspection`)}.`),(i.currentIssues||i.failed)&&t.push(`Index watch currently has ${Z(i.blocked,`exact blocked state`)}; inspect stable verdict, robots, indexing, fetch, and canonical evidence before changing copy.`)):t.push(`No index-watch snapshot is saved yet.`),r?(t.push(`Latest link-recover checked ${Z(r.checked,`search-value URL`)}; ${Z(r.recoverable,`recoverable issue`)}, ${r.high} high severity, ${r.clicksAtRisk.toFixed(0)} clicks at risk.`),r.topUrl&&r.topAction&&t.push(`Top recovery target: ${r.topUrl}. ${r.topAction}`),r.repeatedUrls&&t.push(`${Z(r.repeatedUrls,`recoverable URL`)} appeared in multiple recent link-recover runs${r.repeatedTopUrl?`; first repeat: ${r.repeatedTopUrl}`:``}.`)):t.push(`No link-recover run is saved yet.`),t}function vN(e){let t=e.quickWins.items.map(e=>e.contentVerification?.fetchDiagnostics).filter(e=>e!==void 0);if(!t.length)return null;let n=t.filter(e=>e.blocked).length,r=t.filter(e=>e.rendered).length;return`Verified-page fetch status: ${t.filter(e=>e.cache===`hit`).length} cached, ${t.filter(e=>e.fetched&&e.cache!==`hit`&&!e.rendered).length} fetched, ${r} rendered, ${n} blocked.`}function yN(e){let t=e.quickWins.items.filter(e=>e.contentVerification?.classification===`content-gap`).length,n=e.quickWins.items.filter(e=>e.contentVerification?.classification===`serp-framing`).length;return`${t} ${t===1?`shows`:`show`} true content gaps; ${n} look more like SERP/title/H1 framing gaps.`}function bN(e){let t=e.quickWins.templates[0];return!t||t.urlCount<2?`No dominant opportunity template stood out.`:t.id===`other`?`${t.urlCount} quick-win URLs sit outside a recognised reusable template. Triage them individually, then create a content group only if they share real page structure.`:`${t.urlCount} distinct quick-win URLs sit in the ${t.label} template. Compare their technical and page evidence before choosing a shared change.`}function xN(e){return e===`lost_ctr`?`CTR dropped while rankings held`:e===`lost_position`?`average position worsened`:e===`lost_impressions`?`impressions dropped`:e===`lost_clicks`?`clicks dropped without one dominant signal`:e.replaceAll(`_`,` `)}function SN(e,t,n=`${t}s`){return e===1?t:n}function CN(e){let t=e.decay.groups[0];if(!t)return`No material decay cluster stood out.`;let n=xN(t.diagnosis),r=t.sampleQueries.slice(0,2).join(`; `);return`Top decay cluster: ${t.count} ${SN(t.count,`finding`)} in the ${t.template.label} template (${n}), ${t.totalClickLoss.toFixed(0)} fewer observed clicks across rows retained in both windows. Action: ${t.recommendation}${r?` Example queries: ${r}.`:``}`}function wN(e){let t=e.cannibalization.selection.suppressedQueries;return t?`${t} branded multi-URL query candidates were excluded (${Object.entries(e.cannibalization.suppressionSummary).map(([e,t])=>`${t} ${e.replace(/_/g,` `)}`).join(`, `)}).`:`No branded multi-URL query candidates were excluded.`}function TN(e){let t=[],n=[[`decay analysis`,`content decay`],[`cannibalisation analysis`,`multi-URL query`],[`striking-distance opportunities`,`striking-distance`],[`quick-win opportunities`,`quick-win`]].filter(([t])=>sN(e,t)).map(([,e])=>e);if(n.length&&t.push(`Could not assess ${n.join(`, `)} opportunities in this run; see report caveats.`),e.summary.decayItems){let n=e.decay.items[0];t.push(n?`${e.summary.decayItems} observed retained query/page ${SN(e.summary.decayItems,`decline`)} need review. Start with "${n.query}" on ${n.url}; it had ${n.clickLoss.toFixed(0)} fewer clicks.`:`${e.summary.decayItems} observed retained query/page ${SN(e.summary.decayItems,`decline`)} need review.`),t.push(CN(e))}if(e.summary.cannibalItems){let n=e.cannibalization.items[0];t.push(n?`${e.summary.cannibalItems} multi-URL query ${SN(e.summary.cannibalItems,`candidate`)} need intent and technical review. Start with "${n.query}" and decide whether ${n.pages.length} ranking URLs answer the same intent.`:`${e.summary.cannibalItems} multi-URL query ${SN(e.summary.cannibalItems,`candidate`)} need intent and technical review.`)}if(e.cannibalization.selection.suppressedQueries&&t.push(wN(e)),e.summary.strikingDistanceItems){let n=e.strikingDistance.items[0];t.push(n?`${e.summary.strikingDistanceItems} query/page ${SN(e.summary.strikingDistanceItems,`candidate`)} have an average GSC position above 10 and at most 20. Start by investigating "${n.query}" on ${n.url}; it averages position ${n.position.toFixed(1)} with ${n.impressions.toFixed(0)} impressions.`:`${e.summary.strikingDistanceItems} query/page ${SN(e.summary.strikingDistanceItems,`candidate`)} have an average GSC position above 10 and at most 20.`)}if(!sN(e,`quick-win opportunities`)&&e.summary.quickWinItems){let n=e.quickWins.items[0];t.push(n?`${e.summary.quickWinItems} retained query/page ${SN(e.summary.quickWinItems,`candidate`)} fell below a site-peer or versioned heuristic CTR target. Start with "${n.query}" on ${n.url}; it had ${n.impressions.toFixed(0)} impressions at average position ${n.position.toFixed(1)}.`:`${e.summary.quickWinItems} CTR-target ${SN(e.summary.quickWinItems,`candidate`)} need review.`)}let r=bN(e);if(r.startsWith(`No dominant`)||t.push(r),e.quickWins.verification.requested){t.push(`Verified content for ${e.quickWins.verification.verified} of ${e.quickWins.items.length} quick-win candidates; ${yN(e)}`);let n=vN(e);n&&t.push(n)}return t.length||t.push(`No material content opportunity stood out in this window. Keep monitoring, but do not force content edits from this report alone.`),t}function EN(e){return[`Date window: ${e.period.startDate} to ${e.period.endDate}.`,`Brand queries: ${e.includeBrand?`included`:e.brandTerms?.length?`excluded where saved brand terms matched`:`no saved brand terms, so no brand filter was applied`}.`,`Data freshness: ${e.refresh?`fresh fetch requested; local cache bypassed where supported`:`local cache allowed; rerun with --refresh to bypass cached GSC/HTTP data`}.`,e.skipSearchData?"Search Console: skipped because no property was selected. Run `seo start` to add performance evidence.":`Search Console: included where the selected property returned usable data.`,`GA4: not included in this narrative; use refresh-priorities when GA4 value should influence prioritisation.`,e.skipSearchData?`Content verification: not run because Search Console did not provide page candidates for this report.`:e.verifyContent?`Content verification: checked ${e.verified} of ${Z(e.quickWinCount,`quick-win candidate`)}, limit ${e.verifyLimit??3}.`:`Content verification: not run; recommendations are based on GSC/query data unless stated otherwise.`]}async function DN(e){let t=e.days??90;if(e.startDate&&!e.endDate||!e.startDate&&e.endDate)throw Error(`Pass both startDate and endDate, or neither.`);let n=e.startDate&&e.endDate?{startDate:e.startDate,endDate:e.endDate}:void 0;e.progress?.(`Running diagnosis primitives`);let r=await tO({site:e.site,days:n?$M(n):t,recentDays:e.recentDays,startDate:n?.startDate,endDate:n?.endDate,limit:e.limit,brandTerms:e.brandTerms,includeBrand:e.includeBrand,verifyContent:e.verifyContent,verifyLimit:e.verifyLimit,js:e.js,rate:e.rate,refresh:e.refresh,skipSearchData:e.skipSearchData,progress:e.progress});e.progress?.(`Measuring saved changes`);let i=await nN(bD({site:e.site,limit:e.changeLimit??5}),{refresh:e.refresh}),a=i.flatMap(e=>e.status===`measured`?[e.measurement]:[]),o={crawlRuns:Ck(e.site),indexWatch:MA(e.site),linkRecover:BA(e.site)};e.progress?.(`Rendering narrative`);let s=n??{startDate:r.anomaly.anomalies[0]?.baselineStart??r.quickWins.range.startDate,endDate:r.anomaly.anomalies[0]?.comparisonEnd??r.quickWins.range.endDate},c={site:e.site,generatedAt:new Date().toISOString(),dataStatus:aN(r.dataStatus,i),periodDays:n?$M(n):t,period:s,headline:fN(r),caveats:[...EN({period:s,brandTerms:e.brandTerms,includeBrand:e.includeBrand,refresh:e.refresh,verifyContent:e.verifyContent,verifyLimit:e.verifyLimit,verified:r.quickWins.verification.verified,quickWinCount:r.quickWins.items.length,skipSearchData:e.skipSearchData}),...lN(r),...iN(i)],sections:[{title:`Performance`,bullets:[uN(r),pN(r),mN(r)]},{title:`Content Opportunities`,bullets:TN(r)},{title:`Change Measurements`,bullets:i.length?i.map(gN):[`No measured changes are saved for this property yet.`]},{title:`Technical Monitoring`,bullets:_N({monitoring:o})}],priorities:r.priorities.map(e=>({title:e.label,confidence:e.confidence,action:e.action})),diagnosis:r,changeMeasurements:a,changeMeasurementAttempts:i,monitoring:o};return{...c,markdown:oN(c)}}async function ON(e){let t=e.month??eN().slice(0,7),n=tN(t);e.progress?.(`Preparing monthly period ${t}`);let r=await DN({site:e.site,startDate:n.startDate,endDate:n.endDate,recentDays:Math.min(14,Math.max(3,$M(n))),limit:e.limit,brandTerms:e.brandTerms,includeBrand:e.includeBrand,verifyContent:e.verifyContent,verifyLimit:e.verifyLimit,js:e.js,rate:e.rate,refresh:e.refresh,progress:e.progress});return{...r,month:t,markdown:r.markdown.replace(`# SEO report: ${e.site}`,`# Monthly SEO report (${t}): ${e.site}`)}}function kN(e){return{tables:[{id:`report_caveats`,title:`Report Caveats`,columns:[{key:`caveat`,label:`Caveat`}],rows:e.caveats.map(e=>({caveat:e}))},{id:`report_sections`,title:`Report Sections`,columns:[{key:`section`,label:`Section`},{key:`finding`,label:`Finding`}],rows:e.sections.flatMap(e=>e.bullets.map(t=>({section:e.title,finding:t})))},{id:`report_priorities`,title:`Report Priorities`,columns:[{key:`priority`,label:`Priority`,type:`number`},{key:`title`,label:`Title`},{key:`confidence`,label:`Confidence`},{key:`action`,label:`Action`}],rows:e.priorities.map((e,t)=>({priority:t+1,title:e.title,confidence:e.confidence,action:e.action}))}],charts:[]}}function AN(e,t,n,r){return e===void 0||!Number.isFinite(e)?t:Math.min(r,Math.max(n,Math.floor(e)))}function jN(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 MN(e){try{let t=new URL(e);return t.protocol!==`http:`&&t.protocol!==`https:`?void 0:(t.hash=``,t.toString())}catch{return}}function NN(e,t,n){return!!(t&&n)&&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 PN(e){return{query:e.keys[0]?.trim()??``,clicks:Number(e.clicks.toFixed(3)),impressions:Number(e.impressions.toFixed(3)),ctr:Number(e.ctr.toFixed(4)),position:Number(e.position.toFixed(2))}}function FN(e,t){return t.impressions-e.impressions||e.position-t.position||t.clicks-e.clicks||jN(e.query,t.query)}function IN(e){return{type:`investigate-ranking`,confidence:`low`,evidence:`GSC reports ${e.impressions} eligible impressions at an impression-weighted average position of ${e.position.toFixed(2)} for ${e.url}.`,action:`Inspect indexability, canonical state, query coverage, competing URLs, and relevant internal links for ${e.url} before choosing a change. Start with "${e.primaryQuery}" as the highest-impression query in this page group.`}}function LN(e,t){let n=t.map(PN).sort(FN),r=n.reduce((e,t)=>e+t.impressions,0),i=n.reduce((e,t)=>e+t.clicks,0),a=n.reduce((e,t)=>e+t.position*t.impressions,0)/r,o=Math.min(1,Math.max(.1,(21-a)/10)),s=n[0]?.query??``,c=IN({url:e,primaryQuery:s,impressions:r,position:a});return{url:e,primaryQuery:s,template:NT(e),queries:n,queryCount:n.length,clicks:Number(i.toFixed(3)),impressions:Number(r.toFixed(3)),ctr:Number((i/r).toFixed(4)),position:Number(a.toFixed(2)),priority:{method:`impressions_x_position_proximity`,score:Number((r*o).toFixed(2)),demandImpressions:Number(r.toFixed(3)),positionProximity:Number(o.toFixed(4)),heuristic:!0,estimatedClickLift:!1},finding:`unverified`,recommendation:c}}function RN(e,t){return t.priority.score-e.priority.score||t.impressions-e.impressions||e.position-t.position||jN(e.url,t.url)}function zN(e){let t=AN(e.minImpressions,50,0,1e9),n=AN(e.limit,10,1,100),r={sourceRows:e.rows.length,invalidRows:0,outsidePositionRows:0,brandRows:0,eligibleRows:0,sourcePages:0,belowMinimumPages:0,eligiblePages:0,returnedPages:0,limitedPages:0},i=new Map;for(let t of e.rows){let n=t.keys[0]?.trim()??``,a=MN(t.keys[1]?.trim()??``);if(!NN(t,n,a))r.invalidRows++;else if(t.position<=10||t.position>20)r.outsidePositionRows++;else if(M_({query:n,siteUrl:e.site,brandTerms:e.brandTerms,includeBrand:e.includeBrand}))r.brandRows++;else{let e=i.get(a??``)??[];e.push({...t,keys:[n,a??``]}),i.set(a??``,e),r.eligibleRows++}}r.sourcePages=i.size;let a=[...i.entries()].map(([e,t])=>LN(e,t)).filter(e=>e.impressions>=t?!0:(r.belowMinimumPages++,!1)).sort(RN),o=a.slice(0,n);r.eligiblePages=a.length,r.returnedPages=o.length,r.limitedPages=a.length-o.length;let s=a.reduce((e,t)=>e+t.clicks,0),c=a.reduce((e,t)=>e+t.impressions,0),l=o.reduce((e,t)=>e+t.clicks,0),u=o.reduce((e,t)=>e+t.impressions,0);return{site:e.site,minImpressions:t,limit:n,dataStatus:e.rows.length===0?`empty`:a.length===0?`filtered`:`available`,selection:r,methodology:{id:`gsc_second_page_v2`,source:`google_search_console_query_page_rows`,position:{metric:`gsc_average_position`,minimumExclusive:10,maximumInclusive:20,appliedAt:`query_page_row`},aggregation:{unit:`page`,minimumImpressionsAppliedAt:`eligible_page_aggregate`,ctr:`sum_clicks_divided_by_sum_impressions`,position:`impression_weighted_query_page_position`,queries:`all_eligible_queries_retained`},priority:{method:`impressions_x_position_proximity`,formula:`page impressions * clamp((21 - weighted position) / 10, 0.1, 1)`,heuristic:!0,estimatedClickLift:!1}},provenance:{inputScope:`provided_rows`,selectionOrder:[`valid_row`,`position`,`brand`,`page_aggregation`,`minimum_page_impressions`,`priority`,`limit`],selection:r},summary:{eligiblePages:a.length,returnedPages:o.length,eligibleTemplates:new Set(a.map(e=>e.template.id)).size,returnedTemplates:new Set(o.map(e=>e.template.id)).size,eligibleQueries:a.reduce((e,t)=>e+t.queryCount,0),returnedQueries:o.reduce((e,t)=>e+t.queryCount,0),eligibleClicks:Number(s.toFixed(3)),eligibleImpressions:Number(c.toFixed(3)),returnedClicks:Number(l.toFixed(3)),returnedImpressions:Number(u.toFixed(3))},items:o}}const BN=1e5,VN={searchAnalytics:e_,fetch:Ag,extract:Th,now:()=>new Date};function HN(e){if(e.value===void 0)return e.fallback;if(!Number.isInteger(e.value)||e.value<e.minimum||e.value>e.maximum)throw new A(`INVALID_INPUT`,`${e.label} must be a whole number between ${e.minimum} and ${e.maximum}.`);return e.value}function UN(e){return[...new Set(e.filter(Boolean))]}function WN(e){return[...new Map(e.map(e=>[`${e.stage}\u0000${e.url}\u0000${e.code}\u0000${e.message}`,e])).values()]}function GN(e,t){return Zg(e,t)}function KN(e){let t=e.error??e.summary;return e.status===`failed`?{type:`inspect-fetch`,confidence:`medium`,evidence:t,action:gv(e)}:e.classification===`technical-check`?{type:`fix-technical`,confidence:`medium`,evidence:t,action:gv(e)}:e.classification===`content-gap`?{type:`fix-content-gap`,confidence:`medium`,evidence:t,action:gv(e)}:e.classification===`serp-framing`?{type:`improve-serp-framing`,confidence:`medium`,evidence:t,action:gv(e)}:{type:`investigate-ranking`,confidence:`low`,evidence:t,action:`The page covers "${e.query}". Inspect internal links, competing URLs, intent alignment, and the search results before changing copy.`}}function qN(e,t){let n=KN(t);e.contentVerification=t,e.fetchDiagnostics=t.fetchDiagnostics,e.finding=n.type,e.recommendation=n}function JN(e){return e.dataStatus===`empty`?`Google Search Console returned no retained query/page rows for this window.`:e.dataStatus===`filtered`?`No pages met the position, brand, and minimum-impression criteria.`:e.technical>0?`${e.eligible} eligible average-position pages found; ${e.technical} returned pages have verified technical issues to fix before content changes.`:e.failed>0?`${e.eligible} eligible average-position pages found; ${e.failed} returned pages could not be verified and need a fetch check before content changes.`:e.content>0?`${e.eligible} eligible average-position pages found; ${e.content} returned pages have verified content or search-framing gaps.`:`${e.eligible} eligible average-position pages found; ${e.returned} are returned in priority order for investigation.`}function YN(e){let t=e[0];return t?UN([t.recommendation.action,`Review page-level query groups before creating another URL; several queries may already map to the same ranking page.`,`Treat the priority score as a triage heuristic, not an estimated traffic lift.`]):[`Widen the date window or lower the minimum page impressions only if you need to inspect smaller opportunities.`]}async function XN(e,t=VN){let n=HN({value:e.range,fallback:28,minimum:1,maximum:548,label:`Days`}),r=HN({value:e.verifyLimit,fallback:5,minimum:0,maximum:100,label:`Verification limit`}),i=t.now(),a=GN(n,i),o=await t.searchAnalytics(e.site,{...a,dimensions:[`query`,`page`],type:`web`,dataState:`final`,maxRows:BN},{refresh:e.refresh}),s=new OT;s.addGsc(o.calls,o.rowsFetched);let c=zN({rows:o.rows,site:e.site,minImpressions:e.minImpressions,limit:e.limit,brandTerms:e.brandTerms,includeBrand:e.includeBrand}),l=i.toISOString(),u=[],d=e.verifyContent===!0||e.verifyLimit!==void 0,f=d?c.items.slice(0,r):[];for(let n of f){let r=await bv({query:n.primaryQuery,url:n.url,js:e.js,refresh:e.refresh,rate:e.rate,verifiedAt:l,fetch:t.fetch,extract:t.extract});qN(n,r),u.push(...(r.warnings??[]).map(e=>({stage:`verification`,url:n.url,code:`page-warning`,message:e}))),r.error&&u.push({stage:`verification`,url:n.url,code:`verification-failed`,message:r.error})}let p=f.filter(e=>e.contentVerification?.status===`verified`).length,m=f.filter(e=>e.contentVerification?.status===`failed`).length,h=f.filter(e=>e.finding===`fix-technical`).length,g=f.filter(e=>[`fix-content-gap`,`improve-serp-framing`].includes(e.finding)).length;return{site:e.site,range:n,dateRange:a,generatedAt:l,source:{provider:`google-search-console`,dimensions:[`query`,`page`],searchType:`web`,dataState:`final`,rowsFetched:o.rowsFetched,calls:o.calls,maxRows:BN,possiblyTruncated:o.rowsFetched>=BN,completeness:`retained-query-rows-only`},dataStatus:c.dataStatus,selection:c.selection,methodology:c.methodology,provenance:{...c.provenance,verification:{optional:!0,population:`returned_pages_in_priority_order`}},summary:{...c.summary,contentIssues:g,technicalIssues:h,fetchFailures:m,brandFiltering:e.includeBrand?`included`:`excluded`,verdict:JN({dataStatus:c.dataStatus,eligible:c.summary.eligiblePages,returned:c.summary.returnedPages,technical:h,failed:m,content:g})},verification:d?{requested:!0,limit:r,attempted:f.length,verified:p,failed:m,technicalChecks:h}:{requested:!1,attempted:0,verified:0,failed:0},items:c.items,caveats:[`Date window: ${a.startDate} to ${a.endDate}, using final GSC data where available.`,`Selection: ${c.selection.sourceRows} source rows, ${c.selection.eligibleRows} eligible query/page rows, ${c.selection.eligiblePages} eligible page groups, ${c.selection.returnedPages} returned.`,`Positions use GSC average position per query/page row and must be greater than 10 and at most 20. Page position is impression-weighted across retained queries.`,`Minimum impressions (${c.minImpressions}) are applied after eligible rows are aggregated by page.`,d?`Content verification was attempted for the first ${f.length} returned pages in priority order.`:`Content verification was not requested; unverified items are investigation prompts, not content findings.`,`Priority is a deterministic demand-and-position heuristic. It is not an estimated click or ranking lift.`,`Search Analytics exposes retained query rows only. Anonymized and lower-value queries can be absent even when pagination is exhausted.`,o.rowsFetched>=BN?`The report reached its ${BN.toLocaleString(`en-US`)}-row safety cap.`:``].filter(Boolean),recommendations:YN(c.items),ledgerSummary:s.summary(),warnings:WN(u)}}const ZN=/^(?:how|what|why|when|where|who|which|can|could|do|does|is|are|should|would)\b/i,QN=/^(?:(?:how\s+(?:many|much)|what|which|who|why|when|where)\s+(?:do|does|did|is|are|can|could|should|would)?|how\s+to|can|could|do|does|did|is|are|should|would)\s+/i,$N=/^how\s+(?:(?:do|can|should|would)\s+(?:i|we|you|someone)\s+|to\s+)(?:choose|find|pick|use|compare)\s+/i,eP=/^(?:can|could|do|does|did|should|would)\s+(?:i|we|you|someone|they|he|she)\s+(?:use|choose|buy|get|try|consider|pick|find|hire)\s+/i,tP=/\b(?:best|vs|versus|reviews?|worth|alternatives?)\b/,nP=/\b(?:price|cost|salary|rate|fee|fees)\b/,rP=/\b(?:review|reviews|worth)\b/;function iP(e){return e.normalize(`NFC`).replace(/\s+/g,` `).trim()}function aP(e){let t=iP(e);return t.replace($N,``).replace(eP,``).replace(QN,``).replace(/^(?:the\s+)?(?:best|top\s+\d+)\s+/i,``).replace(/^(?:reviews?)(?:\s+(?:of|for))?\s+/i,``).replace(/\s+(?:reviews?|worth\s+it)$/i,``).replace(/[?.!]+$/,``).trim()||t.replace(/[?.!]+$/,``)}function oP(e){let t=iP(e).replace(/[?.!]+$/,``).split(/\s+(?:vs\.?|versus)\s+/i).map(e=>e.trim());return t.length===2&&t[0]&&t[1]?[t[0],t[1]]:void 0}function sP(e){let t=iP(e);if(!t)return[];let n=R_(t),r=aP(t),i=oP(t),a=new Set;return ZN.test(t)&&a.add(`${t.replace(/[?.!]+$/,``)}?`),a.add(`What should someone know about ${r}?`),a.add(`Explain ${r}, including key facts and caveats.`),/\bbest\b/.test(n)&&a.add(`Which ${r} options are strongest for different needs, and why?`),i&&a.add(`Compare ${i[0]} and ${i[1]}, including tradeoffs and use-case fit.`),nP.test(n)&&a.add(`What factors determine ${r}?`),rP.test(n)&&a.add(`Is ${r} worth considering? Include evidence, pros, cons, and alternatives.`),a.add(tP.test(n)?`What criteria should someone use to evaluate ${r}?`:`What evidence or data supports claims about ${r}?`),[...a].slice(0,5)}function cP(e){return e.sourceRows===0?`empty`:e.partial?`partial`:e.eligibleRows===0?`filtered`:`available`}async function lP(e,t=ev){let n=await tv(e,t),r=n.rows.slice(0,n.filters.limit).map(e=>({...e,evidenceScope:`retained-gsc-query`,prompts:sP(e.query)})),i=n.source.possiblyTruncated||n.selection.invalidRows>0||n.selection.conflictingRows>0,a=cP({sourceRows:n.selection.sourceRows,eligibleRows:n.selection.eligibleRows,partial:i}),o=a===`empty`?`GSC returned no retained query rows for this range.`:a===`partial`&&r.length===0?`No prompt candidates survived the retained rows and filters, but incomplete source evidence makes that negative inconclusive.`:a===`partial`?`Partial evidence: ${r.length} retained GSC queries were converted into prompt suggestions, but incomplete source rows may omit other queries.`:a===`filtered`?`No retained ${e.includeBrand?``:`non-brand `}queries met the minimum-impression threshold.`:`${r.length} retained GSC queries were converted into deterministic monitoring-prompt suggestions; these are not observed AI prompts.`;return{schemaVersion:2,site:n.site,generatedAt:n.generatedAt,rangeDays:n.rangeDays,dateRange:n.dateRange,dataStatus:a,source:n.source,filters:{...n.filters,brandFiltering:e.includeBrand?`included`:`excluded`},methodology:{id:`gsc_query_to_ai_monitoring_prompt`,version:2,sourceUnit:`retained-query-row`,promptType:`deterministic-template-heuristic`,observedAiPromptData:!1,estimatedTrafficLift:!1},selection:{...n.selection,returnedRows:r.length,limitedRows:n.selection.eligibleRows-r.length},summary:{eligibleQueries:n.selection.eligibleRows,returnedQueries:r.length,prompts:r.reduce((e,t)=>e+t.prompts.length,0),verdict:o},items:r,warnings:n.warnings,caveats:[...n.caveats,`Generated prompts are deterministic monitoring suggestions derived from GSC query wording, not evidence of demand in any AI product.`]}}const uP={decay:1.25,"quick-win":1.15,"striking-distance":1,cannibalization:.9,diagnosis:.75,template:1.35,crawl:1.3},dP={high:1.2,medium:1,low:.7},fP={S:1.15,M:1,L:.8},pP={"technical-check":1.25,"content-gap":1.2,"serp-framing":1.1,covered:.85,"fetch-failed":.7};function mP(e){return!Number.isFinite(e)||e<=0?0:Math.min(100,Math.log1p(e)*12)}function hP(e=1){return e<=1?1:Math.min(1.6,1+Math.log1p(e-1)/8)}function gP(e=0){return e<=0?1:Math.min(1.35,1+Math.log1p(e)/30)}function _P(e,t){return t===`technical-check`?`technical`:t===`content-gap`?`content`:t===`serp-framing`||t===`covered`?`serp`:e===`cannibalization`?`authority`:e===`template`||e===`diagnosis`?`strategy`:e===`crawl`?`technical`:e===`decay`?`content`:`serp`}function vP(e){let t=mP(e.impact),n=uP[e.source],r=dP[e.confidence],i=fP[e.effort??`M`],a=e.verification?pP[e.verification]:1,o=hP(e.templateCount),s=gP(e.analyticsSessions),c=t*n*r*i*a*o;return{impact:Number(t.toFixed(2)),source:Number(n.toFixed(2)),confidence:Number(r.toFixed(2)),effort:Number(i.toFixed(2)),verification:Number(a.toFixed(2)),template:Number(o.toFixed(2)),analytics:Number(s.toFixed(2)),final:Number(c.toFixed(2))}}function yP(e){return{workflow:e.workflow,site:e.site,generatedAt:new Date().toISOString(),summary:e.summary,steps:e.steps,actions:e.actions,output:e.output}}const bP={canonical:`technical`,content:`content`,geo:`content`,headings:`content`,images:`content`,indexability:`technical`,international:`technical`,links:`technical`,metadata:`content`,mobile:`technical`,performance:`technical`,response:`technical`,security:`technical`,social:`content`,"structured-data":`technical`},xP={high:100,medium:50,low:20};function SP(e){return e.severity===`high`||e.scoreFactors.searchVisibleUrls>0||e.scoreFactors.sessions>0||e.scoreFactors.conversions>0?`high`:e.severity===`medium`?`medium`:`low`}function CP(e){return e.scoreFactors.effort===`low`?`S`:e.scoreFactors.effort===`high`?`L`:`M`}function wP(e){if(!(!e.scoreFactors.sessions&&!e.scoreFactors.totalUsers))return{sessions:e.scoreFactors.sessions,totalUsers:e.scoreFactors.totalUsers}}function TP(e){return xP[e.severity]+e.count*10+e.scoreFactors.searchVisibleUrls*25+e.scoreFactors.clicks*5+Math.min(e.scoreFactors.impressions,1e4)/100+e.scoreFactors.sessions+e.scoreFactors.conversions*25}function EP(e){let t=SP(e),n=Number(TP(e).toFixed(2)),r=vP({source:`crawl`,impact:n,confidence:t,effort:CP(e),templateCount:e.count,analyticsSessions:e.scoreFactors.sessions});return{source:`crawl`,title:e.title,target:e.sampleUrls[0]??``,category:bP[e.category],score:r.final,impact:n,confidence:t,analytics:wP(e),action:e.howToFix,evidence:e.whyThisRanks,scoreBreakdown:r,ruleId:e.ruleId,severity:e.severity,affectedUrls:e.count,sampleUrls:e.sampleUrls,whyThisRanks:e.whyThisRanks,verification:e.verification}}function DP(e){if(!e.enabled)return{tool:e.tool,status:`skipped`,summary:e.skippedSummary};let t=e.warnings.find(t=>t.startsWith(e.warningPrefix));return{tool:e.tool,status:t?`skipped`:`completed`,summary:t??e.enabledSummary}}async function OP(e,t){let n=await hS(e,t),r=tC(n,{limit:e.limit??25}),i=r.map(EP).sort((e,t)=>t.score-e.score);return yP({workflow:`crawl-implementation-queue`,site:e.site??n.config.url,summary:`${i.length} crawler fixes ranked from ${n.summary.totalPages} crawled pages.`,steps:[{tool:`seo_crawl_site`,status:n.status===`failed`?`skipped`:`completed`,summary:`Crawled ${n.summary.totalPages} pages with ${n.issues.length} issues. Status: ${n.status}.`},DP({tool:`seo_gsc_page_metrics`,enabled:!!e.site,warningPrefix:`GSC metrics skipped:`,enabledSummary:`Joined Search Console page metrics where URLs matched.`,skippedSummary:`No GSC property was selected.`,warnings:n.warnings}),DP({tool:`seo_ga4_landing_page_values`,enabled:!!e.ga4PropertyId,warningPrefix:`GA4 metrics skipped:`,enabledSummary:`Joined GA4 landing-page value where URLs matched.`,skippedSummary:`No GA4 property was selected.`,warnings:n.warnings}),{tool:`seo_top_crawl_fixes`,status:`completed`,summary:`Grouped crawl issues, weighted them by severity, affected URLs, GSC visibility, GA4 value, and effort.`}],actions:i.slice(0,5).map(e=>({title:e.title,action:e.action,confidence:e.confidence})),output:{queue:i,warnings:n.warnings,crawl:n,topFixes:r}})}async function kP(e){let t=await DN(e),n=t.diagnosis.skippedSections?.length??0,r=t.dataStatus===`complete`?`Generated diagnosis, movement, change, and monitoring narrative.`:n?`Generated ${t.dataStatus===`unavailable`?`an`:`a`} ${t.dataStatus} diagnosis; ${n} sections were unavailable.`:`Generated a partial diagnosis because one or more source datasets were incomplete.`;return yP({workflow:`diagnose-property`,site:e.site,summary:t.headline,steps:[{tool:`seo_report_narrative`,status:`completed`,summary:r}],actions:t.priorities.map(e=>({title:e.title,action:e.action,confidence:e.confidence})),output:{narrative:t}})}function AP(e){return typeof e==`object`&&!!e}function jP(e){if(!AP(e))return[];let t=e.queue;return Array.isArray(t)?t.filter(e=>AP(e)):[]}function MP(e){if(e.steps.length)return{id:`steps`,title:`Steps`,columns:[{key:`tool`,label:`Tool`},{key:`status`,label:`Status`},{key:`summary`,label:`Summary`}],rows:e.steps.map(e=>({tool:e.tool,status:e.status,summary:e.summary}))}}function NP(e){if(e.actions.length)return{id:`recommended_actions`,title:`Recommended Actions`,columns:[{key:`priority`,label:`Priority`,type:`number`},{key:`title`,label:`Title`},{key:`confidence`,label:`Confidence`},{key:`action`,label:`Action`}],rows:e.actions.map((e,t)=>({priority:t+1,title:e.title,confidence:e.confidence,action:e.action}))}}function PP(e,t){if(e.length)return{id:`priority_queue`,title:`Priority Queue`,columns:[{key:`rank`,label:`Rank`,type:`number`},{key:`source`,label:`Source`},{key:`category`,label:`Category`},{key:`score`,label:`Score`,type:`number`},{key:`confidence`,label:`Confidence`},{key:`target`,label:`Target`,type:`url`},{key:`action`,label:`Action`}],rows:e.slice(0,t).map((e,t)=>({rank:t+1,source:e.source,category:e.category,score:Number(e.score.toFixed(1)),confidence:e.confidence,target:e.target,action:e.action}))}}function FP(e,t={}){let n=jP(e.output);return{tables:[MP(e),NP(e),PP(n,t.queueLimit??10)].filter(e=>!!e),charts:n.length?[{id:`priority_scores`,title:`Priority Scores`,type:`bar`,tableId:`priority_queue`,xKey:`rank`,yKey:`score`}]:[]}}function IP(e,t={}){let n=FP(e,t);return[`# ${e.workflow}`,``,`Property: ${e.site}`,`Generated: ${e.generatedAt}`,``,e.summary,``,...OM(n.tables)].join(`
708
- `).trim()}async function LP(e){let t=await ON(e);return yP({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 RP=new Set([`quick-win`,`striking-distance`]);function zP(e){return e?e.id.endsWith(`-salary`)?`salary-page`:e.id:`no-template`}function BP(e){if(!RP.has(e.source)||e.grouped)return;let t=zP(e.template);return[e.source,e.category,t,R_(e.title)].join(`|`)}function VP(e){if(!RP.has(e.source)||e.grouped)return;let t=zP(e.template);return[e.source,e.category,t,R_(e.target)].join(`|`)}function HP(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 UP(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 WP(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:HP(t),evidence:UP(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 GP(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?[WP(e)]:e)]}function KP(e){return GP(GP(e,VP),BP).sort((e,t)=>t.score-e.score)}function qP(e){let t=e.templates?.find(t=>t.id===e.id);return t?.urlCount??t?.count}function JP(e,t){let n=vP({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:_P(e.source,e.verification),score:n.final,scoreBreakdown:n}}function YP(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 XP(e){let t=await tO({...e,verifyContent:e.verifyContent??!0,verifyLimit:e.verifyLimit??Math.min(e.limit??5,5)}),n=H_(e.days??28),r=await Yx({propertyId:e.ga4PropertyId,startDate:n.startDate,endDate:n.endDate}),i=Jx({propertyId:e.ga4PropertyId,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=qP({templates:t.strikingDistance.templates,id:e.template.id}),i=Kx(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=qP({templates:t.quickWins.templates,id:e.template.id}),i=Kx(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=qP({templates:t.decay.templates,id:e.template.id}),i=Kx(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(...YP({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=KP(o.map(e=>JP(e,i.canRank)).filter(e=>e.score>0).sort((e,t)=>t.score-e.score)).slice(0,e.limit??25);return yP({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 ZP(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 QP(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?Tk({site:e.site,startUrl:e.startUrl,limit:e.limit,refresh:e.refresh,js:e.js}):void 0,e.sitemaps?.length?LA({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?jA({site:e.site,urls:e.urls,languageCode:e.languageCode,dailyLimit:e.dailyLimit}):void 0,t?YA({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=ZP(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`}),yP({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 $P(e,t){return e<t?-1:+(e>t)}function eF(e){let t=e.items.filter(e=>e.clickDelta>0).sort((e,t)=>t.clickDelta-e.clickDelta||$P(e.key,t.key)),n=e.items.filter(e=>e.clickDelta<0).sort((e,t)=>e.clickDelta-t.clickDelta||$P(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 tF(e){return Math.abs(e).toLocaleString(`en-GB`)}function nF(e){return`${Math.round(e*100)}%`}function rF(e,t){return e<t?-1:+(e>t)}function iF(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 aF(e){return e.clickDelta>0?`gained`:e.clickDelta<0?`lost`:`held flat`}function oF(e,t){return e===`page`?t.key.replace(/^https?:\/\//,``):t.key}function sF(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: ${iF(t.coverage)}.`};let n=t.dataStatus===`partial`?` Evidence is partial; ${t.summary.unmatchedRows} one-window rows were not treated as zero.`:``,r=` Scope: ${iF(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 ${oF(e,i)} ${aF(i)} ${tF(i.clickDelta)} clicks; top loser ${oF(e,a)} ${aF(a)} ${tF(a.clickDelta)} clicks.${n}${r}`}:i?{dimension:e,dataStatus:t.dataStatus,coverage:t.coverage,unmatchedRows:t.summary.unmatchedRows,winner:i,summary:`${e}: top winner ${oF(e,i)} ${aF(i)} ${tF(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 ${oF(e,a)} ${aF(a)} ${tF(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 cF(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 ${tF(t.winner.clickDelta)} clicks`:t?.loser?`top returned page lost ${tF(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 ${tF(n.winner.clickDelta)} clicks`:n?.loser?`top returned query lost ${tF(n.loser.clickDelta)} clicks`:`no material query movement found in returned rows`}.`}function lF(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 uF(e,t){return e.filter(e=>t===`winner`?e.clickDelta>0:e.clickDelta<0)}function dF(e){let t=new Map;for(let n of e.items){let r=Fk(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 fF(e){return e?[...new Set(e.shape.variableSegments.flatMap(e=>e.tokenExamples))].slice(0,6):[]}function pF(e){return dF({items:uF(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=fF(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} ${tF(t.clickDelta)} clicks across ${t.items.length} moved URLs (${nF(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||rF(e.signature,t.signature))}function mF(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)||rF(e.key,t.key));if(t.length<6)return[];let n=Lk(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[...pF({direction:`winner`,items:t,clusters:n,totalMovement:r,segmentCoverage:e.coverage}),...pF({direction:`loser`,items:t,clusters:n,totalMovement:i,segmentCoverage:e.coverage})].slice(0,6)}async function hF(e,t={}){let n=e.limit??20,r=t.updateCorrelation??UD,i=t.segmentImpact??DT,[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:eF(o),query:eF(s),device:eF(c),country:eF(l)},d=[sF(`page`,u.page),sF(`query`,u.query),sF(`device`,u.device),sF(`country`,u.country)],f=mF(u.page),p=[u.page,u.query,u.device,u.country].every(e=>e.dataStatus===`empty`||e.dataStatus===`unavailable`)?`skipped`:`completed`;return yP({workflow:`update-postmortem`,site:e.site,summary:cF({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:lF({update:a,segments:u,templateMovement:f}),output:{update:a,insights:d,templateMovement:f,segments:u}})}function gF(e){return e.toLowerCase().replace(/^https?:\/\//,``).replace(/^sc-domain:/,``).replace(/[^a-z0-9]+/g,`-`).replace(/^-+|-+$/g,``).slice(0,60)}function _F(e=[]){return[...new Set(e.map(e=>e.trim()).filter(Boolean))]}function vF(e){let t=gF(e.id??e.name??e.siteUrl);if(!t)throw Error(`Client id could not be derived.`);return t}function yF(){return[...Qe().clients].sort((e,t)=>e.name.localeCompare(t.name))}function bF(e){let t=Qe().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 xF(e){let t=Qe(),n=vF(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:_F(e.watchUrls??r?.watchUrls),brandTerms:_F(e.brandTerms??r?.brandTerms??A_({id:n,name:e.name??r?.name,siteUrl:e.siteUrl})),ga4PropertyId:e.ga4PropertyId??r?.ga4PropertyId,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],$e(t),a}function SF(e){let t=Qe(),n=bF(e);return n?(t.clients=t.clients.filter(e=>e.id!==n.id),$e(t),!0):!1}function CF(e){let t=Qe(),n=bF(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,$e(t),{...n,isDefault:!0}}const wF=[`https://www.googleapis.com/auth/webmasters.readonly`,`https://www.googleapis.com/auth/analytics.readonly`];async function TF(){let e=Ne(),t=Qe(),n=_t(),r=At(),a=await st(),o=ft(),s=a?!!yt(a.client_source):void 0,c=[];c.push({id:`config-dir`,label:`Config directory`,status:i(e.configDir)?`pass`:`warn`,detail:e.configDir,fix:i(e.configDir)?void 0:"Run `seo start` or `seo auth setup-client`."}),c.push({id:`oauth-client`,label:`Google credentials`,status:r.configured?`pass`:s===!1||r.error?`fail`:n.sharedConfigured||n.byoConfigured?`pass`:`fail`,detail:r.configured?`Service account configured for ${r.identity}.`:s===!1?`Stored Google login uses the ${a?.client_source===`shared`?`shared seo app`:`BYO client`}, but that client is not configured.`:r.error?r.error:n.sharedConfigured?`Shared client configured.`:n.byoConfigured?`BYO client configured at ${e.oauthClientFile}.`:`No shared or BYO Google OAuth client configured.`,fix:r.configured?void 0:s===!1?a?.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.":r.error?"Set one valid service account credential source, then run `seo doctor` again.":n.sharedConfigured||n.byoConfigured?void 0:"Run `seo auth setup-client` or set SEO_GOOGLE_CLIENT_ID and SEO_GOOGLE_CLIENT_SECRET."}),c.push({id:`oauth-client-file`,label:`BYO OAuth file`,status:o?`pass`:`warn`,detail:o?e.oauthClientFile:`No BYO OAuth client file. This is fine if using a shared client.`}),c.push({id:`google-login`,label:`Google login`,status:r.configured||a?`pass`:`fail`,detail:r.configured?`Using service account ${r.identity}.`:a?`Signed in as ${a.account_email}.`:`No Google token found.`,fix:r.configured||a?void 0:"Run `seo auth login`, or configure a service account for CI."});let l=new Set(a?.scope.split(/\s+/).filter(Boolean)??[]);for(let e of wF)c.push({id:`scope:${e}`,label:e.endsWith(`webmasters.readonly`)?`GSC scope`:`GA4 scope`,status:r.configured||a&&l.has(e)?`pass`:a?`fail`:`warn`,detail:r.configured?`${e} requested by the service account.`:a?l.has(e)?e:`Missing ${e}.`:`Cannot inspect scopes until login is complete.`,fix:!r.configured&&a&&!l.has(e)?"Run `seo auth logout` then `seo auth login`.":void 0});return c.push({id:`default-site`,label:`Saved GSC property`,status:`pass`,detail:t.defaultSite??`No saved default. Human CLI commands will prompt; agents can pass --site.`}),c.push({id:`default-ga4`,label:`Saved GA4 property`,status:`pass`,detail:t.google.defaultGa4PropertyId??`No saved default. Human CLI commands will prompt; agents can pass --property.`}),{ok:c.every(e=>e.status!==`fail`),generatedAt:new Date().toISOString(),checks:c}}var EF=class{name=`dataforseo`;capabilities={overview:!0};getAuthHeader(){let e=Qe(),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 DF(e){return e.trim().split(`
709
- `).map(e=>e.split(`;`).map(e=>e.replace(/^"|"$/g,``).trim()))}function OF(e){return e/1e3*.05}async function kF(e,t,r,i,a,o=!1){let s=Qe().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=j(),u=on([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:OF(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=DF(m),g=r(h),_=Math.max(0,h.length-1)*a;return l.prepare(`INSERT OR REPLACE INTO semrush_cache
704
+ VALUES (?, ?, ?, ?, ?, ?)`).run(e.id,e.url,e.strategy,JSON.stringify(n),Date.parse(e.generatedAt),t+864e5)}async function Uj(e){let t=await Ag(e.url,{js:!1,refresh:!0}),n=t.diagnostics.durationMs,r={schemaVersion:1,methodology:`performance-v2`,dataStatus:`partial`,id:e.id,url:e.url,finalUrl:t.finalUrl,strategy:e.strategy,generatedAt:e.generatedAt,cache:{status:e.cacheStatus,ttlHours:24},source:`fetch-fallback`,grade:`unknown`,headline:`Lighthouse lab data was unavailable; fallback evidence is unscored HTTP transport data.`,metrics:{fallbackFetchDuration:{value:n,displayValue:`${n}ms`,source:`fetch-fallback`}},labInsights:[],labDataStatus:{provider:`lighthouse`,status:`unavailable`,reason:e.lighthouseFailure.reason,failureCode:e.lighthouseFailure.failureCode},fallbackEvidence:{requestedUrl:e.url,finalUrl:t.finalUrl,httpStatus:t.status,blocked:t.diagnostics.blocked,redirectCount:t.diagnostics.redirectChain?.length??0},fieldDataStatus:{provider:`crux`,status:`not_configured`,reason:`Field Core Web Vitals were not checked yet.`,checkedUrl:e.url,checkedOrigin:new URL(e.url).origin,formFactor:e.strategy===`desktop`?`DESKTOP`:`PHONE`},caveats:[`Fallback response duration includes the complete local fetch workflow and is not TTFB, Lighthouse, or Core Web Vitals.`,`Fallback mode does not measure JavaScript execution, LCP, CLS, TBT, or INP and never receives a performance score.`,...t.warnings],topActions:[]};return{...r,topActions:Dj(r)}}function Wj(e){let t=e.fieldData;if(!t)return e.headline;let n=`${t.formFactor.toLowerCase()} ${t.scope}-level`;return t.assessment.status===`incomplete`?`CrUX ${n} field data is incomplete (${t.assessment.availableMetrics}/3 Core Web Vitals); ${e.headline}`:`CrUX ${n} Core Web Vitals are ${t.assessment.status}; ${e.headline}`}function Gj(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function Kj(e){if(!Gj(e))return!1;let t=e.cache,n=e.fieldDataStatus,r=e.labDataStatus;return e.schemaVersion===1&&e.methodology===`performance-v2`&&(e.dataStatus===`complete`||e.dataStatus===`partial`)&&typeof e.id==`string`&&typeof e.url==`string`&&(e.strategy===`mobile`||e.strategy===`desktop`)&&typeof e.generatedAt==`string`&&Gj(t)&&typeof t.ttlHours==`number`&&(e.source===`lighthouse`||e.source===`fetch-fallback`)&&typeof e.headline==`string`&&Gj(e.metrics)&&Array.isArray(e.labInsights)&&Gj(r)&&(r.status===`available`||r.status===`unavailable`)&&Gj(n)&&[`not_configured`,`available`,`unavailable_no_coverage`,`request_failed`].includes(String(n.status))&&Array.isArray(e.topActions)&&Array.isArray(e.caveats)}function qj(e){let{report:t}=e;return!e.includeRaw&&!e.customLighthouse&&!e.customCrux&&t.dataStatus===`complete`&&t.source===`lighthouse`&&t.labDataStatus.status===`available`&&t.fieldDataStatus.status!==`request_failed`&&(t.fieldDataStatus.status!==`available`||t.fieldData?.status===`available`&&t.fieldData.assessment.status!==`incomplete`)&&t.raw===void 0}async function Jj(e){let t=e.strategy??`mobile`,n=Mg(e.url),r=e.cruxApiKey??process.env.SEO_CRUX_API_KEY??process.env.CRUX_API_KEY,i=Bj({url:n,strategy:t,fieldDataEnabled:!!r}),a=e.now?.()??new Date,o=!!(e.refresh||e.includeRaw||e.lighthouseBin||e.cruxFetch);if(!o){let e=Vj(i,a.getTime());if(e)return{...e,cache:{...e.cache,status:`hit`}}}let s=a.toISOString(),c=o?`bypass`:`miss`,l;try{l=await zj({id:i,url:n,strategy:t,lighthouseBin:e.lighthouseBin,timeoutMs:e.timeoutMs,generatedAt:s,includeRaw:e.includeRaw}),l.cache.status=c}catch(e){l=await Uj({id:i,url:n,strategy:t,generatedAt:s,cacheStatus:c,lighthouseFailure:Rj(e)})}let u=await kj({url:n,strategy:t,apiKey:r,fetchImpl:e.cruxFetch,timeoutMs:e.cruxTimeoutMs});return l={...l,...u.fieldData?{fieldData:u.fieldData}:{},fieldDataStatus:u.status,caveats:[...new Set([...l.caveats,u.caveat])]},l={...l,dataStatus:l.labDataStatus.status===`unavailable`||u.status.status===`request_failed`||u.fieldData?.assessment.status===`incomplete`?`partial`:l.dataStatus,headline:Wj(l),topActions:Dj(l)},qj({report:l,customCrux:!!e.cruxFetch,customLighthouse:!!e.lighthouseBin,includeRaw:!!e.includeRaw})&&Hj(l,a.getTime()),l}const Yj=new Set(`a.an.and.are.best.for.first.from.how.in.is.last.letter.letters.name.names.of.on.or.start.starts.starting.that.the.to.vs.with`.split(`.`)),Xj=new Set([...Yj,`about`,`after`,`all`,`at`,`be`,`by`,`can`,`chart`,`check`,`day`,`days`,`did`,`do`,`does`,`each`,`ever`,`find`,`free`,`get`,`high`,`hour`,`hours`,`into`,`low`,`made`,`make`,`makes`,`many`,`month`,`months`,`much`,`near`,`new`,`page`,`pages`,`per`,`search`,`show`,`site`,`sites`,`than`,`they`,`this`,`time`,`times`,`today`,`tool`,`tools`,`top`,`what`,`when`,`where`,`year`,`years`,`you`,`your`]);function Zj(e){return e.normalize(`NFKC`).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 Qj(e){return e.length>1||/^\d$/u.test(e)||/[^\p{ASCII}]/u.test(e)}function $j(e){let t=Zj(e).split(` `).filter(e=>Qj(e)&&!Yj.has(e));return[...new Set(t)]}function eM(e){return/^[a-z]+$/i.test(e)?[`surname`,`surnames`].includes(e)?`name`:[`beginning`,`begins`,`started`].includes(e)?`start`:/(as|is|ss|us)$/.test(e)?e:e.length>4&&e.endsWith(`ies`)?`${e.slice(0,-3)}y`:e.length>4&&e.endsWith(`xes`)?e.slice(0,-2):e.length>4&&e.endsWith(`es`)||e.length>4&&e.endsWith(`s`)?e.slice(0,-1):e:e}function tM(e){return Zj(e).split(` `).filter(e=>Qj(e)&&!Xj.has(e))}function nM(e){let t=Zj(e);return/\b(vs|versus|compare|comparison)\b/.test(t)?`comparison`:/\balternative|alternatives\b/.test(t)?`alternatives`:/\bwithout (account|login|signing|sign in)\b/.test(t)?`no-login modifier`:/\b(export|download|save|convert)\b/.test(t)?`workflow/action`:/\bmeaning|origin|history\b/.test(t)?`meaning/origin`:/\brare|rarity|popular|popularity\b/.test(t)?`rarity/popularity`:/\bhow many|number of|people with|people have\b/.test(t)?`count/statistic`:/\b(starting with|starts with|start with|beginning with|letter)\b/.test(t)?`list/facet`:/\b(price|pricing|cost|fee)\b/.test(t)?`pricing`:/\breview|reviews|reddit|forum|community\b/.test(t)?`reviews/community`:/\bguide|docs|documentation|api\b/.test(t)?`docs/how-to`:`general`}function rM(e){let t=tM(e).map(eM),n=[];for(let e of[3,2])for(let r=0;r<=t.length-e;r+=1)n.push(t.slice(r,r+e).join(` `));return n.push(...t),[...new Set(n)]}function iM(e){let t=new Map,n=new Map,r=new Map;for(let t of e)r.set(t.query,(r.get(t.query)??0)+t.impressions);for(let[e,i]of r){let r=rM(e);n.set(e,r);for(let e of r){let n=t.get(e)??{queryCount:0,impressions:0};n.queryCount+=1,n.impressions+=i,t.set(e,n)}}let i=[...r.values()].reduce((e,t)=>e+t,0),a=[...t.entries()].filter(([,e])=>e.queryCount>=2).filter(([e,t])=>e.includes(` `)||t.impressions>=i*.05).sort((e,t)=>t[1].impressions-e[1].impressions||t[0].length-e[0].length),o=new Map;for(let[e,t]of n){let n=a.find(([e])=>t.includes(e));n&&o.set(e,`theme: ${n[0]}`)}return o}function aM(e){let t=new Map,n=new Map,r=iM(e);for(let i of e){let e=nM(i.query),a=e===`general`?r.get(i.query)??e:e,o=t.get(a)??{label:a,queryCount:0,clicks:0,impressions:0,examples:[]},s=n.get(a)??new Set;s.add(i.query),n.set(a,s),o.queryCount=s.size,o.clicks+=i.clicks,o.impressions+=i.impressions,o.examples.length<3&&!o.examples.includes(i.query)&&o.examples.push(i.query),t.set(a,o)}return[...t.values()].sort((e,t)=>t.impressions-e.impressions).slice(0,5)}function oM(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)return t}return n.length-r.length}function sM(e){try{return[`http:`,`https:`].includes(new URL(e).protocol)}catch{return!1}}function cM(e,t=0){return Number.isFinite(e)&&e>=t}function lM(e){return!!e.query.trim()&&sM(e.page)&&cM(e.clicks)&&cM(e.impressions)&&e.clicks<=e.impressions&&cM(e.position,1)}function uM(e){return sM(e.page)&&cM(e.clicks)&&cM(e.impressions)&&e.clicks<=e.impressions&&cM(e.position,1)}function dM(e){let t=e.reduce((e,t)=>e+t.impressions,0);return t?e.reduce((e,t)=>e+t.position*t.impressions,0)/t:0}function fM(e){let t=new Map;for(let n of e){let e=t.get(n.page);t.set(n.page,{page:n.page,clicks:(e?.clicks??0)+n.clicks,impressions:(e?.impressions??0)+n.impressions,position:e?dM([e,n]):n.position})}return[...t.values()]}function pM(e){let t=new Map;for(let n of e){let e=`${n.query}\u0000${n.page}`,r=t.get(e);t.set(e,{query:n.query,page:n.page,clicks:(r?.clicks??0)+n.clicks,impressions:(r?.impressions??0)+n.impressions,position:r?dM([r,n]):n.position})}return[...t.values()]}function mM(e,t){let n=$j(e).map(eM);if(!n.length)return{coverage:0,missingTerms:[]};let r=new Set($j(Zj(t??``)).map(eM));return{coverage:n.filter(e=>r.has(e)).length/n.length,missingTerms:n.filter(e=>!r.has(e))}}function hM(e){let t=mM(e.query,e.title),n=mM(e.query,e.h1),r=mM(e.query,e.body),i=r.coverage<.75?`body-term-review`:t.coverage<.75||n.coverage<.75?`serp-framing-review`:`covered`;return{method:`literal-query-term-presence-v1`,heuristic:!0,query:e.query,classification:i,titleCoverage:t.coverage,h1Coverage:n.coverage,bodyCoverage:r.coverage,missingTerms:r.missingTerms.slice(0,8)}}function gM(e,t){let n=Jk(e),r=t.shape.variableSegments.flatMap(e=>{let t=n[e.index];return t?tM(t):[]});return[...new Set(r.map(eM))]}function _M(e,t){let n=0,r=0,i=0,a=0,o=[];for(let s of e){let e=gM(s.page,t);if(!e.length)continue;let c=new Set($j(s.query).map(eM));n+=1,i+=s.impressions,e.some(e=>c.has(e))?(r+=1,a+=s.impressions):o.push({url:s.page,query:s.query,pathTerms:e.slice(0,6),impressions:s.impressions})}return{method:`any-path-variable-term-v1`,heuristic:!0,checkedQueries:n,matchedQueries:r,impressionShare:i?a/i:0,weakExamples:o.sort((e,t)=>t.impressions-e.impressions||oM(e.query,t.query)||oM(e.url,t.url)).slice(0,5)}}function vM(e){let t=e.pageRows.reduce((e,t)=>e+t.clicks,0),n=e.pageRows.reduce((e,t)=>e+t.impressions,0),r=new Map;for(let t of e.queryRows){let e=r.get(t.query);r.set(t.query,{query:t.query,clicks:(e?.clicks??0)+t.clicks,impressions:(e?.impressions??0)+t.impressions,position:e?dM([e,t]):t.position})}return{clicks:t,impressions:n,ctr:n?t/n:0,position:dM(e.pageRows),impressionsPerUrl:e.cluster.urlCount?n/e.cluster.urlCount:0,clicksPerUrl:e.cluster.urlCount?t/e.cluster.urlCount:0,retainedQueryImpressions:e.queryRows.reduce((e,t)=>e+t.impressions,0),queryCount:r.size,pageCountWithGsc:e.pageRows.length,zeroClickImpressions:e.pageRows.filter(e=>e.clicks===0).reduce((e,t)=>e+t.impressions,0),entityFit:_M(e.queryRows,e.cluster),queryPatterns:aM(e.queryRows),topQueries:[...r.values()].sort((e,t)=>t.impressions-e.impressions||t.clicks-e.clicks||oM(e.query,t.query)).slice(0,5)}}function yM(e){if(!e.length)return;let t=[...e].sort((e,t)=>e-t),n=Math.floor(t.length/2);return t.length%2?t[n]:((t[n-1]??0)+(t[n]??0))/2}function bM(e){let t=new Map;for(let n of e){let e=Zj(n??``);e&&t.set(e,(t.get(e)??0)+1)}return[...t.values()].reduce((e,t)=>e+(t>1?t:0),0)}function xM(e){return e===`PASS`?`indexed`:e===`NEUTRAL`?`excluded`:e===`FAIL`?`invalid`:`unknown`}function SM(e,t,n){if(!n)return[];let r=new Map(t.map(e=>[e.page,e])),i=[...e].sort((e,t)=>{let n=r.get(e),i=r.get(t);return(i?.clicks??0)-(n?.clicks??0)||(i?.impressions??0)-(n?.impressions??0)||oM(e,t)}),a=[0,i.length-1,Math.floor((i.length-1)/2)],o=[];for(let e of a){let t=i[e];if(t&&!o.includes(t)&&o.push(t),o.length>=n)return o}for(let e of i)if(o.includes(e)||o.push(e),o.length>=n)break;return o}function CM(e,t){let n=e.filter(e=>e.technicalStatus!==`fetch-error`).length,r=e.filter(e=>[`ok`,`redirected`].includes(e.technicalStatus)),i=r.map(e=>e.wordCount).filter(e=>e!==void 0),a=yM(i);return{requested:t,attempted:e.length,succeeded:n,failed:e.length-n,usable:r.length,samples:e,wordCount:i.length&&a!==void 0?{min:Math.min(...i),median:a,max:Math.max(...i)}:void 0,medianWordCount:a,weakQueryCoverage:r.filter(e=>e.queryCoverage?.classification===`body-term-review`).length,duplicateTitles:bM(r.map(e=>e.title)),duplicateMetaDescriptions:bM(r.map(e=>e.metaDescription)),blockedOrFailed:e.filter(e=>![`ok`,`redirected`].includes(e.technicalStatus)).length}}function wM(e,t){let n=e.filter(e=>!e.warning),r=n.filter(e=>e.indexStatus===`indexed`).length,i=n.filter(e=>[`excluded`,`invalid`].includes(e.indexStatus)).length;return{requested:t,attempted:e.length,succeeded:n.length,failed:e.length-n.length,samples:e,indexed:r,notIndexed:i,unknown:n.length-r-i,warnings:e.length-n.length}}function TM(e){let t=e.inspection.indexed+e.inspection.notIndexed;return e.inspection.notIndexed&&(t===1||e.inspection.notIndexed/t>=.5)?{verdict:`index-risk`,confidence:t>=2?`medium`:`low`}:e.crawl.blockedOrFailed&&(e.crawl.attempted===1||e.crawl.blockedOrFailed/e.crawl.attempted>=.5)?{verdict:`crawl-risk`,confidence:e.crawl.attempted>=2?`medium`:`low`}:e.crawl.usable>=3&&(e.crawl.weakQueryCoverage/e.crawl.usable>=.5||e.crawl.duplicateTitles>=2||e.crawl.duplicateMetaDescriptions>=2)?{verdict:`content-review`,confidence:`medium`}:e.metrics.impressions>0&&(e.metrics.position>8||e.metrics.position<=10&&e.metrics.ctr<.01||e.metrics.entityFit.checkedQueries>=10&&e.metrics.entityFit.impressionShare<.35)?{verdict:`opportunity`,confidence:`low`}:t>=2&&e.inspection.indexed===t&&e.crawl.usable>=2&&e.crawl.blockedOrFailed===0?{verdict:`healthy`,confidence:`medium`}:e.metrics.impressions>0?{verdict:`inconclusive`,confidence:`low`}:{verdict:`no-data`,confidence:`low`}}function EM(e){let t=[];if(e.metrics.impressions&&t.push(`${Math.round(e.metrics.impressions).toLocaleString(`en-GB`)} impressions from ${Z(e.metrics.pageCountWithGsc,`retained GSC page row`)}`),e.inspection.attempted){let n=Math.max(0,e.population.discoveredUrls-e.inspection.attempted);t.push(`${e.inspection.indexed}/${e.inspection.attempted} sampled URLs returned exact PASS verdicts; ${n} discovered URLs were not inspected`)}if(e.crawl.attempted){let n=Math.max(0,e.population.discoveredUrls-e.crawl.attempted);t.push(`${e.crawl.usable}/${e.crawl.attempted} sampled URLs returned usable 2xx crawl evidence; ${n} discovered URLs were not sampled`)}return e.crawl.weakQueryCoverage&&t.push(`${Z(e.crawl.weakQueryCoverage,`usable sample`)} need literal query-term review`),e.crawl.duplicateTitles&&t.push(`${Z(e.crawl.duplicateTitles,`sampled URL`)} share duplicate titles`),t}function DM(e){return e.verdict===`index-risk`?`${Z(e.inspection.notIndexed,`inspected URL`)} returned an exact excluded or invalid verdict as of Google's last indexed crawl. Review their robots/noindex, canonical, fetch, and sitemap evidence; do not generalise the result to ${Z(e.population.untestedUrls,`untested URL`)}.`:e.verdict===`crawl-risk`?`${Z(e.crawl.blockedOrFailed,`sampled URL`)} returned objective HTTP, robots, noindex, canonical, or fetch failures. Fix those sampled URLs first, then rerun before assessing content.`:e.verdict===`content-review`?`Review the sampled pages for distinct utility and intent fit. Literal term coverage and repeated metadata are heuristics, not proof that pages need more words or violate Google's policies.`:e.verdict===`opportunity`?`Retained GSC page rows show search visibility but weak average position, CTR, or entity-fit heuristics. Inspect the top queries and sampled pages before changing this template.`:e.verdict===`healthy`?`No material issue was found in the bounded crawl and URL Inspection samples. Keep monitoring; this is sample evidence, not a guarantee for every URL.`:e.verdict===`inconclusive`?`Retained GSC rows show search visibility, but technical sampling is not strong enough for a health verdict. Add bounded crawl and URL Inspection samples.`:`No retained page-level Search Analytics evidence was found for this discovered template. That is not proof of zero demand or low quality; inspect representative URLs and canonical attribution.`}const OM=`page-demand-stratified-url-v1`;function kM(e){let t=e.queryPageRows.filter(e=>!lM(e)).length,n=e.queryPageRows.filter(lM),r=n.filter(e=>Rv(e.query)).length,i=n.filter(e=>!Rv(e.query)).map(t=>({row:t,isBrand:M_({query:t.query,siteUrl:e.site,brandTerms:e.brandTerms,includeBrand:e.includeBrand})}));return{rows:pM(i.filter(e=>!e.isBrand).map(e=>e.row)),invalid:t,lowActionability:r,brand:i.filter(e=>e.isBrand).length}}function AM(e,t){let n=new Map;for(let r of e){let e=eA(r.page,t);n.set(e,[...n.get(e)??[],r])}return n}function jM(e){let t=nA(e.allUrls,{minUrls:e.dataset.minimumTemplateUrls,minShare:e.dataset.minimumTemplateShare,limit:Math.max(1,e.allUrls.length),sampleSize:0}),n=new Map;for(let r of e.allUrls){let e=eA(r,t);n.set(e,[...n.get(e)??[],r])}let r=AM(e.pageRows,t),i=AM(e.queryRows,t),a=t.map(e=>{let t=r.get(e.signature)??[],a=i.get(e.signature)??[];return{cluster:e,urls:n.get(e.signature)??[],pageRows:t,metrics:vM({pageRows:t,queryRows:a,cluster:e})}}).filter(t=>t.metrics.impressions>=e.dataset.minimumTemplateImpressions).sort((e,t)=>t.metrics.impressions-e.metrics.impressions||t.metrics.clicks-e.metrics.clicks||t.cluster.urlCount-e.cluster.urlCount||oM(e.cluster.signature,t.cluster.signature)),o=new Map((e.dataset.crawlSamples??[]).map(e=>[e.url,e])),s=new Map((e.dataset.inspectionSamples??[]).map(e=>[e.url,e]));return{templates:a.slice(0,e.dataset.templateLimit).map(t=>{let n=Math.max(e.dataset.crawlSamplesPerTemplate,e.dataset.inspectionSamplesPerTemplate),r=SM(t.urls,t.pageRows,n),i=CM(r.slice(0,e.dataset.crawlSamplesPerTemplate).map(e=>o.get(e)).filter(e=>!!e),e.dataset.crawlSamplesPerTemplate),a=wM(r.slice(0,e.dataset.inspectionSamplesPerTemplate).map(e=>s.get(e)).filter(e=>!!e),e.dataset.inspectionSamplesPerTemplate),c={...t.cluster,sampleUrls:r,population:{discoveredUrls:t.cluster.urlCount,gscVisibleUrls:t.pageRows.length,untestedUrls:Math.max(0,t.cluster.urlCount-Math.max(i.attempted,a.attempted)),sampleSelection:OM},metrics:t.metrics,crawl:i,inspection:a,evidence:[],verdict:`inconclusive`,confidence:`low`,recommendation:``},l={...c,...TM(c)};return{...l,evidence:EM(l),recommendation:DM(l)}}),eligibleTemplates:a.length}}function MM(e){let t=kM(e),n=e.pageRows.filter(e=>!uM(e)).length,r=fM(e.pageRows.filter(uM)),i=[...new Set(e.sitemapUrls??[])].filter(sM).sort(oM),a=[...new Set([...i,...r.map(e=>e.page),...t.rows.map(e=>e.page)])].sort(oM),{templates:o,eligibleTemplates:s}=jM({dataset:e,allUrls:a,pageRows:r,queryRows:t.rows}),c=[...new Set(e.warnings??[])],l=e.pageRowsFetched>=e.maxRowsPerRequest,u=e.queryPageRowsFetched>=e.maxRowsPerRequest,d=o.reduce((e,t)=>e+t.crawl.attempted,0),f=o.reduce((e,t)=>e+t.crawl.usable,0),p=o.reduce((e,t)=>e+t.inspection.attempted,0),m=o.reduce((e,t)=>e+t.inspection.succeeded,0),h=e.queryPageRows.length>0||e.pageRows.length>0||i.length>0,g=l||u||t.invalid>0||n>0||c.length>0||d>f||p>m,_=h?a.length?g?`partial`:`complete`:`filtered`:`empty`,v=e.brandTerms?.length?e.brandTerms:A_({siteUrl:e.site});return{schemaVersion:1,methodology:`pseo-audit-v2`,site:e.site,generatedAt:e.generatedAt,rangeDays:e.days,range:e.range,dataStatus:_,source:{searchAnalytics:{pageRows:e.pageRowsFetched,queryPageRows:e.queryPageRowsFetched,maxRowsPerRequest:e.maxRowsPerRequest,pageRowsPossiblyTruncated:l,queryPageRowsPossiblyTruncated:u,dimensions:{page:[`page`],queryPage:[`query`,`page`]},searchType:`web`,dataState:`final`,aggregation:`auto`},sitemaps:{requested:e.sitemapsRequested,discoveredUrls:i.length,maxUrlsPerSitemap:e.maxUrlsPerSitemap}},selection:{inputQueryPageRows:e.queryPageRows.length,invalidQueryPageRows:t.invalid,lowActionabilityRows:t.lowActionability,brandRows:t.brand,retainedQueryPageRows:t.rows.length,inputPageRows:e.pageRows.length,invalidPageRows:n,retainedPageRows:r.length,discoveredUrls:a.length,eligibleTemplates:s,returnedTemplates:o.length,templateLimit:e.templateLimit,minimumTemplateUrls:e.minimumTemplateUrls,minimumTemplateShare:e.minimumTemplateShare,minimumTemplateImpressions:e.minimumTemplateImpressions,templateOrder:`page-impressions-clicks-url-count-signature-v1`},summary:{sitemapUrls:i.length,gscPages:r.length,templates:o.length,clicks:r.reduce((e,t)=>e+t.clicks,0),impressions:r.reduce((e,t)=>e+t.impressions,0),crawlAttempts:d,crawledUrls:f,crawlFailures:d-f,inspectionAttempts:p,inspectedUrls:m,inspectionFailures:p-m},caveats:[...e.caveats??[],`Search Analytics range: ${e.range.startDate} through ${e.range.endDate}, using finalized web data.`,`Clicks, impressions, CTR, and position use retained page-dimension rows; Search Console does not guarantee every row or complete totals.`,`Query patterns and examples use retained query/page rows, which omit anonymized and lower-ranked data.`,`Brand queries: ${e.includeBrand?`included`:`excluded using ${v.join(`, `)||`no terms`}`}.`,`Template detection requires at least ${e.minimumTemplateUrls} URLs and ${(e.minimumTemplateShare*100).toFixed(2)}% of discovered URLs.`,`Samples use ${OM}; observations apply to sampled URLs, not every URL in a template.`,`URL Inspection reports Google's indexed snapshot for a specific URL; an exact PASS verdict does not guarantee current search appearance.`,`Word counts are descriptive only. This report has no preferred or minimum word-count rule.`,`Literal term coverage and path entity fit are review heuristics, not Google ranking factors or scaled-content policy verdicts.`],templates:o,warnings:c}}function NM(e){return{crawl:e.templates.flatMap(e=>e.sampleUrls.slice(0,e.crawl.requested)),inspection:e.templates.flatMap(e=>e.sampleUrls.slice(0,e.inspection.requested))}}const PM=5e4,FM={searchAnalytics:e_,fetchPage:Ag,inspectUrl:qg,fetchSitemapUrls:xS,now:()=>new Date};function IM(e){let t=e.value??e.fallback;if(!Number.isFinite(t)||t<e.minimum||t>e.maximum)throw new A(`INVALID_INPUT`,`${e.label} must be between ${e.minimum} and ${e.maximum}.`);return t}function LM(e){let t=Y({value:e.days,fallback:28,minimum:1,maximum:548,label:`days`}),n=Y({value:e.templateLimit,fallback:25,minimum:1,maximum:100,label:`templateLimit`}),r=Y({value:e.minimumTemplateUrls,fallback:3,minimum:2,maximum:100,label:`minimumTemplateUrls`}),i=Y({value:e.crawlSamples,fallback:0,minimum:0,maximum:10,label:`crawlSamples`}),a=Y({value:e.inspectSamples,fallback:0,minimum:0,maximum:10,label:`inspectSamples`}),o=Y({value:e.maxSitemapUrls,fallback:5e4,minimum:1,maximum:1e5,label:`maxSitemapUrls`}),s=IM({value:e.minimumTemplateImpressions,fallback:0,minimum:0,maximum:1e9,label:`minimumTemplateImpressions`}),c=IM({value:e.minimumTemplateShare,fallback:0,minimum:0,maximum:1,label:`minimumTemplateShare`});if((e.sitemaps?.length??0)>20)throw new A(`INVALID_INPUT`,`sitemaps must contain at most 20 URLs.`);for(let t of e.sitemaps??[])try{if(![`http:`,`https:`].includes(new URL(t).protocol))throw Error()}catch{throw new A(`INVALID_INPUT`,`Invalid sitemap URL: ${t}`)}if(e.brandTerms&&(e.brandTerms.length>20||e.brandTerms.some(e=>!e.trim()||e.length>200)))throw new A(`INVALID_INPUT`,`brandTerms must contain at most 20 non-empty terms of 200 characters or fewer.`);return{...e,days:t,templateLimit:n,minimumTemplateUrls:r,minimumTemplateShare:c,minimumTemplateImpressions:s,crawlSamples:i,inspectSamples:a,maxSitemapUrls:o}}function RM(e,t){let n=new Map;for(let r of e){if(Rv(r.query)||M_({query:r.query,siteUrl:t.site,brandTerms:t.brandTerms,includeBrand:t.includeBrand}))continue;let e=r.impressions+r.clicks*10,i=n.get(r.page);(!i||e>i.score)&&n.set(r.page,{query:r.query,score:e})}return new Map([...n].map(([e,t])=>[e,t.query]))}function zM(e,t){if(e)try{let n=new URL(e,t);return n.hash=``,n.toString()}catch{return}}function BM(e){if(e.fetched.diagnostics.blocked||e.fetched.robotsTxt?.allowed===!1)return`blocked`;if(e.fetched.status<200||e.fetched.status>=300)return`http-error`;if(rm(e).has(`noindex`))return`noindex`;let t=zM(e.canonical,e.fetched.finalUrl),n=zM(e.fetched.finalUrl,e.fetched.finalUrl);return t&&n&&t!==n?`canonical-mismatch`:e.requestedUrl!==e.fetched.finalUrl||(e.fetched.diagnostics.redirectChain?.length??0)>0?`redirected`:`ok`}async function VM(e){try{let t=await e.fetch(e.url,{refresh:e.refresh,js:e.js??`auto`,rate:e.rate}),n=await Th(t),r=n.headings.find(e=>e.level===1)?.text,i=BM({requestedUrl:e.url,fetched:t,canonical:n.canonical,metaRobots:n.metaRobots,xRobotsTag:n.xRobotsTag});return{url:e.url,finalUrl:n.finalUrl,status:t.status,title:n.title,h1:r,metaDescription:n.metaDescription,metaRobots:n.metaRobots,xRobotsTag:n.xRobotsTag,canonical:zM(n.canonical,n.finalUrl),wordCount:n.wordCount,technicalStatus:i,queryCoverage:e.topQuery&&[`ok`,`redirected`].includes(i)?hM({query:e.topQuery,title:n.title,h1:r,body:n.contentText}):void 0,fetchDiagnostics:t.diagnostics,warnings:[...t.warnings,...n.warnings]}}catch(t){let n=t instanceof Error?t.message:String(t);return{url:e.url,technicalStatus:`fetch-error`,warnings:[n],warning:n}}}function HM(e,t){let n=t.inspectionResult?.indexStatusResult;return{url:e,indexStatus:xM(n?.verdict),verdict:n?.verdict,coverageState:n?.coverageState,indexingState:n?.indexingState,robotsTxtState:n?.robotsTxtState,pageFetchState:n?.pageFetchState,lastCrawlTime:n?.lastCrawlTime,userCanonical:n?.userCanonical,googleCanonical:n?.googleCanonical}}async function UM(e){try{return HM(e.url,await e.inspect({siteUrl:e.site,inspectionUrl:e.url}))}catch(t){return{url:e.url,indexStatus:`unknown`,warning:t instanceof Error?t.message:String(t)}}}async function WM(e,t=FM){let n=LM(e),r=t.now(),i=r.toISOString(),a=G_(n.days,r),o=[];e.progress?.(`Fetching sitemap URLs`);let s=(await Promise.all((e.sitemaps??[]).map(e=>t.fetchSitemapUrls({sitemapUrl:e,limit:n.maxSitemapUrls})))).flatMap(e=>(o.push(...e.warnings),e.urls)),c={...a,type:`web`,dataState:`final`,aggregationType:`auto`,maxRows:PM};e.progress?.(`Fetching ${n.days} days of GSC page and query/page rows`);let[l,u]=await Promise.all([t.searchAnalytics(e.site,{...c,dimensions:[`page`]},{refresh:e.refresh}),t.searchAnalytics(e.site,{...c,dimensions:[`query`,`page`]},{refresh:e.refresh})]),d=l.rows.map(e=>({page:e.keys[0]??``,clicks:e.clicks,impressions:e.impressions,position:e.position})),f=u.rows.map(e=>({query:e.keys[0]??``,page:e.keys[1]??``,clicks:e.clicks,impressions:e.impressions,position:e.position})),p=t=>MM({site:e.site,generatedAt:i,range:a,days:n.days,queryPageRows:f,pageRows:d,sitemapUrls:s,crawlSamples:t?.crawl,inspectionSamples:t?.inspection,templateLimit:n.templateLimit,minimumTemplateUrls:n.minimumTemplateUrls,minimumTemplateShare:n.minimumTemplateShare,minimumTemplateImpressions:n.minimumTemplateImpressions,crawlSamplesPerTemplate:n.crawlSamples,inspectionSamplesPerTemplate:n.inspectSamples,maxRowsPerRequest:PM,pageRowsFetched:l.rowsFetched,queryPageRowsFetched:u.rowsFetched,sitemapsRequested:e.sitemaps?.length??0,maxUrlsPerSitemap:n.maxSitemapUrls,brandTerms:e.brandTerms,includeBrand:e.includeBrand,warnings:o,caveats:[`Data freshness: ${e.refresh?`fresh fetch requested; caches bypassed where supported`:`local cache allowed; use --refresh to bypass cached provider and HTTP data`}.`,e.sitemaps?.length?`Sitemaps: ${Z(e.sitemaps.length,`sitemap URL`)} requested.`:`Sitemaps: none provided; template discovery used retained GSC page rows.`]}),m=NM(p()),h=RM(f,e),g=[];for(let[n,r]of m.crawl.entries())e.progress?.(`Crawling pSEO sample ${n+1}/${m.crawl.length}`),g.push(await VM({url:r,topQuery:h.get(r),refresh:e.refresh,js:e.js,rate:e.rate,fetch:t.fetchPage}));let _=[];for(let[n,r]of m.inspection.entries())e.progress?.(`Inspecting pSEO sample ${n+1}/${m.inspection.length}`),_.push(await UM({site:e.site,url:r,inspect:t.inspectUrl}));return e.progress?.(`Scoring pSEO template evidence`),p({crawl:g,inspection:_})}function GM(e){return String(e??`-`).replaceAll(`
705
+ `,` `).replaceAll(`|`,`\\|`)}function KM(e,t=120){let n=GM(e).trim();return n.length<=t?n:`${n.slice(0,t-1)}...`}function qM(e){return e.flatMap(e=>{let t=e.columns.map(e=>GM(e.label)),n=e.columns.map(e=>e.type===`number`?`---:`:`---`);return[`## ${e.title}`,``,`| ${t.join(` | `)} |`,`| ${n.join(` | `)} |`,...e.rows.map(t=>[``,...e.columns.map(e=>KM(t[e.key])),``].join(` | `)),``]})}function JM(e){let t=e.inspection.indexed+e.inspection.notIndexed+e.inspection.unknown+e.inspection.warnings;return t?`${e.inspection.indexed}/${t} indexed`:`not checked`}function YM(e){return e.crawl.attempted?`${e.crawl.usable}/${e.crawl.attempted} usable`:`not crawled`}function XM(e){let t=e.templates.length?[{id:`template_impressions`,title:`Template Impressions`,type:`bar`,tableId:`pseo_templates`,xKey:`rank`,yKey:`impressions`},{id:`template_clicks`,title:`Template Clicks`,type:`bar`,tableId:`pseo_templates`,xKey:`rank`,yKey:`clicks`}]:[];return{tables:[{id:`pseo_summary`,title:`pSEO Summary`,columns:[{key:`metric`,label:`Metric`},{key:`value`,label:`Value`,type:`number`}],rows:[{metric:`templates`,value:e.summary.templates},{metric:`gsc_pages`,value:e.summary.gscPages},{metric:`sitemap_urls`,value:e.summary.sitemapUrls},{metric:`clicks`,value:e.summary.clicks},{metric:`impressions`,value:e.summary.impressions},{metric:`crawled_urls`,value:e.summary.crawledUrls},{metric:`crawl_attempts`,value:e.summary.crawlAttempts},{metric:`inspected_urls`,value:e.summary.inspectedUrls},{metric:`inspection_attempts`,value:e.summary.inspectionAttempts}]},{id:`pseo_templates`,title:`pSEO Templates`,columns:[{key:`rank`,label:`Rank`,type:`number`},{key:`template`,label:`Template`},{key:`verdict`,label:`Verdict`},{key:`confidence`,label:`Confidence`},{key:`urls`,label:`URLs`,type:`number`},{key:`clicks`,label:`Clicks`,type:`number`},{key:`impressions`,label:`Impressions`,type:`number`},{key:`ctr`,label:`CTR`,type:`number`},{key:`position`,label:`Position`,type:`number`},{key:`index`,label:`Index`},{key:`crawl`,label:`Crawl`},{key:`recommendation`,label:`Recommendation`}],rows:e.templates.map((e,t)=>({rank:t+1,template:e.signature,verdict:e.verdict,confidence:e.confidence,urls:e.urlCount,clicks:e.metrics.clicks,impressions:e.metrics.impressions,ctr:Number(e.metrics.ctr.toFixed(3)),position:Number(e.metrics.position.toFixed(1)),index:JM(e),crawl:YM(e),recommendation:e.recommendation}))}],charts:t}}function ZM(e){let t=XM(e),n=[`# pSEO audit: ${e.site}`,``,`Generated: ${e.generatedAt}`,`Window: ${e.rangeDays} days`,``,`${Z(e.summary.templates,`template`)} audited from ${Z(e.summary.gscPages,`GSC page`)}.`,``];return e.caveats.length&&(n.push(`## Report Caveats`,``),n.push(...e.caveats.map(e=>`- ${e}`),``)),n.push(...qM(t.tables)),e.warnings.length&&(n.push(`## Warnings`,``),n.push(...e.warnings.map(e=>`- ${e}`))),n.join(`
706
+ `).trim()}const QM=new Set([`and`,`are`,`best`,`can`,`for`,`from`,`how`,`near`,`the`,`to`,`vs`,`what`,`when`,`where`,`with`,`your`]);function $M(e,t){return e<t?-1:+(e>t)}function eN(e){return B_(e).filter(e=>!QM.has(e))}function tN(e){let t=new Map;for(let n of e){let e=t.get(n.query)??{query:n.query,impressions:0,clicks:0,weightedPosition:0,tokens:new Set,pages:new Map};e.impressions+=n.impressions,e.clicks+=n.clicks,e.weightedPosition+=n.position*n.impressions;for(let t of n.tokens)e.tokens.add(t);let r=e.pages.get(n.page)??{url:n.page,impressions:0,clicks:0};r.impressions+=n.impressions,r.clicks+=n.clicks,r.url&&e.pages.set(r.url,r),t.set(n.query,e)}return[...t.values()].map(e=>({query:e.query,impressions:e.impressions,clicks:e.clicks,position:e.impressions?e.weightedPosition/e.impressions:0,tokens:[...e.tokens].sort(),pages:[...e.pages.values()].sort((e,t)=>t.impressions-e.impressions||$M(e.url,t.url))})).sort((e,t)=>t.impressions-e.impressions||$M(e.query,t.query))}function nN(e){if(e.length<=1)return e.map(e=>[e]);let t=e.map(e=>({...e,tokens:eN(e.query)})),n=new Map;for(let[e,r]of t.entries())for(let t of r.tokens){let r=n.get(t)??[];r.push(e),n.set(t,r)}let r=Math.max(20,Math.min(250,Math.floor(e.length*.08))),i=new Set,a=[],o=t.map((e,t)=>({index:t,impressions:e.impressions,query:e.query})).sort((e,t)=>t.impressions-e.impressions||$M(e.query,t.query));for(let e of o){if(i.has(e.index))continue;let o=t[e.index];if(!o)continue;let s=new Set;for(let t of o.tokens){let a=n.get(t);if(!(!a||a.length>r))for(let t of a)t!==e.index&&!i.has(t)&&s.add(t)}let c=[o];i.add(e.index);let l=[...s].sort((e,n)=>{let r=t[e],i=t[n];return!r||!i?e-n:i.impressions-r.impressions||$M(r.query,i.query)});for(let e of l){let n=t[e];n&&V_(o.tokens,n.tokens)>=.5&&(c.push(n),i.add(e))}a.push(c)}return a}function rN(e){return e.includes(`"`)?`'${e}'`:`"${e}"`}function iN(e,t){let n=new Set(B_(e)),r=t?B_(t):[];return r.length&&r.every(e=>n.has(e))?`navigational`:n.has(`buy`)||n.has(`price`)||n.has(`near`)&&n.has(`me`)?`transactional`:[`vs`,`review`,`best`,`top`,`compare`].some(e=>n.has(e))?`commercial`:[`how`,`what`,`why`,`guide`].some(e=>n.has(e))?`informational`:`mixed`}function aN(e){let t=e.queries.reduce((e,t)=>({impressions:e.impressions+t.impressions,clicks:e.clicks+t.clicks,weightedPosition:e.weightedPosition+t.position*t.impressions}),{impressions:0,clicks:0,weightedPosition:0});return{impressions:t.impressions,clicks:t.clicks,averagePosition:t.impressions?t.weightedPosition/t.impressions:0,ctr:t.impressions?t.clicks/t.impressions:0}}function oN(e){return e.queries<=1||e.impressions<50?0:e.position>10?Number((e.impressions*.03).toFixed(2)):e.estimatedClickLift??0}function sN(e){let t=rN(e.label);if(e.template&&e.template.urlCount>=3&&e.template.share>=.6){let n=e.topPage&&e.template.urlCount>1?` Start with ${e.topPage.url}.`:``,r=e.template.urlCount>1?`This is template-level demand, not a one-off page edit. Tighten the shared title/H1/intro/schema/internal-link rules so each page makes the entity and query angle clear. If ${t} is broader than one entity, route it to a better hub/list page instead of letting several template URLs compete.`:`Improve the shared title, H1, intro, schema, and internal-link pattern, then spot-check the highest-impression URL.`;return`${t} maps mostly to ${e.template.signature} (${e.template.urlCount} URLs). ${r}${n}`}return e.queries===1?e.topPage?`This is a single-query cluster. Inspect ${e.topPage.url} before making a template or hub decision.`:`This is a single-query cluster. Use it as supporting detail, not as a content hub or template decision by itself.`:e.impressions>=500&&e.clicks===0?e.topPage?`This ${t} cluster has search demand but no clicks. Check ${e.topPage.url}; if it is the right target, make the exact intent obvious in the title, H1, intro, and internal links.`:`This ${t} cluster has search demand but no clicks. Check whether the ranking page actually answers the cluster intent, then improve the section/title or create a stronger dedicated page if intent is distinct.`:e.position>10?e.topPage?`This ${t} cluster is mostly outside page one. Strengthen ${e.topPage.url} around this exact intent and add internal links from related pages before treating it as a CTR problem.`:`This ${t} cluster is mostly outside page one. Tighten the page section around the shared intent and add internal links from related pages before treating this as a CTR problem.`:e.clicks/Math.max(1,e.impressions)<e.expectedCtr?`This ${t} cluster ranks below its CTR benchmark. Review SERP framing around the dominant intent (${e.intent}) before expanding content.`:`This ${t} cluster already gets clicks. Use it to refine page structure, FAQs, and internal anchors rather than creating duplicate pages.`}function cN(e){return`${e.queries} queries, ${e.impressions.toFixed(0)} impressions, ${e.clicks.toFixed(0)} clicks, average position ${e.position.toFixed(1)}.`}function lN(e){return[...e].sort((e,t)=>t.impressions-e.impressions||$M(e.query,t.query))[0]?.query??``}function uN(e){let t=new Map;for(let n of e)for(let e of n.pages){let n=t.get(e.url)??{url:e.url,impressions:0,clicks:0};n.impressions+=e.impressions,n.clicks+=e.clicks,t.set(e.url,n)}return[...t.values()].sort((e,t)=>t.impressions-e.impressions||$M(e.url,t.url))}function dN(e){let[t]=nA(e.map(e=>e.url),{minUrls:3,minShare:.6,limit:1});return t?{signature:t.signature,urlCount:t.urlCount,share:t.share,sampleUrls:t.sampleUrls}:void 0}function fN(e){return{expectedCtr:e.ctr,source:e.source,peerRows:e.rows,peerImpressions:e.impressions,qualifiedPeerImpressions:e.qualifiedImpressions,urlSamples:e.urlSamples,positiveUrlSamples:e.positiveUrlSamples}}function pN(e){let t=Number.isFinite(e.value)?e.value??0:e.fallback;return Math.min(e.maximum??1/0,Math.max(1,Math.floor(t)))}function mN(e){let t=pN({value:e.minImpressions,fallback:25}),n=pN({value:e.limit,fallback:25,maximum:100}),r=e.rows.filter(e=>e.position>=1&&e.position<=10&&e.impressions>0&&!!e.pages[0]?.url).map(e=>({keys:[e.query,e.pages[0]?.url??``],clicks:e.clicks,impressions:e.impressions,ctr:e.clicks/e.impressions,position:e.position})),i=new Map(r.map(e=>[e.keys[0]??``,e])),a=Ev(r),o=[],s=e.rows.filter(e=>e.impressions>=t);for(let t of nN(s)){let n=lN(t),r=new Set(t.map(t=>iN(t.query,e.brand))),[s]=r,c=r.size===1&&s?s:`mixed`,l=aN({queries:t}),u=uN(t),d=u.slice(0,5),f=t.map(e=>i.get(e.query)).filter(e=>!!e),p=a.forAggregate({keys:[n,d[0]?.url??``],clicks:l.clicks,impressions:l.impressions,ctr:l.ctr,position:l.averagePosition},f),m=l.averagePosition<=10?Number((Math.max(0,p.ctr-l.ctr)*l.impressions).toFixed(2)):void 0,h=dN(u);o.push({label:n,intent:c,queries:t.map(e=>({query:e.query,impressions:e.impressions,clicks:e.clicks,position:e.position})).sort((e,t)=>t.impressions-e.impressions||$M(e.query,t.query)),topPages:d,template:h,totals:l,benchmark:fN(p),...m===void 0?{}:{estimatedClickLift:m},opportunityScore:oN({queries:t.length,impressions:l.impressions,position:l.averagePosition,estimatedClickLift:m}),summary:cN({label:n,queries:t.length,impressions:l.impressions,clicks:l.clicks,position:l.averagePosition}),recommendation:sN({label:n,intent:c,queries:t.length,impressions:l.impressions,clicks:l.clicks,position:l.averagePosition,expectedCtr:p.ctr,topPage:d[0],template:h})})}let c=o.filter(e=>e.queries.length>=2||(e.totals?.impressions??0)>=t*3).sort((e,t)=>(t.opportunityScore??0)-(e.opportunityScore??0)||(t.totals?.impressions??0)-(e.totals?.impressions??0)||t.queries.length-e.queries.length||$M(e.label,t.label)).slice(0,n);return{clusters:c,totals:c.reduce((e,t)=>({queries:e.queries+t.queries.length,impressions:e.impressions+(t.totals?.impressions??0),clicks:e.clicks+(t.totals?.clicks??0)}),{queries:0,impressions:0,clicks:0}),highOpportunityClusters:c.filter(e=>{let t=e.totals;return!!(t&&(e.opportunityScore??0)>0&&e.queries.length>1&&t.impressions>=100)}).length,minImpressions:t,limit:n}}function hN(e,t,n=`${t}s`){return e===1?t:n}function gN(e){if(!e.clusters.length)return`No query clusters were generated for this scope.`;if(e.highOpportunityClusters>0){let t=e.highOpportunityClusters===1?`has`:`have`;return`${e.highOpportunityClusters} ${hN(e.highOpportunityClusters,`cluster`)} ${t} enough demand and weak enough performance to review first.`}return`No obvious high-opportunity clusters were found. Use this report for content structure and intent mapping rather than urgent fixes.`}async function _N(e){let t=H_(28),n=e.scope?[{groupType:`and`,filters:[{dimension:`page`,operator:`contains`,expression:e.scope}]}]:void 0,{rows:r}=await e_(e.site,{...t,dimensions:[`query`,`page`],type:`web`,dataState:`final`,dimensionFilterGroups:n},{refresh:e.refresh}),{clusters:i,totals:a,highOpportunityClusters:o,minImpressions:s,limit:c}=mN({rows:tN(r.map(e=>({query:e.keys[0]??``,impressions:e.impressions,clicks:e.clicks,position:e.position,tokens:eN(e.keys[0]??``),page:e.keys[1]??``})).filter(t=>t.query&&!Rv(t.query)&&!M_({query:t.query,siteUrl:e.site,brandTerms:e.brandTerms??(e.brand?[e.brand]:void 0),includeBrand:e.includeBrand}))),brand:e.brand,minImpressions:e.minImpressions,limit:e.limit});return{site:e.site,scope:e.scope,range:t,generatedAt:new Date().toISOString(),summary:{clusters:i.length,queries:a.queries,impressions:a.impressions,clicks:a.clicks,highOpportunityClusters:o,minImpressions:s,limit:c,brandFiltering:e.includeBrand?`included`:`excluded`,verdict:gN({clusters:i,highOpportunityClusters:o})},clusters:i,caveats:[`Date window: ${t.startDate} to ${t.endDate} (28 days), using final GSC data where available.`,e.scope?`Scope: only pages containing "${e.scope}" were included.`:`Scope: all pages in the selected GSC property were included.`,e.brand?`Brand filtering: ${e.includeBrand?`brand queries included`:`brand queries excluded using ${e.brandTerms?.length?e.brandTerms.join(`, `):e.brand}`}.`:`Brand intent detection used only query language because no brand term was supplied.`,`Clusters are token-overlap groups. Review the top queries before creating, merging, or deleting pages.`,`Query threshold: at least ${s} impressions per query; low-volume singletons are omitted and output is capped at ${c} clusters.`,`Expected CTR is a heuristic using a leave-cluster-out site benchmark when enough peer URL data exists, otherwise the default position curve. Page-two clusters do not claim CTR-only click lift.`],recommendations:i.filter(e=>e.recommendation).slice(0,5).map(e=>e.recommendation??``)}}function vN(e){let t=Date.parse(`${e.startDate}T00:00:00Z`),n=Date.parse(`${e.endDate}T00:00:00Z`);return Math.floor((n-t)/864e5)+1}function yN(e=new Date){return Xg(e)}function bN(e,t=new Date){if(!/^\d{4}-\d{2}$/.test(e))throw Error(`Pass --month as YYYY-MM.`);let n=new Date(`${e}-01T00:00:00Z`);if(Number.isNaN(n.getTime()))throw Error(`Pass --month as YYYY-MM.`);let r=new Date(n);r.setUTCMonth(r.getUTCMonth()+1),r.setUTCDate(0);let i=r.toISOString().slice(0,10),a=yN(t),o=i<a?i:a,s=n.toISOString().slice(0,10);if(o<s)throw Error(`Final GSC data is only available through ${a}. Choose an earlier month.`);return{startDate:s,endDate:o}}async function xN(e,t={}){let n=t.measure??QO;return Promise.all(e.map(async e=>{try{let r=await n({id:e.id,refresh:t.refresh});return{status:`measured`,dataStatus:r.dataStatus,change:e,measurement:r}}catch(t){let n=Se(t);return{status:`failed`,dataStatus:`unavailable`,change:e,error:{code:n.code,message:n.message,retryable:n.retryable}}}}))}function SN(e){return e.warnings[0]??e.caveats.find(e=>/incomplete|partial|truncated/i.test(e))??`The requested window or source evidence was incomplete; inspect the structured measurement for provenance.`}function CN(e){return e.flatMap(e=>e.status===`failed`?[`Unavailable saved change measurement "${e.change.title}": ${e.error.message.replace(/[.!?]+$/,``)} (${e.error.code}).`]:e.dataStatus===`partial`?[`Partial saved change measurement "${e.change.title}": ${SN(e.measurement).replace(/[.!?]+$/,``)}.`]:[])}function wN(e,t){return e===`complete`&&t.some(e=>e.dataStatus!==`complete`)?`partial`:e}function TN(e){let t=[`# SEO report: ${e.site}`,``,e.headline,``];if(t.push(`Period: ${e.period.startDate} to ${e.period.endDate}`),t.push(``),e.caveats.length){t.push(`## Report Caveats`);for(let n of e.caveats)t.push(`- ${n}`);t.push(``)}for(let n of e.sections){t.push(`## ${n.title}`);for(let e of n.bullets)t.push(`- ${e}`);t.push(``)}t.push(`## Priorities`);for(let n of e.priorities)t.push(`- ${n.title} (${n.confidence}): ${n.action}`);return t.join(`
707
+ `)}function EN(e,t){return e.skippedSections?.find(e=>e.section===t)?.reason}function DN(e,t,n){return EN(e,t)?`${n} unavailable`:void 0}function ON(e){let t=(e,t)=>{let n=new Map;for(let e of t){let t=n.get(e.reason)??[];t.push(e.section),n.set(e.reason,t)}return[...n.entries()].map(([t,n])=>n.length===1?`${e} ${n[0]}: ${t.replace(/[.!?]+$/,``)}.`:`${e} ${n.length} sections: ${t.replace(/[.!?]+$/,``)}.`)};return[...t(`Skipped`,e.skippedSections??[]),...t(`Partial`,e.partialReasons??[])]}function kN(e){if(EN(e,`traffic anomaly`))return`Traffic anomaly detection was not available for this run.`;let t=e.anomaly.anomalies.find(e=>e.significant);if(!t)return`No significant traffic anomaly signal was detected.`;let n=t.direction===`spike`?`spiked`:t.direction===`drop`?`dropped`:t.direction;return`Average daily ${t.metric} ${n} from ${t.baselineMean.toFixed(1)} to ${t.comparisonMean.toFixed(1)}.`}function AN(e,t,n=`${t}s`){return e===0?`no ${n}`:`${e} ${VN(e,t,n)}`}function jN(e){let t=[DN(e,`traffic anomaly`,`traffic anomaly analysis`)??AN(e.summary.significantAnomalies,`significant anomaly signal`),DN(e,`decay analysis`,`content decay analysis`)??AN(e.summary.decayItems,`observed retained query/page decline`),DN(e,`striking-distance opportunities`,`striking-distance analysis`)??AN(e.summary.strikingDistanceItems,`striking-distance opportunity`,`striking-distance opportunities`),DN(e,`quick-win opportunities`,`quick-win analysis`)??AN(e.summary.quickWinItems,`quick-win candidate`)];return(e.summary.cannibalItems||EN(e,`cannibalisation analysis`))&&t.push(DN(e,`cannibalisation analysis`,`multi-URL query analysis`)??AN(e.summary.cannibalItems,`multi-URL query candidate`)),`${e.dataStatus===`partial`?`Partial diagnosis; `:e.dataStatus===`unavailable`?`Diagnosis unavailable; `:``}${t.join(`; `)}.`}function MN(e){if(EN(e,`page movement segments`))return`Page movement comparison was not available for this run.`;let t=e.segments.page;if(t.dataStatus===`empty`)return`Search Console returned no retained page rows for either movement window.`;if(t.dataStatus===`unavailable`)return`No page was retained in both movement windows, so page movement could not be compared.`;let n=t.items[0];if(!n)return`No matched retained page movement stood out.`;let r=n.clickDelta<0?`lost`:`gained`;return`${n.key} ${r} ${Math.abs(n.clickDelta)} clicks compared with the previous window.`}function NN(e){if(e.summary.updateAttributionStatus===`unavailable`)return`Update correlation was not available for this run.`;let t=e.updateCorrelation.overlappingUpdates.length,n=e.anomaly.anomalies.some(e=>e.significant);return t?n?`${Z(t,`official Google update window`)} overlapped the significant movement comparison window. This timing does not establish that the update caused the movement.`:`${Z(t,`official Google update window`)} overlapped the comparison window, but no significant movement was detected.`:`No official Google update window overlapped the comparison window.`}function PN(e){let t=e.dataStatus===`partial`?`partial; `:``;if(e.delta.clicks===null)return`${e.change.title}: ${t}${e.verdict}; comparable click movement is unavailable.`;let n=e.delta.clickPct===null?``:` (${e.delta.clickPct}%)`;return`${e.change.title}: ${t}${e.verdict}, ${e.delta.clicks} clicks${n}.`}function FN(e){return e.status===`failed`?`${e.change.title}: measurement unavailable (${e.error.code}); ${e.error.message.replace(/[.!?]+$/,``)}.`:PN(e.measurement)}function IN(e){let t=[],n=e.monitoring.crawlRuns[0],r=e.monitoring.linkRecover;n?(t.push(`Latest crawl checked ${Z(n.urlCount,`URL`)} with ${Z(n.statusErrors,`status error`)}, ${Z(n.nonIndexable,`non-indexable page`)}, and ${Z(n.highPriorityRecommendations,`high-priority crawl action`)}.`),n.topRecommendation&&t.push(`Top crawl action: ${n.topRecommendation.url}. ${n.topRecommendation.action}`),(n.statusErrors||n.nonIndexable)&&!r&&t.push(`Run link-recover before content work if those URLs have GSC search value; recoverable 4xx, noindex, and canonical issues can look like content decay.`)):t.push(`No crawl-diff run is saved yet.`);let i=e.monitoring.indexWatch;return i.inspectedUrls?(t.push(`Index watch tracks ${Z(i.inspectedUrls,`URL`)}; ${Z(i.currentIssues,`current indexing review`)} and ${Z(i.failed,`failed inspection`)}.`),(i.currentIssues||i.failed)&&t.push(`Index watch currently has ${Z(i.blocked,`exact blocked state`)}; inspect stable verdict, robots, indexing, fetch, and canonical evidence before changing copy.`)):t.push(`No index-watch snapshot is saved yet.`),r?(t.push(`Latest link-recover checked ${Z(r.checked,`search-value URL`)}; ${Z(r.recoverable,`recoverable issue`)}, ${r.high} high severity, ${r.clicksAtRisk.toFixed(0)} clicks at risk.`),r.topUrl&&r.topAction&&t.push(`Top recovery target: ${r.topUrl}. ${r.topAction}`),r.repeatedUrls&&t.push(`${Z(r.repeatedUrls,`recoverable URL`)} appeared in multiple recent link-recover runs${r.repeatedTopUrl?`; first repeat: ${r.repeatedTopUrl}`:``}.`)):t.push(`No link-recover run is saved yet.`),t}function LN(e){let t=e.quickWins.items.map(e=>e.contentVerification?.fetchDiagnostics).filter(e=>e!==void 0);if(!t.length)return null;let n=t.filter(e=>e.blocked).length,r=t.filter(e=>e.rendered).length;return`Verified-page fetch status: ${t.filter(e=>e.cache===`hit`).length} cached, ${t.filter(e=>e.fetched&&e.cache!==`hit`&&!e.rendered).length} fetched, ${r} rendered, ${n} blocked.`}function RN(e){let t=e.quickWins.items.filter(e=>e.contentVerification?.classification===`content-gap`).length,n=e.quickWins.items.filter(e=>e.contentVerification?.classification===`serp-framing`).length;return`${t} ${t===1?`shows`:`show`} true content gaps; ${n} look more like SERP/title/H1 framing gaps.`}function zN(e){let t=e.quickWins.templates[0];return!t||t.urlCount<2?`No dominant opportunity template stood out.`:t.id===`other`?`${t.urlCount} quick-win URLs sit outside a recognised reusable template. Triage them individually, then create a content group only if they share real page structure.`:`${t.urlCount} distinct quick-win URLs sit in the ${t.label} template. Compare their technical and page evidence before choosing a shared change.`}function BN(e){return e===`lost_ctr`?`CTR dropped while rankings held`:e===`lost_position`?`average position worsened`:e===`lost_impressions`?`impressions dropped`:e===`lost_clicks`?`clicks dropped without one dominant signal`:e.replaceAll(`_`,` `)}function VN(e,t,n=`${t}s`){return e===1?t:n}function HN(e){let t=e.decay.groups[0];if(!t)return`No material decay cluster stood out.`;let n=BN(t.diagnosis),r=t.sampleQueries.slice(0,2).join(`; `);return`Top decay cluster: ${t.count} ${VN(t.count,`finding`)} in the ${t.template.label} template (${n}), ${t.totalClickLoss.toFixed(0)} fewer observed clicks across rows retained in both windows. Action: ${t.recommendation}${r?` Example queries: ${r}.`:``}`}function UN(e){let t=e.cannibalization.selection.suppressedQueries;return t?`${t} branded multi-URL query candidates were excluded (${Object.entries(e.cannibalization.suppressionSummary).map(([e,t])=>`${t} ${e.replace(/_/g,` `)}`).join(`, `)}).`:`No branded multi-URL query candidates were excluded.`}function WN(e){let t=[],n=[[`decay analysis`,`content decay`],[`cannibalisation analysis`,`multi-URL query`],[`striking-distance opportunities`,`striking-distance`],[`quick-win opportunities`,`quick-win`]].filter(([t])=>EN(e,t)).map(([,e])=>e);if(n.length&&t.push(`Could not assess ${n.join(`, `)} opportunities in this run; see report caveats.`),e.summary.decayItems){let n=e.decay.items[0];t.push(n?`${e.summary.decayItems} observed retained query/page ${VN(e.summary.decayItems,`decline`)} need review. Start with "${n.query}" on ${n.url}; it had ${n.clickLoss.toFixed(0)} fewer clicks.`:`${e.summary.decayItems} observed retained query/page ${VN(e.summary.decayItems,`decline`)} need review.`),t.push(HN(e))}if(e.summary.cannibalItems){let n=e.cannibalization.items[0];t.push(n?`${e.summary.cannibalItems} multi-URL query ${VN(e.summary.cannibalItems,`candidate`)} need intent and technical review. Start with "${n.query}" and decide whether ${n.pages.length} ranking URLs answer the same intent.`:`${e.summary.cannibalItems} multi-URL query ${VN(e.summary.cannibalItems,`candidate`)} need intent and technical review.`)}if(e.cannibalization.selection.suppressedQueries&&t.push(UN(e)),e.summary.strikingDistanceItems){let n=e.strikingDistance.items[0];t.push(n?`${e.summary.strikingDistanceItems} query/page ${VN(e.summary.strikingDistanceItems,`candidate`)} have an average GSC position above 10 and at most 20. Start by investigating "${n.query}" on ${n.url}; it averages position ${n.position.toFixed(1)} with ${n.impressions.toFixed(0)} impressions.`:`${e.summary.strikingDistanceItems} query/page ${VN(e.summary.strikingDistanceItems,`candidate`)} have an average GSC position above 10 and at most 20.`)}if(!EN(e,`quick-win opportunities`)&&e.summary.quickWinItems){let n=e.quickWins.items[0];t.push(n?`${e.summary.quickWinItems} retained query/page ${VN(e.summary.quickWinItems,`candidate`)} fell below a site-peer or versioned heuristic CTR target. Start with "${n.query}" on ${n.url}; it had ${n.impressions.toFixed(0)} impressions at average position ${n.position.toFixed(1)}.`:`${e.summary.quickWinItems} CTR-target ${VN(e.summary.quickWinItems,`candidate`)} need review.`)}let r=zN(e);if(r.startsWith(`No dominant`)||t.push(r),e.quickWins.verification.requested){t.push(`Verified content for ${e.quickWins.verification.verified} of ${e.quickWins.items.length} quick-win candidates; ${RN(e)}`);let n=LN(e);n&&t.push(n)}return t.length||t.push(`No material content opportunity stood out in this window. Keep monitoring, but do not force content edits from this report alone.`),t}function GN(e){return[`Date window: ${e.period.startDate} to ${e.period.endDate}.`,`Brand queries: ${e.includeBrand?`included`:e.brandTerms?.length?`excluded where saved brand terms matched`:`no saved brand terms, so no brand filter was applied`}.`,`Data freshness: ${e.refresh?`fresh fetch requested; local cache bypassed where supported`:`local cache allowed; rerun with --refresh to bypass cached GSC/HTTP data`}.`,e.skipSearchData?"Search Console: skipped because no property was selected. Run `seo start` to add performance evidence.":`Search Console: included where the selected property returned usable data.`,`GA4: not included in this narrative; use refresh-priorities when GA4 value should influence prioritisation.`,e.skipSearchData?`Content verification: not run because Search Console did not provide page candidates for this report.`:e.verifyContent?`Content verification: checked ${e.verified} of ${Z(e.quickWinCount,`quick-win candidate`)}, limit ${e.verifyLimit??3}.`:`Content verification: not run; recommendations are based on GSC/query data unless stated otherwise.`]}async function KN(e){let t=e.days??90;if(e.startDate&&!e.endDate||!e.startDate&&e.endDate)throw Error(`Pass both startDate and endDate, or neither.`);let n=e.startDate&&e.endDate?{startDate:e.startDate,endDate:e.endDate}:void 0;e.progress?.(`Running diagnosis primitives`);let r=await bO({site:e.site,days:n?vN(n):t,recentDays:e.recentDays,startDate:n?.startDate,endDate:n?.endDate,limit:e.limit,brandTerms:e.brandTerms,includeBrand:e.includeBrand,verifyContent:e.verifyContent,verifyLimit:e.verifyLimit,js:e.js,rate:e.rate,refresh:e.refresh,skipSearchData:e.skipSearchData,progress:e.progress});e.progress?.(`Measuring saved changes`);let i=await xN(zD({site:e.site,limit:e.changeLimit??5}),{refresh:e.refresh}),a=i.flatMap(e=>e.status===`measured`?[e.measurement]:[]),o={crawlRuns:Hk(e.site),indexWatch:ZA(e.site),linkRecover:aj(e.site)};e.progress?.(`Rendering narrative`);let s=n??{startDate:r.anomaly.anomalies[0]?.baselineStart??r.quickWins.range.startDate,endDate:r.anomaly.anomalies[0]?.comparisonEnd??r.quickWins.range.endDate},c={site:e.site,generatedAt:new Date().toISOString(),dataStatus:wN(r.dataStatus,i),periodDays:n?vN(n):t,period:s,headline:jN(r),caveats:[...GN({period:s,brandTerms:e.brandTerms,includeBrand:e.includeBrand,refresh:e.refresh,verifyContent:e.verifyContent,verifyLimit:e.verifyLimit,verified:r.quickWins.verification.verified,quickWinCount:r.quickWins.items.length,skipSearchData:e.skipSearchData}),...ON(r),...CN(i)],sections:[{title:`Performance`,bullets:[kN(r),MN(r),NN(r)]},{title:`Content Opportunities`,bullets:WN(r)},{title:`Change Measurements`,bullets:i.length?i.map(FN):[`No measured changes are saved for this property yet.`]},{title:`Technical Monitoring`,bullets:IN({monitoring:o})}],priorities:r.priorities.map(e=>({title:e.label,confidence:e.confidence,action:e.action})),diagnosis:r,changeMeasurements:a,changeMeasurementAttempts:i,monitoring:o};return{...c,markdown:TN(c)}}async function qN(e){let t=e.month??yN().slice(0,7),n=bN(t);e.progress?.(`Preparing monthly period ${t}`);let r=await KN({site:e.site,startDate:n.startDate,endDate:n.endDate,recentDays:Math.min(14,Math.max(3,vN(n))),limit:e.limit,brandTerms:e.brandTerms,includeBrand:e.includeBrand,verifyContent:e.verifyContent,verifyLimit:e.verifyLimit,js:e.js,rate:e.rate,refresh:e.refresh,progress:e.progress});return{...r,month:t,markdown:r.markdown.replace(`# SEO report: ${e.site}`,`# Monthly SEO report (${t}): ${e.site}`)}}function JN(e){return{tables:[{id:`report_caveats`,title:`Report Caveats`,columns:[{key:`caveat`,label:`Caveat`}],rows:e.caveats.map(e=>({caveat:e}))},{id:`report_sections`,title:`Report Sections`,columns:[{key:`section`,label:`Section`},{key:`finding`,label:`Finding`}],rows:e.sections.flatMap(e=>e.bullets.map(t=>({section:e.title,finding:t})))},{id:`report_priorities`,title:`Report Priorities`,columns:[{key:`priority`,label:`Priority`,type:`number`},{key:`title`,label:`Title`},{key:`confidence`,label:`Confidence`},{key:`action`,label:`Action`}],rows:e.priorities.map((e,t)=>({priority:t+1,title:e.title,confidence:e.confidence,action:e.action}))}],charts:[]}}function YN(e,t,n,r){return e===void 0||!Number.isFinite(e)?t:Math.min(r,Math.max(n,Math.floor(e)))}function XN(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 ZN(e){try{let t=new URL(e);return t.protocol!==`http:`&&t.protocol!==`https:`?void 0:(t.hash=``,t.toString())}catch{return}}function QN(e,t,n){return!!(t&&n)&&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 $N(e){return{query:e.keys[0]?.trim()??``,clicks:Number(e.clicks.toFixed(3)),impressions:Number(e.impressions.toFixed(3)),ctr:Number(e.ctr.toFixed(4)),position:Number(e.position.toFixed(2))}}function eP(e,t){return t.impressions-e.impressions||e.position-t.position||t.clicks-e.clicks||XN(e.query,t.query)}function tP(e){return{type:`investigate-ranking`,confidence:`low`,evidence:`GSC reports ${e.impressions} eligible impressions at an impression-weighted average position of ${e.position.toFixed(2)} for ${e.url}.`,action:`Inspect indexability, canonical state, query coverage, competing URLs, and relevant internal links for ${e.url} before choosing a change. Start with "${e.primaryQuery}" as the highest-impression query in this page group.`}}function nP(e,t){let n=t.map($N).sort(eP),r=n.reduce((e,t)=>e+t.impressions,0),i=n.reduce((e,t)=>e+t.clicks,0),a=n.reduce((e,t)=>e+t.position*t.impressions,0)/r,o=Math.min(1,Math.max(.1,(21-a)/10)),s=n[0]?.query??``,c=tP({url:e,primaryQuery:s,impressions:r,position:a});return{url:e,primaryQuery:s,template:QT(e),queries:n,queryCount:n.length,clicks:Number(i.toFixed(3)),impressions:Number(r.toFixed(3)),ctr:Number((i/r).toFixed(4)),position:Number(a.toFixed(2)),priority:{method:`impressions_x_position_proximity`,score:Number((r*o).toFixed(2)),demandImpressions:Number(r.toFixed(3)),positionProximity:Number(o.toFixed(4)),heuristic:!0,estimatedClickLift:!1},finding:`unverified`,recommendation:c}}function rP(e,t){return t.priority.score-e.priority.score||t.impressions-e.impressions||e.position-t.position||XN(e.url,t.url)}function iP(e){let t=YN(e.minImpressions,50,0,1e9),n=YN(e.limit,10,1,100),r={sourceRows:e.rows.length,invalidRows:0,outsidePositionRows:0,brandRows:0,eligibleRows:0,sourcePages:0,belowMinimumPages:0,eligiblePages:0,returnedPages:0,limitedPages:0},i=new Map;for(let t of e.rows){let n=t.keys[0]?.trim()??``,a=ZN(t.keys[1]?.trim()??``);if(!QN(t,n,a))r.invalidRows++;else if(t.position<=10||t.position>20)r.outsidePositionRows++;else if(M_({query:n,siteUrl:e.site,brandTerms:e.brandTerms,includeBrand:e.includeBrand}))r.brandRows++;else{let e=i.get(a??``)??[];e.push({...t,keys:[n,a??``]}),i.set(a??``,e),r.eligibleRows++}}r.sourcePages=i.size;let a=[...i.entries()].map(([e,t])=>nP(e,t)).filter(e=>e.impressions>=t?!0:(r.belowMinimumPages++,!1)).sort(rP),o=a.slice(0,n);r.eligiblePages=a.length,r.returnedPages=o.length,r.limitedPages=a.length-o.length;let s=a.reduce((e,t)=>e+t.clicks,0),c=a.reduce((e,t)=>e+t.impressions,0),l=o.reduce((e,t)=>e+t.clicks,0),u=o.reduce((e,t)=>e+t.impressions,0);return{site:e.site,minImpressions:t,limit:n,dataStatus:e.rows.length===0?`empty`:a.length===0?`filtered`:`available`,selection:r,methodology:{id:`gsc_second_page_v2`,source:`google_search_console_query_page_rows`,position:{metric:`gsc_average_position`,minimumExclusive:10,maximumInclusive:20,appliedAt:`query_page_row`},aggregation:{unit:`page`,minimumImpressionsAppliedAt:`eligible_page_aggregate`,ctr:`sum_clicks_divided_by_sum_impressions`,position:`impression_weighted_query_page_position`,queries:`all_eligible_queries_retained`},priority:{method:`impressions_x_position_proximity`,formula:`page impressions * clamp((21 - weighted position) / 10, 0.1, 1)`,heuristic:!0,estimatedClickLift:!1}},provenance:{inputScope:`provided_rows`,selectionOrder:[`valid_row`,`position`,`brand`,`page_aggregation`,`minimum_page_impressions`,`priority`,`limit`],selection:r},summary:{eligiblePages:a.length,returnedPages:o.length,eligibleTemplates:new Set(a.map(e=>e.template.id)).size,returnedTemplates:new Set(o.map(e=>e.template.id)).size,eligibleQueries:a.reduce((e,t)=>e+t.queryCount,0),returnedQueries:o.reduce((e,t)=>e+t.queryCount,0),eligibleClicks:Number(s.toFixed(3)),eligibleImpressions:Number(c.toFixed(3)),returnedClicks:Number(l.toFixed(3)),returnedImpressions:Number(u.toFixed(3))},items:o}}const aP=1e5,oP={searchAnalytics:e_,fetch:Ag,extract:Th,now:()=>new Date};function sP(e){if(e.value===void 0)return e.fallback;if(!Number.isInteger(e.value)||e.value<e.minimum||e.value>e.maximum)throw new A(`INVALID_INPUT`,`${e.label} must be a whole number between ${e.minimum} and ${e.maximum}.`);return e.value}function cP(e){return[...new Set(e.filter(Boolean))]}function lP(e){return[...new Map(e.map(e=>[`${e.stage}\u0000${e.url}\u0000${e.code}\u0000${e.message}`,e])).values()]}function uP(e,t){return Zg(e,t)}function dP(e){let t=e.error??e.summary;return e.status===`failed`?{type:`inspect-fetch`,confidence:`medium`,evidence:t,action:gv(e)}:e.classification===`technical-check`?{type:`fix-technical`,confidence:`medium`,evidence:t,action:gv(e)}:e.classification===`content-gap`?{type:`fix-content-gap`,confidence:`medium`,evidence:t,action:gv(e)}:e.classification===`serp-framing`?{type:`improve-serp-framing`,confidence:`medium`,evidence:t,action:gv(e)}:{type:`investigate-ranking`,confidence:`low`,evidence:t,action:`The page covers "${e.query}". Inspect internal links, competing URLs, intent alignment, and the search results before changing copy.`}}function fP(e,t){let n=dP(t);e.contentVerification=t,e.fetchDiagnostics=t.fetchDiagnostics,e.finding=n.type,e.recommendation=n}function pP(e){return e.dataStatus===`empty`?`Google Search Console returned no retained query/page rows for this window.`:e.dataStatus===`filtered`?`No pages met the position, brand, and minimum-impression criteria.`:e.technical>0?`${e.eligible} eligible average-position pages found; ${e.technical} returned pages have verified technical issues to fix before content changes.`:e.failed>0?`${e.eligible} eligible average-position pages found; ${e.failed} returned pages could not be verified and need a fetch check before content changes.`:e.content>0?`${e.eligible} eligible average-position pages found; ${e.content} returned pages have verified content or search-framing gaps.`:`${e.eligible} eligible average-position pages found; ${e.returned} are returned in priority order for investigation.`}function mP(e){let t=e[0];return t?cP([t.recommendation.action,`Review page-level query groups before creating another URL; several queries may already map to the same ranking page.`,`Treat the priority score as a triage heuristic, not an estimated traffic lift.`]):[`Widen the date window or lower the minimum page impressions only if you need to inspect smaller opportunities.`]}async function hP(e,t=oP){let n=sP({value:e.range,fallback:28,minimum:1,maximum:548,label:`Days`}),r=sP({value:e.verifyLimit,fallback:5,minimum:0,maximum:100,label:`Verification limit`}),i=t.now(),a=uP(n,i),o=await t.searchAnalytics(e.site,{...a,dimensions:[`query`,`page`],type:`web`,dataState:`final`,maxRows:aP},{refresh:e.refresh}),s=new qT;s.addGsc(o.calls,o.rowsFetched);let c=iP({rows:o.rows,site:e.site,minImpressions:e.minImpressions,limit:e.limit,brandTerms:e.brandTerms,includeBrand:e.includeBrand}),l=i.toISOString(),u=[],d=e.verifyContent===!0||e.verifyLimit!==void 0,f=d?c.items.slice(0,r):[];for(let n of f){let r=await bv({query:n.primaryQuery,url:n.url,js:e.js,refresh:e.refresh,rate:e.rate,verifiedAt:l,fetch:t.fetch,extract:t.extract});fP(n,r),u.push(...(r.warnings??[]).map(e=>({stage:`verification`,url:n.url,code:`page-warning`,message:e}))),r.error&&u.push({stage:`verification`,url:n.url,code:`verification-failed`,message:r.error})}let p=f.filter(e=>e.contentVerification?.status===`verified`).length,m=f.filter(e=>e.contentVerification?.status===`failed`).length,h=f.filter(e=>e.finding===`fix-technical`).length,g=f.filter(e=>[`fix-content-gap`,`improve-serp-framing`].includes(e.finding)).length;return{site:e.site,range:n,dateRange:a,generatedAt:l,source:{provider:`google-search-console`,dimensions:[`query`,`page`],searchType:`web`,dataState:`final`,rowsFetched:o.rowsFetched,calls:o.calls,maxRows:aP,possiblyTruncated:o.rowsFetched>=aP,completeness:`retained-query-rows-only`},dataStatus:c.dataStatus,selection:c.selection,methodology:c.methodology,provenance:{...c.provenance,verification:{optional:!0,population:`returned_pages_in_priority_order`}},summary:{...c.summary,contentIssues:g,technicalIssues:h,fetchFailures:m,brandFiltering:e.includeBrand?`included`:`excluded`,verdict:pP({dataStatus:c.dataStatus,eligible:c.summary.eligiblePages,returned:c.summary.returnedPages,technical:h,failed:m,content:g})},verification:d?{requested:!0,limit:r,attempted:f.length,verified:p,failed:m,technicalChecks:h}:{requested:!1,attempted:0,verified:0,failed:0},items:c.items,caveats:[`Date window: ${a.startDate} to ${a.endDate}, using final GSC data where available.`,`Selection: ${c.selection.sourceRows} source rows, ${c.selection.eligibleRows} eligible query/page rows, ${c.selection.eligiblePages} eligible page groups, ${c.selection.returnedPages} returned.`,`Positions use GSC average position per query/page row and must be greater than 10 and at most 20. Page position is impression-weighted across retained queries.`,`Minimum impressions (${c.minImpressions}) are applied after eligible rows are aggregated by page.`,d?`Content verification was attempted for the first ${f.length} returned pages in priority order.`:`Content verification was not requested; unverified items are investigation prompts, not content findings.`,`Priority is a deterministic demand-and-position heuristic. It is not an estimated click or ranking lift.`,`Search Analytics exposes retained query rows only. Anonymized and lower-value queries can be absent even when pagination is exhausted.`,o.rowsFetched>=aP?`The report reached its ${aP.toLocaleString(`en-US`)}-row safety cap.`:``].filter(Boolean),recommendations:mP(c.items),ledgerSummary:s.summary(),warnings:lP(u)}}const gP=/^(?:how|what|why|when|where|who|which|can|could|do|does|is|are|should|would)\b/i,_P=/^(?:(?:how\s+(?:many|much)|what|which|who|why|when|where)\s+(?:do|does|did|is|are|can|could|should|would)?|how\s+to|can|could|do|does|did|is|are|should|would)\s+/i,vP=/^how\s+(?:(?:do|can|should|would)\s+(?:i|we|you|someone)\s+|to\s+)(?:choose|find|pick|use|compare)\s+/i,yP=/^(?:can|could|do|does|did|should|would)\s+(?:i|we|you|someone|they|he|she)\s+(?:use|choose|buy|get|try|consider|pick|find|hire)\s+/i,bP=/\b(?:best|vs|versus|reviews?|worth|alternatives?)\b/,xP=/\b(?:price|cost|salary|rate|fee|fees)\b/,SP=/\b(?:review|reviews|worth)\b/;function CP(e){return e.normalize(`NFC`).replace(/\s+/g,` `).trim()}function wP(e){let t=CP(e);return t.replace(vP,``).replace(yP,``).replace(_P,``).replace(/^(?:the\s+)?(?:best|top\s+\d+)\s+/i,``).replace(/^(?:reviews?)(?:\s+(?:of|for))?\s+/i,``).replace(/\s+(?:reviews?|worth\s+it)$/i,``).replace(/[?.!]+$/,``).trim()||t.replace(/[?.!]+$/,``)}function TP(e){let t=CP(e).replace(/[?.!]+$/,``).split(/\s+(?:vs\.?|versus)\s+/i).map(e=>e.trim());return t.length===2&&t[0]&&t[1]?[t[0],t[1]]:void 0}function EP(e){let t=CP(e);if(!t)return[];let n=R_(t),r=wP(t),i=TP(t),a=new Set;return gP.test(t)&&a.add(`${t.replace(/[?.!]+$/,``)}?`),a.add(`What should someone know about ${r}?`),a.add(`Explain ${r}, including key facts and caveats.`),/\bbest\b/.test(n)&&a.add(`Which ${r} options are strongest for different needs, and why?`),i&&a.add(`Compare ${i[0]} and ${i[1]}, including tradeoffs and use-case fit.`),xP.test(n)&&a.add(`What factors determine ${r}?`),SP.test(n)&&a.add(`Is ${r} worth considering? Include evidence, pros, cons, and alternatives.`),a.add(bP.test(n)?`What criteria should someone use to evaluate ${r}?`:`What evidence or data supports claims about ${r}?`),[...a].slice(0,5)}function DP(e){return e.sourceRows===0?`empty`:e.partial?`partial`:e.eligibleRows===0?`filtered`:`available`}async function OP(e,t=ev){let n=await tv(e,t),r=n.rows.slice(0,n.filters.limit).map(e=>({...e,evidenceScope:`retained-gsc-query`,prompts:EP(e.query)})),i=n.source.possiblyTruncated||n.selection.invalidRows>0||n.selection.conflictingRows>0,a=DP({sourceRows:n.selection.sourceRows,eligibleRows:n.selection.eligibleRows,partial:i}),o=a===`empty`?`GSC returned no retained query rows for this range.`:a===`partial`&&r.length===0?`No prompt candidates survived the retained rows and filters, but incomplete source evidence makes that negative inconclusive.`:a===`partial`?`Partial evidence: ${r.length} retained GSC queries were converted into prompt suggestions, but incomplete source rows may omit other queries.`:a===`filtered`?`No retained ${e.includeBrand?``:`non-brand `}queries met the minimum-impression threshold.`:`${r.length} retained GSC queries were converted into deterministic monitoring-prompt suggestions; these are not observed AI prompts.`;return{schemaVersion:2,site:n.site,generatedAt:n.generatedAt,rangeDays:n.rangeDays,dateRange:n.dateRange,dataStatus:a,source:n.source,filters:{...n.filters,brandFiltering:e.includeBrand?`included`:`excluded`},methodology:{id:`gsc_query_to_ai_monitoring_prompt`,version:2,sourceUnit:`retained-query-row`,promptType:`deterministic-template-heuristic`,observedAiPromptData:!1,estimatedTrafficLift:!1},selection:{...n.selection,returnedRows:r.length,limitedRows:n.selection.eligibleRows-r.length},summary:{eligibleQueries:n.selection.eligibleRows,returnedQueries:r.length,prompts:r.reduce((e,t)=>e+t.prompts.length,0),verdict:o},items:r,warnings:n.warnings,caveats:[...n.caveats,`Generated prompts are deterministic monitoring suggestions derived from GSC query wording, not evidence of demand in any AI product.`]}}const kP={decay:1.25,"quick-win":1.15,"striking-distance":1,cannibalization:.9,diagnosis:.75,template:1.35,crawl:1.3},AP={high:1.2,medium:1,low:.7},jP={S:1.15,M:1,L:.8},MP={"technical-check":1.25,"content-gap":1.2,"serp-framing":1.1,covered:.85,"fetch-failed":.7};function NP(e){return!Number.isFinite(e)||e<=0?0:Math.min(100,Math.log1p(e)*12)}function PP(e=1){return e<=1?1:Math.min(1.6,1+Math.log1p(e-1)/8)}function FP(e=0){return e<=0?1:Math.min(1.35,1+Math.log1p(e)/30)}function IP(e,t){return t===`technical-check`?`technical`:t===`content-gap`?`content`:t===`serp-framing`||t===`covered`?`serp`:e===`cannibalization`?`authority`:e===`template`||e===`diagnosis`?`strategy`:e===`crawl`?`technical`:e===`decay`?`content`:`serp`}function LP(e){let t=NP(e.impact),n=kP[e.source],r=AP[e.confidence],i=jP[e.effort??`M`],a=e.verification?MP[e.verification]:1,o=PP(e.templateCount),s=FP(e.analyticsSessions),c=t*n*r*i*a*o;return{impact:Number(t.toFixed(2)),source:Number(n.toFixed(2)),confidence:Number(r.toFixed(2)),effort:Number(i.toFixed(2)),verification:Number(a.toFixed(2)),template:Number(o.toFixed(2)),analytics:Number(s.toFixed(2)),final:Number(c.toFixed(2))}}function RP(e){return{workflow:e.workflow,site:e.site,generatedAt:new Date().toISOString(),summary:e.summary,steps:e.steps,actions:e.actions,output:e.output}}const zP={canonical:`technical`,content:`content`,geo:`content`,headings:`content`,images:`content`,indexability:`technical`,international:`technical`,links:`technical`,metadata:`content`,mobile:`technical`,performance:`technical`,response:`technical`,security:`technical`,social:`content`,"structured-data":`technical`},BP={high:100,medium:50,low:20};function VP(e){return e.severity===`high`||e.scoreFactors.searchVisibleUrls>0||e.scoreFactors.sessions>0||e.scoreFactors.conversions>0?`high`:e.severity===`medium`?`medium`:`low`}function HP(e){return e.scoreFactors.effort===`low`?`S`:e.scoreFactors.effort===`high`?`L`:`M`}function UP(e){if(!(!e.scoreFactors.sessions&&!e.scoreFactors.totalUsers))return{sessions:e.scoreFactors.sessions,totalUsers:e.scoreFactors.totalUsers}}function WP(e){return BP[e.severity]+e.count*10+e.scoreFactors.searchVisibleUrls*25+e.scoreFactors.clicks*5+Math.min(e.scoreFactors.impressions,1e4)/100+e.scoreFactors.sessions+e.scoreFactors.conversions*25}function GP(e){let t=VP(e),n=Number(WP(e).toFixed(2)),r=LP({source:`crawl`,impact:n,confidence:t,effort:HP(e),templateCount:e.count,analyticsSessions:e.scoreFactors.sessions});return{source:`crawl`,title:e.title,target:e.sampleUrls[0]??``,category:zP[e.category],score:r.final,impact:n,confidence:t,analytics:UP(e),action:e.howToFix,evidence:e.whyThisRanks,scoreBreakdown:r,ruleId:e.ruleId,severity:e.severity,affectedUrls:e.count,sampleUrls:e.sampleUrls,whyThisRanks:e.whyThisRanks,verification:e.verification}}function KP(e){if(!e.enabled)return{tool:e.tool,status:`skipped`,summary:e.skippedSummary};let t=e.warnings.find(t=>t.startsWith(e.warningPrefix));return{tool:e.tool,status:t?`skipped`:`completed`,summary:t??e.enabledSummary}}async function qP(e,t){let n=await XS(e,t),r=NC(n,{limit:e.limit??25}),i=r.map(GP).sort((e,t)=>t.score-e.score);return RP({workflow:`crawl-implementation-queue`,site:e.site??n.config.url,summary:`${i.length} crawler fixes ranked from ${n.summary.totalPages} crawled pages.`,steps:[{tool:`seo_crawl_site`,status:n.status===`failed`?`skipped`:`completed`,summary:`Crawled ${n.summary.totalPages} pages with ${n.issues.length} issues. Status: ${n.status}.`},KP({tool:`seo_gsc_page_metrics`,enabled:!!e.site,warningPrefix:`GSC metrics skipped:`,enabledSummary:`Joined Search Console page metrics where URLs matched.`,skippedSummary:`No GSC property was selected.`,warnings:n.warnings}),KP({tool:`seo_ga4_landing_page_values`,enabled:!!e.ga4PropertyId,warningPrefix:`GA4 metrics skipped:`,enabledSummary:`Joined GA4 landing-page value where URLs matched.`,skippedSummary:`No GA4 property was selected.`,warnings:n.warnings}),{tool:`seo_top_crawl_fixes`,status:`completed`,summary:`Grouped crawl issues, weighted them by severity, affected URLs, GSC visibility, GA4 value, and effort.`}],actions:i.slice(0,5).map(e=>({title:e.title,action:e.action,confidence:e.confidence})),output:{queue:i,warnings:n.warnings,crawl:n,topFixes:r}})}async function JP(e){let t=await KN(e),n=t.diagnosis.skippedSections?.length??0,r=t.dataStatus===`complete`?`Generated diagnosis, movement, change, and monitoring narrative.`:n?`Generated ${t.dataStatus===`unavailable`?`an`:`a`} ${t.dataStatus} diagnosis; ${n} sections were unavailable.`:`Generated a partial diagnosis because one or more source datasets were incomplete.`;return RP({workflow:`diagnose-property`,site:e.site,summary:t.headline,steps:[{tool:`seo_report_narrative`,status:`completed`,summary:r}],actions:t.priorities.map(e=>({title:e.title,action:e.action,confidence:e.confidence})),output:{narrative:t}})}function YP(e){return typeof e==`object`&&!!e}function XP(e){if(!YP(e))return[];let t=e.queue;return Array.isArray(t)?t.filter(e=>YP(e)):[]}function ZP(e){if(e.steps.length)return{id:`steps`,title:`Steps`,columns:[{key:`tool`,label:`Tool`},{key:`status`,label:`Status`},{key:`summary`,label:`Summary`}],rows:e.steps.map(e=>({tool:e.tool,status:e.status,summary:e.summary}))}}function QP(e){if(e.actions.length)return{id:`recommended_actions`,title:`Recommended Actions`,columns:[{key:`priority`,label:`Priority`,type:`number`},{key:`title`,label:`Title`},{key:`confidence`,label:`Confidence`},{key:`action`,label:`Action`}],rows:e.actions.map((e,t)=>({priority:t+1,title:e.title,confidence:e.confidence,action:e.action}))}}function $P(e,t){if(e.length)return{id:`priority_queue`,title:`Priority Queue`,columns:[{key:`rank`,label:`Rank`,type:`number`},{key:`source`,label:`Source`},{key:`category`,label:`Category`},{key:`score`,label:`Score`,type:`number`},{key:`confidence`,label:`Confidence`},{key:`target`,label:`Target`,type:`url`},{key:`action`,label:`Action`}],rows:e.slice(0,t).map((e,t)=>({rank:t+1,source:e.source,category:e.category,score:Number(e.score.toFixed(1)),confidence:e.confidence,target:e.target,action:e.action}))}}function eF(e,t={}){let n=XP(e.output);return{tables:[ZP(e),QP(e),$P(n,t.queueLimit??10)].filter(e=>!!e),charts:n.length?[{id:`priority_scores`,title:`Priority Scores`,type:`bar`,tableId:`priority_queue`,xKey:`rank`,yKey:`score`}]:[]}}function tF(e,t={}){let n=eF(e,t);return[`# ${e.workflow}`,``,`Property: ${e.site}`,`Generated: ${e.generatedAt}`,``,e.summary,``,...qM(n.tables)].join(`
708
+ `).trim()}async function nF(e){let t=await qN(e);return RP({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 rF=new Set([`quick-win`,`striking-distance`]);function iF(e){return e?e.id.endsWith(`-salary`)?`salary-page`:e.id:`no-template`}function aF(e){if(!rF.has(e.source)||e.grouped)return;let t=iF(e.template);return[e.source,e.category,t,R_(e.title)].join(`|`)}function oF(e){if(!rF.has(e.source)||e.grouped)return;let t=iF(e.template);return[e.source,e.category,t,R_(e.target)].join(`|`)}function sF(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 cF(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 lF(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:sF(t),evidence:cF(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 uF(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?[lF(e)]:e)]}function dF(e){return uF(uF(e,oF),aF).sort((e,t)=>t.score-e.score)}function fF(e){let t=e.templates?.find(t=>t.id===e.id);return t?.urlCount??t?.count}function pF(e,t){let n=LP({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:IP(e.source,e.verification),score:n.final,scoreBreakdown:n}}function mF(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 hF(e){let t=await bO({...e,verifyContent:e.verifyContent??!0,verifyLimit:e.verifyLimit??Math.min(e.limit??5,5)}),n=H_(e.days??28),r=await AS({propertyId:e.ga4PropertyId,startDate:n.startDate,endDate:n.endDate}),i=kS({propertyId:e.ga4PropertyId,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=fF({templates:t.strikingDistance.templates,id:e.template.id}),i=DS(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=fF({templates:t.quickWins.templates,id:e.template.id}),i=DS(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=fF({templates:t.decay.templates,id:e.template.id}),i=DS(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(...mF({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=dF(o.map(e=>pF(e,i.canRank)).filter(e=>e.score>0).sort((e,t)=>t.score-e.score)).slice(0,e.limit??25);return RP({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 gF(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 _F(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?Wk({site:e.site,startUrl:e.startUrl,limit:e.limit,refresh:e.refresh,js:e.js}):void 0,e.sitemaps?.length?nj({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?XA({site:e.site,urls:e.urls,languageCode:e.languageCode,dailyLimit:e.dailyLimit}):void 0,t?mj({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=gF(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`}),RP({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 vF(e,t){return e<t?-1:+(e>t)}function yF(e){let t=e.items.filter(e=>e.clickDelta>0).sort((e,t)=>t.clickDelta-e.clickDelta||vF(e.key,t.key)),n=e.items.filter(e=>e.clickDelta<0).sort((e,t)=>e.clickDelta-t.clickDelta||vF(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 bF(e){return Math.abs(e).toLocaleString(`en-GB`)}function xF(e){return`${Math.round(e*100)}%`}function SF(e,t){return e<t?-1:+(e>t)}function CF(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 wF(e){return e.clickDelta>0?`gained`:e.clickDelta<0?`lost`:`held flat`}function TF(e,t){return e===`page`?t.key.replace(/^https?:\/\//,``):t.key}function EF(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: ${CF(t.coverage)}.`};let n=t.dataStatus===`partial`?` Evidence is partial; ${t.summary.unmatchedRows} one-window rows were not treated as zero.`:``,r=` Scope: ${CF(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 ${TF(e,i)} ${wF(i)} ${bF(i.clickDelta)} clicks; top loser ${TF(e,a)} ${wF(a)} ${bF(a.clickDelta)} clicks.${n}${r}`}:i?{dimension:e,dataStatus:t.dataStatus,coverage:t.coverage,unmatchedRows:t.summary.unmatchedRows,winner:i,summary:`${e}: top winner ${TF(e,i)} ${wF(i)} ${bF(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 ${TF(e,a)} ${wF(a)} ${bF(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 DF(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 ${bF(t.winner.clickDelta)} clicks`:t?.loser?`top returned page lost ${bF(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 ${bF(n.winner.clickDelta)} clicks`:n?.loser?`top returned query lost ${bF(n.loser.clickDelta)} clicks`:`no material query movement found in returned rows`}.`}function OF(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 kF(e,t){return e.filter(e=>t===`winner`?e.clickDelta>0:e.clickDelta<0)}function AF(e){let t=new Map;for(let n of e.items){let r=eA(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 jF(e){return e?[...new Set(e.shape.variableSegments.flatMap(e=>e.tokenExamples))].slice(0,6):[]}function MF(e){return AF({items:kF(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=jF(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} ${bF(t.clickDelta)} clicks across ${t.items.length} moved URLs (${xF(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||SF(e.signature,t.signature))}function NF(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)||SF(e.key,t.key));if(t.length<6)return[];let n=nA(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[...MF({direction:`winner`,items:t,clusters:n,totalMovement:r,segmentCoverage:e.coverage}),...MF({direction:`loser`,items:t,clusters:n,totalMovement:i,segmentCoverage:e.coverage})].slice(0,6)}async function PF(e,t={}){let n=e.limit??20,r=t.updateCorrelation??cO,i=t.segmentImpact??KT,[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:yF(o),query:yF(s),device:yF(c),country:yF(l)},d=[EF(`page`,u.page),EF(`query`,u.query),EF(`device`,u.device),EF(`country`,u.country)],f=NF(u.page),p=[u.page,u.query,u.device,u.country].every(e=>e.dataStatus===`empty`||e.dataStatus===`unavailable`)?`skipped`:`completed`;return RP({workflow:`update-postmortem`,site:e.site,summary:DF({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:OF({update:a,segments:u,templateMovement:f}),output:{update:a,insights:d,templateMovement:f,segments:u}})}function FF(e){return e.toLowerCase().replace(/^https?:\/\//,``).replace(/^sc-domain:/,``).replace(/[^a-z0-9]+/g,`-`).replace(/^-+|-+$/g,``).slice(0,60)}function IF(e=[]){return[...new Set(e.map(e=>e.trim()).filter(Boolean))]}function LF(e){let t=FF(e.id??e.name??e.siteUrl);if(!t)throw Error(`Client id could not be derived.`);return t}function RF(){return[...Qe().clients].sort((e,t)=>e.name.localeCompare(t.name))}function zF(e){let t=Qe().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 BF(e){let t=Qe(),n=LF(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:IF(e.watchUrls??r?.watchUrls),brandTerms:IF(e.brandTerms??r?.brandTerms??A_({id:n,name:e.name??r?.name,siteUrl:e.siteUrl})),ga4PropertyId:e.ga4PropertyId??r?.ga4PropertyId,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],$e(t),a}function VF(e){let t=Qe(),n=zF(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),$e(t),!0):!1}function HF(e){let t=Qe(),n=zF(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,$e(t),{...n,isDefault:!0}}const UF=[`https://www.googleapis.com/auth/webmasters.readonly`,`https://www.googleapis.com/auth/analytics.readonly`];async function WF(){let e=Ne(),t=Qe(),n=_t(),r=At(),a=await st(),o=ft(),s=a?!!yt(a.client_source):void 0,c=[];c.push({id:`config-dir`,label:`Config directory`,status:i(e.configDir)?`pass`:`warn`,detail:e.configDir,fix:i(e.configDir)?void 0:"Run `seo start` or `seo auth setup-client`."}),c.push({id:`oauth-client`,label:`Google credentials`,status:r.configured?`pass`:s===!1||r.error?`fail`:n.sharedConfigured||n.byoConfigured?`pass`:`fail`,detail:r.configured?`Service account configured for ${r.identity}.`:s===!1?`Stored Google login uses the ${a?.client_source===`shared`?`shared seo app`:`BYO client`}, but that client is not configured.`:r.error?r.error:n.sharedConfigured?`Shared client configured.`:n.byoConfigured?`BYO client configured at ${e.oauthClientFile}.`:`No shared or BYO Google OAuth client configured.`,fix:r.configured?void 0:s===!1?a?.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.":r.error?"Set one valid service account credential source, then run `seo doctor` again.":n.sharedConfigured||n.byoConfigured?void 0:"Run `seo auth setup-client` or set SEO_GOOGLE_CLIENT_ID and SEO_GOOGLE_CLIENT_SECRET."}),c.push({id:`oauth-client-file`,label:`BYO OAuth file`,status:o?`pass`:`warn`,detail:o?e.oauthClientFile:`No BYO OAuth client file. This is fine if using a shared client.`}),c.push({id:`google-login`,label:`Google login`,status:r.configured||a?`pass`:`fail`,detail:r.configured?`Using service account ${r.identity}.`:a?`Signed in as ${a.account_email}.`:`No Google token found.`,fix:r.configured||a?void 0:"Run `seo auth login`, or configure a service account for CI."});let l=new Set(a?.scope.split(/\s+/).filter(Boolean)??[]);for(let e of UF)c.push({id:`scope:${e}`,label:e.endsWith(`webmasters.readonly`)?`GSC scope`:`GA4 scope`,status:r.configured||a&&l.has(e)?`pass`:a?`fail`:`warn`,detail:r.configured?`${e} requested by the service account.`:a?l.has(e)?e:`Missing ${e}.`:`Cannot inspect scopes until login is complete.`,fix:!r.configured&&a&&!l.has(e)?"Run `seo auth logout` then `seo auth login`.":void 0});return c.push({id:`default-site`,label:`Saved GSC property`,status:`pass`,detail:t.defaultSite??`No saved default. Human CLI commands will prompt; agents can pass --site.`}),c.push({id:`default-ga4`,label:`Saved GA4 property`,status:`pass`,detail:t.google.defaultGa4PropertyId??`No saved default. Human CLI commands will prompt; agents can pass --property.`}),{ok:c.every(e=>e.status!==`fail`),generatedAt:new Date().toISOString(),checks:c}}var GF=class{name=`dataforseo`;capabilities={overview:!0};getAuthHeader(){let e=Qe(),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 KF(e){return e.trim().split(`
709
+ `).map(e=>e.split(`;`).map(e=>e.replace(/^"|"$/g,``).trim()))}function qF(e){return e/1e3*.05}async function JF(e,t,r,i,a,o=!1){let s=Qe().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=j(),u=on([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:qF(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=KF(m),g=r(h),_=Math.max(0,h.length-1)*a;return l.prepare(`INSERT OR REPLACE INTO semrush_cache
710
710
  (endpoint, query_hash, request_json, response_json, credits_used, fetched_at, expires_at)
711
- VALUES (?, ?, ?, ?, ?, ?, ?)`).run(e,u,JSON.stringify(c),JSON.stringify(g),_,Date.now(),Date.now()+i),{data:g,usage:{provider:`Semrush`,units:_,unitLabel:`units`,estimatedUsd:OF(_),calls:1}}}function AF(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 jF(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 MF=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 kF(`phrase_this`,{phrase:e,database:t.database??`us`,export_columns:`Ph,Nq,Cp,Co,Nr,Td,Kd`},AF,7*864e5,10,t.refresh)}async batchKeywordOverview(e,t={}){return kF(`phrase_these`,{phrase:e.join(`;`),database:t.database??`us`,export_columns:`Ph,Nq,Cp,Co,Nr,Td,Kd`},e=>jF(e).map(e=>({...e,phrase:e.phrase})),7*864e5,10,t.refresh)}async relatedKeywords(e,t={}){return kF(`phrase_related`,{phrase:e,database:t.database??`us`,display_limit:20,export_columns:`Ph,Nq,Kd,Cp,Co`},jF,14*864e5,40,t.refresh)}async questions(e,t={}){return kF(`phrase_questions`,{phrase:e,database:t.database??`us`,display_limit:20,export_columns:`Ph,Nq,Kd,Cp,Co`},jF,14*864e5,40,t.refresh)}async keywordDifficulty(e,t={}){return kF(`phrase_kdi`,{phrase:e.join(`;`),database:t.database??`us`,export_columns:`Ph,Kd`},e=>jF(e).map(e=>({phrase:e.phrase,kd:e.difficulty??0})),7*864e5,50,t.refresh)}async domainKeywords(e,t={}){return kF(`domain_organic`,{domain:e,database:t.database??`us`,display_limit:100,export_columns:`Ph,Nq,Cp,Co,Kd,Po,Ur,Dn`},jF,7*864e5,10,t.refresh)}async urlKeywords(e,t={}){return kF(`url_organic`,{url:e,database:t.database??`us`,display_limit:100,export_columns:`Ph,Nq,Cp,Co,Kd,Po,Ur,Dn`},jF,7*864e5,10,t.refresh)}};function NF(e){let t=Qe(),n=e??t.providers.prefer;if(n===`authoritative`&&t.providers.semrushApiKey)return new MF;if(n===`cheap`&&t.providers.dataForSeoLogin&&t.providers.dataForSeoPassword)return new EF;if(t.providers.semrushApiKey)return new MF;if(t.providers.dataForSeoLogin&&t.providers.dataForSeoPassword)return new EF}new class{getGoogleOAuthClient(){let e=ft();if(e)return{clientId:e.clientId,clientSecret:e.clientSecret,source:`byo`};let t=Qe(),n=process.env.SEO_GOOGLE_CLIENT_ID??process.env.GSC_CLIENT_ID??gt.clientId??t.auth.sharedClientId,r=process.env.SEO_GOOGLE_CLIENT_SECRET??process.env.GSC_CLIENT_SECRET??gt.clientSecret??t.auth.sharedClientSecret;if(!(!n||!r))return{clientId:n,clientSecret:r,source:`shared`}}readGoogleTokens(){return st()}writeGoogleTokens(e){return ct(e)}deleteGoogleTokens(){return lt()}};const PF=`0.1.0`,FF=O.iso.date(`Use a YYYY-MM-DD date.`),IF=O.string().regex(/^\d{4}-(?:0[1-9]|1[0-2])$/,`Use a YYYY-MM month.`);function LF(e,t){return e===void 0?t:e}const RF={site:{mcp:`site`,schema:O.string()},days:{mcp:`days`,schema:O.number().optional()},recentDays:{mcp:`recentDays`,schema:O.number().optional()},range:{mcp:`range`,schema:O.number().optional()},limit:{mcp:`limit`,schema:O.number().optional()},checkLimit:{mcp:`checkLimit`,schema:O.number().optional()},minImpressions:{mcp:`minImpressions`,schema:O.number().optional()},includeBrand:{mcp:`includeBrand`,schema:O.boolean().optional()},verifyContent:{mcp:`verifyContent`,schema:O.boolean().optional()},verifyLimit:{mcp:`verifyLimit`,schema:O.number().optional()},js:{mcp:`js`,schema:O.boolean().optional()},fetchConcurrency:{mcp:`fetchConcurrency`,schema:O.number().optional()},fetchIntervalCap:{mcp:`fetchIntervalCap`,schema:O.number().optional()},fetchIntervalMs:{mcp:`fetchIntervalMs`,schema:O.number().optional()},refresh:{mcp:`refresh`,schema:O.boolean().optional()}};function zF(e){return Object.fromEntries(e.map(e=>{let t=RF[e];return[t.mcp,t.schema]}))}function BF(e){return JSON.stringify(e,null,2)}function Q(e){let t=we(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 VF=O.union([FF,O.string().regex(/^(?:today|yesterday|\d+daysAgo)$/)]).describe(`Use YYYY-MM-DD or a GA4 relative date.`),HF={site:O.string().trim().min(1),days:O.number().int().min(1).max(548).optional(),startDate:FF.optional(),endDate:FF.optional(),limit:O.number().int().min(1).max(100).optional(),minImpressions:O.number().int().min(0).max(1e9).optional(),maxRows:O.number().int().min(1).max(5e4).optional(),brandTerms:O.array(O.string().trim().min(1)).max(100).optional(),includeBrand:O.boolean().optional(),refresh:O.boolean().optional()};function UF(e,t={}){e.registerTool(`seo_ai_referrals`,{description:`Find AI referral traffic detected in GA4. maxRows bounds retained provider rows; resultLimit bounds returned landing pages.`,inputSchema:{property:O.string().trim().min(1),startDate:VF.optional(),endDate:VF.optional(),maxRows:O.number().int().min(1).max(1e5).describe(`Maximum retained GA4 rows. If limit is also supplied, both values must match.`).optional(),limit:O.number().int().min(1).max(1e5).describe(`Legacy alias for maxRows. If maxRows is also supplied, both values must match.`).optional(),resultLimit:O.number().int().min(1).max(1e3).describe(`Maximum ranked landing pages returned. Defaults to 25.`).optional(),refresh:O.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 A(`INVALID_INPUT`,`maxRows and the legacy limit option must match when both are provided.`);let c=await(t.aiReferralsReport??Hn)({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:{...zF([`site`,`days`,`limit`,`minImpressions`,`includeBrand`,`verifyContent`,`refresh`,`js`]),days:O.number().int().min(1).max(548).optional(),limit:O.number().int().min(1).max(100).optional(),minImpressions:O.number().int().positive().optional(),url:O.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??dy)({site:e,url:n,days:r,limit:i,minImpressions:a,includeBrand:o,verifyContent:s,refresh:c,js:LF(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:{...zF([`site`,`days`,`limit`,`minImpressions`,`includeBrand`,`verifyContent`,`refresh`,`js`]),days:O.number().int().min(1).max(548).optional(),limit:O.number().int().min(1).max(100).optional(),minImpressions:O.number().int().positive().optional(),url:O.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??xy)({site:e,url:n,days:r,limit:i,minImpressions:a,includeBrand:o,verifyContent:s,refresh:c,js:LF(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:O.string().url(),strategy:O.enum([`mobile`,`desktop`]).optional(),refresh:O.boolean().optional()}},async({url:e,strategy:t,refresh:n})=>{try{let r=await kj({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:HF},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??lP)({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:HF},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??rv)({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 WF(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 GF(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:tC(e,{limit:10}),reviewObservations:nC(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 KF(e){if(!e.value||e.value===`latest`||e.value===`previous`){let t=Kb({site:e.site,limit:20}).filter(t=>t.id!==e.skipId),n=e.value===`previous`?t[1]:t[0];return n?qb(n.id):void 0}return qb(e.value)}function qF(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:O.string().url(),site:O.string().optional(),ga4PropertyId:O.string().optional(),maxPages:O.number().int().positive().optional(),maxDepth:O.number().int().nonnegative().optional(),concurrency:O.number().int().positive().optional(),fetchIntervalCap:O.number().int().positive().optional(),fetchIntervalMs:O.number().int().positive().optional(),refresh:O.boolean().optional(),include:O.array(O.string()).optional(),exclude:O.array(O.string()).optional(),useSitemap:O.boolean().optional(),respectRobots:O.boolean().optional(),js:O.boolean().optional(),includePages:O.boolean().optional(),includeIssues:O.boolean().optional(),saveReport:O.boolean().optional()}},async({url:e,site:t,ga4PropertyId: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 hS({url:e,site:t,ga4PropertyId:n,maxPages:r,maxDepth:i,concurrency:a,refresh:c,fetchRate:WF({fetchConcurrency:a,fetchIntervalCap:o,fetchIntervalMs:s}),include:l,exclude:u,useSitemap:d,respectRobots:f,js:!!p}),v=g?Gb(_):void 0;return $(`Crawl complete for ${e}. Found ${_.issues.length} issues across ${_.summary.totalPages} pages.`,{...GF(_,{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:O.array(O.string().url()).min(1),site:O.string().optional(),ga4PropertyId:O.string().optional(),maxPages:O.number().int().positive().optional(),concurrency:O.number().int().positive().optional(),fetchIntervalCap:O.number().int().positive().optional(),fetchIntervalMs:O.number().int().positive().optional(),refresh:O.boolean().optional(),includePages:O.boolean().optional(),includeIssues:O.boolean().optional(),saveReport:O.boolean().optional()}},async({urls:e,site:t,ga4PropertyId:n,maxPages:r,concurrency:i,fetchIntervalCap:a,fetchIntervalMs:o,refresh:s,includePages:c,includeIssues:l,saveReport:u})=>{try{let d=await hS({url:e[0]??``,urls:e,mode:`list`,site:t,ga4PropertyId:n,maxPages:r??e.length,concurrency:i,refresh:s,fetchRate:WF({fetchConcurrency:i,fetchIntervalCap:a,fetchIntervalMs:o}),useSitemap:!1}),f=u?Gb(d):void 0;return $(`URL audit complete. Found ${d.issues.length} issues across ${d.summary.totalPages} pages.`,{...GF(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:O.string().url().optional(),reportId:O.string().optional(),site:O.string().optional(),maxPages:O.number().int().positive().optional(),maxDepth:O.number().int().nonnegative().optional(),fetchIntervalCap:O.number().int().positive().optional(),fetchIntervalMs:O.number().int().positive().optional(),refresh:O.boolean().optional(),category:O.string().optional(),limit:O.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{let u=e?await hS({url:e,site:n,maxPages:r,maxDepth:i,refresh:s,fetchRate:WF({fetchIntervalCap:a,fetchIntervalMs:o})}):t?qb(t):Yb(n);if(!u)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let d=tC(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:O.string()}},async({ruleId:e})=>{let t=Ly(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:cw.optional()}},async({category:e})=>{let t=Iy().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:O.string().url().optional(),reportId:O.string().optional(),site:O.string().optional(),ruleId:O.string().optional(),category:O.string().optional(),severity:O.string().optional(),maxPages:O.number().int().positive().optional(),fetchIntervalCap:O.number().int().positive().optional(),fetchIntervalMs:O.number().int().positive().optional(),refresh:O.boolean().optional(),limit:O.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 hS({url:e,site:n,maxPages:o,refresh:l,fetchRate:WF({fetchIntervalCap:s,fetchIntervalMs:c})}):t?qb(t):Yb(n);if(!d)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let f=Cy(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:O.string().url().optional(),reportId:O.string().optional(),site:O.string().optional(),maxPages:O.number().int().positive().optional(),fetchIntervalCap:O.number().int().positive().optional(),fetchIntervalMs:O.number().int().positive().optional(),refresh:O.boolean().optional(),limit:O.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 hS({url:e,site:n,maxPages:r,refresh:o,fetchRate:WF({fetchIntervalCap:i,fetchIntervalMs:a})}):t?qb(t):Yb(n);if(!c)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let l=mC(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_ai_readiness`,{description:`Return AI-search technical evidence and optional observations without an aggregate score or citation prediction.`,inputSchema:{url:O.string().url().optional(),reportId:O.string().optional(),site:O.string().optional(),maxPages:O.number().int().positive().optional(),fetchIntervalCap:O.number().int().positive().optional(),fetchIntervalMs:O.number().int().positive().optional(),refresh:O.boolean().optional()}},async({url:e,reportId:t,site:n,maxPages:r,fetchIntervalCap:i,fetchIntervalMs:a,refresh:o})=>{try{let s=e?await hS({url:e,site:n,maxPages:r,refresh:o,fetchRate:WF({fetchIntervalCap:i,fetchIntervalMs:a})}):t?qb(t):Yb(n);if(!s)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let c=My(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:O.string().url().optional(),reportId:O.string().optional(),site:O.string().optional(),maxPages:O.number().int().positive().optional(),fetchIntervalCap:O.number().int().positive().optional(),fetchIntervalMs:O.number().int().positive().optional(),refresh:O.boolean().optional()}},async({url:e,reportId:t,site:n,maxPages:r,fetchIntervalCap:i,fetchIntervalMs:a,refresh:o})=>{try{let s=e?await hS({url:e,site:n,maxPages:r,refresh:o,fetchRate:WF({fetchIntervalCap:i,fetchIntervalMs:a})}):t?qb(t):Yb(n);if(!s)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let c=VC(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:O.string().url().optional(),reportId:O.string().optional(),site:O.string().optional(),maxPages:O.number().int().positive().optional(),maxUrls:O.number().int().positive().optional(),tokenBudget:O.number().int().positive().optional(),exclude:O.array(O.string()).optional(),title:O.string().optional(),description:O.string().optional(),fetchIntervalCap:O.number().int().positive().optional(),fetchIntervalMs:O.number().int().positive().optional(),refresh:O.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 hS({url:e,site:n,maxPages:r,refresh:d,fetchRate:WF({fetchIntervalCap:l,fetchIntervalMs:u})}):t?qb(t):Yb(n);if(!f)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let p=HC(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:O.string().url().optional(),reportId:O.string().optional(),site:O.string().optional(),maxPages:O.number().int().positive().optional(),fetchIntervalCap:O.number().int().positive().optional(),fetchIntervalMs:O.number().int().positive().optional(),refresh:O.boolean().optional()}},async({url:e,reportId:t,site:n,maxPages:r,fetchIntervalCap:i,fetchIntervalMs:a,refresh:o})=>{try{let s=e?await hS({url:e,site:n,maxPages:r,refresh:o,fetchRate:WF({fetchIntervalCap:i,fetchIntervalMs:a})}):t?qb(t):Yb(n);if(!s)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let c=ES(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:O.string().url().optional(),reportId:O.string().optional(),site:O.string().optional(),maxPages:O.number().int().positive().optional(),maxConcepts:O.number().int().min(1).max(100).optional(),includeFiles:O.boolean().optional(),title:O.string().min(1).max(200).optional(),fetchIntervalCap:O.number().int().positive().optional(),fetchIntervalMs:O.number().int().positive().optional(),refresh:O.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 hS({url:e,site:n,maxPages:r,refresh:l,fetchRate:WF({fetchIntervalCap:s,fetchIntervalMs:c})}):t?qb(t):Yb(n);if(!u)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let d=$C(u,{maxConcepts:i??(a?25:100),title:o}),f=iw(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:O.array(O.object({path:O.string().min(1).max(512),content:O.string().max(2e6)})).min(1).max(5006)}},async({files:e})=>{let t=iw(e);return $(t.valid?`Bundle passes seo OKF checks.`:`Bundle has seo OKF issues.`,{validation:t,explanation:aw(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:O.string().optional(),site:O.string().optional(),includePages:O.boolean().optional(),includeIssues:O.boolean().optional()}},async({id:e,site:t,includePages:n,includeIssues:r})=>{try{let i=e?qb(e):Yb(t);return i?$(`Saved crawl report ${i.id}.`,{...GF(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:O.string().optional(),limit:O.number().int().positive().optional()}},async({site:e,limit:t})=>{try{let n=Kb({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:O.string().optional(),before:O.string().optional(),site:O.string().optional()}},async({after:e,before:t,site:n})=>{try{let r=KF({value:e??`latest`,site:n});if(!r)return Q(`No newer crawl report found.`);let i=KF({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=NC({before:i,after:r});return $(a.headline,a)}catch(e){return Q(e)}})}function JF(e,t={}){e.registerTool(`seo_doctor`,{description:`Check local auth, scopes, config, and defaults`,inputSchema:{}},async()=>{try{let e=await TF();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:{...zF([`site`,`days`,`recentDays`,`limit`,`includeBrand`,`refresh`])}},async({site:e,days:t,recentDays:n,limit:r,includeBrand:i,refresh:a})=>{try{let o=await tO({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:O.string().trim().min(1),days:O.number().int().min(1).max(240).optional(),limit:O.number().int().min(1).max(100).optional(),refresh:O.boolean().optional(),dimension:O.enum([`page`,`query`,`country`,`device`]).optional(),compareDays:O.number().int().min(1).max(240).optional(),startDate:FF.optional(),endDate:FF.optional(),maxRows:O.number().int().min(1).max(25e4).optional(),unmatchedLimit:O.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??DT)({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:{...zF([`site`,`days`,`minImpressions`,`limit`,`verifyContent`,`verifyLimit`,`includeBrand`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),days:O.number().int().min(1).max(548).optional(),minImpressions:O.number().int().min(0).max(1e9).optional(),limit:O.number().int().min(1).max(100).optional(),verifyLimit:O.number().int().min(0).max(100).optional(),brandTerms:O.array(O.string().trim().min(1).max(200)).max(20).optional(),fetchConcurrency:O.number().int().min(1).max(16).optional(),fetchIntervalCap:O.number().int().min(1).max(60).optional(),fetchIntervalMs:O.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 dD({site:e,days:t,minImpressions:n,limit:r,verifyContent:i,verifyLimit:a,includeBrand:o,brandTerms:s,js:LF(c,void 0),rate:WF({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:{...zF([`site`,`days`,`recentDays`,`refresh`])}},async({site:e,days:t,recentDays:n,refresh:r})=>{try{let i=await HD({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:{...zF([`site`,`days`,`recentDays`,`refresh`]),paddingDays:O.number().optional()}},async({site:e,days:t,recentDays:n,paddingDays:r,refresh:i})=>{try{let a=await UD({site:e,days:t,recentDays:n,paddingDays:r,refresh:i});return $(`Classification: ${a.classification}.`,a)}catch(e){return Q(e)}})}function YF(e){e.registerTool(`seo_content_groups`,{description:`List, create, or delete reusable page/query groups`,inputSchema:{action:O.enum([`list`,`add`,`delete`]),site:O.string().optional(),id:O.string().optional(),name:O.string().optional(),dimension:O.enum([`page`,`query`]).optional(),matchType:O.enum([`equals`,`contains`,`regex`]).optional(),pattern:O.string().optional()}},async({action:e,site:t,id:n,name:r,dimension:i,matchType:a,pattern:o})=>{try{if(e===`list`){let e=rO(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=aO(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=nO({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:O.enum([`list`,`add`,`delete`]),site:O.string().optional(),id:O.string().optional(),limit:O.number().optional(),scope:O.enum([`site`,`page`,`query`,`group`]).optional(),target:O.string().optional(),title:O.string().optional(),description:O.string().optional(),changedAt:O.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=bD({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=SD(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=yD({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 GA4/control evidence`,inputSchema:{id:O.string().min(1).optional(),site:O.string().min(1).optional(),scope:O.enum([`site`,`page`,`query`,`group`]).optional(),target:O.string().min(1).optional(),title:O.string().min(1).optional(),changedAt:FF.optional(),ga4PropertyId:O.string().min(1).optional(),controlScope:O.enum([`site`,`page`,`query`,`group`]).optional(),controlTarget:O.string().min(1).optional(),controlTitle:O.string().min(1).optional(),beforeDays:O.number().int().min(1).max(548).optional(),afterDays:O.number().int().min(1).max(548).optional(),refresh:O.boolean().optional()}},async({id:e,site:t,scope:n,target:r,title:i,changedAt:a,ga4PropertyId:o,controlScope:s,controlTarget:c,controlTitle:l,beforeDays:u,afterDays:d,refresh:f})=>{try{let p=await NO({id:e,site:t,scope:n,target:r,title:i,changedAt:a,ga4PropertyId: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 XF(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:O.string().url(),site:O.string().optional(),limit:O.number().optional(),refresh:O.boolean().optional(),js:O.boolean().optional()}},async({startUrl:e,site:t,limit:n,refresh:r,js:i})=>{try{let a=await Tk({startUrl:e,site:t,limit:n,refresh:r,js:LF(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:O.string(),urls:O.array(O.string().url()).min(1).max(100),languageCode:O.string().min(1).max(35).optional(),dailyLimit:O.number().int().min(1).max(2e3).optional()}},async({site:e,urls:t,languageCode:n,dailyLimit:r})=>{try{let i=await jA({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:O.string(),sitemaps:O.array(O.string().url()).min(1).max(20),properties:O.array(O.string()).optional(),dailyLimit:O.number().int().min(1).max(2e3).optional(),targetCycleDays:O.number().int().min(1).max(365).optional(),maxUrls:O.number().int().min(1).max(25e4).optional()}},async({site:e,sitemaps:t,properties:n,dailyLimit:r,targetCycleDays:i,maxUrls:a})=>{try{let o=await hA({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:O.string().min(1),crawlReportId:O.string().min(1).optional(),sitemaps:O.array(O.string().url()).min(1).max(20).optional(),days:O.number().int().min(1).max(548).optional(),rowLimit:O.number().int().min(1).max(25e4).optional(),maxSitemapUrls:O.number().int().min(1).max(25e4).optional(),itemsPerSection:O.number().int().min(1).max(1e3).optional(),templateClusters:O.number().int().min(1).max(200).optional(),templateSamples:O.number().int().min(1).max(25).optional(),refresh:O.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 rA({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:O.string(),sitemaps:O.array(O.string().url()).min(1).max(20),properties:O.array(O.string()).optional(),dailyLimit:O.number().int().min(1).max(2e3).optional(),inspectLimit:O.number().int().min(1).max(100).optional(),maxUrls:O.number().int().min(1).max(25e4).optional(),languageCode:O.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 LA({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:O.string().url(),maxHops:O.number().optional(),refresh:O.boolean().optional(),js:O.boolean().optional()}},async({url:e,maxHops:t,refresh:n,js:r})=>{try{let i=await WA({url:e,maxHops:t,refresh:n,js:LF(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:{...zF([`site`,`days`,`limit`,`minImpressions`,`refresh`,`js`]),minClicks:O.number().optional()}},async({site:e,days:t,limit:n,minClicks:r,minImpressions:i,refresh:a,js:o})=>{try{let s=await YA({site:e,days:t,limit:n,minClicks:r,minImpressions:i,refresh:a,js:LF(o,`auto`)});return $(`Checked ${s.summary.checked} search-value URLs. ${s.summary.recoverable} recoverable issues.`,s)}catch(e){return Q(e)}})}function ZF(e,t={}){e.registerTool(`seo_cannibal`,{description:`Find multi-URL query exposure candidates for intent and technical review`,inputSchema:{...zF([`site`,`days`,`limit`,`minImpressions`,`includeBrand`,`refresh`]),days:O.number().int().min(1).max(548).optional(),limit:O.number().int().min(1).max(100).optional(),minImpressions:O.number().int().min(0).max(1e9).optional(),brandTerms:O.array(O.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 aE({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:{...zF([`site`,`days`,`limit`,`includeBrand`,`refresh`]),days:O.number().int().min(1).max(548).optional(),limit:O.number().int().min(1).max(100).optional(),comparison:O.enum([`previous-period`,`year-over-year`]).optional(),minDropPct:O.number().min(0).max(100).optional(),minPreviousClicks:O.number().min(0).max(1e9).optional(),minClickLoss:O.number().min(0).max(1e9).optional(),brandTerms:O.array(O.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 xE({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:{...zF([`site`,`days`,`limit`,`minImpressions`,`verifyContent`,`verifyLimit`,`includeBrand`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),days:O.number().int().min(1).max(548).optional(),limit:O.number().int().min(1).max(100).optional(),minImpressions:O.number().int().min(0).max(1e9).optional(),verifyLimit:O.number().int().min(0).max(100).optional(),brandTerms:O.array(O.string().trim().min(1).max(200)).max(20).optional(),fetchConcurrency:O.number().int().min(1).max(16).optional(),fetchIntervalCap:O.number().int().min(1).max(60).optional(),fetchIntervalMs:O.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??GE)({site:e,days:n,limit:r,minImpressions:i,brandTerms:a,verifyContent:o,verifyLimit:s,includeBrand:c,js:LF(l,void 0),rate:WF({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:{...zF([`site`,`days`,`limit`,`checkLimit`,`minImpressions`,`includeBrand`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),targetUrl:O.string().url().refine(e=>/^https?:\/\//.test(e),`Use an HTTP(S) URL.`),days:O.number().int().min(1).max(548).optional(),limit:O.number().int().min(1).max(100).optional(),checkLimit:O.number().int().min(1).max(200).optional(),minImpressions:O.number().min(0).max(1e9).optional(),brandTerms:O.array(O.string().trim().min(1).max(200)).max(20).optional(),fetchConcurrency:O.number().int().min(1).max(16).optional(),fetchIntervalCap:O.number().int().min(1).max(60).optional(),fetchIntervalMs:O.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??lk)({site:e,targetUrl:n,days:r,limit:i,checkLimit:a,minImpressions:o,brandTerms:s,includeBrand:c,js:LF(l,void 0),rate:WF({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:{...zF([`site`,`minImpressions`,`limit`,`includeBrand`,`refresh`]),site:O.string().trim().min(1),minImpressions:O.number().int().min(1).max(1e9).optional(),limit:O.number().int().min(1).max(100).optional(),brandTerms:O.array(O.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??Xw)({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:{...zF([`site`,`includeBrand`,`minImpressions`,`limit`,`refresh`]),site:O.string().trim().min(1),scope:O.string().trim().min(1).max(2e3).optional(),minImpressions:O.number().int().min(1).max(1e9).optional(),limit:O.number().int().min(1).max(100).optional(),brandTerms:O.array(O.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??QM)({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:O.enum([`phrase_this`,`phrase_related`,`phrase_questions`]),phrase:O.string()}},async({endpoint:e,phrase:t})=>{try{let n=NF(`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 $(BF(r.data),r)}catch(e){return Q(e)}})}function QF(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 $F(e){e.registerTool(`seo_pseo_audit`,{description:`Audit pSEO templates with first-party GSC, optional crawl, and optional URL Inspection evidence`,inputSchema:{site:O.string().min(1),days:O.number().int().min(1).max(548).optional(),sitemaps:O.array(O.string().url()).max(20).optional(),maxSitemapUrls:O.number().int().min(1).max(1e5).optional(),templateLimit:O.number().int().min(1).max(100).optional(),minimumTemplateUrls:O.number().int().min(2).max(100).optional(),minimumTemplateShare:O.number().min(0).max(1).optional(),minimumTemplateImpressions:O.number().min(0).max(1e9).optional(),crawlSamples:O.number().int().min(0).max(10).optional(),inspectSamples:O.number().int().min(0).max(10).optional(),brandTerms:O.array(O.string().min(1).max(200)).max(20).optional(),includeBrand:O.boolean().optional(),refresh:O.boolean().optional(),js:O.boolean().optional(),fetchConcurrency:O.number().int().min(1).max(20).optional(),fetchIntervalCap:O.number().int().min(1).max(100).optional(),fetchIntervalMs:O.number().int().min(100).max(6e4).optional(),detail:O.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 TM({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:LF(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:jM(v)}:{report:QF(v)},y?{markdown:MM(v)}:void 0)}catch(e){return Q(e)}})}const eI={"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.`},"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 GA4 and see a ranked, bounded set of landing pages that received them.`,useWhen:[`You want measured AI referral traffic from GA4.`,`You need the landing pages and sources behind those visits.`],avoidWhen:[`GA4 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.`},"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 tI(e){return eI[e]}function nI(e){e.registerTool(`seo_audit_page`,{description:`Run a single-page technical and content audit`,inputSchema:{url:O.string().url(),site:O.string().optional(),js:O.boolean().optional(),refresh:O.boolean().optional()}},async({url:e,site:t,js:n,refresh:r})=>{try{let i=await S_({url:e,site:t,js:LF(n,`auto`),refresh:r});return $(`Audit complete for ${e}. Found ${i.issues.length} issues.`,i)}catch(e){return Q(e)}})}const rI=zF([`verifyContent`,`verifyLimit`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]);function iI(e){return{verifyContent:e.verifyContent,verifyLimit:e.verifyLimit,js:LF(e.js,void 0),rate:WF({fetchConcurrency:e.fetchConcurrency,fetchIntervalCap:e.fetchIntervalCap,fetchIntervalMs:e.fetchIntervalMs}),refresh:e.refresh}}function aI(e){e.registerTool(`seo_monthly_report`,{description:`Generate a monthly SEO report narrative`,inputSchema:{...zF([`site`,`limit`,`includeBrand`]),month:IF.optional(),...rI}},async({site:e,month:t,limit:n,includeBrand:r,...i})=>{try{let a=await ON({site:e,month:t,limit:n,includeBrand:r,...iI(i)});return $(`Monthly report generated for ${a.month}. ${a.headline}`,{...a,presentation:kN(a)},{markdown:a.markdown})}catch(e){return Q(e)}})}function oI(e){e.registerTool(`seo_report_narrative`,{description:`Generate a client-ready SEO narrative across diagnosis, changes, and monitoring`,inputSchema:{...zF([`site`,`days`,`recentDays`,`limit`,`includeBrand`]),startDate:FF.optional(),endDate:FF.optional(),changeLimit:O.number().optional(),...rI}},async({site:e,days:t,recentDays:n,startDate:r,endDate:i,limit:a,changeLimit:o,includeBrand:s,...c})=>{try{let l=await DN({site:e,days:t,recentDays:n,startDate:r,endDate:i,limit:a,changeLimit:o,includeBrand:s,...iI(c)});return $(l.headline,{...l,presentation:kN(l)},{markdown:l.markdown})}catch(e){return Q(e)}})}function sI(e){e.registerTool(`seo_second_page`,{description:`Find URLs with GSC average position above 10 through 20 and return evidence-grounded investigation prompts`,inputSchema:{...zF([`site`,`range`,`minImpressions`,`limit`,`includeBrand`]),...rI,range:O.number().int().min(1).max(548).optional(),minImpressions:O.number().int().min(0).max(1e9).optional(),limit:O.number().int().min(1).max(100).optional(),verifyLimit:O.number().int().min(0).max(100).optional(),brandTerms:O.array(O.string().trim().min(1).max(200)).max(20).optional(),fetchConcurrency:O.number().int().min(1).max(16).optional(),fetchIntervalCap:O.number().int().min(1).max(60).optional(),fetchIntervalMs:O.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 XN({site:e,range:t,minImpressions:n,limit:r,includeBrand:i,brandTerms:a,...iI(o)});return $(s.summary.verdict,s)}catch(e){return Q(e)}})}function cI(e){oI(e),aI(e),nI(e),sI(e)}const lI=O.strictObject({site:O.string().trim().min(1).max(2048),startUrl:O.string().url().optional(),urls:O.array(O.string().url()).min(1).max(100).optional(),sitemaps:O.array(O.string().url()).min(1).max(20).optional(),properties:O.array(O.string().trim().min(1).max(2048)).min(1).max(1e3).optional(),limit:O.number().int().min(1).max(25e4).optional(),refresh:O.boolean().optional(),js:O.boolean().optional(),languageCode:O.string().trim().min(1).max(35).optional(),dailyLimit:O.number().int().min(1).max(2e3).optional(),inspectLimit:O.number().int().min(1).max(100).optional(),maxUrls:O.number().int().min(1).max(25e4).optional(),recoverLinks:O.boolean().optional(),recoverDays:O.number().int().min(1).max(548).optional(),recoverLimit:O.number().int().min(1).max(100).optional(),recoverMinClicks:O.number().int().min(0).max(1e9).optional(),recoverMinImpressions:O.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 uI(e){return $(e.summary,{...e,presentation:FP(e)},{markdown:IP(e)})}function dI(e){e.registerTool(`seo_workflow_diagnose_property`,{description:`Run the full agent workflow for property diagnosis and next actions`,inputSchema:{...zF([`site`,`days`,`recentDays`,`limit`,`includeBrand`,`refresh`])}},async({site:e,days:t,recentDays:n,limit:r,includeBrand:i,refresh:a})=>{try{return uI(await kP({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:{...zF([`site`,`limit`,`includeBrand`,`refresh`]),month:IF.optional()}},async({site:e,month:t,limit:n,includeBrand:r,refresh:i})=>{try{return uI(await LP({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:{...zF([`site`,`days`,`recentDays`,`limit`,`includeBrand`,`refresh`]),knownConfounders:O.array(O.string()).optional(),includeChangeLog:O.boolean().optional()}},async({site:e,days:t,recentDays:n,limit:r,includeBrand:i,knownConfounders:a,includeChangeLog:o,refresh:s})=>{try{return uI(await hF({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:lI},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 uI(await QP({site:e,startUrl:t,urls:n,sitemaps:r,properties:i,limit:a,refresh:o,js:LF(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:{...zF([`site`,`days`,`recentDays`,`limit`,`includeBrand`,`verifyContent`,`verifyLimit`,`refresh`]),ga4PropertyId:O.string().optional()}},async({site:e,days:t,recentDays:n,limit:r,includeBrand:i,ga4PropertyId:a,verifyContent:o,verifyLimit:s,refresh:c})=>{try{return uI(await XP({site:e,days:t,recentDays:n,limit:r,includeBrand:i,ga4PropertyId:a,verifyContent:o,verifyLimit:s,refresh:c}))}catch(e){return Q(e)}})}const fI=[`ai-search`,`crawl`,`diagnosis`,`experiments`,`monitoring`,`opportunities`,`reporting`,`setup`,`workflows`],pI=[{category:`setup`,register:JF,names:[`seo_doctor`]},{category:`diagnosis`,register:JF,names:[`seo_segment_impact`,`seo_striking_distance`,`seo_traffic_anomaly`,`seo_update_correlate`]},{category:`opportunities`,register:ZF,names:[`seo_cannibal`,`seo_ctr_underperformers`,`seo_decaying`,`seo_internal_links`,`seo_query_cluster`,`seo_quick_wins`]},{category:`ai-search`,register:UF,names:[`seo_ai_referrals`,`seo_community_intent`,`seo_content_optimization`,`seo_page_opportunities`,`seo_performance_audit`,`seo_to_ai_query`]},{category:`crawl`,register:qF,names:[`seo_affected_urls`,`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:XF,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:cI,names:[`seo_audit_page`,`seo_monthly_report`,`seo_report_narrative`,`seo_second_page`]},{category:`experiments`,register:YF,names:[`seo_measure_change`]},{category:`reporting`,register:$F,names:[`seo_pseo_audit`]},{category:`workflows`,register:dI,names:[`seo_workflow_diagnose_property`,`seo_workflow_monthly_report`,`seo_workflow_refresh_priorities`,`seo_workflow_technical_watch`,`seo_workflow_update_postmortem`]}],mI={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 hI(e){let t=e.replace(/^seo_/,``).replaceAll(`_`,`-`);return mI[t]??t}function gI(e,t){return e.id<t.id?-1:+(e.id>t.id)}function _I(e){let t=new Map;return e({registerTool(e,n,r){t.set(e,{inputSchema:n.inputSchema,handler:r})}}),t}function vI(e){return e instanceof O.ZodObject?e:O.strictObject(e??{})}function yI(){let e=new Map,t=[];for(let n of pI){let r=e.get(n.register);r||(r=_I(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=hI(e),o=tI(a);if(!o)throw Error(`MCP report registry is missing guidance for ${a}.`);t.push({id:a,category:n.category,...o,inputSchema:vI(i.inputSchema),handler:i.handler})}}return t.sort(gI)}const bI=yI(),xI=new Set(bI.map(e=>e.id)),SI=Object.keys(eI).filter(e=>!xI.has(e));if(SI.length>0)throw Error(`MCP report guidance references unknown reports: ${SI.join(`, `)}.`);const CI=new Map(bI.map(e=>[e.id,e]));function wI(e){return bI.filter(t=>!e||t.category===e).map(({id:e,name:t,description:n,category:r})=>({id:e,category:r,name:t,description:n}))}function TI(e){return CI.get(e)}function EI(e){return e.issues.map(e=>`${e.path.length>0?e.path.join(`.`):`input`}: ${e.message}`).join(`; `)}function DI(e){return wI(e)}function OI(e){let t=TI(e);if(!t)throw new A(`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,inputSchema:O.toJSONSchema(t.inputSchema)}}async function kI(e,t={}){let n=TI(e);if(!n)throw new A(`INVALID_INPUT`,`Unknown report: ${e}.`);let r=n.inputSchema.safeParse(t);if(!r.success)throw new A(`INVALID_INPUT`,`Invalid parameters for ${e}: ${EI(r.error)}`);return n.handler(r.data)}async function AI(e,t={}){try{return await kI(e,t)}catch(e){return Q(e)}}const jI=O.looseObject({});function MI(e){e.registerTool(`seo_list_reports`,{description:`List compact SEO report ids, names, and purposes, optionally by category`,inputSchema:{category:O.enum(fI).optional()},outputSchema:jI,annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0}},async({category:e})=>{let t=DI(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:O.string().trim().min(1).max(100)},outputSchema:jI,annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0}},async({id:e})=>{try{let t=OI(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:O.string().trim().min(1).max(100),params:O.record(O.string(),O.unknown()).optional()},outputSchema:jI,annotations:{destructiveHint:!1}},async({id:e,params:t})=>AI(e,t))}function NI(){let e=new ce({name:`seo`,version:PF},{capabilities:{logging:{}}});return MI(e),e}async function PI(e={}){let t=NI();if(e.test){process.stdout.write(`seo MCP server constructed successfully.
712
- `);return}let n=new le;await t.connect(n)}export{Z as $,ut as $t,WA as A,My as At,rO as B,Hn as Bt,DN as C,Jb as Ct,kj as D,Gb as Dt,TM as E,qb as Et,Tk as F,I_ as Ft,bD as G,dn as Gt,HD as H,hn as Ht,lk as I,S_ as It,dD as J,$t as Jt,yD as K,cn as Kt,NO as L,s_ as Lt,jA as M,dy as Mt,hA as N,rv as Nt,QA as O,Ly as Ot,rA as P,A_ as Pt,DT as Q,mt as Qt,nO as R,e_ as Rt,ON as S,hS as St,vM as T,Kb as Tt,UD as U,fn as Ut,tO as V,mn as Vt,SD as W,pn as Wt,xE as X,Kt as Xt,GE as Y,Jt as Yt,aE as Z,lt as Zt,XP as _,US as _t,DI as a,Ne as an,VC as at,lP as b,ES as bt,PF as c,Se as cn,aC as ct,bF as d,oC as dt,Qe as en,Xw as et,yF as f,sC as ft,QP as g,VS as gt,hF as h,zS as ht,kI as i,Pe as in,iw as it,LA as j,xy as jt,YA as k,Iy as kt,TF as l,cC as lt,CF as m,tC as mt,NI as n,$e as nn,aw as nt,AI as o,A as on,HC as ot,xF as p,nC as pt,mD as q,sn as qt,OI as r,pt as rn,XC as rt,PI as s,we as sn,NC as st,fI as t,dt as tn,$C as tt,SF as u,lC as ut,kP as v,HS as vt,QM as w,Yb as wt,XN as x,CS as xt,OP as y,IS as yt,aO as z,qg as zt};
713
- //# sourceMappingURL=dist-CSY2_p-j.js.map
711
+ VALUES (?, ?, ?, ?, ?, ?, ?)`).run(e,u,JSON.stringify(c),JSON.stringify(g),_,Date.now(),Date.now()+i),{data:g,usage:{provider:`Semrush`,units:_,unitLabel:`units`,estimatedUsd:qF(_),calls:1}}}function YF(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 XF(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 ZF=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 JF(`phrase_this`,{phrase:e,database:t.database??`us`,export_columns:`Ph,Nq,Cp,Co,Nr,Td,Kd`},YF,7*864e5,10,t.refresh)}async batchKeywordOverview(e,t={}){return JF(`phrase_these`,{phrase:e.join(`;`),database:t.database??`us`,export_columns:`Ph,Nq,Cp,Co,Nr,Td,Kd`},e=>XF(e).map(e=>({...e,phrase:e.phrase})),7*864e5,10,t.refresh)}async relatedKeywords(e,t={}){return JF(`phrase_related`,{phrase:e,database:t.database??`us`,display_limit:20,export_columns:`Ph,Nq,Kd,Cp,Co`},XF,14*864e5,40,t.refresh)}async questions(e,t={}){return JF(`phrase_questions`,{phrase:e,database:t.database??`us`,display_limit:20,export_columns:`Ph,Nq,Kd,Cp,Co`},XF,14*864e5,40,t.refresh)}async keywordDifficulty(e,t={}){return JF(`phrase_kdi`,{phrase:e.join(`;`),database:t.database??`us`,export_columns:`Ph,Kd`},e=>XF(e).map(e=>({phrase:e.phrase,kd:e.difficulty??0})),7*864e5,50,t.refresh)}async domainKeywords(e,t={}){return JF(`domain_organic`,{domain:e,database:t.database??`us`,display_limit:100,export_columns:`Ph,Nq,Cp,Co,Kd,Po,Ur,Dn`},XF,7*864e5,10,t.refresh)}async urlKeywords(e,t={}){return JF(`url_organic`,{url:e,database:t.database??`us`,display_limit:100,export_columns:`Ph,Nq,Cp,Co,Kd,Po,Ur,Dn`},XF,7*864e5,10,t.refresh)}};function QF(e){let t=Qe(),n=e??t.providers.prefer;if(n===`authoritative`&&t.providers.semrushApiKey)return new ZF;if(n===`cheap`&&t.providers.dataForSeoLogin&&t.providers.dataForSeoPassword)return new GF;if(t.providers.semrushApiKey)return new ZF;if(t.providers.dataForSeoLogin&&t.providers.dataForSeoPassword)return new GF}new class{getGoogleOAuthClient(){let e=ft();if(e)return{clientId:e.clientId,clientSecret:e.clientSecret,source:`byo`};let t=Qe(),n=process.env.SEO_GOOGLE_CLIENT_ID??process.env.GSC_CLIENT_ID??gt.clientId??t.auth.sharedClientId,r=process.env.SEO_GOOGLE_CLIENT_SECRET??process.env.GSC_CLIENT_SECRET??gt.clientSecret??t.auth.sharedClientSecret;if(!(!n||!r))return{clientId:n,clientSecret:r,source:`shared`}}readGoogleTokens(){return st()}writeGoogleTokens(e){return ct(e)}deleteGoogleTokens(){return lt()}};const $F=`0.2.1`,eI=O.iso.date(`Use a YYYY-MM-DD date.`),tI=O.string().regex(/^\d{4}-(?:0[1-9]|1[0-2])$/,`Use a YYYY-MM month.`);function nI(e,t){return e===void 0?t:e}const rI={site:{mcp:`site`,schema:O.string()},days:{mcp:`days`,schema:O.number().optional()},recentDays:{mcp:`recentDays`,schema:O.number().optional()},range:{mcp:`range`,schema:O.number().optional()},limit:{mcp:`limit`,schema:O.number().optional()},checkLimit:{mcp:`checkLimit`,schema:O.number().optional()},minImpressions:{mcp:`minImpressions`,schema:O.number().optional()},includeBrand:{mcp:`includeBrand`,schema:O.boolean().optional()},verifyContent:{mcp:`verifyContent`,schema:O.boolean().optional()},verifyLimit:{mcp:`verifyLimit`,schema:O.number().optional()},js:{mcp:`js`,schema:O.boolean().optional()},fetchConcurrency:{mcp:`fetchConcurrency`,schema:O.number().optional()},fetchIntervalCap:{mcp:`fetchIntervalCap`,schema:O.number().optional()},fetchIntervalMs:{mcp:`fetchIntervalMs`,schema:O.number().optional()},refresh:{mcp:`refresh`,schema:O.boolean().optional()}};function iI(e){return Object.fromEntries(e.map(e=>{let t=rI[e];return[t.mcp,t.schema]}))}function aI(e){return JSON.stringify(e,null,2)}function Q(e){let t=we(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 oI=O.union([eI,O.string().regex(/^(?:today|yesterday|\d+daysAgo)$/)]).describe(`Use YYYY-MM-DD or a GA4 relative date.`),sI={site:O.string().trim().min(1),days:O.number().int().min(1).max(548).optional(),startDate:eI.optional(),endDate:eI.optional(),limit:O.number().int().min(1).max(100).optional(),minImpressions:O.number().int().min(0).max(1e9).optional(),maxRows:O.number().int().min(1).max(5e4).optional(),brandTerms:O.array(O.string().trim().min(1)).max(100).optional(),includeBrand:O.boolean().optional(),refresh:O.boolean().optional()};function cI(e,t={}){e.registerTool(`seo_ai_referrals`,{description:`Find AI referral traffic detected in GA4. maxRows bounds retained provider rows; resultLimit bounds returned landing pages.`,inputSchema:{property:O.string().trim().min(1),startDate:oI.optional(),endDate:oI.optional(),maxRows:O.number().int().min(1).max(1e5).describe(`Maximum retained GA4 rows. If limit is also supplied, both values must match.`).optional(),limit:O.number().int().min(1).max(1e5).describe(`Legacy alias for maxRows. If maxRows is also supplied, both values must match.`).optional(),resultLimit:O.number().int().min(1).max(1e3).describe(`Maximum ranked landing pages returned. Defaults to 25.`).optional(),refresh:O.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 A(`INVALID_INPUT`,`maxRows and the legacy limit option must match when both are provided.`);let c=await(t.aiReferralsReport??Hn)({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:{...iI([`site`,`days`,`limit`,`minImpressions`,`includeBrand`,`verifyContent`,`refresh`,`js`]),days:O.number().int().min(1).max(548).optional(),limit:O.number().int().min(1).max(100).optional(),minImpressions:O.number().int().positive().optional(),url:O.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??dy)({site:e,url:n,days:r,limit:i,minImpressions:a,includeBrand:o,verifyContent:s,refresh:c,js:nI(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:{...iI([`site`,`days`,`limit`,`minImpressions`,`includeBrand`,`verifyContent`,`refresh`,`js`]),days:O.number().int().min(1).max(548).optional(),limit:O.number().int().min(1).max(100).optional(),minImpressions:O.number().int().positive().optional(),url:O.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??xy)({site:e,url:n,days:r,limit:i,minImpressions:a,includeBrand:o,verifyContent:s,refresh:c,js:nI(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:O.string().url(),strategy:O.enum([`mobile`,`desktop`]).optional(),refresh:O.boolean().optional()}},async({url:e,strategy:t,refresh:n})=>{try{let r=await Jj({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:sI},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??OP)({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:sI},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??rv)({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 lI(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 uI(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:NC(e,{limit:10}),reviewObservations:PC(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 dI(e){if(!e.value||e.value===`latest`||e.value===`previous`){let t=Dx({site:e.site,limit:20}).filter(t=>t.id!==e.skipId),n=e.value===`previous`?t[1]:t[0];return n?Ox(n.id):void 0}return Ox(e.value)}function fI(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:O.string().url(),site:O.string().optional(),ga4PropertyId:O.string().optional(),maxPages:O.number().int().positive().optional(),maxDepth:O.number().int().nonnegative().optional(),concurrency:O.number().int().positive().optional(),fetchIntervalCap:O.number().int().positive().optional(),fetchIntervalMs:O.number().int().positive().optional(),refresh:O.boolean().optional(),include:O.array(O.string()).optional(),exclude:O.array(O.string()).optional(),useSitemap:O.boolean().optional(),respectRobots:O.boolean().optional(),js:O.boolean().optional(),includePages:O.boolean().optional(),includeIssues:O.boolean().optional(),saveReport:O.boolean().optional()}},async({url:e,site:t,ga4PropertyId: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 XS({url:e,site:t,ga4PropertyId:n,maxPages:r,maxDepth:i,concurrency:a,refresh:c,fetchRate:lI({fetchConcurrency:a,fetchIntervalCap:o,fetchIntervalMs:s}),include:l,exclude:u,useSitemap:d,respectRobots:f,js:!!p}),v=g?Ex(_):void 0;return $(`Crawl complete for ${e}. Found ${_.issues.length} issues across ${_.summary.totalPages} pages.`,{...uI(_,{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:O.array(O.string().url()).min(1),site:O.string().optional(),ga4PropertyId:O.string().optional(),maxPages:O.number().int().positive().optional(),concurrency:O.number().int().positive().optional(),fetchIntervalCap:O.number().int().positive().optional(),fetchIntervalMs:O.number().int().positive().optional(),refresh:O.boolean().optional(),includePages:O.boolean().optional(),includeIssues:O.boolean().optional(),saveReport:O.boolean().optional()}},async({urls:e,site:t,ga4PropertyId:n,maxPages:r,concurrency:i,fetchIntervalCap:a,fetchIntervalMs:o,refresh:s,includePages:c,includeIssues:l,saveReport:u})=>{try{let d=await XS({url:e[0]??``,urls:e,mode:`list`,site:t,ga4PropertyId:n,maxPages:r??e.length,concurrency:i,refresh:s,fetchRate:lI({fetchConcurrency:i,fetchIntervalCap:a,fetchIntervalMs:o}),useSitemap:!1}),f=u?Ex(d):void 0;return $(`URL audit complete. Found ${d.issues.length} issues across ${d.summary.totalPages} pages.`,{...uI(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:O.string().url().optional(),reportId:O.string().optional(),site:O.string().optional(),maxPages:O.number().int().positive().optional(),maxDepth:O.number().int().nonnegative().optional(),fetchIntervalCap:O.number().int().positive().optional(),fetchIntervalMs:O.number().int().positive().optional(),refresh:O.boolean().optional(),category:O.string().optional(),limit:O.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{let u=e?await XS({url:e,site:n,maxPages:r,maxDepth:i,refresh:s,fetchRate:lI({fetchIntervalCap:a,fetchIntervalMs:o})}):t?Ox(t):Ax(n);if(!u)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let d=NC(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:O.string()}},async({ruleId:e})=>{let t=vb(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:Dw.optional()}},async({category:e})=>{let t=_b().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:O.string().url().optional(),reportId:O.string().optional(),site:O.string().optional(),ruleId:O.string().optional(),category:O.string().optional(),severity:O.string().optional(),maxPages:O.number().int().positive().optional(),fetchIntervalCap:O.number().int().positive().optional(),fetchIntervalMs:O.number().int().positive().optional(),refresh:O.boolean().optional(),limit:O.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 XS({url:e,site:n,maxPages:o,refresh:l,fetchRate:lI({fetchIntervalCap:s,fetchIntervalMs:c})}):t?Ox(t):Ax(n);if(!d)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let f=Cy(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:O.string().url().optional(),reportId:O.string().optional(),site:O.string().optional(),maxPages:O.number().int().positive().optional(),fetchIntervalCap:O.number().int().positive().optional(),fetchIntervalMs:O.number().int().positive().optional(),refresh:O.boolean().optional(),limit:O.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 XS({url:e,site:n,maxPages:r,refresh:o,fetchRate:lI({fetchIntervalCap:i,fetchIntervalMs:a})}):t?Ox(t):Ax(n);if(!c)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let l=zy(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_ai_readiness`,{description:`Return AI-search technical evidence and optional observations without an aggregate score or citation prediction.`,inputSchema:{url:O.string().url().optional(),reportId:O.string().optional(),site:O.string().optional(),maxPages:O.number().int().positive().optional(),fetchIntervalCap:O.number().int().positive().optional(),fetchIntervalMs:O.number().int().positive().optional(),refresh:O.boolean().optional()}},async({url:e,reportId:t,site:n,maxPages:r,fetchIntervalCap:i,fetchIntervalMs:a,refresh:o})=>{try{let s=e?await XS({url:e,site:n,maxPages:r,refresh:o,fetchRate:lI({fetchIntervalCap:i,fetchIntervalMs:a})}):t?Ox(t):Ax(n);if(!s)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let c=My(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:O.string().url().optional(),reportId:O.string().optional(),site:O.string().optional(),maxPages:O.number().int().positive().optional(),fetchIntervalCap:O.number().int().positive().optional(),fetchIntervalMs:O.number().int().positive().optional(),refresh:O.boolean().optional()}},async({url:e,reportId:t,site:n,maxPages:r,fetchIntervalCap:i,fetchIntervalMs:a,refresh:o})=>{try{let s=e?await XS({url:e,site:n,maxPages:r,refresh:o,fetchRate:lI({fetchIntervalCap:i,fetchIntervalMs:a})}):t?Ox(t):Ax(n);if(!s)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let c=pb(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:O.string().url().optional(),reportId:O.string().optional(),site:O.string().optional(),maxPages:O.number().int().positive().optional(),fetchIntervalCap:O.number().int().positive().optional(),fetchIntervalMs:O.number().int().positive().optional(),refresh:O.boolean().optional()}},async({url:e,reportId:t,site:n,maxPages:r,fetchIntervalCap:i,fetchIntervalMs:a,refresh:o})=>{try{let s=e?await XS({url:e,site:n,maxPages:r,refresh:o,fetchRate:lI({fetchIntervalCap:i,fetchIntervalMs:a})}):t?Ox(t):Ax(n);if(!s)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let c=qy(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:O.string().url().optional(),reportId:O.string().optional(),site:O.string().optional(),maxPages:O.number().int().positive().optional(),maxUrls:O.number().int().positive().optional(),tokenBudget:O.number().int().positive().optional(),exclude:O.array(O.string()).optional(),title:O.string().optional(),description:O.string().optional(),fetchIntervalCap:O.number().int().positive().optional(),fetchIntervalMs:O.number().int().positive().optional(),refresh:O.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 XS({url:e,site:n,maxPages:r,refresh:d,fetchRate:lI({fetchIntervalCap:l,fetchIntervalMs:u})}):t?Ox(t):Ax(n);if(!f)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let p=Jy(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:O.string().url().optional(),reportId:O.string().optional(),site:O.string().optional(),maxPages:O.number().int().positive().optional(),fetchIntervalCap:O.number().int().positive().optional(),fetchIntervalMs:O.number().int().positive().optional(),refresh:O.boolean().optional()}},async({url:e,reportId:t,site:n,maxPages:r,fetchIntervalCap:i,fetchIntervalMs:a,refresh:o})=>{try{let s=e?await XS({url:e,site:n,maxPages:r,refresh:o,fetchRate:lI({fetchIntervalCap:i,fetchIntervalMs:a})}):t?Ox(t):Ax(n);if(!s)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let c=Fy(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:O.string().url().optional(),reportId:O.string().optional(),site:O.string().optional(),maxPages:O.number().int().positive().optional(),maxConcepts:O.number().int().min(1).max(100).optional(),includeFiles:O.boolean().optional(),title:O.string().min(1).max(200).optional(),fetchIntervalCap:O.number().int().positive().optional(),fetchIntervalMs:O.number().int().positive().optional(),refresh:O.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 XS({url:e,site:n,maxPages:r,refresh:l,fetchRate:lI({fetchIntervalCap:s,fetchIntervalMs:c})}):t?Ox(t):Ax(n);if(!u)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let d=vw(u,{maxConcepts:i??(a?25:100),title:o}),f=Cw(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:O.array(O.object({path:O.string().min(1).max(512),content:O.string().max(2e6)})).min(1).max(5006)}},async({files:e})=>{let t=Cw(e);return $(t.valid?`Bundle passes seo OKF checks.`:`Bundle has seo OKF issues.`,{validation:t,explanation:ww(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:O.string().optional(),site:O.string().optional(),includePages:O.boolean().optional(),includeIssues:O.boolean().optional()}},async({id:e,site:t,includePages:n,includeIssues:r})=>{try{let i=e?Ox(e):Ax(t);return i?$(`Saved crawl report ${i.id}.`,{...uI(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:O.string().optional(),limit:O.number().int().positive().optional()}},async({site:e,limit:t})=>{try{let n=Dx({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:O.string().optional(),before:O.string().optional(),site:O.string().optional()}},async({after:e,before:t,site:n})=>{try{let r=dI({value:e??`latest`,site:n});if(!r)return Q(`No newer crawl report found.`);let i=dI({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=sw({before:i,after:r});return $(a.headline,a)}catch(e){return Q(e)}})}function pI(e,t={}){e.registerTool(`seo_doctor`,{description:`Check local auth, scopes, config, and defaults`,inputSchema:{}},async()=>{try{let e=await WF();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:{...iI([`site`,`days`,`recentDays`,`limit`,`includeBrand`,`refresh`])}},async({site:e,days:t,recentDays:n,limit:r,includeBrand:i,refresh:a})=>{try{let o=await bO({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:O.string().trim().min(1),days:O.number().int().min(1).max(240).optional(),limit:O.number().int().min(1).max(100).optional(),refresh:O.boolean().optional(),dimension:O.enum([`page`,`query`,`country`,`device`]).optional(),compareDays:O.number().int().min(1).max(240).optional(),startDate:eI.optional(),endDate:eI.optional(),maxRows:O.number().int().min(1).max(25e4).optional(),unmatchedLimit:O.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??KT)({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:{...iI([`site`,`days`,`minImpressions`,`limit`,`verifyContent`,`verifyLimit`,`includeBrand`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),days:O.number().int().min(1).max(548).optional(),minImpressions:O.number().int().min(0).max(1e9).optional(),limit:O.number().int().min(1).max(100).optional(),verifyLimit:O.number().int().min(0).max(100).optional(),brandTerms:O.array(O.string().trim().min(1).max(200)).max(20).optional(),fetchConcurrency:O.number().int().min(1).max(16).optional(),fetchIntervalCap:O.number().int().min(1).max(60).optional(),fetchIntervalMs:O.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 AD({site:e,days:t,minImpressions:n,limit:r,verifyContent:i,verifyLimit:a,includeBrand:o,brandTerms:s,js:nI(c,void 0),rate:lI({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:{...iI([`site`,`days`,`recentDays`,`refresh`])}},async({site:e,days:t,recentDays:n,refresh:r})=>{try{let i=await sO({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:{...iI([`site`,`days`,`recentDays`,`refresh`]),paddingDays:O.number().optional()}},async({site:e,days:t,recentDays:n,paddingDays:r,refresh:i})=>{try{let a=await cO({site:e,days:t,recentDays:n,paddingDays:r,refresh:i});return $(`Classification: ${a.classification}.`,a)}catch(e){return Q(e)}})}function mI(e){e.registerTool(`seo_content_groups`,{description:`List, create, or delete reusable page/query groups`,inputSchema:{action:O.enum([`list`,`add`,`delete`]),site:O.string().optional(),id:O.string().optional(),name:O.string().optional(),dimension:O.enum([`page`,`query`]).optional(),matchType:O.enum([`equals`,`contains`,`regex`]).optional(),pattern:O.string().optional()}},async({action:e,site:t,id:n,name:r,dimension:i,matchType:a,pattern:o})=>{try{if(e===`list`){let e=SO(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=wO(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=xO({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:O.enum([`list`,`add`,`delete`]),site:O.string().optional(),id:O.string().optional(),limit:O.number().optional(),scope:O.enum([`site`,`page`,`query`,`group`]).optional(),target:O.string().optional(),title:O.string().optional(),description:O.string().optional(),changedAt:O.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=zD({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=VD(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=RD({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 GA4/control evidence`,inputSchema:{id:O.string().min(1).optional(),site:O.string().min(1).optional(),scope:O.enum([`site`,`page`,`query`,`group`]).optional(),target:O.string().min(1).optional(),title:O.string().min(1).optional(),changedAt:eI.optional(),ga4PropertyId:O.string().min(1).optional(),controlScope:O.enum([`site`,`page`,`query`,`group`]).optional(),controlTarget:O.string().min(1).optional(),controlTitle:O.string().min(1).optional(),beforeDays:O.number().int().min(1).max(548).optional(),afterDays:O.number().int().min(1).max(548).optional(),refresh:O.boolean().optional()}},async({id:e,site:t,scope:n,target:r,title:i,changedAt:a,ga4PropertyId:o,controlScope:s,controlTarget:c,controlTitle:l,beforeDays:u,afterDays:d,refresh:f})=>{try{let p=await QO({id:e,site:t,scope:n,target:r,title:i,changedAt:a,ga4PropertyId: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 hI(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:O.string().url(),site:O.string().optional(),limit:O.number().optional(),refresh:O.boolean().optional(),js:O.boolean().optional()}},async({startUrl:e,site:t,limit:n,refresh:r,js:i})=>{try{let a=await Wk({startUrl:e,site:t,limit:n,refresh:r,js:nI(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:O.string(),urls:O.array(O.string().url()).min(1).max(100),languageCode:O.string().min(1).max(35).optional(),dailyLimit:O.number().int().min(1).max(2e3).optional()}},async({site:e,urls:t,languageCode:n,dailyLimit:r})=>{try{let i=await XA({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:O.string(),sitemaps:O.array(O.string().url()).min(1).max(20),properties:O.array(O.string()).optional(),dailyLimit:O.number().int().min(1).max(2e3).optional(),targetCycleDays:O.number().int().min(1).max(365).optional(),maxUrls:O.number().int().min(1).max(25e4).optional()}},async({site:e,sitemaps:t,properties:n,dailyLimit:r,targetCycleDays:i,maxUrls:a})=>{try{let o=await PA({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:O.string().min(1),crawlReportId:O.string().min(1).optional(),sitemaps:O.array(O.string().url()).min(1).max(20).optional(),days:O.number().int().min(1).max(548).optional(),rowLimit:O.number().int().min(1).max(25e4).optional(),maxSitemapUrls:O.number().int().min(1).max(25e4).optional(),itemsPerSection:O.number().int().min(1).max(1e3).optional(),templateClusters:O.number().int().min(1).max(200).optional(),templateSamples:O.number().int().min(1).max(25).optional(),refresh:O.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 SA({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:O.string(),sitemaps:O.array(O.string().url()).min(1).max(20),properties:O.array(O.string()).optional(),dailyLimit:O.number().int().min(1).max(2e3).optional(),inspectLimit:O.number().int().min(1).max(100).optional(),maxUrls:O.number().int().min(1).max(25e4).optional(),languageCode:O.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 nj({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:O.string().url(),maxHops:O.number().optional(),refresh:O.boolean().optional(),js:O.boolean().optional()}},async({url:e,maxHops:t,refresh:n,js:r})=>{try{let i=await lj({url:e,maxHops:t,refresh:n,js:nI(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:{...iI([`site`,`days`,`limit`,`minImpressions`,`refresh`,`js`]),minClicks:O.number().optional()}},async({site:e,days:t,limit:n,minClicks:r,minImpressions:i,refresh:a,js:o})=>{try{let s=await mj({site:e,days:t,limit:n,minClicks:r,minImpressions:i,refresh:a,js:nI(o,`auto`)});return $(`Checked ${s.summary.checked} search-value URLs. ${s.summary.recoverable} recoverable issues.`,s)}catch(e){return Q(e)}})}function gI(e,t={}){e.registerTool(`seo_cannibal`,{description:`Find multi-URL query exposure candidates for intent and technical review`,inputSchema:{...iI([`site`,`days`,`limit`,`minImpressions`,`includeBrand`,`refresh`]),days:O.number().int().min(1).max(548).optional(),limit:O.number().int().min(1).max(100).optional(),minImpressions:O.number().int().min(0).max(1e9).optional(),brandTerms:O.array(O.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 wE({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:{...iI([`site`,`days`,`limit`,`includeBrand`,`refresh`]),days:O.number().int().min(1).max(548).optional(),limit:O.number().int().min(1).max(100).optional(),comparison:O.enum([`previous-period`,`year-over-year`]).optional(),minDropPct:O.number().min(0).max(100).optional(),minPreviousClicks:O.number().min(0).max(1e9).optional(),minClickLoss:O.number().min(0).max(1e9).optional(),brandTerms:O.array(O.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 BE({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:{...iI([`site`,`days`,`limit`,`minImpressions`,`verifyContent`,`verifyLimit`,`includeBrand`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),days:O.number().int().min(1).max(548).optional(),limit:O.number().int().min(1).max(100).optional(),minImpressions:O.number().int().min(0).max(1e9).optional(),verifyLimit:O.number().int().min(0).max(100).optional(),brandTerms:O.array(O.string().trim().min(1).max(200)).max(20).optional(),fetchConcurrency:O.number().int().min(1).max(16).optional(),fetchIntervalCap:O.number().int().min(1).max(60).optional(),fetchIntervalMs:O.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??uD)({site:e,days:n,limit:r,minImpressions:i,brandTerms:a,verifyContent:o,verifyLimit:s,includeBrand:c,js:nI(l,void 0),rate:lI({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:{...iI([`site`,`days`,`limit`,`checkLimit`,`minImpressions`,`includeBrand`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),targetUrl:O.string().url().refine(e=>/^https?:\/\//.test(e),`Use an HTTP(S) URL.`),days:O.number().int().min(1).max(548).optional(),limit:O.number().int().min(1).max(100).optional(),checkLimit:O.number().int().min(1).max(200).optional(),minImpressions:O.number().min(0).max(1e9).optional(),brandTerms:O.array(O.string().trim().min(1).max(200)).max(20).optional(),fetchConcurrency:O.number().int().min(1).max(16).optional(),fetchIntervalCap:O.number().int().min(1).max(60).optional(),fetchIntervalMs:O.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??Ok)({site:e,targetUrl:n,days:r,limit:i,checkLimit:a,minImpressions:o,brandTerms:s,includeBrand:c,js:nI(l,void 0),rate:lI({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:{...iI([`site`,`minImpressions`,`limit`,`includeBrand`,`refresh`]),site:O.string().trim().min(1),minImpressions:O.number().int().min(1).max(1e9).optional(),limit:O.number().int().min(1).max(100).optional(),brandTerms:O.array(O.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??hT)({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:{...iI([`site`,`includeBrand`,`minImpressions`,`limit`,`refresh`]),site:O.string().trim().min(1),scope:O.string().trim().min(1).max(2e3).optional(),minImpressions:O.number().int().min(1).max(1e9).optional(),limit:O.number().int().min(1).max(100).optional(),brandTerms:O.array(O.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??_N)({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:O.enum([`phrase_this`,`phrase_related`,`phrase_questions`]),phrase:O.string()}},async({endpoint:e,phrase:t})=>{try{let n=QF(`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 $(aI(r.data),r)}catch(e){return Q(e)}})}function _I(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 vI(e){e.registerTool(`seo_pseo_audit`,{description:`Audit pSEO templates with first-party GSC, optional crawl, and optional URL Inspection evidence`,inputSchema:{site:O.string().min(1),days:O.number().int().min(1).max(548).optional(),sitemaps:O.array(O.string().url()).max(20).optional(),maxSitemapUrls:O.number().int().min(1).max(1e5).optional(),templateLimit:O.number().int().min(1).max(100).optional(),minimumTemplateUrls:O.number().int().min(2).max(100).optional(),minimumTemplateShare:O.number().min(0).max(1).optional(),minimumTemplateImpressions:O.number().min(0).max(1e9).optional(),crawlSamples:O.number().int().min(0).max(10).optional(),inspectSamples:O.number().int().min(0).max(10).optional(),brandTerms:O.array(O.string().min(1).max(200)).max(20).optional(),includeBrand:O.boolean().optional(),refresh:O.boolean().optional(),js:O.boolean().optional(),fetchConcurrency:O.number().int().min(1).max(20).optional(),fetchIntervalCap:O.number().int().min(1).max(100).optional(),fetchIntervalMs:O.number().int().min(100).max(6e4).optional(),detail:O.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 WM({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:nI(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:XM(v)}:{report:_I(v)},y?{markdown:ZM(v)}:void 0)}catch(e){return Q(e)}})}const yI={"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.`}]},"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 GA4 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 GA4 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`,`GA4 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 GA4 session signal have different semantics and are not interchangeable.`,`Missing GA4 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 GA4 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.`}]}},bI={"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.`},"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 GA4 and see a ranked, bounded set of landing pages that received them.`,useWhen:[`You want measured AI referral traffic from GA4.`,`You need the landing pages and sources behind those visits.`],avoidWhen:[`GA4 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 xI(e){let t=bI[e],n=yI[e];if(!(!t||!n))return{...t,...n}}function SI(e){e.registerTool(`seo_audit_page`,{description:`Run a single-page technical and content audit`,inputSchema:{url:O.string().url(),site:O.string().optional(),js:O.boolean().optional(),refresh:O.boolean().optional()}},async({url:e,site:t,js:n,refresh:r})=>{try{let i=await S_({url:e,site:t,js:nI(n,`auto`),refresh:r});return $(`Audit complete for ${e}. Found ${i.issues.length} issues.`,i)}catch(e){return Q(e)}})}const CI=iI([`verifyContent`,`verifyLimit`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]);function wI(e){return{verifyContent:e.verifyContent,verifyLimit:e.verifyLimit,js:nI(e.js,void 0),rate:lI({fetchConcurrency:e.fetchConcurrency,fetchIntervalCap:e.fetchIntervalCap,fetchIntervalMs:e.fetchIntervalMs}),refresh:e.refresh}}function TI(e){e.registerTool(`seo_monthly_report`,{description:`Generate a monthly SEO report narrative`,inputSchema:{...iI([`site`,`limit`,`includeBrand`]),month:tI.optional(),...CI}},async({site:e,month:t,limit:n,includeBrand:r,...i})=>{try{let a=await qN({site:e,month:t,limit:n,includeBrand:r,...wI(i)});return $(`Monthly report generated for ${a.month}. ${a.headline}`,{...a,presentation:JN(a)},{markdown:a.markdown})}catch(e){return Q(e)}})}function EI(e){e.registerTool(`seo_report_narrative`,{description:`Generate a client-ready SEO narrative across diagnosis, changes, and monitoring`,inputSchema:{...iI([`site`,`days`,`recentDays`,`limit`,`includeBrand`]),startDate:eI.optional(),endDate:eI.optional(),changeLimit:O.number().optional(),...CI}},async({site:e,days:t,recentDays:n,startDate:r,endDate:i,limit:a,changeLimit:o,includeBrand:s,...c})=>{try{let l=await KN({site:e,days:t,recentDays:n,startDate:r,endDate:i,limit:a,changeLimit:o,includeBrand:s,...wI(c)});return $(l.headline,{...l,presentation:JN(l)},{markdown:l.markdown})}catch(e){return Q(e)}})}function DI(e){e.registerTool(`seo_second_page`,{description:`Find URLs with GSC average position above 10 through 20 and return evidence-grounded investigation prompts`,inputSchema:{...iI([`site`,`range`,`minImpressions`,`limit`,`includeBrand`]),...CI,range:O.number().int().min(1).max(548).optional(),minImpressions:O.number().int().min(0).max(1e9).optional(),limit:O.number().int().min(1).max(100).optional(),verifyLimit:O.number().int().min(0).max(100).optional(),brandTerms:O.array(O.string().trim().min(1).max(200)).max(20).optional(),fetchConcurrency:O.number().int().min(1).max(16).optional(),fetchIntervalCap:O.number().int().min(1).max(60).optional(),fetchIntervalMs:O.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 hP({site:e,range:t,minImpressions:n,limit:r,includeBrand:i,brandTerms:a,...wI(o)});return $(s.summary.verdict,s)}catch(e){return Q(e)}})}function OI(e){EI(e),TI(e),SI(e),DI(e)}const kI=O.strictObject({site:O.string().trim().min(1).max(2048),startUrl:O.string().url().optional(),urls:O.array(O.string().url()).min(1).max(100).optional(),sitemaps:O.array(O.string().url()).min(1).max(20).optional(),properties:O.array(O.string().trim().min(1).max(2048)).min(1).max(1e3).optional(),limit:O.number().int().min(1).max(25e4).optional(),refresh:O.boolean().optional(),js:O.boolean().optional(),languageCode:O.string().trim().min(1).max(35).optional(),dailyLimit:O.number().int().min(1).max(2e3).optional(),inspectLimit:O.number().int().min(1).max(100).optional(),maxUrls:O.number().int().min(1).max(25e4).optional(),recoverLinks:O.boolean().optional(),recoverDays:O.number().int().min(1).max(548).optional(),recoverLimit:O.number().int().min(1).max(100).optional(),recoverMinClicks:O.number().int().min(0).max(1e9).optional(),recoverMinImpressions:O.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 AI(e){return $(e.summary,{...e,presentation:eF(e)},{markdown:tF(e)})}function jI(e){e.registerTool(`seo_workflow_diagnose_property`,{description:`Run the full agent workflow for property diagnosis and next actions`,inputSchema:{...iI([`site`,`days`,`recentDays`,`limit`,`includeBrand`,`refresh`])}},async({site:e,days:t,recentDays:n,limit:r,includeBrand:i,refresh:a})=>{try{return AI(await JP({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:{...iI([`site`,`limit`,`includeBrand`,`refresh`]),month:tI.optional()}},async({site:e,month:t,limit:n,includeBrand:r,refresh:i})=>{try{return AI(await nF({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:{...iI([`site`,`days`,`recentDays`,`limit`,`includeBrand`,`refresh`]),knownConfounders:O.array(O.string()).optional(),includeChangeLog:O.boolean().optional()}},async({site:e,days:t,recentDays:n,limit:r,includeBrand:i,knownConfounders:a,includeChangeLog:o,refresh:s})=>{try{return AI(await PF({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:kI},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 AI(await _F({site:e,startUrl:t,urls:n,sitemaps:r,properties:i,limit:a,refresh:o,js:nI(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:{...iI([`site`,`days`,`recentDays`,`limit`,`includeBrand`,`verifyContent`,`verifyLimit`,`refresh`]),ga4PropertyId:O.string().optional()}},async({site:e,days:t,recentDays:n,limit:r,includeBrand:i,ga4PropertyId:a,verifyContent:o,verifyLimit:s,refresh:c})=>{try{return AI(await hF({site:e,days:t,recentDays:n,limit:r,includeBrand:i,ga4PropertyId:a,verifyContent:o,verifyLimit:s,refresh:c}))}catch(e){return Q(e)}})}const MI=[`ai-search`,`crawl`,`diagnosis`,`experiments`,`monitoring`,`opportunities`,`reporting`,`setup`,`workflows`],NI=[{category:`setup`,register:pI,names:[`seo_doctor`]},{category:`diagnosis`,register:pI,names:[`seo_segment_impact`,`seo_striking_distance`,`seo_traffic_anomaly`,`seo_update_correlate`]},{category:`opportunities`,register:gI,names:[`seo_cannibal`,`seo_ctr_underperformers`,`seo_decaying`,`seo_internal_links`,`seo_query_cluster`,`seo_quick_wins`]},{category:`ai-search`,register:cI,names:[`seo_ai_referrals`,`seo_community_intent`,`seo_content_optimization`,`seo_page_opportunities`,`seo_performance_audit`,`seo_to_ai_query`]},{category:`ai-search`,register:fI,names:[`seo_ai_search_scorecard`]},{category:`crawl`,register:fI,names:[`seo_affected_urls`,`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:hI,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:OI,names:[`seo_audit_page`,`seo_monthly_report`,`seo_report_narrative`,`seo_second_page`]},{category:`experiments`,register:mI,names:[`seo_measure_change`]},{category:`reporting`,register:vI,names:[`seo_pseo_audit`]},{category:`workflows`,register:jI,names:[`seo_workflow_diagnose_property`,`seo_workflow_monthly_report`,`seo_workflow_refresh_priorities`,`seo_workflow_technical_watch`,`seo_workflow_update_postmortem`]}],PI={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 FI(e){let t=e.replace(/^seo_/,``).replaceAll(`_`,`-`);return PI[t]??t}function II(e,t){return e.id<t.id?-1:+(e.id>t.id)}function LI(e){let t=new Map;return e({registerTool(e,n,r){t.set(e,{inputSchema:n.inputSchema,handler:r})}}),t}function RI(e){return e instanceof O.ZodObject?e:O.strictObject(e??{})}function zI(){let e=new Map,t=[];for(let n of NI){let r=e.get(n.register);r||(r=LI(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=FI(e),o=xI(a);if(!o)throw Error(`MCP report registry is missing guidance for ${a}.`);t.push({id:a,category:n.category,...o,inputSchema:RI(i.inputSchema),handler:i.handler})}}return t.sort(II)}const BI=zI(),VI=new Set(BI.map(e=>e.id)),HI=Object.keys(bI).filter(e=>!VI.has(e));if(HI.length>0)throw Error(`MCP report guidance references unknown reports: ${HI.join(`, `)}.`);const UI=new Map(BI.map(e=>[e.id,e]));function WI(e){return BI.filter(t=>!e||t.category===e).map(({id:e,name:t,description:n,category:r})=>({id:e,category:r,name:t,description:n}))}function GI(e){return UI.get(e)}function KI(e){return e.issues.map(e=>`${e.path.length>0?e.path.join(`.`):`input`}: ${e.message}`).join(`; `)}function qI(e){return WI(e)}function JI(e){let t=GI(e);if(!t)throw new A(`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:O.toJSONSchema(t.inputSchema)}}async function YI(e,t={}){let n=GI(e);if(!n)throw new A(`INVALID_INPUT`,`Unknown report: ${e}.`);let r=n.inputSchema.safeParse(t);if(!r.success)throw new A(`INVALID_INPUT`,`Invalid parameters for ${e}: ${KI(r.error)}`);return n.handler(r.data)}async function XI(e,t={}){try{return await YI(e,t)}catch(e){return Q(e)}}const ZI=O.looseObject({});function QI(e){e.registerTool(`seo_list_reports`,{description:`List compact SEO report ids, names, and purposes, optionally by category`,inputSchema:{category:O.enum(MI).optional()},outputSchema:ZI,annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0}},async({category:e})=>{let t=qI(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:O.string().trim().min(1).max(100)},outputSchema:ZI,annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0}},async({id:e})=>{try{let t=JI(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:O.string().trim().min(1).max(100),params:O.record(O.string(),O.unknown()).optional()},outputSchema:ZI,annotations:{destructiveHint:!1}},async({id:e,params:t})=>XI(e,t))}function $I(){let e=new ce({name:`seo`,version:$F},{capabilities:{logging:{}}});return QI(e),e}async function eL(e={}){let t=$I();if(e.test){process.stdout.write(`seo MCP server constructed successfully.
712
+ `);return}let n=new le;await t.connect(n)}export{Z as $,ut as $t,lj as A,My as At,SO as B,Hn as Bt,KN as C,Ox as Ct,Jj as D,qy as Dt,WM as E,_b as Et,Wk as F,I_ as Ft,zD as G,dn as Gt,sO as H,hn as Ht,Ok as I,S_ as It,AD as J,$t as Jt,RD as K,cn as Kt,QO as L,s_ as Lt,XA as M,dy as Mt,PA as N,rv as Nt,_j as O,Jy as Ot,SA as P,A_ as Pt,KT as Q,mt as Qt,xO as R,e_ as Rt,qN as S,Dx as St,LM as T,vb as Tt,cO as U,fn as Ut,bO as V,mn as Vt,VD as W,pn as Wt,BE as X,Kt as Xt,uD as Y,Jt as Yt,wE as Z,lt as Zt,hF as _,mC as _t,qI as a,Ne as an,sw as at,OP as b,kx as bt,$F as c,Se as cn,VC as ct,zF as d,PC as dt,Qe as en,hT as et,RF as f,NC as ft,_F as g,bC as gt,PF as h,xC as ht,YI as i,Pe as in,Cw as it,nj as j,xy as jt,mj as k,Fy as kt,WF as l,RC as lt,HF as m,yC as mt,$I as n,$e as nn,ww as nt,XI as o,A as on,LC as ot,BF as p,_C as pt,ND as q,sn as qt,JI as r,pt as rn,hw as rt,eL as s,we as sn,BC as st,MI as t,dt as tn,vw as tt,VF as u,zC as ut,JP as v,iC as vt,_N as w,Ex as wt,hP as x,Ax as xt,qP as y,XS as yt,wO as z,qg as zt};
713
+ //# sourceMappingURL=dist-CTBhWxn6.js.map