skillware 0.3.5__tar.gz → 0.3.7__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.
Files changed (123) hide show
  1. {skillware-0.3.5/skillware.egg-info → skillware-0.3.7}/PKG-INFO +22 -7
  2. {skillware-0.3.5 → skillware-0.3.7}/README.md +12 -5
  3. {skillware-0.3.5 → skillware-0.3.7}/pyproject.toml +17 -4
  4. skillware-0.3.7/skills/compliance/mica_module/test_skill.py +67 -0
  5. skillware-0.3.7/skills/compliance/pii_masker/test_skill.py +58 -0
  6. {skillware-0.3.5 → skillware-0.3.7}/skills/data_engineering/novelty_extractor/test_skill.py +39 -10
  7. skillware-0.3.7/skills/data_engineering/synthetic_generator/test_skill.py +58 -0
  8. {skillware-0.3.5 → skillware-0.3.7}/skills/finance/wallet_screening/card.json +4 -4
  9. {skillware-0.3.5 → skillware-0.3.7}/skills/finance/wallet_screening/instructions.md +5 -5
  10. {skillware-0.3.5 → skillware-0.3.7}/skills/finance/wallet_screening/manifest.yaml +1 -1
  11. {skillware-0.3.5 → skillware-0.3.7}/skills/finance/wallet_screening/skill.py +5 -0
  12. skillware-0.3.7/skills/finance/wallet_screening/test_skill.py +84 -0
  13. skillware-0.3.7/skills/office/pdf_form_filler/test_skill.py +84 -0
  14. skillware-0.3.7/skills/optimization/prompt_rewriter/__init__.py +3 -0
  15. skillware-0.3.7/skills/optimization/prompt_rewriter/test_skill.py +55 -0
  16. skillware-0.3.7/skills/wellness/mental_coach/__init__.py +3 -0
  17. skillware-0.3.7/skills/wellness/mental_coach/card.json +25 -0
  18. skillware-0.3.7/skills/wellness/mental_coach/constraints.py +93 -0
  19. skillware-0.3.7/skills/wellness/mental_coach/crisis_gate.py +175 -0
  20. skillware-0.3.7/skills/wellness/mental_coach/instructions.md +37 -0
  21. skillware-0.3.7/skills/wellness/mental_coach/kb/corpus.json +214 -0
  22. skillware-0.3.7/skills/wellness/mental_coach/kb/crisis_resources.json +219 -0
  23. skillware-0.3.7/skills/wellness/mental_coach/kb/hard_constraints.yaml +86 -0
  24. skillware-0.3.7/skills/wellness/mental_coach/kb_provider.py +101 -0
  25. skillware-0.3.7/skills/wellness/mental_coach/manifest.yaml +66 -0
  26. skillware-0.3.7/skills/wellness/mental_coach/resources.py +71 -0
  27. skillware-0.3.7/skills/wellness/mental_coach/skill.py +396 -0
  28. skillware-0.3.7/skills/wellness/mental_coach/test_skill.py +112 -0
  29. skillware-0.3.7/skillware/__main__.py +4 -0
  30. {skillware-0.3.5 → skillware-0.3.7}/skillware/cli.py +77 -28
  31. skillware-0.3.7/skillware/core/__init__.py +0 -0
  32. {skillware-0.3.5 → skillware-0.3.7}/skillware/version_policy.py +1 -1
  33. {skillware-0.3.5 → skillware-0.3.7/skillware.egg-info}/PKG-INFO +22 -7
  34. {skillware-0.3.5 → skillware-0.3.7}/skillware.egg-info/SOURCES.txt +21 -1
  35. {skillware-0.3.5 → skillware-0.3.7}/skillware.egg-info/requires.txt +11 -1
  36. {skillware-0.3.5 → skillware-0.3.7}/tests/test_cli.py +60 -0
  37. skillware-0.3.5/skills/optimization/prompt_rewriter/__init__.py +0 -3
  38. skillware-0.3.5/tests/test_mica_module.py +0 -66
  39. {skillware-0.3.5 → skillware-0.3.7}/LICENSE +0 -0
  40. {skillware-0.3.5 → skillware-0.3.7}/MANIFEST.in +0 -0
  41. {skillware-0.3.5 → skillware-0.3.7}/setup.cfg +0 -0
  42. {skillware-0.3.5 → skillware-0.3.7}/skills/__init__.py +0 -0
  43. {skillware-0.3.5 → skillware-0.3.7}/skills/compliance/__init__.py +0 -0
  44. {skillware-0.3.5 → skillware-0.3.7}/skills/compliance/mica_module/__init__.py +0 -0
  45. {skillware-0.3.5 → skillware-0.3.7}/skills/compliance/mica_module/card.json +0 -0
  46. {skillware-0.3.5 → skillware-0.3.7}/skills/compliance/mica_module/instructions.md +0 -0
  47. {skillware-0.3.5 → skillware-0.3.7}/skills/compliance/mica_module/manifest.yaml +0 -0
  48. {skillware-0.3.5 → skillware-0.3.7}/skills/compliance/mica_module/mica_corpus.json +0 -0
  49. {skillware-0.3.5 → skillware-0.3.7}/skills/compliance/mica_module/skill.py +0 -0
  50. {skillware-0.3.5 → skillware-0.3.7}/skills/compliance/pii_masker/__init__.py +0 -0
  51. {skillware-0.3.5 → skillware-0.3.7}/skills/compliance/pii_masker/card.json +0 -0
  52. {skillware-0.3.5 → skillware-0.3.7}/skills/compliance/pii_masker/instructions.md +0 -0
  53. {skillware-0.3.5 → skillware-0.3.7}/skills/compliance/pii_masker/manifest.yaml +0 -0
  54. {skillware-0.3.5 → skillware-0.3.7}/skills/compliance/pii_masker/skill.py +0 -0
  55. {skillware-0.3.5 → skillware-0.3.7}/skills/compliance/tos_evaluator/__init__.py +0 -0
  56. {skillware-0.3.5 → skillware-0.3.7}/skills/compliance/tos_evaluator/card.json +0 -0
  57. {skillware-0.3.5 → skillware-0.3.7}/skills/compliance/tos_evaluator/instructions.md +0 -0
  58. {skillware-0.3.5 → skillware-0.3.7}/skills/compliance/tos_evaluator/manifest.yaml +0 -0
  59. {skillware-0.3.5 → skillware-0.3.7}/skills/compliance/tos_evaluator/skill.py +0 -0
  60. {skillware-0.3.5 → skillware-0.3.7}/skills/compliance/tos_evaluator/test_skill.py +0 -0
  61. {skillware-0.3.5 → skillware-0.3.7}/skills/data_engineering/__init__.py +0 -0
  62. {skillware-0.3.5 → skillware-0.3.7}/skills/data_engineering/novelty_extractor/__init__.py +0 -0
  63. {skillware-0.3.5 → skillware-0.3.7}/skills/data_engineering/novelty_extractor/card.json +0 -0
  64. {skillware-0.3.5 → skillware-0.3.7}/skills/data_engineering/novelty_extractor/instructions.md +0 -0
  65. {skillware-0.3.5 → skillware-0.3.7}/skills/data_engineering/novelty_extractor/manifest.yaml +0 -0
  66. {skillware-0.3.5 → skillware-0.3.7}/skills/data_engineering/novelty_extractor/skill.py +0 -0
  67. {skillware-0.3.5 → skillware-0.3.7}/skills/data_engineering/synthetic_generator/__init__.py +0 -0
  68. {skillware-0.3.5 → skillware-0.3.7}/skills/data_engineering/synthetic_generator/card.json +0 -0
  69. {skillware-0.3.5 → skillware-0.3.7}/skills/data_engineering/synthetic_generator/instructions.md +0 -0
  70. {skillware-0.3.5 → skillware-0.3.7}/skills/data_engineering/synthetic_generator/manifest.yaml +0 -0
  71. {skillware-0.3.5 → skillware-0.3.7}/skills/data_engineering/synthetic_generator/skill.py +0 -0
  72. {skillware-0.3.5 → skillware-0.3.7}/skills/defi/__init__.py +0 -0
  73. {skillware-0.3.5 → skillware-0.3.7}/skills/defi/evm_tx_handler/__init__.py +0 -0
  74. {skillware-0.3.5 → skillware-0.3.7}/skills/defi/evm_tx_handler/abis.py +0 -0
  75. {skillware-0.3.5 → skillware-0.3.7}/skills/defi/evm_tx_handler/card.json +0 -0
  76. {skillware-0.3.5 → skillware-0.3.7}/skills/defi/evm_tx_handler/config.yaml.example +0 -0
  77. {skillware-0.3.5 → skillware-0.3.7}/skills/defi/evm_tx_handler/data/addressbook.yaml +0 -0
  78. {skillware-0.3.5 → skillware-0.3.7}/skills/defi/evm_tx_handler/data/chains.yaml +0 -0
  79. {skillware-0.3.5 → skillware-0.3.7}/skills/defi/evm_tx_handler/data/tokens.yaml +0 -0
  80. {skillware-0.3.5 → skillware-0.3.7}/skills/defi/evm_tx_handler/instructions.md +0 -0
  81. {skillware-0.3.5 → skillware-0.3.7}/skills/defi/evm_tx_handler/manifest.yaml +0 -0
  82. {skillware-0.3.5 → skillware-0.3.7}/skills/defi/evm_tx_handler/skill.py +0 -0
  83. {skillware-0.3.5 → skillware-0.3.7}/skills/defi/evm_tx_handler/test_skill.py +0 -0
  84. {skillware-0.3.5 → skillware-0.3.7}/skills/dev_tools/__init__.py +0 -0
  85. {skillware-0.3.5 → skillware-0.3.7}/skills/dev_tools/issue_resolver/__init__.py +0 -0
  86. {skillware-0.3.5 → skillware-0.3.7}/skills/dev_tools/issue_resolver/card.json +0 -0
  87. {skillware-0.3.5 → skillware-0.3.7}/skills/dev_tools/issue_resolver/instructions.md +0 -0
  88. {skillware-0.3.5 → skillware-0.3.7}/skills/dev_tools/issue_resolver/manifest.yaml +0 -0
  89. {skillware-0.3.5 → skillware-0.3.7}/skills/dev_tools/issue_resolver/skill.py +0 -0
  90. {skillware-0.3.5 → skillware-0.3.7}/skills/dev_tools/issue_resolver/test_skill.py +0 -0
  91. {skillware-0.3.5 → skillware-0.3.7}/skills/dev_tools/issue_resolver/workflow.py +0 -0
  92. {skillware-0.3.5 → skillware-0.3.7}/skills/finance/__init__.py +0 -0
  93. {skillware-0.3.5 → skillware-0.3.7}/skills/finance/wallet_screening/__init__.py +0 -0
  94. {skillware-0.3.5 → skillware-0.3.7}/skills/finance/wallet_screening/data/entities.ftm.json +0 -0
  95. {skillware-0.3.5 → skillware-0.3.7}/skills/finance/wallet_screening/data/malicious_scs_2025.json +0 -0
  96. {skillware-0.3.5 → skillware-0.3.7}/skills/finance/wallet_screening/data/normalized_fbi_lazarus_20250722_180019.json +0 -0
  97. {skillware-0.3.5 → skillware-0.3.7}/skills/finance/wallet_screening/data/normalized_israel_nbctf_20250722_180019.json +0 -0
  98. {skillware-0.3.5 → skillware-0.3.7}/skills/finance/wallet_screening/data/normalized_uniswap_trm.json +0 -0
  99. {skillware-0.3.5 → skillware-0.3.7}/skills/finance/wallet_screening/maintenance/normalization_tool.py +0 -0
  100. {skillware-0.3.5 → skillware-0.3.7}/skills/finance/wallet_screening/maintenance/normalize_uniswap_trm.py +0 -0
  101. {skillware-0.3.5 → skillware-0.3.7}/skills/office/__init__.py +0 -0
  102. {skillware-0.3.5 → skillware-0.3.7}/skills/office/pdf_form_filler/__init__.py +0 -0
  103. {skillware-0.3.5 → skillware-0.3.7}/skills/office/pdf_form_filler/card.json +0 -0
  104. {skillware-0.3.5 → skillware-0.3.7}/skills/office/pdf_form_filler/instructions.md +0 -0
  105. {skillware-0.3.5 → skillware-0.3.7}/skills/office/pdf_form_filler/manifest.yaml +0 -0
  106. {skillware-0.3.5 → skillware-0.3.7}/skills/office/pdf_form_filler/skill.py +0 -0
  107. {skillware-0.3.5 → skillware-0.3.7}/skills/office/pdf_form_filler/utils.py +0 -0
  108. {skillware-0.3.5 → skillware-0.3.7}/skills/optimization/__init__.py +0 -0
  109. {skillware-0.3.5 → skillware-0.3.7}/skills/optimization/prompt_rewriter/card.json +0 -0
  110. {skillware-0.3.5 → skillware-0.3.7}/skills/optimization/prompt_rewriter/instructions.md +0 -0
  111. {skillware-0.3.5 → skillware-0.3.7}/skills/optimization/prompt_rewriter/manifest.yaml +0 -0
  112. {skillware-0.3.5 → skillware-0.3.7}/skills/optimization/prompt_rewriter/skill.py +0 -0
  113. {skillware-0.3.5/skillware → skillware-0.3.7/skills/wellness}/__init__.py +0 -0
  114. {skillware-0.3.5/skillware/core → skillware-0.3.7/skillware}/__init__.py +0 -0
  115. {skillware-0.3.5 → skillware-0.3.7}/skillware/core/base_skill.py +0 -0
  116. {skillware-0.3.5 → skillware-0.3.7}/skillware/core/env.py +0 -0
  117. {skillware-0.3.5 → skillware-0.3.7}/skillware/core/loader.py +0 -0
  118. {skillware-0.3.5 → skillware-0.3.7}/skillware.egg-info/dependency_links.txt +0 -0
  119. {skillware-0.3.5 → skillware-0.3.7}/skillware.egg-info/entry_points.txt +0 -0
  120. {skillware-0.3.5 → skillware-0.3.7}/skillware.egg-info/top_level.txt +0 -0
  121. {skillware-0.3.5 → skillware-0.3.7}/tests/test_loader.py +0 -0
  122. {skillware-0.3.5 → skillware-0.3.7}/tests/test_skill_issuer.py +0 -0
  123. {skillware-0.3.5 → skillware-0.3.7}/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.5
