Skip to content
Merged
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: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

### Added
- Scenario taxonomy for `adr_planning_context` — four named scenarios (`strategic_planning`, `focused_implementation`, `pre_decision`, `supersession_impact`) replace the previous free-text `context_type` field; each scenario produces a different projection of the architecture contract
- `ContextRequest` model — structured request contract with typed `scope_hints`, `change_mode`, `detail_level`, and `known_targets`; existing callers passing only `task_description` continue to work unchanged (defaults to `strategic_planning`)
- `ScenarioContextPacket` response model — scenario-aware response shape with `overview`, ranked `constraints`, `warnings`, and `inspect_deeper` references to ADRs and clauses for progressive disclosure
- `OutputMode` enum (`native_config`, `native_rules`, `generated_checker`, `policy_file`, `script_fallback`) and `EnforcementStage` enum (`commit`, `push`, `ci`) — enforcement adapters now declare what kind of artifact they emit and at which gate it is evaluated, making the enforcement plane's output taxonomy explicit and inspectable
- `FallbackAdapter` — unroutable policy keys now flow through a standard adapter interface rather than a pipeline side path; fallback promptlets appear in `EnforcementResult.fragments_applied` with `output_mode="script_fallback"` alongside native-config fragments, making the full enforcement picture visible in one place
- `output_mode` field on `EnforcementResult.fragments_applied` entries — each applied fragment now reports its output mode, enabling callers to distinguish native enforcement artifacts from agent-authored validation scripts
Expand Down
28 changes: 27 additions & 1 deletion adr_kit/context/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,22 @@

from .analyzer import TaskAnalyzer, TaskContext, TaskType
from .guidance import ContextualPromptlet, GuidanceGenerator, GuidanceType
from .models import ContextPacket, ContextualADR, PlanningGuidance, TaskHint
from .models import (
ChangeMode,
ConstraintSummary,
ContextPacket,
ContextRequest,
ContextScenario,
ContextualADR,
DetailLevel,
InspectReference,
PacketMetadata,
PlanningGuidance,
ScenarioContextPacket,
ScopeHint,
TargetRef,
TaskHint,
)
from .planner import PlanningConfig, PlanningContext
from .ranker import RankingStrategy, RelevanceRanker, RelevanceScore

Expand All @@ -34,4 +49,15 @@
"GuidanceGenerator",
"GuidanceType",
"ContextualPromptlet",
# SCN: scenario taxonomy
"ContextScenario",
"ChangeMode",
"DetailLevel",
"ScopeHint",
"TargetRef",
"ContextRequest",
"ConstraintSummary",
"InspectReference",
"PacketMetadata",
"ScenarioContextPacket",
]
197 changes: 197 additions & 0 deletions adr_kit/context/models.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,209 @@
"""Data models for the Planning Context Service."""

from datetime import datetime, timezone
from enum import Enum
from typing import Any

from pydantic import BaseModel, Field

from ..core.model import ADRStatus

# ---------------------------------------------------------------------------
# Scenario taxonomy (SCN)
# ---------------------------------------------------------------------------


class ContextScenario(str, Enum):
"""Supported context retrieval scenarios for v1."""

STRATEGIC_PLANNING = "strategic_planning"
"""Feature planning, spec writing, architectural exploration."""

FOCUSED_IMPLEMENTATION = "focused_implementation"
"""Coding in a specific area or bounded task."""

PRE_DECISION = "pre_decision"
"""Before committing to a direction — evaluate whether a new ADR is needed."""

SUPERSESSION_IMPACT = "supersession_impact"
"""When an existing decision may change — assess downstream implications."""


class ChangeMode(str, Enum):
"""How the caller intends to change the codebase."""

NONE = "none"
"""Reading or exploring — no changes planned."""

ADDITIVE = "additive"
"""Adding new code or features without changing existing behaviour."""

MODIFYING = "modifying"
"""Changing existing code or behaviour."""

REPLACING = "replacing"
"""Superseding or replacing a decision or component."""


class DetailLevel(str, Enum):
"""How much detail to include in the response packet."""

MINIMAL = "minimal"
"""Constraints only, no explanations."""

STANDARD = "standard"
"""Constraints with brief rationale (default)."""

DETAILED = "detailed"
"""Full context including history and tradeoffs."""


class ScopeHint(BaseModel):
"""Typed scope signal so the kit doesn't have to guess what a string means."""

hint_type: str = Field(
...,
description=("Signal type: 'file_path', 'module', 'domain', 'stack', or 'tag'"),
)
value: str = Field(
...,
description="The signal value, e.g. 'src/auth/', 'authentication', 'backend'",
)


class TargetRef(BaseModel):
"""Typed reference to a known ADR or clause the caller wants included."""

ref_type: str = Field(
...,
description="Reference type: 'adr_id' or 'clause_id'",
)
ref_id: str = Field(
...,
description="The identifier, e.g. 'ADR-003' or a clause UUID",
)


class ContextRequest(BaseModel):
"""Structured request contract for adr_planning_context callers."""

