Compare commits

...

1 Commits

Author SHA1 Message Date
HAL9000 6cb01ba399 fix(cli): add [REGEX] positional argument to actor context list (#6500)
ISSUES CLOSED: #6500
2026-04-10 19:44:07 +00:00
6 changed files with 176 additions and 7 deletions
+9
View File
@@ -6,6 +6,15 @@ Feature: Actor context remove, export, and import commands
Background:
Given a temporary context directory for actor context tests
@actor_context_list_regex
Scenario: List actor contexts filtered by regex
Given an actor context named "docs" exists
And an actor context named "notes" exists
When I run actor context list "docs"
Then the actor context list command should succeed
And the list output should contain "docs"
And the list output should not contain "notes"
# ── context remove ─────────────────────────────────────────
Scenario: Remove a named actor context
@@ -105,6 +105,36 @@ def step_create_json_file_with_name(context, name):
context.import_file.write_text(json.dumps(data, indent=2), encoding="utf-8")
# ---------------------------------------------------------------------------
# When — list
# ---------------------------------------------------------------------------
@when('I run actor context list "{pattern}"')
def step_list_with_pattern(context, pattern):
context.result = context.runner.invoke(
actor_context_app,
[
"list",
pattern,
"--context-dir",
str(context.context_dir),
],
)
@when("I run actor context list without a pattern")
def step_list_without_pattern(context):
context.result = context.runner.invoke(
actor_context_app,
[
"list",
"--context-dir",
str(context.context_dir),
],
)
# ---------------------------------------------------------------------------
# When — remove
# ---------------------------------------------------------------------------
@@ -330,6 +360,15 @@ def step_roundtrip_import(context, name):
# ---------------------------------------------------------------------------
@then("the actor context list command should succeed")
def step_list_success(context):
assert context.result.exit_code == 0, (
f"Expected exit 0, got {context.result.exit_code}.\n"
f"stdout: {context.result.output}\n"
f"stderr: {getattr(context.result, 'stderr', '')}"
)
@then("the actor context remove command should succeed")
def step_remove_success(context):
assert context.result.exit_code == 0, (
@@ -414,6 +453,18 @@ def step_context_exists_check(context, name):
# ---------------------------------------------------------------------------
@then('the list output should contain "{text}"')
def step_list_output_contains(context, text):
output = context.result.output
assert text in output, f"Expected '{text}' in list output: {output}"
@then('the list output should not contain "{text}"')
def step_list_output_not_contains(context, text):
output = context.result.output
assert text not in output, f"Did not expect '{text}' in list output: {output}"
@then('the output should contain valid JSON with key "{key}"')
def step_output_json_key(context, key):
output = context.result.output
@@ -0,0 +1,18 @@
*** Settings ***
Documentation Verify CLI help shows optional [REGEX] positional argument for context list commands.
Resource ../../common.resource
Suite Setup Setup Test Environment
Suite Teardown Cleanup Test Environment
*** Test Cases ***
Actor Context List Help Includes Regex Placeholder
${result}= Run Process ${PYTHON} -m cleveragents actor context list --help
... timeout=120s on_timeout=kill stderr=STDOUT env:NO_COLOR=1 env:TERM=dumb
Should Be Equal As Integers ${result.rc} 0
Should Contain ${result.stdout} [REGEX]
Project Context List Help Includes Regex Placeholder
${result}= Run Process ${PYTHON} -m cleveragents context list --help
... timeout=120s on_timeout=kill stderr=STDOUT env:NO_COLOR=1 env:TERM=dumb
Should Be Equal As Integers ${result.rc} 0
Should Contain ${result.stdout} [REGEX]
+19
View File
@@ -179,6 +179,25 @@ List Context Files
[Teardown] Cleanup Test Directory
Actor Context List Supports Regex Filter
[Documentation] Verify actor context list supports optional regex filtering
[Setup] Initialize Test Project With Context
${result}= Run Process ${PYTHON} -m cleveragents actor context list test
... cwd=${TEST_DIR} timeout=120s on_timeout=kill
Should Be Equal As Integers ${result.rc} 0
Should Contain ${result.stdout} test.py
Should Not Contain ${result.stdout} utils.py
${result}= Run Process ${PYTHON} -m cleveragents actor context list nomatch
... cwd=${TEST_DIR} timeout=120s on_timeout=kill
Should Be Equal As Integers ${result.rc} 0
Should Contain ${result.stdout} No contexts found.
[Teardown] Cleanup Test Directory
Show Context Content
[Documentation] Test context show command
[Setup] Initialize Test Project With Context
@@ -11,6 +11,7 @@ from __future__ import annotations
import hashlib
import json
import re
from pathlib import Path
from typing import Annotated, Any
@@ -29,6 +30,48 @@ console = Console()
_FORMAT_HELP = "Output format: json, yaml, plain, table, rich, or color (default: rich)"
@app.command("list")
def context_list(
regex: Annotated[
str | None,
typer.Argument(
help="Optional regex filter for context names",
),
] = None,
context_dir: Annotated[
Path | None,
typer.Option(
"--context-dir",
help="Directory where contexts are stored",
resolve_path=True,
),
] = None,
) -> None:
"""List named actor contexts, optionally filtered by a regular expression."""
pattern: re.Pattern[str] | None = None
if regex is not None:
try:
pattern = re.compile(regex)
except re.error as exc:
console.print(f"[red]Error:[/red] Invalid regex pattern: {exc}")
raise typer.Abort() from exc
base = _default_context_base(context_dir)
context_names = _list_context_names(base)
if pattern is not None:
context_names = [name for name in context_names if pattern.search(name)]
if not context_names:
typer.echo("No contexts found.")
return
for name in context_names:
typer.echo(name)
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
+36 -7
View File
@@ -9,6 +9,7 @@ Deprecated alias: ``agents context <subcommand>`` (emits deprecation warning)
from __future__ import annotations
import re
from pathlib import Path
from typing import TYPE_CHECKING, Annotated, Any
@@ -396,6 +397,12 @@ def context_rm_deprecated(
@app.command("list")
def context_list(
regex: Annotated[
str | None,
typer.Argument(
help="Optional regex filter for context names",
),
] = None,
context_dir: Annotated[
Path | None,
typer.Option(
@@ -409,6 +416,14 @@ def context_list(
Or list named contexts in a directory.
"""
pattern: re.Pattern[str] | None = None
if regex is not None:
try:
pattern = re.compile(regex)
except re.error as exc:
console.print(f"[red]Error:[/red] Invalid regex pattern: {exc}")
raise typer.Abort() from exc
if context_dir is not None:
# List named contexts in the given directory
ctx_base = context_dir
@@ -417,6 +432,8 @@ def context_list(
return
context_dirs = sorted([d.name for d in ctx_base.iterdir() if d.is_dir()])
if pattern is not None:
context_dirs = [name for name in context_dirs if pattern.search(name)]
if not context_dirs:
typer.echo("No contexts found.")
@@ -451,24 +468,36 @@ def context_list(
console.print("Use 'agents actor context add <path>' to add files.")
return
# Display context files
table = Table(title=f"Context Files ({len(context_files)} total)")
table.add_column("File Path", style="cyan")
table.add_column("Type", style="green")
table.add_column("Size", style="magenta")
table.add_column("Added", style="yellow")
# Filter and normalize entries
filtered_entries: list[tuple[str, str, str, str]] = []
for file_info in context_files:
path_text, type_label, size, added_at, _ = _normalize_context_entry(
file_info
)
display_name = Path(path_text).name if path_text else ""
if pattern is not None and not pattern.search(display_name):
continue
if isinstance(size, str):
size_str = size
else:
size_str = f"{size:,} bytes" if size > 0 else "0 bytes"
filtered_entries.append((display_name, type_label, size_str, added_at))
if not filtered_entries:
console.print("[yellow]No files matched the pattern.[/yellow]")
return
# Display context files
table = Table(title=f"Context Files ({len(filtered_entries)} total)")
table.add_column("File Path", style="cyan")
table.add_column("Type", style="green")
table.add_column("Size", style="magenta")
table.add_column("Added", style="yellow")
for display_name, type_label, size_str, added_at in filtered_entries:
table.add_row(
display_name,
type_label,