feat(cli): repo indexing CLI functional #982

Merged
brent.edwards merged 4 commits from feature/m7-repo-indexing-cli into master 2026-03-20 22:28:33 +00:00
10 changed files with 906 additions and 55 deletions
+103
View File
@@ -0,0 +1,103 @@
Feature: Repository indexing CLI commands
As a developer
I want to index repository resources via the CLI
So that I can manage repository indexing through agents repo commands
Background:
Given a fresh in-memory repo indexing environment
# ---- agents repo index ----
Scenario: Index a valid repository resource
Given a registered git-checkout resource "local/test-repo" with a temp directory
When I run repo index on resource "local/test-repo"
Then the repo command should succeed
And the repo output should contain "complete"
And the repo output should contain "local/test-repo"
Scenario: Index with --full forces full re-index
Given a registered git-checkout resource "local/full-repo" with a temp directory
When I run repo full-index on resource "local/full-repo"
Then the repo command should succeed
And the repo output should contain "Full index complete"
Scenario: Incremental re-index after initial index
Given a registered git-checkout resource "local/incr-repo" with a temp directory
And I have already indexed "local/incr-repo"
When I run repo index on resource "local/incr-repo"
Then the repo command should succeed
And the repo output should contain "Incremental refresh complete"
Scenario: Index with JSON output format
Given a registered git-checkout resource "local/json-repo" with a temp directory
When I run repo index-json on resource "local/json-repo"
Then the repo command should succeed
And the repo output should be valid JSON
And the repo JSON output should contain key "status"
And the repo JSON output should contain key "file_count"
And the repo JSON output should contain key "primary_language"
Scenario: Index with non-existent resource shows error
When I run repo index on resource "local/nonexistent-resource"
Then the repo command should fail
And the repo output should contain "Resource not found"
# ---- agents repo status ----
Scenario: Status for an indexed resource
Given a registered git-checkout resource "local/status-repo" with a temp directory
And I have already indexed "local/status-repo"
When I run repo status on resource "local/status-repo"
Then the repo command should succeed
And the repo output should contain "Status"
And the repo output should contain "Files"
And the repo output should contain "Tokens"
Scenario: Status with no index shows appropriate message
Given a registered git-checkout resource "local/noindex-repo" with a temp directory
When I run repo status on resource "local/noindex-repo"
Then the repo command should succeed
And the repo output should contain "No index found"
Scenario: Status with JSON output format
Given a registered git-checkout resource "local/json-status-repo" with a temp directory
And I have already indexed "local/json-status-repo"
When I run repo status-json on resource "local/json-status-repo"
Then the repo command should succeed
And the repo output should be valid JSON
And the repo JSON output should contain key "status"
And the repo JSON output should contain key "file_count"
And the repo JSON output should contain key "token_estimate"
Scenario: Status for non-existent resource shows error
When I run repo status on resource "local/missing-resource"
Then the repo command should fail
And the repo output should contain "Resource not found"
Scenario: Status with no index in JSON format
Given a registered git-checkout resource "local/noindex-json" with a temp directory
When I run repo status-json on resource "local/noindex-json"
Then the repo command should succeed
And the repo output should be valid JSON
And the repo JSON output should contain key "status"
And the repo JSON output field "status" should equal "not_indexed"
# ---- edge cases for coverage ----
Scenario: Index resource with no filesystem path shows error
Given a registered resource "local/no-path" with no location
When I run repo index on resource "local/no-path"
Then the repo command should fail
And the repo output should contain "no filesystem path"
Scenario: Index resource with path only in properties
Given a registered resource "local/prop-path" with path in properties only
When I run repo index on resource "local/prop-path"
Then the repo command should succeed
And the repo output should contain "complete"
Scenario: Index with missing root path shows error
Given a registered resource "local/bad-path" with nonexistent path
When I run repo index on resource "local/bad-path"
Then the repo command should fail
And the repo output should contain "Error"
+273
View File
@@ -0,0 +1,273 @@
"""Step definitions for Repository Indexing CLI feature tests."""
from __future__ import annotations
import contextlib
import json
import tempfile
from io import StringIO
from pathlib import Path
from typing import Any
from unittest.mock import patch
from behave import given, then, when # type: ignore[import-untyped]
from behave.runner import Context # type: ignore[import-untyped]
from rich.console import Console
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from cleveragents.application.services.repo_indexing_service import (
RepoIndexingService,
)
from cleveragents.application.services.resource_registry_service import (
ResourceRegistryService,
)
from cleveragents.infrastructure.database.models import Base
def _setup_services(
context: Context,
) -> tuple[ResourceRegistryService, RepoIndexingService]:
"""Return the cached services created by the Background step."""
return context._repo_cli_resource_svc, context._repo_cli_indexing_svc
def _capture_repo_command(
context: Context,
func: Any,
*args: Any,
**kwargs: Any,
) -> None:
"""Run a CLI function capturing output and success status.
Captures both Rich console output (via *buf*) **and** direct
``sys.stdout`` writes (used by ``format_output`` for JSON/YAML
to avoid Rich line-wrapping).
"""
buf = StringIO()
stdout_buf = StringIO()
console = Console(
file=buf, width=200, no_color=True, highlight=False, force_terminal=False
)
import cleveragents.cli.commands.repo as repo_mod
orig_console = repo_mod.console
repo_mod.console = console
failed = False
try:
with contextlib.redirect_stdout(stdout_buf):
func(*args, **kwargs)
except SystemExit:
failed = True
except Exception:
failed = True
finally:
repo_mod.console = orig_console
# Combine Rich console output with any direct stdout writes
context.repo_cli_output = buf.getvalue() + stdout_buf.getvalue()
context.repo_cli_failed = failed
def _run_with_mock(
context: Context,
func: Any,
*args: Any,
**kwargs: Any,
) -> None:
"""Run a CLI function with mocked container providing test services."""
resource_svc, indexing_svc = _setup_services(context)
with patch("cleveragents.cli.commands.repo.get_container") as mock_container:
container = mock_container.return_value
container.resource_registry_service.return_value = resource_svc
container.repo_indexing_service.return_value = indexing_svc
_capture_repo_command(context, func, *args, **kwargs)
@given("a fresh in-memory repo indexing environment")
def step_fresh_repo_env(context: Context) -> None:
"""Set up fresh in-memory services for repo indexing tests."""
# Create fresh in-memory database for each scenario. We cannot
# ``delattr`` on behave Context (it only works at the current level),
# so we unconditionally set new services each time.
engine = create_engine("sqlite:///:memory:", echo=False)
Base.metadata.create_all(engine)
factory = sessionmaker(bind=engine, expire_on_commit=False)
context._repo_cli_engine = engine
context._repo_cli_factory = factory
context._repo_cli_resource_svc = ResourceRegistryService(session_factory=factory)
context._repo_cli_resource_svc.bootstrap_builtin_types()
context._repo_cli_indexing_svc = RepoIndexingService(session_factory=factory)
context.repo_cli_output = ""
context.repo_cli_failed = False
context._repo_cli_temp_dirs = {} # dict[str, str]
@given('a registered git-checkout resource "{name}" with a temp directory')
def step_register_resource_with_tempdir(context: Context, name: str) -> None:
"""Register a git-checkout resource backed by a real temporary directory."""
resource_svc, _indexing_svc = _setup_services(context)
tmp_dir = tempfile.mkdtemp()
context._repo_cli_temp_dirs[name] = tmp_dir
sample = Path(tmp_dir) / "README.md"
sample.write_text("# Test Repository\n\nSample content for indexing.\n")
resource_svc.register_resource(
type_name="git-checkout",
name=name,
location=tmp_dir,
properties={"path": tmp_dir},
)
@given('I have already indexed "{name}"')
def step_already_indexed(context: Context, name: str) -> None:
"""Perform an initial index on the resource."""
resource_svc, indexing_svc = _setup_services(context)
resource = resource_svc.show_resource(name)
root_path = resource.location or ""
if not root_path and resource.properties:
path_val = resource.properties.get("path")
root_path = str(path_val) if path_val is not None else ""
indexing_svc.index_resource(resource.resource_id, root_path)
@when('I run repo index on resource "{name}"')
def step_run_repo_index(context: Context, name: str) -> None:
"""Run the repo index command (incremental)."""
from cleveragents.cli.commands.repo import index_command
_run_with_mock(context, index_command, name, full=False, fmt="text")
@when('I run repo full-index on resource "{name}"')
def step_run_repo_full_index(context: Context, name: str) -> None:
"""Run the repo index command with --full flag."""
from cleveragents.cli.commands.repo import index_command
_run_with_mock(context, index_command, name, full=True, fmt="text")
@when('I run repo index-json on resource "{name}"')
def step_run_repo_index_json(context: Context, name: str) -> None:
"""Run the repo index command with JSON format."""
from cleveragents.cli.commands.repo import index_command
_run_with_mock(context, index_command, name, full=False, fmt="json")
@when('I run repo status on resource "{name}"')
def step_run_repo_status(context: Context, name: str) -> None:
"""Run the repo status command."""
from cleveragents.cli.commands.repo import status_command
_run_with_mock(context, status_command, name, fmt="text")
@when('I run repo status-json on resource "{name}"')
def step_run_repo_status_json(context: Context, name: str) -> None:
"""Run the repo status command with JSON format."""
from cleveragents.cli.commands.repo import status_command
_run_with_mock(context, status_command, name, fmt="json")
@then("the repo command should succeed")
def step_repo_should_succeed(context: Context) -> None:
"""Assert the repo command did not fail."""
assert not context.repo_cli_failed, (
f"Expected success but command failed. Output:\n{context.repo_cli_output}"
)
@then("the repo command should fail")
def step_repo_should_fail(context: Context) -> None:
"""Assert the repo command failed."""
assert context.repo_cli_failed, (
f"Expected failure but command succeeded. Output:\n{context.repo_cli_output}"
)
@then('the repo output should contain "{text}"')
def step_repo_output_contains(context: Context, text: str) -> None:
"""Assert the repo output contains the given text."""
assert text in context.repo_cli_output, (
f"Expected output to contain '{text}'. Got:\n{context.repo_cli_output}"
)
@then("the repo output should be valid JSON")
def step_repo_output_valid_json(context: Context) -> None:
"""Assert the repo output is valid JSON."""
output = context.repo_cli_output.strip()
try:
context.repo_cli_json = json.loads(output)
except json.JSONDecodeError as exc:
raise AssertionError(
f"Output is not valid JSON: {exc}\nOutput:\n{output}"
) from exc
@then('the repo JSON output should contain key "{key}"')
def step_repo_json_has_key(context: Context, key: str) -> None:
"""Assert the parsed JSON output contains a key."""
assert hasattr(context, "repo_cli_json"), "JSON not parsed; call 'valid JSON' first"
assert key in context.repo_cli_json, (
f"Key '{key}' not found in JSON: {context.repo_cli_json}"
)
@then('the repo JSON output field "{key}" should equal "{value}"')
def step_repo_json_field_equals(context: Context, key: str, value: str) -> None:
"""Assert a JSON field equals the expected value."""
assert hasattr(context, "repo_cli_json"), "JSON not parsed; call 'valid JSON' first"
actual = str(context.repo_cli_json.get(key, ""))
assert actual == value, f"Expected {key}='{value}', got '{actual}'"
@given('a registered resource "{name}" with no location')
def step_register_resource_no_location(context: Context, name: str) -> None:
"""Register a resource with no location or path property."""
resource_svc, _indexing_svc = _setup_services(context)
resource_svc.register_resource(
type_name="git-checkout",
name=name,
location=None,
properties={},
)
@given('a registered resource "{name}" with path in properties only')
def step_register_resource_properties_path(context: Context, name: str) -> None:
"""Register a resource with path only in properties (no location)."""
resource_svc, _indexing_svc = _setup_services(context)
tmp_dir = tempfile.mkdtemp()
context._repo_cli_temp_dirs[name] = tmp_dir
sample = Path(tmp_dir) / "README.md"
sample.write_text("# Properties path test\n")
resource_svc.register_resource(
type_name="git-checkout",
name=name,
location=None,
properties={"path": tmp_dir},
)
@given('a registered resource "{name}" with nonexistent path')
def step_register_resource_bad_path(context: Context, name: str) -> None:
"""Register a resource pointing to a nonexistent directory."""
resource_svc, _indexing_svc = _setup_services(context)
resource_svc.register_resource(
type_name="git-checkout",
name=name,
location="/tmp/nonexistent-path-for-indexing-test-12345",
properties={"path": "/tmp/nonexistent-path-for-indexing-test-12345"},
)
+11 -11
View File
@@ -16,20 +16,20 @@ Run CLI With Clean Home
[Documentation] Run a CLI command with a temp HOME to avoid stale config
[Arguments] @{cmd}
${tmpdir}= Evaluate __import__('tempfile').mkdtemp(prefix='cli_core_')
${result}= Run Process @{cmd} timeout=120s on_timeout=kill env:HOME=${tmpdir}
${result}= Run Process @{cmd} timeout=60s env:HOME=${tmpdir}
RETURN ${result}
*** Test Cases ***
Version Command Default Rich Format
[Documentation] Version command with default (rich) format shows version string
${result}= Run Process ${PYTHON} -m cleveragents version timeout=120s on_timeout=kill
${result}= Run Process ${PYTHON} -m cleveragents version timeout=60s
Should Be Equal As Integers ${result.rc} 0
Should Contain ${result.stdout} 1.0.0
Should Contain ${result.stdout} CleverAgents
Version Command JSON Format
[Documentation] Version command with --format json returns valid JSON
${result}= Run Process ${PYTHON} -m cleveragents version --format json timeout=120s on_timeout=kill
${result}= Run Process ${PYTHON} -m cleveragents version --format json timeout=60s
Should Be Equal As Integers ${result.rc} 0
Should Contain ${result.stdout} "version": "1.0.0"
Should Contain ${result.stdout} "schema": "v3"
@@ -41,21 +41,21 @@ Version Command JSON Format
Version Command Plain Format
[Documentation] Version command with --format plain returns key-value pairs
${result}= Run Process ${PYTHON} -m cleveragents version --format plain timeout=120s on_timeout=kill
${result}= Run Process ${PYTHON} -m cleveragents version --format plain timeout=60s
Should Be Equal As Integers ${result.rc} 0
Should Contain ${result.stdout} version: 1.0.0
Should Contain ${result.stdout} schema: v3
Version Command YAML Format
[Documentation] Version command with --format yaml returns YAML
${result}= Run Process ${PYTHON} -m cleveragents version --format yaml timeout=120s on_timeout=kill
${result}= Run Process ${PYTHON} -m cleveragents version --format yaml timeout=60s
Should Be Equal As Integers ${result.rc} 0
Should Contain ${result.stdout} version: 1.0.0
Should Contain ${result.stdout} schema: v3
Info Command Default Rich Format
[Documentation] Info command with default (rich) format shows environment details
${result}= Run Process ${PYTHON} -m cleveragents info timeout=120s on_timeout=kill
${result}= Run Process ${PYTHON} -m cleveragents info timeout=60s
Should Be Equal As Integers ${result.rc} 0
Should Contain ${result.stdout} Environment
Should Contain ${result.stdout} Runtime
@@ -78,14 +78,14 @@ Info Command Plain Format
Diagnostics Command Default Rich Format
[Documentation] Diagnostics command with default (rich) format runs checks
${result}= Run Process ${PYTHON} -m cleveragents diagnostics timeout=120s on_timeout=kill
${result}= Run Process ${PYTHON} -m cleveragents diagnostics timeout=60s
Should Be Equal As Integers ${result.rc} 0
Should Contain ${result.stdout} Checks
Should Contain ${result.stdout} Summary
Diagnostics Command JSON Format
[Documentation] Diagnostics command with --format json returns structured data
${result}= Run Process ${PYTHON} -m cleveragents diagnostics --format json timeout=120s on_timeout=kill
${result}= Run Process ${PYTHON} -m cleveragents diagnostics --format json timeout=60s
Should Be Equal As Integers ${result.rc} 0
Should Contain ${result.stdout} "checks"
Should Contain ${result.stdout} "summary"
@@ -96,14 +96,14 @@ Diagnostics Command JSON Format
Diagnostics Command Plain Format
[Documentation] Diagnostics command with --format plain returns key-value pairs
${result}= Run Process ${PYTHON} -m cleveragents diagnostics --format plain timeout=120s on_timeout=kill
${result}= Run Process ${PYTHON} -m cleveragents diagnostics --format plain timeout=60s
Should Be Equal As Integers ${result.rc} 0
Should Contain ${result.stdout} checks:
Should Contain ${result.stdout} summary:
Diagnostics Command Check Flag Returns Valid Exit Code
[Documentation] Diagnostics --check exits 0 when no errors are found
${result}= Run Process ${PYTHON} -m cleveragents diagnostics --check --format json timeout=120s on_timeout=kill
${result}= Run Process ${PYTHON} -m cleveragents diagnostics --check --format json timeout=60s
Should Be Equal As Integers ${result.rc} 0 Diagnostics --check failed with rc=${result.rc}: ${result.stdout}
Should Contain ${result.stdout} "checks"
Should Contain ${result.stdout} "has_errors"
@@ -111,7 +111,7 @@ Diagnostics Command Check Flag Returns Valid Exit Code
Diagnostics Command Performance
[Documentation] Diagnostics command completes within acceptable time
${start}= Get Time epoch
${result}= Run Process ${PYTHON} -m cleveragents diagnostics --format json timeout=120s on_timeout=kill
${result}= Run Process ${PYTHON} -m cleveragents diagnostics --format json timeout=60s
${end}= Get Time epoch
${duration}= Evaluate ${end} - ${start}
Should Be True ${duration} < 30 Diagnostics took too long: ${duration}s
+38 -38
View File
@@ -17,7 +17,7 @@ ${PROJECT_NAME} test-project
*** Test Cases ***
Test CLI Help Shows All Commands
[Documentation] Verify that CLI help displays all expected command groups
${result} = Run Process ${PYTHON} -m cleveragents --help timeout=120s on_timeout=kill
${result} = Run Process ${PYTHON} -m cleveragents --help timeout=120s
Should Be Equal As Numbers ${result.rc} 0
Should Contain ${result.stdout} AI-powered development assistant
Should Contain ${result.stdout} project
@@ -32,7 +32,7 @@ Test Project Initialization
[Documentation] Test initializing a new CleverAgents project
Create Directory ${TEST_DIR}/project1
${result} = Run Process ${PYTHON} -m cleveragents init ${PROJECT_NAME}
... cwd=${TEST_DIR}/project1 timeout=120s on_timeout=kill
... cwd=${TEST_DIR}/project1 timeout=120s
Should Be Equal As Numbers ${result.rc} 0
Should Contain ${result.stdout} Project '${PROJECT_NAME}'
Should Contain ${result.stdout} initialized
@@ -46,10 +46,10 @@ Test Project Initialization
Test Project Cannot Initialize Twice
[Documentation] Verify project cannot be initialized twice without force
Create Directory ${TEST_DIR}/project2
${first_init}= Run Process ${PYTHON} -m cleveragents init first-project cwd=${TEST_DIR}/project2 timeout=120s on_timeout=kill
${first_init}= Run Process ${PYTHON} -m cleveragents init first-project cwd=${TEST_DIR}/project2 timeout=120s
Should Be Equal As Numbers ${first_init.rc} 0
${result} = Run Process ${PYTHON} -m cleveragents init second-project
... cwd=${TEST_DIR}/project2 timeout=120s on_timeout=kill
... cwd=${TEST_DIR}/project2 timeout=120s
Should Not Be Equal As Numbers ${result.rc} 0
Should Contain ${result.stderr} Project already initialized
Should Contain ${result.stderr} --force
@@ -57,21 +57,21 @@ Test Project Cannot Initialize Twice
Test Force Reinitialize Project
[Documentation] Test force reinitialization of a project
Create Directory ${TEST_DIR}/project3
${first_init}= Run Process ${PYTHON} -m cleveragents init first-project cwd=${TEST_DIR}/project3 timeout=120s on_timeout=kill
${first_init}= Run Process ${PYTHON} -m cleveragents init first-project cwd=${TEST_DIR}/project3 timeout=120s
Should Be Equal As Numbers ${first_init.rc} 0
${result} = Run Process ${PYTHON} -m cleveragents init second-project --force
... cwd=${TEST_DIR}/project3 timeout=120s on_timeout=kill
... cwd=${TEST_DIR}/project3 timeout=120s
Should Be Equal As Numbers ${result.rc} 0
Should Contain ${result.stdout} Project 'second-project' initialized successfully
Test Context Add Files
[Documentation] Test adding files to context
Create Directory ${TEST_DIR}/project4
${init_result}= Run Process ${PYTHON} -m cleveragents init ${PROJECT_NAME} cwd=${TEST_DIR}/project4 timeout=120s on_timeout=kill
${init_result}= Run Process ${PYTHON} -m cleveragents init ${PROJECT_NAME} cwd=${TEST_DIR}/project4 timeout=120s
Should Be Equal As Numbers ${init_result.rc} 0
Create File ${TEST_DIR}/project4/test.py print('hello world')
${result} = Run Process ${PYTHON} -m cleveragents context add test.py
... cwd=${TEST_DIR}/project4 timeout=120s on_timeout=kill
... cwd=${TEST_DIR}/project4 timeout=120s
Should Be Equal As Numbers ${result.rc} 0
Should Contain ${result.stdout} Added 1 file(s) to context
@@ -80,16 +80,16 @@ Test Context List Files
${unique_dir} = Set Variable ${TEST_DIR}/project5_${TEST NAME.replace(' ', '_')}
Create Directory ${unique_dir}
${init_result} = Run Process ${PYTHON} -m cleveragents init ${PROJECT_NAME}
... cwd=${unique_dir} timeout=120s on_timeout=kill
... cwd=${unique_dir} timeout=120s
Should Be Equal As Numbers ${init_result.rc} 0
Create File ${unique_dir}/file1.py # File 1
Create File ${unique_dir}/file2.py # File 2
${add_result} = Run Process ${PYTHON} -m cleveragents context add file1.py file2.py
... cwd=${unique_dir} timeout=120s on_timeout=kill
... cwd=${unique_dir} timeout=120s
Should Be Equal As Numbers ${add_result.rc} 0
Directory Should Exist ${unique_dir}
${result} = Run Process ${PYTHON} -m cleveragents context list
... cwd=${unique_dir} timeout=120s on_timeout=kill
... cwd=${unique_dir} timeout=120s
Should Be Equal As Numbers ${result.rc} 0
Should Contain ${result.stdout} file1.py
Should Contain ${result.stdout} file2.py
@@ -97,23 +97,23 @@ Test Context List Files
Test Context Clear
[Documentation] Test clearing context files
Create Directory ${TEST_DIR}/project6
${init_result}= Run Process ${PYTHON} -m cleveragents init ${PROJECT_NAME} cwd=${TEST_DIR}/project6 timeout=120s on_timeout=kill
${init_result}= Run Process ${PYTHON} -m cleveragents init ${PROJECT_NAME} cwd=${TEST_DIR}/project6 timeout=120s
Should Be Equal As Numbers ${init_result.rc} 0
Create File ${TEST_DIR}/project6/test.py # Test file
${add_result}= Run Process ${PYTHON} -m cleveragents context add test.py cwd=${TEST_DIR}/project6 timeout=120s on_timeout=kill
${add_result}= Run Process ${PYTHON} -m cleveragents context add test.py cwd=${TEST_DIR}/project6 timeout=120s
Should Be Equal As Numbers ${add_result.rc} 0
${result} = Run Process ${PYTHON} -m cleveragents context clear --yes
... cwd=${TEST_DIR}/project6 timeout=120s on_timeout=kill
... cwd=${TEST_DIR}/project6 timeout=120s
Should Be Equal As Numbers ${result.rc} 0
Should Contain ${result.stdout} Cleared all files from context
Test Plan Creation With Tell
[Documentation] Test creating a plan using tell command
Create Directory ${TEST_DIR}/project7
${init_result}= Run Process ${PYTHON} -m cleveragents init ${PROJECT_NAME} cwd=${TEST_DIR}/project7 timeout=120s on_timeout=kill
${init_result}= Run Process ${PYTHON} -m cleveragents init ${PROJECT_NAME} cwd=${TEST_DIR}/project7 timeout=120s
Should Be Equal As Numbers ${init_result.rc} 0
${result} = Run Process ${PYTHON} -m cleveragents tell Add error handling to main function
... cwd=${TEST_DIR}/project7 timeout=120s on_timeout=kill
... cwd=${TEST_DIR}/project7 timeout=120s
Should Be Equal As Numbers ${result.rc} 0
Should Contain ${result.stdout} Plan created
Should Contain ${result.stdout} error handling
@@ -121,12 +121,12 @@ Test Plan Creation With Tell
Test Plan Build
[Documentation] Test building a plan
Create Directory ${TEST_DIR}/project8
${init_result}= Run Process ${PYTHON} -m cleveragents init ${PROJECT_NAME} cwd=${TEST_DIR}/project8 timeout=120s on_timeout=kill
${init_result}= Run Process ${PYTHON} -m cleveragents init ${PROJECT_NAME} cwd=${TEST_DIR}/project8 timeout=120s
Should Be Equal As Numbers ${init_result.rc} 0
${tell_result}= Run Process ${PYTHON} -m cleveragents tell Create example code cwd=${TEST_DIR}/project8 timeout=120s on_timeout=kill
${tell_result}= Run Process ${PYTHON} -m cleveragents tell Create example code cwd=${TEST_DIR}/project8 timeout=120s
Should Be Equal As Numbers ${tell_result.rc} 0
${result} = Run Process ${PYTHON} -m cleveragents build cwd=${TEST_DIR}/project8
... env:CLEVERAGENTS_TESTING_USE_MOCK_AI=true timeout=120s on_timeout=kill
... env:CLEVERAGENTS_TESTING_USE_MOCK_AI=true timeout=120s
Should Be Equal As Numbers ${result.rc} 0
Should Contain ${result.stdout} Plan built successfully
Should Contain ${result.stdout} Generated
@@ -135,14 +135,14 @@ Test Plan Build
Test Plan Apply
[Documentation] Test applying plan changes
Create Directory ${TEST_DIR}/project9
${init_result}= Run Process ${PYTHON} -m cleveragents init ${PROJECT_NAME} cwd=${TEST_DIR}/project9 timeout=120s on_timeout=kill
${init_result}= Run Process ${PYTHON} -m cleveragents init ${PROJECT_NAME} cwd=${TEST_DIR}/project9 timeout=120s
Should Be Equal As Numbers ${init_result.rc} 0
${tell_result}= Run Process ${PYTHON} -m cleveragents tell Create example file cwd=${TEST_DIR}/project9 timeout=120s on_timeout=kill
${tell_result}= Run Process ${PYTHON} -m cleveragents tell Create example file cwd=${TEST_DIR}/project9 timeout=120s
Should Be Equal As Numbers ${tell_result.rc} 0
${build_result}= Run Process ${PYTHON} -m cleveragents build cwd=${TEST_DIR}/project9
... env:CLEVERAGENTS_TESTING_USE_MOCK_AI=true timeout=120s on_timeout=kill
... env:CLEVERAGENTS_TESTING_USE_MOCK_AI=true timeout=120s
Should Be Equal As Numbers ${build_result.rc} 0
${result} = Run Process ${PYTHON} -m cleveragents apply --yes cwd=${TEST_DIR}/project9 timeout=120s on_timeout=kill
${result} = Run Process ${PYTHON} -m cleveragents apply --yes cwd=${TEST_DIR}/project9 timeout=120s
Should Be Equal As Numbers ${result.rc} 0
Should Contain ${result.stdout} Successfully applied
File Should Exist ${TEST_DIR}/project9/example.py
@@ -150,25 +150,25 @@ Test Plan Apply
Test Plan List
[Documentation] Test listing plans
Create Directory ${TEST_DIR}/project10
${init_result}= Run Process ${PYTHON} -m cleveragents init ${PROJECT_NAME} cwd=${TEST_DIR}/project10 timeout=120s on_timeout=kill
${init_result}= Run Process ${PYTHON} -m cleveragents init ${PROJECT_NAME} cwd=${TEST_DIR}/project10 timeout=120s
Should Be Equal As Numbers ${init_result.rc} 0
${tell_result}= Run Process ${PYTHON} -m cleveragents tell First plan cwd=${TEST_DIR}/project10 timeout=120s on_timeout=kill
${tell_result}= Run Process ${PYTHON} -m cleveragents tell First plan cwd=${TEST_DIR}/project10 timeout=120s
Should Be Equal As Numbers ${tell_result.rc} 0
${plan_result}= Run Process ${PYTHON} -m cleveragents plan new second-plan cwd=${TEST_DIR}/project10 timeout=120s on_timeout=kill
${plan_result}= Run Process ${PYTHON} -m cleveragents plan new second-plan cwd=${TEST_DIR}/project10 timeout=120s
Should Be Equal As Numbers ${plan_result.rc} 0
${result} = Run Process ${PYTHON} -m cleveragents plan list cwd=${TEST_DIR}/project10 timeout=120s on_timeout=kill
${result} = Run Process ${PYTHON} -m cleveragents plan list cwd=${TEST_DIR}/project10 timeout=120s
Should Be Equal As Numbers ${result.rc} 0
Should Contain ${result.stdout} Plans (3 total)
Test Shortcut Commands Work
[Documentation] Test that shortcut commands work properly
Create Directory ${TEST_DIR}/project11
${init_result}= Run Process ${PYTHON} -m cleveragents init ${PROJECT_NAME} cwd=${TEST_DIR}/project11 timeout=120s on_timeout=kill
${init_result}= Run Process ${PYTHON} -m cleveragents init ${PROJECT_NAME} cwd=${TEST_DIR}/project11 timeout=120s
Should Be Equal As Numbers ${init_result.rc} 0
Create File ${TEST_DIR}/project11/test.txt Test content
# Test context-load shortcut
${result} = Run Process ${PYTHON} -m cleveragents context-load test.txt
... cwd=${TEST_DIR}/project11 timeout=120s on_timeout=kill
... cwd=${TEST_DIR}/project11 timeout=120s
Should Be Equal As Numbers ${result.rc} 0
Should Contain ${result.stdout} Added 1 file(s) to context
@@ -177,30 +177,30 @@ Test End To End Workflow
Create Directory ${TEST_DIR}/project12
# Initialize project
${init} = Run Process ${PYTHON} -m cleveragents init workflow-project
... cwd=${TEST_DIR}/project12 timeout=120s on_timeout=kill
... cwd=${TEST_DIR}/project12 timeout=120s
Should Be Equal As Numbers ${init.rc} 0
# Add context
Create File ${TEST_DIR}/project12/input.py def main(): pass
${add} = Run Process ${PYTHON} -m cleveragents context add input.py
... cwd=${TEST_DIR}/project12 timeout=120s on_timeout=kill
... cwd=${TEST_DIR}/project12 timeout=120s
Should Be Equal As Numbers ${add.rc} 0
# Create plan
${tell} = Run Process ${PYTHON} -m cleveragents tell Add logging to main function
... cwd=${TEST_DIR}/project12 timeout=120s on_timeout=kill
... cwd=${TEST_DIR}/project12 timeout=120s
Should Be Equal As Numbers ${tell.rc} 0
# Build plan
${build} = Run Process ${PYTHON} -m cleveragents build cwd=${TEST_DIR}/project12
... env:CLEVERAGENTS_TESTING_USE_MOCK_AI=true timeout=120s on_timeout=kill
... env:CLEVERAGENTS_TESTING_USE_MOCK_AI=true timeout=120s
Should Be Equal As Numbers ${build.rc} 0
# Apply changes
${apply} = Run Process ${PYTHON} -m cleveragents apply --yes cwd=${TEST_DIR}/project12 timeout=120s on_timeout=kill
${apply} = Run Process ${PYTHON} -m cleveragents apply --yes cwd=${TEST_DIR}/project12 timeout=120s
Should Be Equal As Numbers ${apply.rc} 0
# Verify file created
File Should Exist ${TEST_DIR}/project12/example.py
Test Command Error Handling
[Documentation] Test error handling for invalid commands
${result} = Run Process ${PYTHON} -m cleveragents invalid-command timeout=120s on_timeout=kill
${result} = Run Process ${PYTHON} -m cleveragents invalid-command timeout=120s
Should Not Be Equal As Numbers ${result.rc} 0
Should Contain ${result.stderr} Invalid command
@@ -208,17 +208,17 @@ Test Project Status Without Project
[Documentation] Test project status command without initialized project
Create Directory ${TEST_DIR}/no_project
${result} = Run Process ${PYTHON} -m cleveragents project status
... cwd=${TEST_DIR}/no_project timeout=120s on_timeout=kill
... cwd=${TEST_DIR}/no_project timeout=120s
Should Not Be Equal As Numbers ${result.rc} 0
Should Contain ${result.stderr} No project found
Test Project Status With Project
[Documentation] Test project status command with initialized project
Create Directory ${TEST_DIR}/with_project
${init_result}= Run Process ${PYTHON} -m cleveragents init status-test cwd=${TEST_DIR}/with_project timeout=120s on_timeout=kill
${init_result}= Run Process ${PYTHON} -m cleveragents init status-test cwd=${TEST_DIR}/with_project timeout=120s
Should Be Equal As Numbers ${init_result.rc} 0
${result} = Run Process ${PYTHON} -m cleveragents project status
... cwd=${TEST_DIR}/with_project timeout=120s on_timeout=kill
... cwd=${TEST_DIR}/with_project timeout=120s
Should Be Equal As Numbers ${result.rc} 0
Should Contain ${result.stdout} Project: status-test
Should Contain ${result.stdout} Plans: 1
+1 -1
View File
@@ -759,7 +759,7 @@ Run Python Script
# Create File writes to it (avoids leaking an open descriptor).
${temp_file}= Evaluate (lambda t: (__import__('os').close(t[0]), t[1])[-1])(__import__('tempfile').mkstemp(suffix='.py', dir='/tmp'))
Create File ${temp_file} ${full_code}
${result}= Run Process ${PYTHON} ${temp_file} timeout=120s on_timeout=kill stderr=STDOUT env:PYTHONWARNINGS=ignore env:PYTHONDONTWRITEBYTECODE=1
${result}= Run Process ${PYTHON} ${temp_file} timeout=60s stderr=STDOUT env:PYTHONWARNINGS=ignore env:PYTHONDONTWRITEBYTECODE=1
Remove File ${temp_file}
# Check if process failed and log stderr if present
IF ${result.rc} != 0
+184
View File
@@ -0,0 +1,184 @@
"""Robot Framework helper for repo indexing CLI integration tests.
Provides end-to-end test scenarios that exercise the RepoIndexingService
and CLI wiring together, using an in-memory SQLite database.
"""
from __future__ import annotations
import tempfile
from pathlib import Path
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from cleveragents.application.services.repo_indexing_service import (
RepoIndexingService,
)
from cleveragents.application.services.resource_registry_service import (
ResourceRegistryService,
)
from cleveragents.infrastructure.database.models import Base
def _make_services() -> tuple[ResourceRegistryService, RepoIndexingService]:
"""Create fresh services backed by an in-memory database."""
engine = create_engine("sqlite:///:memory:", echo=False)
Base.metadata.create_all(engine)
factory = sessionmaker(bind=engine, expire_on_commit=False)
resource_svc = ResourceRegistryService(session_factory=factory)
resource_svc.bootstrap_builtin_types()
indexing_svc = RepoIndexingService(session_factory=factory)
return resource_svc, indexing_svc
def test_e2e_add_index_status() -> str:
"""End-to-end: add resource -> index -> check status."""
resource_svc, indexing_svc = _make_services()
# Create a temp directory with sample files
tmp_dir = tempfile.mkdtemp()
readme = Path(tmp_dir) / "README.md"
readme.write_text("# Test\nSample content.\n")
main_py = Path(tmp_dir) / "main.py"
main_py.write_text("print('hello')\n")
# Register resource
resource = resource_svc.register_resource(
type_name="git-checkout",
name="local/e2e-repo",
location=tmp_dir,
properties={"path": tmp_dir},
)
# Index
repo_index = indexing_svc.index_resource(resource.resource_id, tmp_dir)
assert repo_index.metadata.status.value == "ready"
assert repo_index.metadata.file_count >= 2
# Check status
status = indexing_svc.get_index_status(resource.resource_id)
assert status is not None
assert status.status.value == "ready"
assert status.file_count == repo_index.metadata.file_count
return "E2E add-index-status PASSED"
def test_incremental_reindex_after_change() -> str:
"""Incremental re-index after file change."""
resource_svc, indexing_svc = _make_services()
tmp_dir = tempfile.mkdtemp()
readme = Path(tmp_dir) / "README.md"
readme.write_text("# Original\n")
resource = resource_svc.register_resource(
type_name="git-checkout",
name="local/incr-repo",
location=tmp_dir,
properties={"path": tmp_dir},
)
# Initial index
idx1 = indexing_svc.index_resource(resource.resource_id, tmp_dir)
count1 = idx1.metadata.file_count
# Add a new file
new_file = Path(tmp_dir) / "app.py"
new_file.write_text("x = 1\n")
# Incremental refresh
idx2 = indexing_svc.refresh_index(resource.resource_id, tmp_dir)
assert idx2.metadata.file_count == count1 + 1
assert idx2.metadata.status.value == "ready"
return "Incremental reindex PASSED"
def test_full_reindex() -> str:
"""Full re-index replaces the entire index."""
resource_svc, indexing_svc = _make_services()
tmp_dir = tempfile.mkdtemp()
readme = Path(tmp_dir) / "README.md"
readme.write_text("# Test\n")
resource = resource_svc.register_resource(
type_name="git-checkout",
name="local/full-repo",
location=tmp_dir,
properties={"path": tmp_dir},
)
# Initial index
idx1 = indexing_svc.index_resource(resource.resource_id, tmp_dir)
# Full re-index
idx2 = indexing_svc.index_resource(resource.resource_id, tmp_dir)
assert idx2.metadata.status.value == "ready"
assert idx2.metadata.file_count == idx1.metadata.file_count
return "Full reindex PASSED"
def test_status_no_index() -> str:
"""Status returns None when no index exists."""
resource_svc, indexing_svc = _make_services()
tmp_dir = tempfile.mkdtemp()
resource = resource_svc.register_resource(
type_name="git-checkout",
name="local/no-index-repo",
location=tmp_dir,
properties={"path": tmp_dir},
)
status = indexing_svc.get_index_status(resource.resource_id)
assert status is None
return "Status no-index PASSED"
def test_remove_index() -> str:
"""Remove index cleans up all data."""
resource_svc, indexing_svc = _make_services()
tmp_dir = tempfile.mkdtemp()
readme = Path(tmp_dir) / "README.md"
readme.write_text("# Test\n")
resource = resource_svc.register_resource(
type_name="git-checkout",
name="local/remove-repo",
location=tmp_dir,
properties={"path": tmp_dir},
)
indexing_svc.index_resource(resource.resource_id, tmp_dir)
assert indexing_svc.get_index_status(resource.resource_id) is not None
removed = indexing_svc.remove_index(resource.resource_id)
assert removed is True
assert indexing_svc.get_index_status(resource.resource_id) is None
return "Remove index PASSED"
if __name__ == "__main__":
import sys
_dispatch = {
"e2e": test_e2e_add_index_status,
"incremental": test_incremental_reindex_after_change,
"full": test_full_reindex,
"no-index": test_status_no_index,
"remove": test_remove_index,
}
if len(sys.argv) < 2 or sys.argv[1] not in _dispatch:
# Run all tests
for fn in _dispatch.values():
print(fn())
else:
print(_dispatch[sys.argv[1]]())
+46
View File
@@ -0,0 +1,46 @@
*** Settings ***
Documentation Repository Indexing CLI Integration Tests
Library Process
Library OperatingSystem
*** Variables ***
${PYTHON} python
${HELPER} ${CURDIR}/helper_repo_indexing_cli.py
*** Test Cases ***
E2E Add Resource Index And Check Status
[Documentation] End-to-end: add resource, index it, check status
${result}= Run Process ${PYTHON} ${HELPER} e2e
... on_timeout=kill timeout=60s
Should Be Equal As Integers ${result.rc} 0 E2E test failed: ${result.stderr}
Should Contain ${result.stdout} E2E add-index-status PASSED
Incremental Reindex After File Change
[Documentation] Incremental re-index detects new files
${result}= Run Process ${PYTHON} ${HELPER} incremental
... on_timeout=kill timeout=60s
Should Be Equal As Integers ${result.rc} 0 Incremental test failed: ${result.stderr}
Should Contain ${result.stdout} Incremental reindex PASSED
Full Reindex Replaces Entire Index
[Documentation] Full re-index replaces the entire index
${result}= Run Process ${PYTHON} ${HELPER} full
... on_timeout=kill timeout=60s
Should Be Equal As Integers ${result.rc} 0 Full reindex test failed: ${result.stderr}
Should Contain ${result.stdout} Full reindex PASSED
Status Returns None When No Index Exists
[Documentation] Status check with no index returns None
${result}= Run Process ${PYTHON} ${HELPER} no-index
... on_timeout=kill timeout=60s
Should Be Equal As Integers ${result.rc} 0 Status no-index test failed: ${result.stderr}
Should Contain ${result.stdout} Status no-index PASSED
Remove Index Cleans Up All Data
[Documentation] Removing an index cleans up all data
${result}= Run Process ${PYTHON} ${HELPER} remove
... on_timeout=kill timeout=60s
Should Be Equal As Integers ${result.rc} 0 Remove index test failed: ${result.stderr}
Should Contain ${result.stdout} Remove index PASSED
*** Keywords ***
+5 -5
View File
@@ -65,7 +65,7 @@ Scientific Paper Writer Full Workflow
# Test 8: Advance toward later stages (using structure or latex_generation as targets)
Log Advancing toward later stages...
${result}= Run Paper Command !next structure timeout=120s on_timeout=kill
${result}= Run Paper Command !next structure timeout=120s
Should Be Equal As Integers ${result.rc} 0
# Test 9: Verify stage list command works
@@ -90,7 +90,7 @@ Scientific Paper Writer LaTeX Generation
... ${V2_PAPER_CONTEXTS_DIR}/03_brainstorming.json
... --context-dir ${CONTEXT_DIR}
... stderr=STDOUT
... timeout=120s on_timeout=kill
... timeout=30s
Should Be Equal As Integers ${result.rc} 0
Log Context imported: ${result.stdout}
@@ -142,7 +142,7 @@ Scientific Paper Writer LaTeX Generation
... --allow-rxpy-in-run-mode
... -p !context
... stderr=STDOUT
... timeout=120s on_timeout=kill
... timeout=30s
Should Contain ${result.stdout} latex_source
Log LaTeX generation test completed
@@ -229,7 +229,7 @@ Cleanup Test Environment
Log Test environment cleaned up
Run Paper Command
[Arguments] ${command} ${timeout}=120s
[Arguments] ${command} ${timeout}=30s
[Documentation] Run a command against the paper writer with the persistent context
${result}= Run Process ${PYTHON} -m cleveragents actor run
... -c ${CONFIG_FILE}
@@ -239,7 +239,7 @@ Run Paper Command
... --allow-rxpy-in-run-mode
... -p ${command}
... stderr=STDOUT
... timeout=${timeout} on_timeout=kill
... timeout=${timeout}
Log Command: ${command}
Log Output: ${result.stdout}
RETURN ${result}
+238
View File
@@ -0,0 +1,238 @@
"""Repository indexing CLI commands for CleverAgents.
The ``agents repo`` command group manages repository indexing:
| Command | Description |
|-----------------------|------------------------------------------|
| ``agents repo index`` | Index or re-index a repository resource |
| ``agents repo status``| Show indexing status for a resource |
## Example Usage
```bash
# Index a resource (incremental by default)
agents repo index local/my-repo
# Force full re-index
agents repo index local/my-repo --full
# Check indexing status
agents repo status local/my-repo
# JSON output
agents repo status local/my-repo --format json
```
Wires the existing :class:`RepoIndexingService` backend to the CLI layer.
Based on issue #856: feat(cli): repo indexing CLI functional.
"""
from __future__ import annotations
import logging
from typing import Annotated
import typer
from rich.console import Console
from rich.panel import Panel
from cleveragents.application.container import get_container
from cleveragents.cli.formatting import format_output
from cleveragents.core.exceptions import NotFoundError
logger = logging.getLogger(__name__)
app = typer.Typer(
help="Repository indexing management.",
)
console = Console()
_FORMAT_HELP = "Output format: text or json (default: text)"
def _resolve_resource(
resource_name: str,
) -> tuple[str, str]:
"""Resolve a resource name to (resource_id, root_path).
Uses the ``ResourceRegistryService`` to look up the resource by
namespaced name or ULID, then extracts the filesystem path from
the resource's ``location`` or ``properties["path"]``.
Returns:
Tuple of (resource_id, root_path).
Raises:
typer.Exit: If the resource is not found or has no path.
"""
container = get_container()
registry_service = container.resource_registry_service()
try:
resource = registry_service.show_resource(resource_name)
except NotFoundError as exc:
console.print(f"[red]Error:[/red] Resource not found: {resource_name}")
raise typer.Exit(code=1) from exc
# Extract filesystem path from location or properties
root_path = resource.location
if not root_path and resource.properties:
path_val = resource.properties.get("path")
root_path = str(path_val) if path_val is not None else None
if not root_path:
console.print(
f"[red]Error:[/red] Resource '{resource_name}' has no filesystem path"
)
raise typer.Exit(code=1)
return resource.resource_id, root_path
@app.command("index")
def index_command(
resource_name: Annotated[
str,
typer.Argument(help="Resource name or ULID to index (e.g., local/my-repo)"),
],
full: Annotated[
bool,
typer.Option("--full", help="Force full re-index (not incremental)"),
] = False,
fmt: Annotated[
str,
typer.Option("--format", "-f", help=_FORMAT_HELP),
] = "text",
) -> None:
"""Index or re-index a repository resource.
By default performs an incremental refresh (only changed files).
Use ``--full`` to force a complete re-index.
Examples:
agents repo index local/my-repo
agents repo index local/my-repo --full
agents repo index local/my-repo --format json
"""
resource_id, root_path = _resolve_resource(resource_name)
container = get_container()
indexing_service = container.repo_indexing_service()
try:
if full:
repo_index = indexing_service.index_resource(resource_id, root_path)
else:
repo_index = indexing_service.refresh_index(resource_id, root_path)
except FileNotFoundError as exc:
console.print(f"[red]Error:[/red] {exc}")
raise typer.Exit(code=1) from exc
except ValueError as exc:
console.print(f"[red]Error:[/red] {exc}")
raise typer.Exit(code=1) from exc
metadata = repo_index.metadata
if fmt.lower() == "json":
data: dict[str, object] = {
"resource": resource_name,
"resource_id": metadata.resource_id,
"index_id": metadata.index_id,
"status": metadata.status.value,
"file_count": metadata.file_count,
"token_estimate": metadata.token_estimate,
"primary_language": metadata.primary_language,
"indexed_at": metadata.indexed_at.isoformat(),
"mode": "full" if full else "incremental",
}
console.print(format_output(data, "json"))
return
# Text output
mode_label = "Full index" if full else "Incremental refresh"
console.print(f"[green]{mode_label} complete:[/green] {resource_name}")
details = (
f"[bold]Status:[/bold] {metadata.status.value}\n"
f"[bold]Files:[/bold] {metadata.file_count}\n"
f"[bold]Tokens:[/bold] {metadata.token_estimate:,}\n"
f"[bold]Language:[/bold] {metadata.primary_language}\n"
f"[bold]Indexed at:[/bold] {metadata.indexed_at.isoformat()}"
)
console.print(Panel(details, title="Index Result", expand=False))
@app.command("status")
def status_command(
resource_name: Annotated[
str,
typer.Argument(
help="Resource name or ULID to check status (e.g., local/my-repo)"
),
],
fmt: Annotated[
str,
typer.Option("--format", "-f", help=_FORMAT_HELP),
] = "text",
) -> None:
"""Show indexing status for a repository resource.
Displays index metadata including file count, token estimate,
primary language, status, and last indexed timestamp.
Examples:
agents repo status local/my-repo
agents repo status local/my-repo --format json
"""
resource_id, _root_path = _resolve_resource(resource_name)
container = get_container()
indexing_service = container.repo_indexing_service()
metadata = indexing_service.get_index_status(resource_id)
if metadata is None:
if fmt.lower() == "json":
data: dict[str, object] = {
"resource": resource_name,
"status": "not_indexed",
"message": "No index found for this resource",
}
console.print(format_output(data, "json"))
else:
console.print(
f"[yellow]No index found for resource:[/yellow] {resource_name}"
)
return
if fmt.lower() == "json":
data = {
"resource": resource_name,
"resource_id": metadata.resource_id,
"index_id": metadata.index_id,
"status": metadata.status.value,
"file_count": metadata.file_count,
"token_estimate": metadata.token_estimate,
"primary_language": metadata.primary_language,
"indexed_at": metadata.indexed_at.isoformat(),
}
if metadata.error_message:
data["error_message"] = metadata.error_message
console.print(format_output(data, "json"))
return
# Text output
console.print(f"[bold]Index status for:[/bold] {resource_name}")
error_line = ""
if metadata.error_message:
error_line = f"\n[bold]Error:[/bold] {metadata.error_message}"
details = (
f"[bold]Status:[/bold] {metadata.status.value}\n"
f"[bold]Files:[/bold] {metadata.file_count}\n"
f"[bold]Tokens:[/bold] {metadata.token_estimate:,}\n"
f"[bold]Language:[/bold] {metadata.primary_language}\n"
f"[bold]Indexed at:[/bold] {metadata.indexed_at.isoformat()}"
f"{error_line}"
)
console.print(Panel(details, title="Index Status", expand=False))
+7
View File
@@ -88,6 +88,7 @@ def _register_subcommands() -> None:
lsp,
plan,
project,
repo,
resource,
session,
skill,
@@ -206,6 +207,11 @@ def _register_subcommands() -> None:
name="server",
help="Server connection management (stub)",
)
app.add_typer(
repo.app,
name="repo",
help="Repository indexing management",
)
_subcommands_registered = True
@@ -627,6 +633,7 @@ def main(args: list[str] | None = None) -> int:
"repl", # Interactive REPL
"tui", # Textual TUI
"server", # Server connection management
"repo", # Repository indexing management
"tell", # Shortcut for plan tell
"build", # Shortcut for plan build
"apply", # Shortcut for plan apply