Source code for ralph.mcp.artifacts.plan._summary

"""Summary section for the plan artifact schema.

Carries the explicit ``intent`` and ``intent_verb`` analysis fields in
addition to the existing ``context`` and ``scope_items`` fields. ``intent`` is
a free-form 1-line user-facing outcome (defaults to empty string so it is
dropped by ``model_dump(exclude_defaults=True)``, mirroring ``context``).
``intent_verb`` is a closed enum stored as ``str`` defaulting to ``""`` (also
dropped from ``model_dump(exclude_defaults=True)``). A before-validator
lowercases the value before the closed-set check fires (so ``Add`` and
``ADD`` both pass), rejects unknown values, and explicitly rejects ``""`` to
distinguish a deliberate empty value from an omitted field.
"""

from __future__ import annotations

from typing import Literal, cast

from pydantic import ConfigDict, Field, field_validator

from ralph.mcp.artifacts.plan._scope_item import ScopeItem
from ralph.pydantic_compat import RalphBaseModel

_INTENT_VERB_SET: frozenset[str] = frozenset(
    {
        "add",
        "fix",
        "refactor",
        "migrate",
        "document",
        "investigate",
        "improve",
        "configure",
        "remove",
    }
)

CoverageArea = Literal[
    "bugfix",
    "feature",
    "refactor",
    "test",
    "docs",
    "infra",
    "security",
    "performance",
    "migration",
    "release",
]

_COVERAGE_AREAS: frozenset[str] = frozenset(
    {
        "bugfix",
        "feature",
        "refactor",
        "test",
        "docs",
        "infra",
        "security",
        "performance",
        "migration",
        "release",
    }
)


[docs] class Summary(RalphBaseModel): """Summary section of a plan artifact. Captures the user-facing context, the explicit intent and intent verb, the scope items that bound the work, and the coverage areas the plan touches. ``intent_verb`` is a closed vocabulary used by the executor to choose the right kind of verification and artifact semantics. """ model_config = ConfigDict(extra="forbid") context: str = Field( default="", max_length=8000, description="Free-form context (max 8000 chars; medium tier).", ) intent: str = Field( default="", max_length=500, description="One-line user-facing outcome (max 500 chars; short tier).", ) intent_verb: str = Field( default="", description="Closed verb; see _INTENT_VERB_SET (9 values).", ) scope_items: list[ScopeItem] = Field( ..., min_length=3, max_length=200, description="At least 3 scope items (max 200); see ScopeItem.", ) coverage_areas: list[CoverageArea] = Field( default_factory=list, max_length=50, description="Optional CoverageArea enum list (max 50); see CoverageArea literal.", ) @field_validator("intent") @classmethod def _strip_intent(cls, value: str) -> str: return value.strip() @field_validator("intent_verb", mode="before") @classmethod def _normalize_intent_verb(cls, value: object) -> str: if value is None: return "" if not isinstance(value, str): msg = "intent_verb must be a string" raise ValueError(msg) stripped = value.strip() if not stripped: msg = "intent_verb must not be empty" raise ValueError(msg) lowered = stripped.lower() if lowered not in _INTENT_VERB_SET: msg = f"intent_verb {value!r} is not one of: {sorted(_INTENT_VERB_SET)!r}" raise ValueError(msg) return lowered @field_validator("coverage_areas", mode="before") @classmethod def _validate_coverage_areas(cls, value: object) -> list[CoverageArea]: if value is None: return [] if not isinstance(value, list): msg = "coverage_areas must be a list" raise ValueError(msg) cleaned: list[CoverageArea] = [] for entry in value: if not isinstance(entry, str): msg = f"coverage_areas elements must be strings, got {type(entry).__name__}" raise ValueError(msg) if entry not in _COVERAGE_AREAS: msg = f"coverage_areas element {entry!r} is not one of: {sorted(_COVERAGE_AREAS)!r}" raise ValueError(msg) cleaned.append(cast("CoverageArea", entry)) return cleaned
__all__ = ["_COVERAGE_AREAS", "_INTENT_VERB_SET", "CoverageArea", "Summary"]