universal-agent-context 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- uacs/__init__.py +12 -0
- uacs/adapters/__init__.py +19 -0
- uacs/adapters/agent_skill_adapter.py +202 -0
- uacs/adapters/agents_md_adapter.py +330 -0
- uacs/adapters/base.py +261 -0
- uacs/adapters/clinerules_adapter.py +39 -0
- uacs/adapters/cursorrules_adapter.py +39 -0
- uacs/api.py +262 -0
- uacs/cli/__init__.py +6 -0
- uacs/cli/context.py +349 -0
- uacs/cli/main.py +195 -0
- uacs/cli/mcp.py +115 -0
- uacs/cli/memory.py +142 -0
- uacs/cli/packages.py +309 -0
- uacs/cli/skills.py +144 -0
- uacs/cli/utils.py +24 -0
- uacs/config/repositories.yaml +26 -0
- uacs/context/__init__.py +0 -0
- uacs/context/agent_context.py +406 -0
- uacs/context/shared_context.py +661 -0
- uacs/context/unified_context.py +332 -0
- uacs/mcp_server_entry.py +80 -0
- uacs/memory/__init__.py +5 -0
- uacs/memory/simple_memory.py +255 -0
- uacs/packages/__init__.py +26 -0
- uacs/packages/manager.py +413 -0
- uacs/packages/models.py +60 -0
- uacs/packages/sources.py +270 -0
- uacs/protocols/__init__.py +5 -0
- uacs/protocols/mcp/__init__.py +8 -0
- uacs/protocols/mcp/manager.py +77 -0
- uacs/protocols/mcp/skills_server.py +700 -0
- uacs/skills_validator.py +367 -0
- uacs/utils/__init__.py +5 -0
- uacs/utils/paths.py +24 -0
- uacs/visualization/README.md +132 -0
- uacs/visualization/__init__.py +36 -0
- uacs/visualization/models.py +195 -0
- uacs/visualization/static/index.html +857 -0
- uacs/visualization/storage.py +402 -0
- uacs/visualization/visualization.py +328 -0
- uacs/visualization/web_server.py +364 -0
- universal_agent_context-0.2.0.dist-info/METADATA +873 -0
- universal_agent_context-0.2.0.dist-info/RECORD +47 -0
- universal_agent_context-0.2.0.dist-info/WHEEL +4 -0
- universal_agent_context-0.2.0.dist-info/entry_points.txt +2 -0
- universal_agent_context-0.2.0.dist-info/licenses/LICENSE +21 -0
uacs/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Universal Agent Context System (UACS)
|
|
2
|
+
|
|
3
|
+
Provides unified context management for AI agents:
|
|
4
|
+
- Marketplace discovery (Skills + MCP)
|
|
5
|
+
- Format adapters (Agent Skills, AGENTS.md, etc.)
|
|
6
|
+
- Context management (shared memory + compression)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from uacs.api import UACS
|
|
10
|
+
|
|
11
|
+
__version__ = "0.2.0"
|
|
12
|
+
__all__ = ["UACS"]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Format adapters for various instruction formats."""
|
|
2
|
+
|
|
3
|
+
from .agent_skill_adapter import AgentSkillAdapter
|
|
4
|
+
from .agents_md_adapter import AgentsMDAdapter, AgentsMDConfig, AgentsMDSection
|
|
5
|
+
from .base import BaseFormatAdapter, FormatAdapterRegistry, ParsedContent
|
|
6
|
+
from .clinerules_adapter import ClineRulesAdapter
|
|
7
|
+
from .cursorrules_adapter import CursorRulesAdapter
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"AgentSkillAdapter",
|
|
11
|
+
"AgentsMDAdapter",
|
|
12
|
+
"AgentsMDConfig",
|
|
13
|
+
"AgentsMDSection",
|
|
14
|
+
"BaseFormatAdapter",
|
|
15
|
+
"ClineRulesAdapter",
|
|
16
|
+
"CursorRulesAdapter",
|
|
17
|
+
"FormatAdapterRegistry",
|
|
18
|
+
"ParsedContent",
|
|
19
|
+
]
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""Adapter for Agent Skills SKILL.md format (individual skill files).
|
|
2
|
+
|
|
3
|
+
Supports both .agent/ (recommended) and .claude/ (for importing external repos) directories.
|
|
4
|
+
See https://agentskills.io for the vendor-neutral format specification.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from .base import BaseFormatAdapter, FormatAdapterRegistry, ParsedContent
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@FormatAdapterRegistry.register
|
|
15
|
+
class AgentSkillAdapter(BaseFormatAdapter):
|
|
16
|
+
"""Agent Skills SKILL.md format adapter.
|
|
17
|
+
|
|
18
|
+
Supports the vendor-neutral Agent Skills format with YAML frontmatter:
|
|
19
|
+
```
|
|
20
|
+
---
|
|
21
|
+
name: skill-name
|
|
22
|
+
description: Brief description
|
|
23
|
+
---
|
|
24
|
+
# skill-name
|
|
25
|
+
|
|
26
|
+
## Instructions
|
|
27
|
+
...
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
See https://agentskills.io for full specification.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
FORMAT_NAME = "agent_skill"
|
|
34
|
+
SUPPORTED_FILES = ["SKILL.md"]
|
|
35
|
+
|
|
36
|
+
source_directory: str | None = None
|
|
37
|
+
|
|
38
|
+
def parse(self, content: str) -> ParsedContent:
|
|
39
|
+
"""Parse Claude Code SKILL.md format.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
content: Raw SKILL.md content
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
ParsedContent with skill metadata and instructions
|
|
46
|
+
"""
|
|
47
|
+
# Parse YAML frontmatter
|
|
48
|
+
metadata = {}
|
|
49
|
+
instructions = content
|
|
50
|
+
|
|
51
|
+
# Check for YAML frontmatter (--- ... ---)
|
|
52
|
+
yaml_pattern = r"^---\s*\n(.*?)\n---\s*\n(.*)$"
|
|
53
|
+
match = re.match(yaml_pattern, content, re.DOTALL)
|
|
54
|
+
|
|
55
|
+
if match:
|
|
56
|
+
yaml_content = match.group(1)
|
|
57
|
+
instructions = match.group(2)
|
|
58
|
+
|
|
59
|
+
# Parse simple YAML (key: value pairs)
|
|
60
|
+
for line in yaml_content.split("\n"):
|
|
61
|
+
line = line.strip()
|
|
62
|
+
if ":" in line:
|
|
63
|
+
key, value = line.split(":", 1)
|
|
64
|
+
metadata[key.strip()] = value.strip()
|
|
65
|
+
|
|
66
|
+
return ParsedContent(
|
|
67
|
+
name=metadata.get("name", "unnamed-skill"),
|
|
68
|
+
description=metadata.get("description", ""),
|
|
69
|
+
instructions=instructions.strip(),
|
|
70
|
+
metadata=metadata,
|
|
71
|
+
triggers=self._extract_triggers(instructions),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def _extract_triggers(self, content: str) -> list[str]:
|
|
75
|
+
"""Extract triggers from markdown content."""
|
|
76
|
+
triggers = []
|
|
77
|
+
|
|
78
|
+
# Look for ## Triggers section
|
|
79
|
+
trigger_section = re.search(
|
|
80
|
+
r"##\s+Triggers\s*\n(.*?)(?:\n##|$)", content, re.DOTALL | re.IGNORECASE
|
|
81
|
+
)
|
|
82
|
+
if trigger_section:
|
|
83
|
+
section_content = trigger_section.group(1)
|
|
84
|
+
# Extract list items
|
|
85
|
+
for line in section_content.split("\n"):
|
|
86
|
+
line = line.strip()
|
|
87
|
+
if line.startswith("- "):
|
|
88
|
+
triggers.append(line[2:].strip())
|
|
89
|
+
|
|
90
|
+
return triggers
|
|
91
|
+
|
|
92
|
+
def to_system_prompt(self) -> str:
|
|
93
|
+
"""Convert to system prompt.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Formatted system prompt
|
|
97
|
+
"""
|
|
98
|
+
if not self.parsed:
|
|
99
|
+
return ""
|
|
100
|
+
|
|
101
|
+
prompt_parts = [
|
|
102
|
+
f"# Skill: {self.parsed.name}",
|
|
103
|
+
"",
|
|
104
|
+
f"**Description**: {self.parsed.description}",
|
|
105
|
+
"",
|
|
106
|
+
"## Instructions",
|
|
107
|
+
self.parsed.instructions,
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
return "\n".join(prompt_parts)
|
|
111
|
+
|
|
112
|
+
def to_adk_capabilities(self) -> dict[str, Any]:
|
|
113
|
+
"""Convert to ADK capability format.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
ADK capability dictionary
|
|
117
|
+
"""
|
|
118
|
+
if not self.parsed:
|
|
119
|
+
return {}
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
"name": self.parsed.name,
|
|
123
|
+
"description": self.parsed.description,
|
|
124
|
+
"instructions": self.parsed.instructions,
|
|
125
|
+
"metadata": self.parsed.metadata,
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@classmethod
|
|
129
|
+
def discover_skills(cls, project_path: Path) -> list["AgentSkillAdapter"]:
|
|
130
|
+
"""Discover all SKILL.md files in project and personal directories.
|
|
131
|
+
|
|
132
|
+
Searches multiple directories with precedence:
|
|
133
|
+
1. project_path/.agent/skills/*/SKILL.md (highest priority)
|
|
134
|
+
2. ~/.agent/skills/*/SKILL.md
|
|
135
|
+
3. project_path/.claude/skills/*/SKILL.md
|
|
136
|
+
4. ~/.claude/skills/*/SKILL.md (lowest priority)
|
|
137
|
+
|
|
138
|
+
When duplicate skill names exist, .agent/ versions take precedence.
|
|
139
|
+
The .claude/ directory is supported for importing external skill repos.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
List of skill adapters, deduplicated by skill name
|
|
143
|
+
"""
|
|
144
|
+
skills_by_name = {} # Use dict to handle deduplication
|
|
145
|
+
|
|
146
|
+
# Search in precedence order (higher priority first)
|
|
147
|
+
search_paths = [
|
|
148
|
+
project_path / ".agent" / "skills",
|
|
149
|
+
Path.home() / ".agent" / "skills",
|
|
150
|
+
project_path / ".claude" / "skills",
|
|
151
|
+
Path.home() / ".claude" / "skills",
|
|
152
|
+
]
|
|
153
|
+
|
|
154
|
+
for search_path in search_paths:
|
|
155
|
+
if not search_path.exists():
|
|
156
|
+
continue
|
|
157
|
+
|
|
158
|
+
# Find all SKILL.md files in subdirectories
|
|
159
|
+
for skill_dir in search_path.iterdir():
|
|
160
|
+
if not skill_dir.is_dir():
|
|
161
|
+
continue
|
|
162
|
+
|
|
163
|
+
skill_file = skill_dir / "SKILL.md"
|
|
164
|
+
if skill_file.exists():
|
|
165
|
+
try:
|
|
166
|
+
adapter = cls(skill_file)
|
|
167
|
+
skill_name = (
|
|
168
|
+
adapter.parsed.name if adapter.parsed else skill_dir.name
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# Only add if not already found (precedence: earlier paths win)
|
|
172
|
+
if skill_name not in skills_by_name:
|
|
173
|
+
# Track source directory for metadata
|
|
174
|
+
adapter.source_directory = str(search_path)
|
|
175
|
+
skills_by_name[skill_name] = adapter
|
|
176
|
+
except Exception as e:
|
|
177
|
+
# Log warning but continue discovering other skills
|
|
178
|
+
print(f"Warning: Failed to load {skill_file}: {e}")
|
|
179
|
+
|
|
180
|
+
return list(skills_by_name.values())
|
|
181
|
+
|
|
182
|
+
def find_skill_by_trigger(self, query: str) -> bool:
|
|
183
|
+
"""Check if this skill matches a query trigger.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
query: Query string to match against triggers
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
True if any trigger matches the query
|
|
190
|
+
"""
|
|
191
|
+
if not self.parsed or not hasattr(self.parsed, "triggers"):
|
|
192
|
+
return False
|
|
193
|
+
|
|
194
|
+
query_lower = query.lower()
|
|
195
|
+
for trigger in self.parsed.triggers:
|
|
196
|
+
if trigger.lower() in query_lower or query_lower in trigger.lower():
|
|
197
|
+
return True
|
|
198
|
+
|
|
199
|
+
return False
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
__all__ = ["AgentSkillAdapter"]
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"""Adapter for AGENTS.md specification support.
|
|
2
|
+
|
|
3
|
+
This module parses AGENTS.md files (the open standard for AI agent instructions)
|
|
4
|
+
and converts them to A2A/ADK-compatible agent capabilities. The adapter follows
|
|
5
|
+
the AGENTS.md specification and integrates with the Agent Development Kit (ADK)
|
|
6
|
+
and Agent-to-Agent (A2A) protocol.
|
|
7
|
+
|
|
8
|
+
Spec: https://agents.md/
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from .base import BaseFormatAdapter, FormatAdapterRegistry, ParsedContent
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class AgentsMDSection:
|
|
20
|
+
"""Represents a parsed section from AGENTS.md.
|
|
21
|
+
|
|
22
|
+
Attributes:
|
|
23
|
+
title: Section title/heading text.
|
|
24
|
+
content: Section body content as markdown.
|
|
25
|
+
level: Heading level (1 for #, 2 for ##, etc.).
|
|
26
|
+
subsections: Nested child sections.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
title: str
|
|
30
|
+
content: str
|
|
31
|
+
level: int
|
|
32
|
+
subsections: list["AgentsMDSection"]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class AgentsMDConfig:
|
|
37
|
+
"""Parsed AGENTS.md configuration.
|
|
38
|
+
|
|
39
|
+
Attributes:
|
|
40
|
+
project_overview: High-level project description and context.
|
|
41
|
+
setup_commands: Commands for project setup/installation.
|
|
42
|
+
dev_environment_tips: Development environment recommendations.
|
|
43
|
+
code_style: Code style guidelines and conventions.
|
|
44
|
+
build_commands: Commands for building the project.
|
|
45
|
+
testing_instructions: How to run tests and validation.
|
|
46
|
+
security_considerations: Security guidelines and best practices.
|
|
47
|
+
pr_instructions: Pull request guidelines and workflow.
|
|
48
|
+
custom_sections: Additional custom sections not in the standard spec.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
project_overview: str
|
|
52
|
+
setup_commands: list[str]
|
|
53
|
+
dev_environment_tips: list[str]
|
|
54
|
+
code_style: list[str]
|
|
55
|
+
build_commands: list[str]
|
|
56
|
+
testing_instructions: list[str]
|
|
57
|
+
security_considerations: list[str]
|
|
58
|
+
pr_instructions: list[str]
|
|
59
|
+
custom_sections: dict[str, str]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@FormatAdapterRegistry.register
|
|
63
|
+
class AgentsMDAdapter(BaseFormatAdapter):
|
|
64
|
+
"""Adapter for parsing and using AGENTS.md files.
|
|
65
|
+
|
|
66
|
+
This adapter implements support for the AGENTS.md specification, which provides
|
|
67
|
+
a standardized way to document project context and guidelines for AI agents.
|
|
68
|
+
It parses AGENTS.md files and converts them to A2A/ADK-compatible capabilities.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
FORMAT_NAME = "agents_md"
|
|
72
|
+
SUPPORTED_FILES = ["AGENTS.md"]
|
|
73
|
+
|
|
74
|
+
def __init__(self, file_path: Path | None):
|
|
75
|
+
"""Initialize AGENTS.md adapter.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
file_path: Path to AGENTS.md file
|
|
79
|
+
"""
|
|
80
|
+
# Call parent init which will parse the content
|
|
81
|
+
super().__init__(file_path)
|
|
82
|
+
|
|
83
|
+
# Store parsed config for backward compatibility
|
|
84
|
+
self.config: AgentsMDConfig | None = None
|
|
85
|
+
if self.parsed:
|
|
86
|
+
self.config = self.parsed.to_dict().get("config")
|
|
87
|
+
|
|
88
|
+
# For backward compatibility
|
|
89
|
+
self.project_root = file_path.parent if file_path else None
|
|
90
|
+
self.agents_md_path = file_path
|
|
91
|
+
|
|
92
|
+
def parse(self, content: str) -> ParsedContent:
|
|
93
|
+
"""Parse AGENTS.md content (required by base class).
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
content: Raw AGENTS.md content
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
ParsedContent with parsed configuration
|
|
100
|
+
"""
|
|
101
|
+
config = self._parse_agents_md_content(content)
|
|
102
|
+
return ParsedContent(config=config)
|
|
103
|
+
|
|
104
|
+
def _parse_agents_md_content(self, content: str) -> AgentsMDConfig:
|
|
105
|
+
"""Parse AGENTS.md file into structured config.
|
|
106
|
+
|
|
107
|
+
Extracts standard sections (overview, setup, code style, etc.) and
|
|
108
|
+
custom sections from the AGENTS.md file into a structured configuration.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
content: Raw AGENTS.md content
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Parsed AgentsMDConfig
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
# Initialize config with defaults
|
|
118
|
+
config = AgentsMDConfig(
|
|
119
|
+
project_overview="",
|
|
120
|
+
setup_commands=[],
|
|
121
|
+
dev_environment_tips=[],
|
|
122
|
+
code_style=[],
|
|
123
|
+
build_commands=[],
|
|
124
|
+
testing_instructions=[],
|
|
125
|
+
security_considerations=[],
|
|
126
|
+
pr_instructions=[],
|
|
127
|
+
custom_sections={},
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Parse sections
|
|
131
|
+
sections = self._parse_sections(content)
|
|
132
|
+
|
|
133
|
+
for section in sections:
|
|
134
|
+
title_lower = section.title.lower()
|
|
135
|
+
|
|
136
|
+
if "overview" in title_lower or "project" in title_lower:
|
|
137
|
+
config.project_overview = section.content.strip()
|
|
138
|
+
elif "setup" in title_lower:
|
|
139
|
+
config.setup_commands = self._extract_commands(section.content)
|
|
140
|
+
elif "dev" in title_lower or "environment" in title_lower:
|
|
141
|
+
config.dev_environment_tips = self._extract_bullets(section.content)
|
|
142
|
+
elif "style" in title_lower or "code" in title_lower:
|
|
143
|
+
config.code_style = self._extract_bullets(section.content)
|
|
144
|
+
elif "build" in title_lower:
|
|
145
|
+
config.build_commands = self._extract_commands(section.content)
|
|
146
|
+
elif "test" in title_lower:
|
|
147
|
+
config.testing_instructions = self._extract_bullets(section.content)
|
|
148
|
+
elif "security" in title_lower:
|
|
149
|
+
config.security_considerations = self._extract_bullets(section.content)
|
|
150
|
+
elif "pr" in title_lower or "pull request" in title_lower:
|
|
151
|
+
config.pr_instructions = self._extract_bullets(section.content)
|
|
152
|
+
else:
|
|
153
|
+
# Custom section
|
|
154
|
+
config.custom_sections[section.title] = section.content.strip()
|
|
155
|
+
|
|
156
|
+
return config
|
|
157
|
+
|
|
158
|
+
def _parse_sections(self, content: str) -> list[AgentsMDSection]:
|
|
159
|
+
"""Parse markdown sections.
|
|
160
|
+
|
|
161
|
+
Identifies markdown headers (# ## ###) and groups content into
|
|
162
|
+
hierarchical sections with their heading levels preserved.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
content: Markdown content
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
List of parsed sections
|
|
169
|
+
"""
|
|
170
|
+
sections = []
|
|
171
|
+
lines = content.split("\n")
|
|
172
|
+
current_section = None
|
|
173
|
+
current_content = []
|
|
174
|
+
|
|
175
|
+
for line in lines:
|
|
176
|
+
# Check for section header
|
|
177
|
+
if line.startswith("#"):
|
|
178
|
+
# Save previous section
|
|
179
|
+
if current_section:
|
|
180
|
+
current_section.content = "\n".join(current_content)
|
|
181
|
+
sections.append(current_section)
|
|
182
|
+
|
|
183
|
+
# Parse new section
|
|
184
|
+
level = len(re.match(r"^#+", line).group())
|
|
185
|
+
title = line.lstrip("#").strip()
|
|
186
|
+
|
|
187
|
+
current_section = AgentsMDSection(
|
|
188
|
+
title=title, content="", level=level, subsections=[]
|
|
189
|
+
)
|
|
190
|
+
current_content = []
|
|
191
|
+
elif current_section:
|
|
192
|
+
current_content.append(line)
|
|
193
|
+
|
|
194
|
+
# Save last section
|
|
195
|
+
if current_section:
|
|
196
|
+
current_section.content = "\n".join(current_content)
|
|
197
|
+
sections.append(current_section)
|
|
198
|
+
|
|
199
|
+
return sections
|
|
200
|
+
|
|
201
|
+
def _extract_commands(self, content: str) -> list[str]:
|
|
202
|
+
"""Extract command strings from content.
|
|
203
|
+
|
|
204
|
+
Finds commands in inline code (`command`) and code blocks,
|
|
205
|
+
typically used for setup, build, and test commands.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
content: Section content
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
List of command strings
|
|
212
|
+
"""
|
|
213
|
+
commands = []
|
|
214
|
+
|
|
215
|
+
# Extract from code blocks
|
|
216
|
+
code_blocks = re.findall(r"`([^`]+)`", content)
|
|
217
|
+
commands.extend(code_blocks)
|
|
218
|
+
|
|
219
|
+
# Extract from bullets with code
|
|
220
|
+
bullets = re.findall(r"^\s*[-*]\s+.*?`([^`]+)`", content, re.MULTILINE)
|
|
221
|
+
commands.extend(bullets)
|
|
222
|
+
|
|
223
|
+
return commands
|
|
224
|
+
|
|
225
|
+
def _extract_bullets(self, content: str) -> list[str]:
|
|
226
|
+
"""Extract bullet points from content.
|
|
227
|
+
|
|
228
|
+
Parses markdown list items (- or *) from the content,
|
|
229
|
+
commonly used for guidelines and recommendations.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
content: Section content
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
List of bullet point strings
|
|
236
|
+
"""
|
|
237
|
+
bullets = []
|
|
238
|
+
|
|
239
|
+
for line in content.split("\n"):
|
|
240
|
+
line = line.strip()
|
|
241
|
+
if line.startswith("-") or line.startswith("*"):
|
|
242
|
+
bullets.append(line[1:].strip())
|
|
243
|
+
|
|
244
|
+
return bullets
|
|
245
|
+
|
|
246
|
+
def to_system_prompt(self) -> str:
|
|
247
|
+
"""Convert AGENTS.md to system prompt for agents.
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
Formatted system prompt string
|
|
251
|
+
"""
|
|
252
|
+
if not self.config:
|
|
253
|
+
return ""
|
|
254
|
+
|
|
255
|
+
prompt_parts = []
|
|
256
|
+
|
|
257
|
+
if self.config.project_overview:
|
|
258
|
+
prompt_parts.append(f"# Project Context\n\n{self.config.project_overview}")
|
|
259
|
+
|
|
260
|
+
if self.config.setup_commands:
|
|
261
|
+
prompt_parts.append("\n## Setup Commands\n")
|
|
262
|
+
for cmd in self.config.setup_commands:
|
|
263
|
+
prompt_parts.append(f"- `{cmd}`")
|
|
264
|
+
|
|
265
|
+
if self.config.code_style:
|
|
266
|
+
prompt_parts.append("\n## Code Style Rules\n")
|
|
267
|
+
for rule in self.config.code_style:
|
|
268
|
+
prompt_parts.append(f"- {rule}")
|
|
269
|
+
|
|
270
|
+
if self.config.build_commands:
|
|
271
|
+
prompt_parts.append("\n## Build Commands\n")
|
|
272
|
+
for cmd in self.config.build_commands:
|
|
273
|
+
prompt_parts.append(f"- `{cmd}`")
|
|
274
|
+
|
|
275
|
+
if self.config.testing_instructions:
|
|
276
|
+
prompt_parts.append("\n## Testing Instructions\n")
|
|
277
|
+
for instruction in self.config.testing_instructions:
|
|
278
|
+
prompt_parts.append(f"- {instruction}")
|
|
279
|
+
|
|
280
|
+
if self.config.pr_instructions:
|
|
281
|
+
prompt_parts.append("\n## PR Guidelines\n")
|
|
282
|
+
for instruction in self.config.pr_instructions:
|
|
283
|
+
prompt_parts.append(f"- {instruction}")
|
|
284
|
+
|
|
285
|
+
return "\n".join(prompt_parts)
|
|
286
|
+
|
|
287
|
+
def to_adk_capabilities(self) -> dict:
|
|
288
|
+
"""Convert AGENTS.md to ADK capabilities format.
|
|
289
|
+
|
|
290
|
+
Transforms the parsed AGENTS.md configuration into the capability
|
|
291
|
+
format expected by the Agent Development Kit (ADK) and A2A protocol.
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
ADK capabilities dictionary with project context and guidelines.
|
|
295
|
+
"""
|
|
296
|
+
if not self.config:
|
|
297
|
+
return {}
|
|
298
|
+
|
|
299
|
+
return {
|
|
300
|
+
"project_context": self.config.project_overview,
|
|
301
|
+
"setup": self.config.setup_commands,
|
|
302
|
+
"development": self.config.dev_environment_tips,
|
|
303
|
+
"code_style": self.config.code_style,
|
|
304
|
+
"build": self.config.build_commands,
|
|
305
|
+
"testing": self.config.testing_instructions,
|
|
306
|
+
"security": self.config.security_considerations,
|
|
307
|
+
"pr_guidelines": self.config.pr_instructions,
|
|
308
|
+
"custom": self.config.custom_sections,
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
def merge_with_skills(self, skills_prompt: str) -> str:
|
|
312
|
+
"""Merge AGENTS.md context with agent skills prompt.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
skills_prompt: Existing skills-based prompt
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
Combined prompt with both contexts
|
|
319
|
+
"""
|
|
320
|
+
agents_prompt = self.to_system_prompt()
|
|
321
|
+
|
|
322
|
+
if not agents_prompt:
|
|
323
|
+
return skills_prompt
|
|
324
|
+
|
|
325
|
+
return f"""{agents_prompt}
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
{skills_prompt}
|
|
330
|
+
"""
|