skillseal 0.1.0__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 (45) hide show
  1. skillseal-0.1.0/.github/dependabot.yml +11 -0
  2. skillseal-0.1.0/.github/workflows/ci.yml +56 -0
  3. skillseal-0.1.0/.github/workflows/release.yml +56 -0
  4. skillseal-0.1.0/.gitignore +11 -0
  5. skillseal-0.1.0/.python-version +1 -0
  6. skillseal-0.1.0/LICENSE +21 -0
  7. skillseal-0.1.0/PKG-INFO +302 -0
  8. skillseal-0.1.0/README.md +289 -0
  9. skillseal-0.1.0/SECURITY.md +13 -0
  10. skillseal-0.1.0/examples/bad-skill/SKILL.md +57 -0
  11. skillseal-0.1.0/examples/bad-skill/skillseal.yaml +18 -0
  12. skillseal-0.1.0/examples/good-skill/SKILL.md +26 -0
  13. skillseal-0.1.0/examples/good-skill/skillseal.yaml +16 -0
  14. skillseal-0.1.0/pyproject.toml +45 -0
  15. skillseal-0.1.0/src/skillseal/__init__.py +3 -0
  16. skillseal-0.1.0/src/skillseal/cli.py +131 -0
  17. skillseal-0.1.0/src/skillseal/linter.py +21 -0
  18. skillseal-0.1.0/src/skillseal/models.py +135 -0
  19. skillseal-0.1.0/src/skillseal/parser.py +85 -0
  20. skillseal-0.1.0/src/skillseal/reporters/__init__.py +1 -0
  21. skillseal-0.1.0/src/skillseal/reporters/json_reporter.py +79 -0
  22. skillseal-0.1.0/src/skillseal/reporters/terminal.py +89 -0
  23. skillseal-0.1.0/src/skillseal/routing/__init__.py +1 -0
  24. skillseal-0.1.0/src/skillseal/routing/evaluator.py +268 -0
  25. skillseal-0.1.0/src/skillseal/routing/runner.py +92 -0
  26. skillseal-0.1.0/src/skillseal/rules/__init__.py +1 -0
  27. skillseal-0.1.0/src/skillseal/rules/base.py +115 -0
  28. skillseal-0.1.0/src/skillseal/rules/metadata.py +172 -0
  29. skillseal-0.1.0/src/skillseal/rules/portability.py +121 -0
  30. skillseal-0.1.0/src/skillseal/rules/quality.py +193 -0
  31. skillseal-0.1.0/src/skillseal/rules/security.py +177 -0
  32. skillseal-0.1.0/src/skillseal/scoring.py +67 -0
  33. skillseal-0.1.0/tests/conftest.py +35 -0
  34. skillseal-0.1.0/tests/test_cli.py +70 -0
  35. skillseal-0.1.0/tests/test_json_reporter.py +58 -0
  36. skillseal-0.1.0/tests/test_parser.py +87 -0
  37. skillseal-0.1.0/tests/test_routing_config.py +39 -0
  38. skillseal-0.1.0/tests/test_routing_heuristic.py +52 -0
  39. skillseal-0.1.0/tests/test_rules_ids.py +8 -0
  40. skillseal-0.1.0/tests/test_rules_metadata.py +76 -0
  41. skillseal-0.1.0/tests/test_rules_portability.py +40 -0
  42. skillseal-0.1.0/tests/test_rules_quality.py +64 -0
  43. skillseal-0.1.0/tests/test_rules_security.py +67 -0
  44. skillseal-0.1.0/tests/test_scoring.py +51 -0
  45. skillseal-0.1.0/uv.lock +607 -0