scenario: ContextScenario = Field(
ContextScenario.STRATEGIC_PLANNING,
description="Which scenario this request falls under",
)
task_summary: str = Field(
...,
description="What the caller is trying to accomplish",
)
scope_hints: list[ScopeHint] = Field(
default_factory=list,
description="Typed scope signals (file paths, domains, stack tags, etc.)",
)
change_mode: ChangeMode = Field(
ChangeMode.NONE,
description="How the caller intends to modify the codebase",
)
focus: str = Field(
"",
description="Specific area of concern (free text, feeds semantic retrieval)",
)
known_targets: list[TargetRef] = Field(
default_factory=list,
description="Typed references to ADRs or clauses to include unconditionally",
)
detail_level: DetailLevel = Field(
DetailLevel.STANDARD,
description="How much detail to include in the response",
)


# ---------------------------------------------------------------------------
# Response-side models (SCN)
# ---------------------------------------------------------------------------


class ConstraintSummary(BaseModel):
"""A single enforced constraint derived from an approved ADR."""

clause_id: str | None = Field(
None,
description="Clause identifier (None until clause IDs land in a future item)",
)
source_adr: str = Field(..., description="ADR that produced this constraint")
summary: str = Field(..., description="One-line constraint description")
relevance_score: float = Field(
...,
description="How relevant this constraint is to the request (0.0-1.0)",
)
domain: str | None = Field(
None,
description="Architectural domain this constraint belongs to",
)


class InspectReference(BaseModel):
"""A reference returned so callers can inspect further detail on demand."""

ref_type: str = Field(
...,
description="Reference type: 'adr', 'clause', or 'resource'",
)
ref_id: str = Field(
...,
description="Identifier: ADR-003, clause UUID, or resource URI",
)
label: str = Field(..., description="Human-readable label for display")


class PacketMetadata(BaseModel):
"""Metadata describing how a ScenarioContextPacket was assembled."""

token_estimate: int = Field(
...,
description="Rough token count for the full packet",
)
candidate_count: int = Field(
...,
description="Number of candidates evaluated before ranking",
)
ranking_strategy: str = Field(
"",
description="Strategy used to rank candidates (e.g. 'semantic', 'exact')",
)


class ScenarioContextPacket(BaseModel):
"""Scenario-aware response packet (v2).

Coexists with the legacy ContextPacket. New tool paths return this shape;
the legacy ContextPacket remains in place for existing callers.
"""

scenario: ContextScenario = Field(
...,
description="Scenario this packet was assembled for",
)
overview: str = Field(
...,
description="1-3 sentence architectural orientation for the scenario",
)
constraints: list[ConstraintSummary] = Field(
default_factory=list,
description="Relevant constraints, ranked by relevance",
)
warnings: list[str] = Field(
default_factory=list,
description="AI warnings and tradeoff notes",
)
inspect_deeper: list[InspectReference] = Field(
default_factory=list,
description="References to ADRs, clauses, or resources for further inspection",
)
metadata: PacketMetadata | None = Field(
None,
description="Assembly metadata (token estimate, candidate count, etc.)",
)


class TaskHint(BaseModel):
"""Hints about the task that help determine relevant context."""
Expand Down
40 changes: 39 additions & 1 deletion adr_kit/mcp/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@

from pydantic import BaseModel, Field

from ..context.models import (
ChangeMode,
ContextScenario,
DetailLevel,
ScopeHint,
TargetRef,
)


class MCPStatus(str, Enum):
"""Standard status codes for MCP responses."""
Expand Down Expand Up @@ -160,8 +168,14 @@ class SupersedeADRRequest(BaseModel):


class PlanningContextRequest(BaseModel):
"""Parameters for architectural context for agent tasks."""
"""Parameters for architectural context for agent tasks.

Legacy fields (task_description, context_type, domain_hints, priority_level) are
preserved for backward compatibility. New callers should also supply the scenario
fields — if omitted, scenario defaults to STRATEGIC_PLANNING.
"""

# --- legacy fields (unchanged, all existing callers continue to work) ---
task_description: str = Field(
..., description="Description of what the agent is trying to do"
)
Expand All @@ -179,6 +193,30 @@ class PlanningContextRequest(BaseModel):
)
adr_dir: str = Field("docs/adr", description="ADR directory path")

# --- scenario fields (all optional, backward-compat defaults) ---
scenario: ContextScenario = Field(
ContextScenario.STRATEGIC_PLANNING,
description=(
"Context scenario type; inferred as STRATEGIC_PLANNING when omitted"
),
)
change_mode: ChangeMode | None = Field(
None,
description="How the caller intends to change the codebase (optional)",
)
detail_level: DetailLevel = Field(
DetailLevel.STANDARD,
description="How much detail to include in the response",
)
scope: ScopeHint | None = Field(
None,
description="Typed scope signal for narrowing retrieval (optional)",
)
target: TargetRef | None = Field(
None,
description="Typed reference to a specific ADR or clause to include (optional)",
)


class DecisionGuidanceRequest(BaseModel):
"""Parameters for getting decision quality guidance."""
Expand Down
Loading
Loading