diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index cb25a3461..2c5b3e60e 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -46,6 +46,7 @@ Below are some of the specific details of various contributions. * HAL 9000 has contributed the PureGraph BDD coverage suite (PR #9601 / issue #9531): wired the previously orphaned `features/steps/pure_graph_coverage_steps.py` definitions through the existing `features/consolidated_langgraph.feature` (topological ordering, function execution, missing function fallback, and non-functional node handling); created Robot Framework integration tests in `robot/langgraph/pure_graph.robot` backed by the `robot/langgraph/pure_graph_lib.py` Python library; and implemented ASV benchmarks in `benchmarks/pure_graph_bench.py` measuring execution throughput across varying node counts. * HAL 9000 has contributed comprehensive milestone documentation for v3.2.0 (Decisions + Validations + Invariants) and v3.3.0 (Corrections + Subplans + Checkpoints), including CLI command reference, decision system guide, and subplan/checkpoint documentation (PR #9796). * Rui Hu has contributed the v3 actor YAML schema validation fix (#5869): added `ActorConfigSchema` validation to the `agents actor add --config` CLI command, covering cycle detection, required field validation, and enum validation for v3 YAML actor definitions. +* HAL 9000 has contributed the ACP → A2A BDD test suite (#10995 / issue #8615): added `a2a_module_rename_standardization.feature` with 3 scenarios validating all 22 exported symbol exports, zero legacy ACP references across the a2a module, and documentation accuracy per ADR-047 naming conventions. * HAL 9000 has contributed the agent-evolution-pool-supervisor PR metadata assignment (#7888): the supervisor now automatically looks up the Type/Automation label and earliest open milestone before dispatching improvement PR creation workers, ensuring all generated improvement PRs have correct Type labels and milestone assignments. * HAL 9000 has contributed the decision recording hook for the Strategize phase (issue #8522): captures every decision point with question, chosen option, alternatives, confidence, rationale, and full context snapshot for replay and correction. * HAL 9000 has contributed the ContextStrategy protocol and StrategyRegistry plugin registration system (PR #10590 / issue #8616): implemented the pluggable context assembly strategy protocol with proper type-safe method signatures, created the central thread-safe StrategyRegistry supporting registration, lookup, entry-point discovery, and per-strategy configuration (timeout, fragments limits, workers, circuit breaker threshold). Six built-in strategies implemented and documented: simple-keyword, semantic-embedding, breadth-depth-navigator, arce, temporal-archaeology, and plan-decision-context. Full BDD test coverage including thread safety, boundary validation, and error handling tests. (Part of Epic #8505) diff --git a/features/a2a_module_rename_standardization.feature b/features/a2a_module_rename_standardization.feature index fdd1c61ac..0e4dc053d 100644 --- a/features/a2a_module_rename_standardization.feature +++ b/features/a2a_module_rename_standardization.feature @@ -1,40 +1,49 @@ -Feature: A2A Module Rename and Symbol Standardization - As a developer - I want all A2A symbols to be standardized - So that the codebase uses consistent A2A naming conventions +@refactor_v360_acp_to_a2a_rename @epic_8569 +Feature: ACP to A2A module rename and symbol standardization + As a CleverAgents developer + I want the ACP → A2A module rename to be fully validated via BDD tests + So that no legacy ACP references remain and all exports follow ADR-047 naming - Scenario: A2A module exports all required symbols - When I import from cleveragents.a2a - Then I should have access to A2aError - And I should have access to A2aErrorDetail - And I should have access to A2aEvent - And I should have access to A2aEventQueue - And I should have access to A2aHttpTransport - And I should have access to A2aLocalFacade - And I should have access to A2aNotAvailableError - And I should have access to A2aOperationNotFoundError - And I should have access to A2aRequest - And I should have access to A2aResponse - And I should have access to A2aStdioTransport - And I should have access to A2aVersion - And I should have access to A2aVersionMismatchError - And I should have access to A2aVersionNegotiator - And I should have access to AuthClient - And I should have access to RemoteExecutionClient - And I should have access to ServerClient - And I should have access to ServerConnectionConfig - And I should have access to StubAuthClient - And I should have access to StubRemoteExecutionClient - And I should have access to StubServerClient - And I should have access to TransportSelector + Background: + Given the "a2a" Python package is importable from "cleveragents.a2a" - Scenario: A2A module has no ACP references - When I check the A2A module for ACP references - Then there should be no ACP imports - And there should be no ACP class names - And there should be no ACP function names + Scenario: The 22 ACP-to-A2A rename symbols are exported via a2a package + When I import "cleveragents.a2a" + And all of the following 22 symbols should be importable from it: + | symbol | + | A2aError | + | A2aErrorDetail | + | A2aEvent | + | A2aEventQueue | + | A2aHttpTransport | + | A2aLocalFacade | + | A2aNotAvailableError | + | A2aOperationNotFoundError | + | A2aRequest | + | A2aResponse | + | A2aStdioTransport | + | A2aVersion | + | A2aVersionMismatchError | + | A2aVersionNegotiator | + | AuthClient | + | RemoteExecutionClient | + | ServerClient | + | ServerConnectionConfig | + | StubAuthClient | + | StubRemoteExecutionClient | + | StubServerClient | + | TransportSelector | + Then every symbol should resolve to a non-None object + And every listed symbol should appear in __all__ - Scenario: A2A module is properly documented - When I check the A2A module documentation - Then the module docstring should mention A2A - And the module docstring should not mention ACP + Scenario: Zero ACP references remain in the a2a module source files + When I recursively scan all Python files under "src/cleveragents/a2a" + And I search for the legacy prefix string "ACP" + Then zero instances of "ACP" should be found + And this confirms the complete ACP → A2A rename is clean + + Scenario: Documentation strings use A2A naming per ADR-047 + When I read the docstring of the "cleveragents.a2a" package + Then it should contain the term "A2A (Agent-to-Agent Protocol)" + And it should reference "ADRs" or "ADR-047" for standard adoption + And the docstring should NOT contain any mentions of "ACP protocol" diff --git a/features/steps/a2a_module_rename_standardization_steps.py b/features/steps/a2a_module_rename_standardization_steps.py index e6fa82273..23e06075c 100644 --- a/features/steps/a2a_module_rename_standardization_steps.py +++ b/features/steps/a2a_module_rename_standardization_steps.py @@ -1,179 +1,171 @@ -"""Step definitions for A2A module rename and symbol standardization. +"""Step definitions for ACP → A2A module rename standardization BDD scenarios.""" -Validates that all A2A symbols are properly exported from the ``cleveragents.a2a`` -module following the JSON-RPC 2.0 specification naming convention, that no legacy -ACP references remain in the codebase, and that documentation is accurate. -""" +from __future__ import annotations -import inspect import os import re +from typing import Any +from pathlib import Path -from behave import when, then -import cleveragents.a2a as a2a_module -from cleveragents.a2a import ( - A2aError, - A2aErrorDetail, - A2aEvent, - A2aEventQueue, - A2aHttpTransport, - A2aLocalFacade, - A2aNotAvailableError, - A2aOperationNotFoundError, - A2aRequest, - A2aResponse, - A2aStdioTransport, - A2aVersion, - A2aVersionMismatchError, - A2aVersionNegotiator, - AuthClient, - RemoteExecutionClient, - ServerClient, - ServerConnectionConfig, - StubAuthClient, - StubRemoteExecutionClient, - StubServerClient, - TransportSelector, -) +from behave import given, then, when -@when("I import from cleveragents.a2a") -def step_import_a2a(context): - """Import from cleveragents.a2a.""" - context.a2a_symbols = { - "A2aError": A2aError, - "A2aErrorDetail": A2aErrorDetail, - "A2aEvent": A2aEvent, - "A2aEventQueue": A2aEventQueue, - "A2aHttpTransport": A2aHttpTransport, - "A2aLocalFacade": A2aLocalFacade, - "A2aNotAvailableError": A2aNotAvailableError, - "A2aOperationNotFoundError": A2aOperationNotFoundError, - "A2aRequest": A2aRequest, - "A2aResponse": A2aResponse, - "A2aStdioTransport": A2aStdioTransport, - "A2aVersion": A2aVersion, - "A2aVersionMismatchError": A2aVersionMismatchError, - "A2aVersionNegotiator": A2aVersionNegotiator, - "AuthClient": AuthClient, - "RemoteExecutionClient": RemoteExecutionClient, - "ServerClient": ServerClient, - "ServerConnectionConfig": ServerConnectionConfig, - "StubAuthClient": StubAuthClient, - "StubRemoteExecutionClient": StubRemoteExecutionClient, - "StubServerClient": StubServerClient, - "TransportSelector": TransportSelector, - } +# ── Helpers ──────────────────────────────────────────────────────────────── -@then("I should have access to {symbol_name}") -def step_have_access_to_symbol(context, symbol_name): - """Verify access to a specific symbol.""" - assert symbol_name in context.a2a_symbols - assert context.a2a_symbols[symbol_name] is not None +def _resolve_repo_root(context: Any) -> Path: + """Return the repository root path from the feature context.""" + repo_root = getattr(context, "repo_root", None) + if repo_root is not None: + return Path(repo_root) + candidate = Path(os.getcwd()) + while candidate != candidate.parent: + if (candidate / ".git").exists(): + context.repo_root = str(candidate) + return candidate + candidate = candidate.parent + raise RuntimeError("Could not find repository root") -@when("I check the A2A module for ACP references") -def step_check_acp_references(context): - """Recursively scan all .py files in the A2A module for ACP references.""" - context.acp_references = { - "imports": [], - "class_names": [], - "function_names": [], - } - - # Recursively scan the entire a2a directory - a2a_dir = os.path.dirname(inspect.getfile(a2a_module)) - for root, _dirs, files in os.walk(a2a_dir): - for fname in files: - if not fname.endswith(".py"): - continue - filepath = os.path.join(root, fname) - with open(filepath, encoding="utf-8") as f: - content = f.read() - # Check imports - acp_import = ( - "from cleveragents.acp" in content - or "import cleveragents.acp" in content - ) - if acp_import: - context.acp_references["imports"].append(filepath) - # Check class/function names for ACP prefix remnants - acp_patterns = re.findall(r"\s+def\s+(_?[Aa][Cc][Pp]\w*)", content) - acp_patterns += re.findall(r"\(\s*(_?[Aa][Cc][Pp]\w*)\)", content) - for match in acp_patterns: - context.acp_references["function_names"].append(match) - - acp_class_patterns = re.findall(r"class\s+(_?[Aa][Cc][Pp]\w*)", content) - for match in acp_class_patterns: - context.acp_references["class_names"].append(match) +def _scan_dir_for_pattern(root: Path, pattern_str: str) -> int: + """Recursively count occurrences of *pattern_str* in all .py files.""" + count = 0 + py_files = list(root.rglob("*.py")) + for py_file in py_files: + try: + text = py_file.read_text(encoding="utf-8", errors="replace") + except OSError: + continue + count += text.count(pattern_str) + return count -@then("there should be no ACP imports") -def step_no_acp_imports(context): - """Verify no ACP imports.""" - assert len(context.acp_references["imports"]) == 0 +# ── Given steps ──────────────────────────────────────────────────────────── -@then("there should be no ACP class names") -def step_no_acp_class_names(context): - """Verify no ACP class names.""" - assert len(context.acp_references["class_names"]) == 0 +@given('the "a2a" Python package is importable from "cleveragents.a2a"') +def step_a2a_importable(context: Any) -> None: + """Ensure cleveragents.a2a can be imported.""" + try: + import cleveragents.a2a # noqa: F401 + except ImportError as exc: + raise AssertionError(f"cleveragents.a2a could not be imported: {exc}") from exc -@then("there should be no ACP function names") -def step_no_acp_function_names(context): - """Verify no ACP function names.""" - assert len(context.acp_references["function_names"]) == 0 +# ── When steps ───────────────────────────────────────────────────────────── -# Symbol definitions used by Step 3 to validate A2A documentation. -# Self-contained: does not depend on context set by other scenarios. -_A2A_DOCUMENTATION_SYMBOLS = { - "A2aError": A2aError, - "A2aErrorDetail": A2aErrorDetail, - "A2aEvent": A2aEvent, - "A2aEventQueue": A2aEventQueue, - "A2aHttpTransport": A2aHttpTransport, - "A2aLocalFacade": A2aLocalFacade, - "A2aNotAvailableError": A2aNotAvailableError, - "A2aOperationNotFoundError": A2aOperationNotFoundError, - "A2aRequest": A2aRequest, - "A2aResponse": A2aResponse, - "A2aStdioTransport": A2aStdioTransport, - "A2aVersion": A2aVersion, - "A2aVersionMismatchError": A2aVersionMismatchError, - "A2aVersionNegotiator": A2aVersionNegotiator, - "AuthClient": AuthClient, - "RemoteExecutionClient": RemoteExecutionClient, - "ServerClient": ServerClient, - "ServerConnectionConfig": ServerConnectionConfig, - "StubAuthClient": StubAuthClient, - "StubRemoteExecutionClient": StubRemoteExecutionClient, - "StubServerClient": StubServerClient, - "TransportSelector": TransportSelector, -} +@when('I import "cleveragents.a2a"') +def step_import_a2a(context: Any) -> None: + """Import the a2a package and store it on context.""" + import cleveragents.a2a + + context._a2a_module = cleveragents.a2a -@when("I check the A2A module documentation") -def step_check_a2a_documentation(context): - """Check A2A module documentation.""" - context.module_doc = a2a_module.__doc__ - context.class_docs = {} - for name, symbol in _A2A_DOCUMENTATION_SYMBOLS.items(): - if inspect.isclass(symbol): - context.class_docs[name] = symbol.__doc__ +@when("all of the following {count:d} symbols should be importable from it:") +def step_symbols_importable(context: Any, count: int) -> None: + """Store all expected symbol names on context for later assertion. + + Reads the Gherkin data table as the single source of truth so the feature + file documents the exact 22 symbols the ACP→A2A rename must export. + """ + assert hasattr(context._a2a_module, "__all__"), ( + "cleveragents.a2a must define __all__" + ) + symbols = [row[0].strip() for row in context.table.rows] + context._expected_symbols = symbols + assert len(symbols) == count, f"Expected {count} symbols, got {len(symbols)}" -@then("the module docstring should mention A2A") -def step_module_doc_mentions_a2a(context): - """Verify module docstring mentions A2A.""" - assert context.module_doc is not None - assert "A2A" in context.module_doc +@when('I recursively scan all Python files under "src/cleveragents/a2a"') +def step_scan_a2a_python_files(context: Any) -> None: + """Set context for ACP-remnant scanning.""" + root = _resolve_repo_root(context) + a2a_dir = root / "src" / "cleveragents" / "a2a" + assert a2a_dir.is_dir(), f"a2a module directory not found at {a2a_dir}" + context._a2a_scan_root = str(a2a_dir) -@then("the module docstring should not mention ACP") -def step_module_doc_no_acp(context): - """Verify module docstring doesn't mention ACP.""" - assert context.module_doc is not None - assert "ACP" not in context.module_doc +@when('I search for the legacy prefix string "ACP"') +def step_search_acp_pattern(context: Any) -> None: + """Perform recursive scan and store results.""" + count = _scan_dir_for_pattern(Path(context._a2a_scan_root), "ACP") + context._acp_references_found = count + + +@when('I read the docstring of the "cleveragents.a2a" package') +def step_read_a2a_docstring(context: Any) -> None: + """Store the a2a package docstring on context.""" + import cleveragents.a2a as a2a_pkg + + assert a2a_pkg.__doc__ is not None, "cleveragents.a2a has no __doc__" + context._a2a_docstring = a2a_pkg.__doc__ + + +# ── Then steps ───────────────────────────────────────────────────────────── + + +@then("every symbol should resolve to a non-None object") +def step_all_symbols_resolved(context: Any) -> None: + """Assert every expected symbol is present and non-None.""" + module = context._a2a_module + missing = [] + for name in context._expected_symbols: + obj = getattr(module, name, None) + if obj is None: + missing.append(name) + assert not missing, f"Missing symbols: {missing}" + + +@then("every listed symbol should appear in __all__") +def step_listed_symbols_in_all(context: Any) -> None: + """Assert every symbol named in the feature data table is present in __all__. + + Subset check rather than exact-count match — ``__all__`` may legitimately + grow with additional unrelated exports (AgentCard*, Sync*, etc.) without + invalidating the ACP→A2A rename contract. + """ + actual_all = set(context._a2a_module.__all__) + missing = [s for s in context._expected_symbols if s not in actual_all] + assert not missing, f"Listed symbols absent from __all__: {missing}" + + +@then('zero instances of "ACP" should be found') +def step_zero_acp_references(context: Any) -> None: + """Assert zero ACP remnants were found.""" + assert context._acp_references_found == 0, ( + f"Found {context._acp_references_found} ACP references — rename is incomplete" + ) + + +@then("this confirms the complete ACP → A2A rename is clean") +def step_rename_clean(context: Any) -> None: + """No additional assertion needed; marker step for clarity.""" + pass + + +@then('it should contain the term "A2A (Agent-to-Agent Protocol)"') +def step_docstring_has_descriptive_term(context: Any) -> None: + """Assert docstring contains the canonical descriptive term.""" + assert "A2A (Agent-to-Agent Protocol)" in context._a2a_docstring, ( + f"Docstring missing 'A2A (Agent-to-Agent Protocol)':\n{context._a2a_docstring[:500]}" + ) + + +@then('it should reference "ADRs" or "ADR-047" for standard adoption') +def step_docstring_references_adr(context: Any) -> None: + """Assert docstring mentions an ADR (ADR-047 for A2A std).""" + pattern = re.compile(r"(ADR-?0*47|A2A Standard Adoption)", re.IGNORECASE) + assert pattern.search(context._a2a_docstring), ( + f"Docstring does not reference ADR-047 or A2A Standard Adoption:\n{context._a2a_docstring[:500]}" + ) + + +@then('the docstring should NOT contain any mentions of "ACP protocol"') +def step_no_acp_protocol_mention(context: Any) -> None: + """Assert docstring does not reference ACP.""" + assert ( + "ACP(protocol)" not in context._a2a_docstring + and "ACP protocol" not in context._a2a_docstring + ), f"Docstring contains legacy 'ACP protocol':\n{context._a2a_docstring[:500]}" diff --git a/src/cleveragents/a2a/__init__.py b/src/cleveragents/a2a/__init__.py index 784e3f526..e53a399b1 100644 --- a/src/cleveragents/a2a/__init__.py +++ b/src/cleveragents/a2a/__init__.py @@ -18,6 +18,8 @@ compatibility. :class:`ServerConnectionConfig` validates connection parameters. The :class:`TransportSelector` chooses the appropriate transport based on configuration: stdio for local mode, HTTP for server mode. + +Naming follows ADR-047 (A2A Standard Adoption). """ from __future__ import annotations