3
+ Version: 0.3.7
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 (Gemini, Claude, OpenAI, Ollama, ...)
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 # Unit tests and schema validation
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/ # Automated test suite
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 and path resolution are working. Running `skillware` with no arguments opens the interactive menu.
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
 
@@ -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 (Gemini, Claude, OpenAI, Ollama, ...)
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 # Unit tests and schema validation
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/ # Automated test suite
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 and path resolution are working. Running `skillware` with no arguments opens the interactive menu.
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
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "skillware"
7
- version = "0.3.5"
7
+ version = "0.3.7"
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]
@@ -70,6 +79,10 @@ include = ["skillware*", "skills*"]
70
79
  [tool.setuptools.package-data]
71
80
  skills = ["**/*"]
72
81
 
82
+ [tool.pytest.ini_options]
83
+ testpaths = ["tests", "skills"]
84
+ addopts = "--ignore=examples"
85
+
73
86
  [tool.black]
74
87
  line-length = 88
75
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
@@ -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
+ )
@@ -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
- import os
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.",
@@ -0,0 +1,58 @@
1
+ import os
2
+
3
+ import pytest
4
+ import yaml
5
+
6
+ from .skill import SyntheticGeneratorSkill
7
+
8
+
9
+ @pytest.fixture
10
+ def skill():
11
+ return SyntheticGeneratorSkill()
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_entropy_score(skill):
28
+ low_entropy_text = "test " * 100
29
+ score_low = skill._calculate_entropy_score(low_entropy_text)
30
+
31
+ high_text = "The brown fox jumps over the dog. Programming is fun!"
32
+ score_high = skill._calculate_entropy_score(high_text)
33
+
34
+ assert score_high > score_low
35
+
36
+
37
+ def test_execute_success(mocker, skill):
38
+ mock_json_response = """```json
39
+ [
40
+ {"instruction": "x", "input": "y", "output": "z"}
41
+ ]
42
+ ```"""
43
+ mocker.patch.object(skill, "_call_gemini", return_value=mock_json_response)
44
+
45
+ result = skill.execute(
46
+ {
47
+ "domain": "test domain",
48
+ "num_samples": 1,
49
+ "diversity_prompt": "be diverse",
50
+ "model_provider": "gemini",
51
+ }
52
+ )
53
+
54
+ assert result["status"] == "success"
55
+ assert result["provider_used"] == "gemini"
56
+ assert result["samples_generated"] == 1
57
+ assert "samples" in result
58
+ assert result["samples"][0]["instruction"] == "x"
@@ -12,10 +12,10 @@
12
12
  "ui_schema": {
13
13
  "type": "card",
14
14
  "fields": [
15
- {"key": "summary.address", "label": "Address"},
16
- {"key": "summary.sanctioned", "label": "Sanctioned", "type": "status_badge"},
17
- {"key": "summary.malicious_interactions_count", "label": "Risky Interactions", "type": "count"},
18
- {"key": "summary.pnl_eth", "label": "PnL (ETH)", "type": "currency"}
15
+ {"key": "metadata.wallet_address", "label": "Address"},
16
+ {"key": "summary.sanctioned_entity_match", "label": "Sanctioned", "type": "status_badge"},
17
+ {"key": "summary.malicious_interaction_count", "label": "Risky Interactions", "type": "count"},
18
+ {"key": "financial_analysis.pnl_eth", "label": "PnL (ETH)", "type": "currency"}
19
19
  ]
20
20
  }
