feat(cli): register v3 project and resource commands in main.py + DI wiring (B2.9)

This commit is contained in:
khyari hamza
2026-02-12 16:52:25 +00:00
parent 659e6c00ac
commit d0cfdecdd5
4 changed files with 201 additions and 0 deletions
+46
View File
@@ -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
+133
View File
@@ -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}"
)
@@ -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,
+16
View File
@@ -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 <name>'.")
@@ -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