Add and ship OpenCode support for one Claude Code plugin at a time. Includes core migration to a reviewable `opencode-plugin/` adapter, maintainer-facing docs, and follow-up CI/versioning setup for package publishing or skill-copy drift checks.
92
92%
Does it follow best practices?
Impact
97%
1.25xAverage score across 2 eval scenarios
Passed
No known issues
#!/usr/bin/env python3
"""Shared helpers for migrate-plugin skill scripts."""
from __future__ import annotations
import json
import os
import re
from pathlib import Path
from typing import Any
IGNORED_DIRS = {
".git",
".hg",
".svn",
"node_modules",
"__pycache__",
".venv",
"venv",
"dist",
"build",
".next",
".cache",
".tessl",
}
SKILL_NAME_RE = re.compile(r"^[a-z0-9]+(-[a-z0-9]+)*$")
def rel(path: Path, root: Path) -> str:
try:
return path.relative_to(root).as_posix()
except ValueError:
return path.as_posix()
def iter_files(root: Path):
for dirpath, dirnames, filenames in os.walk(root):
dirnames[:] = [d for d in dirnames if d not in IGNORED_DIRS]
current = Path(dirpath)
for filename in filenames:
yield current / filename
def iter_skill_files(root: Path) -> list[Path]:
return sorted(path for path in iter_files(root) if path.name == "SKILL.md")
def parse_frontmatter(path: Path) -> tuple[dict[str, str], list[str]]:
errors: list[str] = []
try:
text = path.read_text(encoding="utf-8")
except UnicodeDecodeError:
text = path.read_text(errors="replace")
lines = text.splitlines()
if not lines or lines[0].strip() != "---":
return {}, ["missing opening YAML frontmatter delimiter"]
end = None
for index, line in enumerate(lines[1:], start=1):
if line.strip() == "---":
end = index
break
if end is None:
return {}, ["missing closing YAML frontmatter delimiter"]
data: dict[str, str] = {}
frontmatter = lines[1:end]
index = 0
while index < len(frontmatter):
line = frontmatter[index]
if not line.strip() or line.lstrip().startswith("#"):
index += 1
continue
if ":" not in line or line.startswith((" ", "\t")):
errors.append(f"unsupported frontmatter line: {line}")
index += 1
continue
key, raw_value = line.split(":", 1)
key = key.strip()
value = raw_value.strip()
if value in {">", "|-", "|", ">-"}:
parts: list[str] = []
index += 1
while index < len(frontmatter) and frontmatter[index].startswith((" ", "\t")):
parts.append(frontmatter[index].strip())
index += 1
data[key] = " ".join(parts).strip()
continue
data[key] = value.strip('"').strip("'")
index += 1
return data, errors
def print_result(result: dict[str, Any], output_format: str) -> None:
if output_format == "json":
print(json.dumps(result, indent=2, sort_keys=True))
return
print(markdown_result(result))
def markdown_result(result: dict[str, Any]) -> str:
lines: list[str] = []
title = result.get("title") or "Report"
lines.append(f"# {title}")
summary = result.get("summary")
if isinstance(summary, dict) and summary:
lines.append("")
lines.append("## Summary")
for key, value in summary.items():
lines.append(f"- {key}: {value}")
for section in ("assets", "checks", "warnings", "errors"):
values = result.get(section)
if not values:
continue
lines.append("")
lines.append(f"## {section.title()}")
if isinstance(values, list):
for item in values:
if isinstance(item, dict):
bits = [f"{key}={value}" for key, value in item.items()]
lines.append(f"- {'; '.join(bits)}")
else:
lines.append(f"- {item}")
return "\n".join(lines)