skillware 0.3.3__tar.gz → 0.3.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.3.3/skillware.egg-info → skillware-0.3.6}/PKG-INFO +23 -8
- {skillware-0.3.3 → skillware-0.3.6}/README.md +13 -6
- {skillware-0.3.3 → skillware-0.3.6}/pyproject.toml +21 -4
- skillware-0.3.6/skills/compliance/mica_module/test_skill.py +67 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/compliance/pii_masker/skill.py +16 -15
- skillware-0.3.6/skills/compliance/pii_masker/test_skill.py +58 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/compliance/tos_evaluator/skill.py +34 -13
- {skillware-0.3.3 → skillware-0.3.6}/skills/data_engineering/novelty_extractor/test_skill.py +39 -10
- {skillware-0.3.3 → skillware-0.3.6}/skills/data_engineering/synthetic_generator/skill.py +17 -30
- skillware-0.3.6/skills/data_engineering/synthetic_generator/test_skill.py +58 -0
- skillware-0.3.6/skills/defi/evm_tx_handler/abis.py +136 -0
- skillware-0.3.6/skills/defi/evm_tx_handler/card.json +20 -0
- skillware-0.3.6/skills/defi/evm_tx_handler/config.yaml.example +11 -0
- skillware-0.3.6/skills/defi/evm_tx_handler/data/addressbook.yaml +4 -0
- skillware-0.3.6/skills/defi/evm_tx_handler/data/chains.yaml +18 -0
- skillware-0.3.6/skills/defi/evm_tx_handler/data/tokens.yaml +25 -0
- skillware-0.3.6/skills/defi/evm_tx_handler/instructions.md +81 -0
- skillware-0.3.6/skills/defi/evm_tx_handler/manifest.yaml +63 -0
- skillware-0.3.6/skills/defi/evm_tx_handler/skill.py +1114 -0
- skillware-0.3.6/skills/defi/evm_tx_handler/test_skill.py +542 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/dev_tools/issue_resolver/workflow.py +16 -7
- {skillware-0.3.3 → skillware-0.3.6}/skills/finance/wallet_screening/manifest.yaml +1 -1
- {skillware-0.3.3 → skillware-0.3.6}/skills/finance/wallet_screening/skill.py +5 -0
- skillware-0.3.6/skills/finance/wallet_screening/test_skill.py +84 -0
- skillware-0.3.6/skills/office/pdf_form_filler/test_skill.py +84 -0
- skillware-0.3.6/skills/optimization/prompt_rewriter/__init__.py +3 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/optimization/prompt_rewriter/skill.py +26 -9
- skillware-0.3.6/skills/optimization/prompt_rewriter/test_skill.py +55 -0
- skillware-0.3.6/skillware/__init__.py +0 -0
- skillware-0.3.6/skillware/__main__.py +4 -0
- {skillware-0.3.3 → skillware-0.3.6}/skillware/cli.py +77 -28
- skillware-0.3.6/skillware/core/__init__.py +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skillware/core/loader.py +5 -1
- {skillware-0.3.3 → skillware-0.3.6}/skillware/version_policy.py +1 -1
- {skillware-0.3.3 → skillware-0.3.6/skillware.egg-info}/PKG-INFO +23 -8
- {skillware-0.3.3 → skillware-0.3.6}/skillware.egg-info/SOURCES.txt +19 -0
- {skillware-0.3.3 → skillware-0.3.6}/skillware.egg-info/requires.txt +11 -1
- {skillware-0.3.3 → skillware-0.3.6}/tests/test_cli.py +60 -0
- {skillware-0.3.3 → skillware-0.3.6}/tests/test_loader.py +27 -18
- {skillware-0.3.3 → skillware-0.3.6}/tests/test_skill_issuer.py +18 -14
- skillware-0.3.3/skills/optimization/prompt_rewriter/__init__.py +0 -3
- {skillware-0.3.3 → skillware-0.3.6}/LICENSE +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/MANIFEST.in +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/setup.cfg +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/__init__.py +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/compliance/__init__.py +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/compliance/mica_module/__init__.py +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/compliance/mica_module/card.json +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/compliance/mica_module/instructions.md +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/compliance/mica_module/manifest.yaml +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/compliance/mica_module/mica_corpus.json +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/compliance/mica_module/skill.py +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/compliance/pii_masker/__init__.py +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/compliance/pii_masker/card.json +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/compliance/pii_masker/instructions.md +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/compliance/pii_masker/manifest.yaml +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/compliance/tos_evaluator/__init__.py +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/compliance/tos_evaluator/card.json +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/compliance/tos_evaluator/instructions.md +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/compliance/tos_evaluator/manifest.yaml +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/compliance/tos_evaluator/test_skill.py +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/data_engineering/__init__.py +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/data_engineering/novelty_extractor/__init__.py +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/data_engineering/novelty_extractor/card.json +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/data_engineering/novelty_extractor/instructions.md +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/data_engineering/novelty_extractor/manifest.yaml +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/data_engineering/novelty_extractor/skill.py +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/data_engineering/synthetic_generator/__init__.py +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/data_engineering/synthetic_generator/card.json +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/data_engineering/synthetic_generator/instructions.md +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/data_engineering/synthetic_generator/manifest.yaml +0 -0
- {skillware-0.3.3/skills/finance → skillware-0.3.6/skills/defi}/__init__.py +0 -0
- {skillware-0.3.3/skills/office → skillware-0.3.6/skills/defi/evm_tx_handler}/__init__.py +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/dev_tools/__init__.py +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/dev_tools/issue_resolver/__init__.py +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/dev_tools/issue_resolver/card.json +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/dev_tools/issue_resolver/instructions.md +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/dev_tools/issue_resolver/manifest.yaml +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/dev_tools/issue_resolver/skill.py +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/dev_tools/issue_resolver/test_skill.py +0 -0
- {skillware-0.3.3/skills/office/pdf_form_filler → skillware-0.3.6/skills/finance}/__init__.py +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/finance/wallet_screening/__init__.py +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/finance/wallet_screening/card.json +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/finance/wallet_screening/data/entities.ftm.json +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/finance/wallet_screening/data/malicious_scs_2025.json +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/finance/wallet_screening/data/normalized_fbi_lazarus_20250722_180019.json +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/finance/wallet_screening/data/normalized_israel_nbctf_20250722_180019.json +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/finance/wallet_screening/data/normalized_uniswap_trm.json +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/finance/wallet_screening/instructions.md +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/finance/wallet_screening/maintenance/normalization_tool.py +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/finance/wallet_screening/maintenance/normalize_uniswap_trm.py +0 -0
- {skillware-0.3.3/skills/optimization → skillware-0.3.6/skills/office}/__init__.py +0 -0
- {skillware-0.3.3/skillware → skillware-0.3.6/skills/office/pdf_form_filler}/__init__.py +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/office/pdf_form_filler/card.json +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/office/pdf_form_filler/instructions.md +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/office/pdf_form_filler/manifest.yaml +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/office/pdf_form_filler/skill.py +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/office/pdf_form_filler/utils.py +0 -0
- {skillware-0.3.3/skillware/core → skillware-0.3.6/skills/optimization}/__init__.py +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/optimization/prompt_rewriter/card.json +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/optimization/prompt_rewriter/instructions.md +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skills/optimization/prompt_rewriter/manifest.yaml +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skillware/core/base_skill.py +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skillware/core/env.py +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skillware.egg-info/dependency_links.txt +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skillware.egg-info/entry_points.txt +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/skillware.egg-info/top_level.txt +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/tests/test_mica_module.py +0 -0
- {skillware-0.3.3 → skillware-0.3.6}/tests/test_version_policy.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: skillware
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.6
|
|
4
4
|
Summary: A framework for modular, self-contained AI skills.
|
|
5
5
|
Author-email: ARPA Hellenic Logical Systems <skillware-os@arpacorp.net>
|
|
6
6
|
License: MIT License
|
|
@@ -38,13 +38,13 @@ Requires-Dist: pyyaml
|
|
|
38
38
|
Requires-Dist: python-dotenv
|
|
39
39
|
Requires-Dist: beautifulsoup4
|
|
40
40
|
Requires-Dist: packaging
|
|
41
|
+
Requires-Dist: rich>=13.0
|
|
41
42
|
Provides-Extra: dev
|
|
42
43
|
Requires-Dist: pytest; extra == "dev"
|
|
43
44
|
Requires-Dist: pytest-mock; extra == "dev"
|
|
44
45
|
Requires-Dist: flake8; extra == "dev"
|
|
45
46
|
Requires-Dist: black; extra == "dev"
|
|
46
47
|
Provides-Extra: cli
|
|
47
|
-
Requires-Dist: rich>=13.0; extra == "cli"
|
|
48
48
|
Provides-Extra: gemini
|
|
49
49
|
Requires-Dist: google-genai; extra == "gemini"
|
|
50
50
|
Provides-Extra: claude
|
|
@@ -53,12 +53,20 @@ Provides-Extra: openai
|
|
|
53
53
|
Requires-Dist: openai; extra == "openai"
|
|
54
54
|
Provides-Extra: office
|
|
55
55
|
Requires-Dist: pymupdf; extra == "office"
|
|
56
|
+
Provides-Extra: defi
|
|
57
|
+
Requires-Dist: web3>=6.0.0; extra == "defi"
|
|
58
|
+
Provides-Extra: embeddings
|
|
59
|
+
Requires-Dist: fastembed; extra == "embeddings"
|
|
60
|
+
Requires-Dist: numpy; extra == "embeddings"
|
|
56
61
|
Provides-Extra: all
|
|
57
62
|
Requires-Dist: rich>=13.0; extra == "all"
|
|
58
63
|
Requires-Dist: google-genai; extra == "all"
|
|
59
64
|
Requires-Dist: anthropic; extra == "all"
|
|
60
65
|
Requires-Dist: openai; extra == "all"
|
|
61
66
|
Requires-Dist: pymupdf; extra == "all"
|
|
67
|
+
Requires-Dist: web3>=6.0.0; extra == "all"
|
|
68
|
+
Requires-Dist: fastembed; extra == "all"
|
|
69
|
+
Requires-Dist: numpy; extra == "all"
|
|
62
70
|
Dynamic: license-file
|
|
63
71
|
|
|
64
72
|
<div align="center">
|
|
@@ -118,7 +126,7 @@ documentation. Runnable provider scripts are indexed in
|
|
|
118
126
|
```text
|
|
119
127
|
Skillware/
|
|
120
128
|
├── docs/ # Introduction, testing, skill catalog, usage guides (docs/usage/)
|
|
121
|
-
├── examples/ # Provider reference scripts
|
|
129
|
+
├── examples/ # Provider reference scripts — usage demos, not pytest (see examples/README.md)
|
|
122
130
|
├── skills/ # Skill Registry
|
|
123
131
|
│ └── category/ # Domain boundaries (e.g., finance)
|
|
124
132
|
│ └── skill_name/ # The Skill bundle
|
|
@@ -126,7 +134,7 @@ Skillware/
|
|
|
126
134
|
│ ├── skill.py # Executable Python logic
|
|
127
135
|
│ ├── instructions.md # Cognitive map for the LLM
|
|
128
136
|
│ ├── card.json # Optional UI presentation metadata
|
|
129
|
-
│ └── test_skill.py #
|
|
137
|
+
│ └── test_skill.py # Bundle test (required for new skills; see docs/TESTING.md)
|
|
130
138
|
├── skillware/ # Core Framework Package
|
|
131
139
|
│ ├── cli.py # Command-line interface
|
|
132
140
|
│ └── core/
|
|
@@ -135,7 +143,9 @@ Skillware/
|
|
|
135
143
|
│ └── loader.py # Universal Skill Loader and Model Adapter
|
|
136
144
|
├── templates/ # Boilerplate templates for new skills
|
|
137
145
|
│ └── python_skill/ # Standard template with required files
|
|
138
|
-
└── tests/ #
|
|
146
|
+
└── tests/ # Clone-repo tests (framework + optional maintainer skill tests)
|
|
147
|
+
├── test_*.py # Framework tests (loader, CLI, issuer, …)
|
|
148
|
+
└── skills/ # Optional maintainer skill tests (edge cases)
|
|
139
149
|
```
|
|
140
150
|
|
|
141
151
|
## Quick Start
|
|
@@ -161,11 +171,16 @@ pip install -e .
|
|
|
161
171
|
### 2. Verify your installation
|
|
162
172
|
|
|
163
173
|
```bash
|
|
164
|
-
pip install "skillware[cli]"
|
|
165
174
|
skillware list
|
|
166
175
|
```
|
|
167
176
|
|
|
168
|
-
This prints a table of all locally available skills and confirms the install
|
|
177
|
+
This prints a table of all locally available skills and confirms the install
|
|
178
|
+
and path resolution are working. Running `skillware` with no arguments opens
|
|
179
|
+
the interactive menu.
|
|
180
|
+
|
|
181
|
+
If `skillware` is not recognized, Python's `Scripts` directory may not be on
|
|
182
|
+
your PATH — use `python -m skillware list` as a fallback. See
|
|
183
|
+
[CLI Reference](docs/usage/cli.md#running-the-cli) for details.
|
|
169
184
|
|
|
170
185
|
### 3. Configuration
|
|
171
186
|
|
|
@@ -255,7 +270,7 @@ Also read the [Agent Code of Conduct](CODE_OF_CONDUCT.md). Open PRs with the [pu
|
|
|
255
270
|
|
|
256
271
|
## Comparison
|
|
257
272
|
|
|
258
|
-
Skillware differs from the Model Context Protocol (MCP), and
|
|
273
|
+
Skillware differs from the Model Context Protocol (MCP), and Agent Skills (SKILL.md) in several ways:
|
|
259
274
|
|
|
260
275
|
* **Model Agnostic**: Native adapters for Gemini, Claude, Ollama, and OpenAI.
|
|
261
276
|
* **Code-First**: Skills are executable Python packages, not just server specs.
|
|
@@ -55,7 +55,7 @@ documentation. Runnable provider scripts are indexed in
|
|
|
55
55
|
```text
|
|
56
56
|
Skillware/
|
|
57
57
|
├── docs/ # Introduction, testing, skill catalog, usage guides (docs/usage/)
|
|
58
|
-
├── examples/ # Provider reference scripts
|
|
58
|
+
├── examples/ # Provider reference scripts — usage demos, not pytest (see examples/README.md)
|
|
59
59
|
├── skills/ # Skill Registry
|
|
60
60
|
│ └── category/ # Domain boundaries (e.g., finance)
|
|
61
61
|
│ └── skill_name/ # The Skill bundle
|
|
@@ -63,7 +63,7 @@ Skillware/
|
|
|
63
63
|
│ ├── skill.py # Executable Python logic
|
|
64
64
|
│ ├── instructions.md # Cognitive map for the LLM
|
|
65
65
|
│ ├── card.json # Optional UI presentation metadata
|
|
66
|
-
│ └── test_skill.py #
|
|
66
|
+
│ └── test_skill.py # Bundle test (required for new skills; see docs/TESTING.md)
|
|
67
67
|
├── skillware/ # Core Framework Package
|
|
68
68
|
│ ├── cli.py # Command-line interface
|
|
69
69
|
│ └── core/
|
|
@@ -72,7 +72,9 @@ Skillware/
|
|
|
72
72
|
│ └── loader.py # Universal Skill Loader and Model Adapter
|
|
73
73
|
├── templates/ # Boilerplate templates for new skills
|
|
74
74
|
│ └── python_skill/ # Standard template with required files
|
|
75
|
-
└── tests/ #
|
|
75
|
+
└── tests/ # Clone-repo tests (framework + optional maintainer skill tests)
|
|
76
|
+
├── test_*.py # Framework tests (loader, CLI, issuer, …)
|
|
77
|
+
└── skills/ # Optional maintainer skill tests (edge cases)
|
|
76
78
|
```
|
|
77
79
|
|
|
78
80
|
## Quick Start
|
|
@@ -98,11 +100,16 @@ pip install -e .
|
|
|
98
100
|
### 2. Verify your installation
|
|
99
101
|
|
|
100
102
|
```bash
|
|
101
|
-
pip install "skillware[cli]"
|
|
102
103
|
skillware list
|
|
103
104
|
```
|
|
104
105
|
|
|
105
|
-
This prints a table of all locally available skills and confirms the install
|
|
106
|
+
This prints a table of all locally available skills and confirms the install
|
|
107
|
+
and path resolution are working. Running `skillware` with no arguments opens
|
|
108
|
+
the interactive menu.
|
|
109
|
+
|
|
110
|
+
If `skillware` is not recognized, Python's `Scripts` directory may not be on
|
|
111
|
+
your PATH — use `python -m skillware list` as a fallback. See
|
|
112
|
+
[CLI Reference](docs/usage/cli.md#running-the-cli) for details.
|
|
106
113
|
|
|
107
114
|
### 3. Configuration
|
|
108
115
|
|
|
@@ -192,7 +199,7 @@ Also read the [Agent Code of Conduct](CODE_OF_CONDUCT.md). Open PRs with the [pu
|
|
|
192
199
|
|
|
193
200
|
## Comparison
|
|
194
201
|
|
|
195
|
-
Skillware differs from the Model Context Protocol (MCP), and
|
|
202
|
+
Skillware differs from the Model Context Protocol (MCP), and Agent Skills (SKILL.md) in several ways:
|
|
196
203
|
|
|
197
204
|
* **Model Agnostic**: Native adapters for Gemini, Claude, Ollama, and OpenAI.
|
|
198
205
|
* **Code-First**: Skills are executable Python packages, not just server specs.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "skillware"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.6"
|
|
8
8
|
description = "A framework for modular, self-contained AI skills."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [
|
|
@@ -23,6 +23,7 @@ dependencies = [
|
|
|
23
23
|
"python-dotenv",
|
|
24
24
|
"beautifulsoup4",
|
|
25
25
|
"packaging",
|
|
26
|
+
"rich>=13.0",
|
|
26
27
|
]
|
|
27
28
|
requires-python = ">=3.10"
|
|
28
29
|
|
|
@@ -36,9 +37,7 @@ dev = [
|
|
|
36
37
|
"flake8",
|
|
37
38
|
"black",
|
|
38
39
|
]
|
|
39
|
-
cli = [
|
|
40
|
-
"rich>=13.0",
|
|
41
|
-
]
|
|
40
|
+
cli = []
|
|
42
41
|
gemini = [
|
|
43
42
|
"google-genai",
|
|
44
43
|
]
|
|
@@ -51,12 +50,22 @@ openai = [
|
|
|
51
50
|
office = [
|
|
52
51
|
"pymupdf",
|
|
53
52
|
]
|
|
53
|
+
defi = [
|
|
54
|
+
"web3>=6.0.0",
|
|
55
|
+
]
|
|
56
|
+
embeddings = [
|
|
57
|
+
"fastembed",
|
|
58
|
+
"numpy",
|
|
59
|
+
]
|
|
54
60
|
all = [
|
|
55
61
|
"rich>=13.0",
|
|
56
62
|
"google-genai",
|
|
57
63
|
"anthropic",
|
|
58
64
|
"openai",
|
|
59
65
|
"pymupdf",
|
|
66
|
+
"web3>=6.0.0",
|
|
67
|
+
"fastembed",
|
|
68
|
+
"numpy",
|
|
60
69
|
]
|
|
61
70
|
|
|
62
71
|
[tool.setuptools]
|
|
@@ -69,3 +78,11 @@ include = ["skillware*", "skills*"]
|
|
|
69
78
|
# and this wildcard. No per-skill entries needed when adding registry skills.
|
|
70
79
|
[tool.setuptools.package-data]
|
|
71
80
|
skills = ["**/*"]
|
|
81
|
+
|
|
82
|
+
[tool.pytest.ini_options]
|
|
83
|
+
testpaths = ["tests", "skills"]
|
|
84
|
+
addopts = "--ignore=examples"
|
|
85
|
+
|
|
86
|
+
[tool.black]
|
|
87
|
+
line-length = 88
|
|
88
|
+
target-version = ["py310"]
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
import yaml
|
|
5
|
+
|
|
6
|
+
from .skill import MiCAModuleSkill
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.fixture
|
|
10
|
+
def skill():
|
|
11
|
+
return MiCAModuleSkill()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.fixture
|
|
15
|
+
def manifest():
|
|
16
|
+
manifest_path = os.path.join(os.path.dirname(__file__), "manifest.yaml")
|
|
17
|
+
with open(manifest_path, "r", encoding="utf-8") as f:
|
|
18
|
+
return yaml.safe_load(f)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_skill_manifest_consistency(skill, manifest):
|
|
22
|
+
skill_manifest = skill.manifest
|
|
23
|
+
assert skill_manifest["name"] == manifest["name"]
|
|
24
|
+
assert skill_manifest["version"] == manifest["version"]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_stateless_rag_execution(skill):
|
|
28
|
+
result = skill.execute(
|
|
29
|
+
{
|
|
30
|
+
"user_prompt": (
|
|
31
|
+
"I want to issue an asset-referenced token. "
|
|
32
|
+
"What are the authorization rules?"
|
|
33
|
+
),
|
|
34
|
+
"run_evaluator": False,
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
assert result["policy_status"] == "CAUTION"
|
|
38
|
+
assert "retrieved_sections" in result
|
|
39
|
+
assert "final_context_for_agent" in result
|
|
40
|
+
assert "Evaluator disabled" in result["gemini_evaluator_feedback"]["holes_found"]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_router_normalization(skill):
|
|
44
|
+
mock_corpus = [
|
|
45
|
+
{
|
|
46
|
+
"title_num": "Title V",
|
|
47
|
+
"article_num": "Article 59",
|
|
48
|
+
"keywords": ["authorisation", "casp"],
|
|
49
|
+
"content": "CASP Authorization rules...",
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
matched = skill._route_and_fetch("Authorization of a CASP", mock_corpus)
|
|
53
|
+
assert len(matched) > 0
|
|
54
|
+
assert matched[0]["article_num"] == "Article 59"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_router_deduplication(skill):
|
|
58
|
+
mock_corpus = [
|
|
59
|
+
{
|
|
60
|
+
"title_num": "Title V",
|
|
61
|
+
"article_num": "Article 59",
|
|
62
|
+
"keywords": ["authorisation", "casp"],
|
|
63
|
+
"content": "CASP Authorization rules...",
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
matched = skill._route_and_fetch("authorisation casp", mock_corpus)
|
|
67
|
+
assert len(matched) == 1
|
|
@@ -11,10 +11,7 @@ class PIIMaskerSkill(BaseSkill):
|
|
|
11
11
|
|
|
12
12
|
@property
|
|
13
13
|
def manifest(self) -> Dict[str, Any]:
|
|
14
|
-
return {
|
|
15
|
-
"name": "compliance/pii_masker",
|
|
16
|
-
"version": "0.1.0"
|
|
17
|
-
}
|
|
14
|
+
return {"name": "compliance/pii_masker", "version": "0.1.0"}
|
|
18
15
|
|
|
19
16
|
def execute(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
20
17
|
text = params.get("text", "")
|
|
@@ -25,7 +22,7 @@ class PIIMaskerSkill(BaseSkill):
|
|
|
25
22
|
sanitized_text = self._apply_mode(sanitized_text, mode)
|
|
26
23
|
|
|
27
24
|
# Build unique entity types list
|
|
28
|
-
entities = list(set([re.sub(r
|
|
25
|
+
entities = list(set([re.sub(r"_[0-9]+$", "", e) for e in detected_entities]))
|
|
29
26
|
|
|
30
27
|
return {
|
|
31
28
|
"sanitized_text": sanitized_text,
|
|
@@ -33,17 +30,21 @@ class PIIMaskerSkill(BaseSkill):
|
|
|
33
30
|
"detected_entities": entities,
|
|
34
31
|
"entity_count": len(detected_entities),
|
|
35
32
|
"security_level": "local-only",
|
|
36
|
-
"model": "arpacorp/micro-f1-mask"
|
|
37
|
-
}
|
|
33
|
+
"model": "arpacorp/micro-f1-mask",
|
|
34
|
+
},
|
|
38
35
|
}
|
|
39
36
|
|
|
40
37
|
def _call_ollama(self, text: str, endpoint: str) -> Tuple[str, List[str]]:
|
|
41
38
|
try:
|
|
42
|
-
response = requests.post(
|
|
43
|
-
"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
response = requests.post(
|
|
40
|
+
f"{endpoint}/api/generate",
|
|
41
|
+
json={
|
|
42
|
+
"model": "arpacorp/micro-f1-mask",
|
|
43
|
+
"prompt": text,
|
|
44
|
+
"stream": False,
|
|
45
|
+
},
|
|
46
|
+
timeout=30,
|
|
47
|
+
)
|
|
47
48
|
if response.status_code == 200:
|
|
48
49
|
result_text = response.json().get("response", text)
|
|
49
50
|
else:
|
|
@@ -56,7 +57,7 @@ class PIIMaskerSkill(BaseSkill):
|
|
|
56
57
|
result_text = text
|
|
57
58
|
|
|
58
59
|
# Detect entities in the response
|
|
59
|
-
detected = re.findall(r
|
|
60
|
+
detected = re.findall(r"\[([A-Z_]+(?:_[0-9]+)?)\]", result_text)
|
|
60
61
|
return result_text, detected
|
|
61
62
|
|
|
62
63
|
def _apply_mode(self, text: str, mode: str) -> str:
|
|
@@ -64,14 +65,14 @@ class PIIMaskerSkill(BaseSkill):
|
|
|
64
65
|
return text
|
|
65
66
|
|
|
66
67
|
# Pattern to catch [DOCUMENT], [PERSON_1], etc.
|
|
67
|
-
pattern = r
|
|
68
|
+
pattern = r"\[[A-Z_]+(?:_[0-9]+)?\]"
|
|
68
69
|
if mode == "redact":
|
|
69
70
|
return re.sub(pattern, "XXXX", text)
|
|
70
71
|
elif mode == "remove":
|
|
71
72
|
# Replace token and any immediate preceding/following spaces safely
|
|
72
73
|
# A simple sub is sufficient. Cleaning up double spaces.
|
|
73
74
|
text = re.sub(pattern, "", text)
|
|
74
|
-
text = re.sub(r
|
|
75
|
+
text = re.sub(r"\s+", " ", text).strip()
|
|
75
76
|
return text
|
|
76
77
|
|
|
77
78
|
return text
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
import yaml
|
|
5
|
+
|
|
6
|
+
from .skill import PIIMaskerSkill
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.fixture
|
|
10
|
+
def skill():
|
|
11
|
+
return PIIMaskerSkill()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.fixture
|
|
15
|
+
def manifest():
|
|
16
|
+
manifest_path = os.path.join(os.path.dirname(__file__), "manifest.yaml")
|
|
17
|
+
with open(manifest_path, "r", encoding="utf-8") as f:
|
|
18
|
+
return yaml.safe_load(f)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_skill_manifest_consistency(skill, manifest):
|
|
22
|
+
skill_manifest = skill.manifest
|
|
23
|
+
assert skill_manifest["name"] == manifest["name"]
|
|
24
|
+
assert skill_manifest["version"] == manifest["version"]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_pii_masker_modes(mocker, skill):
|
|
28
|
+
mock_response = (
|
|
29
|
+
"Hello [PERSON_1], your wallet [CRYPTO_ADDRESS] and [EMAIL] have been verified."
|
|
30
|
+
)
|
|
31
|
+
mocker.patch.object(
|
|
32
|
+
skill,
|
|
33
|
+
"_call_ollama",
|
|
34
|
+
return_value=(mock_response, ["PERSON_1", "CRYPTO_ADDRESS", "EMAIL"]),
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
sample_text = (
|
|
38
|
+
"Hello John Doe, your wallet 0xabc and john@doe.com have been verified."
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
result_mask = skill.execute({"text": sample_text})
|
|
42
|
+
assert (
|
|
43
|
+
result_mask["sanitized_text"]
|
|
44
|
+
== "Hello [PERSON_1], your wallet [CRYPTO_ADDRESS] and [EMAIL] have been verified."
|
|
45
|
+
)
|
|
46
|
+
assert "PERSON" in result_mask["metadata"]["detected_entities"]
|
|
47
|
+
assert "CRYPTO_ADDRESS" in result_mask["metadata"]["detected_entities"]
|
|
48
|
+
|
|
49
|
+
result_redact = skill.execute({"text": sample_text, "mode": "redact"})
|
|
50
|
+
assert (
|
|
51
|
+
result_redact["sanitized_text"]
|
|
52
|
+
== "Hello XXXX, your wallet XXXX and XXXX have been verified."
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
result_remove = skill.execute({"text": sample_text, "mode": "remove"})
|
|
56
|
+
assert (
|
|
57
|
+
result_remove["sanitized_text"] == "Hello , your wallet and have been verified."
|
|
58
|
+
)
|
|
@@ -213,7 +213,9 @@ class TOSEvaluatorSkill(BaseSkill):
|
|
|
213
213
|
try:
|
|
214
214
|
response = self.session.get(robots_url, timeout=10)
|
|
215
215
|
if response.status_code >= 400:
|
|
216
|
-
assessment["reason"] =
|
|
216
|
+
assessment["reason"] = (
|
|
217
|
+
f"robots.txt returned HTTP {response.status_code}."
|
|
218
|
+
)
|
|
217
219
|
return assessment
|
|
218
220
|
|
|
219
221
|
parser = RobotFileParser()
|
|
@@ -266,13 +268,20 @@ class TOSEvaluatorSkill(BaseSkill):
|
|
|
266
268
|
else:
|
|
267
269
|
candidates[item["url"]] = item
|
|
268
270
|
|
|
269
|
-
ordered = sorted(
|
|
271
|
+
ordered = sorted(
|
|
272
|
+
candidates.values(), key=lambda item: item["score"], reverse=True
|
|
273
|
+
)
|
|
270
274
|
return {"candidates": ordered[:max_terms_pages]}
|
|
271
275
|
|
|
272
|
-
def _extract_candidate_links(
|
|
276
|
+
def _extract_candidate_links(
|
|
277
|
+
self, page_url: str, origin: str
|
|
278
|
+
) -> List[Dict[str, Any]]:
|
|
273
279
|
links: List[Dict[str, Any]] = []
|
|
274
280
|
response = self._safe_get(page_url, timeout=10)
|
|
275
|
-
if
|
|
281
|
+
if (
|
|
282
|
+
not response
|
|
283
|
+
or "html" not in response.headers.get("Content-Type", "").lower()
|
|
284
|
+
):
|
|
276
285
|
return links
|
|
277
286
|
|
|
278
287
|
soup = BeautifulSoup(response.text[:300000], "html.parser")
|
|
@@ -449,7 +458,10 @@ class TOSEvaluatorSkill(BaseSkill):
|
|
|
449
458
|
return False
|
|
450
459
|
if robots_assessment.get("can_fetch") is False:
|
|
451
460
|
return False
|
|
452
|
-
return bool(
|
|
461
|
+
return bool(
|
|
462
|
+
tos_assessment.get("matched_clauses")
|
|
463
|
+
or tos_assessment["status"] == "caution"
|
|
464
|
+
)
|
|
453
465
|
|
|
454
466
|
def _run_llm_evaluator(
|
|
455
467
|
self, normalized: Dict[str, Any], tos_assessment: Dict[str, Any]
|
|
@@ -512,26 +524,33 @@ class TOSEvaluatorSkill(BaseSkill):
|
|
|
512
524
|
verdict = "INSUFFICIENT_EVIDENCE"
|
|
513
525
|
confidence_score = 0.35
|
|
514
526
|
reason = "Insufficient policy evidence to safely approve the requested action."
|
|
515
|
-
recommended_next_step =
|
|
527
|
+
recommended_next_step = (
|
|
528
|
+
"Review the discovered policy pages manually before proceeding."
|
|
529
|
+
)
|
|
516
530
|
|
|
517
531
|
if robots_assessment.get("can_fetch") is False:
|
|
518
532
|
verdict = "UNSAFE"
|
|
519
533
|
confidence_score = 0.98
|
|
520
534
|
reason = robots_assessment["reason"]
|
|
521
|
-
recommended_next_step =
|
|
522
|
-
"Do not automate access to this path unless you have explicit permission."
|
|
523
|
-
)
|
|
535
|
+
recommended_next_step = "Do not automate access to this path unless you have explicit permission."
|
|
524
536
|
elif tos_assessment["status"] == "blocked":
|
|
525
537
|
verdict = "UNSAFE"
|
|
526
538
|
confidence_score = 0.9
|
|
527
539
|
reason = tos_assessment["summary"]
|
|
528
|
-
recommended_next_step =
|
|
540
|
+
recommended_next_step = (
|
|
541
|
+
"Avoid the requested action or obtain explicit written permission."
|
|
542
|
+
)
|
|
529
543
|
elif tos_assessment["status"] == "caution":
|
|
530
544
|
verdict = "CAUTION"
|
|
531
545
|
confidence_score = 0.65
|
|
532
546
|
reason = tos_assessment["summary"]
|
|
533
|
-
recommended_next_step =
|
|
534
|
-
|
|
547
|
+
recommended_next_step = (
|
|
548
|
+
"Prefer an official API or documented integration path if one exists."
|
|
549
|
+
)
|
|
550
|
+
elif (
|
|
551
|
+
tos_assessment["status"] == "allowed"
|
|
552
|
+
and robots_assessment.get("can_fetch") is not False
|
|
553
|
+
):
|
|
535
554
|
verdict = "SAFE"
|
|
536
555
|
confidence_score = 0.72
|
|
537
556
|
reason = tos_assessment["summary"]
|
|
@@ -575,7 +594,9 @@ class TOSEvaluatorSkill(BaseSkill):
|
|
|
575
594
|
"tos_assessment": tos_assessment,
|
|
576
595
|
"llm_assessment": llm_assessment or {"status": "not_used"},
|
|
577
596
|
"discovered_policy_urls": {
|
|
578
|
-
"candidates": [
|
|
597
|
+
"candidates": [
|
|
598
|
+
item["url"] for item in policy_candidates.get("candidates", [])
|
|
599
|
+
]
|
|
579
600
|
},
|
|
580
601
|
"evidence": evidence,
|
|
581
602
|
}
|
|
@@ -1,32 +1,66 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from unittest.mock import MagicMock
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
1
5
|
import pytest
|
|
2
6
|
import yaml
|
|
3
|
-
|
|
7
|
+
|
|
4
8
|
from .skill import NoveltyExtractor
|
|
5
9
|
|
|
6
10
|
|
|
11
|
+
def _mock_embed_vectors(texts):
|
|
12
|
+
"""Deterministic keyword vectors — no HuggingFace download in CI."""
|
|
13
|
+
vectors = []
|
|
14
|
+
for text in texts:
|
|
15
|
+
t = text.lower()
|
|
16
|
+
if "bitcoin" in t:
|
|
17
|
+
vectors.append(np.array([1.0, 0.0, 0.0], dtype=float))
|
|
18
|
+
elif "sky" in t or "blue" in t:
|
|
19
|
+
vectors.append(np.array([0.0, 1.0, 0.0], dtype=float))
|
|
20
|
+
elif "python" in t:
|
|
21
|
+
vectors.append(np.array([0.0, 0.0, 1.0], dtype=float))
|
|
22
|
+
else:
|
|
23
|
+
vectors.append(np.array([0.1, 0.1, 0.1], dtype=float))
|
|
24
|
+
return vectors
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@pytest.fixture(autouse=True)
|
|
28
|
+
def mock_embedding_model(monkeypatch):
|
|
29
|
+
"""Bundle tests must stay offline; mock fastembed to avoid HF rate limits in CI."""
|
|
30
|
+
NoveltyExtractor._model = None
|
|
31
|
+
mock_model = MagicMock()
|
|
32
|
+
mock_model.embed.side_effect = _mock_embed_vectors
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def _fake_get_model(cls):
|
|
36
|
+
if cls._model is None:
|
|
37
|
+
cls._model = mock_model
|
|
38
|
+
return cls._model
|
|
39
|
+
|
|
40
|
+
monkeypatch.setattr(NoveltyExtractor, "_get_model", _fake_get_model)
|
|
41
|
+
yield
|
|
42
|
+
NoveltyExtractor._model = None
|
|
43
|
+
|
|
44
|
+
|
|
7
45
|
@pytest.fixture
|
|
8
46
|
def skill():
|
|
9
|
-
"""Fixture to initialize the skill."""
|
|
10
47
|
return NoveltyExtractor()
|
|
11
48
|
|
|
12
49
|
|
|
13
50
|
@pytest.fixture
|
|
14
51
|
def manifest():
|
|
15
|
-
"""Fixture to load manifest.yaml for validation."""
|
|
16
52
|
manifest_path = os.path.join(os.path.dirname(__file__), "manifest.yaml")
|
|
17
53
|
with open(manifest_path, "r", encoding="utf-8") as f:
|
|
18
54
|
return yaml.safe_load(f)
|
|
19
55
|
|
|
20
56
|
|
|
21
57
|
def test_skill_manifest_consistency(skill, manifest):
|
|
22
|
-
"""Verify the skill manifest matches manifest.yaml."""
|
|
23
58
|
skill_manifest = skill.manifest
|
|
24
59
|
assert skill_manifest["name"] == manifest["name"]
|
|
25
60
|
assert skill_manifest["version"] == manifest["version"]
|
|
26
61
|
|
|
27
62
|
|
|
28
63
|
def test_skill_returns_dict(skill):
|
|
29
|
-
"""Execution result must be a dictionary."""
|
|
30
64
|
result = skill.execute(
|
|
31
65
|
{
|
|
32
66
|
"dataset_chunk": "Bitcoin is going to rise.\n\nThe sky is blue.",
|
|
@@ -37,7 +71,6 @@ def test_skill_returns_dict(skill):
|
|
|
37
71
|
|
|
38
72
|
|
|
39
73
|
def test_skill_output_keys(skill):
|
|
40
|
-
"""Result must contain the three expected output keys."""
|
|
41
74
|
result = skill.execute(
|
|
42
75
|
{
|
|
43
76
|
"dataset_chunk": "Bitcoin is going to rise.\n\nThe sky is blue.",
|
|
@@ -50,7 +83,6 @@ def test_skill_output_keys(skill):
|
|
|
50
83
|
|
|
51
84
|
|
|
52
85
|
def test_skill_filters_redundant_chunks(skill):
|
|
53
|
-
"""Semantically similar chunks should be filtered out."""
|
|
54
86
|
result = skill.execute(
|
|
55
87
|
{
|
|
56
88
|
"dataset_chunk": (
|
|
@@ -67,7 +99,6 @@ def test_skill_filters_redundant_chunks(skill):
|
|
|
67
99
|
|
|
68
100
|
|
|
69
101
|
def test_skill_empty_input(skill):
|
|
70
|
-
"""Empty input should return empty result without crashing."""
|
|
71
102
|
result = skill.execute(
|
|
72
103
|
{
|
|
73
104
|
"dataset_chunk": "",
|
|
@@ -79,7 +110,6 @@ def test_skill_empty_input(skill):
|
|
|
79
110
|
|
|
80
111
|
|
|
81
112
|
def test_skill_baseline_filters_seen_content(skill):
|
|
82
|
-
"""Chunks already in baseline should be filtered out."""
|
|
83
113
|
baseline = "Bitcoin is going to rise.\n\nThe sky is blue."
|
|
84
114
|
result = skill.execute(
|
|
85
115
|
{
|
|
@@ -93,7 +123,6 @@ def test_skill_baseline_filters_seen_content(skill):
|
|
|
93
123
|
|
|
94
124
|
|
|
95
125
|
def test_skill_sentence_strategy(skill):
|
|
96
|
-
"""Sentence chunking strategy should work without crashing."""
|
|
97
126
|
result = skill.execute(
|
|
98
127
|
{
|
|
99
128
|
"dataset_chunk": "Bitcoin is going to rise. The sky is blue. Python is great.",
|