feat(plugin-eval): scaffold project with uv, ruff, ty

This commit is contained in:
Seth Hobson
2026-03-25 17:31:00 -04:00
parent 8bdde96433
commit ef6fe431c3
8 changed files with 1206 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
# plugin-eval
Three-layer quality evaluation framework for Claude Code plugins.
+56
View File
@@ -0,0 +1,56 @@
[project]
name = "plugin-eval"
version = "0.1.0"
description = "Three-layer quality evaluation framework for Claude Code plugins"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"pydantic>=2.0",
"typer>=0.15",
"rich>=13.0",
"pyyaml>=6.0",
]
[project.optional-dependencies]
llm = [
"claude-agent-sdk>=0.1.50",
]
api = [
"anthropic>=0.45",
]
dev = [
"pytest>=8.0",
"pytest-asyncio>=0.24",
"pytest-cov>=6.0",
"ruff>=0.9",
]
[project.scripts]
plugin-eval = "plugin_eval.cli:app"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["src/plugin_eval"]
[tool.ruff]
target-version = "py312"
line-length = 100
[tool.ruff.lint]
select = ["E4", "E7", "E9", "F", "B", "I", "UP", "SIM"]
ignore = ["E501"]
[tool.ruff.format]
quote-style = "double"
[tool.ty.rules]
possibly-unresolved-reference = "error"
possibly-missing-attribute = "error"
possibly-missing-import = "error"
[tool.pytest.ini_options]
testpaths = ["tests"]
asyncio_mode = "auto"
@@ -0,0 +1,3 @@
"""PluginEval — Quality evaluation framework for Claude Code plugins."""
__version__ = "0.1.0"
+79
View File
@@ -0,0 +1,79 @@
from pathlib import Path
import pytest
@pytest.fixture
def fixtures_dir() -> Path:
return Path(__file__).parent / "fixtures"
@pytest.fixture
def sample_skill_dir(tmp_path: Path) -> Path:
"""Create a minimal valid skill directory."""
skill_dir = tmp_path / "test-skill"
skill_dir.mkdir()
skill_md = skill_dir / "SKILL.md"
skill_md.write_text(
"---\n"
"name: test-skill\n"
'description: "Test skill for evaluation. Use when testing plugin-eval."\n'
"---\n\n"
"# Test Skill\n\n"
"## Overview\n\n"
"This is a test skill for evaluation purposes.\n\n"
"## Usage\n\n"
"```python\nprint('hello')\n```\n\n"
"## Troubleshooting\n\n"
"Check the logs.\n"
)
refs_dir = skill_dir / "references"
refs_dir.mkdir()
(refs_dir / "guide.md").write_text("# Guide\n\nDetailed reference content.\n")
return skill_dir
@pytest.fixture
def sample_plugin_dir(tmp_path: Path, sample_skill_dir: Path) -> Path:
"""Create a minimal valid plugin directory."""
plugin_dir = tmp_path / "test-plugin"
plugin_dir.mkdir()
claude_dir = plugin_dir / ".claude-plugin"
claude_dir.mkdir()
(claude_dir / "plugin.json").write_text('{"name": "test-plugin"}')
skills_dir = plugin_dir / "skills"
skills_dir.mkdir()
import shutil
dest = skills_dir / "test-skill"
shutil.copytree(sample_skill_dir, dest)
agents_dir = plugin_dir / "agents"
agents_dir.mkdir()
(agents_dir / "test-agent.md").write_text(
"---\n"
"name: test-agent\n"
'description: "Test agent. Use PROACTIVELY for testing."\n'
"model: sonnet\n"
"tools: Read, Grep, Glob\n"
"---\n\n"
"You are a test agent.\n"
)
return plugin_dir
@pytest.fixture
def poor_skill_dir(tmp_path: Path) -> Path:
"""Create a skill with multiple anti-patterns."""
skill_dir = tmp_path / "poor-skill"
skill_dir.mkdir()
skill_md = skill_dir / "SKILL.md"
lines = ["---\n", "name: poor-skill\n", 'description: "A skill."\n', "---\n\n"]
lines.append("# Poor Skill\n\n")
for i in range(100):
lines.append(f"You MUST follow rule {i}. You ALWAYS do this. NEVER skip.\n")
skill_md.write_text("".join(lines))
refs = skill_dir / "references"
refs.mkdir()
(refs / "orphan.md").write_text("# Orphan\n\nNot referenced from SKILL.md.\n")
return skill_dir
+1065
View File
File diff suppressed because it is too large Load Diff