From d0cfdecdd51a39aeece47ada1a1b405e6e05ddbf Mon Sep 17 00:00:00 2001 From: khyari hamza Date: Thu, 12 Feb 2026 16:52:25 +0000 Subject: [PATCH] feat(cli): register v3 project and resource commands in main.py + DI wiring (B2.9) --- features/cli_v3_registration.feature | 46 +++++++ features/steps/cli_v3_registration_steps.py | 133 ++++++++++++++++++++ src/cleveragents/application/container.py | 6 + src/cleveragents/cli/main.py | 16 +++ 4 files changed, 201 insertions(+) create mode 100644 features/cli_v3_registration.feature create mode 100644 features/steps/cli_v3_registration_steps.py diff --git a/features/cli_v3_registration.feature b/features/cli_v3_registration.feature new file mode 100644 index 00000000..dab49222 --- /dev/null +++ b/features/cli_v3_registration.feature @@ -0,0 +1,46 @@ +@cli @v3 @registration +Feature: V3 CLI Registration and DI Wiring + As a developer using the CleverAgents CLI + I want v3 project and resource commands registered in main.py + So that I can access them from the top-level CLI + + Background: + Given the v3 CLI registration module is loaded + + # ── Subcommand registration ── + + Scenario: project-v3 subcommand is registered in the CLI app + When I check the v3 registered subcommand names + Then the v3 subcommand "project-v3" should be present + + Scenario: resource-v3 subcommand is registered in the CLI app + When I check the v3 registered subcommand names + Then the v3 subcommand "resource-v3" should be present + + Scenario: project-v3 is listed in valid_cmds for argument validation + When I inspect the v3 valid commands list + Then the v3 valid command "project-v3" should be listed + + Scenario: resource-v3 is listed in valid_cmds for argument validation + When I inspect the v3 valid commands list + Then the v3 valid command "resource-v3" should be listed + + # ── DI Container wiring ── + + Scenario: DI container provides ProjectServiceV3 + When I request the v3 project service from the container + Then the v3 container should return a ProjectServiceV3 instance + + Scenario: DI container provides ResourceServiceV3 + When I request the v3 resource service from the container + Then the v3 container should return a ResourceServiceV3 instance + + # ── End-to-end smoke: commands don't crash on --help ── + + Scenario: project-v3 --help runs without error + When I invoke the v3 CLI with "project-v3 --help" + Then the v3 CLI exit code should be 0 + + Scenario: resource-v3 --help runs without error + When I invoke the v3 CLI with "resource-v3 --help" + Then the v3 CLI exit code should be 0 diff --git a/features/steps/cli_v3_registration_steps.py b/features/steps/cli_v3_registration_steps.py new file mode 100644 index 00000000..052d677d --- /dev/null +++ b/features/steps/cli_v3_registration_steps.py @@ -0,0 +1,133 @@ +"""Step definitions for V3 CLI registration and DI wiring feature. + +All step texts use the 'v3' prefix to avoid global behave +step-name collisions with existing definitions. +""" + +from __future__ import annotations + +from behave import given, then, when # type: ignore[import-untyped] + + +# ── Background ── + + +@given("the v3 CLI registration module is loaded") +def step_v3_cli_module_loaded(context): + """Import the CLI main module so registration runs.""" + import importlib + + context.cli_main = importlib.import_module("cleveragents.cli.main") + # Ensure subcommands are registered + context.cli_main.ensure_cli_commands_registered() + + +# ── Subcommand registration ── + + +@when("I check the v3 registered subcommand names") +def step_check_v3_subcommand_names(context): + """Collect registered Typer group names.""" + app = context.cli_main.app + context.v3_group_names = [g.name for g in app.registered_groups] + + +@then('the v3 subcommand "{name}" should be present') +def step_v3_subcommand_present(context, name): + """Assert a subcommand name exists in registered groups.""" + assert name in context.v3_group_names, ( + f"Subcommand '{name}' not found in registered groups: {context.v3_group_names}" + ) + + +# ── valid_cmds list ── + + +@when("I inspect the v3 valid commands list") +def step_inspect_v3_valid_cmds(context): + """Read the valid_cmds list from the main() function source. + + Since valid_cmds is a local variable inside main(), we extract it + by inspecting the source code. Alternatively, we call main() with + a known-bad command and check the error, but reading source is simpler. + """ + import inspect + import re + + src = inspect.getsource(context.cli_main.main) + # Find the valid_cmds = [...] list literal + match = re.search(r"valid_cmds\s*=\s*\[(.*?)\]", src, re.DOTALL) + assert match, "valid_cmds list not found in main() source" + raw = match.group(1) + # Extract all string literals from it + context.v3_valid_cmds = re.findall(r'"([^"]+)"', raw) + + +@then('the v3 valid command "{name}" should be listed') +def step_v3_valid_cmd_listed(context, name): + """Assert a command name is in the valid_cmds list.""" + assert name in context.v3_valid_cmds, ( + f"Command '{name}' not in valid_cmds: {context.v3_valid_cmds}" + ) + + +# ── DI Container wiring ── + + +@when("I request the v3 project service from the container") +def step_request_v3_project_service(context): + """Get ProjectServiceV3 from the DI container.""" + from cleveragents.application.container import get_container + + container = get_container() + context.v3_project_service = container.project_service_v3() + + +@then("the v3 container should return a ProjectServiceV3 instance") +def step_v3_container_project_service(context): + """Assert the container returned a ProjectServiceV3.""" + from cleveragents.application.services.project_service_v3 import ( + ProjectServiceV3, + ) + + assert isinstance(context.v3_project_service, ProjectServiceV3), ( + f"Expected ProjectServiceV3, got {type(context.v3_project_service)}" + ) + + +@when("I request the v3 resource service from the container") +def step_request_v3_resource_service(context): + """Get ResourceServiceV3 from the DI container.""" + from cleveragents.application.container import get_container + + container = get_container() + context.v3_resource_service = container.resource_service_v3() + + +@then("the v3 container should return a ResourceServiceV3 instance") +def step_v3_container_resource_service(context): + """Assert the container returned a ResourceServiceV3.""" + from cleveragents.application.services.resource_service_v3 import ( + ResourceServiceV3, + ) + + assert isinstance(context.v3_resource_service, ResourceServiceV3), ( + f"Expected ResourceServiceV3, got {type(context.v3_resource_service)}" + ) + + +# ── Smoke tests: --help ── + + +@when('I invoke the v3 CLI with "{args}"') +def step_invoke_v3_cli(context, args): + """Run the CLI main function with the given args.""" + context.v3_exit_code = context.cli_main.main(args.split()) + + +@then("the v3 CLI exit code should be {code:d}") +def step_v3_cli_exit_code(context, code): + """Assert the CLI exit code.""" + assert context.v3_exit_code == code, ( + f"Expected exit code {code}, got {context.v3_exit_code}" + ) diff --git a/src/cleveragents/application/container.py b/src/cleveragents/application/container.py index c4213d31..a5a3ca60 100644 --- a/src/cleveragents/application/container.py +++ b/src/cleveragents/application/container.py @@ -14,6 +14,8 @@ from cleveragents.application.services.actor_service import ActorService from cleveragents.application.services.context_service import ContextService from cleveragents.application.services.plan_service import PlanService from cleveragents.application.services.project_service import ProjectService +from cleveragents.application.services.project_service_v3 import ProjectServiceV3 +from cleveragents.application.services.resource_service_v3 import ResourceServiceV3 from cleveragents.application.services.vector_store_service import VectorStoreService from cleveragents.config.settings import Settings, get_settings from cleveragents.domain.providers.ai_provider import AIProviderInterface @@ -129,6 +131,10 @@ class Container(containers.DeclarativeContainer): unit_of_work=unit_of_work, ) + # V3 services (in-memory, no persistence dependencies yet) + project_service_v3 = providers.Factory(ProjectServiceV3) + resource_service_v3 = providers.Factory(ResourceServiceV3) + vector_store_service = providers.Factory( VectorStoreService, settings=settings, diff --git a/src/cleveragents/cli/main.py b/src/cleveragents/cli/main.py index 42103e79..05135e91 100644 --- a/src/cleveragents/cli/main.py +++ b/src/cleveragents/cli/main.py @@ -78,6 +78,8 @@ def _register_subcommands() -> None: try: from cleveragents.cli.commands import action, actor, context, plan, project from cleveragents.cli.commands.auto_debug import app as auto_debug_app + from cleveragents.cli.commands.project_v3 import app as project_v3_app + from cleveragents.cli.commands.resource_v3 import app as resource_v3_app except Exception as exc: # pragma: no cover import traceback @@ -110,6 +112,16 @@ def _register_subcommands() -> None: help="Manage actions (reusable plan templates) for the v3 plan lifecycle", ) app.add_typer(auto_debug_app, name="auto-debug", help="Auto-debug operations") + app.add_typer( + project_v3_app, + name="project-v3", + help="Project management (v3 with namespaces and resource linking)", + ) + app.add_typer( + resource_v3_app, + name="resource-v3", + help="Resource management (v3 with types, registry, and tree view)", + ) _subcommands_registered = True @@ -141,6 +153,8 @@ def _print_basic_help() -> None: typer.echo(" build Build the current plan") typer.echo(" apply Apply plan changes") typer.echo(" auto-debug Auto-debug operations") + typer.echo(" project-v3 Project management (v3)") + typer.echo(" resource-v3 Resource management (v3)") typer.echo(" version Show version") typer.echo("") typer.echo("Actors: set a default with 'agents actor set-default '.") @@ -458,6 +472,8 @@ def main(args: list[str] | None = None) -> int: "actor", "action", # v3 plan lifecycle actions "auto-debug", # Auto-debug commands + "project-v3", # V3 project commands (namespaces, linking) + "resource-v3", # V3 resource commands (types, registry, tree) "tell", # Shortcut for plan tell "build", # Shortcut for plan build "apply", # Shortcut for plan apply