Skip to content

Commit ec605c3

Browse files
chore: bump skillsaw to 0.16.0 and clear new strict warnings (#139)
1 parent c4d1647 commit ec605c3

26 files changed

Lines changed: 200 additions & 210 deletions

.coderabbit.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ reviews:
4949
Scripts are executable code that AI agents can run.
5050
- Must be self-contained or clearly document dependencies.
5151
- Should include helpful error messages.
52-
- Should handle edge cases gracefully.
52+
- Should handle edge cases by exiting non-zero with an actionable message rather than crashing with a stack trace.
5353
- Check for security issues (command injection, credential leaks).
5454
5555
- path: "plugins/auth0/.claude-plugin/**"

.github/workflows/skillsaw.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
timeout-minutes: 10
2020
env:
2121
UV_PYTHON: "3.12"
22-
SKILLSAW_VERSION: "0.4.3"
22+
SKILLSAW_VERSION: "0.16.0"
2323
steps:
2424
- name: Checkout
2525
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0

.skillsaw.yaml

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# skillsaw configuration
22
# See https://github.com/stbenjam/skillsaw for more information
33

4+
# Pin to the skillsaw version this config targets so rules added in later
5+
# versions are enabled (see CI: .github/workflows/skillsaw.yml).
6+
version: "0.16.0"
7+
48
# Enable/disable rules with configurable severity levels
59
rules:
610
# Plugin Structure
@@ -73,11 +77,42 @@ rules:
7377
enabled: true
7478
severity: error
7579

76-
# Custom: Enforce openclaw metadata (emoji, homepage) in SKILL.md frontmatter
80+
# Custom: presence check only — require the openclaw block plus emoji and
81+
# homepage in SKILL.md frontmatter. Field *shape* validation (os, requires,
82+
# install, ...) is delegated to the built-in openclaw-metadata rule below, so
83+
# this rule intentionally does not re-validate those fields.
7784
skill-openclaw-metadata:
7885
enabled: true
7986
severity: error
8087

88+
# Built-in: validate the shape of every metadata.openclaw field (os values,
89+
# requires/install structure, install kinds, archive types, ...). Ships with
90+
# skillsaw and defaults to warning; promote to error so a malformed field
91+
# fails CI the same way the presence check above does.
92+
openclaw-metadata:
93+
enabled: true
94+
severity: error
95+
96+
# This repo's single skill is a router: SKILL.md is a detection/dispatch table
97+
# (frameworks, intents, tooling) locked down by scripts/check_routing_evals.py
98+
# and check_router_reachability.py, so its token budget is intentionally large
99+
# and can't be trimmed without dropping routes. Raise the skill body warn limit
100+
# to 4000 (still catches real bloat) while keeping the 6000 error ceiling. The
101+
# skill-description budget (200) is unchanged and enforced separately.
102+
context-budget:
103+
limits:
104+
skill:
105+
warn: 4000
106+
error: 6000
107+
108+
# validate-skill.sh is a CI-only structural gate (invoked from
109+
# .github/workflows/skillsaw.yml), not agent-facing behavior the router should
110+
# point at — same class as the rule's built-in tests/ exemption. Exempt it so
111+
# it isn't flagged as an unreferenced/shadow file.
112+
agentskill-unreferenced-files:
113+
exclude:
114+
- "scripts/validate-skill.sh"
115+
81116
# Custom rules for marketplace-specific validation
82117
custom-rules:
83118
- ./.skillsaw/rules.py

.skillsaw/rules.py

Lines changed: 30 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -486,24 +486,22 @@ def _parse_frontmatter(skill_md: Path) -> Optional[Dict[str, Any]]:
486486

487487
class SkillOpenclawMetadataRule(Rule):
488488
"""
489-
Enforce that SKILL.md frontmatter includes metadata.openclaw with required fields.
489+
Enforce that SKILL.md frontmatter declares the required metadata.openclaw block.
490490
491-
All skills must declare openclaw metadata for marketplace compatibility:
492-
- metadata.openclaw.emoji (string, non-empty)
493-
- metadata.openclaw.homepage (string, valid URL)
491+
This rule is a *presence* check only: it guarantees every skill ships the
492+
block plus the two fields we require for marketplace compatibility:
493+
- metadata.openclaw.emoji (present, non-empty string)
494+
- metadata.openclaw.homepage (present, valid URL)
494495
495-
Optional openclaw fields (not enforced but validated if present):
496-
- metadata.openclaw.requires.bins (list of binary names)
497-
- metadata.openclaw.os (list of OS identifiers, e.g., "darwin", "linux")
498-
- metadata.openclaw.install (list of install definitions)
496+
The *shape* of every other openclaw field (os, requires, install, ...) is
497+
validated by skillsaw's built-in ``openclaw-metadata`` rule, enabled at
498+
error severity in ``.skillsaw.yaml``. Keep this rule limited to presence so
499+
the two do not duplicate (or contradict) each other.
499500
"""
500501

501502
# Simple URL pattern: must start with https://
502503
URL_RE = re.compile(r'^https?://\S+$')
503504

504-
# Valid OS identifiers
505-
VALID_OS = {'darwin', 'linux', 'windows'}
506-
507505
@property
508506
def rule_id(self) -> str:
509507
return "skill-openclaw-metadata"
@@ -554,134 +552,35 @@ def check(self, context: RepositoryContext) -> List[RuleViolation]:
554552
)
555553
continue
556554

