mirror of
https://github.com/codestable/CodeStable.git
synced 2026-09-19 09:03:09 +08:00
fix(plugin): align Codex manifest with validator schema
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Added complete Codex plugin publisher and interface metadata, with package checks that prevent
|
||||
future manifests from passing without the fields required by generic plugin validation.
|
||||
|
||||
## 2.0.2
|
||||
|
||||
- Added the owner-gated `item_progression: parallel` policy to `cs-epic`: the main workflow stays the
|
||||
|
||||
@@ -2,5 +2,24 @@
|
||||
"name": "codestable",
|
||||
"version": "2.0.2",
|
||||
"description": "CodeStable AI coding workflow skills.",
|
||||
"skills": "./skills/"
|
||||
"author": {
|
||||
"name": "CodeStable"
|
||||
},
|
||||
"skills": "./skills/",
|
||||
"interface": {
|
||||
"displayName": "CodeStable",
|
||||
"shortDescription": "Stable engineering workflows for AI coding.",
|
||||
"longDescription": "Workflow skills for scoped implementation, diagnosis, review, refactoring, epics, onboarding, and durable project learning.",
|
||||
"developerName": "CodeStable",
|
||||
"category": "development",
|
||||
"capabilities": [
|
||||
"Interactive",
|
||||
"Write"
|
||||
],
|
||||
"defaultPrompt": [
|
||||
"Use CodeStable to implement this change with scoped verification.",
|
||||
"Diagnose and fix this issue with red-to-green evidence.",
|
||||
"Review the current diff for concrete risks and missing tests."
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,22 @@ def make_repo(tmp_path: Path) -> Path:
|
||||
)
|
||||
write_json(
|
||||
repo / "plugins/codestable/.codex-plugin/plugin.json",
|
||||
{"name": "codestable", "version": "0.1.0", "skills": "./skills/"},
|
||||
{
|
||||
"name": "codestable",
|
||||
"version": "0.1.0",
|
||||
"description": "CodeStable AI coding workflow skills.",
|
||||
"author": {"name": "CodeStable"},
|
||||
"skills": "./skills/",
|
||||
"interface": {
|
||||
"displayName": "CodeStable",
|
||||
"shortDescription": "Stable engineering workflows for AI coding.",
|
||||
"longDescription": "Workflow skills for scoped AI-assisted software development.",
|
||||
"developerName": "CodeStable",
|
||||
"category": "development",
|
||||
"capabilities": ["Interactive", "Write"],
|
||||
"defaultPrompt": ["Use CodeStable to handle this software task."],
|
||||
},
|
||||
},
|
||||
)
|
||||
write_json(
|
||||
repo / "plugins/codestable/.claude-plugin/plugin.json",
|
||||
@@ -196,6 +211,41 @@ def test_invalid_manifest_contract_fails(tmp_path: Path) -> None:
|
||||
assert any("skills must equal './skills/'" in message for message in messages(findings))
|
||||
|
||||
|
||||
def test_codex_manifest_requires_ingestion_metadata(tmp_path: Path) -> None:
|
||||
repo = make_repo(tmp_path)
|
||||
path = repo / "plugins/codestable/.codex-plugin/plugin.json"
|
||||
manifest = json.loads(path.read_text(encoding="utf-8"))
|
||||
manifest.pop("author")
|
||||
manifest.pop("interface")
|
||||
write_json(path, manifest)
|
||||
|
||||
finding_messages = messages(checker.check_repo(repo))
|
||||
|
||||
assert "author.name must equal 'CodeStable'" in finding_messages
|
||||
assert "interface is required" in finding_messages
|
||||
|
||||
|
||||
def test_codex_manifest_requires_complete_interface_metadata(tmp_path: Path) -> None:
|
||||
repo = make_repo(tmp_path)
|
||||
path = repo / "plugins/codestable/.codex-plugin/plugin.json"
|
||||
manifest = json.loads(path.read_text(encoding="utf-8"))
|
||||
manifest["interface"] = {}
|
||||
write_json(path, manifest)
|
||||
|
||||
finding_messages = messages(checker.check_repo(repo))
|
||||
|
||||
for field in (
|
||||
"displayName",
|
||||
"shortDescription",
|
||||
"longDescription",
|
||||
"developerName",
|
||||
"category",
|
||||
):
|
||||
assert f"interface.{field} is required" in finding_messages
|
||||
assert "interface.capabilities must be an array of strings" in finding_messages
|
||||
assert "interface.defaultPrompt must be an array of strings" in finding_messages
|
||||
|
||||
|
||||
def test_codex_catalog_contract_fails(tmp_path: Path) -> None:
|
||||
repo = make_repo(tmp_path)
|
||||
write_json(
|
||||
|
||||
@@ -125,6 +125,7 @@ def check_catalog_contracts(root: Path, findings: list[Finding]) -> None:
|
||||
(".claude-plugin/marketplace.json", ["description"], "CodeStable AI coding workflow skills."),
|
||||
(".claude-plugin/marketplace.json", ["plugins", 0, "source"], "./plugins/codestable"),
|
||||
("plugins/codestable/.claude-plugin/plugin.json", ["author", "name"], "CodeStable"),
|
||||
("plugins/codestable/.codex-plugin/plugin.json", ["author", "name"], "CodeStable"),
|
||||
("plugins/codestable/.codex-plugin/plugin.json", ["skills"], "./skills/"),
|
||||
]
|
||||
cache: dict[str, Any | None] = {}
|
||||
@@ -150,6 +151,48 @@ def check_catalog_contracts(root: Path, findings: list[Finding]) -> None:
|
||||
if not nested(codex_marketplace, ["plugins", 0, "interface", "displayName"]):
|
||||
findings.append(Finding(".agents/plugins/marketplace.json", "plugins.0.interface.displayName is required"))
|
||||
|
||||
codex_manifest = cache.get("plugins/codestable/.codex-plugin/plugin.json")
|
||||
if codex_manifest is not None:
|
||||
interface = nested(codex_manifest, ["interface"])
|
||||
if not isinstance(interface, dict):
|
||||
findings.append(Finding("plugins/codestable/.codex-plugin/plugin.json", "interface is required"))
|
||||
else:
|
||||
for field in (
|
||||
"displayName",
|
||||
"shortDescription",
|
||||
"longDescription",
|
||||
"developerName",
|
||||
"category",
|
||||
):
|
||||
value = interface.get(field)
|
||||
if not isinstance(value, str) or not value.strip():
|
||||
findings.append(
|
||||
Finding(
|
||||
"plugins/codestable/.codex-plugin/plugin.json",
|
||||
f"interface.{field} is required",
|
||||
)
|
||||
)
|
||||
capabilities = interface.get("capabilities")
|
||||
if not isinstance(capabilities, list) or not all(
|
||||
isinstance(value, str) and value.strip() for value in capabilities
|
||||
):
|
||||
findings.append(
|
||||
Finding(
|
||||
"plugins/codestable/.codex-plugin/plugin.json",
|
||||
"interface.capabilities must be an array of strings",
|
||||
)
|
||||
)
|
||||
default_prompt = interface.get("defaultPrompt")
|
||||
if not isinstance(default_prompt, list) or not all(
|
||||
isinstance(value, str) and value.strip() for value in default_prompt
|
||||
):
|
||||
findings.append(
|
||||
Finding(
|
||||
"plugins/codestable/.codex-plugin/plugin.json",
|
||||
"interface.defaultPrompt must be an array of strings",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def check_skill_layout(root: Path, findings: list[Finding]) -> None:
|
||||
skills_dir = root / "plugins/codestable/skills"
|
||||
|
||||
Reference in New Issue
Block a user