From c12dbf932566a4723e132d4a1f3d93eba9e05e93 Mon Sep 17 00:00:00 2001 From: ValhallaBuilder <286693580+4gjnbzb4zf-sudo@users.noreply.github.com> Date: Sun, 7 Jun 2026 19:26:40 -0400 Subject: [PATCH] fix(buildIntent): hoist inferProductPhraseProjectName regex compilations to module scope MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `inferProductPhraseProjectName` is invoked from the build-intent parser on every Telegram-bot /build PRD heuristic check. Each call rebuilds four identical RegExp objects from a static `productType` fragment that never changes — three inside the patterns array and a fourth inside the for-loop's match-tail check. RegExp construction parses the source pattern every time, so per-message PRD parsing pays a redundant ~4x compile cost. Hoist the productType fragment + the three patterns + the tail-match regex to module-level constants. Behavior is byte-identical: the four RegExp instances become long-lived singletons instead of being rebuilt per call. The function body now reads from the cached PRODUCT_PHRASE_PATTERNS array and PRODUCT_TYPE_TAIL constant. --- src/buildIntent.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/buildIntent.ts b/src/buildIntent.ts index 35998ef79..05f75e603 100644 --- a/src/buildIntent.ts +++ b/src/buildIntent.ts @@ -141,14 +141,19 @@ export function polishBuildProjectName(value: string): string { return polishInferredProjectName(clean); } +const PRODUCT_TYPE_PATTERN = '(?:domain[-\\s]*chip|landing\\s+page|dashboard|workbench|agent|tool|app|game|system|tracker|planner|timer|clock|site|website|page)'; + +const PRODUCT_PHRASE_PATTERNS = [ + new RegExp(`^(?:this\\s+)?(?:(?:a|an|the|new)\\s+)?([A-Za-z0-9][A-Za-z0-9' -]{2,90}?\\b${PRODUCT_TYPE_PATTERN})\\b(?=[.,:;?!]|\\s+(?:that|which|where|with|for|to|using|and|plan|prototype|build|only|minimal|playable)\\b|$)`, 'i'), + new RegExp(`\\b(?:build|create|make|scaffold|ship|implement|design)\\s+(?:this\\s+)?(?:(?:a|an|the|new)\\s+)?([A-Za-z0-9][A-Za-z0-9' -]{2,90}?\\b${PRODUCT_TYPE_PATTERN})\\b(?=[.,:;?!]|\\s+(?:that|which|where|with|for|to|using|and|plan|prototype|build|only|minimal|playable)\\b|$)`, 'i'), + new RegExp(`\\bi\\s+(?:want|need|could\\s+use|would\\s+like)\\s+(?:(?:a|an|the|new)\\s+)?([A-Za-z0-9][A-Za-z0-9' -]{2,90}?\\b${PRODUCT_TYPE_PATTERN})\\b(?=[.,:;?!]|\\s+(?:that|which|where|with|for|to|using|and|plan|prototype|build|only|minimal|playable)\\b|$)`, 'i') +]; + +const PRODUCT_TYPE_TAIL = new RegExp(`\\b${PRODUCT_TYPE_PATTERN}\\b$`, 'i'); + function inferProductPhraseProjectName(prd: string): string | null { const normalized = prd.replace(/\s+/g, ' ').trim(); - const productType = '(?:domain[-\\s]*chip|landing\\s+page|dashboard|workbench|agent|tool|app|game|system|tracker|planner|timer|clock|site|website|page)'; - const patterns = [ - new RegExp(`^(?:this\\s+)?(?:(?:a|an|the|new)\\s+)?([A-Za-z0-9][A-Za-z0-9' -]{2,90}?\\b${productType})\\b(?=[.,:;?!]|\\s+(?:that|which|where|with|for|to|using|and|plan|prototype|build|only|minimal|playable)\\b|$)`, 'i'), - new RegExp(`\\b(?:build|create|make|scaffold|ship|implement|design)\\s+(?:this\\s+)?(?:(?:a|an|the|new)\\s+)?([A-Za-z0-9][A-Za-z0-9' -]{2,90}?\\b${productType})\\b(?=[.,:;?!]|\\s+(?:that|which|where|with|for|to|using|and|plan|prototype|build|only|minimal|playable)\\b|$)`, 'i'), - new RegExp(`\\bi\\s+(?:want|need|could\\s+use|would\\s+like)\\s+(?:(?:a|an|the|new)\\s+)?([A-Za-z0-9][A-Za-z0-9' -]{2,90}?\\b${productType})\\b(?=[.,:;?!]|\\s+(?:that|which|where|with|for|to|using|and|plan|prototype|build|only|minimal|playable)\\b|$)`, 'i') - ]; + const patterns = PRODUCT_PHRASE_PATTERNS; const genericLeadingWords = new Set([ 'a', 'an', @@ -182,7 +187,7 @@ function inferProductPhraseProjectName(prd: string): string | null { words = words.slice(1); } phrase = words.join(' ').replace(/\s+/g, ' ').trim().replace(/[.!?]+$/, ''); - const productMatch = phrase.match(new RegExp(`\\b${productType}\\b$`, 'i')); + const productMatch = phrase.match(PRODUCT_TYPE_TAIL); if (!productMatch) continue; const qualifier = phrase.slice(0, productMatch.index).trim(); const meaningfulQualifier = qualifier