fix(cli): implement spec-required structured panels in agents plan explain rich output
Replace the single flat 'Decision Details' table in explain_decision_cmd()
with six structured Rich panels matching the spec exactly:
- Decision panel: ID, Type, Question, Chosen, Confidence, Plan, Sequence, Created
- Alternatives Considered panel: numbered list with '(chosen)' marker
- Impact panel: Downstream Decisions, Downstream Child Plans, Artifacts Produced,
Correction Impact (derived from downstream_decision_ids count)
- Context Snapshot panel (--show-context): resource paths + hot context hash
- Rationale panel (--show-reasoning): rationale text
- Correction panel: 'agents plan correct <ID> --mode revert --guidance "..."' hint
Also adds the missing success message '✓ OK Decision explained'.
Updates _build_explain_dict() to:
- Accept total_decisions parameter for 'X of Y' sequence format
- Return structured alternatives list [{index, description, chosen}] instead
of flat string list
- Include impact dict with downstream_decisions, downstream_child_plans,
artifacts_produced, correction_impact
- Include correction_hint field
Updates feature files and step definitions to test the new output structure.
ISSUES CLOSED: #2815
This commit is contained in:
@@ -14,6 +14,8 @@ Feature: Plan explain and decision tree CLI commands
|
||||
And the explain dict should contain key "chosen"
|
||||
And the explain dict should contain key "type"
|
||||
And the explain dict should contain key "alternatives"
|
||||
And the explain dict should contain key "impact"
|
||||
And the explain dict should contain key "correction_hint"
|
||||
And the explain dict should not contain key "rationale"
|
||||
And the explain dict should not contain key "context_snapshot"
|
||||
|
||||
@@ -47,8 +49,38 @@ Feature: Plan explain and decision tree CLI commands
|
||||
When I build the explain dict with default options
|
||||
Then the explain dict should contain key "alternatives"
|
||||
And the alternatives list should have 2 items
|
||||
And each alternative should have keys "index", "description", and "chosen"
|
||||
And exactly one alternative should have chosen set to true
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# plan explain - impact dict always included
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
Scenario: Explain includes impact dict by default
|
||||
Given a test decision for explain
|
||||
When I build the explain dict with default options
|
||||
Then the explain dict should contain key "impact"
|
||||
And the impact dict should contain key "downstream_decisions"
|
||||
And the impact dict should contain key "downstream_child_plans"
|
||||
And the impact dict should contain key "artifacts_produced"
|
||||
And the impact dict should contain key "correction_impact"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# plan explain - correction_hint always included
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
Scenario: Explain includes correction_hint by default
|
||||
Given a test decision for explain
|
||||
When I build the explain dict with default options
|
||||
Then the explain dict should contain key "correction_hint"
|
||||
And the correction hint should reference the decision id
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# plan explain - sequence format
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
Scenario: Explain sequence shows X of Y when total provided
|
||||
Given a test decision for explain
|
||||
When I build the explain dict with total decisions 5
|
||||
Then the sequence should be formatted as "X of Y"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# plan explain - json format
|
||||
|
||||
@@ -12,7 +12,7 @@ Feature: Plan explain and tree CLI command coverage
|
||||
Given pec a mock DecisionService returning a valid decision
|
||||
When pec I invoke "explain" with the decision id
|
||||
Then pec the exit code should be 0
|
||||
And pec the output should contain "Decision Details"
|
||||
And pec the output should contain "Decision"
|
||||
And pec the output should contain "decision_id"
|
||||
|
||||
Scenario: Explain CLI renders json format
|
||||
@@ -49,7 +49,7 @@ Feature: Plan explain and tree CLI command coverage
|
||||
Given pec a mock DecisionService returning a decision with alternatives
|
||||
When pec I invoke "explain" with the decision id
|
||||
Then pec the exit code should be 0
|
||||
And pec the output should contain "alternatives"
|
||||
And pec the output should contain "Alternatives Considered"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# tree_decisions_cmd - rich tree format
|
||||
|
||||
@@ -106,6 +106,8 @@ def _make_decision(
|
||||
|
||||
|
||||
def _mock_container_with_decision_svc(svc_mock: MagicMock) -> MagicMock:
|
||||
# Ensure count_decisions returns an int for sequence formatting.
|
||||
svc_mock.count_decisions.return_value = 3
|
||||
container = MagicMock()
|
||||
container.decision_service.return_value = svc_mock
|
||||
return container
|
||||
|
||||
@@ -238,6 +238,13 @@ def step_build_explain_reasoning(context: Context) -> None:
|
||||
)
|
||||
|
||||
|
||||
@when("I build the explain dict with total decisions {total:d}")
|
||||
def step_build_explain_with_total(context: Context, total: int) -> None:
|
||||
context.pe_explain_dict = _build_explain_dict(
|
||||
context.pe_decision, total_decisions=total
|
||||
)
|
||||
|
||||
|
||||
def _capture_format_output(data, fmt):
|
||||
"""Call format_output capturing stdout (machine-readable formats write there)."""
|
||||
from contextlib import redirect_stdout
|
||||
@@ -321,6 +328,15 @@ def step_snapshot_has_key(context: Context, key: str) -> None:
|
||||
assert key in snap, f"Expected key '{key}' in snapshot: {list(snap.keys())}"
|
||||
|
||||
|
||||
@then('the impact dict should contain key "{key}"')
|
||||
def step_impact_has_key(context: Context, key: str) -> None:
|
||||
impact = context.pe_explain_dict["impact"]
|
||||
assert isinstance(impact, dict), (
|
||||
f"Expected 'impact' to be a dict, got {type(impact)}"
|
||||
)
|
||||
assert key in impact, f"Expected key '{key}' in impact: {list(impact.keys())}"
|
||||
|
||||
|
||||
@then("the alternatives list should have {count:d} items")
|
||||
def step_alternatives_count(context: Context, count: int) -> None:
|
||||
alts = context.pe_explain_dict["alternatives"]
|
||||
@@ -352,6 +368,21 @@ def step_exactly_one_chosen(context: Context) -> None:
|
||||
)
|
||||
|
||||
|
||||
@then("the correction hint should reference the decision id")
|
||||
def step_correction_hint_has_decision_id(context: Context) -> None:
|
||||
hint = context.pe_explain_dict["correction_hint"]
|
||||
decision_id = context.pe_explain_dict["decision_id"]
|
||||
assert decision_id in str(hint), (
|
||||
f"Expected decision_id '{decision_id}' in correction_hint '{hint}'"
|
||||
)
|
||||
|
||||
|
||||
@then('the sequence should be formatted as "X of Y"')
|
||||
def step_sequence_x_of_y(context: Context) -> None:
|
||||
seq = context.pe_explain_dict["sequence"]
|
||||
assert " of " in str(seq), f"Expected sequence in 'X of Y' format, got '{seq}'"
|
||||
|
||||
|
||||
@then('the json output should contain "{text}"')
|
||||
def step_json_contains(context: Context, text: str) -> None:
|
||||
assert text in context.pe_json_output, f"Expected '{text}' in JSON output"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user