WIP simplifying load/startup
This commit is contained in:
@@ -2,12 +2,13 @@ import re
|
||||
from collections.abc import Mapping
|
||||
from pathlib import PurePosixPath
|
||||
from types import MappingProxyType
|
||||
from typing import Annotated
|
||||
from typing import ClassVar
|
||||
from typing import Final
|
||||
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BeforeValidator
|
||||
from pydantic import ConfigDict
|
||||
from pydantic import field_validator
|
||||
|
||||
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.-]+)?$")
|
||||
@@ -29,30 +30,35 @@ def frozen_mapping[K, V](value: Mapping[K, V] | None = None) -> Mapping[K, V]:
|
||||
return MappingProxyType(dict(value) if value is not None else {})
|
||||
|
||||
|
||||
def parse_docs_path(value: str | PurePosixPath) -> PurePosixPath:
|
||||
raw = value.as_posix() if isinstance(value, PurePosixPath) else value
|
||||
if "\\" in raw:
|
||||
raise ValueError("docs path must use POSIX separators")
|
||||
|
||||
path = PurePosixPath(raw)
|
||||
if path.is_absolute() or ".." in path.parts:
|
||||
raise ValueError("path must be a docs-relative path")
|
||||
if path.as_posix() != raw:
|
||||
raise ValueError("path must be normalized")
|
||||
if path.suffix.lower() != ".md":
|
||||
raise ValueError("path must point to a markdown file")
|
||||
return path
|
||||
|
||||
|
||||
def parse_reference_path(value: str | PurePosixPath) -> PurePosixPath:
|
||||
path = parse_docs_path(value)
|
||||
if len(path.parts) < 2 or path.parts[0] != "references":
|
||||
raise ValueError("reference path must stay under references/")
|
||||
return path
|
||||
|
||||
|
||||
type DocsPath = Annotated[PurePosixPath, BeforeValidator(parse_docs_path)]
|
||||
type ReferencePath = Annotated[PurePosixPath, BeforeValidator(parse_reference_path)]
|
||||
|
||||
|
||||
class ReferenceEntry(StrictFrozenModel):
|
||||
"""Reference metadata for a markdown file within a skill."""
|
||||
|
||||
path: str
|
||||
path: ReferencePath
|
||||
mime_type: str = "text/markdown"
|
||||
title: str | None = None
|
||||
|
||||
@field_validator("path")
|
||||
@classmethod
|
||||
def validate_reference_path(cls, value: str) -> str:
|
||||
path = PurePosixPath(value)
|
||||
if path.is_absolute() or ".." in path.parts:
|
||||
raise ValueError("reference path must be a relative in-skill path")
|
||||
if not str(path).startswith("references/"):
|
||||
raise ValueError("reference path must stay under references/")
|
||||
if path.suffix.lower() != ".md":
|
||||
raise ValueError("reference path must target a markdown file")
|
||||
return path.as_posix()
|
||||
|
||||
|
||||
def _normalize_docs_path(path: str) -> str:
|
||||
normalized = PurePosixPath(path)
|
||||
if normalized.is_absolute() or ".." in normalized.parts:
|
||||
raise ValueError("path must be a normalized docs-relative path")
|
||||
if normalized.suffix.lower() != ".md":
|
||||
raise ValueError("path must point to a markdown file")
|
||||
return normalized.as_posix()
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import re
|
||||
from collections.abc import Mapping
|
||||
from pathlib import PurePosixPath
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import yaml
|
||||
@@ -10,6 +9,7 @@ from pydantic import model_validator
|
||||
|
||||
from .common import SEMVER_RE
|
||||
from .common import SKILL_ID_RE
|
||||
from .common import DocsPath
|
||||
from .common import StrictFrozenModel
|
||||
from .common import frozen_mapping
|
||||
|
||||
@@ -103,7 +103,7 @@ class StoredPrompt(StrictFrozenModel):
|
||||
"""Normalized prompt document content with path and frontmatter for storage in the registry."""
|
||||
|
||||
prompt_id: str
|
||||
relpath: PurePosixPath
|
||||
relpath: DocsPath
|
||||
content: str
|
||||
frontmatter: PromptFrontmatter
|
||||
|
||||
|
||||
@@ -3,17 +3,22 @@ from collections.abc import Mapping
|
||||
from pydantic import Field
|
||||
from pydantic import field_validator
|
||||
|
||||
from .common import DocsPath
|
||||
from .common import StrictFrozenModel
|
||||
from .common import frozen_mapping
|
||||
from .prompt import PromptArgumentEntry
|
||||
|
||||
|
||||
def _empty_docs_mapping() -> Mapping[DocsPath, str]:
|
||||
return frozen_mapping()
|
||||
|
||||
|
||||
class ReferenceRecord(StrictFrozenModel):
|
||||
"""Registry record for a resolved skill reference document."""
|
||||
|
||||
ref_id: str
|
||||
uri: str
|
||||
relpath: str
|
||||
relpath: DocsPath
|
||||
mime_type: str
|
||||
title: str | None
|
||||
content: str
|
||||
@@ -29,7 +34,7 @@ class SkillRecord(StrictFrozenModel):
|
||||
tags: tuple[str, ...]
|
||||
capabilities: tuple[str, ...]
|
||||
document_uri: str
|
||||
document_relpath: str
|
||||
document_relpath: DocsPath
|
||||
document_content: str
|
||||
references: Mapping[str, ReferenceRecord] = Field(default_factory=frozen_mapping)
|
||||
|
||||
@@ -74,7 +79,7 @@ class PromptRecord(StrictFrozenModel):
|
||||
capabilities: tuple[str, ...]
|
||||
arguments: Mapping[str, PromptArgumentEntry] = Field(default_factory=frozen_mapping)
|
||||
document_uri: str
|
||||
document_relpath: str
|
||||
document_relpath: DocsPath
|
||||
document_content: str
|
||||
|
||||
@field_validator("arguments", mode="before")
|
||||
@@ -196,8 +201,8 @@ class DocsRegistry(StrictFrozenModel):
|
||||
skills_by_id: Mapping[str, SkillRecord] = Field(default_factory=frozen_mapping)
|
||||
skills_in_load_order: tuple[str, ...]
|
||||
skills_summary_in_load_order: tuple[SkillSummaryRecord, ...]
|
||||
docs_markdown_by_path: Mapping[str, str] = Field(default_factory=frozen_mapping)
|
||||
docs_markdown_path_index: tuple[str, ...]
|
||||
docs_markdown_by_path: Mapping[DocsPath, str] = Field(default_factory=_empty_docs_mapping)
|
||||
docs_markdown_path_index: tuple[DocsPath, ...]
|
||||
tag_to_skill_ids: Mapping[str, tuple[str, ...]] = Field(default_factory=frozen_mapping)
|
||||
capability_to_skill_ids: Mapping[str, tuple[str, ...]] = Field(default_factory=frozen_mapping)
|
||||
prompts_by_id: Mapping[str, PromptRecord] = Field(default_factory=frozen_mapping)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from collections.abc import Mapping
|
||||
from pathlib import PurePosixPath
|
||||
|
||||
import yaml
|
||||
from pydantic import Field
|
||||
@@ -8,6 +7,7 @@ from pydantic import model_validator
|
||||
|
||||
from .common import SEMVER_RE
|
||||
from .common import SKILL_ID_RE
|
||||
from .common import DocsPath
|
||||
from .common import ReferenceEntry
|
||||
from .common import StrictFrozenModel
|
||||
from .common import frozen_mapping
|
||||
@@ -91,7 +91,7 @@ class StoredSkillReference(StrictFrozenModel):
|
||||
"""Structured representation of a skill reference markdown document."""
|
||||
|
||||
ref_id: str
|
||||
relpath: PurePosixPath
|
||||
relpath: DocsPath
|
||||
content: str
|
||||
entry: ReferenceEntry
|
||||
|
||||
@@ -100,7 +100,7 @@ class StoredSkill(StrictFrozenModel):
|
||||
"""Structured representation of a skill markdown document."""
|
||||
|
||||
skill_id: str
|
||||
relpath: PurePosixPath
|
||||
relpath: DocsPath
|
||||
content: str
|
||||
frontmatter: SkillFrontmatter
|
||||
references: Mapping[str, StoredSkillReference] = Field(default_factory=frozen_mapping)
|
||||
|
||||
Reference in New Issue
Block a user