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