21
21
  }
@@ -1,6 +1,6 @@
1
1
  # Wallet Screening Skill Instructions
2
2
 
3
- You are equipped with the `wallet_screening` skill. This tool allows you to perform due diligence on Ethereum addresses.
3
+ You are equipped with the `finance/wallet_screening` skill. This tool allows you to perform due diligence on Ethereum addresses.
4
4
 
5
5
  ## When to use
6
6
  Use this skill when the user:
@@ -13,10 +13,10 @@ Use this skill when the user:
13
13
  The tool returns a JSON object. You should summarize this for the user in a professional, "Compliance Officer" tone.
14
14
 
15
15
  ### Key Fields to Check:
16
- 1. **`summary.sanctioned` (Boolean)**: If `true`, this is CRITICAL. Report the `sanctions_hits` immediately.
17
- 2. **`summary.malicious_interactions` (Integer)**: If > 0, the wallet has touched bad actors. List the `malicious_contracts_check.matches`.
18
- 3. **`summary.pnl`**: Profit and Loss. Useful for determining if it's a profitable trader or a victim.
19
- 4. **`counterparty_analysis`**: Who are they sending money to?
16
+ 1. **`summary.sanctioned_entity_match` (Boolean)**: If `true`, this is CRITICAL. Report the `risk_details.sanctions_hits` immediately.
17
+ 2. **`summary.malicious_interaction_count` (Integer)**: If > 0, the wallet has touched bad actors. List the `risk_details.malicious_interactions`.
18
+ 3. **`financial_analysis.pnl_eth` / `pnl_usd`**: Profit and Loss. Useful for determining if it's a profitable trader or a victim.
19
+ 4. **`network_analysis.top_10_counterparties`**: Who are they sending money to?
20
20
 
