Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d41e7eaf7b | |||
| 8e9ead35c5 | |||
| d51acc7619 | |||
| 91d16c8025 |
@@ -3,8 +3,6 @@ name: CI
|
||||
on:
|
||||
push:
|
||||
branches: [master, develop]
|
||||
pull_request:
|
||||
branches: [master, develop]
|
||||
|
||||
vars:
|
||||
docker_prefix: "http://harbor.cleverthis.com/docker/"
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
"""Step definitions for TDD Issue #4737 — agents plan execute rich output panels.
|
||||
|
||||
Verifies that the rich (default) output of ``agents plan execute`` renders the
|
||||
four spec-required panels (Execution, Sandbox, Strategy Summary, Progress) and
|
||||
the ``✓ OK Execution started`` footer line, instead of the generic
|
||||
``_print_lifecycle_plan`` output.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from behave import given, then, when
|
||||
from typer.testing import CliRunner
|
||||
|
||||
from cleveragents.cli.commands.plan import app as plan_app
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_PLAN_ID = "01JAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
|
||||
|
||||
def _make_execute_plan(
|
||||
plan_id: str = _PLAN_ID,
|
||||
phase: str = "execute",
|
||||
state: str = "queued",
|
||||
) -> object:
|
||||
"""Build a real v3 Plan object in execute/queued state for issue 4737."""
|
||||
from cleveragents.domain.models.core.plan import (
|
||||
NamespacedName,
|
||||
PlanIdentity,
|
||||
PlanPhase,
|
||||
PlanTimestamps,
|
||||
ProcessingState,
|
||||
ProjectLink,
|
||||
)
|
||||
from cleveragents.domain.models.core.plan import Plan as LifecyclePlan
|
||||
|
||||
return LifecyclePlan(
|
||||
identity=PlanIdentity(plan_id=plan_id, attempt=1),
|
||||
namespaced_name=NamespacedName(namespace="local", name="test-plan"),
|
||||
action_name="local/test-action",
|
||||
description="Test plan for issue 4737 regression",
|
||||
phase=PlanPhase(phase),
|
||||
processing_state=ProcessingState(state),
|
||||
project_links=[ProjectLink(project_name="proj-1")],
|
||||
execution_actor="local/executor",
|
||||
timestamps=PlanTimestamps(
|
||||
created_at=datetime.now(),
|
||||
updated_at=datetime.now(),
|
||||
execute_started_at=datetime(2026, 1, 1, 12, 58, 10),
|
||||
),
|
||||
sandbox_refs=[],
|
||||
decisions=[],
|
||||
invariants=[],
|
||||
last_completed_step=-1,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Given steps
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@given("a CLI runner and mocked services for issue 4737")
|
||||
def step_given_cli_runner_4737(context: object) -> None:
|
||||
context.runner_4737 = CliRunner()
|
||||
context.mock_service_4737 = MagicMock()
|
||||
context.mock_executor_4737 = MagicMock()
|
||||
|
||||
|
||||
@given("a plan in execute/queued state for issue 4737")
|
||||
def step_given_execute_queued_plan_4737(context: object) -> None:
|
||||
plan = _make_execute_plan(phase="execute", state="queued")
|
||||
context.mock_service_4737.execute_plan.return_value = plan
|
||||
context.mock_service_4737.get_plan.return_value = plan
|
||||
context.plan_4737 = plan
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# When steps
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@when("I invoke the plan execute CLI command for issue 4737")
|
||||
def step_when_invoke_execute_4737(context: object) -> None:
|
||||
with (
|
||||
patch(
|
||||
"cleveragents.cli.commands.plan._get_lifecycle_service",
|
||||
return_value=context.mock_service_4737,
|
||||
),
|
||||
patch(
|
||||
"cleveragents.cli.commands.plan._get_plan_executor",
|
||||
return_value=context.mock_executor_4737,
|
||||
),
|
||||
):
|
||||
result = context.runner_4737.invoke(
|
||||
plan_app,
|
||||
["execute", _PLAN_ID],
|
||||
)
|
||||
context.result_4737 = result
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Then steps
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@then("the CLI should succeed for issue 4737")
|
||||
def step_then_cli_succeed_4737(context: object) -> None:
|
||||
assert context.result_4737.exit_code == 0, (
|
||||
f"Exit code {context.result_4737.exit_code}, "
|
||||
f"output: {context.result_4737.output}"
|
||||
)
|
||||
|
||||
|
||||
@then("the output should contain an Execution panel for issue 4737")
|
||||
def step_then_execution_panel_4737(context: object) -> None:
|
||||
out = context.result_4737.output
|
||||
assert "Execution" in out, (
|
||||
f"Expected 'Execution' panel in output, got:\n{out}"
|
||||
)
|
||||
assert _PLAN_ID in out, (
|
||||
f"Expected plan ID {_PLAN_ID!r} in Execution panel, got:\n{out}"
|
||||
)
|
||||
|
||||
|
||||
@then("the output should contain a Sandbox panel for issue 4737")
|
||||
def step_then_sandbox_panel_4737(context: object) -> None:
|
||||
out = context.result_4737.output
|
||||
assert "Sandbox" in out, (
|
||||
f"Expected 'Sandbox' panel in output, got:\n{out}"
|
||||
)
|
||||
assert "git_worktree" in out, (
|
||||
f"Expected 'git_worktree' strategy in Sandbox panel, got:\n{out}"
|
||||
)
|
||||
|
||||
|
||||
@then("the output should contain a Strategy Summary panel for issue 4737")
|
||||
def step_then_strategy_summary_panel_4737(context: object) -> None:
|
||||
out = context.result_4737.output
|
||||
assert "Strategy Summary" in out, (
|
||||
f"Expected 'Strategy Summary' panel in output, got:\n{out}"
|
||||
)
|
||||
assert "Decisions" in out, (
|
||||
f"Expected 'Decisions' field in Strategy Summary panel, got:\n{out}"
|
||||
)
|
||||
|
||||
|
||||
@then("the output should contain a Progress panel for issue 4737")
|
||||
def step_then_progress_panel_4737(context: object) -> None:
|
||||
out = context.result_4737.output
|
||||
assert "Progress" in out, (
|
||||
f"Expected 'Progress' panel in output, got:\n{out}"
|
||||
)
|
||||
assert "Collect context" in out, (
|
||||
f"Expected 'Collect context' step in Progress panel, got:\n{out}"
|
||||
)
|
||||
assert "Validate" in out, (
|
||||
f"Expected 'Validate' step in Progress panel, got:\n{out}"
|
||||
)
|
||||
|
||||
|
||||
@then("the output should contain the OK Execution started footer for issue 4737")
|
||||
def step_then_ok_footer_4737(context: object) -> None:
|
||||
out = context.result_4737.output
|
||||
assert "Execution started" in out, (
|
||||
f"Expected 'Execution started' footer in output, got:\n{out}"
|
||||
)
|
||||
|
||||
|
||||
@then("the output should not contain the generic Plan Executed panel for issue 4737")
|
||||
def step_then_no_generic_panel_4737(context: object) -> None:
|
||||
out = context.result_4737.output
|
||||
assert "Plan Executed" not in out, (
|
||||
f"Expected generic 'Plan Executed' panel to be absent, got:\n{out}"
|
||||
)
|
||||
# Also verify the generic fields from _print_lifecycle_plan are absent
|
||||
assert "Processing State" not in out, (
|
||||
f"Expected generic 'Processing State' field to be absent, got:\n{out}"
|
||||
)
|
||||
@@ -0,0 +1,57 @@
|
||||
@tdd_issue @tdd_issue_4737 @mock_only
|
||||
Feature: TDD Issue #4737 — agents plan execute rich output renders spec-required panels
|
||||
As a developer
|
||||
I want to verify that the rich output of `agents plan execute` renders the four
|
||||
spec-required panels (Execution, Sandbox, Strategy Summary, Progress)
|
||||
So that the bug is captured and will be caught by a regression test
|
||||
|
||||
Bug #4737: The `execute_plan` CLI command was calling
|
||||
`_print_lifecycle_plan(plan, title="Plan Executed")` which renders a generic
|
||||
plan state dump. The spec requires four distinct panels:
|
||||
1. Execution — Plan ID, Phase, Sandbox strategy, Worker, Started, Attempt
|
||||
2. Sandbox — Strategy, Path, Branch, Status
|
||||
3. Strategy Summary — Decisions, Invariants, Planned Child Plans, Estimated Files, Risk
|
||||
4. Progress — 4 steps with status indicators
|
||||
Followed by a "✓ OK Execution started" footer line.
|
||||
|
||||
Scenario: Execute plan rich output renders Execution panel
|
||||
Given a CLI runner and mocked services for issue 4737
|
||||
And a plan in execute/queued state for issue 4737
|
||||
When I invoke the plan execute CLI command for issue 4737
|
||||
Then the CLI should succeed for issue 4737
|
||||
And the output should contain an Execution panel for issue 4737
|
||||
|
||||
Scenario: Execute plan rich output renders Sandbox panel
|
||||
Given a CLI runner and mocked services for issue 4737
|
||||
And a plan in execute/queued state for issue 4737
|
||||
When I invoke the plan execute CLI command for issue 4737
|
||||
Then the CLI should succeed for issue 4737
|
||||
And the output should contain a Sandbox panel for issue 4737
|
||||
|
||||
Scenario: Execute plan rich output renders Strategy Summary panel
|
||||
Given a CLI runner and mocked services for issue 4737
|
||||
And a plan in execute/queued state for issue 4737
|
||||
When I invoke the plan execute CLI command for issue 4737
|
||||
Then the CLI should succeed for issue 4737
|
||||
And the output should contain a Strategy Summary panel for issue 4737
|
||||
|
||||
Scenario: Execute plan rich output renders Progress panel
|
||||
Given a CLI runner and mocked services for issue 4737
|
||||
And a plan in execute/queued state for issue 4737
|
||||
When I invoke the plan execute CLI command for issue 4737
|
||||
Then the CLI should succeed for issue 4737
|
||||
And the output should contain a Progress panel for issue 4737
|
||||
|
||||
Scenario: Execute plan rich output renders OK footer line
|
||||
Given a CLI runner and mocked services for issue 4737
|
||||
And a plan in execute/queued state for issue 4737
|
||||
When I invoke the plan execute CLI command for issue 4737
|
||||
Then the CLI should succeed for issue 4737
|
||||
And the output should contain the OK Execution started footer for issue 4737
|
||||
|
||||
Scenario: Execute plan rich output does not render generic plan details panel
|
||||
Given a CLI runner and mocked services for issue 4737
|
||||
And a plan in execute/queued state for issue 4737
|
||||
When I invoke the plan execute CLI command for issue 4737
|
||||
Then the CLI should succeed for issue 4737
|
||||
And the output should not contain the generic Plan Executed panel for issue 4737
|
||||
+21
@@ -45,6 +45,27 @@ nav:
|
||||
- Documentation Writer: development/docs-writer.md
|
||||
- Implementation Timeline: timeline.md
|
||||
- FAQ: faq.md
|
||||
- Showcase:
|
||||
- Overview: showcase/index.md
|
||||
- CLI Tools:
|
||||
- Overview: showcase/cli-tools/README.md
|
||||
- CleverAgents CLI Basics: showcase/cli-tools/cleveragents-cli-basics.md
|
||||
- Action and Plan Management: showcase/cli-tools/action-and-plan-management.md
|
||||
- Actor Management Workflow: showcase/cli-tools/actor-management-workflow.md
|
||||
- Actor Context Management: showcase/cli-tools/actor-context-management.md
|
||||
- Project Init and Context Management: showcase/cli-tools/project-init-and-context-management.md
|
||||
- Resource and Skill Management: showcase/cli-tools/resource-and-skill-management.md
|
||||
- Session Management Workflows: showcase/cli-tools/session-management-workflows.md
|
||||
- Tool and Validation Management: showcase/cli-tools/tool-and-validation-management.md
|
||||
- REPL and Actor Run: showcase/cli-tools/repl-and-actor-run.md
|
||||
- Repo Indexing Workflows: showcase/cli-tools/repo-indexing-workflows.md
|
||||
- Database Migration Management: showcase/cli-tools/database-migration-management.md
|
||||
- Server and A2A Integration: showcase/cli-tools/server-and-a2a-integration.md
|
||||
- Output Format Flags: showcase/cli-tools/output-format-flags.md
|
||||
- Audit Log and Security: showcase/cli-tools/audit-log-and-security.md
|
||||
- API Clients: showcase/api-clients/README.md
|
||||
- Data Processing: showcase/data-processing/README.md
|
||||
- Testing Tools: showcase/testing-tools/README.md
|
||||
- Reference: reference/
|
||||
- Architecture Decision Records (ADRs):
|
||||
- Overview: adr/index.md
|
||||
|
||||
@@ -2376,6 +2376,128 @@ def _print_lifecycle_plan(plan: Any, title: str = "Plan") -> None:
|
||||
console.print(Panel(details, title=title, expand=False))
|
||||
|
||||
|
||||
def _print_execute_output(plan: Any) -> None:
|
||||
"""Print spec-required execute output panels for ``agents plan execute``.
|
||||
|
||||
Renders the four panels defined in the spec §agents plan execute:
|
||||
|
||||
1. **Execution** — Plan ID, Phase, Sandbox strategy, Worker, Started, Attempt
|
||||
2. **Sandbox** — Strategy, Path, Branch, Status
|
||||
3. **Strategy Summary** — Decisions, Invariants, Planned Child Plans,
|
||||
Estimated Files, Risk
|
||||
4. **Progress** — 4 steps (Collect context, Run tools, Build changeset,
|
||||
Validate) with status indicators
|
||||
|
||||
Followed by a ``✓ OK Execution started`` footer line.
|
||||
|
||||
Args:
|
||||
plan: A v3 Plan object from the domain model.
|
||||
"""
|
||||
from cleveragents.domain.models.core.plan import (
|
||||
Plan as LifecyclePlan,
|
||||
)
|
||||
|
||||
if not isinstance(plan, LifecyclePlan):
|
||||
console.print(Panel(f"Plan: {plan}", title="Execution", expand=False))
|
||||
return
|
||||
|
||||
plan_id = plan.identity.plan_id
|
||||
sandbox = plan.sandbox_refs[0] if plan.sandbox_refs else None
|
||||
worker = plan.execution_actor or "local/executor"
|
||||
started_str = (
|
||||
plan.timestamps.execute_started_at.strftime("%H:%M:%S")
|
||||
if plan.timestamps.execute_started_at
|
||||
else "—"
|
||||
)
|
||||
attempt = plan.identity.attempt
|
||||
|
||||
# Execution panel
|
||||
console.print(
|
||||
Panel(
|
||||
f"[bold]Plan:[/bold] {plan_id}\n"
|
||||
f"[bold]Phase:[/bold] {plan.phase.value}\n"
|
||||
f"[bold]Sandbox:[/bold] git_worktree\n"
|
||||
f"[bold]Worker:[/bold] {worker}\n"
|
||||
f"[bold]Started:[/bold] {started_str}\n"
|
||||
f"[bold]Attempt:[/bold] {attempt}",
|
||||
title="Execution",
|
||||
expand=False,
|
||||
)
|
||||
)
|
||||
|
||||
# Sandbox panel
|
||||
sandbox_path = sandbox or "(pending)"
|
||||
sandbox_branch = f"cleveragents/plan-{plan_id[:8]}"
|
||||
sandbox_status = "active" if sandbox else "pending"
|
||||
console.print(
|
||||
Panel(
|
||||
f"[bold]Strategy:[/bold] git_worktree\n"
|
||||
f"[bold]Path:[/bold] {sandbox_path}\n"
|
||||
f"[bold]Branch:[/bold] {sandbox_branch}\n"
|
||||
f"[bold]Status:[/bold] {sandbox_status}",
|
||||
title="Sandbox",
|
||||
expand=False,
|
||||
)
|
||||
)
|
||||
|
||||
# Strategy Summary panel
|
||||
decisions_count = len(plan.decisions) if plan.decisions else 0
|
||||
invariants_count = len(plan.invariants) if plan.invariants else 0
|
||||
est = plan.estimation_result
|
||||
child_plans_str = (
|
||||
f"{est.estimated_child_plans}+"
|
||||
if est is not None and est.estimated_child_plans is not None
|
||||
else "—"
|
||||
)
|
||||
estimated_files_str = (
|
||||
f"~{est.estimated_steps}"
|
||||
if est is not None and est.estimated_steps is not None
|
||||
else "—"
|
||||
)
|
||||
risk_str = (
|
||||
est.risk_level if est is not None and est.risk_level is not None else "—"
|
||||
)
|
||||
console.print(
|
||||
Panel(
|
||||
f"[bold]Decisions:[/bold] {decisions_count}\n"
|
||||
f"[bold]Invariants:[/bold] {invariants_count}\n"
|
||||
f"[bold]Planned Child Plans:[/bold] {child_plans_str}\n"
|
||||
f"[bold]Estimated Files:[/bold] {estimated_files_str}\n"
|
||||
f"[bold]Risk:[/bold] {risk_str}",
|
||||
title="Strategy Summary",
|
||||
expand=False,
|
||||
)
|
||||
)
|
||||
|
||||
# Progress panel — 4 steps with status indicators
|
||||
# Determine step status based on last_completed_step
|
||||
steps = [
|
||||
"Collect context",
|
||||
"Run tools",
|
||||
"Build changeset",
|
||||
"Validate",
|
||||
]
|
||||
last_step = plan.last_completed_step # -1 means none completed
|
||||
step_lines: list[str] = []
|
||||
for i, step_name in enumerate(steps):
|
||||
if i <= last_step:
|
||||
indicator = "✓"
|
||||
elif i == last_step + 1:
|
||||
indicator = "⏳"
|
||||
else:
|
||||
indicator = "•"
|
||||
step_lines.append(f"{indicator} {step_name}")
|
||||
console.print(
|
||||
Panel(
|
||||
"\n".join(step_lines),
|
||||
title="Progress",
|
||||
expand=False,
|
||||
)
|
||||
)
|
||||
|
||||
console.print("[green]✓ OK[/green] Execution started")
|
||||
|
||||
|
||||
@app.command("use")
|
||||
def use_action(
|
||||
action_name: Annotated[
|
||||
@@ -2925,21 +3047,7 @@ def execute_plan(
|
||||
)
|
||||
console.print(format_output(envelope, fmt))
|
||||
else:
|
||||
_print_lifecycle_plan(plan, title="Plan Executed")
|
||||
phase_label = f"{plan.phase.value}/{plan.state.value}"
|
||||
if plan.phase == PlanPhase.EXECUTE and plan.state in (
|
||||
ProcessingState.COMPLETE,
|
||||
ProcessingState.APPLIED,
|
||||
):
|
||||
console.print(
|
||||
f"\n[dim]Plan execution completed ({phase_label}). "
|
||||
"Run 'agents plan apply <id>' when ready.[/dim]"
|
||||
)
|
||||
else:
|
||||
console.print(
|
||||
f"\n[dim]Plan is now in {phase_label} state. "
|
||||
"Run 'agents plan execute <id>' to continue.[/dim]"
|
||||
)
|
||||
_print_execute_output(plan)
|
||||
|
||||
except PreflightRejection as e:
|
||||
console.print(f"[red]Pre-flight check failed:[/red] {e}")
|
||||
|
||||
Reference in New Issue
Block a user