@neyugn/agent-kits 0.3.5 → 0.3.6
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.
|
@@ -20,7 +20,7 @@ AGT-Kit is a portable, modular AI agent system consisting of:
|
|
|
20
20
|
|
|
21
21
|
### Modular Skill Loading
|
|
22
22
|
|
|
23
|
-
Agent activated → Check
|
|
23
|
+
Agent activated → Check ARCHITECTURE.md for assigned skills → Use `skill` tool to load each → Apply.
|
|
24
24
|
|
|
25
25
|
- **Priority:** P0 (AGENTS.md) > P1 (Agent.md) > P2 (SKILL.md). All binding.
|
|
26
26
|
- **Enforcement:** Never skip reading. "Read → Understand → Apply" mandatory.
|
|
@@ -101,12 +101,12 @@ Agent activated → Check frontmatter `skills:` → Read SKILL.md → Apply.
|
|
|
101
101
|
|
|
102
102
|
### Routing Checklist
|
|
103
103
|
|
|
104
|
-
| Step | Check
|
|
105
|
-
| ---- |
|
|
106
|
-
| 1 | Correct agent identified?
|
|
107
|
-
| 2 | Read agent's .md file?
|
|
108
|
-
| 3 | Announced @agent?
|
|
109
|
-
| 4 | Loaded skills from
|
|
104
|
+
| Step | Check | If Unchecked |
|
|
105
|
+
| ---- | --------------------------------- | ------------------------------------------- |
|
|
106
|
+
| 1 | Correct agent identified? | → Analyze domain |
|
|
107
|
+
| 2 | Read agent's .md file? | → Open `.agent/agents/{agent}.md` |
|
|
108
|
+
| 3 | Announced @agent? | → Add announcement |
|
|
109
|
+
| 4 | Loaded skills from ARCHITECTURE? | → Check ARCHITECTURE.md agent-skills table |
|
|
110
110
|
|
|
111
111
|
❌ Code without agent = PROTOCOL VIOLATION
|
|
112
112
|
❌ Skip announcement = USER CANNOT VERIFY
|
|
@@ -129,7 +129,24 @@ Agent activated → Check frontmatter `skills:` → Read SKILL.md → Apply.
|
|
|
129
129
|
## 🛠️ SKILL LOADING PROTOCOL
|
|
130
130
|
|
|
131
131
|
```
|
|
132
|
-
|
|
132
|
+
Agent activated → Check ARCHITECTURE.md for assigned skills → Use `skill` tool → Apply SKILL.md content
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### How Skills Work in OpenCode
|
|
136
|
+
|
|
137
|
+
OpenCode has a **native `skill` tool** that automatically discovers all available skills.
|
|
138
|
+
|
|
139
|
+
1. **Discovery**: OpenCode scans `.agent/skills/*/SKILL.md` and lists them in the `skill` tool description
|
|
140
|
+
2. **Selection**: Check ARCHITECTURE.md → find your agent → note the "Skills Used" column
|
|
141
|
+
3. **Loading**: Call `skill({ name: "skill-name" })` to load each assigned skill
|
|
142
|
+
4. **Apply**: Follow the loaded SKILL.md instructions
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
# Example: frontend-specialist activated
|
|
146
|
+
1. Read ARCHITECTURE.md → Skills: clean-code, react-patterns, typescript-patterns, ...
|
|
147
|
+
2. Call skill({ name: "clean-code" }) → Read content
|
|
148
|
+
3. Call skill({ name: "react-patterns" }) → Read content
|
|
149
|
+
4. Apply all loaded skill rules to the task
|
|
133
150
|
```
|
|
134
151
|
|
|
135
152
|
### Profile-Aware Loading
|
|
@@ -148,6 +165,31 @@ User Request → Check Profile → Skill Description Match → Load SKILL.md →
|
|
|
148
165
|
- Behave as if no filtering is applied
|
|
149
166
|
```
|
|
150
167
|
|
|
168
|
+
### Skill Permissions (OpenCode)
|
|
169
|
+
|
|
170
|
+
Control skill access in `opencode.json`:
|
|
171
|
+
|
|
172
|
+
```json
|
|
173
|
+
{
|
|
174
|
+
"permission": {
|
|
175
|
+
"skill": {
|
|
176
|
+
"*": "allow",
|
|
177
|
+
"internal-*": "deny"
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Or per-agent in agent frontmatter:
|
|
184
|
+
|
|
185
|
+
```yaml
|
|
186
|
+
---
|
|
187
|
+
permission:
|
|
188
|
+
skill:
|
|
189
|
+
"documents-*": "allow"
|
|
190
|
+
---
|
|
191
|
+
```
|
|
192
|
+
|
|
151
193
|
### Core Skills (Always Available)
|
|
152
194
|
|
|
153
195
|
These skills are NEVER disabled regardless of profile:
|
|
@@ -90,6 +90,46 @@ def parse_frontmatter(file_path: Path) -> Dict[str, Any]:
|
|
|
90
90
|
return {}
|
|
91
91
|
|
|
92
92
|
|
|
93
|
+
def parse_skills_from_architecture(agent_dir: Path) -> Dict[str, List[str]]:
|
|
94
|
+
"""Parse agent-skill mappings from ARCHITECTURE.md tables.
|
|
95
|
+
|
|
96
|
+
Falls back to this when agents don't have skills in frontmatter
|
|
97
|
+
(e.g., OpenCode strips the skills field during transformation).
|
|
98
|
+
"""
|
|
99
|
+
arch_file = agent_dir / "ARCHITECTURE.md"
|
|
100
|
+
if not arch_file.exists():
|
|
101
|
+
return {}
|
|
102
|
+
|
|
103
|
+
mapping = {}
|
|
104
|
+
try:
|
|
105
|
+
content = arch_file.read_text()
|
|
106
|
+
# Match table rows: | `agent-name` | description | skills-list |
|
|
107
|
+
# Pattern: | `name` | ... | skill1, skill2, ... |
|
|
108
|
+
for line in content.split('\n'):
|
|
109
|
+
if not line.startswith('|'):
|
|
110
|
+
continue
|
|
111
|
+
cells = [c.strip() for c in line.split('|')]
|
|
112
|
+
# Filter out empty cells from leading/trailing pipes
|
|
113
|
+
cells = [c for c in cells if c]
|
|
114
|
+
if len(cells) >= 3:
|
|
115
|
+
# First cell might be agent name wrapped in backticks
|
|
116
|
+
agent_name = cells[0].strip('`').strip()
|
|
117
|
+
# Last cell contains skills
|
|
118
|
+
skills_str = cells[-1]
|
|
119
|
+
# Skip header/separator rows
|
|
120
|
+
if agent_name.startswith('-') or agent_name == 'Agent':
|
|
121
|
+
continue
|
|
122
|
+
# Parse comma-separated skills
|
|
123
|
+
skills = [s.strip().strip('`') for s in skills_str.split(',') if s.strip()]
|
|
124
|
+
# Only add if skills look valid (not header text)
|
|
125
|
+
if skills and not any(s in ['Skills Used', '---', 'Skills'] for s in skills):
|
|
126
|
+
mapping[agent_name] = skills
|
|
127
|
+
except Exception:
|
|
128
|
+
pass
|
|
129
|
+
|
|
130
|
+
return mapping
|
|
131
|
+
|
|
132
|
+
|
|
93
133
|
def get_agents(agent_dir: Path) -> List[Dict[str, Any]]:
|
|
94
134
|
"""Get list of agents with their metadata."""
|
|
95
135
|
agents = []
|
|
@@ -98,10 +138,12 @@ def get_agents(agent_dir: Path) -> List[Dict[str, Any]]:
|
|
|
98
138
|
if not agents_path.exists():
|
|
99
139
|
return agents
|
|
100
140
|
|
|
141
|
+
# Parse agent-skill mappings from ARCHITECTURE.md (single source of truth)
|
|
142
|
+
arch_skills = parse_skills_from_architecture(agent_dir)
|
|
143
|
+
|
|
101
144
|
for agent_file in agents_path.glob("*.md"):
|
|
102
145
|
frontmatter = parse_frontmatter(agent_file)
|
|
103
|
-
skills =
|
|
104
|
-
skills = [s.strip() for s in skills if s.strip()]
|
|
146
|
+
skills = arch_skills.get(agent_file.stem, [])
|
|
105
147
|
|
|
106
148
|
agents.append({
|
|
107
149
|
"name": agent_file.stem,
|
|
@@ -146,22 +188,33 @@ def get_skills(agent_dir: Path) -> List[Dict[str, Any]]:
|
|
|
146
188
|
|
|
147
189
|
|
|
148
190
|
def get_workflows(agent_dir: Path) -> List[Dict[str, Any]]:
|
|
149
|
-
"""Get list of workflows.
|
|
150
|
-
workflows = []
|
|
151
|
-
workflows_path = agent_dir / "workflows"
|
|
191
|
+
"""Get list of workflows/commands.
|
|
152
192
|
|
|
153
|
-
|
|
154
|
-
|
|
193
|
+
Checks both 'workflows/' (Cursor, Gemini) and 'commands/' (OpenCode)
|
|
194
|
+
directories since different AI tools use different naming.
|
|
195
|
+
"""
|
|
196
|
+
workflows = []
|
|
155
197
|
|
|
156
|
-
|
|
157
|
-
|
|
198
|
+
# Check both possible directories
|
|
199
|
+
for dir_name in ["workflows", "commands"]:
|
|
200
|
+
workflows_path = agent_dir / dir_name
|
|
158
201
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
202
|
+
if not workflows_path.exists():
|
|
203
|
+
continue
|
|
204
|
+
|
|
205
|
+
for workflow_file in workflows_path.glob("*.md"):
|
|
206
|
+
frontmatter = parse_frontmatter(workflow_file)
|
|
207
|
+
|
|
208
|
+
workflows.append({
|
|
209
|
+
"name": workflow_file.stem,
|
|
210
|
+
"command": f"/{workflow_file.stem}",
|
|
211
|
+
"file": str(workflow_file.relative_to(agent_dir)),
|
|
212
|
+
"description": frontmatter.get("description", ""),
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
# Only use the first directory found (don't double-count)
|
|
216
|
+
if workflows:
|
|
217
|
+
break
|
|
165
218
|
|
|
166
219
|
return sorted(workflows, key=lambda x: x["name"])
|
|
167
220
|
|