mirror of
https://github.com/alirezarezvani/claude-skills.git
synced 2026-08-28 04:24:58 +00:00
Issue #686 was the second round of the same Claude Code path-validator tightening: v2.1.107 rejected bare "./" (fixed in #539 by moving to "./skills"), then v2.1.133 also rejected "./skills". The validator that codified the #539 fix was still recommending "./skills" verbatim — so a future round 3 would have hit the same trap. This commit makes the validator catch the regression and runs it in CI: - scripts/check_plugin_json.py - Reject any "skills" string starting with "./" (catches both "./skills" and "./skills/sub" patterns) - Update docstring + error message to point at the layout-correct forms instead of the now-broken "./skills" - Recognize "source" and "attribution" as approved extension fields (already documented in CLAUDE.md but not in the validator), so the 21 pre-existing false-positives go away and CI can run blocking - Drop the "./" rejection inside arrays — CLAUDE.md says ["./"] is the correct single-skill-at-root form - .github/workflows/ci-quality-gate.yml - Add blocking "Validate plugin.json manifests" step that runs the validator on every PR - CLAUDE.md - Add an Enforcement note pointing at the validator and the lockstep rule: when CC tightens its path validator again, update validator rules and CLAUDE.md together Verified: 69/69 manifests pass; 6-case smoke test confirms validator rejects all three known-broken forms ("./skills", "./", "./skills/sub") and accepts all three documented-valid forms ("skills", ["./"], explicit array). Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
daa88bb299
commit
f7bb1f86bc
3 changed files with 28 additions and 7 deletions
4
.github/workflows/ci-quality-gate.yml
vendored
4
.github/workflows/ci-quality-gate.yml
vendored
|
|
@ -82,6 +82,10 @@ jobs:
|
|||
engineering-team ra-qm-team engineering \
|
||||
business-growth finance project-management scripts
|
||||
|
||||
- name: Validate plugin.json manifests (blocking — guards #539 + #686)
|
||||
run: |
|
||||
python scripts/check_plugin_json.py --all
|
||||
|
||||
- name: Run test suite
|
||||
run: |
|
||||
python -m pytest tests/ --tb=short -q
|
||||
|
|
|
|||
|
|
@ -337,6 +337,8 @@ This repository publishes skills to **ClawHub** (clawhub.com) as the distributio
|
|||
- Single-skill plugin (SKILL.md at root): `"skills": ["./"]` (array form required).
|
||||
- Plugin with `skills/` subdir: `"skills": "skills"` (no `./` prefix — see issue #686).
|
||||
- Multi-skill domain plugin (skills are subfolders at root): `"skills": ["./sub1", "./sub2", ...]` (explicit list, omit `"./"` to avoid namespace collision with the index SKILL.md).
|
||||
|
||||
**Enforcement:** `scripts/check_plugin_json.py --all` runs in `ci-quality-gate.yml` on every PR and blocks merge on any violation. It actively rejects the `"./"` (issue #539) and `"./skills"` (issue #686) regressions. When CC tightens its path validator again in the future, update both the validator's `_check_skills_string` rules and this section together — they must move in lockstep.
|
||||
6. **Version follows repo versioning.** ClawHub package versions must match the repo release version (currently v2.7.0+).
|
||||
|
||||
## Anti-Patterns to Avoid
|
||||
|
|
|
|||
|
|
@ -1,11 +1,22 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Validate plugin.json files against the strict ClawHub schema.
|
||||
|
||||
Required fields (exactly these 8, no others):
|
||||
Required fields (exactly these 8):
|
||||
name, description, version, author{name,url}, homepage, repository, license, skills
|
||||
|
||||
skills: must be either a string ("./skills") or an array of relative paths.
|
||||
The bare "./" form is REJECTED (Claude Code v2.1.107+ rejects it).
|
||||
Two approved extension fields (documented in CLAUDE.md, stripped at ClawHub-publish):
|
||||
source, attribution
|
||||
|
||||
skills layouts (Claude Code tightens its path validator regularly — be explicit):
|
||||
- Single-skill plugin (SKILL.md at root): "skills": ["./"] (array form)
|
||||
- Plugin with skills/ subdir: "skills": "skills" (NO "./" prefix — #686)
|
||||
- Multi-skill domain plugin (subfolders at root):
|
||||
"skills": ["./sub1", "./sub2", ...]
|
||||
|
||||
REJECTED forms and why:
|
||||
- "skills": "./" — Claude Code v2.1.107+ rejects ("Path escapes plugin directory")
|
||||
- "skills": "./skills" — Claude Code v2.1.133+ rejects (issue #686)
|
||||
- Any string starting with "./" (lifted out of array context)
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
|
|
@ -15,6 +26,7 @@ import sys
|
|||
|
||||
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
ALLOWED = {"name", "description", "version", "author", "homepage", "repository", "license", "skills"}
|
||||
APPROVED_EXTENSIONS = {"source", "attribution"}
|
||||
STRING_FIELDS = ("name", "description", "homepage", "repository", "license")
|
||||
SEMVER = re.compile(r"^\d+\.\d+\.\d+(?:-[\w.]+)?$")
|
||||
|
||||
|
|
@ -22,7 +34,7 @@ SEMVER = re.compile(r"^\d+\.\d+\.\d+(?:-[\w.]+)?$")
|
|||
def _check_keys(data):
|
||||
keys = set(data.keys())
|
||||
errors = []
|
||||
extra = keys - ALLOWED
|
||||
extra = keys - ALLOWED - APPROVED_EXTENSIONS
|
||||
missing = ALLOWED - keys
|
||||
if extra:
|
||||
errors.append(f"extra fields: {sorted(extra)}")
|
||||
|
|
@ -63,7 +75,12 @@ def _check_author(data):
|
|||
|
||||
def _check_skills_string(s):
|
||||
if s in ("./", ""):
|
||||
return ['skills: "./" is rejected by Claude Code v2.1.107+; use "./skills" or an array']
|
||||
return ['skills: bare "./" is rejected by Claude Code v2.1.107+; '
|
||||
'use ["./"] (array) for single-skill at root, or "skills" for subdir layout']
|
||||
if s.startswith("./"):
|
||||
return [f'skills: {s!r} starts with "./" — Claude Code v2.1.133+ rejects this as '
|
||||
f'"Path escapes plugin directory" (issue #686). Drop the "./" prefix: '
|
||||
f'"{s[2:]}". (For single-skill plugins, use ["./"] in an array instead.)']
|
||||
return []
|
||||
|
||||
|
||||
|
|
@ -74,8 +91,6 @@ def _check_skills_array(s):
|
|||
for entry in s:
|
||||
if not isinstance(entry, str):
|
||||
errors.append(f"skills: entries must be strings, got {entry!r}")
|
||||
elif entry == "./":
|
||||
errors.append('skills: "./" is rejected by Claude Code v2.1.107+; list explicit subfolders')
|
||||
return errors
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue