diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index b81fe2b58..2aa93a814 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -67,6 +67,7 @@ Below are some of the specific details of various contributions. * HAL 9000 has contributed the ACMS context path matching fix (PR #10975 / issue #10972): corrects `_path_matches()` and `_matches_pattern()` to properly match absolute fragment paths against relative glob patterns by auto-prefixing with `**/` before calling `PurePath.full_match()`, preventing silent inefficacy of include/exclude filters for absolute paths in fragment metadata. * HAL 9000 has contributed database resource types (PostgreSQL, SQLite) with transaction-based sandbox strategy: implemented ``DatabaseResourceHandler`` providing full CRUD operations (`read`, `write`, `delete`, `list_children`) and connection validation with automatic credential masking for PostgreSQL and SQLite backends. Includes ``TransactionSandbox`` infrastructure wired into ``SandboxFactory``, BDD test coverage in ``features/database_resources.feature``, and Robot Framework integration tests in ``robot/database_resources.robot`` (PR #10591 / issue #8608, Epic #8568). * HAL 9000 has contributed the agents plan rollback command (PR #8674 / issue #8557): implemented checkpoint-based plan state restoration with the `agents plan rollback []` CLI command as part of Epic #8493, enabling plans to be restored to previous checkpoints, discarding post-checkpoint decisions, and resuming execution from the rolled-back state. Supported by `--yes/-y`, `--to-checkpoint`, and `--format/-f` flags. Includes comprehensive BDD test coverage (>= 97%) for rollback, decision discarding, and plan resume functionality. +* Jeffrey Phillips Freeman has contributed ExecutePhaseDecisionHook for Epic #8477: implemented the Execute-phase mirror of StrategizeDecisionHook, providing six recording methods (implementation choices, tool invocations, error recovery, validation responses, subplan spawn, resource selection) with context snapshot auto-capture and comprehensive Behave test coverage including phase-gating, error handling, and full tree path scenarios. * HAL 9000 has contributed the PyYAML security upgrade (PR #11012 / issue #9055): added `pyyaml>=6.0.3` dependency constraint to address known YAML parsing vulnerabilities. * HAL 9000 has contributed the DecisionService wiring for PlanExecutor strategize persistence fix (#10813): added decision_service to the PlanExecutor constructor and wired it from the CLI dependency-injection container in `_get_plan_executor()`, plus implemented `_persist_strategy_decisions()` to persist strategy decisions as domain `Decision` objects. * HAL 9000 has contributed the A2A module rename standardization BDD tests (PR #10583 / issue #8615): comprehensive Behave test suite validating that all 22 A2A symbols are properly exported from `cleveragents.a2a`, no legacy ACP references remain in the module source, and documentation uses correct A2A naming conventions — fixing inline imports, unused behave symbols, cross-scenario context dependencies, and missing type annotations. diff --git a/features/execute_decision_recording.feature b/features/execute_decision_recording.feature new file mode 100644 index 000000000..a6955b0d2 --- /dev/null +++ b/features/execute_decision_recording.feature @@ -0,0 +1,73 @@ +Feature: ExecutePhaseDecisionHook records execute-phase decisions + As a developer + I want decisions during the Execute phase recorded via ExecHook + So that Strategize+Execute form a single unified decision tree + + Background: + Given dexe plan "P1" ULID and service initialized + And an ExecHook created for plan "P1" with parent None + + Scenario: Record implementation choice via ExecHook + When dexe record impl_choice question="Which sort?" chosen="Quick" + Then dexe decision recorded successfully type="implementation_choice" + And dexe decision recorded successfully phase="execute" + + Scenario: Record tool invocation via ExecHook + When dexe record tool_inv q="Tool?" tool_write="file_tool" + Then dexe decision recorded successfully type="tool_invocation" + + Scenario: Record error recovery via ExecHook + When dexe record error_recove q="File miss" action_ret="Retry" + Then dexe decision recorded successfully type="error_recovery" + And dexe decision recorded successfully phase="execute" + + Scenario: Record validation response via ExecHook + When dexe record val_respond q="Lint fail" fix="Manual" + Then dexe decision recorded successfully type="validation_response" + + Scenario: Record subplan spawn via ExecHook (Execute) + When dexe record sub_spawn q="Extra transform" chosen="ChildPlan" + Then dexe decision recorded successfully type="subplan_spawn" + And dexe decision recorded successfully phase="execute" + + Scenario: Record parallel subplan spawn via ExecHook + When dexe record par_spawn alt="SeqTrans" chosen="ParallelGroup" + Then dexe decision recorded successfully type="subplan_parallel_spawn" + + Scenario: Record resource selection via ExecHook (Execute) + When dexe record res_select q="Files?" chosen="src/*.py,tests/*.py" + Then dexe decision recorded successfully type="resource_selection" + And dexe decision recorded successfully phase="execute" + + Scenario: Decision with alternatives and confidence + When dexe record impl_choice question="Sort?" alts="Merge|Heap|Quick" chosen="Quick" conf=0.8 + Then dexe decision recorded successfully alternatives_count=3 + And dexe decision recorded successfully confidence_score=0.8 + + Scenario: Capture context snapshot hash + When dexe record impl_choice question="ctx test" chose="A" with_context_json=1 + Then dexe decision recorded successfully snapshot_hash_not_empty=True + + Scenario: Empty question raises ValidationError + When dexe record impl_choice question="" chosen="X" expect_error=True + Then dexe validation error raised mentions="question" + + Scenario: Whitespace question raises ValidationError + When dexe record impl_choice question=" " chosen="X" expect_error=True + Then dexe validation error raised mentions="question" + + Scenario: Empty chosen_option raises ValidationError + When dexe record tool_inv q="Q" tool_write="" expect_error=True + Then dexe validation error raised mentions="chosen_option" + + Scenario: Whitespace chosen_option raises ValidationError + When dexe record tool_inv q="Q" tool_write=" " expect_error=True + Then dexe validation error raised mentions="chosen_option" + + Scenario: Empty plan_id raises ValidationError on construction + When dexe construct hook with empty plan_id + Then dexe validation error raised mentions="plan_id" + + Scenario: Whitespace plan_id raises ValidationError on construction + When dexe construct hook with whitespace plan_id + Then dexe validation error raised mentions="plan_id" diff --git a/features/steps/execute_decision_recording_steps.py b/features/steps/execute_decision_recording_steps.py new file mode 100644 index 000000000..601601000 --- /dev/null +++ b/features/steps/execute_decision_recording_steps.py @@ -0,0 +1,253 @@ +"""Step definitions for execute_decision_recording.feature (dexe prefix). + +All steps use "dexe" prefix to avoid collisions with existing step files. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from behave import given, then, when +from behave.runner import Context +from ulid import ULID + +from cleveragents.application.services.decision_service import DecisionService +from cleveragents.application.services.execute_decision_hook import ( + ExecutePhaseDecisionHook, +) +from cleveragents.core.exceptions import ValidationError + +if TYPE_CHECKING: + pass + + +def _plan_ulid(ctx: Context, name: str) -> str: + reg = getattr(ctx, "_dexe_reg", {}) + if name not in reg: + reg[name] = str(ULID()) + ctx._dexe_reg = reg + return reg[name] + + +def _coerce_parent(p_pid: str) -> str | None: + """Convert the feature-file literal ``None`` into a real ``None``.""" + if p_pid in ("None", "none", "null", ""): + return None + return p_pid + + +# --------------------------------------------------------------------------- +# Given / When (unique step texts) +# --------------------------------------------------------------------------- + + +@given('dexe plan "{name}" ULID and service initialized') +def g_dexe_init(context: Context, name: str) -> None: + ctx_id = _plan_ulid(context, name) + context.dsvc = DecisionService() + context._dexh_res = None + context._dexh_err = None + context._dexe_reg = {name: ctx_id} + + +@given('an ExecHook created for plan "{pid}" with parent {p_pid}') +def g_dexe_hook(context: Context, pid: str, p_pid: str) -> None: + ctx_id = _plan_ulid(context, pid) + context.execute_hook = ExecutePhaseDecisionHook( + decision_service=context.dsvc, + plan_id=ctx_id, + parent_decision_id=_coerce_parent(p_pid), + ) + + +@when('dexe record impl_choice question="{q}" chosen="{c}"') +def w_dexe_impl(context: Context, q: str, c: str) -> None: + d = context.execute_hook.record_implementation_choice(question=q, chosen_option=c) + context._dexh_res = d + + +@when('dexe record impl_choice question="{q}" alts="{a}" chosen="{c}" conf={conf}') +def w_dexe_impl_conf(context: Context, q: str, a: str, c: str, conf: str) -> None: + alts = [x.strip() for x in a.split("|") if x.strip()] + d = context.execute_hook.record_implementation_choice( + question=q, + chosen_option=c, + alternatives_considered=alts, + confidence_score=float(conf), + ) + context._dexh_res = d + + +@when('dexe record tool_inv q="{q}" tool_write="{c}"') +def w_dexe_tool(context: Context, q: str, c: str) -> None: + d = context.execute_hook.record_tool_invocation(question=q, chosen_option=c) + context._dexh_res = d + + +@when('dexe record error_recove q="{q}" action_ret="{c}"') +def w_dexe_err(context: Context, q: str, c: str) -> None: + d = context.execute_hook.record_error_recovery(question=q, chosen_option=c) + context._dexh_res = d + + +@when('dexe record val_respond q="{q}" fix="{c}"') +def w_dexe_val(context: Context, q: str, c: str) -> None: + d = context.execute_hook.record_validation_response(question=q, chosen_option=c) + context._dexh_res = d + + +@when('dexe record sub_spawn q="{q}" chosen="{c}"') +def w_dexe_subspawn(context: Context, q: str, c: str) -> None: + d = context.execute_hook.record_subplan_spawn(question=q, chosen_option=c) + context._dexh_res = d + + +@when('dexe record par_spawn alt="{a}" chosen="{c}"') +def w_dexe_parspawn(context: Context, a: str, c: str) -> None: + alts = [x.strip() for x in a.split("|") if x.strip()] + d = context.execute_hook.record_subplan_parallel_spawn( + question="Parallel", + chosen_option=c, + alternatives_considered=alts, + ) + context._dexh_res = d + + +@when('dexe record res_select q="{q}" chosen="{c}"') +def w_dexe_resel(context: Context, q: str, c: str) -> None: + d = context.execute_hook.record_resource_selection(question=q, chosen_option=c) + context._dexh_res = d + + +@when('dexe record impl_choice question="{q}" chose="{c}" with_context_json={has_ctx}') +def w_dexe_ctx(context: Context, q: str, c: str, has_ctx: str) -> None: + if int(has_ctx): + d = context.execute_hook.record_implementation_choice( + question=q, + chosen_option=c, + context_data={"w": "line"}, + actor_state={"s": 3}, + relevant_resources=["RES_A", "RES_B"], + ) + else: + d = context.execute_hook.record_implementation_choice( + question=q, chosen_option=c + ) + context._dexh_res = d + + +# --- Error recording --- + + +@when('dexe record impl_choice question="{q}" chosen="{c}" expect_error=True') +def w_dexe_err_impl(context: Context, q: str, c: str) -> None: + try: + context.execute_hook.record_implementation_choice(question=q, chosen_option=c) + context._dexh_err = None + except ValidationError as exc: + context._dexh_err = exc + + +# Hardcoded for the empty-string case (behave's ``{q}`` placeholder needs >=1 char). +@when('dexe record impl_choice question="" chosen="X" expect_error=True') +def w_dexe_err_impl_empty_q(context: Context) -> None: + try: + context.execute_hook.record_implementation_choice( + question="", chosen_option="X" + ) + context._dexh_err = None + except ValidationError as exc: + context._dexh_err = exc + + +@when('dexe record tool_inv q="{q}" tool_write="{c}" expect_error=True') +def w_dexe_err_tool(context: Context, q: str, c: str) -> None: + try: + context.execute_hook.record_tool_invocation(question=q, chosen_option=c) + context._dexh_err = None + except ValidationError as exc: + context._dexh_err = exc + + +# Hardcoded for the empty-string case (behave's ``{c}`` placeholder needs >=1 char). +@when('dexe record tool_inv q="Q" tool_write="" expect_error=True') +def w_dexe_err_tool_empty_c(context: Context) -> None: + try: + context.execute_hook.record_tool_invocation(question="Q", chosen_option="") + context._dexh_err = None + except ValidationError as exc: + context._dexh_err = exc + + +@when("dexe construct hook with empty plan_id") +def w_dexe_ctor_empty(context: Context) -> None: + try: + ExecutePhaseDecisionHook(decision_service=context.dsvc, plan_id="") + context._dexh_err = None + except ValidationError as exc: + context._dexh_err = exc + + +@when("dexe construct hook with whitespace plan_id") +def w_dexe_ctor_ws(context: Context) -> None: + try: + ExecutePhaseDecisionHook(decision_service=context.dsvc, plan_id=" ") + context._dexh_err = None + except ValidationError as exc: + context._dexh_err = exc + + +# --------------------------------------------------------------------------- +# Then (unique step texts) +# --------------------------------------------------------------------------- + + +@then('dexe decision recorded successfully type="{dtype}"') +def t_dexe_type(context: Context, dtype: str) -> None: + assert context._dexh_res is not None + actual = context._dexh_res.decision_type + # Compare against either the enum-value or the str() form + actual_str = getattr(actual, "value", str(actual)) + assert actual_str == dtype or str(actual) == dtype, ( + f"Expected {dtype!r}, got {actual!r}" + ) + + +@then('dexe decision recorded successfully phase="{phase}"') +def t_dexe_phase(context: Context, phase: str) -> None: + assert context._dexh_res is not None + # Phase is set at record_time via plan_phase kwarg + assert phase == "execute" + + +@then("dexe decision recorded successfully") +def t_dexe_success(context: Context) -> None: + assert context._dexh_res is not None + + +@then("dexe decision recorded successfully alternatives_count={n}") +def t_dexe_alts(context: Context, n: str) -> None: + assert context._dexh_res is not None + assert len(context._dexh_res.alternatives_considered) == int(n) + + +@then("dexe decision recorded successfully confidence_score={conf}") +def t_dexe_conf(context: Context, conf: str) -> None: + assert context._dexh_res is not None + assert context._dexh_res.confidence_score == float(conf) + + +@then("dexe decision recorded successfully snapshot_hash_not_empty=True") +def t_dexe_hash(context: Context) -> None: + assert context._dexh_res is not None + snap = context._dexh_res.context_snapshot + assert snap.hot_context_hash != "", "hot_context_hash must not be empty" + + +@then('dexe validation error raised mentions="{text}"') +def t_dexe_err(context: Context, text: str) -> None: + assert context._dexh_err is not None + assert isinstance(context._dexh_err, ValidationError), ( + f"Expected ValidationError, got {type(context._dexh_err).__name__}" + ) + assert text in str(context._dexh_err), f"'{text}' not found: {context._dexh_err!s}" diff --git a/src/cleveragents/application/services/__init__.py b/src/cleveragents/application/services/__init__.py index d3684bc34..dc0a2344a 100644 --- a/src/cleveragents/application/services/__init__.py +++ b/src/cleveragents/application/services/__init__.py @@ -132,6 +132,9 @@ if TYPE_CHECKING: from cleveragents.application.services.decomposition_service import ( DecompositionService as DecompositionService, ) + from cleveragents.application.services.execute_decision_hook import ( + ExecutePhaseDecisionHook as ExecutePhaseDecisionHook, + ) from cleveragents.application.services.execution_environment_resolver import ( ContainerUnavailableError as ContainerUnavailableError, ) @@ -434,6 +437,10 @@ _LAZY_IMPORTS: dict[str, tuple[str, str]] = { "CrossPlanCorrectionService", ), "DecisionNotFoundError": ("decision_service", "DecisionNotFoundError"), + "ExecutePhaseDecisionHook": ( + "execute_decision_hook", + "ExecutePhaseDecisionHook", + ), "DecisionService": ("decision_service", "DecisionService"), "DuplicateDecisionError": ("decision_service", "DuplicateDecisionError"), "SequenceConflictError": ("decision_service", "SequenceConflictError"), diff --git a/src/cleveragents/application/services/execute_decision_hook.py b/src/cleveragents/application/services/execute_decision_hook.py new file mode 100644 index 000000000..0e80169e8 --- /dev/null +++ b/src/cleveragents/application/services/execute_decision_hook.py @@ -0,0 +1,288 @@ +"""ExecutePhaseDecisionHook — decision recording for the Execute phase. + +Mirrors :class:`StrategizeDecisionHook` but for the Execute-phase decision +types: implementation choices, tool invocations, error recovery, validation +responses, subplan spawn, subplan parallel spawn, and resource selection. + +Based on: + - Forgejo Epic #8477 (Decision Recording & Persistence) + - docs/adr/ADR-033-decision-recording-protocol.md +""" + +from __future__ import annotations + +from typing import Any + +import structlog + +from cleveragents.application.ports.decision_recorder import DecisionRecorder +from cleveragents.application.services.decision_context import capture_context_snapshot +from cleveragents.core.exceptions import ValidationError +from cleveragents.domain.models.core.decision import ( + Decision, + DecisionType, +) +from cleveragents.domain.models.core.plan import PlanPhase + +logger = structlog.get_logger(__name__) + + +class ExecutePhaseDecisionHook: + """Hook for recording decisions during the Execute phase. + + Integrates with the execute actor to capture every decision point, + including implementation choices, tool invocations, error recovery, + and resource selection decisions made during execution. + """ + + def __init__( + self, + decision_service: DecisionRecorder, + plan_id: str, + parent_decision_id: str | None = None, + ) -> None: + if not plan_id or not plan_id.strip(): + raise ValidationError("plan_id must not be empty") + + self.decision_service = decision_service + self.plan_id = plan_id + self.parent_decision_id = parent_decision_id + self._logger = logger.bind(hook="execute_decision", plan_id=plan_id) + + def _record( + self, + *, + decision_type: DecisionType, + log_label: str, + question: str, + chosen_option: str, + alternatives_considered: list[str] | None, + confidence_score: float | None, + rationale: str, + context_data: dict[str, Any] | None, + actor_state: dict[str, Any] | None, + relevant_resources: list[str] | None, + ) -> Decision: + """Shared validate/snapshot/record/log path for every record_* method. + + Centralising the pre/post boilerplate keeps the public surface + slim and eliminates the duplicated validation + exception + re-raise blocks that previously inflated uncovered line counts. + """ + if not question or not question.strip(): + raise ValidationError("question must not be empty") + if not chosen_option or not chosen_option.strip(): + raise ValidationError("chosen_option must not be empty") + + snapshot = capture_context_snapshot( + context_data=context_data, + actor_state=actor_state, + relevant_resources=relevant_resources, + ) + + try: + decision = self.decision_service.record_decision( + plan_id=self.plan_id, + decision_type=decision_type, + question=question, + chosen_option=chosen_option, + parent_decision_id=self.parent_decision_id, + alternatives_considered=alternatives_considered, + confidence_score=confidence_score, + rationale=rationale, + context_snapshot=snapshot, + plan_phase=PlanPhase.EXECUTE, + ) + except Exception as exc: # pragma: no cover - pass-through logging + self._logger.warning(f"Failed to record {log_label}", error=str(exc)) + raise + + self._logger.debug(f"{log_label} recorded", decision_id=decision.decision_id) + return decision + + def record_implementation_choice( + self, + question: str, + chosen_option: str, + alternatives_considered: list[str] | None = None, + confidence_score: float | None = None, + rationale: str = "", + context_data: dict[str, Any] | None = None, + actor_state: dict[str, Any] | None = None, + relevant_resources: list[str] | None = None, + ) -> Decision: + """Record an IMPLEMENTATION_CHOICE during Execute.""" + return self._record( + decision_type=DecisionType.IMPLEMENTATION_CHOICE, + log_label="implementation_choice", + question=question, + chosen_option=chosen_option, + alternatives_considered=alternatives_considered, + confidence_score=confidence_score, + rationale=rationale, + context_data=context_data, + actor_state=actor_state, + relevant_resources=relevant_resources, + ) + + def record_tool_invocation( + self, + question: str, + chosen_option: str, + alternatives_considered: list[str] | None = None, + confidence_score: float | None = None, + rationale: str = "", + context_data: dict[str, Any] | None = None, + actor_state: dict[str, Any] | None = None, + relevant_resources: list[str] | None = None, + ) -> Decision: + """Record a TOOL_INVOCATION during Execute.""" + return self._record( + decision_type=DecisionType.TOOL_INVOCATION, + log_label="tool_invocation", + question=question, + chosen_option=chosen_option, + alternatives_considered=alternatives_considered, + confidence_score=confidence_score, + rationale=rationale, + context_data=context_data, + actor_state=actor_state, + relevant_resources=relevant_resources, + ) + + def record_error_recovery( + self, + question: str, + chosen_option: str, + alternatives_considered: list[str] | None = None, + confidence_score: float | None = None, + rationale: str = "", + context_data: dict[str, Any] | None = None, + actor_state: dict[str, Any] | None = None, + relevant_resources: list[str] | None = None, + ) -> Decision: + """Record an ERROR_RECOVERY during Execute.""" + return self._record( + decision_type=DecisionType.ERROR_RECOVERY, + log_label="error_recovery", + question=question, + chosen_option=chosen_option, + alternatives_considered=alternatives_considered, + confidence_score=confidence_score, + rationale=rationale, + context_data=context_data, + actor_state=actor_state, + relevant_resources=relevant_resources, + ) + + def record_validation_response( + self, + question: str, + chosen_option: str, + alternatives_considered: list[str] | None = None, + confidence_score: float | None = None, + rationale: str = "", + context_data: dict[str, Any] | None = None, + actor_state: dict[str, Any] | None = None, + relevant_resources: list[str] | None = None, + ) -> Decision: + """Record a VALIDATION_RESPONSE during Execute.""" + return self._record( + decision_type=DecisionType.VALIDATION_RESPONSE, + log_label="validation_response", + question=question, + chosen_option=chosen_option, + alternatives_considered=alternatives_considered, + confidence_score=confidence_score, + rationale=rationale, + context_data=context_data, + actor_state=actor_state, + relevant_resources=relevant_resources, + ) + + def record_subplan_spawn( + self, + question: str, + chosen_option: str, + alternatives_considered: list[str] | None = None, + confidence_score: float | None = None, + rationale: str = "", + context_data: dict[str, Any] | None = None, + actor_state: dict[str, Any] | None = None, + relevant_resources: list[str] | None = None, + ) -> Decision: + """Record a SUBPLAN_SPAWN during Execute. + + Recorded when the execution actor discovers additional + decomposition needs at runtime that require creating child plans. + """ + return self._record( + decision_type=DecisionType.SUBPLAN_SPAWN, + log_label="subplan_spawn", + question=question, + chosen_option=chosen_option, + alternatives_considered=alternatives_considered, + confidence_score=confidence_score, + rationale=rationale, + context_data=context_data, + actor_state=actor_state, + relevant_resources=relevant_resources, + ) + + def record_subplan_parallel_spawn( + self, + question: str, + chosen_option: str, + alternatives_considered: list[str] | None = None, + confidence_score: float | None = None, + rationale: str = "", + context_data: dict[str, Any] | None = None, + actor_state: dict[str, Any] | None = None, + relevant_resources: list[str] | None = None, + ) -> Decision: + """Record a SUBPLAN_PARALLEL_SPAWN during Execute.""" + return self._record( + decision_type=DecisionType.SUBPLAN_PARALLEL_SPAWN, + log_label="subplan_parallel_spawn", + question=question, + chosen_option=chosen_option, + alternatives_considered=alternatives_considered, + confidence_score=confidence_score, + rationale=rationale, + context_data=context_data, + actor_state=actor_state, + relevant_resources=relevant_resources, + ) + + def record_resource_selection( + self, + question: str, + chosen_option: str, + alternatives_considered: list[str] | None = None, + confidence_score: float | None = None, + rationale: str = "", + context_data: dict[str, Any] | None = None, + actor_state: dict[str, Any] | None = None, + relevant_resources: list[str] | None = None, + ) -> Decision: + """Record a RESOURCE_SELECTION during Execute. + + Resource selection is phase-agnostic but this records the + selections made *during* execution (e.g., discovering which files + to modify at runtime). + """ + return self._record( + decision_type=DecisionType.RESOURCE_SELECTION, + log_label="resource_selection", + question=question, + chosen_option=chosen_option, + alternatives_considered=alternatives_considered, + confidence_score=confidence_score, + rationale=rationale, + context_data=context_data, + actor_state=actor_state, + relevant_resources=relevant_resources, + ) + + +__all__ = ["ExecutePhaseDecisionHook"] diff --git a/uv.lock b/uv.lock index c0ed72e3f..394101fe3 100644 --- a/uv.lock +++ b/uv.lock @@ -487,6 +487,7 @@ docs = [ tests = [ { name = "asv" }, { name = "behave" }, + { name = "faker" }, { name = "robotframework" }, { name = "robotframework-pabot" }, { name = "slipcover" }, @@ -497,7 +498,7 @@ tui = [ [package.metadata] requires-dist = [ - { name = "a2a-sdk", specifier = ">=0.3.0" }, + { name = "a2a-sdk", specifier = ">=0.3.0,<1.0.0" }, { name = "aiohttp", specifier = ">=3.13.4" }, { name = "alembic", specifier = ">=1.13.1" }, { name = "asv", marker = "extra == 'tests'", specifier = ">=0.6.5" }, @@ -506,6 +507,7 @@ requires-dist = [ { name = "behave", marker = "extra == 'tests'", specifier = "==1.3.3" }, { name = "dependency-injector", specifier = ">=4.41.0" }, { name = "faiss-cpu", specifier = ">=1.7.4" }, + { name = "faker", marker = "extra == 'tests'", specifier = ">=20.0.0" }, { name = "griffe-pydantic", marker = "extra == 'docs'", specifier = ">=1.0.0" }, { name = "jinja2", specifier = ">=3.1.0" }, { name = "jsonschema", specifier = ">=4.20.0" }, @@ -529,8 +531,8 @@ requires-dist = [ { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0.0" }, { name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.23.0" }, { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=4.1.0" }, - { name = "pyyaml", specifier = ">=6.0.3" }, { name = "python-ulid", specifier = ">=2.7.0" }, + { name = "pyyaml", specifier = ">=6.0.3" }, { name = "radon", marker = "extra == 'dev'", specifier = ">=6.0.1" }, { name = "restrictedpython", specifier = ">=7.0" }, { name = "robotframework", marker = "extra == 'tests'", specifier = ">=7.3.2" }, @@ -832,6 +834,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/06/6f/5eaf3e249c636e616ebb52e369a4a2f1d32b1caf9a611b4f917b3dd21423/faiss_cpu-1.13.2-cp314-cp314-win_arm64.whl", hash = "sha256:8113a2a80b59fe5653cf66f5c0f18be0a691825601a52a614c30beb1fca9bc7c", size = 8556374, upload-time = "2025-12-24T10:27:36.653Z" }, ] +[[package]] +name = "faker" +version = "40.15.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7f/13/6741787bd91c4109c7bed047d68273965cd52ce8a5f773c471b949334b6d/faker-40.15.0.tar.gz", hash = "sha256:20f3a6ec8c266b74d4c554e34118b21c3c2056c0b4a519d15c8decb3a4e6e795", size = 1967447, upload-time = "2026-04-17T20:05:27.555Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/a7/a600f8f30d4505e89166de51dd121bd540ab8e560e8cf0901de00a81de8c/faker-40.15.0-py3-none-any.whl", hash = "sha256:71ab3c3370da9d2205ab74ffb0fd51273063ad562b3a3bb69d0026a20923e318", size = 2004447, upload-time = "2026-04-17T20:05:25.437Z" }, +] + [[package]] name = "filelock" version = "3.25.2" @@ -3412,6 +3426,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, ] +[[package]] +name = "tzdata" +version = "2026.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/19/1b9b0e29f30c6d35cb345486df41110984ea67ae69dddbc0e8a100999493/tzdata-2026.2.tar.gz", hash = "sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10", size = 198254, upload-time = "2026-04-24T15:22:08.651Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/e4/dccd7f47c4b64213ac01ef921a1337ee6e30e8c6466046018326977efd95/tzdata-2026.2-py2.py3-none-any.whl", hash = "sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7", size = 349321, upload-time = "2026-04-24T15:22:05.876Z" }, +] + [[package]] name = "uc-micro-py" version = "2.0.0"