This commit is contained in:
John Lancaster
2026-06-21 12:41:14 -05:00
parent 9c8ab70c06
commit aec3500370
4 changed files with 25 additions and 29 deletions
+7 -9
View File
@@ -2,21 +2,19 @@ import re
from collections.abc import Mapping
from pathlib import PurePosixPath
from types import MappingProxyType
from typing import TypeVar
from typing import ClassVar
from typing import Final
from pydantic import BaseModel
from pydantic import ConfigDict
from pydantic import field_validator
SKILL_ID_RE = re.compile(r"^[a-z][a-z0-9-]*$")
SEMVER_RE = re.compile(r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:[-+][0-9A-Za-z.-]+)?$")
K = TypeVar("K")
V = TypeVar("V")
SKILL_ID_RE: Final[re.Pattern[str]] = re.compile(r"^[a-z][a-z0-9-]*$")
SEMVER_RE: Final[re.Pattern[str]] = re.compile(r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:[-+][0-9A-Za-z.-]+)?$")
class StrictFrozenModel(BaseModel):
model_config = ConfigDict(
model_config: ClassVar[ConfigDict] = ConfigDict(
extra="forbid",
frozen=True,
validate_by_alias=True,
@@ -25,8 +23,8 @@ class StrictFrozenModel(BaseModel):
)
def frozen_mapping(value: Mapping[K, V] | None = None) -> Mapping[K, V]:
return MappingProxyType(dict(value or {}))
def frozen_mapping[K, V](value: Mapping[K, V] | None = None) -> Mapping[K, V]:
return MappingProxyType(dict(value) if value is not None else {})
class ReferenceEntry(StrictFrozenModel):
+2 -1
View File
@@ -1,4 +1,5 @@
from pathlib import PurePosixPath
from typing import Self
from .common import StrictFrozenModel
@@ -24,7 +25,7 @@ class SkillFilesBundleModel(StrictFrozenModel):
skill: MarkdownDocumentModel,
references: tuple[MarkdownDocumentModel, ...] = (),
other: tuple[MarkdownDocumentModel, ...] = (),
) -> "SkillFilesBundleModel":
) -> Self:
# Ensure deterministic ordering even when the source collection is unordered.
references_sorted = tuple(sorted(references, key=lambda doc: doc.relpath))
other_sorted = tuple(sorted(other, key=lambda doc: doc.relpath))
+11 -16
View File
@@ -1,6 +1,7 @@
import re
from collections.abc import Mapping
from typing import Any
from typing import Literal
from pydantic import Field
from pydantic import field_validator
@@ -10,28 +11,22 @@ from .common import SKILL_ID_RE
from .common import StrictFrozenModel
from .common import frozen_mapping
class PromptArgumentEntry(StrictFrozenModel):
type: str = Field(min_length=1)
description: str | None = None
required: bool = False
default: Any | None = None
enum: tuple[str, ...] | None = None
@field_validator("type")
@classmethod
def validate_type(cls, value: str) -> str:
allowed_types = {
type PromptArgumentType = Literal[
"string",
"number",
"integer",
"boolean",
"array",
"object",
}
if value not in allowed_types:
raise ValueError(f"unsupported prompt argument type: {value}")
return value
]
class PromptArgumentEntry(StrictFrozenModel):
type: PromptArgumentType
description: str | None = None
required: bool = False
default: Any | None = None
enum: tuple[str, ...] | None = None
@field_validator("enum")
@classmethod
+5 -3
View File
@@ -9,6 +9,8 @@ from .common import ReferenceEntry
from .common import StrictFrozenModel
from .common import frozen_mapping
type ToolSelector = str | tuple[str, ...]
class SkillMetadata(StrictFrozenModel):
id: str
@@ -66,8 +68,8 @@ class SkillFrontmatter(StrictFrozenModel):
name: str = Field(min_length=1, max_length=64)
description: str = Field(min_length=1, max_length=1024)
when_to_use: str | None = None
allowed_tools: str | tuple[str, ...] | None = Field(default=None, alias="allowed-tools")
disallowed_tools: str | tuple[str, ...] | None = Field(
allowed_tools: ToolSelector | None = Field(default=None, alias="allowed-tools")
disallowed_tools: ToolSelector | None = Field(
default=None,
alias="disallowed-tools",
)
@@ -77,7 +79,7 @@ class SkillFrontmatter(StrictFrozenModel):
)
user_invocable: bool | None = Field(default=None, alias="user-invocable")
argument_hint: str | None = Field(default=None, alias="argument-hint")
arguments: str | tuple[str, ...] | None = None
arguments: ToolSelector | None = None
license: str | None = None
compatibility: str | None = None
metadata: Mapping[str, str] | None = None