138 lines
4.4 KiB
Python
138 lines
4.4 KiB
Python
"""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"<!-- Source: {relative_path} -->")
|
|
sections.append(content)
|
|
sections.append(f"<!-- End source: {relative_path} -->")
|
|
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"<!-- Generated by APM CLI from .apm/ primitives -->")
|
|
from .constants import BUILD_ID_PLACEHOLDER
|
|
sections.append(BUILD_ID_PLACEHOLDER)
|
|
sections.append(f"<!-- APM Version: {template_data.version} -->")
|
|
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) |