21
21
  ## Safety Protocol
22
22
  * If a wallet is **Sanctioned**: severe warning. "⚠️ WARNING: This wallet appears on the following sanctions lists..."
@@ -1,4 +1,4 @@
1
- name: wallet_screening
1
+ name: finance/wallet_screening
2
2
  version: 1.0.0
3
3
  description: |
4
4
  A comprehensive crypto wallet screening tool. Checks Ethereum addresses against sanctions lists (OFAC, FBI, etc.) and known malicious contracts (Mixers, Scams). analyze transaction history for risk.
@@ -3,6 +3,7 @@ import os
3
3
  import re
4
4
  import requests
5
5
  import glob
6
+ import yaml
6
7
  from typing import Any, Dict, List, Optional
7
8
  from datetime import datetime
8
9
  from skillware.core.base_skill import BaseSkill
@@ -44,6 +45,10 @@ class WalletScreeningSkill(BaseSkill):
44
45
 
45
46
  @property
46
47
  def manifest(self) -> Dict[str, Any]:
48
+ manifest_path = os.path.join(os.path.dirname(__file__), "manifest.yaml")
49
+ if os.path.exists(manifest_path):
50
+ with open(manifest_path, "r", encoding="utf-8") as f:
51
+ return yaml.safe_load(f)
47
52
  return {}
