68 lines
2.5 KiB
Python
68 lines
2.5 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import PurePosixPath
|
|
|
|
from .config import Settings
|
|
from .git import GitWorkspace
|
|
from .models import ChangeSummary
|
|
|
|
_PROTECTED_PARTS = {".git", "vendor", "node_modules", "storage"}
|
|
_PROTECTED_PREFIXES = (
|
|
".gitea/",
|
|
"bootstrap/cache/",
|
|
"docker/",
|
|
)
|
|
_PROTECTED_FILES = {
|
|
"Dockerfile",
|
|
"docker-compose.yml",
|
|
"docker-compose.yaml",
|
|
"compose.yml",
|
|
"compose.yaml",
|
|
}
|
|
_CODE_SUFFIXES = {".php", ".js", ".ts", ".vue", ".css", ".py"}
|
|
_TEMPORARY_SUFFIXES = {".bak", ".backup", ".orig", ".rej", ".swp", ".temp", ".tmp"}
|
|
_TEMPORARY_NAMES = {"debug.php", "test_debug.php", "scratch.php"}
|
|
|
|
|
|
def validate_changes(
|
|
workspace: GitWorkspace,
|
|
summary: ChangeSummary,
|
|
settings: Settings,
|
|
) -> None:
|
|
if workspace.has_path(".git"):
|
|
raise ValueError("The agent created forbidden Git metadata in the worktree")
|
|
if not summary.files:
|
|
raise ValueError("The agent did not produce a change")
|
|
if len(summary.files) > settings.max_changed_files:
|
|
raise ValueError(
|
|
f"Changed file limit exceeded "
|
|
f"({len(summary.files)} > {settings.max_changed_files})"
|
|
)
|
|
if summary.changed_lines > settings.max_changed_lines:
|
|
raise ValueError(
|
|
f"Changed line limit exceeded "
|
|
f"({summary.changed_lines} > {settings.max_changed_lines})"
|
|
)
|
|
|
|
code_changed = False
|
|
for raw_name in summary.files:
|
|
name = raw_name.replace("\\", "/")
|
|
path = PurePosixPath(name)
|
|
if name.startswith("/") or ".." in path.parts:
|
|
raise ValueError(f"Unsafe changed path: {name}")
|
|
if name.startswith(".env") or path.name.startswith(".env"):
|
|
raise ValueError(f"Protected environment file changed: {name}")
|
|
if path.parts and path.parts[0] in _PROTECTED_PARTS:
|
|
raise ValueError(f"Protected path changed: {name}")
|
|
if name in _PROTECTED_FILES or name.startswith(_PROTECTED_PREFIXES):
|
|
raise ValueError(f"Protected deployment path changed: {name}")
|
|
if path.suffix.lower() in _TEMPORARY_SUFFIXES or path.name.lower() in _TEMPORARY_NAMES:
|
|
raise ValueError(f"Temporary artifact changed: {name}")
|
|
if workspace.has_symlink(name):
|
|
raise ValueError(f"Symbolic links are not allowed in agent changes: {name}")
|
|
if path.suffix in _CODE_SUFFIXES and not name.startswith(("tests/", "docs/")):
|
|
code_changed = True
|
|
|
|
if not code_changed:
|
|
raise ValueError("The agent must change at least one application source file")
|