From 10ed6efac40cf2902f05cb6ca36068b781b96de5 Mon Sep 17 00:00:00 2001 From: CleverThis Date: Wed, 29 Apr 2026 02:21:38 +0000 Subject: [PATCH] fix(plan-tree): visually mark corrected nodes in build_decision_tree - Add label key to node dicts in _node_dict - Append [corrected] marker when decision.is_correction is True - Add TDD BDD scenario proving corrected nodes are visually marked --- .../tdd_plan_tree_correction_visual_steps.py | 155 ++++++++++++++++++ .../tdd_plan_tree_correction_visual.feature | 23 +++ src/cleveragents/cli/commands/plan.py | 4 + 3 files changed, 182 insertions(+) create mode 100644 features/steps/tdd_plan_tree_correction_visual_steps.py create mode 100644 features/tdd_plan_tree_correction_visual.feature diff --git a/features/steps/tdd_plan_tree_correction_visual_steps.py b/features/steps/tdd_plan_tree_correction_visual_steps.py new file mode 100644 index 000000000..fec59c260 --- /dev/null +++ b/features/steps/tdd_plan_tree_correction_visual_steps.py @@ -0,0 +1,155 @@ +"""Step definitions for tdd_plan_tree_correction_visual.feature. + +Tests that corrected nodes (decisions with is_correction=True) are visually +marked in the plan tree output. This is a TDD scenario documenting the gap +where the current implementation does NOT visually distinguish corrected nodes. +""" + +from __future__ import annotations + +from behave import given, then, when +from behave.runner import Context +from ulid import ULID + +from cleveragents.cli.commands.plan import build_decision_tree +from cleveragents.domain.models.core.decision import Decision, DecisionType + +# --------------------------------------------------------------------------- +# Constants +# --------------------------------------------------------------------------- + +_PLAN_ID = str(ULID()) + + +# --------------------------------------------------------------------------- +# Given steps +# --------------------------------------------------------------------------- + + +@given("a set of test decisions with a corrected decision") +def step_decisions_with_correction(context: Context) -> None: + """Create a set of decisions where one is marked as a correction.""" + root_id = str(ULID()) + corrected_id = str(ULID()) + corrects_id = str(ULID()) + + context.tdd_decisions = [ + # Root decision + Decision( + decision_id=root_id, + plan_id=_PLAN_ID, + sequence_number=0, + decision_type=DecisionType.PROMPT_DEFINITION, + question="What to build?", + chosen_option="REST API", + ), + # Original decision that will be corrected + Decision( + decision_id=corrects_id, + plan_id=_PLAN_ID, + parent_decision_id=root_id, + sequence_number=1, + decision_type=DecisionType.STRATEGY_CHOICE, + question="Which framework?", + chosen_option="Django", + ), + # Corrected decision (is_correction=True) + Decision( + decision_id=corrected_id, + plan_id=_PLAN_ID, + parent_decision_id=root_id, + sequence_number=2, + decision_type=DecisionType.STRATEGY_CHOICE, + question="Which framework?", + chosen_option="FastAPI", + is_correction=True, + corrects_decision_id=corrects_id, + ), + ] + context.tdd_corrected_id = corrected_id + + +# --------------------------------------------------------------------------- +# When steps +# --------------------------------------------------------------------------- + + +@when("I build the decision tree with default options") +def step_build_tree_default(context: Context) -> None: + """Build the decision tree from the test decisions.""" + context.tdd_tree_data = build_decision_tree( + context.tdd_decisions, + show_superseded=False, + max_depth=0, + ) + + +# --------------------------------------------------------------------------- +# Then steps +# --------------------------------------------------------------------------- + + +@then("the tree should include a visual marker for the corrected decision") +def step_tree_has_correction_marker(context: Context) -> None: + """Verify that the tree data includes a marker for the corrected decision.""" + tree_nodes: list[dict[str, object]] = context.tdd_tree_data + + # The tree_data is a list of root-level node dicts. + # We need to search through it to find the corrected decision node + # and verify it has a visual marker. + + def find_corrected_node( + nodes: list[dict[str, object]], + ) -> dict[str, object] | None: + """Recursively search for the corrected decision node.""" + for node in nodes: + if node.get("decision_id") == context.tdd_corrected_id: + return node + children = node.get("children", []) + assert isinstance(children, list) + result = find_corrected_node(children) + if result is not None: + return result + return None + + corrected_node = find_corrected_node(tree_nodes) + assert corrected_node is not None, ( + f"Corrected decision {context.tdd_corrected_id} not found in tree" + ) + + # Store the node for the next assertion + context.tdd_corrected_node = corrected_node + + +@then( + 'the corrected decision label should contain "[corrected]" or "✎" or similar marker' +) +def step_corrected_label_has_marker(context: Context) -> None: + """Verify that the corrected decision's label contains a visual marker. + + This assertion is expected to FAIL (via @tdd_expected_fail) because + build_decision_tree does not currently include a 'label' key in its + node dicts, and even if it did, it does not add visual markers for + corrected decisions (is_correction=True). This TDD scenario documents + the gap described in Spec Requirement #7. + """ + corrected_node = context.tdd_corrected_node + + # The label should be in the node's label field. + # build_decision_tree currently does NOT include a 'label' key in its + # node dicts -- this assertion will fail with AssertionError, proving + # the bug exists (Spec Requirement #7: corrected nodes must be visually + # marked in the plan tree output). + label = str(corrected_node.get("label", "")) + + # Check for common visual markers for corrections + markers = ["[corrected]", "\u270e", "[correction]", "\u270f", "[fix]", "[amended]"] + + has_marker = any(marker in label for marker in markers) + + assert has_marker, ( + f"Corrected decision label '{label}' does not contain any visual marker. " + f"Expected one of: {markers}. " + f"build_decision_tree does not currently add visual markers for " + f"decisions with is_correction=True (Spec Requirement #7 gap)." + ) diff --git a/features/tdd_plan_tree_correction_visual.feature b/features/tdd_plan_tree_correction_visual.feature new file mode 100644 index 000000000..200afa782 --- /dev/null +++ b/features/tdd_plan_tree_correction_visual.feature @@ -0,0 +1,23 @@ +@tdd_expected_fail +@tdd_issue +@tdd_issue_8576 +Feature: TDD: plan tree visually marks corrected nodes + Spec Requirement #7 states that `agents plan tree` should visually + distinguish corrected nodes (decisions with is_correction=True). + + This TDD scenario documents the gap: the current implementation does NOT + visually mark corrected nodes in the tree output. The tree renderer builds + node labels without checking decision.is_correction. + + Expected behavior: Corrected nodes should be marked with a visual indicator + such as [corrected], ✎, or similar annotation. + + # ------------------------------------------------------------------ + # Scenario: Corrected node is visually marked in tree output + # ------------------------------------------------------------------ + + Scenario: Corrected decision is visually marked in plan tree + Given a set of test decisions with a corrected decision + When I build the decision tree with default options + Then the tree should include a visual marker for the corrected decision + And the corrected decision label should contain "[corrected]" or "✎" or similar marker diff --git a/src/cleveragents/cli/commands/plan.py b/src/cleveragents/cli/commands/plan.py index 8d051f300..7ffaba7d5 100644 --- a/src/cleveragents/cli/commands/plan.py +++ b/src/cleveragents/cli/commands/plan.py @@ -4878,8 +4878,12 @@ def build_decision_tree( roots.append(d.decision_id) def _node_dict(d: Decision) -> dict[str, object]: + label = _get_decision_label(str(d.decision_type), 0) + if d.is_correction: + label = f"{label} [corrected]" return { "decision_id": d.decision_id, + "label": label, "type": str(d.decision_type), "sequence": d.sequence_number, "question": d.question,