Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/skillevaluator/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,14 @@
# SHARED CONSTANTS
# =============================================================================

# Field length constraints per SkillEvaluator specification
# Field limits for serialized metadata
NAME_MIN_LENGTH = 1
NAME_MAX_LENGTH = 64
TITLE_MIN_LENGTH = 1
TITLE_MAX_LENGTH = 256
DESCRIPTION_MIN_LENGTH = 1
DESCRIPTION_MAX_LENGTH = 1024
DESCRIPTION_MAX_BYTES = 1024
COMPATIBILITY_MAX_LENGTH = 500

# Maximum recommended line counts
Expand Down
9 changes: 8 additions & 1 deletion src/skillevaluator/models/skill.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

from skillevaluator.constants import (
COMPATIBILITY_MAX_LENGTH,
DESCRIPTION_MAX_BYTES,
DESCRIPTION_MAX_LENGTH,
DESCRIPTION_MIN_LENGTH,
FORBIDDEN_SKILL_FIELDS,
Expand Down Expand Up @@ -161,7 +162,13 @@ def validate_name_format(cls, v: str) -> str:
@field_validator("description")
@classmethod
def validate_description_content(cls, v: str) -> str:
"""Reject descriptions that contain XML tags."""
"""Reject descriptions that exceed the serialized limit or contain XML tags."""
byte_length = len(v.encode("utf-8"))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Keep the Agent Skills character limit here. The current specification defines description as 1–1024 characters, not bytes (https://github.com/agentskills/agentskills/blob/main/docs/specification.mdx#L91-L96), and docs/tier1-validation.mdx documents the same contract. This rejects standards-valid multilingual descriptions: for example, 400 euro-sign characters are 400 characters but 1,200 UTF-8 bytes and fail this validator. Please retain the character limit unless there is a sourced downstream 1,024-byte contract; if there is, document it as a separate product-specific restriction rather than replacing the standard rule.

if byte_length > DESCRIPTION_MAX_BYTES:
raise ValueError(
f"Description must be at most {DESCRIPTION_MAX_BYTES} UTF-8 bytes "
f"(got {byte_length} bytes)"
)
if XML_TAG_RE.search(v):
raise ValueError("Skill description must not contain XML tags")
return v
Expand Down
22 changes: 21 additions & 1 deletion tests/validators/test_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@

from pathlib import Path

import pytest

from skillevaluator.models.skill import SkillFrontmatter
from skillevaluator.validators.schema import SchemaValidator


Expand Down Expand Up @@ -184,7 +187,7 @@ def test_consecutive_hyphens_rejected(self, tmp_path: Path):
assert any("consecutive" in err.lower() or "hyphen" in err.lower() for err in result.errors)

def test_description_length_constraints(self, tmp_path: Path):
"""Test validation enforces description length constraints (1-1024 chars)."""
"""Test validation enforces description length constraints (1-1024 UTF-8 bytes)."""
skill_dir = tmp_path / "long-description"
skill_dir.mkdir()

Expand All @@ -206,6 +209,23 @@ def test_description_length_constraints(self, tmp_path: Path):
assert not result.passed
assert any("1024" in err or "description" in err.lower() for err in result.errors)

def test_description_accepts_exact_utf8_byte_limit(self):
description = "a" * 1021 + "€"

frontmatter = SkillFrontmatter(name="exact-byte-limit", description=description)

assert len(description.encode("utf-8")) == 1024
assert frontmatter.description == description

def test_description_rejects_utf8_byte_overflow(self):
description = "a" * 1022 + "€"

with pytest.raises(ValueError, match=r"at most 1024 UTF-8 bytes \(got 1025 bytes\)"):
SkillFrontmatter(name="byte-overflow", description=description)

assert len(description) == 1023
assert len(description.encode("utf-8")) == 1025

def test_metadata_author_validation(self, tmp_path: Path):
"""Test author format fails for malformed (no email) author under default profile."""
from skillevaluator.models.result import Severity
Expand Down
Loading