From 7d87f1f3452843ac935576fce59d759c9c5cdd9f Mon Sep 17 00:00:00 2001 From: HAL9000 Date: Fri, 8 May 2026 11:12:57 +0000 Subject: [PATCH] fix(plan): wrap plan tree JSON/YAML output in spec-required command envelope The agents plan tree CLI command's JSON and YAML output paths now include the standardised envelope fields (command, status, exit_code, data, timing) as required by the specification. Previously the envelope had an empty command field which broke programmatic consumers that parse the command key to identify the source command. Updated BDD test assertions to validate against the envelope structure and removed @tdd_expected_fail tag from the JSON format scenario. ISSUES CLOSED: #9313 --- CHANGELOG.md | 10 ++++++++++ CONTRIBUTORS.md | 3 +++ features/plan_explain.feature | 2 +- features/steps/plan_explain_steps.py | 19 ++++++++++++++++++- src/cleveragents/cli/commands/plan.py | 10 +++++++++- 5 files changed, 41 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 905c8b89c..062d90df0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,16 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). from the TDD test so both scenarios run as normal regression guards. (#988) ### Fixed +- **Wrapped plan tree JSON/YAML output in spec-required command envelope (#9313)**: The + ``agents plan tree`` CLI command's JSON and YAML output paths now include the + standardised envelope fields (``command``, ``status``, ``exit_code``, ``data``, + ``timing``, ``messages``) as required by the specification §Output Rendering + Framework. Previously the envelope was created with an empty ``command`` field, + which broke programmatic consumers that parse the ``command`` key to identify + the source command. Updated step definitions to validate against the envelope + structure (checking ``data`` for the tree array). Removed ``@tdd_expected_fail`` + tag from the JSON format BDD scenario so it runs as a normal regression guard. + - **Actor CLI NAME argument made optional, derived from YAML config** (#4186): The `agents actor add` positional ``NAME`` argument is now optional (defaults to ``None``). When omitted, the actor name is derived from the ``name`` field in diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index b7d1bc7a0..cf80688e3 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -35,3 +35,6 @@ Below are some of the specific details of various contributions. * HAL 9000 has contributed the ACMS Index Data Model and File Traversal Engine (PR #9664 / issue #9579): foundational data structures for indexed context entries with hot/warm/cold/archive storage tier classification, tag system, and a timeout-safe chunked file traversal engine for large projects with 10,000+ files. * HAL 9000 has contributed the error-suppression removal fix (PR #9247 / issue #9060): removed both `try...except Exception:` blocks in `register_registry_agents()` that silently suppressed errors from `actor_registry.list_actors()` and the route bridge refresh, enabling exceptions to propagate per CONTRIBUTING.md fail-fast policy. Added three Behave scenarios verifying RuntimeError, AttributeError, and TypeError propagation. * HAL 9000 has contributed the Strategize phase full context snapshot fix (issue #9056): added `_build_strategize_context_snapshot()` helper to `PlanLifecycleService`, updated `_try_record_decision()` to accept and forward a `ContextSnapshot` parameter, and added BDD test coverage verifying all four `ContextSnapshot` fields (`hot_context_hash`, `hot_context_ref`, `actor_state_ref`, `relevant_resources`) are populated during the Strategize phase. + + +* HAL 9000 has contributed the plan tree JSON/YAML command envelope fix (PR #9313): updated ``tree_decisions_cmd`` to pass ``command="plan tree"`` to ``format_output()`` so that machine-readable output conforms to the spec-required command envelope structure. diff --git a/features/plan_explain.feature b/features/plan_explain.feature index 7051cfc33..63dd8ba9f 100644 --- a/features/plan_explain.feature +++ b/features/plan_explain.feature @@ -107,12 +107,12 @@ Feature: Plan explain and decision tree CLI commands # plan tree - json format # ------------------------------------------------------------------ - @tdd_issue @tdd_issue_4254 @tdd_expected_fail Scenario: Tree with json format Given a set of test decisions forming a tree When I format the tree as json Then the json tree output should be valid json And the json tree output should contain "decision_id" + And the json tree envelope should contain "command" field equal to "plan tree" # ------------------------------------------------------------------ # plan tree - yaml format diff --git a/features/steps/plan_explain_steps.py b/features/steps/plan_explain_steps.py index 52f66681a..7ea686fd1 100644 --- a/features/steps/plan_explain_steps.py +++ b/features/steps/plan_explain_steps.py @@ -402,14 +402,31 @@ def step_roots_no_children(context: Context) -> None: @then("the json tree output should be valid json") def step_tree_json_valid(context: Context) -> None: parsed = json.loads(context.pe_tree_json) - assert isinstance(parsed, list), "Expected a JSON array" + assert isinstance(parsed, dict), f"Expected a JSON envelope object, got {type(parsed).__name__}" + assert "data" in parsed, f"Expected 'data' in envelope, keys: {list(parsed.keys())}" + assert isinstance(parsed["data"], list), "Expected 'data' field to be a JSON array (tree)" @then('the json tree output should contain "{text}"') def step_tree_json_contains(context: Context, text: str) -> None: + parsed = json.loads(context.pe_tree_json) assert text in context.pe_tree_json, f"Expected '{text}' in tree JSON" + # Verify the text is within the data (envelope payload). + if isinstance(parsed.get("data"), list): + data_str = json.dumps(parsed["data"]) + assert text in data_str, f"Expected '{text}' in parsed 'data' field" + +@then('the json tree envelope should contain "{field}" field equal to "{value}"') +def step_tree_envelope_has_field(context: Context, field: str, value: str) -> None: + """Validate that the JSON command envelope has a specific top-level field with a given value.""" + parsed = json.loads(context.pe_tree_json) + assert field in parsed, f"Expected '{field}' in envelope at top level. Keys: {list(parsed.keys())}" + assert parsed[field] == value, ( + f"Envelope field '{field}' expected '{value}', got '{parsed[field]}'" + ) + @then('the yaml tree output should contain "{text}"') def step_tree_yaml_contains(context: Context, text: str) -> None: assert text in context.pe_tree_yaml, f"Expected '{text}' in tree YAML" diff --git a/src/cleveragents/cli/commands/plan.py b/src/cleveragents/cli/commands/plan.py index b2a7f64d0..3b6e4478b 100644 --- a/src/cleveragents/cli/commands/plan.py +++ b/src/cleveragents/cli/commands/plan.py @@ -4153,7 +4153,15 @@ def tree_decisions_cmd( ) if fmt in (OutputFormat.JSON, OutputFormat.YAML): - console.print(format_output(tree_data, fmt)) + console.print( + format_output( + tree_data, + fmt, + command="plan tree", + status="ok", + exit_code=0, + ) + ) elif fmt == OutputFormat.TABLE: # Flatten for table view filtered = ( -- 2.52.0