"""Proposal provider registry — K-Ext-1 / P6.""" from __future__ import annotations from dataclasses import dataclass, field from typing import Any, Callable from steering.eval_context import SteeringEvalContext ProposalCompute = Callable[[SteeringEvalContext, dict[str, Any]], Any] @dataclass(frozen=True) class ProposalProvider: key: str requires_elements: frozenset[str] requires_data_slices: frozenset[str] compute: ProposalCompute excludes_elements: frozenset[str] = field(default_factory=frozenset) _PROVIDERS: list[ProposalProvider] = [] def register_proposal(provider: ProposalProvider) -> None: _PROVIDERS.append(provider) def compute_proposals( ctx: SteeringEvalContext, *, read_models: dict[str, Any], ) -> dict[str, Any]: elements = ctx.steering_elements slices = frozenset(ctx.data_slices) result: dict[str, Any] = {} for provider in _PROVIDERS: if provider.requires_elements and not provider.requires_elements <= elements: continue if provider.excludes_elements and provider.excludes_elements & elements: continue if provider.requires_data_slices and not provider.requires_data_slices <= slices: continue result[provider.key] = provider.compute(ctx, read_models) return result def _register_builtin_proposals() -> None: from steering.proposals.gate_next_actions import propose_gate_next_actions from steering.proposals.intake_triage import propose_intake_triage from steering.proposals.sprint_commit import propose_sprint_commit register_proposal( ProposalProvider( key="sprint_commit", requires_elements=frozenset({"work_cycle_scope"}), requires_data_slices=frozenset({"backlog", "work_cycles"}), compute=lambda ctx, read_models: propose_sprint_commit( ctx, read_models=read_models ), ) ) register_proposal( ProposalProvider( key="gate_next_actions", requires_elements=frozenset({"gate_fulfillment"}), requires_data_slices=frozenset({"backlog", "roadmap", "actions"}), compute=lambda ctx, read_models: propose_gate_next_actions( ctx, read_models=read_models ), ) ) register_proposal( ProposalProvider( key="intake_triage", requires_elements=frozenset(), requires_data_slices=frozenset({"backlog"}), excludes_elements=frozenset({"work_cycle_scope"}), compute=lambda ctx, read_models: propose_intake_triage( ctx, read_models=read_models ), ) ) _register_builtin_proposals()