@@ -0,0 +1,11 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "pip"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+
8
+ - package-ecosystem: "github-actions"
9
+ directory: "/"
10
+ schedule:
11
+ interval: "weekly"
@@ -0,0 +1,56 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ test:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
16
+
17
+ - name: Install uv
18
+ uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3.2.4
19
+ with:
20
+ version: "latest"
21
+
22
+ - name: Install dependencies
23
+ run: uv sync
24
+
25
+ - name: Lint (ruff)
26
+ run: uv run ruff check .
27
+
28
+ - name: Type check (mypy)
29
+ run: uv run mypy src
30
+
31
+ - name: Unit tests
32
+ run: uv run pytest -q
33
+
34
+ # Smoke-test the tool against its own examples. good-skill is expected to
35
+ # pass both gates; bad-skill is deliberately broken and expected to fail
36
+ # both, so the pipeline asserts that failure rather than requiring it to
37
+ # (accidentally) pass.
38
+ - name: Check good-skill example (expected to pass)
39
+ run: uv run skillseal check examples/good-skill --fail-on error
40
+
41
+ - name: Check bad-skill example (expected to fail the gate)
42
+ run: |
43
+ if uv run skillseal check examples/bad-skill --fail-on error; then
44
+ echo "expected examples/bad-skill to fail the --fail-on error gate" >&2
45
+ exit 1
46
+ fi
47
+
48
+ - name: Routing tests for good-skill example (expected to pass)
49
+ run: uv run skillseal test examples/good-skill
50
+
51
+ - name: Routing tests for bad-skill example (expected to fail the threshold)
52
+ run: |
53
+ if uv run skillseal test examples/bad-skill; then
54
+ echo "expected examples/bad-skill routing tests to fail the threshold" >&2
55
+ exit 1
56
+ fi
@@ -0,0 +1,56 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*.*.*"
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ build:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
16
+
17
+ - name: Install uv
18
+ uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3.2.4
19
+ with:
20
+ version: "latest"
21
+
22
+ - name: Verify pyproject version matches the tag
23
+ run: |
24
+ tag="${GITHUB_REF_NAME#v}"
25
+ version=$(uv run python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
26
+ if [ "$tag" != "$version" ]; then
27
+ echo "Tag v$tag does not match pyproject.toml version $version" >&2
28
+ exit 1
29
+ fi
30
+
31
+ - name: Build sdist and wheel
32
+ run: uv build
33
+
34
+ - name: Upload dist
35
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
36
+ with:
37
+ name: dist
38
+ path: dist/
39
+
40
+ publish:
41
+ needs: build
42
+ runs-on: ubuntu-latest
43
+ environment:
44
+ name: pypi
45
+ url: https://pypi.org/project/skillseal/
46
+ permissions:
47
+ id-token: write # required for PyPI trusted publishing (OIDC), no token/secret needed
48
+ steps:
49
+ - name: Download dist
50
+ uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
51
+ with:
52
+ name: dist
53
+ path: dist/
54
+
55
+ - name: Publish to PyPI
56
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
@@ -0,0 +1,11 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ .env
5
+ .ruff_cache/
6
+ .mypy_cache/
7
+ .pytest_cache/
8
+ *.egg-info/
9
+ dist/
10
+ build/
11
+ .DS_Store
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pablo Espinel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,302 @@
1
+ Metadata-Version: 2.5
2
+ Name: skillseal
3
+ Version: 0.1.0
4
+ Summary: Lint, score, and routing-test Agent Skills (SKILL.md) before your agents do.
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.12
8
+ Requires-Dist: pydantic>=2.6
9
+ Requires-Dist: pyyaml>=6.0
10
+ Requires-Dist: rich>=13.7
11
+ Requires-Dist: typer>=0.12
12
+ Description-Content-Type: text/markdown
13
+
14
+ # SkillSeal
15
+
16
+ **Test your Agent Skills before your agents do.**
17
+
18
+ `SKILL.md` files can be syntactically valid and still be bad: a vague
19
+ description that never routes correctly, an oversized file that eats context,
20
+ a `curl | sh` buried in a code block, a hardcoded `/Users/you/...` path that
21
+ only works on your machine. None of that shows up until an agent picks the
22
+ wrong skill, or picks the right one and runs something it shouldn't.
23
+
24
+ SkillSeal is a local-first, offline-first CLI that lints, scores, and
25
+ routing-tests `SKILL.md` files, so you catch that before an agent does. It's
26
+ deliberately scoped to what's useful today: static linting across four
27
+ categories, deterministic (LLM-optional) routing tests, and CI-friendly exit
28
+ codes and JSON output. No dashboard, no registry, no cloud — see
29
+ [Roadmap](#roadmap) for what's intentionally not here yet.
30
+
31
+ ## Installation
32
+
33
+ Requires Python 3.12+ and [uv](https://docs.astral.sh/uv/).
34
+
35
+ ```bash
36
+ git clone <this-repo>
37
+ cd skillseal
38
+ uv sync
39
+ ```
40
+
41
+ Run it directly with `uv run skillseal ...`, or install it as a tool:
42
+
43
+ ```bash
44
+ uv tool install .
45
+ skillseal --help
46
+ ```
47
+
48
+ ## Quickstart
49
+
50
+ ```bash
51
+ uv run skillseal check examples
52
+ uv run skillseal test examples
53
+ ```
54
+
55
+ Both commands accept a path to a single `SKILL.md` file, a single skill
56
+ directory, or a directory containing many skills (searched recursively).
57
+
58
+ ## Commands
59
+
60
+ ### `skillseal check <path>`
61
+
62
+ Runs every rule (SPECIFICATION, QUALITY, SECURITY, PORTABILITY) against each
63
+ discovered skill and prints a per-skill report with a 0-100 score.
64
+
65
+ | Flag | Default | Meaning |
66
+ |---|---|---|
67
+ | `--format terminal\|json` | `terminal` | Output format. |
68
+ | `--fail-on warning\|error` | `error` | Minimum finding severity that fails the gate. |
69
+
70
+ ### `skillseal test <path>`
71
+
72
+ Runs the routing test cases declared in each skill's `skillseal.yaml`
73
+ against a `RoutingEvaluator`, and reports accuracy against `should_trigger`
74
+ and `should_not_trigger` prompts. Skills without a `skillseal.yaml` are
75
+ skipped, not failed.
76
+
77
+ | Flag | Default | Meaning |
78
+ |---|---|---|
79
+ | `--threshold <float>` | `0.9` | Minimum accuracy per skill to pass the gate. |
80
+ | `--format terminal\|json` | `terminal` | Output format. |
81
+ | `--provider heuristic\|llm` | `heuristic` | Evaluator to use (see below). |
82
+
83
+ ### Exit codes (both commands)
84
+
85
+ | Code | Meaning |
86
+ |---|---|
87
+ | `0` | Clean, or the gate passed. |
88
+ | `1` | Gate failed (`--fail-on` / `--threshold` not met). |
89
+ | `2` | Usage or config error — bad path, no `SKILL.md` found, malformed `skillseal.yaml`. |
90
+
91
+ A typo'd path can never silently report success: exit `2` is reserved for
92
+ "SkillSeal couldn't even run the check," distinct from "the check ran and
93
+ found problems" (exit `1`).
94
+
95
+ ## Example output
96
+
97
+ ```
98
+ $ uv run skillseal check examples/bad-skill
99
+
100
+ examples/bad-skill/SKILL.md
101
+
102
+ Specification WARN
103
+ Quality WARN
104
+ Security FAIL
105
+ Portability WARN
106
+
107
+ Issues
108
+
109
+ WARN name-directory-mismatch
110
+ Frontmatter 'name' does not match the skill's directory name.
111
+ name: 'helper', directory: 'bad-skill'
112
+
113
+ WARN description-too-vague
114
+ Description may not provide enough information for reliable routing.
115
+ matched vague phrase: "helps with tasks"
116
+
117
+ FAIL rm-rf
118
+ Potential risk: recursive force-delete command found in a code block.
119
+ 1 occurrence(s), e.g. "rm -rf"
120
+
121
+ FAIL pipe-to-shell
122
+ Potential risk: downloads remote content and pipes it directly into a shell.
123
+ 1 occurrence(s), e.g. "curl https://example.com/install.sh | sh"
124
+
125
+ WARN absolute-path
126
+ Skill assumes absolute filesystem paths, which won't exist on other machines.
127
+ /Users/someone/projects/output, /Users/someone/projects/output/tmp
128
+
129
+ ... (more findings omitted for brevity — run it yourself to see the rest)
130
+
131
+ SkillSeal Score: 68/100
132
+
133
+ Specification 90
134
+ Quality 60
135
+ Security 40
136
+ Portability 90
137
+ ```
138
+
139
+ ```
140
+ $ uv run skillseal test examples/bad-skill
141
+
142
+ helper
143
+
144
+ Should trigger 5/5
145
+ Should NOT trigger 5/7
146
+
147
+ Accuracy 83.3%
148
+
149
+ Failures:
150
+
151
+ ✗ "Help me write a poem"
152
+ Expected: NOT TRIGGER
153
+ Actual: TRIGGER
154
+ Likely reason:
155
+ Matched terms: help
156
+ ```
157
+
158
+ ## `skillseal.yaml` format
159
+
160
+ Place a `skillseal.yaml` next to a `SKILL.md` to define its routing tests:
161
+
162
+ ```yaml
163
+ version: 1
164
+
165
+ routing:
166
+ should_trigger:
167
+ - "Review this payment implementation"
168
+ - "Check whether this Stripe integration is secure"
169
+
170
+ should_not_trigger:
171
+ - "Write a React button"
172
+ - "Explain Kubernetes"
173
+ ```
174
+
175
+ - A missing `skillseal.yaml` means that skill is **skipped**, not failed.
176
+ - Empty `should_trigger`/`should_not_trigger` lists are skipped too (no 0/0
177
+ false pass or divide-by-zero).
178
+ - Malformed YAML is a usage error (exit `2`), not a crash.
179
+
180
+ ## Using it in CI
181
+
182
+ ```yaml
183
+ - name: Check Agent Skills
184
+ run: uv run skillseal check ./skills --fail-on error
185
+
186
+ - name: Test Agent Skill Routing
187
+ run: uv run skillseal test ./skills
188
+ ```
189
+
190
+ This repo's own [`.github/workflows/ci.yml`](.github/workflows/ci.yml) does
191
+ the same against `examples/`, plus lint/type-check/unit tests.
192
+
193
+ ## Releasing
194
+
195
+ Publishing to PyPI is automated via
196
+ [`.github/workflows/release.yml`](.github/workflows/release.yml) using
197
+ [PyPI Trusted Publishing](https://docs.pypi.org/trusted-publishers/) — no
198
+ API token stored anywhere.
199
+
200
+ 1. Bump `version` in `pyproject.toml`.
201
+ 2. Commit, then tag: `git tag vX.Y.Z && git push origin vX.Y.Z`.
202
+ 3. The workflow verifies the tag matches `pyproject.toml`, builds the sdist
203
+ and wheel, and publishes to PyPI via OIDC.
204
+
205
+ ## The score
206
+
207
+ Deterministic, no LLM involved. Each of the four categories starts at 100 and
208
+ loses points per finding:
209
+
210
+ | Severity | Deduction |
211
+ |---|---|
212
+ | `ERROR` | -25 |
213
+ | `WARNING` | -10 |
214
+ | `INFO` | -0 |
215
+
216
+ `INFO` findings (like "requires docker") are purely descriptive — declaring a
217
+ real dependency isn't a defect, so it doesn't cost points. Rules aggregate
218
+ repeated occurrences of the *same* issue into one finding with a count, so a
219
+ long file can't rack up an artificially low score just from file size.
220
+
221
+ The total is a weighted sum of the four category scores:
222
+
223
+ | Category | Weight | Why |
224
+ |---|---|---|
225
+ | Specification | 30% | Broken/missing metadata breaks loading and routing outright. |
226
+ | Quality | 30% | Vague or bloated instructions are the main cause of routing failures. |
227
+ | Security | 25% | Real risk, weighted close behind. |
228
+ | Portability | 15% | Declared environment dependencies are often expected, not defects. |
229
+
230
+ ## Architecture
231
+
232
+ ```
233
+ src/skillseal/
234
+ ├── models.py # pydantic models: Skill, Finding, SkillReport, routing models
235
+ ├── parser.py # discover_skills(), parse_skill() — never raises on bad YAML
236
+ ├── linter.py # ties parser + rules + scoring together
237
+ ├── scoring.py # deterministic 0-100 scoring
238
+ ├── rules/
239
+ │ ├── base.py # Rule protocol, FuncRule, registry, text helpers
240
+ │ ├── metadata.py # SPECIFICATION rules
241
+ │ ├── quality.py # QUALITY rules
242
+ │ ├── security.py # SECURITY rules
243
+ │ └── portability.py # PORTABILITY rules
244
+ ├── routing/
245
+ │ ├── evaluator.py # HeuristicRoutingEvaluator, LLMRoutingEvaluator, LLMProvider
246
+ │ └── runner.py # loads skillseal.yaml, runs cases
247
+ ├── reporters/
248
+ │ ├── terminal.py # Rich terminal output
249
+ │ └── json_reporter.py # stable JSON schema
250
+ └── cli.py # typer app: check, test
251
+ ```
252
+
253
+ A `Rule` is `id`, `category`, `severity`, `description`, and
254
+ `check(skill) -> list[Finding]`. Most rules are built with `FuncRule`, which
255
+ wraps a plain function so adding a check doesn't require a new class.
256
+
257
+ Routing evaluation is behind a `RoutingEvaluator` protocol with two
258
+ implementations:
259
+
260
+ - **`HeuristicRoutingEvaluator`** (default): fully offline, no API key needed.
261
+ Scores how much of a prompt's distinctive vocabulary (after stopword
262
+ removal and light suffix stripping) is covered by the skill's own name,
263
+ description, and `keywords:`. It's deliberately simple — not real NLP —
264
+ which is also why it's fast, free, and explainable ("Matched terms: ...").
265
+ - **`LLMRoutingEvaluator`**: delegates the trigger/no-trigger decision to an
266
+ `LLMProvider` (`complete(prompt) -> str`). `OpenAICompatibleProvider`
267
+ implements this against any OpenAI-compatible `/chat/completions` endpoint,
268
+ configured via `SKILLSEAL_BASE_URL`, `SKILLSEAL_API_KEY`, and
269
+ `SKILLSEAL_MODEL`. Use `--provider llm` to opt in — it's never required.
270
+
271
+ ## Limitations
272
+
273
+ - Rules are regex/heuristic-based, not a real parser or NLP model — they will
274
+ have false positives and false negatives. Findings are phrased as
275
+ *potential* risk, never certainty.
276
+ - The heuristic routing evaluator uses simple tokenization and suffix
277
+ stripping, not real stemming or embeddings; words like "secure" and
278
+ "security" won't match each other.
279
+ - Token counts are a rough `len(text) // 4` estimate, not a real tokenizer.
280
+ - No sandboxing or dynamic execution — nothing in a skill is ever run.
281
+ - No compatibility testing against real agents (Claude Code, Codex, Gemini,
282
+ etc.) — see the roadmap.
283
+
284
+ ## Roadmap
285
+
286
+ Documented, not implemented, on purpose — this is an MVP:
287
+
288
+ - Real execution against Claude Code, Codex, Gemini, and other agents
289
+ - A compatibility matrix across agents/environments
290
+ - Sandboxed dynamic analysis of skill-invoked commands
291
+ - Auto-fix for common findings
292
+ - Version-to-version comparison for a skill
293
+ - A GitHub App
294
+ - A web dashboard
295
+ - A skill registry / marketplace
296
+ - A hosted/cloud service
297
+ - Telemetry
298
+ - Skill certification
299
+
300
+ ## License
301
+
302
+ [MIT](LICENSE)