557-
# Validate required fields
558-
violations.extend(self._check_required_fields(openclaw, skill_md))
559-
560-
# Validate optional fields if present
561-
violations.extend(self._check_optional_fields(openclaw, skill_md))
562-
563-
return violations
564-
565-
def _check_required_fields(self, openclaw: Dict[str, Any], skill_md: Path) -> List[RuleViolation]:
566-
"""Check that required openclaw fields are present and valid."""
567-
violations = []
568-
569-
# emoji: must be a non-empty string
570-
emoji = openclaw.get('emoji')
571-
if not emoji or not isinstance(emoji, str) or not emoji.strip():
572-
violations.append(
573-
self.violation(
574-
"metadata.openclaw.emoji is missing or empty. "
575-
"Add an emoji identifier, e.g., 'emoji: \"\\U0001F510\"'.",
576-
file_path=skill_md
577-
)
578-
)
579-
580-
# homepage: must be a valid URL
581-
homepage = openclaw.get('homepage')
582-
if not homepage or not isinstance(homepage, str):
583-
violations.append(
584-
self.violation(
585-
"metadata.openclaw.homepage is missing. "
586-
"Add a homepage URL, e.g., 'homepage: https://github.com/auth0/agent-skills'.",
587-
file_path=skill_md
588-
)
589-
)
590-
elif not self.URL_RE.match(homepage):
591-
violations.append(
592-
self.violation(
593-
f"metadata.openclaw.homepage '{homepage}' is not a valid URL. "
594-
"Must start with http:// or https://.",
595-
file_path=skill_md
596-
)
597-
)
598-
599-
return violations
600-
601-
def _check_optional_fields(self, openclaw: Dict[str, Any], skill_md: Path) -> List[RuleViolation]:
602-
"""Validate optional openclaw fields if they are present."""
603-
violations = []
604-
605-
# requires.bins: if present, must be a list of non-empty strings
606-
requires = openclaw.get('requires')
607-
if requires is not None:
608-
if not isinstance(requires, dict):
609-
violations.append(
610-
self.violation(
611-
"metadata.openclaw.requires must be a mapping. "
612-
"Example: 'requires:\\n bins:\\n - auth0'.",
613-
file_path=skill_md
614-
)
615-
)
616-
else:
617-
bins = requires.get('bins')
618-
if bins is not None:
619-
if not isinstance(bins, list) or not all(
620-
isinstance(b, str) and b.strip() for b in bins
621-
):
622-
violations.append(
623-
self.violation(
624-
"metadata.openclaw.requires.bins must be a list of non-empty strings. "
625-
"Example: 'bins:\\n - auth0'.",
626-
file_path=skill_md
627-
)
628-
)
629-
630-
# os: if present, must be a list of valid OS identifiers
631-
os_list = openclaw.get('os')
632-
if os_list is not None:
633-
if not isinstance(os_list, list) or not os_list:
634-
violations.append(
635-
self.violation(
636-
"metadata.openclaw.os must be a non-empty list. "
637-
f"Valid values: {', '.join(sorted(self.VALID_OS))}.",
638-
file_path=skill_md
639-
)
640-
)
641-
else:
642-
invalid = [o for o in os_list if o not in self.VALID_OS]
643-
if invalid:
555+
# emoji: must be a non-empty string
556+
emoji = openclaw.get('emoji')
557+
if not emoji or not isinstance(emoji, str) or not emoji.strip():
644558
violations.append(
645559
self.violation(
646-
f"metadata.openclaw.os contains invalid entries: {invalid}. "
647-
f"Valid values: {', '.join(sorted(self.VALID_OS))}.",
560+
"metadata.openclaw.emoji is missing or empty. "
561+
"Add an emoji identifier, e.g., 'emoji: \"\\U0001F510\"'.",
648562
file_path=skill_md
649563
)
650564
)
651565

