@sonordev/contracts 2.12.0 → 2.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/llms.mjs CHANGED
@@ -1,3 +1,622 @@
1
+ // src/contracts/error-page.ts
2
+ var ERROR_PAGE_MAX_WORDS = 120;
3
+ var PREFIXED_MAX_WORDS = 400;
4
+ var SHORT_ONLY_MAX_WORDS = 60;
5
+ var START_STATE_MAX_WORDS = 40;
6
+ var HEAD_CHARS = 300;
7
+ var DESCRIBE_WINDOW = 45;
8
+ var NAMED_ENTITIES = {
9
+ amp: "&",
10
+ quot: '"',
11
+ apos: "'",
12
+ nbsp: " ",
13
+ lt: "<",
14
+ gt: ">",
15
+ rsquo: "'",
16
+ lsquo: "'",
17
+ ldquo: '"',
18
+ ndash: "\u2013",
19
+ mdash: "\u2014",
20
+ hellip: "\u2026"
21
+ };
22
+ function decodeEntities(text) {
23
+ let out = text;
24
+ for (let pass = 0; pass < 2 && out.indexOf("&") !== -1; pass++) {
25
+ out = out.replace(/&(?:#x([0-9a-f]{1,6})|#(\d{1,7})|([a-z]{2,8}));/gi, (whole, hex, dec, name) => {
26
+ if (name) {
27
+ const key = name.toLowerCase();
28
+ return Object.prototype.hasOwnProperty.call(NAMED_ENTITIES, key) ? NAMED_ENTITIES[key] : whole;
29
+ }
30
+ const code = hex ? parseInt(hex, 16) : parseInt(dec, 10);
31
+ return code <= 1114111 ? String.fromCodePoint(code) : whole;
32
+ });
33
+ }
34
+ return out;
35
+ }
36
+ function tidy(text) {
37
+ return decodeEntities(text).replace(/[‘’‛ʼ]/g, "'").replace(/\s+/g, " ").trim();
38
+ }
39
+ function textOf(text) {
40
+ const oc = tidy(text);
41
+ const lc = oc.toLowerCase();
42
+ return { lc, oc: lc.length === oc.length ? oc : lc };
43
+ }
44
+ function slice(text, from, to) {
45
+ return { lc: text.lc.slice(from, to), oc: text.oc.slice(from, to) };
46
+ }
47
+ function words(text) {
48
+ return text.split(/\s+/).filter(Boolean).length;
49
+ }
50
+ function wordsAcrossElements(text) {
51
+ return words(text.replace(/([a-z0-9.,;:!?)"'’])([A-Z])/g, "$1 $2"));
52
+ }
53
+ var NOT_A_QUESTION = String.raw`(?!\s*\?|\s+(?:in|on|for|at|with|under|from|during|to)\b[^.!?]{0,50}\?)`;
54
+ var NOT_IN_YOUR_SIZE = String.raw`(?!\s+in\s+your\s+size\b)`;
55
+ var RECORD_NOUN = String.raw`(?:page|project|post|article|product|listing|resource|file|deployment)`;
56
+ var CANT_BE_FOUND = String.raw`\b(?:page|url|link|resource|route|file|deployment|site|website|app|project)\b[^.!?]{0,40}?(?:could not|couldn't|can't|cannot|can not) be found`;
57
+ var WHICH_PAGE = String.raw`(?:\s+(?:you(?:'re| are| were)? (?:looking for|requested|asked for|(?:tried|were trying|are trying) to (?:reach|visit|access|find|open))|that you (?:requested|asked for)|at\s+\S[^.!?]{0,40}|on this (?:site|server|website)))?`;
58
+ var DOESNT_EXIST = String.raw`(?:\b(?:this|that|the|requested)\s+(?:[a-z0-9'-]+\s+){0,2}(?:page|url|link|post|article|listing|product|resource|project|profile|record|entry|story|event|course|job|item|offer|video)|\bpage)${WHICH_PAGE}\s+(?:doesn't|does not)(?: (?:seem|appear))?(?: to)? exist(?!\s+(?:to|for|so)\s)`;
59
+ var MAINTENANCE_QUALIFIER = String.raw`(?!\s+(?:on|from|every|each|this|next|between|during|at|after|before|once|twice|monthly|weekly|nightly|daily|contract|agreement|plan|program|warranty|coverage|with\s+(?:us|our)|for\s+(?:the|a|\d)|until(?!\s+(?:\d{1,2}(?::\d\d)?\s*(?:[ap]\.?m\b|o'clock)|\d{1,2}:\d\d|noon|midnight|tomorrow|further notice)))\b)`;
60
+ var ERROR_PAGE_SIGNATURES = [
61
+ // ---- Not found: the framework defaults and the common custom wordings ----
62
+ new RegExp(RECORD_NOUN + " (?:was |is )?not found" + NOT_A_QUESTION + NOT_IN_YOUR_SIZE),
63
+ // "Can't find the page you need? Use the search box" is a help line; "We can't find that page." isn't.
64
+ /(?:can't|cannot|can not|couldn't|could not|unable to) (?:be )?(?:find|found|locate)(?: that| this| the)? page(?!\s*\?|\s+(?:you|that|which)\b[^.!?]{0,40}\?)/,
65
+ // "We can't seem to find the page", "It seems we can't find what you're looking for".
66
+ /(?:we|i) (?:can't|cannot|couldn't|could not|were unable to|are unable to|weren't able to)(?: seem to)? (?:find|locate) (?:that page|this page|the page|that one|that(?=[.!,?]| link)|what you(?:'re| are| were)? looking for)/,
67
+ // "can not" is two words in the Vercel default.
68
+ new RegExp(CANT_BE_FOUND),
69
+ // Next's own not-found.tsx example: "Not FoundCould not find requested resourceReturn Home".
70
+ /(?:could not|couldn't) find (?:the )?requested resource/,
71
+ // "doesn't exist" only with a page, link or record as the subject: a short
72
+ // marketing page can say "if a tool doesn't exist, we'll build it".
73
+ new RegExp(DOESNT_EXIST),
74
+ /(?:doesn't|does not) exist or (?:has|have) been (?:moved|removed|deleted)/,
75
+ /looking for (?:doesn't|does not) exist/,
76
+ // A page-ish subject, like "doesn't exist" above: a history page can say "the 1890 mill no longer exists".
77
+ new RegExp(String.raw`\b(?:this|that|the)\s+(?:[a-z0-9'-]+\s+){0,2}(?:page|url|link|project|post|article|product|listing|resource|content|item|offer|event|record|story|profile|entry|document|course|job|video)` + WHICH_PAGE + String.raw`\s+no longer exists`),
78
+ /(?:404: ?not[ _]found|code: ?not_found)/,
79
+ // "Error 404" and "404 Error" as a label ("404 Error | Acme") or leading into what a 404 says
80
+ // ("Error 404Page not found"). Not "a custom 404 error page" and not a name ("Error 404 Studio").
81
+ /(?:^|[.!?|:]\s*)(?:http |server )?(?:error 404|404 error)(?=\s*[-–—|:.!?,)]|\s*(?:page|this|that|the|we|oops|whoops|sorry|looks|uh|hmm|nothing|there|it|oh|please)\b)|(?:error 404|404 error)(?=\s*$|\s*[-–—|:]?\s*(?:not found|this (?:page|url)|that page)\b)/,
82
+ // "The page you requested was not found", "The page you were looking for has been removed".
83
+ /page you (?:requested|(?:were|are) looking for|tried to (?:reach|visit|access))[^.!?]{0,60}(?:not found|no longer|(?:has|have|had|might have|may have|may) been|has (?:drifted|wandered|vanished|disappeared|moved|left)|isn't|is not|doesn't|does not|could not|can't|cannot|was not)/,
84
+ // Apache, Flask and Django: "The requested URL was not found on this server."
85
+ /requested (?:url|uri|resource|file|page|document)\b[^.!?]{0,80}(?:not found|not be found|cannot be found)|(?:was|is) not found on (?:this|the) server/,
86
+ // Netlify, Render, Flask and Express answer with the bare words "Not Found".
87
+ /^not found(?=$|\s*[-–—|:.!]|the requested|request id)/,
88
+ // WordPress core and its default themes: "It looks like nothing was found at this location.", "Nothing Found".
89
+ /nothing was found at this location|^nothing found(?=$|[.,:;!?|-]|sorry|it looks|we couldn|nothing|oops|the page)/,
90
+ // Custom 404 copy that never says "404": "Oops! This page took a day off.", "You've hit a dead end.",
91
+ // "This page doesn't live here anymore." The opener needs the page to be the one that's gone ("Oops! This
92
+ // page is a work in progress" is a holding page), and the dead end needs to be a sentence of its own.
93
+ /^(?:oops|whoops|uh[- ]oh)[!,.…]*\s*(?:this|that) page (?:took|went|wandered|got lost|ran away|has (?:gone|wandered|moved|left|vanished|disappeared|been (?:removed|moved|deleted))|is (?:missing|gone|lost)|isn't (?:here|available|there)|(?:doesn't|does not|can't|cannot|couldn't) (?:exist|be found|seem)|seems? (?:to be )?(?:missing|lost|gone))/,
94
+ /(?:^|[.!?]\s*)(?:you've|you have|you) (?:hit|reached) a dead end(?=[.!?…]|$)/,
95
+ /(?:page|link|url)[^.!?]{0,40}(?:doesn't|does not|no longer|isn't|is not) (?:live |exist |work )?here(?: anymore)?(?![a-z]*\s+to\b)/,
96
+ // Gatsby's 404 route: "NOT FOUNDYou just hit a route that doesn't exist... the sadness."
97
+ /hit a route that doesn't exist/,
98
+ // Tomcat and IIS: "HTTP Status 404 – Not Found", "HTTP Error 403.14 - Forbidden", "HTTP Error 503. The service is unavailable."
99
+ /http (?:error|status) [45]\d\d(?:\.\d+)?\s*[–—-]\s*(?:not found|forbidden|unauthori[sz]ed|bad request|internal|method not allowed|service unavailable)|http error [45]\d\d(?:\.\d+)?\. the /,
100
+ // A status number with its reason, anywhere in the head (a nav can precede it):
101
+ // "404 | Not Found", "502 - Bad Gateway", "403: Forbidden", "400Bad Request", "419 | Page Expired".
102
+ // Not "401 unauthorized means your key is missing": a status explained is documentation.
103
+ /(?:^|\D)(?:(?:404|410)\s*[|:–—-]?\s*(?:not found|page not found|file not found|web site not found)|408[\s:|–—-]*request time-?out|50[0-4][\s:|–—-]*(?:internal (?:server )?error|server error|bad gateway|service (?:temporarily )?unavailable|gateway time-?out)|40[13][\s:|–—-]*(?:forbidden|unauthori[sz]ed)|400[\s:|–—-]*bad request|405[\s:|–—-]*method not allowed|413[\s:|–—-]*(?:request entity|payload|content) too large|419[\s:|–—-]*page expired|429[\s:|–—-]*too many)(?!\s+(?:(?:requests?|errors?|responses?|status(?: codes?)?|codes?|messages?|pages?)\s+)?(?:means?|is|are|indicates?|occurs?|happens?|when|if|returns?|tells?|shows?|appears?|can|should|will|occurred)\b)/,
104
+ // Netlify's bare "Not Found - Request ID: ..." and Express's "Cannot GET /about", even with a nav in front.
105
+ /not found\s*[-–—]\s*request id|\bcannot (?:get|post|put|patch|delete) \/[\w\-./?=&%]*(?:$|\s)/,
106
+ // IIS's classic "Server Error 404 - File or directory not found." and its "had its name changed" sentence.
107
+ /file or directory not found|removed,? had its name changed/,
108
+ // This page has been removed / moved: its own sentence, not "removed from our menu".
109
+ /(?:^|[.!?]\s*)this page (?:has been |has |was |is )?(?:permanently )?(?:removed|deleted|moved)(?=[.!,;]|$|\s+(?:or|and)\b|\s*(?:return|go|back|click|please|visit|head)\b)/,
110
+ // ---- Server and client errors ----
111
+ // Not "Application error monitoring for Node teams".
112
+ /^application error(?!\s+(?:monitoring|tracking|reporting|handling|management|detection|analytics|logs?|alerts?|rates?|budgets?|codes?))/,
113
+ /(?:client|server)-side exception has occurred/,
114
+ // The hosts spell a status as a code too: "500: INTERNAL_SERVER_ERROR", "504: GATEWAY_TIMEOUT".
115
+ /internal[ _]server[ _]error/,
116
+ new RegExp(String.raw`(?:bad[ _]gateway|gateway[ _]time-?out)` + NOT_A_QUESTION),
117
+ // An exception's message rendered as the page: "Error: Failed to fetch data", "TypeError: x is undefined".
118
+ /^(?:(?:type|reference|syntax|range)error: |error: (?:failed|cannot|can't|unable|invalid|network|request|fetch|something|unexpected|no |not ))/,
119
+ // "Something went wrong with your insurance claim? ..." is a headline about a claim, not an error state.
120
+ new RegExp(String.raw`something(?: went|'s gone| has gone| had gone) wrong(?!\s+(?:with|during|on|in|at)\s+(?:your|my)\b)` + NOT_A_QUESTION),
121
+ /an (?:unexpected )?error (?:has )?occurred/,
122
+ /this (?:page|site|website) (?:isn't working|can't be reached)/,
123
+ // Next.js 16 error shells: "This page couldn't load. A server error occurred. Reload to try again."
124
+ // and the 403 / 401 pages ("This page could not be accessed.").
125
+ /this page couldn't load|a server error (?:has )?occurred|reload to try again(?=[.,!]|$)|this page could not be accessed/,
126
+ // React Router and Remix, Spring Boot, and the "unexpected error" boundaries.
127
+ /unexpected application error|whitelabel error page|(?:there was|encountered) an unexpected error/,
128
+ // Django and ASP.NET: "Server Error (500)", "Server Error in '/' Application", "Not Found (404)".
129
+ /server error (?:\(500\)|in '?\/)|(?:bad request|forbidden|not found) \((?:400|403|404)\)/,
130
+ // Next's runtime messages when a chunk or a hydration fails and the message is the page.
131
+ /chunkloaderror|loading chunk [\w/.-]+ failed|hydration failed because the|there was an error while hydrating/,
132
+ // Generated error.tsx boundaries: "Oops, we hit a snag.", "Something unexpected happened", "Unable to load page".
133
+ new RegExp(
134
+ String.raw`(?:^|[.!?]\s*)(?:(?:oops|whoops|uh[- ]oh)[!,.…]*\s*)?we hit a snag(?!\s+(?:with|in|on|during|when|while|at)\b)|(?:^|[.!?]\s*)something unexpected happened|(?:^|[.!?]\s*)(?:failed|unable) to load (?:the |this )?(?:page|content|data|resource)` + NOT_A_QUESTION + String.raw`|^error loading (?:the |this )?(?:page|content|data)`
135
+ ),
136
+ // "We're having trouble loading this page", "We couldn't load this page".
137
+ /(?:we're|we are|i'm) having (?:some )?(?:trouble|difficulty|difficulties|problems?) (?:loading|displaying|showing|retrieving) (?:this|that|the) (?:page|content)|we (?:couldn't|could not|can't|cannot) load (?:this|that|the) page/,
138
+ // WordPress core's fatal-error and database screens.
139
+ /there has been a critical error on this (?:website|site)|error establishing a database connection|(?:the|this) (?:site|website) is experiencing technical difficulties/,
140
+ // Apache's stock pages: 503, 400, 401, 408, 410 and 414 each in its own words.
141
+ /the server is temporarily unable to service your request|your browser sent a request that this server could not understand|this server could not verify that you are authorized|server timeout waiting for the http request|(?:was|is) no longer available on this server|request-uri too large/,
142
+ // ---- Unavailable and maintenance ----
143
+ // A qualifier ("in Hawaii", "until April") makes it content about a service, not a dead page.
144
+ /(?:this|the) (?:page|site|website|service) (?:is|are) (?:temporarily|currently) unavailable(?!\s+(?:in|on|for|at|during|to|until|from|outside|after|before|through)\b)/,
145
+ /^(?:service (?:temporarily )?unavailable|(?:temporarily|currently) unavailable(?!:\s))(?!\s+(?:in|on|for|at|during|to|until|from|outside)\b)/,
146
+ // The site or the page is the one under maintenance. Not "is under maintenance contract", not "down for
147
+ // maintenance on Fridays": a day, a time or a contract makes it content.
148
+ new RegExp(String.raw`(?:site|website|page|store|shop|app|portal|server|we're|we are)(?: is| are)?(?: currently| temporarily| now)*,? (?:under|down for) (?:scheduled |routine |some |emergency )?maintenance` + MAINTENANCE_QUALIFIER),
149
+ // "Our website is currently undergoing scheduled maintenance", "Briefly unavailable for scheduled maintenance",
150
+ // "We are offline for a short while". Not "Our rental fleet is undergoing routine maintenance": the subject is the site.
151
+ new RegExp(String.raw`(?:(?:site|website|page|store|shop|app|portal|server|we're|we are)(?: is| are)?(?: currently| temporarily| now)*|briefly|temporarily|currently|now) (?:undergoing|(?:offline|unavailable|down)(?: temporarily)? for) (?:scheduled |routine |some |emergency )?maintenance` + MAINTENANCE_QUALIFIER),
152
+ /(?:we're|we are|site is|website is) offline for an? (?:short|little|brief) (?:while|time|period)/,
153
+ /^be right back\.?$/,
154
+ // "We'll be back soon." ending its sentence, or leading into the next one. Not "We should be back soon with a replacement
155
+ // part", "back soon after our holiday break": what follows decides.
156
+ /(?:we'll|we will|we should|it should) (?:be )?(?:right )?back (?:online )?(?:soon|shortly)(?=[.!;]|$|,?\s*(?:our|the|this|thank|thanks|please|we)\b|\s+(?:once|after)\s+(?:a|the|our)\s+(?:(?:short|brief|quick|scheduled|planned)\s+)*(?:maintenance|upgrade|update|outage|downtime|migration|work)\b)/,
157
+ // An apology for the downtime makes what follows an outage notice, whatever it promises.
158
+ /sorry for the (?:downtime|inconvenience|interruption|outage)\W+(?:we'll|we will|we should|we are working)/,
159
+ // ---- Walls that stand in for the page ----
160
+ new RegExp(String.raw`(?:403 forbidden|access denied)(?!\s+(?:is|was|means|errors?|messages?|pages?)\b)` + NOT_A_QUESTION),
161
+ // A permission wall is addressed to the visitor and about THIS page or resource, and opens the text or follows its own
162
+ // heading ("Sorry, you are not allowed to access this page.", "ForbiddenYou are not authorized to access this resource.").
163
+ // Not a person or a file ("our team is not allowed to access this page of your file"), not a gallery ("you don't have
164
+ // permission to view the gallery"), and not a sentence further in ("Members only. You do not have permission to ...").
165
+ /(?:^|sorry|oops|error|forbidden|unauthori[sz]ed|access denied|40[13])\W*you(?:'re| are| aren't) (?:not |un)?(?:allowed|authori[sz]ed|permitted) to (?:access|view|see) (?:this|that) (?:page|resource|site|website|area|section|content)/,
166
+ /(?:^|sorry|oops|error|forbidden|unauthori[sz]ed|access denied|40[13])\W*you (?:don't|do not) have permission to (?:access|view) (?:this (?:page|resource|site|website|area|section|content)|\S+ on this server)/,
167
+ /^(?:forbidden|unauthori[sz]ed)(?=$|\s*[-–—|:.!]|you(?:'re| are)\b|please\b|this (?:page|resource))/,
168
+ /(?:checking your browser before accessing|enable javascript and cookies to continue(?!\s+\w)|verif(?:y|ying) you are (?:a )?human)/
169
+ ];
170
+ var LEADING_STATUS = /^(?:(?:http )?error:? ?)?(?:400|401|402|403|405|408|410|413|414|419|429|500|501|502|503|504)\W{0,3}(?:(?:service|server|temporarily|internal|gateway|bad|proxy|request|too|access|page|file|resource|connection|http|status|method|payload|entity|payment|authentication)\s+){0,3}(?:error|forbidden|unauthori[sz]ed|gone|unavailable|bad gateway|time-?out|timed out|not found|not allowed|too many|too large|bad request|page expired|payment required|not implemented)/;
171
+ var LEADING_404 = /^404(?![\d,])(?![\s.\-–—)]*\d)/;
172
+ var ADDRESS_AFTER_404 = /^404\s+(?:[a-z0-9.'-]+\s+){0,3}(?:street|st|avenue|ave|road|rd|boulevard|blvd|drive|dr|lane|ln|way|court|ct|highway|hwy|pike|parkway|pkwy|place|pl|suite|ste|unit|apt)\b/;
173
+ var SHORT_ONLY_SIGNATURES = [
174
+ // Opening the text, and not conditional: "Please refresh the page and try again." is the boundary's own line;
175
+ // "Clear your cache, then refresh the page and try again" is a tip, and "...try again if the calendar doesn't load" is one.
176
+ /^(?:please )?(?:refresh|reload)(?: the| this)? (?:page )?(?:and|or) try again(?!\s+(?:if|when|unless|whether|once|after|later|next|the next)\b)/
177
+ ];
178
+ var SHORT_START_SIGNATURES = [new RegExp(String.raw`^(?:[a-z0-9'&-]+\s+){0,2}[a-z0-9'&-]+ not found` + NOT_A_QUESTION + NOT_IN_YOUR_SIZE)];
179
+ var GLUED_404 = /(?:^|[\s|])404(?=[a-z])(?!th\b)/g;
180
+ var DELIMITED_404 = /(?:^|\s)404\s*[|:–—]\s*[a-z]/g;
181
+ var NUMBER_PREFIX = /(?:suite|ste|room|rm|unit|apt|apartment|route|rt|hwy|highway|building|bldg|floor|#)\.?\s*$/;
182
+ var ABOUT_THE_ERROR = String.raw`(?!\W*(?:errors?\b|explained|guide|what (?:it|this|that|is|are)|how to|how do|why|means|fix(?:es|ing)?\b|causes|vs\b|\?))`;
183
+ var PREFIX_SIGNATURES = [
184
+ new RegExp(String.raw`(?:page|resource|post|article|product|listing) (?:was |is )?not found`),
185
+ new RegExp(CANT_BE_FOUND),
186
+ /(?:we|i) (?:can't|cannot|couldn't|could not)(?: seem to)? (?:find|locate) (?:that page|this page|the page|what you(?:'re| are| were)? looking for)/,
187
+ new RegExp(DOESNT_EXIST),
188
+ /something went wrong|(?:client|server)-side exception|internal server error|this page couldn't load/
189
+ ].map((pattern) => new RegExp("(?:" + pattern.source + ")" + ABOUT_THE_ERROR));
190
+ var PREFIXED_LEADING_404 = new RegExp(LEADING_404.source + ABOUT_THE_ERROR);
191
+ var PREFIXED_LEADING_STATUS = new RegExp(LEADING_STATUS.source + ABOUT_THE_ERROR);
192
+ var PREFIX_SEPARATOR = " \u2014 ";
193
+ var PREFIX_MIN_CHARS = 60;
194
+ var PREFIX_MAX_CHARS = 400;
195
+ var SKIP_LINK = /^(?:skip to (?:main )?content|skip to main|skip navigation)\s*/;
196
+ var PREFIX_WINDOW_CHARS = 90;
197
+ var DESCRIBERS = String.raw`if|when|whether|unless|how to|how do|why|what is|what's|fix(?:ing|es)?|handle|handling|resolve|resolving|troubleshoot(?:ing)?|diagnos(?:e|ing)|watch(?:es|ing)? for|encounter(?:ing)?|history of`;
198
+ var LOWERCASE_DESCRIBERS = String.raw`about|monitor(?:ing)?|alert(?:s|ing)? (?:on|for)`;
199
+ var DESCRIBING_CONTEXT = new RegExp(
200
+ String.raw`(?:\b(?:(${DESCRIBERS})|(${LOWERCASE_DESCRIBERS}))(?:\s+(?:you|your|the|a|an|their|our|my|see|get|are|is|seeing|getting|receive|receiving|hit|to|every|any|each|all|\d{3})){0,3}\s*["'“]?|["“])$`
201
+ );
202
+ var INNER_DESCRIBER = /\b(?:if|when|whether|unless|how to)\b/;
203
+ var SENTENCE_OPENING = /[.!?:;|—–"“(]\s*$/;
204
+ var VISIBILITY_SUBJECT = /\b(?:you|your|we|our|they|their|customers?|clients?|people|visitors?|buyers?|patients?|business(?:es)?|brands?|compan(?:y|ies))\s+$/;
205
+ var FORM_FIELDS = /\b(?:name|e-?mail|phone|message|subscribe|submit)\b/;
206
+ function globalOf(patterns) {
207
+ return patterns.map((pattern) => new RegExp(pattern.source, "g"));
208
+ }
209
+ var SIGNATURES = globalOf(ERROR_PAGE_SIGNATURES);
210
+ var SHORT_ONLY_SIGS = globalOf(SHORT_ONLY_SIGNATURES);
211
+ var SHORT_START_SIGS = globalOf(SHORT_START_SIGNATURES);
212
+ var PREFIX_SIGS = globalOf(PREFIX_SIGNATURES);
213
+ var TITLE_VERB = /\b(?:fix|handle|resolve|troubleshoot|diagnose)\b/;
214
+ function describerIsProse(orig, start, describer, context) {
215
+ const word = orig.slice(start, start + describer.length);
216
+ if (word === describer || word === describer.charAt(0).toUpperCase() + describer.slice(1)) return true;
217
+ return start === 0 || SENTENCE_OPENING.test(orig.slice(Math.max(0, start - 3), start)) || TITLE_VERB.test(context);
218
+ }
219
+ function isDescribed(head, hit) {
220
+ const from = Math.max(0, hit.index - DESCRIBE_WINDOW);
221
+ const before = head.lc.slice(from, hit.index);
222
+ const context = DESCRIBING_CONTEXT.exec(before);
223
+ if (context) {
224
+ const [, describer, lowercaseDescriber] = context;
225
+ const at = from + context.index;
226
+ if (describer !== void 0) {
227
+ if (describerIsProse(head.oc, at, describer, context[0])) return true;
228
+ } else if (lowercaseDescriber !== void 0) {
229
+ if (head.oc.slice(at, at + lowercaseDescriber.length) === lowercaseDescriber) return true;
230
+ } else {
231
+ return true;
232
+ }
233
+ }
234
+ const inner = INNER_DESCRIBER.exec(hit[0]);
235
+ if (inner && head.oc.slice(hit.index + inner.index, hit.index + inner.index + inner[0].length) === inner[0]) return true;
236
+ return /be found$/.test(hit[0]) && VISIBILITY_SUBJECT.test(before) || /you are (?:a )?human$/.test(hit[0]) && FORM_FIELDS.test(head.lc);
237
+ }
238
+ function showsSignature(head, patterns, inFirstSentence = false) {
239
+ return patterns.some((pattern) => {
240
+ pattern.lastIndex = 0;
241
+ for (let hit = pattern.exec(head.lc); hit; hit = pattern.exec(head.lc)) {
242
+ if (inFirstSentence && /[.!?]/.test(head.lc.slice(0, hit.index))) return false;
243
+ if (!isDescribed(head, hit)) return true;
244
+ pattern.lastIndex = hit.index + 1;
245
+ }
246
+ return false;
247
+ });
248
+ }
249
+ function showsLoose404(head) {
250
+ for (const pattern of [GLUED_404, DELIMITED_404]) {
251
+ pattern.lastIndex = 0;
252
+ for (let hit = pattern.exec(head); hit; hit = pattern.exec(head)) {
253
+ if (!NUMBER_PREFIX.test(head.slice(Math.max(0, hit.index - 14), hit.index))) return true;
254
+ }
255
+ }
256
+ return false;
257
+ }
258
+ function afterPrefix(text) {
259
+ let start = 0;
260
+ const cut = text.lc.indexOf(PREFIX_SEPARATOR);
261
+ if (cut >= PREFIX_MIN_CHARS && cut < PREFIX_MAX_CHARS) start = cut + PREFIX_SEPARATOR.length;
262
+ const skip = SKIP_LINK.exec(text.lc.slice(start));
263
+ if (skip) start += skip[0].length;
264
+ return start === 0 ? text : slice(text, start);
265
+ }
266
+ function opensWith404(head) {
267
+ return LEADING_404.test(head) && !ADDRESS_AFTER_404.test(head);
268
+ }
269
+ function showsErrorState(head, count) {
270
+ return opensWith404(head.lc) || LEADING_STATUS.test(head.lc) || showsSignature(head, SIGNATURES) || count <= START_STATE_MAX_WORDS && showsSignature(head, SHORT_START_SIGS) || count <= SHORT_ONLY_MAX_WORDS && (showsSignature(head, SHORT_ONLY_SIGS) || showsLoose404(head.lc));
271
+ }
272
+ function looksLikeErrorPage(text, wordCount2) {
273
+ if (!text || typeof text !== "string") return false;
274
+ const trimmed = text.trim();
275
+ if (!trimmed) return false;
276
+ const own = wordsAcrossElements(trimmed);
277
+ const count = own;
278
+ if (count > PREFIXED_MAX_WORDS) return false;
279
+ const whole = textOf(trimmed);
280
+ const rest = afterPrefix(whole);
281
+ if (own > ERROR_PAGE_MAX_WORDS) {
282
+ const lead = rest.lc.slice(0, HEAD_CHARS);
283
+ return PREFIXED_LEADING_404.test(lead) && !ADDRESS_AFTER_404.test(lead) || PREFIXED_LEADING_STATUS.test(lead) || showsSignature(slice(rest, 0, PREFIX_WINDOW_CHARS), PREFIX_SIGS, true);
284
+ }
285
+ if (count > ERROR_PAGE_MAX_WORDS) return false;
286
+ return showsErrorState(slice(whole, 0, HEAD_CHARS), count) || // A long description pushes the page's own words past the first 300 characters.
287
+ rest !== whole && showsErrorState(slice(rest, 0, HEAD_CHARS), words(rest.lc));
288
+ }
289
+ function usablePageText(text, wordCount2) {
290
+ if (!text || typeof text !== "string" || !text.trim()) return null;
291
+ return looksLikeErrorPage(text) ? null : text;
292
+ }
293
+
294
+ // src/contracts/seo-meta.ts
295
+ var TITLE_SEPARATOR = " | ";
296
+ function stripTitleSuffix(title) {
297
+ const t = title.trim();
298
+ const i = t.lastIndexOf(TITLE_SEPARATOR);
299
+ return i > 0 ? t.slice(0, i).trim() : t;
300
+ }
301
+
302
+ // src/contracts/page-titles.ts
303
+ var PIPE_RUN = /\|(?:\s*\|)+/g;
304
+ var EDGE_PIPES = /^[\s|]+|[\s|]+$/g;
305
+ function titleHead(title) {
306
+ if (typeof title !== "string") return "";
307
+ const tidy2 = title.replace(/\s+/g, " ").replace(PIPE_RUN, " | ").replace(EDGE_PIPES, "").replace(/\s+/g, " ");
308
+ return tidy2 ? stripTitleSuffix(tidy2) : "";
309
+ }
310
+ var ACRONYMS = /* @__PURE__ */ new Map([
311
+ ["ac", "AC"],
312
+ ["ai", "AI"],
313
+ ["aeo", "AEO"],
314
+ ["api", "API"],
315
+ ["cms", "CMS"],
316
+ ["crm", "CRM"],
317
+ ["faq", "FAQ"],
318
+ ["faqs", "FAQs"],
319
+ ["fsbo", "FSBO"],
320
+ ["hvac", "HVAC"],
321
+ ["jl", "JL"],
322
+ ["llc", "LLC"],
323
+ ["llp", "LLP"],
324
+ ["mls", "MLS"],
325
+ ["pdf", "PDF"],
326
+ ["pllc", "PLLC"],
327
+ ["ppc", "PPC"],
328
+ ["roi", "ROI"],
329
+ ["seo", "SEO"],
330
+ ["ui", "UI"],
331
+ ["usa", "USA"],
332
+ ["ux", "UX"]
333
+ ]);
334
+ var STATE_CODES = new Set(
335
+ "al ak az ar ca co ct de dc fl ga hi id il in ia ks ky la me md ma mi mn ms mo mt ne nv nh nj nm ny nc nd oh ok or pa ri sc sd tn tx ut vt va wa wv wi wy".split(" ")
336
+ );
337
+ var WORD_STATE_CODES = /* @__PURE__ */ new Set([
338
+ "in",
339
+ "or",
340
+ "me",
341
+ "hi",
342
+ "id",
343
+ "la",
344
+ "ma",
345
+ "pa",
346
+ "al",
347
+ "de",
348
+ "co"
349
+ ]);
350
+ var TRAILING_STATE_CODES = /* @__PURE__ */ new Set(["mt"]);
351
+ function isStateCode(lower, isLast) {
352
+ if (!STATE_CODES.has(lower) || WORD_STATE_CODES.has(lower)) return false;
353
+ return TRAILING_STATE_CODES.has(lower) ? isLast : true;
354
+ }
355
+ var SMALL_WORDS = /* @__PURE__ */ new Set([
356
+ "a",
357
+ "an",
358
+ "and",
359
+ "at",
360
+ "by",
361
+ "for",
362
+ "in",
363
+ "of",
364
+ "on",
365
+ "or",
366
+ "the",
367
+ "to",
368
+ "vs",
369
+ "with"
370
+ ]);
371
+ var PAGE_EXTENSION = /\.(?:html?|php|aspx)$/i;
372
+ var URL_ORIGIN = /^[a-z][a-z0-9+.-]*:\/\/[^/\\]*/i;
373
+ var PERCENT_RUN = /(?:%[0-9a-f]{2})+/gi;
374
+ function decodeEscapes(value) {
375
+ return value.replace(PERCENT_RUN, (run) => {
376
+ try {
377
+ return decodeURIComponent(run);
378
+ } catch {
379
+ return run;
380
+ }
381
+ });
382
+ }
383
+ function capitalizeFirst(word) {
384
+ const code = word.codePointAt(0);
385
+ if (code === void 0) return word;
386
+ const first = String.fromCodePoint(code);
387
+ return first.toUpperCase() + word.slice(first.length);
388
+ }
389
+ function labelWord(token, isFirst, isLast) {
390
+ const lower = token.toLowerCase();
391
+ const acronym = ACRONYMS.get(lower);
392
+ if (acronym) return acronym;
393
+ if (isStateCode(lower, isLast)) return lower.toUpperCase();
394
+ if (/\p{N}/u.test(token)) return /^\p{L}/u.test(token) ? capitalizeFirst(token) : token;
395
+ if (!isFirst && SMALL_WORDS.has(lower)) return lower;
396
+ return capitalizeFirst(lower);
397
+ }
398
+ function slugLabel(input) {
399
+ if (typeof input !== "string") return "";
400
+ const path = input.split(/[?#]/, 1)[0].replace(URL_ORIGIN, "");
401
+ const segment = path.split(/[/\\]/).filter((s) => s.trim() !== "").pop();
402
+ if (!segment) return "";
403
+ const name = decodeEscapes(segment).replace(PAGE_EXTENSION, "");
404
+ const tokens = name.split(/[-_+\s]+/).filter(Boolean);
405
+ return tokens.map((token, i) => labelWord(token, i === 0, i === tokens.length - 1)).join(" ");
406
+ }
407
+ var PLACEHOLDER_TOKEN = /^(?::[A-Za-z_][\w-]*[?*+]?|\[\[\.\.\.[A-Za-z_][\w-]*\]\]|\[(?:\.\.\.)?[A-Za-z_][\w-]*\])$/;
408
+ function isRoutePlaceholder(value) {
409
+ if (typeof value !== "string") return false;
410
+ return value.split("/").some((segment) => PLACEHOLDER_TOKEN.test(segment.trim()));
411
+ }
412
+ var ABSOLUTE_URL = /^[a-z][a-z0-9+.-]*:\/\//i;
413
+ var HOST_NAME = /^(?:[a-z0-9-]+\.)+[a-z]{2,}(?::\d{1,5})?$/i;
414
+ function withoutOuterSlashes(value) {
415
+ let start = 0;
416
+ let end = value.length;
417
+ while (start < end && value[start] === "/") start++;
418
+ while (end > start && value[end - 1] === "/") end--;
419
+ return value.slice(start, end);
420
+ }
421
+ function isHostPath(value) {
422
+ const slash = value.indexOf("/");
423
+ if (slash < 0 || /\s/.test(value)) return false;
424
+ return HOST_NAME.test(value.slice(0, slash));
425
+ }
426
+ function isPageAddress(value, path) {
427
+ if (typeof value !== "string") return false;
428
+ const given = value.trim();
429
+ if (!given) return false;
430
+ if (given.startsWith("/") || ABSOLUTE_URL.test(given) || isHostPath(given) || isRoutePlaceholder(given)) return true;
431
+ if (typeof path !== "string") return false;
432
+ const own = withoutOuterSlashes(path.trim());
433
+ return withoutOuterSlashes(given) === own;
434
+ }
435
+ function pipePieces(title) {
436
+ return title.split("|").map((piece) => piece.trim()).filter(Boolean);
437
+ }
438
+ function isPageName(title, path) {
439
+ if (typeof title !== "string") return false;
440
+ const pieces = pipePieces(title);
441
+ return pieces.length > 0 && pieces.every((piece) => !isPageAddress(piece, path));
442
+ }
443
+
444
+ // src/contracts/sentences.ts
445
+ var ABBREVIATIONS = /* @__PURE__ */ new Set([
446
+ "mr",
447
+ "mrs",
448
+ "ms",
449
+ "dr",
450
+ "prof",
451
+ "st",
452
+ "jr",
453
+ "sr",
454
+ "vs",
455
+ "inc",
456
+ "co",
457
+ "corp",
458
+ "ltd",
459
+ "llc",
460
+ "approx",
461
+ "dept",
462
+ "jan",
463
+ "feb",
464
+ "mar",
465
+ "apr",
466
+ "jun",
467
+ "jul",
468
+ "aug",
469
+ "sep",
470
+ "sept",
471
+ "oct",
472
+ "nov",
473
+ "dec",
474
+ "gen",
475
+ "sen",
476
+ "gov",
477
+ "lt",
478
+ "col",
479
+ "capt",
480
+ "sgt",
481
+ "cpl",
482
+ "maj",
483
+ "adm",
484
+ "pres",
485
+ "atty",
486
+ "bros",
487
+ "mfg",
488
+ "assoc",
489
+ "univ",
490
+ "bldg",
491
+ "ste",
492
+ "ave",
493
+ "blvd",
494
+ "rd",
495
+ "ln",
496
+ "hwy",
497
+ "pkwy",
498
+ "pl"
499
+ ]);
500
+ var CAPITALISED_ABBREVIATIONS = /* @__PURE__ */ new Set([
501
+ "Mon",
502
+ "Tue",
503
+ "Tues",
504
+ "Wed",
505
+ "Thu",
506
+ "Thur",
507
+ "Thurs",
508
+ "Fri",
509
+ "Sat",
510
+ "Sun",
511
+ "Mt",
512
+ "Ft",
513
+ "Ct",
514
+ "Rep",
515
+ "Rev",
516
+ "Hon"
517
+ ]);
518
+ var BEFORE_NUMBER = /* @__PURE__ */ new Set([
519
+ "no",
520
+ "nos",
521
+ "fig",
522
+ "figs",
523
+ "vol",
524
+ "vols",
525
+ "ch",
526
+ "sec",
527
+ "pp",
528
+ "cf",
529
+ "ca",
530
+ "est",
531
+ "ext",
532
+ "tel",
533
+ "ph",
534
+ "ste",
535
+ "apt",
536
+ "bldg",
537
+ "rm",
538
+ "hwy",
539
+ "rt",
540
+ "rte"
541
+ ]);
542
+ var DOTTED_SHORT = /^\d*(?:[A-Za-z]{1,2}\.)+[A-Za-z]{1,2}$/;
543
+ var DOTTED_CAPS = /^(?:[A-Z][A-Za-z]*\.)+[A-Z][A-Za-z]*$/;
544
+ var ABBREVIATION_RANGE = /[A-Za-z]\.[-–—/]/;
545
+ var LIST_MARKER_ALONE = /^\s*(?:(?:step|tip|reason|rule|point|item|option)\s+)?\d{1,3}\.$/i;
546
+ var LIST_MARKER_AFTER_COLON = /:\s+\d{1,3}\.$/;
547
+ var LIST_MARKER_REACH = 24;
548
+ var OPENERS = /* @__PURE__ */ new Set(['"', "'", "\u201C", "\u2018", "(", "["]);
549
+ var CLOSERS = /* @__PURE__ */ new Set(['"', "'", "\u201D", "\u2019", ")", "]"]);
550
+ var SPACE = /\s/;
551
+ var LETTER = /\p{L}/u;
552
+ var DIGIT = /[0-9]/;
553
+ var SENTENCE_START = /[A-Z0-9]/;
554
+ var isStop = (c) => c === "." || c === "!" || c === "?";
555
+ var isSpace = (c) => SPACE.test(c);
556
+ function startsSentence(source, at) {
557
+ const c = OPENERS.has(source[at]) ? source[at + 1] : source[at];
558
+ return c !== void 0 && SENTENCE_START.test(c);
559
+ }
560
+ function digitFollows(source, at) {
561
+ const c = OPENERS.has(source[at]) ? source[at + 1] : source[at];
562
+ return c !== void 0 && DIGIT.test(c);
563
+ }
564
+ function isDottedAbbreviation(bare) {
565
+ const compound = Math.max(bare.lastIndexOf("-"), bare.lastIndexOf("\u2013"), bare.lastIndexOf("\u2014"), bare.lastIndexOf("/"));
566
+ const last = bare.slice(compound + 1);
567
+ return DOTTED_SHORT.test(last) || DOTTED_CAPS.test(last) || ABBREVIATION_RANGE.test(bare);
568
+ }
569
+ function endsWithAbbreviation(source, end, digitNext) {
570
+ let from = end;
571
+ while (from > 0 && !isSpace(source[from - 1])) from--;
572
+ let to = end;
573
+ while (from < to && OPENERS.has(source[from])) from++;
574
+ while (to > from && CLOSERS.has(source[to - 1])) to--;
575
+ while (to > from && source[to - 1] === ".") to--;
576
+ const bare = source.slice(from, to);
577
+ const lower = bare.toLowerCase();
578
+ return isDottedAbbreviation(bare) || /^[A-Za-z]$/.test(bare) || ABBREVIATIONS.has(lower) || CAPITALISED_ABBREVIATIONS.has(bare) || digitNext && BEFORE_NUMBER.has(lower);
579
+ }
580
+ function endsWithListMarker(source, start, end) {
581
+ const from = Math.max(start, end - LIST_MARKER_REACH);
582
+ const tail = source.slice(from, end);
583
+ return from === start && LIST_MARKER_ALONE.test(tail) || LIST_MARKER_AFTER_COLON.test(tail);
584
+ }
585
+ function splitSentences(text) {
586
+ const source = (text ?? "").trim();
587
+ if (!source) return [];
588
+ const sentences = [];
589
+ let start = 0;
590
+ let scanned = 0;
591
+ let sawLetter = false;
592
+ let i = 0;
593
+ while (i < source.length) {
594
+ if (!isStop(source[i])) {
595
+ i++;
596
+ continue;
597
+ }
598
+ let endsStrong = false;
599
+ while (i < source.length && isStop(source[i])) {
600
+ if (source[i] !== ".") endsStrong = true;
601
+ i++;
602
+ }
603
+ while (i < source.length && CLOSERS.has(source[i])) i++;
604
+ let next = i;
605
+ while (next < source.length && isSpace(source[next])) next++;
606
+ if (next === i || !startsSentence(source, next)) continue;
607
+ if (!sawLetter) sawLetter = LETTER.test(source.slice(scanned, i));
608
+ scanned = i;
609
+ if (!sawLetter) continue;
610
+ if (!endsStrong && (endsWithAbbreviation(source, i, digitFollows(source, next)) || endsWithListMarker(source, start, i))) continue;
611
+ sentences.push(source.slice(start, i).trim());
612
+ start = i;
613
+ sawLetter = false;
614
+ }
615
+ const rest = source.slice(start).trim();
616
+ if (rest) sentences.push(rest);
617
+ return sentences;
618
+ }
619
+
1
620
  // src/contracts/llms.ts
2
621
  var LLM_GEO_CONTRACT_VERSION = 1;
3
622
  var LLMS_PUBLIC_SUMMARY_MAX_LENGTH = 400;
@@ -18,8 +637,8 @@ var MANAGED_LLM_SCHEMA_KNOWN_KEYS = /* @__PURE__ */ new Set([
18
637
  ]);
19
638
  var SENTENCE_SHARE = 0.4;
20
639
  var ELLIPSIS = "\u2026";
21
- var CLOSERS = /* @__PURE__ */ new Set(['"', "'", ")", "]", "\u201D", "\u2019", "\xBB"]);
22
- var ABBREVIATIONS = /* @__PURE__ */ new Set([
640
+ var CLOSERS2 = /* @__PURE__ */ new Set(['"', "'", ")", "]", "\u201D", "\u2019", "\xBB"]);
641
+ var ABBREVIATIONS2 = /* @__PURE__ */ new Set([
23
642
  "mr",
24
643
  "mrs",
25
644
  "ms",
@@ -56,7 +675,7 @@ function clipAtWordBoundary(text, max) {
56
675
  function endsSentence(flat, end) {
57
676
  if (flat[end] !== " ") return false;
58
677
  let i = end - 1;
59
- while (i > 0 && CLOSERS.has(flat[i])) i--;
678
+ while (i > 0 && CLOSERS2.has(flat[i])) i--;
60
679
  const terminator = flat[i];
61
680
  if (terminator === "!" || terminator === "?") return true;
62
681
  return terminator === "." && !isAbbreviation(flat, i);
@@ -66,16 +685,43 @@ function isAbbreviation(flat, dot) {
66
685
  while (start > 0 && flat[start - 1] !== " ") start--;
67
686
  const token = flat.slice(start, dot).replace(/^[("'[\u201c\u2018]+/, "").toLowerCase();
68
687
  if (token.includes(".") || /^[a-z]$/.test(token)) return true;
69
- return ABBREVIATIONS.has(token);
688
+ return ABBREVIATIONS2.has(token);
689
+ }
690
+ var PHONE_SOURCE = String.raw`(?:\+\d{1,3}[\s.-]?)?(?:\(\d{3}\)\s?|\b\d{3}[\s.-])\d{3}[\s.-]\d{4}\b` + String.raw`|\+\d[\d\s().-]{8,}\d` + String.raw`|\b\d{10}\b`;
691
+ var EMAIL_SOURCE = String.raw`[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}`;
692
+ var CONTACT_DETAIL = new RegExp(`${EMAIL_SOURCE}|${PHONE_SOURCE}`);
693
+ var CONTACT_LEAD_IN = /\b(?:call|text|phone|telephone|tel|dial|e-?mail|contact|reach|whatsapp)\b[^.!?]{0,40}$/i;
694
+ var HANGING_END = /(?:[\s,;:\u2013\u2014-]+|\s(?:and|or|at|on|by|via|to|from|with|using))$/i;
695
+ function stripContactDetails(text) {
696
+ if (!CONTACT_DETAIL.test(text)) return text;
697
+ const kept = [];
698
+ for (const sentence of splitSentences(text)) {
699
+ const hit = CONTACT_DETAIL.exec(sentence);
700
+ if (!hit) {
701
+ kept.push(sentence);
702
+ continue;
703
+ }
704
+ let before = sentence.slice(0, hit.index);
705
+ const lead = CONTACT_LEAD_IN.exec(before);
706
+ if (lead) before = before.slice(0, lead.index);
707
+ for (let pass = 0; pass < 4 && HANGING_END.test(before); pass++) before = before.replace(HANGING_END, "");
708
+ before = before.trim();
709
+ if (before.split(/\s+/).length >= 4) kept.push(`${before}.`);
710
+ }
711
+ return kept.join(" ");
712
+ }
713
+ var PAGE_FILLER = /^(?:this|the)\s+(?:[\w'\u2019-]+\s+){0,2}?(?:page|url|post|article|resource|guide|section|listing|case study)\s+(?:also\s+)?(?:covers|presents|describes|features|explains|provides|outlines|highlights|offers|details|shows|introduces|lists|includes|compares|answers|walks through|breaks down)\s+/i;
714
+ function stripPageFiller(note) {
715
+ const rest = note.replace(PAGE_FILLER, "");
716
+ if (rest === note || rest.length < 20) return note;
717
+ return `${rest.charAt(0).toUpperCase()}${rest.slice(1)}`;
70
718
  }
71
719
  function sanitizeLlmsPublicSummary(raw) {
72
720
  if (raw === null || raw === void 0) return null;
73
721
  if (typeof raw !== "string") return null;
74
- let s = raw.trim().replace(/\s+/g, " ");
75
- s = s.replace(/[\[\]\r\n]/g, " ").replace(/\s+/g, " ").trim();
722
+ let s = raw.replace(/[\[\]\r\n]/g, " ").replace(/\s+/g, " ").trim();
723
+ s = stripPageFiller(stripContactDetails(s));
76
724
  s = clipAtWordBoundary(s, LLMS_PUBLIC_SUMMARY_MAX_LENGTH);
77
- if (/\b[\w.%+-]+@[\w.-]+\.[A-Za-z]{2,}\b/.test(s)) return null;
78
- if (/\b\+?\d[\d\s().-]{8,}\b/.test(s)) return null;
79
725
  if (s.length < 8) return null;
80
726
  return s;
81
727
  }
@@ -141,5 +787,339 @@ function pickManagedLlmSchemaForJsonLd(schema) {
141
787
  }
142
788
  return Object.keys(out).length ? out : null;
143
789
  }
790
+ var LLMS_DESCRIPTION_MAX_LENGTH = 500;
791
+ var HARD_JOIN = "";
792
+ var SOFT_JOIN = "";
793
+ var SCRAPE_READ_LIMIT = 6e3;
794
+ var FORM_LABEL = String.raw`(?:first name|last name|full name|your name|name|e-?mail(?: address)?|your e-?mail|phone(?: number)?|mobile(?: number)?|message|your message|subject|company(?: name)?|address|city|state|zip(?: code)?|comments?|how can we help(?: you)?\??|preferred (?:contact method|date|time)|service (?:needed|requested)|submit|send(?: message)?|send us a message|get (?:a )?(?:free )?quote|request (?:a )?(?:free )?quote|required)`;
795
+ var FORM_RUN = new RegExp(String.raw`(?:\b${FORM_LABEL}\b[\s:*.]*){3,}`, "gi");
796
+ var STARRED_LABEL = new RegExp(String.raw`\b${FORM_LABEL}\b\s*\*`, "gi");
797
+ var NAV_WORD = String.raw`(?:Home|About(?: Us)?|Services|Contact(?: Us)?|Blog|Pricing|Resources|Work|Portfolio|Careers|Login|Log In|Sign In|Sign Up|FAQs?|Products|Gallery|Menu|Search|Cart|Shop|Reviews|Locations|Our Work|Our Services|Get a Quote|Book Now|Request a Quote)`;
798
+ var NAV_RUN = new RegExp(String.raw`(?:\b${NAV_WORD}\b[\s|\u2022\u00b7/>-]*){4,}`, "g");
799
+ var SKIP_AND_TOGGLE = /\b(?:skip to (?:main )?content|(?:toggle|open|close) (?:the )?(?:main )?(?:navigation|menu))\b/gi;
800
+ var COOKIE_BANNER = /\bcookies?\b.*\b(?:accept|consent|agree|settings|preferences|policy)\b|\b(?:accept|consent|agree)\b.*\bcookies?\b/i;
801
+ var CTA_BUTTON = String.raw`(?:book (?:a )?(?:free )?(?:demo|call|consultation)|read (?:the )?docs|read more|learn more|get started|get (?:a )?free (?:quote|audit|estimate)|call (?:us )?(?:now|today)|see (?:our )?work|view all|view (?:the )?demo|sign up|watch (?:the )?(?:video|demo))`;
802
+ var CHROME_PIECE = new RegExp(String.raw`^(?:${FORM_LABEL}|${NAV_WORD}|${CTA_BUTTON}|skip to (?:main )?content)\s*\*?\.?$`, "i");
803
+ var EYEBROW_MARK = /[\u00b7\u2022|\u203a\u00bb]/;
804
+ function wordCount(text) {
805
+ const trimmed = text.trim();
806
+ return trimmed ? trimmed.split(/\s+/).length : 0;
807
+ }
808
+ function camelCaseTerms(texts) {
809
+ const found = /* @__PURE__ */ new Set();
810
+ for (const text of texts) {
811
+ if (typeof text !== "string") continue;
812
+ for (const token of text.split(/\s+/)) {
813
+ const bare = token.replace(/^[^A-Za-z0-9]+|[^A-Za-z0-9]+$/g, "");
814
+ if (/[a-z][A-Z]/.test(bare)) found.add(bare);
815
+ }
816
+ }
817
+ return [...found];
818
+ }
819
+ var JOIN_WORDS = /* @__PURE__ */ new Set(["first", "last", "name", "email", "phone", "message", "subject", "company", "address", "city", "state", "zip", "submit", "send"]);
820
+ function isElementJoin(left, right) {
821
+ if (/^[a-z]$/.test(left)) return false;
822
+ if (JOIN_WORDS.has(left.toLowerCase()) || JOIN_WORDS.has(right.toLowerCase())) return true;
823
+ if (/^[a-z]/.test(left)) return left.length >= 3;
824
+ return left.length >= 8 || right.length >= 8;
825
+ }
826
+ function markGluedJoins(token, known) {
827
+ if (token.length < 5) return token;
828
+ if (known.has(token.replace(/^[^A-Za-z0-9]+|[^A-Za-z0-9]+$/g, ""))) return token;
829
+ return token.replace(/([.!?*:)\]])(?=[A-Z][a-z]{2,})/g, `$1${HARD_JOIN}`).replace(/([A-Z]{2,})(?=[A-Z][a-z]{2,})/g, `$1${SOFT_JOIN}`).replace(
830
+ /([A-Z]?[a-z]+)(?=([A-Z][a-z]+))/g,
831
+ (match, left, right) => isElementJoin(left, right) ? `${left}${HARD_JOIN}` : match
832
+ );
833
+ }
834
+ function isPageChrome(piece) {
835
+ if (CHROME_PIECE.test(piece)) return true;
836
+ if (piece.endsWith("*") && wordCount(piece) <= 4) return true;
837
+ return EYEBROW_MARK.test(piece) && piece.length <= 70 && wordCount(piece) <= 9;
838
+ }
839
+ function cleanScrapedPageText(raw, options = {}) {
840
+ if (typeof raw !== "string" || !raw.trim()) return "";
841
+ const known = new Set(options.knownTerms ?? []);
842
+ const marked = raw.slice(0, SCRAPE_READ_LIMIT).replace(/[\u00a0\u200b\u2060\ufeff\r]/g, " ").replace(/\n+/g, HARD_JOIN).replace(/\S+/g, (token) => markGluedJoins(token, known)).replace(new RegExp(SOFT_JOIN, "g"), " ");
843
+ const pieces = marked.split(HARD_JOIN).map((piece) => piece.replace(/\s+/g, " ").trim()).filter((piece) => piece && !isPageChrome(piece));
844
+ let joined = "";
845
+ for (const piece of pieces) {
846
+ if (!joined) {
847
+ joined = piece;
848
+ continue;
849
+ }
850
+ const clause = joined.slice(joined.search(/[^.!?]*$/));
851
+ const open = !/[.!?:;,"')\]]$/.test(joined);
852
+ joined += open && wordCount(clause) >= 3 && /^[A-Z]/.test(piece) ? `. ${piece}` : ` ${piece}`;
853
+ }
854
+ const text = joined.replace(SKIP_AND_TOGGLE, " ").replace(FORM_RUN, " ").replace(STARRED_LABEL, " ").replace(NAV_RUN, " ").replace(/\s+/g, " ").trim();
855
+ const cleaned = splitSentences(text).filter((sentence) => !COOKIE_BANNER.test(sentence)).join(" ").replace(/\s+([.,;:!?])/g, "$1").replace(/([.!?])(?:\s*[.!?])+/g, "$1").trim();
856
+ return cleaned.length >= 20 && wordCount(cleaned) >= 3 ? cleaned : "";
857
+ }
858
+ function plainNote(raw) {
859
+ return typeof raw === "string" ? clipAtWordBoundary(raw.replace(/[\[\]\r\n]/g, " "), LLMS_DESCRIPTION_MAX_LENGTH) : "";
860
+ }
861
+ function sameWords(a, b) {
862
+ const norm = (s) => s.toLowerCase().replace(/[^\p{L}\p{N}]+/gu, " ").trim();
863
+ return norm(a) === norm(b);
864
+ }
865
+ function isDescriptive(note, title) {
866
+ if (note.length < 15 || wordCount(note) < 3) return false;
867
+ if (title && sameWords(note, title)) return false;
868
+ return !/^(?:undefined|null|n\/a|none|lorem ipsum)\b/i.test(note);
869
+ }
870
+ function pickLlmsPageDescription(input) {
871
+ const title = plainNote(input.title);
872
+ const withheld = Boolean(input.copyWithheld);
873
+ const known = camelCaseTerms([
874
+ input.title,
875
+ input.llms_public_summary,
876
+ input.managed_meta_description,
877
+ input.meta_description,
878
+ input.llm_schema_description
879
+ ]);
880
+ const meta = plainNote(input.meta_description);
881
+ const candidates = [
882
+ ["summary", () => withheld ? "" : sanitizeLlmsPublicSummary(input.llms_public_summary) ?? ""],
883
+ ["managed_meta", () => withheld ? "" : plainNote(input.managed_meta_description)],
884
+ ["meta", () => withheld || looksLikeErrorPage(meta) ? "" : meta],
885
+ ["schema", () => withheld ? "" : plainNote(stripPageFiller(input.llm_schema_description ?? ""))],
886
+ [
887
+ "scrape",
888
+ () => plainNote(stripContactDetails(cleanScrapedPageText(usablePageText(input.content_text), { knownTerms: known })))
889
+ ]
890
+ ];
891
+ for (const [source, note] of candidates) {
892
+ const text = note();
893
+ if (isDescriptive(text, title)) return { text, source };
894
+ }
895
+ return null;
896
+ }
897
+ function resolveLlmsPageDescription(input) {
898
+ return pickLlmsPageDescription(input)?.text ?? null;
899
+ }
900
+ function llmsPageListNote(page, publicSummaryOnly = false) {
901
+ const summary = sanitizeLlmsPublicSummary(page.llms_public_summary);
902
+ if (summary) return summary;
903
+ if (publicSummaryOnly) return void 0;
904
+ return plainNote(page.description) || void 0;
905
+ }
906
+ function pagePathSegments(path) {
907
+ return path.replace(/^https?:\/\/[^/]+/i, "").split(/[?#]/, 1)[0].split("/").filter(Boolean);
908
+ }
909
+ var UTILITY_SLUGS = /* @__PURE__ */ new Set([
910
+ "thank-you",
911
+ "thanks",
912
+ "thankyou",
913
+ "confirmation",
914
+ "order-confirmation",
915
+ "cart",
916
+ "checkout",
917
+ "login",
918
+ "log-in",
919
+ "signin",
920
+ "sign-in",
921
+ "register",
922
+ "search",
923
+ "404",
924
+ "500",
925
+ "not-found",
926
+ "offline",
927
+ "preview",
928
+ "draft",
929
+ "unsubscribe",
930
+ "wp-admin",
931
+ "wp-login",
932
+ "admin"
933
+ ]);
934
+ function isLlmsListablePage(page) {
935
+ if (typeof page.http_status === "number" && page.http_status >= 400) return false;
936
+ const robots = (page.managed_robots_meta || page.robots_meta || "").toLowerCase();
937
+ if (/\bnoindex\b|\bnone\b/.test(robots)) return false;
938
+ if (!robots && page.has_noindex === true) return false;
939
+ const segments = pagePathSegments(page.path);
940
+ return !segments.some((segment) => UTILITY_SLUGS.has(segment.toLowerCase()));
941
+ }
942
+ function llmsPageTitle(page, homeName) {
943
+ for (const candidate of [page.managed_title, page.title, page.h1]) {
944
+ const head = titleHead(candidate);
945
+ if (head && isPageName(head, page.path) && !looksLikeErrorPage(head)) {
946
+ return clipAtWordBoundary(head, 120);
947
+ }
948
+ }
949
+ if (pagePathSegments(page.path).length === 0) return homeName?.trim() || "Home";
950
+ return slugLabel(page.path) || page.path;
951
+ }
952
+ function selectLlmsPages(pages, cap) {
953
+ if (cap <= 0) return [];
954
+ if (pages.length <= cap) return pages;
955
+ const clusters = /* @__PURE__ */ new Map();
956
+ for (const row of pages) {
957
+ const segment = row.path.replace(/^\//, "").split("/")[0] || "";
958
+ const cluster = `/${segment}`;
959
+ const bucket = clusters.get(cluster);
960
+ if (bucket) bucket.push(row);
961
+ else clusters.set(cluster, [row]);
962
+ }
963
+ const rank = ([cluster]) => {
964
+ const segment = cluster.slice(1);
965
+ if (!segment) return -1;
966
+ return LEGAL_SLUGS.has(segment.toLowerCase()) ? SECTION_VALUE_ORDER.length + 1 : valueRank(segment);
967
+ };
968
+ const selected = [];
969
+ const buckets = [...clusters.entries()].sort((a, b) => rank(a) - rank(b)).map(([, bucket]) => bucket);
970
+ for (let depth = 0; selected.length < cap; depth++) {
971
+ let placedAny = false;
972
+ for (const bucket of buckets) {
973
+ if (depth >= bucket.length) continue;
974
+ selected.push(bucket[depth]);
975
+ placedAny = true;
976
+ if (selected.length >= cap) break;
977
+ }
978
+ if (!placedAny) break;
979
+ }
980
+ const order = new Map(pages.map((row, index) => [row.path, index]));
981
+ selected.sort((a, b) => (order.get(a.path) ?? 0) - (order.get(b.path) ?? 0));
982
+ return selected;
983
+ }
984
+ var SECTION_VALUE_ORDER = [
985
+ "services",
986
+ "service",
987
+ "what-we-do",
988
+ "solutions",
989
+ "products",
990
+ "product",
991
+ "features",
992
+ "shop",
993
+ "locations",
994
+ "location",
995
+ "service-area",
996
+ "service-areas",
997
+ "areas-served",
998
+ "industries",
999
+ "work",
1000
+ "portfolio",
1001
+ "case-studies",
1002
+ "projects",
1003
+ "reviews",
1004
+ "testimonials",
1005
+ "about",
1006
+ "about-us",
1007
+ "team",
1008
+ "pricing",
1009
+ "plans",
1010
+ "resources",
1011
+ "guides",
1012
+ "insights",
1013
+ "blog",
1014
+ "articles",
1015
+ "news",
1016
+ "faq",
1017
+ "faqs",
1018
+ "contact",
1019
+ "contact-us"
1020
+ ];
1021
+ var LEGAL_SLUGS = /* @__PURE__ */ new Set([
1022
+ "privacy",
1023
+ "privacy-policy",
1024
+ "terms",
1025
+ "terms-of-service",
1026
+ "terms-and-conditions",
1027
+ "terms-of-use",
1028
+ "cookies",
1029
+ "cookie-policy",
1030
+ "accessibility",
1031
+ "disclaimer",
1032
+ "legal",
1033
+ "sitemap",
1034
+ "dmca",
1035
+ "refund-policy",
1036
+ "returns",
1037
+ "shipping-policy"
1038
+ ]);
1039
+ var FLAT_LIST_MAX = 12;
1040
+ function valueRank(segment) {
1041
+ const at = SECTION_VALUE_ORDER.indexOf(segment.toLowerCase());
1042
+ return at === -1 ? SECTION_VALUE_ORDER.length : at;
1043
+ }
1044
+ function groupLlmsPages(pages) {
1045
+ const index = new Map(pages.map((page, i) => [page, i]));
1046
+ const main = [];
1047
+ const legal = [];
1048
+ const sections = /* @__PURE__ */ new Map();
1049
+ for (const page of pages) {
1050
+ const segments = pagePathSegments(page.path);
1051
+ if (segments.some((segment) => LEGAL_SLUGS.has(segment.toLowerCase())) && segments.length <= 2) legal.push(page);
1052
+ else if (segments.length <= 1) main.push(page);
1053
+ else {
1054
+ const key = segments[0].toLowerCase();
1055
+ const bucket = sections.get(key);
1056
+ if (bucket) bucket.push(page);
1057
+ else sections.set(key, [page]);
1058
+ }
1059
+ }
1060
+ const byOriginal = (a, b) => (index.get(a) ?? 0) - (index.get(b) ?? 0);
1061
+ const mainRank = (page) => {
1062
+ const segments = pagePathSegments(page.path);
1063
+ return segments.length === 0 ? -1 : valueRank(segments[0]);
1064
+ };
1065
+ main.sort((a, b) => mainRank(a) - mainRank(b) || byOriginal(a, b));
1066
+ const grouped = [];
1067
+ const strays = [];
1068
+ for (const [key, bucket] of sections) {
1069
+ if (bucket.length === 1) strays.push(bucket[0]);
1070
+ else grouped.push([key, bucket.sort(byOriginal)]);
1071
+ }
1072
+ grouped.sort(([ka, a], [kb, b]) => valueRank(ka) - valueRank(kb) || b.length - a.length || ka.localeCompare(kb));
1073
+ strays.sort((a, b) => valueRank(pagePathSegments(a.path)[0]) - valueRank(pagePathSegments(b.path)[0]) || byOriginal(a, b));
1074
+ if (pages.length <= FLAT_LIST_MAX || grouped.length === 0) {
1075
+ return [{ heading: null, pages: [...main, ...strays, ...grouped.flatMap(([, bucket]) => bucket), ...legal] }];
1076
+ }
1077
+ const groups = [];
1078
+ if (main.length > 0) groups.push({ heading: "Key pages", pages: main });
1079
+ for (const [key, bucket] of grouped) groups.push({ heading: slugLabel(key) || key, pages: bucket });
1080
+ if (strays.length > 0) groups.push({ heading: "More pages", pages: strays });
1081
+ if (legal.length > 0) groups.push({ heading: "Legal", pages: legal });
1082
+ return groups;
1083
+ }
1084
+ function llmsBlockquoteSummary(business) {
1085
+ const tagline = business.tagline?.replace(/\s+/g, " ").trim();
1086
+ if (tagline) return tagline;
1087
+ const first = splitSentences(business.description)[0];
1088
+ return clipAtWordBoundary(first || business.name || "Website", 300);
1089
+ }
1090
+ function squash(value) {
1091
+ return typeof value === "string" ? value.replace(/\s+/g, " ").trim() : "";
1092
+ }
1093
+ function hoursLine(hours) {
1094
+ if (typeof hours === "string") return squash(hours);
1095
+ if (!hours || typeof hours !== "object" || Array.isArray(hours)) return "";
1096
+ return Object.entries(hours).map(([day, value]) => typeof value === "string" && squash(value) ? `${squash(day)} ${squash(value)}` : "").filter(Boolean).join("; ");
1097
+ }
1098
+ function llmsContactLines(contact) {
1099
+ if (!contact) return [];
1100
+ const lines = [];
1101
+ const phone = squash(contact.phone);
1102
+ const email = squash(contact.email);
1103
+ if (phone) lines.push(`- **Phone:** ${phone}`);
1104
+ if (email) lines.push(`- **Email:** ${email}`);
1105
+ const regionAndPostal = [squash(contact.state), squash(contact.postal_code)].filter(Boolean).join(" ");
1106
+ const country = squash(contact.country);
1107
+ const address = [
1108
+ squash(contact.address),
1109
+ squash(contact.city),
1110
+ regionAndPostal,
1111
+ /^(?:us|usa|united states(?: of america)?)$/i.test(country) ? "" : country
1112
+ ].filter(Boolean);
1113
+ if (address.length > 0) lines.push(`- **Address:** ${address.join(", ")}`);
1114
+ const hours = hoursLine(contact.hours);
1115
+ if (hours) lines.push(`- **Hours:** ${hours}`);
1116
+ return lines;
1117
+ }
1118
+ function llmsAbsoluteUrl(baseUrl, pathOrUrl) {
1119
+ if (/^[a-z][a-z0-9+.-]*:/i.test(pathOrUrl)) return pathOrUrl;
1120
+ const base = (baseUrl ?? "").trim().replace(/\/+$/, "");
1121
+ if (!base) return pathOrUrl;
1122
+ return `${base}${pathOrUrl.startsWith("/") ? "" : "/"}${pathOrUrl}`;
1123
+ }
144
1124
 
145
- export { LLMS_DISCLAIMER_MAX_LENGTH, LLMS_PUBLIC_SUMMARY_MAX_LENGTH, LLM_GEO_CONTRACT_VERSION, MANAGED_LLM_SCHEMA_KNOWN_KEYS, clipAtWordBoundary, isEmittableReviewNode, pickManagedLlmSchemaForJsonLd, sanitizeLlmsDisclaimerLine, sanitizeLlmsPublicSummary, sanitizePrimaryLanguageTag };
1125
+ export { LLMS_DESCRIPTION_MAX_LENGTH, LLMS_DISCLAIMER_MAX_LENGTH, LLMS_PUBLIC_SUMMARY_MAX_LENGTH, LLM_GEO_CONTRACT_VERSION, MANAGED_LLM_SCHEMA_KNOWN_KEYS, cleanScrapedPageText, clipAtWordBoundary, groupLlmsPages, isEmittableReviewNode, isLlmsListablePage, llmsAbsoluteUrl, llmsBlockquoteSummary, llmsContactLines, llmsPageListNote, llmsPageTitle, pickLlmsPageDescription, pickManagedLlmSchemaForJsonLd, resolveLlmsPageDescription, sanitizeLlmsDisclaimerLine, sanitizeLlmsPublicSummary, sanitizePrimaryLanguageTag, selectLlmsPages, stripContactDetails, stripPageFiller };