Skip to content

Commit 7467200

Browse files
committed
fix(skill): lock marketplace naming to openkb@vectify convention
The previous impl used the KB directory name as both the marketplace 'name' and the plugin 'name', and stitched together a metadata description by truncating the first skill's SKILL.md description at 200 chars (often mid-word). Lock the convention to match skills/openkb/.claude-plugin/marketplace.json from the official skill: - marketplace name: 'vectify' (always) - plugin name: 'openkb' (always) - description: fixed string, no SKILL.md content injection, no truncation Different KBs are distinguished by <owner>/<repo> URL, not manifest name. Users get one canonical install command (/plugin install openkb@vectify) regardless of which KB they're consuming. Also fix _git_owner to pass cwd=kb_dir so 'openkb --kb-dir ... skill new' run from anywhere reads the KB's git config, not the process CWD.
1 parent 439d017 commit 7467200

2 files changed

Lines changed: 49 additions & 25 deletions

File tree

openkb/marketplace.py

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,12 @@
2727
_DESCRIPTION_RE = re.compile(r"^description:\s*(.+?)\s*$", re.MULTILINE)
2828

2929

30-
def _git_owner() -> dict[str, str]:
31-
"""Read user.name and user.email from git config for the manifest owner.
30+
def _git_owner(kb_dir: Path) -> dict[str, str]:
31+
"""Read user.name and user.email from git config (run in kb_dir context).
3232
33-
Falls back to placeholders if git isn't configured — the manifest is
34-
still valid, just less helpful for marketplace listings.
33+
Falls back to placeholders if git isn't configured. ``cwd=kb_dir`` so
34+
that ``git config`` resolves the KB's local-or-walked-up settings,
35+
not the process's working directory at the time of CLI invocation.
3536
"""
3637
import subprocess
3738

@@ -40,6 +41,7 @@ def _git(key: str) -> str:
4041
result = subprocess.run(
4142
["git", "config", "--get", key],
4243
capture_output=True, text=True, timeout=2,
44+
cwd=str(kb_dir),
4345
)
4446
return result.stdout.strip()
4547
except (subprocess.SubprocessError, FileNotFoundError):
@@ -71,11 +73,6 @@ def _read_skill_description(skill_md: Path) -> str:
7173
return desc_match.group(1).strip()
7274

7375

74-
def _kb_name(kb_dir: Path) -> str:
75-
"""Use the KB directory name as the marketplace name (sluggable)."""
76-
return kb_dir.name
77-
78-
7976
def _list_skill_dirs(kb_dir: Path) -> list[Path]:
8077
"""Return skill directories under <kb>/output/skills/ that contain a SKILL.md."""
8178
skills_root = kb_dir / "output" / "skills"
@@ -91,32 +88,31 @@ def _build_manifest(kb_dir: Path) -> dict[str, Any]:
9188
skills = _list_skill_dirs(kb_dir)
9289
skill_paths = [f"./output/skills/{d.name}" for d in skills]
9390

94-
# Aggregate description for the manifest metadata
95-
name = _kb_name(kb_dir)
91+
# Fixed clean descriptions — no truncation, no SKILL.md interpolation.
92+
# Naming convention is locked to `openkb@vectify` so users get one
93+
# canonical install command regardless of which KB they're consuming;
94+
# different KBs are distinguished by <owner>/<repo> URL.
9695
metadata_desc = (
97-
f"Skills compiled from the '{name}' knowledge base via OpenKB."
96+
f"Skills compiled from the {kb_dir.name} knowledge base via OpenKB."
9897
)
99-
if skills:
100-
first_desc = _read_skill_description(skills[0] / "SKILL.md")
101-
if first_desc:
102-
metadata_desc += f" Featured: {first_desc[:200]}"
98+
plugin_desc = "Knowledge skills compiled from this OpenKB-managed knowledge base."
10399

104100
# Pull KB config for version if available; default to 0.1.0
105101
config = load_config(kb_dir / ".openkb" / "config.yaml")
106102
version = str(config.get("version", "0.1.0"))
107103

108-
owner = _git_owner()
104+
owner = _git_owner(kb_dir)
109105
return {
110-
"name": name,
106+
"name": "vectify",
111107
"owner": owner,
112108
"metadata": {
113109
"description": metadata_desc,
114110
"version": version,
115111
},
116112
"plugins": [
117113
{
118-
"name": name,
119-
"description": metadata_desc,
114+
"name": "openkb",
115+
"description": plugin_desc,
120116
"source": "./",
121117
"version": version,
122118
"author": owner,

tests/test_marketplace.py

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,8 @@ def test_regenerate_creates_manifest_with_one_skill(tmp_path):
3636

3737
manifest = json.loads((kb / ".claude-plugin" / "marketplace.json").read_text())
3838
assert manifest["plugins"][0]["skills"] == ["./output/skills/karpathy-thinking"]
39-
# The plugin's description carries the SKILL.md description for the latest entry,
40-
# OR we use a fixed manifest-level description — implementation choice; we just
41-
# check the SKILL.md description appears somewhere in the manifest JSON.
42-
assert "Reason like Karpathy" in json.dumps(manifest)
39+
# Naming convention is locked: top-level marketplace name is always "vectify".
40+
assert manifest["name"] == "vectify"
4341

4442

4543
def test_regenerate_lists_multiple_skills_alphabetical(tmp_path):
@@ -67,7 +65,7 @@ def test_regenerate_replaces_existing_file(tmp_path):
6765
regenerate_marketplace(kb)
6866

6967
manifest = json.loads((kb / ".claude-plugin" / "marketplace.json").read_text())
70-
assert manifest["name"] != "stale"
68+
assert manifest["name"] == "vectify"
7169
assert manifest["plugins"][0]["skills"] == ["./output/skills/demo"]
7270

7371

@@ -155,3 +153,33 @@ def fake_run(cmd, **kwargs):
155153
assert manifest["owner"]["name"] == "openkb-user"
156154
# email is optional when missing
157155
assert "email" not in manifest["owner"] or manifest["owner"]["email"] == ""
156+
157+
158+
def test_regenerate_uses_openkb_at_vectify_convention(tmp_path):
159+
"""All OpenKB-generated marketplaces must self-identify as 'vectify'
160+
(top level) with a plugin named 'openkb', so users install via the
161+
canonical `openkb@vectify` regardless of which KB they're consuming.
162+
Different KBs are distinguished by <owner>/<repo> URL, not manifest name."""
163+
kb = _make_kb(tmp_path)
164+
_make_skill(kb, "demo", "d")
165+
regenerate_marketplace(kb)
166+
167+
manifest = json.loads((kb / ".claude-plugin" / "marketplace.json").read_text())
168+
assert manifest["name"] == "vectify"
169+
assert manifest["plugins"][0]["name"] == "openkb"
170+
171+
172+
def test_regenerate_description_is_not_truncated(tmp_path):
173+
"""Manifest description must be a clean fixed string — no truncation
174+
of SKILL.md content, no '...' mid-word."""
175+
kb = _make_kb(tmp_path)
176+
_make_skill(kb, "demo", "the specific description goes here")
177+
regenerate_marketplace(kb)
178+
179+
manifest = json.loads((kb / ".claude-plugin" / "marketplace.json").read_text())
180+
# Must not contain the per-skill description (we don't inject it anymore)
181+
assert "the specific description goes here" not in manifest["metadata"]["description"]
182+
# Must not end with a truncated word (no trailing space-letter-letter etc.)
183+
desc = manifest["metadata"]["description"]
184+
assert not desc.endswith(" ")
185+
assert desc.endswith(".") or desc.endswith("OpenKB.")

0 commit comments

Comments
 (0)