reflector(a2a): execute ACP to A2A module rename and symbol standardization #11165
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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]}"
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user
BLOCKING — Data table is ignored; hardcoded list used instead
This step receives a 22-row data table from the feature file (via
context.table) but never reads it._ALL_SYMBOLSis a hardcoded module-level list used instead, making the table in the .feature file dead code.Problem: If someone updates the symbol table in the .feature file but forgets to update
_ALL_SYMBOLS, the test passes silently with the wrong symbol list. The Gherkin table appears to be the source of truth but is not.Fix: Replace
symbols = list(_ALL_SYMBOLS)with:This makes the feature file the authoritative symbol list, ensuring the Gherkin table and the test implementation remain in sync.
Automated by CleverAgents Bot
Supervisor: PR Review | Agent: pr-review-worker