@@ -486,24 +486,22 @@ def _parse_frontmatter(skill_md: Path) -> Optional[Dict[str, Any]]:
486486
487487class 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
0 commit comments