mirror of
https://github.com/SuperClaude-Org/SuperClaude_Framework.git
synced 2025-12-29 16:16:08 +00:00
Add validators package with 6 specialized validators: - base.py: Abstract base validator with common patterns - context_contract.py: PM mode context validation - dep_sanity.py: Dependency consistency checks - runtime_policy.py: Runtime policy enforcement - security_roughcheck.py: Security vulnerability scanning - test_runner.py: Automated test execution validation Supports validation gates for quality assurance and risk mitigation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
152 lines
5.3 KiB
Python
152 lines
5.3 KiB
Python
"""Dependency Sanity Validator
|
|
|
|
Validates that:
|
|
- Proposed packages exist on registries (npm, PyPI)
|
|
- Versions are compatible with lockfiles
|
|
- No conflicting dependencies
|
|
"""
|
|
|
|
from typing import Dict, Any, List, Optional
|
|
import subprocess
|
|
import json
|
|
import re
|
|
|
|
from .base import Validator, ValidationResult
|
|
|
|
|
|
class DependencySanityValidator(Validator):
|
|
"""Validates dependency sanity"""
|
|
|
|
def __init__(self):
|
|
super().__init__("Dependency Sanity")
|
|
|
|
def validate(self, context: Dict[str, Any]) -> ValidationResult:
|
|
"""
|
|
Validate dependency changes.
|
|
|
|
Context should contain:
|
|
- changes: Dict of file changes
|
|
- contract: Context Contract (for runtime info)
|
|
"""
|
|
changes = context.get("changes", {})
|
|
contract = context.get("contract", {})
|
|
|
|
issues = []
|
|
warnings = []
|
|
|
|
# Check package.json changes
|
|
for file_path, content in changes.items():
|
|
if "package.json" in file_path:
|
|
result = self._validate_npm_deps(content, contract)
|
|
issues.extend(result.get("issues", []))
|
|
warnings.extend(result.get("warnings", []))
|
|
|
|
elif "pyproject.toml" in file_path or "requirements.txt" in file_path:
|
|
result = self._validate_python_deps(content, contract)
|
|
issues.extend(result.get("issues", []))
|
|
warnings.extend(result.get("warnings", []))
|
|
|
|
# Return result
|
|
if issues:
|
|
return self._fail(
|
|
f"Dependency issues detected ({len(issues)} issues)",
|
|
details={"issues": issues, "warnings": warnings}
|
|
)
|
|
|
|
if warnings:
|
|
return self._warning(
|
|
f"Dependency warnings ({len(warnings)} warnings)",
|
|
details={"warnings": warnings}
|
|
)
|
|
|
|
return self._pass("All dependencies validated")
|
|
|
|
def _validate_npm_deps(self, package_json_content: str, contract: Dict[str, Any]) -> Dict[str, List[str]]:
|
|
"""Validate npm dependencies"""
|
|
issues = []
|
|
warnings = []
|
|
|
|
try:
|
|
# Parse package.json
|
|
data = json.loads(package_json_content)
|
|
dependencies = {**data.get("dependencies", {}), **data.get("devDependencies", {})}
|
|
|
|
# Check if packages exist (basic validation)
|
|
for pkg_name, version in dependencies.items():
|
|
# Check for common typos
|
|
if pkg_name.startswith("@"):
|
|
# Scoped package
|
|
if not re.match(r"^@[\w-]+/[\w-]+$", pkg_name):
|
|
issues.append(f"Invalid scoped package name: {pkg_name}")
|
|
else:
|
|
# Regular package
|
|
if not re.match(r"^[\w-]+$", pkg_name):
|
|
issues.append(f"Invalid package name: {pkg_name}")
|
|
|
|
# Check version format
|
|
if version and not re.match(r"^[\^~]?\d+\.\d+\.\d+|latest|workspace:\*", version):
|
|
warnings.append(f"Unusual version format for {pkg_name}: {version}")
|
|
|
|
except json.JSONDecodeError:
|
|
issues.append("Invalid package.json format")
|
|
|
|
return {"issues": issues, "warnings": warnings}
|
|
|
|
def _validate_python_deps(self, content: str, contract: Dict[str, Any]) -> Dict[str, List[str]]:
|
|
"""Validate Python dependencies"""
|
|
issues = []
|
|
warnings = []
|
|
|
|
# Extract package names from requirements.txt or pyproject.toml
|
|
if "[tool.poetry.dependencies]" in content or "[project.dependencies]" in content:
|
|
# pyproject.toml format - basic validation
|
|
# More sophisticated parsing would use tomli/tomlkit
|
|
pass
|
|
else:
|
|
# requirements.txt format
|
|
for line in content.split("\n"):
|
|
line = line.strip()
|
|
if not line or line.startswith("#"):
|
|
continue
|
|
|
|
# Check for common issues
|
|
if "==" in line:
|
|
pkg_spec = line.split("==")
|
|
if len(pkg_spec) != 2:
|
|
issues.append(f"Invalid requirement format: {line}")
|
|
else:
|
|
pkg_name, version = pkg_spec
|
|
# Basic package name validation
|
|
if not re.match(r"^[a-zA-Z0-9_-]+$", pkg_name):
|
|
issues.append(f"Invalid package name: {pkg_name}")
|
|
|
|
return {"issues": issues, "warnings": warnings}
|
|
|
|
def check_npm_package_exists(self, package_name: str) -> bool:
|
|
"""Check if npm package exists on registry"""
|
|
try:
|
|
result = subprocess.run(
|
|
["npm", "view", package_name, "version"],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=5,
|
|
check=False
|
|
)
|
|
return result.returncode == 0
|
|
except Exception:
|
|
return False
|
|
|
|
def check_pypi_package_exists(self, package_name: str) -> bool:
|
|
"""Check if PyPI package exists"""
|
|
try:
|
|
result = subprocess.run(
|
|
["pip", "index", "versions", package_name],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=5,
|
|
check=False
|
|
)
|
|
return result.returncode == 0
|
|
except Exception:
|
|
return False
|