Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,12 @@ This is the most critical file. It is the "driver" for the LLM.
*Wait for approval/feedback before writing code.*
2. **Fork** the repository.
3. **Create** your skill folder: `skills/<category>/<your_skill>/`.
4. **Implement** the 4 required files (`manifest.yaml`, `skill.py`, `instructions.md`, `card.json`).
5. **Add** a test script in `examples/`.
6. **Verify**: Run linting and tests. See [TESTING.md](docs/TESTING.md) for details.
4. **Implement** the 5 required files (`manifest.yaml`, `skill.py`, `instructions.md`, `card.json`, `test_skill.py`).
5. **Verify**: Run linting and tests locally.
* `pytest skills/<category>/<your_skill>/test_skill.py`
* `python -m black .`
* `python -m flake8 .`
* `python -m pytest tests/`
7. **Submit** PR.
6. **Submit** PR.

---

Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ Skillware/
│ └── skill_name/ # The Skill bundle
│ ├── manifest.yaml # Definition, schema, and constitution
│ ├── skill.py # Executable Python logic
│ └── instructions.md # Cognitive map for the LLM
│ ├── instructions.md # Cognitive map for the LLM
│ └── test_skill.py # Unit tests & schema validation
├── skillware/ # Core Framework Package
│ └── core/
│ ├── base_skill.py # Abstract Base Class for skills
Expand Down
14 changes: 11 additions & 3 deletions docs/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,16 @@ Run the full test suite:
python -m pytest tests/
```

### Testing Individual Skills
Every skill now comes with a `test_skill.py` boilerplate. You can run tests for a specific skill without running the entire suite:

```bash
python -m pytest skills/<category>/<skill_name>/test_skill.py
```

### Writing Tests
- Place tests in the `tests/` directory.
- Mimic the structure of the `skills/` or `skillware/` directory you are testing.
- **Global Tests**: Place core framework tests in the `tests/` directory.
- **Skill Tests**: Place skill-specific logic tests in a `test_skill.py` file within the skill's own directory.
- Use `conftest.py` for shared fixtures (e.g., mocking LLM clients).

## Pre-Commit Checklist
Expand All @@ -70,4 +77,5 @@ Before pushing your code, run the following commands to ensure your changes are

1. `python -m black .` (Format code)
2. `python -m flake8 .` (Check quality)
3. `python -m pytest tests/` (Verify functionality)
3. `python -m pytest tests/` (Verify framework functionality)
4. `python -m pytest skills/` (Verify all skills pass their local tests)
14 changes: 14 additions & 0 deletions templates/python_skill/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Instructions: My Awesome Skill

You are an agent equipped with the **My Awesome Skill**.

### When to use this tool
- Use this tool when the user asks for [describe primary use case].
- Do not use this tool for [describe anti-patterns].

### How to interpret the output
- The tool returns a `result` field containing [describe result].
- If you see an error message, explain to the user that [describe error handling].

### Examples
- User: "Run my awesome skill with value X" -> Call `my-awesome-skill(param1="X")`
18 changes: 13 additions & 5 deletions templates/python_skill/manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,22 @@ name: "my-awesome-skill"
version: "0.1.0"
description: "A short description of what this skill does."
category: "utility"
inputs:
param1:
type: "string"
description: "Description of the first parameter"
parameters:
type: object
properties:
param1:
type: string
description: "Description of the first parameter"
required:
- param1
outputs:
result:
type: "string"
type: string
description: "Description of the output"
requirements: []
constitution: |
1. Always provide helpful and accurate results.
2. Do not include sensitive information in the output.
presentation:
icon: "star"
color: "#3498DB"
23 changes: 18 additions & 5 deletions templates/python_skill/skill.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,23 @@
class MyAwesomeSkill(BaseSkill):
@property
def manifest(self) -> Dict[str, Any]:
# You can load this from manifest.yaml or define it here
return {"name": "my-awesome-skill", "version": "0.1.0"}
"""
Returns the skill's manifest. In a production skill,
you can load this from manifest.yaml using SkillLoader.
"""
return {
"name": "my-awesome-skill",
"version": "0.1.0",
"description": "A short description of what this skill does.",
}

def execute(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""
The main execution logic for the skill.
Expects 'param1' in params as defined in manifest.yaml.
"""
param1 = params.get("param1", "default")

# Implement your logic here

def execute(self, params: Dict[str, Any]) -> Any:
# Your skill logic goes here
param1 = params.get("param1")
return {"result": f"Executed with {param1}"}
51 changes: 51 additions & 0 deletions templates/python_skill/test_skill.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import pytest
import yaml
import os
from .skill import MyAwesomeSkill


@pytest.fixture
def skill():
"""Fixture to initialize the skill class."""
return MyAwesomeSkill()


@pytest.fixture
def manifest():
"""Fixture to load the manifest.yaml for validation."""
manifest_path = os.path.join(os.path.dirname(__file__), "manifest.yaml")
with open(manifest_path, "r", encoding="utf-8") as f:
return yaml.safe_load(f)


def test_skill_manifest_consistency(skill, manifest):
"""Verify the skill's internal manifest matches the manifest.yaml file basics."""
skill_manifest = skill.manifest
assert skill_manifest["name"] == manifest["name"]
assert skill_manifest["version"] == manifest["version"]


def test_skill_execution(skill, manifest):
"""Test the skill execution and validate output schema."""
# 1. Prepare dummy input
params = {"param1": "test-value"}

# 2. Execute
result = skill.execute(params)

# 3. Validate result is a dictionary (JSON serializable)
assert isinstance(result, dict), "Execution result must be a dictionary"

# 4. Validate against 'outputs' defined in manifest.yaml
expected_outputs = manifest.get("outputs", {})
for key, spec in expected_outputs.items():
assert key in result, f"Missing expected output key: '{key}'"

# Optional: Basic type checking based on manifest
expected_type = spec.get("type")
if expected_type == "string":
assert isinstance(result[key], str), f"Output '{key}' should be a string"
elif expected_type == "integer":
assert isinstance(result[key], int), f"Output '{key}' should be an integer"
elif expected_type == "boolean":
assert isinstance(result[key], bool), f"Output '{key}' should be a boolean"
Loading