From 6c11205339c2902d6b910cd2c4744be8c6b4972a Mon Sep 17 00:00:00 2001 From: "Xin.Zh" Date: Sun, 23 Aug 2026 16:55:53 +0800 Subject: [PATCH 1/2] chore: strengthen Python static analysis --- .pre-commit-config.yaml | 9 +++++++ Makefile | 2 -- .../powercontext/hooks/user_prompt_submit.py | 12 +++++++++- .../plugins/powercontext/hooks/recall.py | 3 +++ .../codex/plugins/powercontext/pyproject.toml | 1 + .../codex/plugins/powercontext/settings.py | 4 ++++ .../codex/plugins/powercontext/uv.lock | 8 +++++-- .../hermes/plugins/powercontext/client.py | 12 +++++++++- pyproject.toml | 24 +++++++++++++++++++ .../builtin/handoff_report/catalog_store.py | 3 +++ .../builtin/runtime/application.py | 6 +++-- .../builtin/runtime/composition.py | 3 +++ src/powercontext/client/client.py | 4 ++-- src/powercontext/server/access.py | 2 ++ src/powercontext/server/logging.py | 4 ++++ src/powercontext/server/mcp.py | 2 ++ src/powercontext/server/metrics.py | 2 ++ src/powercontext/server/tracing.py | 4 ++++ tests/builtin/runtime/test_scheduler.py | 5 ++-- tests/e2e/real_experience_skill/harness.py | 7 +++--- tests/integrations/test_hermes_provider.py | 5 ++-- tests/test_handoff_report_models.py | 2 +- uv.lock | 2 ++ 23 files changed, 108 insertions(+), 18 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8f44eb2de..39faa62fa 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,3 +21,12 @@ repos: - id: ruff-check args: [--exit-non-zero-on-fix] - id: ruff-format + + - repo: local + hooks: + - id: ty-check + name: ty check + entry: uv run --locked ty check + language: system + pass_filenames: false + always_run: true diff --git a/Makefile b/Makefile index 323f00c0e..f858955fc 100644 --- a/Makefile +++ b/Makefile @@ -16,8 +16,6 @@ check: ## Run code quality tools. @uv lock --locked @echo "🚀 Linting code: Running prek" @uv run prek run -a - @echo "🚀 Static type checking: Running ty" - @uv run ty check .PHONY: test test: ## Test the code with pytest diff --git a/integrations/claude-code/plugins/powercontext/hooks/user_prompt_submit.py b/integrations/claude-code/plugins/powercontext/hooks/user_prompt_submit.py index 0975f34e2..775121d53 100644 --- a/integrations/claude-code/plugins/powercontext/hooks/user_prompt_submit.py +++ b/integrations/claude-code/plugins/powercontext/hooks/user_prompt_submit.py @@ -24,10 +24,19 @@ from hashlib import sha256 from pathlib import Path from time import monotonic -from typing import Any, Protocol, cast +from typing import TYPE_CHECKING, Any, Protocol, TypeVar, cast from urllib.error import HTTPError from urllib.request import HTTPRedirectHandler, Request, build_opener +if TYPE_CHECKING: + from typing_extensions import override +else: + _MethodT = TypeVar("_MethodT") + + def override(method: _MethodT, /) -> _MethodT: + return method + + _PLUGIN_ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(_PLUGIN_ROOT)) @@ -62,6 +71,7 @@ def read(self, amount: int = -1) -> bytes: ... class _RejectRedirects(HTTPRedirectHandler): """Leave every 3xx response to urllib's default HTTP error handler.""" + @override def redirect_request( self, req: Request, diff --git a/integrations/codex/plugins/powercontext/hooks/recall.py b/integrations/codex/plugins/powercontext/hooks/recall.py index 538b5e65f..f271970cc 100644 --- a/integrations/codex/plugins/powercontext/hooks/recall.py +++ b/integrations/codex/plugins/powercontext/hooks/recall.py @@ -30,6 +30,8 @@ from urllib.error import HTTPError from urllib.request import HTTPRedirectHandler, Request, build_opener +from typing_extensions import override + _PLUGIN_ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(_PLUGIN_ROOT)) @@ -64,6 +66,7 @@ def read(self, amount: int = -1) -> bytes: ... class _RejectRedirects(HTTPRedirectHandler): """Leave every 3xx response to urllib's default HTTP error handler.""" + @override def redirect_request( self, req: Request, diff --git a/integrations/codex/plugins/powercontext/pyproject.toml b/integrations/codex/plugins/powercontext/pyproject.toml index d79adb268..2f30b8336 100644 --- a/integrations/codex/plugins/powercontext/pyproject.toml +++ b/integrations/codex/plugins/powercontext/pyproject.toml @@ -18,6 +18,7 @@ version = "0.2.0" requires-python = ">=3.11,<4.0" dependencies = [ "pydantic-settings>=2.7,<3", + "typing-extensions>=4.12,<5", ] [tool.uv] diff --git a/integrations/codex/plugins/powercontext/settings.py b/integrations/codex/plugins/powercontext/settings.py index 07ebf6746..797d12b33 100644 --- a/integrations/codex/plugins/powercontext/settings.py +++ b/integrations/codex/plugins/powercontext/settings.py @@ -23,6 +23,7 @@ from pydantic import BaseModel, ConfigDict, Field, SecretStr, field_validator, model_validator from pydantic.fields import FieldInfo from pydantic_settings import BaseSettings, PydanticBaseSettingsSource, SettingsConfigDict +from typing_extensions import override _MCP_CONFIGURATION_PATH = Path(__file__).with_name(".mcp.json") _LOOPBACK_HOSTS = frozenset({"127.0.0.1", "::1", "localhost"}) @@ -60,11 +61,13 @@ def validate_server_set(self) -> _McpConfiguration: class _McpEndpointSettingsSource(PydanticBaseSettingsSource): """Load the hook endpoint from the same file consumed by Codex MCP.""" + @override def get_field_value(self, field: FieldInfo, field_name: str) -> tuple[Any, str, bool]: if field_name == "server_url": return _server_url_from_mcp_configuration(), field_name, False return None, field_name, False + @override def __call__(self) -> dict[str, Any]: return {"server_url": _server_url_from_mcp_configuration()} @@ -114,6 +117,7 @@ def validate_authorization(cls, value: SecretStr | None) -> SecretStr | None: return value @classmethod + @override def settings_customise_sources( cls, settings_cls: type[BaseSettings], diff --git a/integrations/codex/plugins/powercontext/uv.lock b/integrations/codex/plugins/powercontext/uv.lock index c6881f67a..6c5591c1d 100644 --- a/integrations/codex/plugins/powercontext/uv.lock +++ b/integrations/codex/plugins/powercontext/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.11, <4.0" [[package]] @@ -17,10 +17,14 @@ version = "0.2.0" source = { virtual = "." } dependencies = [ { name = "pydantic-settings" }, + { name = "typing-extensions" }, ] [package.metadata] -requires-dist = [{ name = "pydantic-settings", specifier = ">=2.7,<3" }] +requires-dist = [ + { name = "pydantic-settings", specifier = ">=2.7,<3" }, + { name = "typing-extensions", specifier = ">=4.12,<5" }, +] [[package]] name = "pydantic" diff --git a/integrations/hermes/plugins/powercontext/client.py b/integrations/hermes/plugins/powercontext/client.py index 5d8472461..1a7d4863a 100644 --- a/integrations/hermes/plugins/powercontext/client.py +++ b/integrations/hermes/plugins/powercontext/client.py @@ -19,10 +19,19 @@ import json from collections.abc import Callable from http.client import HTTPResponse -from typing import Any +from typing import TYPE_CHECKING, Any, TypeVar from urllib.error import HTTPError, URLError from urllib.request import HTTPRedirectHandler, Request, build_opener +if TYPE_CHECKING: + from typing_extensions import override +else: + _MethodT = TypeVar("_MethodT") + + def override(method: _MethodT, /) -> _MethodT: + return method + + MAX_RESPONSE_BYTES = 1_048_576 @@ -43,6 +52,7 @@ class PowerContextTransportError(PowerContextError): class _NoRedirectHandler(HTTPRedirectHandler): + @override def redirect_request(self, req: Request, fp: Any, code: int, msg: str, headers: Any, newurl: str) -> Request | None: return None diff --git a/pyproject.toml b/pyproject.toml index 8cf2989e9..b7ace138f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ requires-python = ">=3.11,<4.0" dependencies = [ "pydantic>=2.10,<3", "rfc8785>=0.1.4,<1", + "typing-extensions>=4.12,<5", ] classifiers = [ "Intended Audience :: Developers", @@ -120,6 +121,21 @@ extra-paths = [ [tool.ty.src] exclude = ["e2e/bub", "integrations/bub", "evaluation"] +[tool.ty.rules] +division-by-zero = "error" +missing-override-decorator = "error" +missing-type-argument = "error" +possibly-missing-attribute = "error" +possibly-missing-import = "error" +possibly-unresolved-reference = "error" +unsupported-dynamic-base = "error" + +[[tool.ty.overrides]] +include = ["tests/**"] + +[tool.ty.overrides.rules] +missing-override-decorator = "ignore" + [tool.pytest.ini_options] testpaths = ["tests"] markers = [ @@ -133,6 +149,8 @@ fix = true [tool.ruff.lint] select = [ + # flake8-async + "ASYNC", # flake8-2020 "YTT", # flake8-bandit @@ -145,6 +163,10 @@ select = [ "C4", # flake8-debugger "T10", + # flake8-datetimez + "DTZ", + # flake8-logging + "LOG", # flake8-simplify "SIM", # isort @@ -155,6 +177,8 @@ select = [ "E", "W", # pyflakes "F", + # pylint errors + "PLE", # pygrep-hooks "PGH", # pyupgrade diff --git a/src/powercontext/builtin/handoff_report/catalog_store.py b/src/powercontext/builtin/handoff_report/catalog_store.py index 0691ec133..dda79e185 100644 --- a/src/powercontext/builtin/handoff_report/catalog_store.py +++ b/src/powercontext/builtin/handoff_report/catalog_store.py @@ -42,6 +42,7 @@ ) from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.asyncio import AsyncConnection +from typing_extensions import override from powercontext.builtin.handoff_report.errors import ( HandoffReportCatalogArgumentError, @@ -151,9 +152,11 @@ def __init__(self, items: tuple[CatalogItemT, ...], next_cursor: str | None) -> self.items = items self.next_cursor = next_cursor + @override def __repr__(self) -> str: return f"CatalogPage(items={self.items!r}, next_cursor={self.next_cursor!r})" + @override def __eq__(self, other: object) -> bool: return isinstance(other, CatalogPage) and self.items == other.items and self.next_cursor == other.next_cursor diff --git a/src/powercontext/builtin/runtime/application.py b/src/powercontext/builtin/runtime/application.py index e41c96479..b3a7d8cf9 100644 --- a/src/powercontext/builtin/runtime/application.py +++ b/src/powercontext/builtin/runtime/application.py @@ -338,6 +338,7 @@ async def prepare(self, request: PrepareContextRequest, /) -> PreparedContext: service = context.artifacts.memory current = await _head_or_none(service, context.artifacts.memory_artifact_id) memory_hits = () + search_mode: str | None = None if current is not None: result = await service.search( request.query, @@ -346,13 +347,14 @@ async def prepare(self, request: PrepareContextRequest, /) -> PreparedContext: mode="auto", ) memory_hits = result.hits + search_mode = result.mode if span is not None: attributes: dict[str, TraceAttribute] = { _MEMORY_SEARCH_MEMORY_PRESENT: current is not None, _MEMORY_SEARCH_RESULT_COUNT: len(memory_hits), } - if current is not None: - attributes[_MEMORY_SEARCH_MODE] = result.mode + if search_mode is not None: + attributes[_MEMORY_SEARCH_MODE] = search_mode span.set_attributes(attributes) experience_recall = self._runtime._experience_recall diff --git a/src/powercontext/builtin/runtime/composition.py b/src/powercontext/builtin/runtime/composition.py index 14f3b3637..144146db9 100644 --- a/src/powercontext/builtin/runtime/composition.py +++ b/src/powercontext/builtin/runtime/composition.py @@ -22,6 +22,7 @@ from typing import TYPE_CHECKING, TypeVar from pydantic import JsonValue +from typing_extensions import override from powercontext.builtin.artifacts.experience import ExperienceCandidatePipeline, ExperienceGenerator from powercontext.builtin.artifacts.handoff import ( @@ -94,6 +95,7 @@ def __init__(self, issue: str) -> None: class _ContentEvidenceProjector(DefaultMemoryEvidenceProjector): + @override def project_source(self, source: Source, /) -> JsonValue: if isinstance(source, ContentSource): return { @@ -106,6 +108,7 @@ def project_source(self, source: Source, /) -> JsonValue: class _ContentHandoffEvidenceProjector(DefaultHandoffEvidenceProjector): + @override def project_source(self, source: Source, /) -> JsonValue: if isinstance(source, ContentSource): return { diff --git a/src/powercontext/client/client.py b/src/powercontext/client/client.py index cccb3d2de..718360e73 100644 --- a/src/powercontext/client/client.py +++ b/src/powercontext/client/client.py @@ -357,8 +357,8 @@ async def _request_handoff_report_content(self, request: GetHandoffReportRequest mode="json", by_alias=True, ) + span = ClientSpan.start(GET_HANDOFF_REPORT.operation_id) try: - span = ClientSpan.start(GET_HANDOFF_REPORT.operation_id) headers = {} if self._headers is None else dict(self._headers) span.inject(headers) response = await self._http_client.request( @@ -582,8 +582,8 @@ async def _request( else: json_payload = payload + span = ClientSpan.start(operation.operation_id) try: - span = ClientSpan.start(operation.operation_id) headers = {} if self._headers is None else dict(self._headers) span.inject(headers) response = await self._http_client.request( diff --git a/src/powercontext/server/access.py b/src/powercontext/server/access.py index d718da1d7..4663e4d6d 100644 --- a/src/powercontext/server/access.py +++ b/src/powercontext/server/access.py @@ -23,6 +23,7 @@ from fastmcp.server.middleware import CallNext, Middleware, MiddlewareContext from starlette.types import ASGIApp, Message, Receive, Scope, Send +from typing_extensions import override from powercontext._logging import log_safely from powercontext.server.context import current_request_id, is_internal_bridge @@ -76,6 +77,7 @@ async def send_with_access_log(message: Message) -> None: class McpAccessLogMiddleware(Middleware): """Log logical MCP protocol requests instead of Streamable HTTP frames.""" + @override async def on_request( self, context: MiddlewareContext[Any], diff --git a/src/powercontext/server/logging.py b/src/powercontext/server/logging.py index a2ea642a4..36f569c64 100644 --- a/src/powercontext/server/logging.py +++ b/src/powercontext/server/logging.py @@ -23,6 +23,7 @@ from typing import Any from opentelemetry import trace +from typing_extensions import override from powercontext.server.settings import ServerLoggingConfig @@ -43,6 +44,7 @@ class OperationalContextFilter(logging.Filter): + @override def filter(self, record: logging.LogRecord) -> bool: span_context = trace.get_current_span().get_span_context() if span_context.is_valid: @@ -59,6 +61,7 @@ def filter(self, record: logging.LogRecord) -> bool: class JsonFormatter(logging.Formatter): """Render a stable operational record without serializing arbitrary extras.""" + @override def format(self, record: logging.LogRecord) -> str: payload: dict[str, Any] = { "timestamp": datetime.fromtimestamp(record.created, UTC).isoformat(), @@ -75,6 +78,7 @@ def format(self, record: logging.LogRecord) -> str: class _HumanContextFilter(OperationalContextFilter): + @override def filter(self, record: logging.LogRecord) -> bool: super().filter(record) request_id = getattr(record, "request_id", None) diff --git a/src/powercontext/server/mcp.py b/src/powercontext/server/mcp.py index a5d8104d4..42b1bd3e6 100644 --- a/src/powercontext/server/mcp.py +++ b/src/powercontext/server/mcp.py @@ -29,6 +29,7 @@ from fastmcp.utilities.lifespan import combine_lifespans from fastmcp.utilities.openapi import HTTPRoute from mcp.types import ToolAnnotations +from typing_extensions import override from powercontext.http._generated.operations import ( ACKNOWLEDGE_HANDOFF, @@ -179,6 +180,7 @@ def create_mcp_server( class _InternalBridgeTransport(httpx.ASGITransport): + @override async def handle_async_request(self, request: httpx.Request) -> httpx.Response: request_id = current_request_id() if request_id is not None: diff --git a/src/powercontext/server/metrics.py b/src/powercontext/server/metrics.py index f6f78f126..287d7e473 100644 --- a/src/powercontext/server/metrics.py +++ b/src/powercontext/server/metrics.py @@ -24,6 +24,7 @@ from fastmcp.server.middleware import CallNext, Middleware, MiddlewareContext from prometheus_client import CONTENT_TYPE_LATEST, CollectorRegistry, Counter, Gauge, Histogram, generate_latest from starlette.types import ASGIApp, Message, Receive, Scope, Send +from typing_extensions import override from powercontext.server.context import is_internal_bridge @@ -164,6 +165,7 @@ class McpMetricsMiddleware(Middleware): def __init__(self, metrics: ServerMetrics) -> None: self.metrics = metrics + @override async def on_request( self, context: MiddlewareContext[Any], diff --git a/src/powercontext/server/tracing.py b/src/powercontext/server/tracing.py index b15cec0f0..b903c8732 100644 --- a/src/powercontext/server/tracing.py +++ b/src/powercontext/server/tracing.py @@ -35,6 +35,7 @@ from opentelemetry.sdk.trace.sampling import ALWAYS_OFF, ParentBased from opentelemetry.trace import Span, SpanKind, Status, StatusCode, Tracer, set_span_in_context from starlette.types import ASGIApp, Message, Receive, Scope, Send +from typing_extensions import override from powercontext.server.context import bind_request_id, is_internal_bridge, reset_request_id from powercontext.server.settings import TracingConfig @@ -60,9 +61,11 @@ def __init__(self, delegate: Tracer, suppressed: ContextVar[bool]) -> None: self._suppressed = suppressed self._noop = trace.NoOpTracer() + @override def start_span(self, name: str, *args: Any, **kwargs: Any) -> Span: return self._selected().start_span(name, *args, **kwargs) + @override def start_as_current_span(self, name: str, *args: Any, **kwargs: Any) -> Any: return self._selected().start_as_current_span(name, *args, **kwargs) @@ -298,6 +301,7 @@ class McpTracingMiddleware(Middleware): def __init__(self, tracing: ServerTracing) -> None: self.tracing = tracing + @override async def on_request( self, context: MiddlewareContext[Any], diff --git a/tests/builtin/runtime/test_scheduler.py b/tests/builtin/runtime/test_scheduler.py index 0b9907848..855e8c8f8 100644 --- a/tests/builtin/runtime/test_scheduler.py +++ b/tests/builtin/runtime/test_scheduler.py @@ -17,6 +17,7 @@ import asyncio import logging import sqlite3 +from typing import Any import pytest @@ -38,10 +39,10 @@ class _Provider: - def __init__(self, context: PowerContext) -> None: + def __init__(self, context: PowerContext[Any, Any, Any]) -> None: self.context = context - async def get(self, scope_id: str, /) -> PowerContext: + async def get(self, scope_id: str, /) -> PowerContext[Any, Any, Any]: del scope_id return self.context diff --git a/tests/e2e/real_experience_skill/harness.py b/tests/e2e/real_experience_skill/harness.py index 5784b6c9b..392c51aed 100644 --- a/tests/e2e/real_experience_skill/harness.py +++ b/tests/e2e/real_experience_skill/harness.py @@ -283,6 +283,7 @@ def main(argv: Sequence[str] | None = None) -> int: # noqa: C901 - one exceptio arguments = _arguments(argv) configured_settings: ServerSettings | None = None configured_scopes: ConfiguredScopes | None = None + external_skill: Path | None = None if arguments.configured: load_dotenv(arguments.env_file, override=False) configured_settings = ServerSettings() @@ -379,7 +380,7 @@ def main(argv: Sequence[str] | None = None) -> int: # noqa: C901 - one exceptio ) ) else: - if configured_scopes is None or configured_server_settings is None: + if configured_scopes is None or configured_server_settings is None or external_skill is None: _fail("configured E2E state was not initialized") journey = asyncio.run( _run_configured_journey( @@ -472,7 +473,7 @@ async def _run_journey( codex_environment: Mapping[str, str], repositories: Mapping[str, Path], server_url: str, - timeout: int, + timeout: int, # noqa: ASYNC109 - external Codex process budget, not an asyncio timeout scope ) -> None: scope_id = f"real-codex-experience-skill:{int(time.time())}" producer_schema = recorder.write_json("schemas/experience.json", PRODUCER_SCHEMA) @@ -751,7 +752,7 @@ async def _run_configured_journey( external_skill: Path, scopes: ConfiguredScopes, server_url: str, - timeout: int, + timeout: int, # noqa: ASYNC109 - external Codex process budget, not an asyncio timeout scope generation_timeout: float, ) -> ConfiguredJourneyState: memory_scope = scopes.memory diff --git a/tests/integrations/test_hermes_provider.py b/tests/integrations/test_hermes_provider.py index 53e2e74e1..86ee52a9c 100644 --- a/tests/integrations/test_hermes_provider.py +++ b/tests/integrations/test_hermes_provider.py @@ -21,6 +21,7 @@ import sys import threading from pathlib import Path +from typing import Any import pytest @@ -50,11 +51,11 @@ def hermes_modules(monkeypatch): class FakeClient: def __init__(self) -> None: - self.calls: list[tuple[str, tuple, dict]] = [] + self.calls: list[tuple[str, tuple[Any, ...], dict[str, Any]]] = [] self.base_url = "http://powercontext.test:8000" self._remember_count = 0 self._revision = 0 - self._memory_entries: dict[str, dict] = {} + self._memory_entries: dict[str, dict[str, Any]] = {} self.memory_extraction = True def prepare_context(self, scope_id, query, *, max_bytes): diff --git a/tests/test_handoff_report_models.py b/tests/test_handoff_report_models.py index 28685e984..781409b93 100644 --- a/tests/test_handoff_report_models.py +++ b/tests/test_handoff_report_models.py @@ -140,7 +140,7 @@ def test_activity_event_validates_timestamp_basis_and_utc_observation() -> None: with pytest.raises(ValidationError, match="observed_at must be UTC"): _activity(observed_at=NOW.astimezone(timezone(timedelta(hours=8)))) with pytest.raises(ValidationError, match="occurred_at must include a UTC offset"): - _activity(occurred_at=datetime(2026, 8, 5, 3, 0)) + _activity(occurred_at=datetime(2026, 8, 5, 3, 0)) # noqa: DTZ001 - intentional invalid input def test_report_selection_entry_requires_exact_handoff_or_explicit_absence() -> None: diff --git a/uv.lock b/uv.lock index 70ab422b4..56f97c471 100644 --- a/uv.lock +++ b/uv.lock @@ -1797,6 +1797,7 @@ source = { editable = "." } dependencies = [ { name = "pydantic" }, { name = "rfc8785" }, + { name = "typing-extensions" }, ] [package.optional-dependencies] @@ -1882,6 +1883,7 @@ requires-dist = [ { name = "rfc8785", specifier = ">=0.1.4,<1" }, { name = "sqlalchemy", extras = ["asyncio"], marker = "extra == 'builtin'", specifier = ">=2,<3" }, { name = "typer", marker = "extra == 'cli'", specifier = ">=0.16,<1" }, + { name = "typing-extensions", specifier = ">=4.12,<5" }, { name = "uvicorn", marker = "extra == 'server'", specifier = ">=0.34,<1" }, ] provides-extras = ["builtin", "client", "server", "tracing-otlp", "cli"] From 567bab557caddd7199d1df66d3d1345b96f68506 Mon Sep 17 00:00:00 2001 From: "Xin.Zh" Date: Sun, 23 Aug 2026 17:21:07 +0800 Subject: [PATCH 2/2] fix: sync static checks for Bub harness --- Makefile | 2 +- e2e/bub/src/powercontext_e2e/harbor_agent.py | 3 ++- e2e/bub/uv.lock | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index f858955fc..55400e89a 100644 --- a/Makefile +++ b/Makefile @@ -45,7 +45,7 @@ harness-sync: ## Install the Bub replay harness environment. harness-check: ## Validate the Bub replay harness and committed scenarios. @uv run ruff check e2e/bub @uv run ruff format --check e2e/bub - @uv run ty check --project e2e/bub --python e2e/bub/.venv e2e/bub/src integrations/bub/src + @uv run ty check --project e2e/bub --python e2e/bub/.venv --python-version 3.12 e2e/bub/src integrations/bub/src @uv run --project e2e/bub python -m pytest e2e/bub/tests @uv run --project e2e/bub powercontext-e2e --help >/dev/null diff --git a/e2e/bub/src/powercontext_e2e/harbor_agent.py b/e2e/bub/src/powercontext_e2e/harbor_agent.py index b9bbfaa47..3a9babe0b 100644 --- a/e2e/bub/src/powercontext_e2e/harbor_agent.py +++ b/e2e/bub/src/powercontext_e2e/harbor_agent.py @@ -19,7 +19,7 @@ import shlex from importlib.metadata import version from pathlib import Path -from typing import Any +from typing import Any, override from harbor.agents.installed import acp as harbor_acp from harbor.environments.base import BaseEnvironment @@ -55,6 +55,7 @@ def __init__(self, **kwargs: Any) -> None: **kwargs, ) + @override async def install(self, environment: BaseEnvironment) -> None: await self.exec_as_root( environment, diff --git a/e2e/bub/uv.lock b/e2e/bub/uv.lock index 3e803e6c5..ffb364607 100644 --- a/e2e/bub/uv.lock +++ b/e2e/bub/uv.lock @@ -1603,6 +1603,7 @@ source = { editable = "../../" } dependencies = [ { name = "pydantic" }, { name = "rfc8785" }, + { name = "typing-extensions" }, ] [package.optional-dependencies] @@ -1644,6 +1645,7 @@ requires-dist = [ { name = "sqlalchemy", extras = ["asyncio"], marker = "extra == 'builtin'", specifier = ">=2,<3" }, { name = "sqlalchemy", extras = ["asyncio"], marker = "extra == 'server'", specifier = ">=2,<3" }, { name = "typer", marker = "extra == 'cli'", specifier = ">=0.16,<1" }, + { name = "typing-extensions", specifier = ">=4.12,<5" }, { name = "uvicorn", marker = "extra == 'server'", specifier = ">=0.34,<1" }, ] provides-extras = ["builtin", "cli", "client", "server", "tracing-otlp"]