Skip to content

Commit dcc7508

Browse files
committed
fixes by the scripts
1 parent 9322f47 commit dcc7508

7 files changed

Lines changed: 854 additions & 2 deletions

File tree

.claude-plugin/marketplace.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@
2323
],
2424
"name": "rfe-creator",
2525
"repository": "https://github.com/jwforres/rfe-creator",
26-
"skills": ["./.claude/skills"],
26+
"skills": [
27+
"./.claude/skills"
28+
],
2729
"source": {
2830
"ref": "main",
2931
"repo": "jwforres/rfe-creator",
@@ -69,7 +71,6 @@
6971
],
7072
"name": "test-plan",
7173
"repository": "https://github.com/fege/test-plan",
72-
"skills": ["./.claude/skills"],
7374
"source": {
7475
"ref": "main",
7576
"repo": "fege/test-plan",

catalog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ Tags: test-plan, test-cases, quality, strategy
4646
|-------|-------------|
4747
| `/test-plan.create` | Generate a test plan from a strategy |
4848
| `/test-plan.create-cases` | Generate test case files from a test plan |
49+
| test-plan.analyze.endpoints | Extract scope and API endpoints |
50+
| test-plan.analyze.risks | Determine test levels, priorities, and risks |
51+
| test-plan.analyze.infra | Identify environment and infrastructure needs |
52+
| test-plan.review | Review test plan for completeness |
4953

5054
```bash
5155
/plugin install test-plan@opendatahub-skills

schema/registry.schema.json

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"title": "Skills Registry",
4+
"description": "Schema for the opendatahub-io skills registry (registry.yaml)",
5+
"type": "object",
6+
"required": ["name", "owner", "plugins"],
7+
"additionalProperties": false,
8+
"properties": {
9+
"name": {
10+
"type": "string",
11+
"description": "Registry identifier"
12+
},
13+
"description": {
14+
"type": "string",
15+
"description": "Human-readable description of the registry"
16+
},
17+
"owner": {
18+
"type": "object",
19+
"required": ["name"],
20+
"properties": {
21+
"name": { "type": "string" },
22+
"email": { "type": "string", "format": "email" }
23+
},
24+
"additionalProperties": false
25+
},
26+
"categories": {
27+
"type": "object",
28+
"additionalProperties": {
29+
"type": "object",
30+
"required": ["name", "description"],
31+
"properties": {
32+
"name": { "type": "string" },
33+
"description": { "type": "string" }
34+
},
35+
"additionalProperties": false
36+
}
37+
},
38+
"plugins": {
39+
"type": "array",
40+
"items": {
41+
"$ref": "#/$defs/plugin"
42+
}
43+
}
44+
},
45+
"$defs": {
46+
"plugin": {
47+
"type": "object",
48+
"required": ["name", "description", "version", "source"],
49+
"additionalProperties": false,
50+
"properties": {
51+
"name": {
52+
"type": "string",
53+
"pattern": "^[a-z0-9][a-z0-9-]*$",
54+
"description": "Plugin identifier (lowercase, hyphens allowed)"
55+
},
56+
"description": {
57+
"type": "string",
58+
"description": "What this plugin does"
59+
},
60+
"version": {
61+
"type": "string",
62+
"pattern": "^\\d+\\.\\d+\\.\\d+(-[0-9A-Za-z.-]+)?(\\+[0-9A-Za-z.-]+)?$",
63+
"description": "Semantic version"
64+
},
65+
"category": {
66+
"type": "string",
67+
"description": "Category key from the categories map"
68+
},
69+
"tags": {
70+
"type": "array",
71+
"items": { "type": "string" }
72+
},
73+
"author": {
74+
"type": "object",
75+
"properties": {
76+
"name": { "type": "string" },
77+
"email": { "type": "string", "format": "email" }
78+
},
79+
"additionalProperties": false
80+
},
81+
"license": {
82+
"type": "string"
83+
},
84+
"source": {
85+
"$ref": "#/$defs/source"
86+
},
87+
"strict": {
88+
"type": "boolean",
89+
"default": true,
90+
"description": "If false, marketplace defines the plugin (no plugin.json required in repo)"
91+
},
92+
"skills_dir": {
93+
"type": "string",
94+
"description": "Path to skills directory in the repo (used when strict: false)"
95+
},
96+
"homepage": {
97+
"type": "string",
98+
"format": "uri"
99+
},
100+
"repository": {
101+
"type": "string",
102+
"format": "uri"
103+
},
104+
"skills": {
105+
"type": "array",
106+
"items": {
107+
"$ref": "#/$defs/skill"
108+
}
109+
},
110+
"harnesses": {
111+
"type": "object",
112+
"additionalProperties": {
113+
"type": "object",
114+
"additionalProperties": true
115+
}
116+
},
117+
"python": {
118+
"type": "object",
119+
"properties": {
120+
"package": { "type": "string" },
121+
"requires_python": { "type": "string" },
122+
"dependencies": {
123+
"type": "array",
124+
"items": { "type": "string" }
125+
}
126+
},
127+
"additionalProperties": false
128+
},
129+
"depends_on": {
130+
"type": "array",
131+
"items": { "type": "string" },
132+
"description": "Names of other plugins in this registry that this plugin depends on"
133+
}
134+
}
135+
},
136+
"source": {
137+
"type": "object",
138+
"required": ["type", "repo"],
139+
"additionalProperties": false,
140+
"properties": {
141+
"type": {
142+
"type": "string",
143+
"enum": ["github", "git-subdir", "npm", "local"],
144+
"description": "Source type"
145+
},
146+
"repo": {
147+
"type": "string",
148+
"description": "Repository identifier (e.g., owner/repo)"
149+
},
150+
"ref": {
151+
"type": "string",
152+
"description": "Git branch, tag, or ref"
153+
},
154+
"sha": {
155+
"type": "string",
156+
"pattern": "^[0-9a-f]{40}$",
157+
"description": "Exact commit SHA"
158+
},
159+
"path": {
160+
"type": "string",
161+
"description": "Subdirectory path (for git-subdir type)"
162+
}
163+
}
164+
},
165+
"skill": {
166+
"type": "object",
167+
"required": ["name", "description"],
168+
"additionalProperties": false,
169+
"properties": {
170+
"name": {
171+
"type": "string",
172+
"description": "Skill name (matches directory name)"
173+
},
174+
"description": {
175+
"type": "string",
176+
"description": "What this skill does"
177+
},
178+
"user-invocable": {
179+
"type": "boolean",
180+
"default": true
181+
}
182+
}
183+
}
184+
}
185+
}

scripts/check_versions.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
#!/usr/bin/env python3
2+
"""Check for version updates in registered plugin repositories.
3+
4+
For each plugin with a GitHub source, fetches the remote plugin.json
5+
and compares the version against registry.yaml. If updates are found,
6+
updates registry.yaml and regenerates marketplace.json.
7+
8+
Usage:
9+
python3 scripts/check_versions.py [--registry registry.yaml] [--dry-run]
10+
"""
11+
12+
import argparse
13+
import base64
14+
import json
15+
import shutil
16+
import subprocess
17+
import sys
18+
from urllib.parse import quote
19+
20+
import yaml
21+
22+
23+
def load_registry(path: str = "registry.yaml") -> dict:
24+
with open(path) as f:
25+
return yaml.safe_load(f)
26+
27+
28+
def save_registry(registry: dict, path: str = "registry.yaml"):
29+
with open(path, "w") as f:
30+
yaml.dump(registry, f, default_flow_style=False, sort_keys=False, allow_unicode=True)
31+
32+
33+
def fetch_remote_version(repo: str, ref: str = "main") -> str | None:
34+
"""Fetch version from remote plugin.json via GitHub API."""
35+
gh_bin = shutil.which("gh")
36+
if not gh_bin:
37+
return None
38+
result = subprocess.run(
39+
[gh_bin, "api",
40+
f"repos/{repo}/contents/.claude-plugin/plugin.json?ref={quote(ref, safe='')}",
41+
"--jq", ".content"],
42+
capture_output=True, text=True,
43+
timeout=30,
44+
)
45+
if result.returncode != 0:
46+
return None
47+
48+
try:
49+
content = base64.b64decode(result.stdout.strip()).decode()
50+
data = json.loads(content)
51+
return data.get("version")
52+
except (json.JSONDecodeError, ValueError, UnicodeDecodeError):
53+
return None
54+
55+
56+
def main():
57+
parser = argparse.ArgumentParser(description=__doc__,
58+
formatter_class=argparse.RawDescriptionHelpFormatter)
59+
parser.add_argument("--registry", default="registry.yaml")
60+
parser.add_argument("--dry-run", action="store_true",
61+
help="Show updates without modifying files")
62+
args = parser.parse_args()
63+
64+
registry = load_registry(args.registry)
65+
updates = []
66+
67+
for plugin in registry.get("plugins", []):
68+
source = plugin.get("source") or {}
69+
if source.get("type") != "github":
70+
continue
71+
72+
# Only check strict-mode plugins (they have their own plugin.json)
73+
if plugin.get("strict", True) is False:
74+
continue
75+
76+
repo = source.get("repo")
77+
if not repo:
78+
print(f" SKIP: {plugin.get('name', '<unknown>')} (missing source.repo)")
79+
continue
80+
current = plugin.get("version", "0.0.0")
81+
remote = fetch_remote_version(repo, source.get("ref", "main"))
82+
83+
name = plugin.get("name", "<unknown>")
84+
if remote is None:
85+
print(f" SKIP: {name} (could not fetch remote version)")
86+
continue
87+
88+
if remote != current:
89+
print(f" UPDATE: {name} {current} -> {remote}")
90+
updates.append((plugin, remote))
91+
else:
92+
print(f" OK: {name} {current}")
93+
94+
if not updates:
95+
print("\nAll plugins up to date.")
96+
return
97+
98+
if args.dry_run:
99+
print(f"\n{len(updates)} update(s) found (dry run, no changes made)")
100+
return
101+
102+
# Apply updates
103+
for plugin, new_version in updates:
104+
plugin["version"] = new_version
105+
106+
save_registry(registry, args.registry)
107+
print(f"\nUpdated {len(updates)} plugin(s) in {args.registry}")
108+
109+
# Regenerate marketplace.json
110+
result = subprocess.run(
111+
[sys.executable, "scripts/sync_marketplace.py", "--registry", args.registry],
112+
capture_output=True, text=True,
113+
timeout=60,
114+
)
115+
if result.returncode == 0:
116+
print(result.stdout.strip())
117+
else:
118+
print(f"WARNING: failed to regenerate marketplace.json: {result.stderr}",
119+
file=sys.stderr)
120+
sys.exit(1)
121+
122+
123+
if __name__ == "__main__":
124+
main()

0 commit comments

Comments
 (0)