feat(context): implement pluggable scope chain resolution extension API

This commit is contained in:
2026-04-19 01:19:35 +00:00
committed by Forgejo
parent f5f75e65c2
commit 31518aa8ef
4 changed files with 179 additions and 1 deletions
+11
View File
@@ -0,0 +1,11 @@
"""Example scope chain resolvers."""
from examples.scope_resolvers.git_issue_resolver import (
GitIssueResolver,
create_git_issue_resolver,
)
__all__ = [
"GitIssueResolver",
"create_git_issue_resolver",
]
@@ -0,0 +1,43 @@
"""Example scope chain resolver for Git issue references."""
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from cleveragents.domain.contexts import (
ScopeChainResolver,
ScopeResolutionContext,
)
class GitIssueResolver:
"""Example resolver for Git issue scope references."""
def __init__(self, repo_path: str | None = None) -> None:
"""Initialize the resolver."""
self.repo_path = repo_path or "."
def resolve(
self,
scope: str,
context: ScopeResolutionContext,
) -> list[str]:
"""Resolve a Git issue scope reference."""
if not scope.startswith("issue:"):
return []
try:
issue_id = scope[6:]
if not issue_id:
return []
fragment_id = f"git_issue_{issue_id}"
return [fragment_id]
except Exception:
return []
def create_git_issue_resolver() -> GitIssueResolver:
"""Factory function for creating a GitIssueResolver instance."""
return GitIssueResolver()
+10 -1
View File
@@ -1,11 +1,20 @@
"""Contexts domain module.
Contains pipeline-specific domain models for the ACMS context assembly
pipeline, including ``ScoredFragment`` for scored/ranked fragments.
pipeline, including ``ScoredFragment`` for scored/ranked fragments and
pluggable scope chain resolution extension API.
"""
from cleveragents.domain.contexts.fragment import ScoredFragment
from cleveragents.domain.contexts.scope_chain_resolver import (
ScopeChainResolver,
ScopeResolutionContext,
ScopeResolverRegistry,
)
__all__ = [
"ScoredFragment",
"ScopeChainResolver",
"ScopeResolutionContext",
"ScopeResolverRegistry",
]
@@ -0,0 +1,115 @@
"""Pluggable scope chain resolution extension API."""
from __future__ import annotations
from typing import Any, Protocol
from pydantic import BaseModel, Field
class ScopeResolutionContext(BaseModel):
"""Context passed to scope resolvers during resolution."""
scope: str = Field(
...,
description="The scope reference to resolve (e.g., 'issue:123').",
)
metadata: dict[str, Any] = Field(
default_factory=dict,
description="Additional metadata for resolution (e.g., project context).",
)
resolved_fragments: list[str] = Field(
default_factory=list,
description="List of already-resolved fragment identifiers.",
)
class ScopeChainResolver(Protocol):
"""Protocol for custom scope chain resolvers."""
def resolve(
self,
scope: str,
context: ScopeResolutionContext,
) -> list[str]:
"""Resolve a scope reference to a list of fragment identifiers."""
...
class ScopeResolverRegistry:
"""Registry for discovering and managing scope chain resolvers."""
def __init__(self) -> None:
"""Initialize the registry and discover resolvers from entry points."""
self._resolvers: dict[str, tuple[ScopeChainResolver, int]] = {}
self._discover_resolvers()
def _discover_resolvers(self) -> None:
"""Discover resolvers from Python entry points."""
try:
import importlib.metadata as metadata
except ImportError:
import importlib_metadata as metadata # type: ignore
try:
entry_points = metadata.entry_points()
if hasattr(entry_points, "select"):
scope_resolvers = entry_points.select(
group="cleveragents.scope_resolvers"
)
else:
scope_resolvers = entry_points.get("cleveragents.scope_resolvers", [])
for ep in scope_resolvers:
try:
resolver_factory = ep.load()
resolver = resolver_factory()
priority = getattr(ep, "priority", 0)
self._resolvers[ep.name] = (resolver, priority)
except Exception:
pass
except Exception:
pass
def register(
self,
name: str,
resolver: ScopeChainResolver,
priority: int = 0,
) -> None:
"""Register a resolver with the given name and priority."""
self._resolvers[name] = (resolver, priority)
def unregister(self, name: str) -> None:
"""Unregister a resolver by name."""
self._resolvers.pop(name, None)
def resolve(
self,
scope: str,
context: ScopeResolutionContext,
) -> list[str]:
"""Resolve a scope using registered resolvers in priority order."""
sorted_resolvers = sorted(
self._resolvers.values(),
key=lambda x: x[1],
reverse=True,
)
for resolver, _ in sorted_resolvers:
result = resolver.resolve(scope, context)
if result:
return result
return []
def get_resolvers(self) -> dict[str, tuple[ScopeChainResolver, int]]:
"""Get all registered resolvers with their priorities."""
return dict(self._resolvers)
def list_resolvers(self) -> list[tuple[str, int]]:
"""List all registered resolvers with their priorities."""
items = [(name, priority) for name, (_, priority) in self._resolvers.items()]
return sorted(items, key=lambda x: x[1], reverse=True)