48
53
 
49
54
  def execute(self, params: Dict[str, Any]) -> Dict[str, Any]:
@@ -0,0 +1,84 @@
1
+ import os
2
+ from unittest.mock import MagicMock, patch
3
+
4
+ import pytest
5
+ import yaml
6
+
7
+ from .skill import WalletScreeningSkill
8
+
9
+
10
+ @pytest.fixture
11
+ def skill():
12
+ return WalletScreeningSkill()
13
+
14
+
15
+ @pytest.fixture
16
+ def manifest():
17
+ manifest_path = os.path.join(os.path.dirname(__file__), "manifest.yaml")
18
+ with open(manifest_path, "r", encoding="utf-8") as f:
19
+ return yaml.safe_load(f)
20
+
21
+
22
+ def test_skill_manifest_consistency(skill, manifest):
23
+ skill_manifest = skill.manifest
24
+ assert skill_manifest["name"] == manifest["name"]
25
+ assert skill_manifest["version"] == manifest["version"]
26
+
27
+
28
+ def test_invalid_address(skill):
29
+ result = skill.execute({"address": "invalid_addr"})
30
+ assert "error" in result
31
+ assert "Invalid Ethereum address" in result["error"]
32
+
33
+
34
+ def test_missing_api_key(skill):
35
+ skill.etherscan_api_key = None
36
+ result = skill.execute({"address": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"})
37
+ assert "error" in result
38
+ assert "Missing ETHERSCAN_API_KEY" in result["error"]
39
+
40
+
41
+ @patch("skills.finance.wallet_screening.skill.requests.get")
42
+ def test_execute_success(mock_get, skill):
43
+ skill.etherscan_api_key = "dummy_key"
44
+
45
+ mock_eth_balance = MagicMock()
46
+ mock_eth_balance.json.return_value = {
47
+ "status": "1",
48
+ "result": "1000000000000000000",
49
+ }
50
+
51
+ mock_txs = MagicMock()
52
+ mock_txs.json.return_value = {
53
+ "status": "1",
54
+ "result": [
55
+ {
56
+ "from": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045".lower(),
57
+ "to": "0x123",
58
+ "value": "500000000000000000",
59
+ "isError": "0",
60
+ "gasUsed": "21000",
61
+ "gasPrice": "1000000000",
62
+ }
63
+ ],
64
+ }
65
+
66
+ mock_price = MagicMock()
67
+ mock_price.json.return_value = {"ethereum": {"usd": 2000.0, "eur": 1800.0}}
68
+
69
+ def get_side_effect(url, **kwargs):
70
+ params = kwargs.get("params") or {}
71
+ if params.get("action") == "balance":
72
+ return mock_eth_balance
73
+ if params.get("action") == "txlist":
74
+ return mock_txs
75
+ return mock_price
76
+
77
+ mock_get.side_effect = get_side_effect
78
+
79
+ result = skill.execute({"address": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"})
80
+
81
+ assert "error" not in result
82
+ assert "summary" in result
83
+ assert result["summary"]["balance_eth"] == 1.0
84
+ assert result["summary"]["balance_usd"] == 2000.0