@reddoorla/maintenance 0.87.0 → 0.88.3

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/dist/airtable-QG5ICI3Y.js +9 -0
  2. package/dist/{announce-I2Z4JN3I.js → announce-GKD5YJNB.js} +9 -9
  3. package/dist/{blux-ZGDSHI3U.js → blux-EATHOHKK.js} +5 -5
  4. package/dist/{bump-deps-CGVELRLK.js → bump-deps-KGH5UYSF.js} +4 -4
  5. package/dist/{chunk-SDDPUAWH.js → chunk-6I6L3CMX.js} +2 -2
  6. package/dist/{chunk-NXNPDCF3.js → chunk-6UCVWYDA.js} +11 -1
  7. package/dist/chunk-6UCVWYDA.js.map +1 -0
  8. package/dist/{chunk-ZBDZTJUO.js → chunk-7FFUBF4I.js} +5 -2
  9. package/dist/chunk-7FFUBF4I.js.map +1 -0
  10. package/dist/{chunk-3JQ2FO3D.js → chunk-B23FFWU4.js} +2 -2
  11. package/dist/{chunk-DJZWYIPP.js → chunk-BGTYPVLT.js} +1 -1
  12. package/dist/chunk-BGTYPVLT.js.map +1 -0
  13. package/dist/chunk-BRYXFH77.js +70 -0
  14. package/dist/chunk-BRYXFH77.js.map +1 -0
  15. package/dist/{chunk-HSI6Q7MI.js → chunk-CEW5PEHZ.js} +168 -31
  16. package/dist/chunk-CEW5PEHZ.js.map +1 -0
  17. package/dist/{chunk-NDWYJLRF.js → chunk-DGZWFI2G.js} +3 -3
  18. package/dist/{chunk-HHMMUR4P.js → chunk-GQDDWYU2.js} +45 -8
  19. package/dist/chunk-GQDDWYU2.js.map +1 -0
  20. package/dist/{chunk-BBUSGEES.js → chunk-J3WEZGIN.js} +6 -6
  21. package/dist/{chunk-BNM5POSJ.js → chunk-JRHIFA5R.js} +2 -2
  22. package/dist/{chunk-MT2JINSS.js → chunk-KJGKZLCT.js} +3 -3
  23. package/dist/{chunk-3SF7D4DY.js → chunk-MFHS7IQ2.js} +36 -1
  24. package/dist/{chunk-3SF7D4DY.js.map → chunk-MFHS7IQ2.js.map} +1 -1
  25. package/dist/{chunk-SNSTJLQW.js → chunk-NO74DXDP.js} +48 -2
  26. package/dist/chunk-NO74DXDP.js.map +1 -0
  27. package/dist/{chunk-C6B4RHXR.js → chunk-O3GT46R2.js} +8 -2
  28. package/dist/chunk-O3GT46R2.js.map +1 -0
  29. package/dist/{chunk-C6CLRKPV.js → chunk-PR343K7F.js} +2 -2
  30. package/dist/{chunk-CROKUHJB.js → chunk-Q47DU255.js} +4 -4
  31. package/dist/{chunk-EVBCK2KC.js → chunk-RARREDQE.js} +2 -1
  32. package/dist/{chunk-EVBCK2KC.js.map → chunk-RARREDQE.js.map} +1 -1
  33. package/dist/chunk-RUPPWAD6.js +7 -0
  34. package/dist/chunk-RUPPWAD6.js.map +1 -0
  35. package/dist/{chunk-VSBFM7DS.js → chunk-SJOXWJ5Y.js} +3 -3
  36. package/dist/{chunk-HEWSRXYV.js → chunk-ULHPALI5.js} +3 -3
  37. package/dist/{chunk-XUGSZP7Z.js → chunk-W45FZEGE.js} +2 -2
  38. package/dist/{chunk-NCMZMLY7.js → chunk-WPL5GERE.js} +5 -69
  39. package/dist/chunk-WPL5GERE.js.map +1 -0
  40. package/dist/{chunk-JHGHLKFS.js → chunk-WS6NU475.js} +2 -2
  41. package/dist/{chunk-DEWJMMG3.js → chunk-XKDCR4UE.js} +19 -8
  42. package/dist/chunk-XKDCR4UE.js.map +1 -0
  43. package/dist/{chunk-ZGBEIWSC.js → chunk-XP5WM2LV.js} +5 -5
  44. package/dist/{chunk-TD2NNXLZ.js → chunk-XSOINOPY.js} +8 -8
  45. package/dist/{chunk-6TWWY7ZU.js → chunk-Z3JXLULD.js} +2 -2
  46. package/dist/cli/bin.js +31 -28
  47. package/dist/cli/bin.js.map +1 -1
  48. package/dist/cli/commands/audit.d.ts +4 -0
  49. package/dist/cli/commands/audit.js +16 -13
  50. package/dist/cli/commands/audit.js.map +1 -1
  51. package/dist/client-JYKPVCJX.js +11 -0
  52. package/dist/configs/playwright-a11y.js +1 -1
  53. package/dist/{convert-to-pnpm-QFJOEP5V.js → convert-to-pnpm-NWB5FCQ2.js} +4 -4
  54. package/dist/{db-UXNIZ2RG.js → db-DXUUAMXT.js} +121 -26
  55. package/dist/db-DXUUAMXT.js.map +1 -0
  56. package/dist/{digest-FW6QMZZJ.js → digest-NBLECFM7.js} +110 -29
  57. package/dist/digest-NBLECFM7.js.map +1 -0
  58. package/dist/digest-collectors-NM7WUWQP.js +50 -0
  59. package/dist/digest-state-3KHZF7GD.js +50 -0
  60. package/dist/digest-state-3KHZF7GD.js.map +1 -0
  61. package/dist/digest-state-F37XUCS5.js +13 -0
  62. package/dist/{dump-P6EBSIOH.js → dump-EPL7AZFR.js} +38 -13
  63. package/dist/dump-EPL7AZFR.js.map +1 -0
  64. package/dist/{email-KJXASVH5.js → email-FMMEUPX6.js} +16 -6
  65. package/dist/{email-KJXASVH5.js.map → email-FMMEUPX6.js.map} +1 -1
  66. package/dist/{ensure-site-4DCEBAPM.js → ensure-site-FU6527OU.js} +2 -2
  67. package/dist/{fleet-state-7TJ2GYT2.js → fleet-state-R5LU7IC3.js} +4 -2
  68. package/dist/{forms-notify-target-2EAWCWBW.js → forms-notify-target-SGDGXXK5.js} +6 -6
  69. package/dist/{github-signals-E2WPYTBK.js → github-signals-DB25UWY4.js} +7 -6
  70. package/dist/{github-signals-E2WPYTBK.js.map → github-signals-DB25UWY4.js.map} +1 -1
  71. package/dist/{header-image-6IC53W27.js → header-image-7A7XLVEU.js} +2 -2
  72. package/dist/{health-endpoint-RFXL2F4A.js → health-endpoint-UVX6R5N5.js} +4 -4
  73. package/dist/{health-mirror-L5JBEJVA.js → health-mirror-GCDN7BJL.js} +11 -6
  74. package/dist/health-mirror-GCDN7BJL.js.map +1 -0
  75. package/dist/index.d.ts +3 -0
  76. package/dist/index.js +136 -95
  77. package/dist/index.js.map +1 -1
  78. package/dist/{init-XRAQZYID.js → init-32AWPS7A.js} +9 -9
  79. package/dist/{launch-IX4Y5JJC.js → launch-52WUE4TZ.js} +12 -11
  80. package/dist/{launch-IX4Y5JJC.js.map → launch-52WUE4TZ.js.map} +1 -1
  81. package/dist/migrate-YITCXBLS.js +7 -0
  82. package/dist/{notify-C645PGD5.js → notify-4HP4GZ2H.js} +5 -5
  83. package/dist/{onboard-O4IAHJMO.js → onboard-BKCYBAVW.js} +4 -4
  84. package/dist/{orchestrate-YLWQ6KK2.js → orchestrate-JNKCWU5K.js} +9 -9
  85. package/dist/{parity-56KA2DGR.js → parity-NBYZUBFU.js} +4 -2
  86. package/dist/pdf-N5QLQ5ZQ.js +38 -0
  87. package/dist/pdf-N5QLQ5ZQ.js.map +1 -0
  88. package/dist/{pipeline-3AANZFCR.js → pipeline-YIBIQ7XK.js} +3 -3
  89. package/dist/{preflight-TNORKGCX.js → preflight-IMTYDDD4.js} +10 -10
  90. package/dist/{prismic-ci-G2AK53VL.js → prismic-ci-MY2U6NN7.js} +4 -4
  91. package/dist/{prismic-models-O6Z3OK3A.js → prismic-models-IJWZW2EO.js} +6 -6
  92. package/dist/prospect/types.d.ts +33 -0
  93. package/dist/{prospect-audit-KSXUNQ5J.js → prospect-audit-WJQI7V5V.js} +32 -13
  94. package/dist/prospect-audit-WJQI7V5V.js.map +1 -0
  95. package/dist/render-CVKE7IWC.js +10 -0
  96. package/dist/{render-from-row-BWSOIYDS.js → render-from-row-DOYJDO2L.js} +5 -5
  97. package/dist/{replay-C2LXMJZP.js → replay-4S6ENYCH.js} +3 -3
  98. package/dist/{report-R3E3Y7HR.js → report-2LM2SX67.js} +20 -20
  99. package/dist/{report-mirror-4MNORYQ4.js → report-mirror-AAK3MXHY.js} +12 -6
  100. package/dist/report-mirror-AAK3MXHY.js.map +1 -0
  101. package/dist/schema-75PJECAE.js +18 -0
  102. package/dist/schema-75PJECAE.js.map +1 -0
  103. package/dist/{self-updating-UCIWESAX.js → self-updating-NTCCWJYZ.js} +4 -4
  104. package/dist/{selftest-VGHWPN72.js → selftest-DIZ5JD2X.js} +12 -12
  105. package/dist/{site-mirror-NIREX5ME.js → site-mirror-Q763PLGG.js} +17 -7
  106. package/dist/site-mirror-Q763PLGG.js.map +1 -0
  107. package/dist/{smoke-suite-U3UKD3NS.js → smoke-suite-4JFZZYVW.js} +4 -4
  108. package/dist/{spam-classifier-XLBFCO7G.js → spam-classifier-T5H4VTP6.js} +4 -2
  109. package/dist/spam-classifier-T5H4VTP6.js.map +1 -0
  110. package/dist/{submissions-IQEJX6FK.js → submissions-TEHPBSZJ.js} +4 -4
  111. package/dist/{svelte-codemods-GOKXTBHW.js → svelte-codemods-QAPDZC6F.js} +4 -4
  112. package/dist/{sync-CTUVXMCK.js → sync-GBFV45WX.js} +4 -2
  113. package/dist/{sync-CTUVXMCK.js.map → sync-GBFV45WX.js.map} +1 -1
  114. package/dist/{sync-configs-SPTLFJCC.js → sync-configs-34JIA6KZ.js} +4 -4
  115. package/dist/{upgrade-W6ZR3FJH.js → upgrade-IR2OO3KP.js} +4 -4
  116. package/dist/{webhook-VOGOW3W6.js → webhook-XM6EV4XT.js} +2 -2
  117. package/dist/{write-audits-to-airtable-CMKHGJNQ.js → write-audits-to-airtable-OPBJXGS4.js} +5 -2
  118. package/dist/write-audits-to-airtable-OPBJXGS4.js.map +1 -0
  119. package/package.json +1 -1
  120. package/dist/airtable-ZBUPQJSC.js +0 -9
  121. package/dist/chunk-C6B4RHXR.js.map +0 -1
  122. package/dist/chunk-DEWJMMG3.js.map +0 -1
  123. package/dist/chunk-DJZWYIPP.js.map +0 -1
  124. package/dist/chunk-HHMMUR4P.js.map +0 -1
  125. package/dist/chunk-HSI6Q7MI.js.map +0 -1
  126. package/dist/chunk-NCMZMLY7.js.map +0 -1
  127. package/dist/chunk-NXNPDCF3.js.map +0 -1
  128. package/dist/chunk-SNSTJLQW.js.map +0 -1
  129. package/dist/chunk-ZBDZTJUO.js.map +0 -1
  130. package/dist/client-AUE3XUPO.js +0 -11
  131. package/dist/db-UXNIZ2RG.js.map +0 -1
  132. package/dist/digest-FW6QMZZJ.js.map +0 -1
  133. package/dist/dump-P6EBSIOH.js.map +0 -1
  134. package/dist/health-mirror-L5JBEJVA.js.map +0 -1
  135. package/dist/migrate-GQH237A6.js +0 -7
  136. package/dist/prospect-audit-KSXUNQ5J.js.map +0 -1
  137. package/dist/render-IFOGFB5T.js +0 -10
  138. package/dist/report-mirror-4MNORYQ4.js.map +0 -1
  139. package/dist/site-mirror-NIREX5ME.js.map +0 -1
  140. /package/dist/{airtable-ZBUPQJSC.js.map → airtable-QG5ICI3Y.js.map} +0 -0
  141. /package/dist/{announce-I2Z4JN3I.js.map → announce-GKD5YJNB.js.map} +0 -0
  142. /package/dist/{blux-ZGDSHI3U.js.map → blux-EATHOHKK.js.map} +0 -0
  143. /package/dist/{bump-deps-CGVELRLK.js.map → bump-deps-KGH5UYSF.js.map} +0 -0
  144. /package/dist/{chunk-SDDPUAWH.js.map → chunk-6I6L3CMX.js.map} +0 -0
  145. /package/dist/{chunk-3JQ2FO3D.js.map → chunk-B23FFWU4.js.map} +0 -0
  146. /package/dist/{chunk-NDWYJLRF.js.map → chunk-DGZWFI2G.js.map} +0 -0
  147. /package/dist/{chunk-BBUSGEES.js.map → chunk-J3WEZGIN.js.map} +0 -0
  148. /package/dist/{chunk-BNM5POSJ.js.map → chunk-JRHIFA5R.js.map} +0 -0
  149. /package/dist/{chunk-MT2JINSS.js.map → chunk-KJGKZLCT.js.map} +0 -0
  150. /package/dist/{chunk-C6CLRKPV.js.map → chunk-PR343K7F.js.map} +0 -0
  151. /package/dist/{chunk-CROKUHJB.js.map → chunk-Q47DU255.js.map} +0 -0
  152. /package/dist/{chunk-VSBFM7DS.js.map → chunk-SJOXWJ5Y.js.map} +0 -0
  153. /package/dist/{chunk-HEWSRXYV.js.map → chunk-ULHPALI5.js.map} +0 -0
  154. /package/dist/{chunk-XUGSZP7Z.js.map → chunk-W45FZEGE.js.map} +0 -0
  155. /package/dist/{chunk-JHGHLKFS.js.map → chunk-WS6NU475.js.map} +0 -0
  156. /package/dist/{chunk-ZGBEIWSC.js.map → chunk-XP5WM2LV.js.map} +0 -0
  157. /package/dist/{chunk-TD2NNXLZ.js.map → chunk-XSOINOPY.js.map} +0 -0
  158. /package/dist/{chunk-6TWWY7ZU.js.map → chunk-Z3JXLULD.js.map} +0 -0
  159. /package/dist/{client-AUE3XUPO.js.map → client-JYKPVCJX.js.map} +0 -0
  160. /package/dist/{convert-to-pnpm-QFJOEP5V.js.map → convert-to-pnpm-NWB5FCQ2.js.map} +0 -0
  161. /package/dist/{fleet-state-7TJ2GYT2.js.map → digest-collectors-NM7WUWQP.js.map} +0 -0
  162. /package/dist/{migrate-GQH237A6.js.map → digest-state-F37XUCS5.js.map} +0 -0
  163. /package/dist/{ensure-site-4DCEBAPM.js.map → ensure-site-FU6527OU.js.map} +0 -0
  164. /package/dist/{notify-C645PGD5.js.map → fleet-state-R5LU7IC3.js.map} +0 -0
  165. /package/dist/{forms-notify-target-2EAWCWBW.js.map → forms-notify-target-SGDGXXK5.js.map} +0 -0
  166. /package/dist/{header-image-6IC53W27.js.map → header-image-7A7XLVEU.js.map} +0 -0
  167. /package/dist/{health-endpoint-RFXL2F4A.js.map → health-endpoint-UVX6R5N5.js.map} +0 -0
  168. /package/dist/{init-XRAQZYID.js.map → init-32AWPS7A.js.map} +0 -0
  169. /package/dist/{orchestrate-YLWQ6KK2.js.map → migrate-YITCXBLS.js.map} +0 -0
  170. /package/dist/{parity-56KA2DGR.js.map → notify-4HP4GZ2H.js.map} +0 -0
  171. /package/dist/{onboard-O4IAHJMO.js.map → onboard-BKCYBAVW.js.map} +0 -0
  172. /package/dist/{pipeline-3AANZFCR.js.map → orchestrate-JNKCWU5K.js.map} +0 -0
  173. /package/dist/{render-IFOGFB5T.js.map → parity-NBYZUBFU.js.map} +0 -0
  174. /package/dist/{render-from-row-BWSOIYDS.js.map → pipeline-YIBIQ7XK.js.map} +0 -0
  175. /package/dist/{preflight-TNORKGCX.js.map → preflight-IMTYDDD4.js.map} +0 -0
  176. /package/dist/{prismic-ci-G2AK53VL.js.map → prismic-ci-MY2U6NN7.js.map} +0 -0
  177. /package/dist/{prismic-models-O6Z3OK3A.js.map → prismic-models-IJWZW2EO.js.map} +0 -0
  178. /package/dist/{spam-classifier-XLBFCO7G.js.map → render-CVKE7IWC.js.map} +0 -0
  179. /package/dist/{write-audits-to-airtable-CMKHGJNQ.js.map → render-from-row-DOYJDO2L.js.map} +0 -0
  180. /package/dist/{replay-C2LXMJZP.js.map → replay-4S6ENYCH.js.map} +0 -0
  181. /package/dist/{report-R3E3Y7HR.js.map → report-2LM2SX67.js.map} +0 -0
  182. /package/dist/{self-updating-UCIWESAX.js.map → self-updating-NTCCWJYZ.js.map} +0 -0
  183. /package/dist/{selftest-VGHWPN72.js.map → selftest-DIZ5JD2X.js.map} +0 -0
  184. /package/dist/{smoke-suite-U3UKD3NS.js.map → smoke-suite-4JFZZYVW.js.map} +0 -0
  185. /package/dist/{submissions-IQEJX6FK.js.map → submissions-TEHPBSZJ.js.map} +0 -0
  186. /package/dist/{svelte-codemods-GOKXTBHW.js.map → svelte-codemods-QAPDZC6F.js.map} +0 -0
  187. /package/dist/{sync-configs-SPTLFJCC.js.map → sync-configs-34JIA6KZ.js.map} +0 -0
  188. /package/dist/{upgrade-W6ZR3FJH.js.map → upgrade-IR2OO3KP.js.map} +0 -0
  189. /package/dist/{webhook-VOGOW3W6.js.map → webhook-XM6EV4XT.js.map} +0 -0
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  isPrivateOrLoopbackHost
3
- } from "./chunk-EVBCK2KC.js";
3
+ } from "./chunk-RARREDQE.js";
4
4
 
