Skip to content

Commit 7c50c4d

Browse files
authored
Merge pull request #7 from opendatahub-io/enforce-strict-skills-dir
Enforce skills_dir requires strict: false
2 parents 2392f77 + 0aeecfe commit 7c50c4d

5 files changed

Lines changed: 208 additions & 9 deletions

File tree

.claude-plugin/marketplace.json

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,11 @@
2323
],
2424
"name": "rfe-creator",
2525
"repository": "https://github.com/jwforres/rfe-creator",
26-
"skills": [
27-
"./.claude/skills"
28-
],
2926
"source": {
3027
"ref": "main",
3128
"repo": "jwforres/rfe-creator",
3229
"source": "github"
3330
},
34-
"strict": false,
3531
"version": "0.1.0"
3632
},
3733
{

ARCHITECTURE.md

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
# Architecture
2+
3+
## Overview
4+
5+
The skills-registry is a centralized catalog that aggregates Claude Code plugins
6+
from multiple GitHub repositories into a single discoverable marketplace. It acts
7+
as an indirection layer: plugin source code lives in separate repos, while this
8+
registry provides the metadata and discovery mechanism.
9+
10+
```
11+
skills-registry
12+
┌──────────────────────────────────────────────────────┐
13+
│ │
14+
│ registry.yaml (source of truth) │
15+
│ │ │
16+
│ ├──► marketplace.json (Claude Code native) │
17+
│ ├──► catalog.md (human-readable) │
18+
│ │ │
19+
│ schema/ (validation) │
20+
│ scripts/ (automation) │
21+
│ .github/workflows/ (CI) │
22+
│ │
23+
└──────────────────────────────────────────────────────┘
24+
```
25+
26+
## Data Flow
27+
28+
```
29+
┌─────────────────┐
30+
│ registry.yaml │ Single source of truth
31+
│ │ (edited by humans)
32+
└────────┬────────┘
33+
34+
┌───────────┼───────────┐
35+
▼ ▼ ▼
36+
sync_marketplace generate validate
37+
.py _catalog _registry
38+
.py .py
39+
│ │ │
40+
▼ ▼ ▼
41+
marketplace.json catalog.md pass/fail
42+
(Claude Code) (docs) (CI gate)
43+
```
44+
45+
## File Structure
46+
47+
```
48+
skills-registry/
49+
├── registry.yaml # Source of truth
50+
├── .claude-plugin/
51+
│ └── marketplace.json # Generated — Claude Code reads this
52+
├── catalog.md # Generated — human-readable listing
53+
├── schema/
54+
│ └── registry.schema.json # JSON Schema for registry.yaml
55+
├── scripts/
56+
│ ├── validate_registry.py # Schema + structure validation
57+
│ ├── sync_marketplace.py # registry.yaml -> marketplace.json
58+
│ ├── generate_catalog.py # registry.yaml -> catalog.md
59+
│ └── check_versions.py # Poll plugin repos for version bumps
60+
├── .github/workflows/
61+
│ └── validate.yml # CI: validate + sync check
62+
├── README.md
63+
├── CONTRIBUTING.md
64+
└── LICENSE
65+
```
66+
67+
## Plugin Model
68+
69+
Each plugin entry in `registry.yaml` points to an external GitHub repo
70+
that contains the actual skills:
71+
72+
```
73+
registry.yaml External Repos
74+
┌──────────────┐
75+
│ plugins: │
76+
│ │ ┌────────────────────────────────┐
77+
│ rfe-creator ├───────►│ jwforres/rfe-creator │
78+
│ │ │ └─ .claude/skills/ │
79+
│ │ │ ├─ rfe.create/SKILL.md │
80+
│ │ │ ├─ rfe.review/SKILL.md │
81+
│ │ │ └─ ... │
82+
│ │ └────────────────────────────────┘
83+
│ │
84+
│ │ ┌────────────────────────────────┐
85+
│ assess-rfe ├───────►│ n1hility/assess-rfe │
86+
│ (strict: │ │ ├─ .claude-plugin/ │
87+
│ true) │ │ │ └─ plugin.json │
88+
│ │ │ └─ skills/ │
89+
│ │ │ ├─ assess-rfe/SKILL.md │
90+
│ │ │ └─ export-rubric/SKILL.md │
91+
│ │ └────────────────────────────────┘
92+
└──────────────┘
93+
```
94+
95+
### Strict vs Non-Strict Plugins
96+
97+
- **strict: true** (default) — `plugin.json` in the repo is the authority
98+
for component definitions. The marketplace entry can supplement it with
99+
additional components, and both sources are merged. Claude Code
100+
auto-discovers skills in default locations (`.claude/skills/`, `skills/`).
101+
Most plugins use this mode, even those without a `plugin.json`.
102+
103+
- **strict: false** — The marketplace entry is the entire plugin definition.
104+
If the repo also has a `plugin.json` that declares components, that is a
105+
conflict and the plugin fails to load. Use `skills_dir` to point to a
106+
non-default skills location. `skills_dir` is only valid with `strict: false`.
107+
108+
Note: `skills_dir` must not be specified without `strict: false`. The schema
109+
and validation scripts enforce this constraint.
110+
111+
### Dependencies
112+
113+
Plugins can declare `depends_on: [other-plugin]` to express inter-plugin
114+
dependencies within the registry. This is registry-level metadata only —
115+
it is not propagated to `marketplace.json` (Claude Code does not support
116+
dependency resolution).
117+
118+
## CI Pipeline
119+
120+
```
121+
push/PR to main
122+
123+
124+
┌─────────────────────────────┐
125+
│ validate.yml │
126+
│ │
127+
│ 1. validate_registry.py │ Schema validation
128+
│ │ │
129+
│ 2. sync_marketplace.py │ Regenerate marketplace.json
130+
│ │ │
131+
│ 3. git diff --exit-code │ Fail if marketplace.json
132+
│ marketplace.json │ is out of sync
133+
│ │ │
134+
│ 4. generate_catalog.py │ Regenerate catalog.md
135+
│ │ │
136+
│ 5. git diff --exit-code │ Fail if catalog.md
137+
│ catalog.md │ is out of sync
138+
└─────────────────────────────┘
139+
```
140+
141+
CI does **not** auto-commit — it only verifies that the generated files
142+
match the registry. Contributors must run the scripts locally before pushing.
143+
144+
## How Claude Code Discovers Plugins
145+
146+
```
147+
Developer Claude Code GitHub
148+
│ │ │
149+
│ claude plugin marketplace │ │
150+
│ add opendatahub-io/ │ │
151+
│ skills-registry │ │
152+
│─────────────────────────────►│ │
153+
│ │ fetch marketplace.json │
154+
│ │─────────────────────────►│
155+
│ │◄─────────────────────────│
156+
│ │ │
157+
│ /plugin install │ │
158+
│ rfe-creator@opendatahub- │ │
159+
│ skills │ │
160+
│─────────────────────────────►│ clone source repo │
161+
│ │─────────────────────────►│
162+
│ │◄─────────────────────────│
163+
│ │ │
164+
│ /rfe.create │ │
165+
│─────────────────────────────►│ (runs skill locally) │
166+
│◄─────────────────────────────│ │
167+
```
168+
169+
## Adding a New Plugin
170+
171+
1. Add an entry to `registry.yaml`
172+
2. Run `python3 scripts/validate_registry.py`
173+
3. Run `python3 scripts/sync_marketplace.py`
174+
4. Run `python3 scripts/generate_catalog.py`
175+
5. Commit all changes and open a PR
176+
6. CI verifies everything is in sync

registry.yaml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,6 @@ plugins:
4646
type: github
4747
repo: jwforres/rfe-creator
4848
ref: main
49-
strict: false
50-
skills_dir: .claude/skills
5149
homepage: https://github.com/jwforres/rfe-creator
5250
repository: https://github.com/jwforres/rfe-creator
5351
skills:
@@ -188,7 +186,6 @@ plugins:
188186
type: github
189187
repo: fege/test-plan
190188
ref: main
191-
skills_dir: .claude/skills
192189
homepage: https://github.com/fege/test-plan
193190
repository: https://github.com/fege/test-plan
194191
skills:

schema/registry.schema.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@
4747
"type": "object",
4848
"required": ["name", "description", "version", "source"],
4949
"additionalProperties": false,
50+
"dependentRequired": {
51+
"skills_dir": ["strict"]
52+
},
5053
"properties": {
5154
"name": {
5255
"type": "string",
@@ -87,11 +90,11 @@
8790
"strict": {
8891
"type": "boolean",
8992
"default": true,
90-
"description": "If false, marketplace defines the plugin (no plugin.json required in repo)"
93+
"description": "If false, marketplace is the entire plugin definition. If true (default), plugin.json in the repo is authoritative and marketplace supplements it. Setting strict: false when the repo also has a plugin.json with components causes a conflict"
9194
},
9295
"skills_dir": {
9396
"type": "string",
94-
"description": "Path to skills directory in the repo (used when strict: false)"
97+
"description": "Path to skills directory in the repo. Only valid when strict: false (marketplace defines the plugin). When strict: true, Claude Code auto-discovers skills in default locations"
9598
},
9699
"homepage": {
97100
"type": "string",

scripts/validate_registry.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,22 @@ def check_categories(registry: dict) -> list[str]:
7070
return errors
7171

7272

73+
def check_strict_consistency(registry: dict) -> list[str]:
74+
"""Check that skills_dir is only used with strict: false."""
75+
errors = []
76+
for plugin in registry.get("plugins", []):
77+
name = plugin.get("name", "<unknown>")
78+
has_skills_dir = "skills_dir" in plugin
79+
strict = plugin.get("strict", True)
80+
81+
if has_skills_dir and strict is not False:
82+
errors.append(
83+
f" Plugin '{name}': skills_dir requires strict: false. "
84+
f"Remove skills_dir or set strict: false"
85+
)
86+
return errors
87+
88+
7389
def check_sources(registry: dict) -> list[str]:
7490
"""Check that GitHub repos are accessible via the GitHub API."""
7591
errors = []
@@ -219,6 +235,17 @@ def main():
219235
else:
220236
print(" OK")
221237

238+
# Strict/skills_dir consistency
239+
print("Checking strict/skills_dir consistency...")
240+
errors = check_strict_consistency(registry)
241+
all_errors.extend(errors)
242+
if errors:
243+
print(f" FAIL: {len(errors)} consistency error(s)")
244+
for e in errors:
245+
print(e)
246+
else:
247+
print(" OK")
248+
222249
# Source accessibility
223250
if args.check_sources:
224251
print("Checking sources...")

0 commit comments

Comments
 (0)