| """ |
| agents_md.py — Parser for repo-local AGENTS.md instruction files. |
| |
| Precedence: system_policy > developer_config > AGENTS.md > team_purpose > skill_cards > user_task |
| """ |
| from __future__ import annotations |
| import logging |
| from dataclasses import dataclass, field |
| from pathlib import Path |
| from typing import Any |
|
|
| logger = logging.getLogger(__name__) |
|
|
| @dataclass |
| class AgentsConfig: |
| instructions: list[str] = field(default_factory=list) |
| capabilities: dict[str, str] = field(default_factory=dict) |
| constraints: list[str] = field(default_factory=list) |
| metadata: dict[str, Any] = field(default_factory=dict) |
| source_path: str = "" |
| def to_prompt_section(self) -> str: |
| parts = [] |
| if self.instructions: |
| parts.append("## Repository Instructions (AGENTS.md)") |
| for i in self.instructions: parts.append(f"- {i}") |
| if self.constraints: |
| parts.append("\n## Constraints") |
| for c in self.constraints: parts.append(f"- {c}") |
| return "\n".join(parts) |
| @property |
| def has_content(self) -> bool: |
| return bool(self.instructions or self.capabilities or self.constraints) |
|
|
| def parse_agents_md(content: str, source_path: str = "") -> AgentsConfig: |
| config = AgentsConfig(source_path=source_path) |
| if content.startswith("---"): |
| end = content.find("---", 3) |
| if end > 0: content = content[end+3:] |
| current = None |
| for line in content.split("\n"): |
| line = line.strip() |
| if line.startswith("## ") or line.startswith("# "): |
| h = line.lstrip("#").strip().lower() |
| if "instruction" in h: current = "instructions" |
| elif "capabilit" in h: current = "capabilities" |
| elif "constraint" in h: current = "constraints" |
| else: current = None |
| continue |
| if not line or line.startswith("<!--"): continue |
| item = line.lstrip("- ").lstrip("* ").strip() |
| if not item: continue |
| if current == "instructions": config.instructions.append(item) |
| elif current == "capabilities": |
| if ":" in item: |
| n, d = item.split(":", 1); config.capabilities[n.strip()] = d.strip() |
| else: config.capabilities[item] = "" |
| elif current == "constraints": config.constraints.append(item) |
| return config |
|
|
| def load_agents_md(path: str = "AGENTS.md") -> AgentsConfig | None: |
| for p in [Path(path), Path(".github/AGENTS.md"), Path("docs/AGENTS.md")]: |
| if p.exists(): |
| try: |
| config = parse_agents_md(p.read_text(), str(p)) |
| if config.has_content: return config |
| except: pass |
| return None |
|
|