5
5
  // src/prospect/crawl.ts
6
6
  import { parse as parse2, NodeType as NodeType2 } from "node-html-parser";
@@ -264,6 +264,17 @@ function decodeXmlText(s) {
264
264
  function parseSitemapLocs(xml) {
265
265
  return [...xml.matchAll(/<(?:[\w-]+:)?loc\b[^>]*>([\s\S]*?)<\/(?:[\w-]+:)?loc>/gi)].map((m) => decodeXmlText(m[1] ?? "").trim()).filter(Boolean);
266
266
  }
267
+ function isSafeNestedSitemap(child, origin) {
268
+ let url;
269
+ try {
270
+ url = new URL(child, origin);
271
+ } catch {
272
+ return false;
273
+ }
274
+ if (url.protocol !== "https:" && url.protocol !== "http:") return false;
275
+ if (url.origin !== origin) return false;
276
+ return !isPrivateOrLoopbackHost(url.hostname);
277
+ }
267
278
  function isSitemapIndex(xml) {
268
279
  return /<(?:[\w-]+:)?sitemapindex[\s>]/i.test(xml);
269
280
  }
@@ -347,7 +358,8 @@ async function fetchSidecars(origin, deps) {
347
358
  if (sitemap.res && /<(urlset|sitemapindex)[\s>]/i.test(sitemap.res.body)) {
348
359
  sitemapPresent = true;
349
360
  if (isSitemapIndex(sitemap.res.body)) {
350
- for (const child of parseSitemapLocs(sitemap.res.body).slice(0, 3)) {
361
+ const children = parseSitemapLocs(sitemap.res.body).filter((child) => isSafeNestedSitemap(child, origin)).slice(0, 3);
362
+ for (const child of children) {
351
363
  const nested = await optional(deps, child);
352
364
  if (nested.res) sitemapUrls.push(...parseSitemapLocs(nested.res.body));
353
365
  }
@@ -369,6 +381,14 @@ async function crawlSite(rawUrl, deps) {
369
381
  start.hash = "";
370
382
  start.username = "";
371
383
  start.password = "";
384
+ if (isPrivateOrLoopbackHost(start.hostname)) {
385
+ throw Object.assign(
386
+ new Error(
387
+ `${start.toString()} is a private address (${start.hostname}) \u2014 refusing to crawl it.`
388
+ ),
389
+ { exitCode: 1 }
390
+ );
391
+ }
372
392
  let home;
373
393
  try {
374
394
  home = await deps.fetchUrl(start.toString());
@@ -787,10 +807,16 @@ Return:
787
807
  the site answers it (yes/partial/no), whether there is a passage an AI could quote verbatim, the page
788
808
  it lives on, and the evidence quote. evidence must be an EXACT substring of that page's quoted text \u2014
789
809
  copied verbatim, never paraphrased or invented \u2014 or null when no exact quote supports the answer.
790
- - categoryQueries: 3-5 searches a buyer types BEFORE they have heard of this company, chosen so that
791
- this company deserves to appear in the results. Each one is sent verbatim to a live answer engine on
792
- its own, with no other context, so it must stand alone: name the service and the place or the
793
- qualifier a buyer would use ("packaging design agency Los Angeles", "how much does a rebrand cost").
810
+ - categoryQueries: 5 searches a buyer types BEFORE they have heard of this company, chosen so that this
811
+ company could PLAUSIBLY RANK for them today \u2014 not ones it arguably deserves. A broad head term
812
+ ("branding agency Los Angeles") returns directories and listicles, which is where small firms are
813
+ aggregated rather than surfaced, so a query like that measures nothing about this company. Give a
814
+ spread: at most ONE head term, and at least THREE that are long-tail \u2014 a specific service, a
815
+ narrower niche or industry, a smaller locality, or a question phrased the way a buyer types it.
816
+ Prefer the specific over the impressive. Each one is sent verbatim to a live answer engine on its
817
+ own, with no other context, so it must stand alone: name the service and the place or the qualifier
818
+ a buyer would use ("trade show booth design for medical device companies", "how much does a rebrand
819
+ cost for a B2B company", "packaging design studio San Antonio").
794
820
  Never refer to the company \u2014 not by name, and not as "this agency", "they", "them" or "you". A query
795
821
  that names the company measures nothing (the engine just echoes the name back); a query that points
796
822
  at it with a pronoun has no antecedent and the engine will answer that it does not know who is meant.
@@ -902,16 +928,19 @@ function verifyEvidence(result, crawl) {
902
928
  if (view) textByUrl.set(p.url, view.text);
903
929
  }
904
930
  const buyerQuestions = result.buyerQuestions.map((q) => {
905
- if (q.page === null) {
906
- return q.evidence === null ? q : { ...q, evidence: null };
907
- }
908
- if (!crawledUrls.has(q.page)) {
909
- return { ...q, page: null, evidence: null };
910
- }
911
- if (q.evidence === null) return q;
912
- const pageText = textByUrl.get(q.page) ?? "";
913
- const verified = normalizeWhitespace(pageText).includes(normalizeWhitespace(q.evidence));
914
- return verified ? q : { ...q, evidence: null };
931
+ const verified = (() => {
932
+ if (q.page === null) {
933
+ return q.evidence === null ? q : { ...q, evidence: null };
934
+ }
935
+ if (!crawledUrls.has(q.page)) {
936
+ return { ...q, page: null, evidence: null };
937
+ }
938
+ if (q.evidence === null) return q;
939
+ const pageText = textByUrl.get(q.page) ?? "";
940
+ const quoted = normalizeWhitespace(pageText).includes(normalizeWhitespace(q.evidence));
941
+ return quoted ? q : { ...q, evidence: null };
942
+ })();
943
+ return verified.evidence === null && verified.answered !== "no" ? { ...verified, answered: "no" } : verified;
915
944
  });
916
945
  return { ...result, buyerQuestions };
917
946
  }
@@ -922,7 +951,7 @@ async function analyzeSite(url, crawl, checks, deps = defaultAnalyzeDeps()) {
922
951
  }
923
952
 
924
953
  // src/prospect/probes.ts
925
- var MAX_QUERIES = 8;
954
+ var MAX_QUERIES = 9;
926
955
  var SNIPPET_CHARS = 300;
927
956
  var PROBE_MODEL = "claude-sonnet-5";
928
957
  function domainOf(raw) {
@@ -937,11 +966,13 @@ function isSameSite(cited, prospect) {
937
966
  return cited === prospect || cited.endsWith(`.${prospect}`) || prospect.endsWith(`.${cited}`);
938
967
  }
939
968
  var MAX_NAME_CHARS = 60;
969
+ var NAME_ABBREVIATIONS = /\b(st|mt|ft|dr|mr|mrs|ms|jr|sr|co|inc|ltd|llc|corp|assoc|bros|dept|ave|blvd|rd)\.\s/gi;
970
+ var INITIALS = /\b[a-z]\.\s/gi;
940
971
  function resolveBusinessName(business, url) {
941
972
  const trimmed = business.trim();
942
- if (!trimmed || trimmed.length > MAX_NAME_CHARS || /\.\s/.test(trimmed)) {
943
- return domainOf(url);
944
- }
973
+ if (!trimmed || trimmed.length > MAX_NAME_CHARS) return domainOf(url);
974
+ const withoutAbbreviations = trimmed.replace(NAME_ABBREVIATIONS, "").replace(INITIALS, "");
975
+ if (/\.\s/.test(withoutAbbreviations)) return domainOf(url);
945
976
  return trimmed;
946
977
  }
947
978
  function buildQueries(input) {
@@ -949,7 +980,11 @@ function buildQueries(input) {
949
980
  const candidates = [
950
981
  { query: `who is ${name}`, kind: "branded" },
951
982
  { query: `${name} reviews`, kind: "branded" },
952
- ...input.categoryQueries.slice(0, 3).map((query) => ({ query, kind: "category" })),
983
+ // All five, not three. The schema asks the model for up to five and we were
984
+ // paying to generate them, then discarding two — which also pinned the
985
+ // denominator at 3, making visibilityScore a four-valued {0,33,67,100}
986
+ // rendered on a 0-100 card. Five halves the step to 20 points.
987
+ ...input.categoryQueries.slice(0, 5).map((query) => ({ query, kind: "category" })),
953
988
  ...input.competitors.slice(0, 2).map((c) => ({ query: `${name} vs ${c}`, kind: "competitor" }))
954
989
  ];
955
990
  const seen = /* @__PURE__ */ new Set();
@@ -964,12 +999,106 @@ function buildQueries(input) {
964
999
  function escapeRegExp(s) {
965
1000
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
966
1001
  }
1002
+ var LEGAL_SUFFIX = /\b(llc|inc|incorporated|ltd|limited|corp|corporation|plc|llp|lp|pllc|pc)\b/g;
1003
+ function normalizeForMatch(s) {
1004
+ return s.toLowerCase().replace(/&/g, " and ").replace(/[‐-―]/g, " ").replace(/[^a-z0-9]+/g, " ").replace(LEGAL_SUFFIX, " ").replace(/\s+/g, " ").trim();
1005
+ }
967
1006
  function mentionsBrand(answer, brand) {
968
- if (!brand) return false;
969
- return new RegExp(`(^|[^a-z0-9])${escapeRegExp(brand)}($|[^a-z0-9])`, "i").test(answer);
1007
+ const needle = normalizeForMatch(brand);
1008
+ if (!needle) return false;
1009
+ return new RegExp(`(^| )${escapeRegExp(needle)}( |$)`).test(normalizeForMatch(answer));
970
1010
  }
1011
+ var CATEGORY_WORDS = /* @__PURE__ */ new Set([
1012
+ // articles and joiners
1013
+ "the",
1014
+ "a",
1015
+ "an",
1016
+ "and",
1017
+ "of",
1018
+ "for",
1019
+ "at",
1020
+ "by",
1021
+ // company words
1022
+ "co",
1023
+ "company",
1024
+ "group",
1025
+ "collective",
1026
+ "partners",
1027
+ "associates",
1028
+ "works",
1029
+ "lab",
1030
+ "labs",
1031
+ "studio",
1032
+ "studios",
1033
+ "agency",
1034
+ "agencies",
1035
+ "firm",
1036
+ "practice",
1037
+ "shop",
1038
+ "house",
1039
+ "office",
1040
+ // sector words
1041
+ "design",
1042
+ "designs",
1043
+ "creative",
1044
+ "creatives",
1045
+ "brand",
1046
+ "branding",
1047
+ "marketing",
1048
+ "media",
1049
+ "digital",
1050
+ "solutions",
1051
+ "services",
1052
+ "consulting",
1053
+ "consultants",
1054
+ "advisors",
1055
+ "strategy",
1056
+ "dental",
1057
+ "dentistry",
1058
+ "orthodontics",
1059
+ "law",
1060
+ "legal",
1061
+ "clinic",
1062
+ "care",
1063
+ "health",
1064
+ "medical",
1065
+ "roofing",
1066
+ "construction",
1067
+ "builders",
1068
+ "contracting",
1069
+ "plumbing",
1070
+ "electric",
1071
+ "landscaping",
1072
+ "interiors",
1073
+ "architects",
1074
+ "photography",
1075
+ "films",
1076
+ "productions",
1077
+ "printing",
1078
+ "packaging",
1079
+ // adjectives that market rather than identify
1080
+ "modern",
1081
+ "premier",
1082
+ "elite",
1083
+ "first",
1084
+ "best",
1085
+ "local",
1086
+ "family",
1087
+ "advanced",
1088
+ "complete",
1089
+ "quality",
1090
+ "professional",
1091
+ "trusted",
1092
+ "expert",
1093
+ "affordable",
1094
+ "custom",
1095
+ "creative"
1096
+ ]);
971
1097
  function isDistinctiveName(brand) {
972
- return /\s/.test(brand) || brand.includes(".");
1098
+ if (brand.includes(".")) return true;
1099
+ const tokens = normalizeForMatch(brand).split(" ").filter(Boolean);
1100
+ if (tokens.length < 2) return false;
1101
+ return tokens.some((t) => !CATEGORY_WORDS.has(t));
973
1102
  }
974
1103
  function isRateLimited(err) {
975
1104
  const message = err instanceof Error ? err.message : String(err);
@@ -983,13 +1112,18 @@ async function runVisibilityProbes(input, engines, opts = {}) {
983
1112
  const queries = buildQueries(input);
984
1113
  const prospect = domainOf(input.url);
985
1114
  const brand = resolveBusinessName(input.business, input.url).toLowerCase();
1115
+ const nameIsDistinctive = isDistinctiveName(brand);
986
1116
  const answers = [];
987
1117
  const competitorCounts = /* @__PURE__ */ new Map();
1118
+ const perEngine = /* @__PURE__ */ new Map();
988
1119
  for (const engine of engines) {
1120
+ const tally = { categoryAttempted: 0, answeredAny: false };
1121
+ perEngine.set(engine.name, tally);
989
1122
  await pacedEach(
990
1123
  queries,
991
1124
  delayMs,
992
1125
  async ({ query, kind }) => {
1126
+ if (kind === "category") tally.categoryAttempted += 1;
993
1127
  let reply;
994
1128
  try {
995
1129
  reply = await engine.ask(query);
@@ -1002,6 +1136,7 @@ async function runVisibilityProbes(input, engines, opts = {}) {
1002
1136
  return;
1003
1137
  }
1004
1138
  }
1139
+ tally.answeredAny = true;
1005
1140
  const citedDomains = reply.citedDomains.map(domainOf);
1006
1141
  const domainCited = citedDomains.some((d) => isSameSite(d, prospect));
1007
1142
  const brandMentioned = mentionsBrand(reply.answer.toLowerCase(), brand);
@@ -1015,6 +1150,8 @@ async function runVisibilityProbes(input, engines, opts = {}) {
1015
1150
  kind,
1016
1151
  domainCited,
1017
1152
  brandMentioned,
1153
+ // The same expression the score uses below — written once, read twice.
1154
+ countedAsVisible: domainCited || brandMentioned && nameIsDistinctive,
1018
1155
  citedDomains,
1019
1156
  snippet: reply.answer.slice(0, SNIPPET_CHARS),
1020
1157
  truncated: reply.answer.length > SNIPPET_CHARS,
@@ -1028,17 +1165,16 @@ async function runVisibilityProbes(input, engines, opts = {}) {
1028
1165
  throw new Error("no visibility engine returned an answer");
1029
1166
  }
1030
1167
  const categoryAnswers = answers.filter((a) => a.kind === "category");
1031
- const nameIsDistinctive = isDistinctiveName(brand);
1032
- const visibleCategory = categoryAnswers.filter(
1033
- (a) => a.domainCited || a.brandMentioned && nameIsDistinctive
1034
- ).length;
1035
- const visibilityScore = categoryAnswers.length === 0 ? null : Math.round(visibleCategory / categoryAnswers.length * 100);
1168
+ const visibleCategory = categoryAnswers.filter((a) => a.countedAsVisible).length;
1169
+ const categoryAttempted = [...perEngine.values()].filter((t) => t.answeredAny).reduce((n, t) => n + t.categoryAttempted, 0);
1170
+ const visibilityScore = categoryAttempted === 0 || categoryAnswers.length === 0 ? null : Math.round(visibleCategory / categoryAttempted * 100);
1036
1171
  const brandedRecognized = answers.some((a) => a.kind === "branded" && a.domainCited);
1037
1172
  return {
1038
1173
  answers,
1039
1174
  visibilityScore,
1040
1175
  brandedRecognized,
1041
- competitorsSeen: [...competitorCounts.entries()].map(([domain, count]) => ({ domain, count })).sort((a, b) => b.count - a.count).slice(0, 8)
1176
+ competitorsSeen: [...competitorCounts.entries()].map(([domain, count]) => ({ domain, count })).sort((a, b) => b.count - a.count).slice(0, 8),
1177
+ categoryProbes: { attempted: categoryAttempted, answered: categoryAnswers.length }
1042
1178
  };
1043
1179
  }
1044
1180
  function perplexityEngine(apiKey, fetchImpl = fetch) {
@@ -1231,8 +1367,9 @@ async function runProspectAudit(url, opts, deps = {}) {
1231
1367
  }
1232
1368
 
1233
1369
  export {
1370
+ resolveBusinessName,
1234
1371
  PROBES_SKIPPED,
1235
1372
  ANALYZE_SKIPPED,
1236
1373
  runProspectAudit
1237
1374
  };
1238
- //# sourceMappingURL=chunk-HSI6Q7MI.js.map
1375
+ //# sourceMappingURL=chunk-CEW5PEHZ.js.map