skillware 0.2.5__tar.gz → 0.2.6__tar.gz
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.
- {skillware-0.2.5 → skillware-0.2.6}/PKG-INFO +12 -7
- {skillware-0.2.5 → skillware-0.2.6}/README.md +11 -6
- {skillware-0.2.5 → skillware-0.2.6}/pyproject.toml +1 -1
- {skillware-0.2.5 → skillware-0.2.6}/skillware/core/loader.py +64 -0
- {skillware-0.2.5 → skillware-0.2.6}/skillware.egg-info/PKG-INFO +12 -7
- {skillware-0.2.5 → skillware-0.2.6}/skillware.egg-info/SOURCES.txt +2 -1
- skillware-0.2.6/tests/test_loader.py +122 -0
- skillware-0.2.6/tests/test_skill_issuer.py +89 -0
- skillware-0.2.5/tests/test_loader.py +0 -63
- {skillware-0.2.5 → skillware-0.2.6}/LICENSE +0 -0
- {skillware-0.2.5 → skillware-0.2.6}/setup.cfg +0 -0
- {skillware-0.2.5 → skillware-0.2.6}/skills/compliance/mica_module/__init__.py +0 -0
- {skillware-0.2.5 → skillware-0.2.6}/skills/compliance/mica_module/skill.py +0 -0
- {skillware-0.2.5 → skillware-0.2.6}/skills/compliance/pii_masker/__init__.py +0 -0
- {skillware-0.2.5 → skillware-0.2.6}/skills/compliance/pii_masker/skill.py +0 -0
- {skillware-0.2.5 → skillware-0.2.6}/skills/compliance/tos_evaluator/__init__.py +0 -0
- {skillware-0.2.5 → skillware-0.2.6}/skills/compliance/tos_evaluator/skill.py +0 -0
- {skillware-0.2.5 → skillware-0.2.6}/skills/compliance/tos_evaluator/test_skill.py +0 -0
- {skillware-0.2.5 → skillware-0.2.6}/skills/data_engineering/synthetic_generator/__init__.py +0 -0
- {skillware-0.2.5 → skillware-0.2.6}/skills/data_engineering/synthetic_generator/skill.py +0 -0
- {skillware-0.2.5 → skillware-0.2.6}/skills/finance/wallet_screening/__init__.py +0 -0
- {skillware-0.2.5 → skillware-0.2.6}/skills/finance/wallet_screening/maintenance/normalization_tool.py +0 -0
- {skillware-0.2.5 → skillware-0.2.6}/skills/finance/wallet_screening/maintenance/normalize_uniswap_trm.py +0 -0
- {skillware-0.2.5 → skillware-0.2.6}/skills/finance/wallet_screening/skill.py +0 -0
- {skillware-0.2.5 → skillware-0.2.6}/skills/office/pdf_form_filler/skill.py +0 -0
- {skillware-0.2.5 → skillware-0.2.6}/skills/office/pdf_form_filler/utils.py +0 -0
- {skillware-0.2.5 → skillware-0.2.6}/skills/optimization/prompt_rewriter/__init__.py +0 -0
- {skillware-0.2.5 → skillware-0.2.6}/skills/optimization/prompt_rewriter/skill.py +0 -0
- {skillware-0.2.5 → skillware-0.2.6}/skillware/__init__.py +0 -0
- {skillware-0.2.5 → skillware-0.2.6}/skillware/core/__init__.py +0 -0
- {skillware-0.2.5 → skillware-0.2.6}/skillware/core/base_skill.py +0 -0
- {skillware-0.2.5 → skillware-0.2.6}/skillware/core/env.py +0 -0
- {skillware-0.2.5 → skillware-0.2.6}/skillware.egg-info/dependency_links.txt +0 -0
- {skillware-0.2.5 → skillware-0.2.6}/skillware.egg-info/requires.txt +0 -0
- {skillware-0.2.5 → skillware-0.2.6}/skillware.egg-info/top_level.txt +0 -0
- {skillware-0.2.5 → skillware-0.2.6}/tests/test_mica_module.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: skillware
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Summary: A framework for modular, self-contained AI skills.
|
|
5
5
|
Author-email: ARPA Hellenic Logic Systems <skillware-os@arpacorp.net>
|
|
6
6
|
License: MIT License
|
|
@@ -175,20 +175,25 @@ print(response.text)
|
|
|
175
175
|
|
|
176
176
|
* **[Core Logic & Philosophy](docs/introduction.md)**: Details on how Skillware decouples Logic, Cognition, and Governance.
|
|
177
177
|
* **[Testing Guidelines](docs/TESTING.md)**: Instructions for validating skills and checking local coverage.
|
|
178
|
+
* **[Usage guides](docs/usage/README.md)**: Provider adapters and navigation to integration docs.
|
|
179
|
+
* **[Agent loops](docs/usage/agent_loops.md)**: Shared load, tool-call, and `execute` pattern across providers.
|
|
178
180
|
* **[Usage Guide: Gemini](docs/usage/gemini.md)**: Integration with Google's GenAI SDK.
|
|
179
181
|
* **[Usage Guide: Claude](docs/usage/claude.md)**: Integration with Anthropic's SDK.
|
|
182
|
+
* **[Usage Guide: OpenAI](docs/usage/openai.md)**: Integration with OpenAI Chat Completions tool calling.
|
|
183
|
+
* **[Usage Guide: DeepSeek](docs/usage/deepseek.md)**: Integration with the DeepSeek API via `to_deepseek_tool()`.
|
|
180
184
|
* **[Usage Guide: Ollama](docs/usage/ollama.md)**: Native integration for local models via Ollama.
|
|
185
|
+
* **[API Keys for Skills](docs/usage/api_keys.md)**: Environment variables, cloud/CI setup, and security for skills that call external APIs.
|
|
181
186
|
* **[Skill Library](docs/skills/README.md)**: Available capabilities.
|
|
182
187
|
|
|
183
188
|
## Contributing
|
|
184
189
|
|
|
185
|
-
We are building the "App Store" for Agents and require professional, robust, and safe skills.
|
|
190
|
+
We are building the "App Store" for Agents and require professional, robust, and safe skills. We welcome contributions to the skill registry, documentation, tests, and core framework.
|
|
186
191
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
+
* **[CONTRIBUTING.md](CONTRIBUTING.md)** — Contribution types, skill standard, pull request process, and navigation to all contributor docs.
|
|
193
|
+
* **[Agent Contribution Workflow](docs/contributing/ai_native_workflow.md)** — Workflow for AI agents contributing to the repository (operators supervise).
|
|
194
|
+
* **[Agent Code of Conduct](CODE_OF_CONDUCT.md)** — Deterministic outputs, safety boundaries, and acceptable use of skills.
|
|
195
|
+
* **[TESTING.md](docs/TESTING.md)** — Local linting and pytest before you open a PR.
|
|
196
|
+
* **[Pull request template](.github/PULL_REQUEST_TEMPLATE.md)** — Checklists for skills, docs, and framework changes (complete only the sections that apply).
|
|
192
197
|
|
|
193
198
|
## Comparison
|
|
194
199
|
|
|
@@ -127,20 +127,25 @@ print(response.text)
|
|
|
127
127
|
|
|
128
128
|
* **[Core Logic & Philosophy](docs/introduction.md)**: Details on how Skillware decouples Logic, Cognition, and Governance.
|
|
129
129
|
* **[Testing Guidelines](docs/TESTING.md)**: Instructions for validating skills and checking local coverage.
|
|
130
|
+
* **[Usage guides](docs/usage/README.md)**: Provider adapters and navigation to integration docs.
|
|
131
|
+
* **[Agent loops](docs/usage/agent_loops.md)**: Shared load, tool-call, and `execute` pattern across providers.
|
|
130
132
|
* **[Usage Guide: Gemini](docs/usage/gemini.md)**: Integration with Google's GenAI SDK.
|
|
131
133
|
* **[Usage Guide: Claude](docs/usage/claude.md)**: Integration with Anthropic's SDK.
|
|
134
|
+
* **[Usage Guide: OpenAI](docs/usage/openai.md)**: Integration with OpenAI Chat Completions tool calling.
|
|
135
|
+
* **[Usage Guide: DeepSeek](docs/usage/deepseek.md)**: Integration with the DeepSeek API via `to_deepseek_tool()`.
|
|
132
136
|
* **[Usage Guide: Ollama](docs/usage/ollama.md)**: Native integration for local models via Ollama.
|
|
137
|
+
* **[API Keys for Skills](docs/usage/api_keys.md)**: Environment variables, cloud/CI setup, and security for skills that call external APIs.
|
|
133
138
|
* **[Skill Library](docs/skills/README.md)**: Available capabilities.
|
|
134
139
|
|
|
135
140
|
## Contributing
|
|
136
141
|
|
|
137
|
-
We are building the "App Store" for Agents and require professional, robust, and safe skills.
|
|
142
|
+
We are building the "App Store" for Agents and require professional, robust, and safe skills. We welcome contributions to the skill registry, documentation, tests, and core framework.
|
|
138
143
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
+
* **[CONTRIBUTING.md](CONTRIBUTING.md)** — Contribution types, skill standard, pull request process, and navigation to all contributor docs.
|
|
145
|
+
* **[Agent Contribution Workflow](docs/contributing/ai_native_workflow.md)** — Workflow for AI agents contributing to the repository (operators supervise).
|
|
146
|
+
* **[Agent Code of Conduct](CODE_OF_CONDUCT.md)** — Deterministic outputs, safety boundaries, and acceptable use of skills.
|
|
147
|
+
* **[TESTING.md](docs/TESTING.md)** — Local linting and pytest before you open a PR.
|
|
148
|
+
* **[Pull request template](.github/PULL_REQUEST_TEMPLATE.md)** — Checklists for skills, docs, and framework changes (complete only the sections that apply).
|
|
144
149
|
|
|
145
150
|
## Comparison
|
|
146
151
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import re
|
|
2
3
|
import yaml
|
|
3
4
|
import json
|
|
4
5
|
import importlib.util
|
|
@@ -126,6 +127,69 @@ class SkillLoader:
|
|
|
126
127
|
|
|
127
128
|
return {"name": name, "description": description, "input_schema": parameters}
|
|
128
129
|
|
|
130
|
+
@staticmethod
|
|
131
|
+
def _sanitize_function_tool_name(name: str) -> str:
|
|
132
|
+
"""
|
|
133
|
+
Normalizes manifest tool IDs for OpenAI-compatible function-calling APIs.
|
|
134
|
+
Allows letters, digits, underscores, and hyphens (max 64 characters).
|
|
135
|
+
"""
|
|
136
|
+
if not name or not str(name).strip():
|
|
137
|
+
return "unknown_tool"
|
|
138
|
+
safe = re.sub(r"[^a-zA-Z0-9_-]", "_", str(name).replace("/", "_"))
|
|
139
|
+
safe = re.sub(r"_+", "_", safe).strip("_")
|
|
140
|
+
if not safe:
|
|
141
|
+
return "unknown_tool"
|
|
142
|
+
return safe[:64]
|
|
143
|
+
|
|
144
|
+
@staticmethod
|
|
145
|
+
def _sanitize_openai_tool_name(name: str) -> str:
|
|
146
|
+
return SkillLoader._sanitize_function_tool_name(name)
|
|
147
|
+
|
|
148
|
+
@staticmethod
|
|
149
|
+
def _sanitize_deepseek_tool_name(name: str) -> str:
|
|
150
|
+
return SkillLoader._sanitize_function_tool_name(name)
|
|
151
|
+
|
|
152
|
+
@staticmethod
|
|
153
|
+
def to_openai_tool(skill_bundle: Dict[str, Any]) -> Dict[str, Any]:
|
|
154
|
+
"""
|
|
155
|
+
Converts a skill manifest to an OpenAI Chat Completions tool definition.
|
|
156
|
+
See: https://platform.openai.com/docs/guides/function-calling
|
|
157
|
+
"""
|
|
158
|
+
manifest = skill_bundle.get("manifest", {})
|
|
159
|
+
raw_name = manifest.get("name", "unknown_tool")
|
|
160
|
+
description = manifest.get("description", "")
|
|
161
|
+
parameters = manifest.get("parameters", {})
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
"type": "function",
|
|
165
|
+
"function": {
|
|
166
|
+
"name": SkillLoader._sanitize_openai_tool_name(raw_name),
|
|
167
|
+
"description": description,
|
|
168
|
+
"parameters": parameters,
|
|
169
|
+
},
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
@staticmethod
|
|
173
|
+
def to_deepseek_tool(skill_bundle: Dict[str, Any]) -> Dict[str, Any]:
|
|
174
|
+
"""
|
|
175
|
+
Converts a skill manifest to a DeepSeek API tool definition.
|
|
176
|
+
DeepSeek uses an OpenAI-compatible tools schema; this adapter is separate from
|
|
177
|
+
to_openai_tool() by design. See: https://api-docs.deepseek.com/
|
|
178
|
+
"""
|
|
179
|
+
manifest = skill_bundle.get("manifest", {})
|
|
180
|
+
raw_name = manifest.get("name", "unknown_tool")
|
|
181
|
+
description = manifest.get("description", "")
|
|
182
|
+
parameters = manifest.get("parameters", {})
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
"type": "function",
|
|
186
|
+
"function": {
|
|
187
|
+
"name": SkillLoader._sanitize_deepseek_tool_name(raw_name),
|
|
188
|
+
"description": description,
|
|
189
|
+
"parameters": parameters,
|
|
190
|
+
},
|
|
191
|
+
}
|
|
192
|
+
|
|
129
193
|
@staticmethod
|
|
130
194
|
def to_ollama_prompt(skill_bundle: Dict[str, Any]) -> str:
|
|
131
195
|
"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: skillware
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Summary: A framework for modular, self-contained AI skills.
|
|
5
5
|
Author-email: ARPA Hellenic Logic Systems <skillware-os@arpacorp.net>
|
|
6
6
|
License: MIT License
|
|
@@ -175,20 +175,25 @@ print(response.text)
|
|
|
175
175
|
|
|
176
176
|
* **[Core Logic & Philosophy](docs/introduction.md)**: Details on how Skillware decouples Logic, Cognition, and Governance.
|
|
177
177
|
* **[Testing Guidelines](docs/TESTING.md)**: Instructions for validating skills and checking local coverage.
|
|
178
|
+
* **[Usage guides](docs/usage/README.md)**: Provider adapters and navigation to integration docs.
|
|
179
|
+
* **[Agent loops](docs/usage/agent_loops.md)**: Shared load, tool-call, and `execute` pattern across providers.
|
|
178
180
|
* **[Usage Guide: Gemini](docs/usage/gemini.md)**: Integration with Google's GenAI SDK.
|
|
179
181
|
* **[Usage Guide: Claude](docs/usage/claude.md)**: Integration with Anthropic's SDK.
|
|
182
|
+
* **[Usage Guide: OpenAI](docs/usage/openai.md)**: Integration with OpenAI Chat Completions tool calling.
|
|
183
|
+
* **[Usage Guide: DeepSeek](docs/usage/deepseek.md)**: Integration with the DeepSeek API via `to_deepseek_tool()`.
|
|
180
184
|
* **[Usage Guide: Ollama](docs/usage/ollama.md)**: Native integration for local models via Ollama.
|
|
185
|
+
* **[API Keys for Skills](docs/usage/api_keys.md)**: Environment variables, cloud/CI setup, and security for skills that call external APIs.
|
|
181
186
|
* **[Skill Library](docs/skills/README.md)**: Available capabilities.
|
|
182
187
|
|
|
183
188
|
## Contributing
|
|
184
189
|
|
|
185
|
-
We are building the "App Store" for Agents and require professional, robust, and safe skills.
|
|
190
|
+
We are building the "App Store" for Agents and require professional, robust, and safe skills. We welcome contributions to the skill registry, documentation, tests, and core framework.
|
|
186
191
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
+
* **[CONTRIBUTING.md](CONTRIBUTING.md)** — Contribution types, skill standard, pull request process, and navigation to all contributor docs.
|
|
193
|
+
* **[Agent Contribution Workflow](docs/contributing/ai_native_workflow.md)** — Workflow for AI agents contributing to the repository (operators supervise).
|
|
194
|
+
* **[Agent Code of Conduct](CODE_OF_CONDUCT.md)** — Deterministic outputs, safety boundaries, and acceptable use of skills.
|
|
195
|
+
* **[TESTING.md](docs/TESTING.md)** — Local linting and pytest before you open a PR.
|
|
196
|
+
* **[Pull request template](.github/PULL_REQUEST_TEMPLATE.md)** — Checklists for skills, docs, and framework changes (complete only the sections that apply).
|
|
192
197
|
|
|
193
198
|
## Comparison
|
|
194
199
|
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from skillware.core.loader import SkillLoader
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def test_load_skill_not_found():
|
|
6
|
+
with pytest.raises(FileNotFoundError):
|
|
7
|
+
SkillLoader.load_skill("nonexistent_skill_path_12345")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_to_ollama_prompt():
|
|
11
|
+
dummy_bundle = {
|
|
12
|
+
"manifest": {
|
|
13
|
+
"name": "test_ollama_skill",
|
|
14
|
+
"description": "A very useful test skill.",
|
|
15
|
+
"parameters": {
|
|
16
|
+
"type": "object",
|
|
17
|
+
"properties": {
|
|
18
|
+
"arg1": {"type": "string", "description": "The first arg"}
|
|
19
|
+
},
|
|
20
|
+
"required": ["arg1"]
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
prompt = SkillLoader.to_ollama_prompt(dummy_bundle)
|
|
26
|
+
assert "### Tool: `test_ollama_skill`" in prompt
|
|
27
|
+
assert "**Description:** A very useful test skill." in prompt
|
|
28
|
+
assert "- `arg1` (string): The first arg [Required]" in prompt
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_to_gemini_tool():
|
|
32
|
+
dummy_bundle = {
|
|
33
|
+
"manifest": {
|
|
34
|
+
"name": "test_gemini_skill",
|
|
35
|
+
"parameters": {
|
|
36
|
+
"type": "object",
|
|
37
|
+
"properties": {
|
|
38
|
+
"param1": {"type": "string"}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
tool = SkillLoader.to_gemini_tool(dummy_bundle)
|
|
44
|
+
assert tool["name"] == "test_gemini_skill"
|
|
45
|
+
# Gemini requires UPPERCASE types for Protobufs
|
|
46
|
+
assert tool["parameters"]["type"] == "OBJECT"
|
|
47
|
+
assert tool["parameters"]["properties"]["param1"]["type"] == "STRING"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_to_claude_tool():
|
|
51
|
+
dummy_bundle = {
|
|
52
|
+
"manifest": {
|
|
53
|
+
"name": "test_claude_skill",
|
|
54
|
+
"description": "desc",
|
|
55
|
+
"parameters": {
|
|
56
|
+
"type": "object",
|
|
57
|
+
"properties": {"arg_claude": {"type": "string"}}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
tool = SkillLoader.to_claude_tool(dummy_bundle)
|
|
62
|
+
assert tool["name"] == "test_claude_skill"
|
|
63
|
+
assert tool["input_schema"]["type"] == "object"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_sanitize_openai_tool_name():
|
|
67
|
+
assert (
|
|
68
|
+
SkillLoader._sanitize_openai_tool_name("compliance/tos_evaluator")
|
|
69
|
+
== "compliance_tos_evaluator"
|
|
70
|
+
)
|
|
71
|
+
assert SkillLoader._sanitize_openai_tool_name("wallet_screening") == "wallet_screening"
|
|
72
|
+
assert SkillLoader._sanitize_openai_tool_name("") == "unknown_tool"
|
|
73
|
+
assert SkillLoader._sanitize_openai_tool_name("a" * 80).startswith("a")
|
|
74
|
+
assert len(SkillLoader._sanitize_openai_tool_name("a" * 80)) == 64
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_to_openai_tool():
|
|
78
|
+
dummy_bundle = {
|
|
79
|
+
"manifest": {
|
|
80
|
+
"name": "compliance/tos_evaluator",
|
|
81
|
+
"description": "Evaluate site policy.",
|
|
82
|
+
"parameters": {
|
|
83
|
+
"type": "object",
|
|
84
|
+
"properties": {
|
|
85
|
+
"target_url": {"type": "string", "description": "URL"}
|
|
86
|
+
},
|
|
87
|
+
"required": ["target_url"],
|
|
88
|
+
},
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
tool = SkillLoader.to_openai_tool(dummy_bundle)
|
|
92
|
+
assert tool["type"] == "function"
|
|
93
|
+
assert tool["function"]["name"] == "compliance_tos_evaluator"
|
|
94
|
+
assert tool["function"]["description"] == "Evaluate site policy."
|
|
95
|
+
assert tool["function"]["parameters"]["type"] == "object"
|
|
96
|
+
assert "target_url" in tool["function"]["parameters"]["properties"]
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_sanitize_deepseek_tool_name():
|
|
100
|
+
assert (
|
|
101
|
+
SkillLoader._sanitize_deepseek_tool_name("compliance/tos_evaluator")
|
|
102
|
+
== "compliance_tos_evaluator"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def test_to_deepseek_tool():
|
|
107
|
+
dummy_bundle = {
|
|
108
|
+
"manifest": {
|
|
109
|
+
"name": "compliance/tos_evaluator",
|
|
110
|
+
"description": "Evaluate site policy.",
|
|
111
|
+
"parameters": {
|
|
112
|
+
"type": "object",
|
|
113
|
+
"properties": {
|
|
114
|
+
"target_url": {"type": "string", "description": "URL"}
|
|
115
|
+
},
|
|
116
|
+
"required": ["target_url"],
|
|
117
|
+
},
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
tool = SkillLoader.to_deepseek_tool(dummy_bundle)
|
|
121
|
+
assert tool["type"] == "function"
|
|
122
|
+
assert tool["function"]["name"] == "compliance_tos_evaluator"
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Registry skills must declare issuer attribution (name + email required)."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import yaml
|
|
7
|
+
|
|
8
|
+
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
9
|
+
SKILLS_ROOT = REPO_ROOT / "skills"
|
|
10
|
+
|
|
11
|
+
PLACEHOLDER_NAMES = frozenset({"your name"})
|
|
12
|
+
PLACEHOLDER_EMAILS = frozenset({"you@example.com"})
|
|
13
|
+
PLACEHOLDER_GITHUB = frozenset({"your_github_username"})
|
|
14
|
+
PLACEHOLDER_ORGS = frozenset({"your org"})
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _discover_skill_dirs():
|
|
18
|
+
if not SKILLS_ROOT.is_dir():
|
|
19
|
+
return []
|
|
20
|
+
return sorted(p.parent for p in SKILLS_ROOT.rglob("manifest.yaml"))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _load_manifest(skill_dir: Path) -> dict:
|
|
24
|
+
with open(skill_dir / "manifest.yaml", encoding="utf-8") as f:
|
|
25
|
+
data = yaml.safe_load(f)
|
|
26
|
+
return data if isinstance(data, dict) else {}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _assert_real_issuer(issuer: dict, context: str) -> None:
|
|
30
|
+
assert isinstance(issuer, dict), f"{context}: issuer must be a mapping"
|
|
31
|
+
|
|
32
|
+
name = (issuer.get("name") or "").strip()
|
|
33
|
+
email = (issuer.get("email") or "").strip()
|
|
34
|
+
assert name, f"{context}: issuer.name is required"
|
|
35
|
+
assert email, f"{context}: issuer.email is required"
|
|
36
|
+
|
|
37
|
+
assert name.lower() not in PLACEHOLDER_NAMES, (
|
|
38
|
+
f"{context}: issuer.name must not be a template placeholder"
|
|
39
|
+
)
|
|
40
|
+
assert email.lower() not in PLACEHOLDER_EMAILS, (
|
|
41
|
+
f"{context}: issuer.email must not be a template placeholder"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
github = (issuer.get("github") or "").strip()
|
|
45
|
+
if github:
|
|
46
|
+
assert github.lower() not in PLACEHOLDER_GITHUB, (
|
|
47
|
+
f"{context}: issuer.github must not be a template placeholder"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
org = (issuer.get("org") or "").strip()
|
|
51
|
+
if org:
|
|
52
|
+
assert org.lower() not in PLACEHOLDER_ORGS, (
|
|
53
|
+
f"{context}: issuer.org must not be a template placeholder"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_registry_skills_declare_issuer():
|
|
58
|
+
skill_dirs = _discover_skill_dirs()
|
|
59
|
+
assert skill_dirs, "expected at least one skill under skills/"
|
|
60
|
+
|
|
61
|
+
for skill_dir in skill_dirs:
|
|
62
|
+
rel = skill_dir.relative_to(REPO_ROOT).as_posix()
|
|
63
|
+
manifest = _load_manifest(skill_dir)
|
|
64
|
+
_assert_real_issuer(manifest.get("issuer"), f"{rel} manifest.yaml")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_registry_card_issuer_matches_manifest_when_present():
|
|
68
|
+
for skill_dir in _discover_skill_dirs():
|
|
69
|
+
rel = skill_dir.relative_to(REPO_ROOT).as_posix()
|
|
70
|
+
manifest_issuer = _load_manifest(skill_dir).get("issuer")
|
|
71
|
+
_assert_real_issuer(manifest_issuer, f"{rel} manifest.yaml")
|
|
72
|
+
|
|
73
|
+
card_path = skill_dir / "card.json"
|
|
74
|
+
if not card_path.is_file():
|
|
75
|
+
continue
|
|
76
|
+
|
|
77
|
+
with open(card_path, encoding="utf-8") as f:
|
|
78
|
+
card = json.load(f)
|
|
79
|
+
|
|
80
|
+
card_issuer = card.get("issuer")
|
|
81
|
+
assert card_issuer is not None, f"{rel} card.json should include issuer"
|
|
82
|
+
_assert_real_issuer(card_issuer, f"{rel} card.json")
|
|
83
|
+
|
|
84
|
+
assert (card_issuer.get("name") or "").strip() == (
|
|
85
|
+
manifest_issuer.get("name") or ""
|
|
86
|
+
).strip(), f"{rel}: card issuer.name should match manifest"
|
|
87
|
+
assert (card_issuer.get("email") or "").strip() == (
|
|
88
|
+
manifest_issuer.get("email") or ""
|
|
89
|
+
).strip(), f"{rel}: card issuer.email should match manifest"
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import pytest
|
|
2
|
-
from skillware.core.loader import SkillLoader
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
def test_load_skill_not_found():
|
|
6
|
-
with pytest.raises(FileNotFoundError):
|
|
7
|
-
SkillLoader.load_skill("nonexistent_skill_path_12345")
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def test_to_ollama_prompt():
|
|
11
|
-
dummy_bundle = {
|
|
12
|
-
"manifest": {
|
|
13
|
-
"name": "test_ollama_skill",
|
|
14
|
-
"description": "A very useful test skill.",
|
|
15
|
-
"parameters": {
|
|
16
|
-
"type": "object",
|
|
17
|
-
"properties": {
|
|
18
|
-
"arg1": {"type": "string", "description": "The first arg"}
|
|
19
|
-
},
|
|
20
|
-
"required": ["arg1"]
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
prompt = SkillLoader.to_ollama_prompt(dummy_bundle)
|
|
26
|
-
assert "### Tool: `test_ollama_skill`" in prompt
|
|
27
|
-
assert "**Description:** A very useful test skill." in prompt
|
|
28
|
-
assert "- `arg1` (string): The first arg [Required]" in prompt
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def test_to_gemini_tool():
|
|
32
|
-
dummy_bundle = {
|
|
33
|
-
"manifest": {
|
|
34
|
-
"name": "test_gemini_skill",
|
|
35
|
-
"parameters": {
|
|
36
|
-
"type": "object",
|
|
37
|
-
"properties": {
|
|
38
|
-
"param1": {"type": "string"}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
tool = SkillLoader.to_gemini_tool(dummy_bundle)
|
|
44
|
-
assert tool["name"] == "test_gemini_skill"
|
|
45
|
-
# Gemini requires UPPERCASE types for Protobufs
|
|
46
|
-
assert tool["parameters"]["type"] == "OBJECT"
|
|
47
|
-
assert tool["parameters"]["properties"]["param1"]["type"] == "STRING"
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def test_to_claude_tool():
|
|
51
|
-
dummy_bundle = {
|
|
52
|
-
"manifest": {
|
|
53
|
-
"name": "test_claude_skill",
|
|
54
|
-
"description": "desc",
|
|
55
|
-
"parameters": {
|
|
56
|
-
"type": "object",
|
|
57
|
-
"properties": {"arg_claude": {"type": "string"}}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
tool = SkillLoader.to_claude_tool(dummy_bundle)
|
|
62
|
-
assert tool["name"] == "test_claude_skill"
|
|
63
|
-
assert tool["input_schema"]["type"] == "object"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|