"""Template building system for AGENTS.md compilation.""" import re from dataclasses import dataclass from pathlib import Path from typing import List, Dict, Optional, Tuple from ..primitives.models import Instruction, Chatmode @dataclass class TemplateData: """Data structure for template generation.""" instructions_content: str # Removed volatile timestamp for deterministic builds version: str chatmode_content: Optional[str] = None def build_conditional_sections(instructions: List[Instruction]) -> str: """Build sections grouped by applyTo patterns. Args: instructions (List[Instruction]): List of instruction primitives. Returns: str: Formatted conditional sections content. """ if not instructions: return "" # Group instructions by pattern - use raw patterns pattern_groups = _group_instructions_by_pattern(instructions) sections = [] for pattern, pattern_instructions in pattern_groups.items(): sections.append(f"## Files matching `{pattern}`") sections.append("") # Combine content from all instructions for this pattern for instruction in pattern_instructions: content = instruction.content.strip() if content: # Add source file comment before the content try: # Try to get relative path for cleaner display if instruction.file_path.is_absolute(): relative_path = instruction.file_path.relative_to(Path.cwd()) else: relative_path = instruction.file_path except (ValueError, OSError): # Fall back to absolute or given path if relative fails relative_path = instruction.file_path sections.append(f"") sections.append(content) sections.append(f"") sections.append("") return "\n".join(sections) def find_chatmode_by_name(chatmodes: List[Chatmode], chatmode_name: str) -> Optional[Chatmode]: """Find a chatmode by name. Args: chatmodes (List[Chatmode]): List of available chatmodes. chatmode_name (str): Name of the chatmode to find. Returns: Optional[Chatmode]: The found chatmode, or None if not found. """ for chatmode in chatmodes: if chatmode.name == chatmode_name: return chatmode return None def _group_instructions_by_pattern(instructions: List[Instruction]) -> Dict[str, List[Instruction]]: """Group instructions by applyTo patterns. Args: instructions (List[Instruction]): List of instructions to group. Returns: Dict[str, List[Instruction]]: Grouped instructions with raw patterns as keys. """ pattern_groups: Dict[str, List[Instruction]] = {} for instruction in instructions: if not instruction.apply_to: continue pattern = instruction.apply_to if pattern not in pattern_groups: pattern_groups[pattern] = [] pattern_groups[pattern].append(instruction) return pattern_groups def generate_agents_md_template(template_data: TemplateData) -> str: """Generate the complete AGENTS.md file content. Args: template_data (TemplateData): Data for template generation. Returns: str: Complete AGENTS.md file content. """ sections = [] # Header sections.append("# AGENTS.md") sections.append(f"") from .constants import BUILD_ID_PLACEHOLDER sections.append(BUILD_ID_PLACEHOLDER) sections.append(f"") sections.append("") # Chatmode content (if provided) if template_data.chatmode_content: sections.append(template_data.chatmode_content.strip()) sections.append("") # Instructions content (grouped by patterns) if template_data.instructions_content: sections.append(template_data.instructions_content) # Footer sections.append("---") sections.append("*This file was generated by APM CLI. Do not edit manually.*") sections.append("*To regenerate: `specify apm compile`*") sections.append("") return "\n".join(sections)