fix(cli): make plan explain accept plan_id for plan-level explanation #1057

Merged
brent.edwards merged 2 commits from bugfix/m3-plan-explain-plan-id into master 2026-03-19 22:10:23 +00:00
4 changed files with 25 additions and 8 deletions
@@ -159,6 +159,7 @@ def step_pec_mock_decision_none(context: Context) -> None:
context.pec_decision_id = str(ULID())
svc = MagicMock()
svc.get_decision.return_value = None
svc.list_decisions.return_value = []
context.pec_container = _mock_container_with_decision_svc(svc)
+1 -1
View File
@@ -1,4 +1,4 @@
@tdd_expected_fail @tdd_bug @tdd_bug_968 @mock_only
@tdd_bug @tdd_bug_968 @mock_only
Feature: TDD Bug #968 — plan explain expects decision_id but test passes plan_id
As a developer
I want to verify that `plan explain <plan_id>` succeeds when given a plan ID
+2 -2
View File
@@ -17,7 +17,7 @@ TDD Plan Explain Succeeds With Plan ID
[Documentation] Verify that ``plan explain <plan_id>`` exits with rc=0
... when given a plan ID that has associated decisions.
... Bug #968: the command currently exits with rc=1.
[Tags] tdd_expected_fail tdd_bug tdd_bug_968
[Tags] tdd_bug tdd_bug_968
${result}= Run Process ${PYTHON} ${HELPER} explain-with-plan-id cwd=${WORKSPACE} timeout=60s on_timeout=kill
Log ${result.stdout}
Log ${result.stderr}
@@ -28,7 +28,7 @@ TDD Plan Explain With Plan ID Shows Root Question
[Documentation] Verify that ``plan explain <plan_id>`` output contains
... the root decision question when given a plan ID.
... Bug #968: the command fails before rendering any output.
[Tags] tdd_expected_fail tdd_bug tdd_bug_968
[Tags] tdd_bug tdd_bug_968
${result}= Run Process ${PYTHON} ${HELPER} explain-plan-id-shows-question cwd=${WORKSPACE} timeout=60s on_timeout=kill
Log ${result.stdout}
Log ${result.stderr}
+21 -5
View File
@@ -3132,9 +3132,9 @@ def _build_explain_dict(
@app.command("explain")
def explain_decision_cmd(
decision_id: Annotated[
identifier: Annotated[
str,
typer.Argument(help="Decision ULID to explain"),
typer.Argument(help="Decision or Plan ULID to explain"),
],
fmt: Annotated[
str,
@@ -3149,14 +3149,30 @@ def explain_decision_cmd(
typer.Option("--show-reasoning", help="Include rationale and actor reasoning"),
] = False,
) -> None:
"""Explain a single decision in the plan decision tree."""
"""Explain a single decision or the root decision of a plan."""
from cleveragents.application.container import get_container
from cleveragents.application.services.decision_service import (
DecisionNotFoundError,
)
container = get_container()
svc = container.decision_service()
decision = svc.get_decision(decision_id)
# First, try treating the identifier as a decision_id (backward compat).
decision = None
with suppress(DecisionNotFoundError):
decision = svc.get_decision(identifier)
# If not found as a decision, try as a plan_id.
if decision is None:
console.print(f"[red]Error:[/red] Decision '{decision_id}' not found.")
decisions = svc.list_decisions(identifier)
if decisions:
decision = decisions[0]
if decision is None:
console.print(
f"[red]Error:[/red] '{identifier}' not found as a decision or plan."
)
raise typer.Exit(1)
data = _build_explain_dict(