started prompt mechanics

This commit is contained in:
John Lancaster
2026-06-20 20:27:32 -05:00
parent 098a2418ee
commit f8e0c14d46
12 changed files with 809 additions and 19 deletions
+334 -1
View File
@@ -1,7 +1,7 @@
from __future__ import annotations
import re
from dataclasses import dataclass
from dataclasses import dataclass, field
from importlib.resources import files
from importlib.resources.abc import Traversable
from pathlib import PurePosixPath
@@ -111,6 +111,80 @@ class PersonalMcpMetadata(BaseModel):
return value
class PromptArgumentEntry(BaseModel):
model_config = ConfigDict(extra="ignore", str_strip_whitespace=True)
type: str = Field(min_length=1)
description: str | None = None
required: bool = False
default: Any | None = None
enum: list[str] | None = None
@field_validator("type")
@classmethod
def validate_type(cls, value: str) -> str:
allowed_types = {
"string",
"number",
"integer",
"boolean",
"array",
"object",
}
if value not in allowed_types:
raise ValueError(f"unsupported prompt argument type: {value}")
return value
@field_validator("enum")
@classmethod
def validate_enum(cls, value: list[str] | None) -> list[str] | None:
if value is not None and not value:
raise ValueError("enum must contain at least one value when provided")
return value
class PromptMetadata(BaseModel):
model_config = ConfigDict(extra="ignore", str_strip_whitespace=True)
id: str
version: str
tags: list[str] = Field(default_factory=list)
capabilities: list[str] = Field(min_length=1)
arguments: dict[str, PromptArgumentEntry] = Field(default_factory=dict)
@field_validator("id")
@classmethod
def validate_id(cls, value: str) -> str:
if not SKILL_ID_RE.fullmatch(value):
raise ValueError("id must be lowercase kebab-case and start with a letter")
return value
@field_validator("version")
@classmethod
def validate_version(cls, value: str) -> str:
if not SEMVER_RE.fullmatch(value):
raise ValueError("version must be semver")
return value
@field_validator("tags")
@classmethod
def validate_tags(cls, value: list[str]) -> list[str]:
for tag in value:
if not SKILL_ID_RE.fullmatch(tag):
raise ValueError(f"invalid tag: {tag}")
return value
@field_validator("arguments")
@classmethod
def validate_argument_names(
cls, value: dict[str, PromptArgumentEntry]
) -> dict[str, PromptArgumentEntry]:
for name in value:
if not re.fullmatch(r"^[A-Za-z_][A-Za-z0-9_]*$", name):
raise ValueError(f"invalid prompt argument name: {name}")
return value
class SkillFrontmatter(BaseModel):
model_config = ConfigDict(extra="ignore", str_strip_whitespace=True)
@@ -146,6 +220,25 @@ class SkillFrontmatter(BaseModel):
return value
class PromptFrontmatter(BaseModel):
model_config = ConfigDict(extra="ignore", str_strip_whitespace=True)
name: str = Field(min_length=1, max_length=64)
description: str = Field(min_length=1, max_length=1024)
x_personal_mcp: PromptMetadata = Field(alias="x-personal-mcp")
@field_validator("name")
@classmethod
def validate_name(cls, value: str) -> str:
if not SKILL_ID_RE.fullmatch(value):
raise ValueError(
"name must be lowercase kebab-case and start with a letter"
)
if "anthropic" in value or "claude" in value:
raise ValueError("name must not contain reserved words anthropic or claude")
return value
@dataclass(frozen=True)
class ReferenceRecord:
ref_id: str
@@ -182,6 +275,31 @@ class SkillSummaryRecord:
version: str
@dataclass(frozen=True)
class PromptRecord:
prompt_id: str
name: str
description: str
version: str
tags: tuple[str, ...]
capabilities: tuple[str, ...]
arguments: dict[str, PromptArgumentEntry]
document_uri: str
document_relpath: str
document_content: str
@dataclass(frozen=True)
class PromptSummaryRecord:
prompt_id: str
name: str
description: str
tags: tuple[str, ...]
capabilities: tuple[str, ...]
document_uri: str
version: str
@dataclass(frozen=True)
class DocsRegistry:
skills_by_id: dict[str, SkillRecord]
@@ -191,6 +309,10 @@ class DocsRegistry:
docs_markdown_path_index: tuple[str, ...]
tag_to_skill_ids: dict[str, tuple[str, ...]]
capability_to_skill_ids: dict[str, tuple[str, ...]]
prompts_by_id: dict[str, PromptRecord] = field(default_factory=dict)
prompts_in_load_order: tuple[str, ...] = ()
prompts_summary_in_load_order: tuple[PromptSummaryRecord, ...] = ()
tag_to_prompt_ids: dict[str, tuple[str, ...]] = field(default_factory=dict)
def _parse_frontmatter(markdown: str, *, path: str) -> tuple[dict[str, Any], str]:
@@ -249,6 +371,20 @@ def _validate_skill_frontmatter(
return model
def _validate_prompt_frontmatter(
raw: dict[str, Any], *, prompt_dir_name: str
) -> PromptFrontmatter:
model = PromptFrontmatter.model_validate(raw)
if model.name != prompt_dir_name:
raise ValueError("frontmatter name must exactly match prompt directory name")
if model.x_personal_mcp.id != model.name:
raise ValueError("x-personal-mcp.id must exactly match name")
expected_capability = f"resource://prompts/{model.name}/document"
if expected_capability not in model.x_personal_mcp.capabilities:
raise ValueError(f"capabilities must include {expected_capability}")
return model
def _normalize_docs_path(path: str) -> str:
normalized = PurePosixPath(path)
if normalized.is_absolute() or ".." in normalized.parts:
@@ -371,6 +507,8 @@ def load_docs_registry(
skills_by_id: dict[str, SkillRecord] = {}
summaries: list[SkillSummaryRecord] = []
prompts_by_id: dict[str, PromptRecord] = {}
prompt_summaries: list[PromptSummaryRecord] = []
for skill_dir in sorted(skills_root.iterdir(), key=lambda item: item.name):
if not skill_dir.is_dir():
@@ -481,6 +619,154 @@ def load_docs_registry(
)
)
prompts_root = docs_dir.joinpath("prompts")
discovered_prompt_docs: set[str] = set()
if prompts_root.is_dir():
for prompt_dir in sorted(prompts_root.iterdir(), key=lambda item: item.name):
if not prompt_dir.is_dir():
continue
prompt_dir_name = prompt_dir.name
prompt_rel_root = PurePosixPath("prompts").joinpath(prompt_dir_name)
prompt_doc_relpath = prompt_rel_root.joinpath("PROMPT.md").as_posix()
prompt_doc_file = prompt_dir.joinpath("PROMPT.md")
if not prompt_doc_file.is_file():
continue
discovered_prompt_docs.add(prompt_doc_relpath)
prompt_markdown = prompt_doc_file.read_text(encoding="utf-8")
try:
raw_frontmatter, _ = _parse_frontmatter(
prompt_markdown,
path=prompt_doc_relpath,
)
frontmatter = _validate_prompt_frontmatter(
raw_frontmatter,
prompt_dir_name=prompt_dir_name,
)
except (ValueError, ValidationError) as exc:
issues.append(
RegistryIssue(
code="invalid_prompt_frontmatter",
message=str(exc),
skill_id=prompt_dir_name,
path=prompt_doc_relpath,
hint="fix PROMPT.md YAML frontmatter to match the contract",
)
)
continue
prompt_id = frontmatter.name
if prompt_id in prompts_by_id:
issues.append(
RegistryIssue(
code="duplicate_prompt_id",
message="duplicate prompt id discovered",
skill_id=prompt_id,
path=prompt_doc_relpath,
hint="ensure each prompt directory has a unique id",
)
)
continue
if prompt_id in skills_by_id:
issues.append(
RegistryIssue(
code="prompt_skill_id_collision",
message="prompt id collides with an existing skill id",
skill_id=prompt_id,
path=prompt_doc_relpath,
hint="use a unique prompt id that does not match a skill id",
)
)
continue
prompt_record = PromptRecord(
prompt_id=prompt_id,
name=frontmatter.name,
description=frontmatter.description,
version=frontmatter.x_personal_mcp.version,
tags=tuple(frontmatter.x_personal_mcp.tags),
capabilities=tuple(frontmatter.x_personal_mcp.capabilities),
arguments=frontmatter.x_personal_mcp.arguments,
document_uri=f"resource://prompts/{prompt_id}/document",
document_relpath=prompt_doc_relpath,
document_content=prompt_markdown,
)
prompts_by_id[prompt_id] = prompt_record
prompt_summaries.append(
PromptSummaryRecord(
prompt_id=prompt_record.prompt_id,
name=prompt_record.name,
description=prompt_record.description,
tags=prompt_record.tags,
capabilities=prompt_record.capabilities,
document_uri=prompt_record.document_uri,
version=prompt_record.version,
)
)
for relpath, prompt_file in _walk_markdown(
prompts_root,
prefix=PurePosixPath("prompts"),
):
if relpath in discovered_prompt_docs or relpath.endswith("/PROMPT.md"):
continue
prompt_markdown = prompt_file.read_text(encoding="utf-8")
prompt_id = _reference_id_from_filename(PurePosixPath(relpath).name)
if prompt_id is None:
continue
if prompt_id in prompts_by_id:
issues.append(
RegistryIssue(
code="duplicate_prompt_id",
message="duplicate prompt id discovered",
skill_id=prompt_id,
path=relpath,
hint="ensure each prompt id is unique",
)
)
continue
if prompt_id in skills_by_id:
issues.append(
RegistryIssue(
code="prompt_skill_id_collision",
message="prompt id collides with an existing skill id",
skill_id=prompt_id,
path=relpath,
hint="use a unique prompt id that does not match a skill id",
)
)
continue
prompt_record = PromptRecord(
prompt_id=prompt_id,
name=prompt_id,
description=f"Legacy prompt loaded from docs/{relpath}",
version="1.0.0",
tags=("prompt", "legacy"),
capabilities=(f"resource://prompts/{prompt_id}/document",),
arguments={},
document_uri=f"resource://prompts/{prompt_id}/document",
document_relpath=relpath,
document_content=prompt_markdown,
)
prompts_by_id[prompt_id] = prompt_record
prompt_summaries.append(
PromptSummaryRecord(
prompt_id=prompt_record.prompt_id,
name=prompt_record.name,
description=prompt_record.description,
tags=prompt_record.tags,
capabilities=prompt_record.capabilities,
document_uri=prompt_record.document_uri,
version=prompt_record.version,
)
)
for skill_id, record in sorted(skills_by_id.items()):
for dependency in record.depends_on:
if dependency == skill_id:
@@ -531,15 +817,37 @@ def load_docs_registry(
)
seen_uris.add(uri)
for prompt_id, record in sorted(prompts_by_id.items()):
for uri in [record.document_uri]:
if uri in seen_uris:
issues.append(
RegistryIssue(
code="duplicate_uri",
message=f"duplicate resource URI generated: {uri}",
skill_id=prompt_id,
path=record.document_relpath,
hint="ensure unique prompt ids",
)
)
seen_uris.add(uri)
if issues:
raise DocsRegistryValidationError(issues)
skill_ids = tuple(sorted(skills_by_id))
summary_by_id = {summary.skill_id: summary for summary in summaries}
ordered_summaries = tuple(summary_by_id[skill_id] for skill_id in skill_ids)
prompt_ids = tuple(sorted(prompts_by_id))
prompt_summary_by_id = {
summary.prompt_id: summary for summary in prompt_summaries
}
ordered_prompt_summaries = tuple(
prompt_summary_by_id[prompt_id] for prompt_id in prompt_ids
)
tag_index: dict[str, list[str]] = {}
capability_index: dict[str, list[str]] = {}
prompt_tag_index: dict[str, list[str]] = {}
for skill_id in skill_ids:
record = skills_by_id[skill_id]
for tag in record.tags:
@@ -547,6 +855,11 @@ def load_docs_registry(
for capability in record.capabilities:
capability_index.setdefault(capability, []).append(skill_id)
for prompt_id in prompt_ids:
record = prompts_by_id[prompt_id]
for tag in record.tags:
prompt_tag_index.setdefault(tag, []).append(prompt_id)
return DocsRegistry(
skills_by_id=skills_by_id,
skills_in_load_order=skill_ids,
@@ -560,6 +873,13 @@ def load_docs_registry(
key: tuple(sorted(values))
for key, values in sorted(capability_index.items())
},
prompts_by_id=prompts_by_id,
prompts_in_load_order=prompt_ids,
prompts_summary_in_load_order=ordered_prompt_summaries,
tag_to_prompt_ids={
key: tuple(sorted(values))
for key, values in sorted(prompt_tag_index.items())
},
)
@@ -608,3 +928,16 @@ def read_docs_markdown_path(registry: DocsRegistry, path: str) -> dict[str, str]
"source_path": f"docs/{normalized_path}",
"content": registry.docs_markdown_by_path[normalized_path],
}
def read_prompt_document(registry: DocsRegistry, prompt_id: str) -> dict[str, str]:
if prompt_id not in registry.prompts_by_id:
raise KeyError(f"unknown prompt_id: {prompt_id}")
prompt = registry.prompts_by_id[prompt_id]
return {
"id": prompt.prompt_id,
"uri": prompt.document_uri,
"format": "markdown",
"source_path": f"docs/{prompt.document_relpath}",
"content": prompt.document_content,
}