652-
# install: if present, must be a list of dicts with required keys
653-
install = openclaw.get('install')
654-
if install is not None:
655-
if not isinstance(install, list):
656-
violations.append(
657-
self.violation(
658-
"metadata.openclaw.install must be a list of install definitions.",
659-
file_path=skill_md
660-
)
661-
)
662-
else:
663-
for i, entry in enumerate(install):
664-
if not isinstance(entry, dict):
665-
violations.append(
666-
self.violation(
667-
f"metadata.openclaw.install[{i}] must be a mapping with "
668-
"'id', 'kind', 'formula', 'bins', and 'label' fields.",
669-
file_path=skill_md
670-
)
566+
# homepage: must be present and a valid URL
567+
homepage = openclaw.get('homepage')
568+
if not homepage or not isinstance(homepage, str):
569+
violations.append(
570+
self.violation(
571+
"metadata.openclaw.homepage is missing. "
572+
"Add a homepage URL, e.g., 'homepage: https://github.com/auth0/agent-skills'.",
573+
file_path=skill_md
671574
)
672-
continue
673-
missing = [
674-
k for k in ('id', 'kind', 'formula', 'bins', 'label')
675-
if not entry.get(k)
676-
]
677-
if missing:
678-
violations.append(
679-
self.violation(
680-
f"metadata.openclaw.install[{i}] is missing required fields: "
681-
f"{', '.join(missing)}.",
682-
file_path=skill_md
683-
)
575+
)
576+
elif not self.URL_RE.match(homepage):
577+
violations.append(
578+
self.violation(
579+
f"metadata.openclaw.homepage '{homepage}' is not a valid URL. "
580+
"Must start with http:// or https://.",
581+
file_path=skill_md
684582
)
583+
)
685584

686585
return violations
687586

.snyk-agent-scan-ignore.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,19 @@
1111
},
1212
{
1313
"code": "W012",
14-
"skills": ["auth0-swift"],
14+
"skills": ["auth0"],
1515
"url": "https://github.com/auth0/Auth0.swift",
1616
"reason": "First-party Auth0 Swift SDK repository"
1717
},
1818
{
1919
"code": "W012",
20-
"skills": ["acul-screen-generator"],
20+
"skills": ["auth0"],
2121
"url": "https://github.com/auth0-samples/auth0-acul-samples/tree/main/react/src/screens/<screen-name>",
2222
"reason": "First-party Auth0 samples repository for ACUL screen examples"
2323
},
2424
{
2525
"code": "W012",
26-
"skills": ["acul-screen-generator"],
26+
"skills": ["auth0"],
2727
"url": "https://github.com/auth0/universal-login/blob/master/packages/auth0-acul-react/examples/<screen-name>",
2828
"reason": "First-party Auth0 universal-login repository"
2929
},
@@ -34,13 +34,13 @@
3434
},
3535
{
3636
"code": "W012",
37-
"skills": ["auth0-branding"],
37+
"skills": ["auth0"],
3838
"url_pattern": "https://api\\.brandfetch\\.io/v2/brands/",
3939
"reason": "Brandfetch API is used to extract brand tokens (colors, logos, fonts) for theming Auth0 Universal Login"
4040
},
4141
{
4242
"code": "W012",
43-
"skills": ["auth0-custom-domains"],
43+
"skills": ["auth0"],
4444
"url": "https://mcp.cloudflare.com/mcp",
4545
"reason": "The skill sets up custom domains for Auth0 tenants, and uses Cloudflare's MCP to write the CNAME record for the custom domain."
4646
},

AGENTS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ Guidance for AI coding agents working in this repository.
55
## What this repo is
66

77
An **Agent Skill** that teaches coding assistants how to implement Auth0
8-
authentication correctly. It ships as a single Claude Code / Cursor / Copilot
8+
authentication that follows Auth0's documented SDK usage and passes this
9+
repo's routing and behavioral evals. It ships as a single Claude Code / Cursor / Copilot
910
plugin (`auth0`) containing **one** consolidated skill at
1011
`plugins/auth0/skills/auth0/`: a router `SKILL.md` over a flat pool of on-demand
1112
reference files. The deliverable is the skill itself.

CONTRIBUTING.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,13 @@ Your `SKILL.md` must include:
5151
requires: # optional: declare external dependencies
5252
bins:
5353
- auth0 # declare `auth0` if the skill runs CLI commands
54-
os: # optional: darwin, linux, windows
54+
os: # optional: darwin, linux, win32
5555
- darwin
5656
- linux
5757
install: # optional: how to install required bins
5858
- id: brew
5959
kind: brew
60-
package: auth0/auth0-cli/auth0
60+
formula: auth0/auth0-cli/auth0
6161
bins: [auth0]
6262
label: 'Install Auth0 CLI (brew)'
6363
---

0 commit comments

Comments
 (0)