重构插件入口与runtime安装流程
This commit is contained in:
125
scripts/validate_plugin.py
Executable file
125
scripts/validate_plugin.py
Executable file
@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate Codex Game Studios plugin structure and package hygiene."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
FORBIDDEN_NAMES = {".DS_Store", ".env", "plan.md"}
|
||||
FORBIDDEN_PARTS = {".git", "__pycache__"}
|
||||
REQUIRED_SKILLS = {
|
||||
"brainstorm",
|
||||
"setup-runtime",
|
||||
"using-codex-game-studios",
|
||||
}
|
||||
REQUIRED_RUNTIME_AGENTS = {
|
||||
"art-director.toml",
|
||||
"creative-director.toml",
|
||||
"producer.toml",
|
||||
"technical-director.toml",
|
||||
}
|
||||
|
||||
|
||||
def fail(message: str) -> None:
|
||||
print(f"ERROR: {message}", file=sys.stderr)
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
def run_git(args: list[str]) -> list[str]:
|
||||
result = subprocess.run(
|
||||
["git", *args],
|
||||
cwd=ROOT,
|
||||
text=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(result.stderr.strip() or "git command failed")
|
||||
return [line for line in result.stdout.splitlines() if line]
|
||||
|
||||
|
||||
def assert_manifest() -> None:
|
||||
manifest_path = ROOT / ".codex-plugin" / "plugin.json"
|
||||
if not manifest_path.is_file():
|
||||
fail("Missing .codex-plugin/plugin.json")
|
||||
|
||||
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||
if manifest.get("name") != "codex-game-studios":
|
||||
fail("plugin.json name must be codex-game-studios")
|
||||
if manifest.get("skills") != "./skills/":
|
||||
fail('plugin.json must expose skills via "./skills/"')
|
||||
|
||||
for prompt in manifest.get("interface", {}).get("defaultPrompt", []):
|
||||
if len(prompt) > 128:
|
||||
fail(f"defaultPrompt exceeds 128 chars: {prompt}")
|
||||
|
||||
|
||||
def assert_skill(skill_name: str) -> None:
|
||||
skill_dir = ROOT / "skills" / skill_name
|
||||
skill_path = skill_dir / "SKILL.md"
|
||||
agent_path = skill_dir / "agents" / "openai.yaml"
|
||||
|
||||
if not skill_path.is_file():
|
||||
fail(f"Missing skill file: {skill_path.relative_to(ROOT)}")
|
||||
if not agent_path.is_file():
|
||||
fail(f"Missing skill UI metadata: {agent_path.relative_to(ROOT)}")
|
||||
|
||||
text = skill_path.read_text(encoding="utf-8")
|
||||
if not text.startswith("---\n"):
|
||||
fail(f"Missing YAML frontmatter: {skill_path.relative_to(ROOT)}")
|
||||
if f"name: {skill_name}" not in text:
|
||||
fail(f"Skill name mismatch in {skill_path.relative_to(ROOT)}")
|
||||
if "description:" not in text.split("---", 2)[1]:
|
||||
fail(f"Missing skill description in {skill_path.relative_to(ROOT)}")
|
||||
|
||||
|
||||
def assert_runtime_agents() -> None:
|
||||
agent_dir = ROOT / "runtime" / "agents"
|
||||
if not agent_dir.is_dir():
|
||||
fail("Missing runtime/agents directory")
|
||||
|
||||
present = {path.name for path in agent_dir.glob("*.toml")}
|
||||
missing = sorted(REQUIRED_RUNTIME_AGENTS - present)
|
||||
if missing:
|
||||
fail(f"Missing runtime agents: {', '.join(missing)}")
|
||||
|
||||
|
||||
def assert_package_hygiene() -> None:
|
||||
try:
|
||||
tracked = run_git(["ls-files"])
|
||||
untracked = run_git(["ls-files", "--others", "--exclude-standard"])
|
||||
candidates = tracked + untracked
|
||||
except RuntimeError:
|
||||
candidates = [
|
||||
str(path.relative_to(ROOT))
|
||||
for path in ROOT.rglob("*")
|
||||
if path.is_file() or path.is_dir()
|
||||
]
|
||||
|
||||
bad: list[str] = []
|
||||
for rel_path in candidates:
|
||||
path = Path(rel_path)
|
||||
if path.name in FORBIDDEN_NAMES or FORBIDDEN_PARTS.intersection(path.parts):
|
||||
bad.append(rel_path)
|
||||
|
||||
if bad:
|
||||
fail("Forbidden package candidates:\n- " + "\n- ".join(sorted(bad)))
|
||||
|
||||
|
||||
def main() -> int:
|
||||
assert_manifest()
|
||||
for skill_name in sorted(REQUIRED_SKILLS):
|
||||
assert_skill(skill_name)
|
||||
assert_runtime_agents()
|
||||
assert_package_hygiene()
|
||||
print("Codex Game Studios plugin validation passed.")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user