refactor(cli): move context commands to actor context subgroup #1194

Merged
freemo merged 1 commits from feature/m4-actor-context-hierarchy into master 2026-04-02 17:53:43 +00:00
6 changed files with 80 additions and 29 deletions
+2 -2
View File
@@ -116,7 +116,7 @@ Feature: CLI Plan and Context Commands - Complete Coverage
Scenario: Remove file from context
Given I have an initialized project
And I have added files "main.py", "utils.py" to context
When I run "cleveragents context rm main.py"
When I run "cleveragents actor context remove main.py"
Then the command should succeed
And "main.py" should not be in context
And "utils.py" should still be in context
@@ -157,7 +157,7 @@ Feature: CLI Plan and Context Commands - Complete Coverage
Scenario: Cannot remove non-existent context file
Given I have an initialized project
When I run "cleveragents context rm non-existent.py"
When I run "cleveragents actor context remove non-existent.py"
Then the command should fail
And the error message should mention "not in context"
@@ -135,7 +135,7 @@ def step_run_context_remove(context):
else:
file_to_remove = "dummy_file.py"
result = runner.invoke(context_app, ["rm", file_to_remove])
result = runner.invoke(context_app, ["remove", file_to_remove])
context.result = result
@@ -424,7 +424,7 @@ def step_verify_context_help(context):
"""Verify context help displayed."""
assert hasattr(context, "result")
assert context.result.exit_code == 0
assert "Context management" in context.result.output
assert "context management" in context.result.output.lower()
@then("the plan help should display")
+14 -14
View File
@@ -307,7 +307,7 @@ def step_exec_context_remove(context):
project_service.get_current_project.return_value = object()
container.project_service.return_value = project_service
result = runner.invoke(context_app.app, ["rm", "/file1.txt", "/file2.txt"])
result = runner.invoke(context_app.app, ["remove", "/file1.txt", "/file2.txt"])
context.result = result
@@ -323,7 +323,7 @@ def step_exec_context_remove_no_project(context):
project_service.get_current_project.return_value = None
container.project_service.return_value = project_service
result = runner.invoke(context_app.app, ["rm", "/file1.txt"])
result = runner.invoke(context_app.app, ["remove", "/file1.txt"])
context.result = result
@@ -339,7 +339,7 @@ def step_exec_context_remove_relative(context):
project_service.get_current_project.return_value = object()
container.project_service.return_value = project_service
result = runner.invoke(context_app.app, ["rm", "file1.txt", "file2.txt"])
result = runner.invoke(context_app.app, ["remove", "file1.txt", "file2.txt"])
context.result = result
@@ -356,7 +356,7 @@ def step_exec_context_remove_failure(context):
project_service.get_current_project.return_value = object()
container.project_service.return_value = project_service
result = runner.invoke(context_app.app, ["rm", "/file1.txt"])
result = runner.invoke(context_app.app, ["remove", "/file1.txt"])
context.result = result
@@ -822,8 +822,8 @@ def step_check_empty_message(context):
f"Clean output (no color codes): '{clean_output}'"
)
assert "agents context add" in clean_output, (
f"Expected help text 'agents context add' in output.\n"
assert "agents actor context add" in clean_output, (
f"Expected help text 'agents actor context add' in output.\n"
f"Raw output: '{context.result.output}'\n"
f"Clean output (no color codes): '{clean_output}'"
)
@@ -1166,47 +1166,47 @@ def step_then_show_returns_content(context):
@when("the programmatic remove command runs without an active project")
def step_when_programmatic_remove_no_project(context):
"""Execute rm_command without an active project configured."""
"""Execute remove_command without an active project configured."""
context.programmatic_exception = None
with patched_container(context, project=None):
try:
context_app.rm_command(["missing.txt"])
context_app.remove_command(["missing.txt"])
except Exception as exc:
context.programmatic_exception = exc
@when("the programmatic remove command runs for files")
def step_when_programmatic_remove(context):
"""Execute rm_command where files are missing."""
"""Execute remove_command where files are missing."""
context.programmatic_exception = None
with patched_container(context, project=object()):
try:
context_app.rm_command(["missing.txt", "other.txt"])
context_app.remove_command(["missing.txt", "other.txt"])
except Exception as exc:
context.programmatic_exception = exc
@when("the programmatic remove command runs with removable files")
def step_when_programmatic_remove_success(context):
"""Execute rm_command when files are present in context."""
"""Execute remove_command when files are present in context."""
context.programmatic_exception = None
context.mock_service.remove_from_context.reset_mock()
with patched_container(context, project=object()):
try:
context_app.rm_command(["/file1.txt", "/file2.txt"])
context_app.remove_command(["/file1.txt", "/file2.txt"])
except Exception as exc:
context.programmatic_exception = exc
@then("a CleverAgentsError is raised for programmatic remove")
def step_then_remove_requires_presence(context):
"""Ensure rm_command raises when files are not in context."""
"""Ensure remove_command raises when files are not in context."""
assert isinstance(context.programmatic_exception, CleverAgentsError)
@then("the programmatic remove command should complete successfully")
def step_then_remove_success(context):
"""Ensure rm_command succeeds when files are removed."""
"""Ensure remove_command succeeds when files are removed."""
assert context.programmatic_exception is None
assert context.mock_service.remove_from_context.call_count == 2
+4 -4
View File
@@ -198,17 +198,17 @@ Show Context Content
[Teardown] Cleanup Test Directory
Remove File From Context
[Documentation] Test context rm command
[Documentation] Test actor context remove command
[Setup] Initialize Test Project With Context
# Remove test.py
${result}= Run Process ${PYTHON} -m cleveragents context rm test.py
# Remove test.py via canonical path: agents actor context remove
${result}= Run Process ${PYTHON} -m cleveragents actor context remove test.py
... cwd=${TEST_DIR} timeout=120s on_timeout=kill
Should Be Equal As Integers ${result.rc} 0
# Verify removal
${result}= Run Process ${PYTHON} -m cleveragents context list
${result}= Run Process ${PYTHON} -m cleveragents actor context list
... cwd=${TEST_DIR} timeout=120s on_timeout=kill
Should Not Contain ${result.stdout} test.py
+42 -5
View File
@@ -2,6 +2,9 @@
This module implements context-related commands for managing files and directories
that the AI will work with.
Canonical path: ``agents actor context <subcommand>``
Deprecated alias: ``agents context <subcommand>`` (emits deprecation warning)
"""
from __future__ import annotations
@@ -23,7 +26,9 @@ if TYPE_CHECKING:
from cleveragents.domain.models.core import Context
# Create sub-app for context commands
app = typer.Typer(help="Context management commands")
app = typer.Typer(
help="Actor context management commands (canonical: agents actor context)"
)
console = _get_console()
@@ -142,7 +147,7 @@ def show_command(path: str | None = None) -> str | None:
return f"Total files: {len(context_files)}"
def rm_command(paths: list[str]) -> None:
def remove_command(paths: list[str]) -> None:
"""Programmatic interface for removing files from context.
Args:
@@ -177,6 +182,18 @@ def rm_command(paths: list[str]) -> None:
raise CleverAgentsError(f"File(s) not in context: {', '.join(not_in_context)}")
def rm_command(paths: list[str]) -> None:
"""Deprecated alias for :func:`remove_command`."""
import warnings
warnings.warn(
"rm_command is deprecated; use remove_command instead.",
DeprecationWarning,
stacklevel=2,
)
remove_command(paths)
def clear_command() -> None:
"""Programmatic interface for clearing all context files."""
from cleveragents.application.container import get_container
@@ -285,7 +302,7 @@ def context_load(
context_add(paths, recursive)
@app.command("rm")
@app.command("remove")
def context_remove(
paths: Annotated[
list[str],
@@ -358,6 +375,25 @@ def context_remove(
raise typer.Abort() from e
@app.command("rm", hidden=True)
def context_rm_deprecated(
paths: Annotated[
list[str],
typer.Argument(help="Paths to remove from context"),
],
) -> None:
"""Remove files from context (deprecated: use 'remove' instead)."""
import warnings
warnings.warn(
"'agents context rm' is deprecated; use 'agents actor context remove' instead.",
DeprecationWarning,
stacklevel=1,
)
console.print("[yellow]Warning:[/yellow] 'rm' is deprecated; use 'remove' instead.")
context_remove(paths)
@app.command("list")
def context_list(
context_dir: Annotated[
@@ -412,7 +448,7 @@ def context_list(
if not context_files:
console.print("[yellow]No files in context.[/yellow]")
console.print("Use 'agents context add <path>' to add files.")
console.print("Use 'agents actor context add <path>' to add files.")
return
# Display context files
@@ -519,7 +555,8 @@ def context_show(
console.print(f"Total size: {total_size:,} bytes")
console.print(
"\nUse 'agents context show <file>' to view specific file content."
"\nUse 'agents actor context show <file>' to view"
" specific file content."
)
except CleverAgentsError as e:
+16 -2
View File
@@ -110,7 +110,21 @@ def _register_subcommands() -> None:
return
app.add_typer(project.app, name="project", help="Project management")
app.add_typer(context.app, name="context", help="Context management")
# Register context as a sub-app of actor (canonical: agents actor context)
actor.app.add_typer(
context.app,
name="context",
help="Actor context management commands",
)
# Keep top-level 'agents context' as deprecated alias
app.add_typer(
context.app,
name="context",
help="Context management (deprecated: use 'agents actor context' instead)",
deprecated=True,
)
app.add_typer(
plan.app,
name="plan",
@@ -236,7 +250,7 @@ def _print_basic_help() -> None:
typer.echo("Usage: cleveragents [OPTIONS] COMMAND [ARGS]...")
typer.echo("\nCommon commands:")
typer.echo(" project Project management")
typer.echo(" context Context management")
typer.echo(" actor context Actor context management")
typer.echo(" plan Plan operations (actor required)")
typer.echo(" actor Actor management and defaults")
typer.echo(" init Initialize a project")