---
name: dev-cli-development
description: Guide for developing AIDB dev-cli commands and services. Covers Click
framework patterns, service architecture, command development, decorators, error
handling, CommandExecutor, CliOutput, BaseService, BaseManager patterns. Use when
working with src/aidb_cli/, adding CLI commands, creating services, or integrating
with Docker/test/adapter systems.
---
# Dev-CLI Development Guide
## Purpose
The AIDB dev-cli (`src/aidb_cli/`) is a Click-based command-line interface for AIDB development workflows. This skill guides developers in:
- Adding new CLI commands and command groups
- Creating reusable services following BaseService pattern
- Using custom decorators (@handle_exceptions, @require_repo_context)
- Integrating with Docker, test, and adapter systems
- Following Click framework best practices
- Proper error handling and output formatting
## When to Use This Skill
Auto-activates when:
- Editing files in `src/aidb_cli/`
- Mentioning "dev-cli", "CLI command", "Click", "CliOutput"
- Adding commands, services, or CLI utilities
- Working with test orchestration or Docker integration
## Related Skills
When developing CLI commands, you may also need:
- **testing-strategy** - CLI orchestrates test execution via test coordinator service
- **code-reuse-enforcement** - CLI must use existing constants and avoid magic strings
## Architecture Overview
**For comprehensive architecture details**, see `docs/developer-guide/cli-reference.md` and source code in `src/aidb_cli/`.
### Component Structure
```
src/aidb_cli/
├── commands/ # CLI command definitions (13 modules)
├── services/ # Business logic services (41+ files)
├── managers/ # High-level orchestration (singleton pattern)
├── core/ # Decorators, utilities, constants, param types
└── generators/ # Code generation for test scenarios
```
### Key Patterns
1. **Commands** - Thin wrappers that delegate to services
1. **Services** - Reusable business logic with CommandExecutor
1. **Managers** - Singleton orchestrators for complex workflows
1. **Context Injection** - Dependencies via Click's `ctx.obj`
1. **Unified Error Handling** - `@handle_exceptions` decorator
1. **Dynamic Parameter Types** - Custom types with shell completion
## Quick Start: Adding a Command
### Step 1: Create Command File
Create `src/aidb_cli/commands/mycommand.py`:
```python
"""My new command description."""
import click
from aidb_cli.core.decorators import handle_exceptions
from aidb_logging import get_cli_logger
logger = get_cli_logger(__name__)
@click.group(name="mycommand")
@click.pass_context
def group(ctx: click.Context) -> None:
"""Command group description."""
pass
@group.command()
@click.option("--option", "-o", help="Option description")
@click.pass_context
@handle_exceptions
def subcommand(ctx: click.Context, option: str) -> None:
"""Subcommand description."""
# Get dependencies from context
output = ctx.obj.output
repo_root = ctx.obj.repo_root
executor = ctx.obj.command_executor
# Delegate to service
from aidb_cli.services.myservice import MyService
service = MyService(repo_root, executor)
result = service.do_something(option)
# User-facing output (via OutputStrategy)
output.success(f"Done: {result}")
```
### Step 2: Register Command
In `src/aidb_cli/cli.py`:
```python
from aidb_cli.commands import mycommand
# In main CLI setup
cli.add_command(mycommand.group)
```
### Step 3: Test
```bash
./dev-cli mycommand subcommand --option value
```
## Command Development Patterns
### Decorator Order (CRITICAL)
**Always stack decorators in this exact order**:
```python
@group.command() # 1. Click command
@click.option("--opt", "-o") # 2. Options
@click.pass_context # 3. Context (BEFORE handle_exceptions)
@handle_exceptions # 4. Error handling (LAST)
def command(ctx: click.Context, opt: str) -> None:
"""Implementation."""
```
**Wrong order causes cryptic errors!**
### Custom Parameter Types
Use custom types for validation and autocompletion:
```python
from aidb_cli.core.param_types import TestSuiteParamType
@click.option("--suite", type=TestSuiteParamType(), required=True)
def command(ctx: click.Context, suite: str) -> None:
"""Command with validated suite parameter."""
```
Available: `TestSuiteParamType`, `LanguageParamType`, `DockerProfileParamType`, `TestMarkerParamType`
**For comprehensive Click patterns**, see [click-framework-patterns.md](resources/click-framework-patterns.md):
- Decorator stacking rules explained
- All custom parameter types with examples
- Context management patterns
- Error handling with @handle_exceptions
- Command groups and subcommands
- Advanced Click patterns
## Service Development Patterns
### BaseService Pattern (Recommended)
Most services extend `BaseService` for consistent dependency injection and utilities:
```python
from aidb_cli.managers.base.service import BaseService
class MyService(BaseService):
def __init__(self, repo_root: Path, command_executor=None, ctx=None):
super().__init__(repo_root, command_executor, ctx)
# Inherited: command_executor, resolved_env, logging methods, path utilities
def do_something(self) -> str:
self.log_info("Starting operation...")
return self.command_executor.execute(["cmd"], cwd=self.repo_root).stdout
```
**See [service-patterns.md](resources/service-patterns.md)** for comprehensive BaseService details, advanced patterns, and testing examples.
### Standalone Service Pattern
For simple services without BaseService:
```python
class MyService:
"""Service for my operations."""
def __init__(
self,
repo_root: Path,
command_executor: CommandExecutor,
logger: Logger | None = None
):
self.repo_root = repo_root
self.executor = command_executor
self.logger = logger or get_cli_logger(__name__)
def do_something(self, arg: str) -> str:
"""Do something useful."""
from aidb.common.errors import AidbError
result = self.executor.execute(["command", arg], cwd=self.repo_root)
if result.returncode != 0:
raise AidbError(f"Operation failed: {result.stderr}")
return result.stdout.strip()
```
**Key principles**:
- Accept dependencies in `__init__` (testable)
- Use `CommandExecutor` for all subprocess calls
- Log with `get_cli_logger(__name__)`
- Raise exceptions for errors
**For comprehensive service patterns**, see [service-patterns.md](resources/service-patterns.md):
- BaseService detailed patterns
- CommandExecutor comprehensive guide
- Service composition and singletons
- Testing services
- Real examples from AIDB codebase
## Error Handling
### @handle_exceptions Decorator
This decorator provides unified error handling:
```python
@handle_exceptions
def command(ctx: click.Context) -> None:
"""Command with automatic error handling."""
# Just raise errors naturally - decorator handles:
# - KeyboardInterrupt (cleanup Docker resources)
# - AidbError (formatted error output)
# - FileNotFoundError (specific exit code)
# - PermissionError (specific exit code)
# - Generic exceptions (traceback in verbose mode)
if not valid:
from aidb.common.errors import AidbError
raise AidbError("Validation failed")
```
### Exit Codes
Use standard exit codes:
```python
from aidb_cli.core.constants import ExitCode
if error:
CliOutput.error("Operation failed")
ctx.exit(ExitCode.GENERAL_ERROR) # 1
if not found:
ctx.exit(ExitCode.NOT_FOUND) # 2
if config_error:
ctx.exit(ExitCode.CONFIG_ERROR) # 3
```
## Output and Logging
### OutputStrategy for Commands
Commands use `ctx.obj.output` (OutputStrategy) for verbosity-aware user-facing output:
```python
output = ctx.obj.output
output.success("Operation completed") # Always visible (green)
output.error("Operation failed") # Always visible (red, stderr)
output.warning("Potential issue") # Always visible (yellow)
output.plain("Regular message") # Always visible (no color)
output.section("Title", Icons.ROCKET) # Always visible (with separator)
output.info("Verbose detail") # Only with -v flag
output.debug("Debug trace") # Only with -vvv flag
```
### CliOutput for Services/Managers
Services and managers (without Click context) use the static `CliOutput` utility:
```python
from aidb_cli.core.utils import CliOutput
CliOutput.success("Operation completed successfully")
CliOutput.error("Operation failed")
```
### Logger for Debugging
Use logger for debugging/trace information:
```python
from aidb_logging import get_cli_logger
logger = get_cli_logger(__name__)
logger.debug("Detailed debugging info")
logger.info("General info")
```
**Rule**: Commands → `ctx.obj.output`, Services → `CliOutput`, Debugging → `logger`
## Verbosity Levels
CLI supports three verbosity levels via global flags:
| Flag | Log Level | Enabled Features |
| ------ | --------- | ----------------------------------------------- |
| (none) | INFO | Standard output only |
| `-v` | DEBUG | + AIDB_ADAPTER_TRACE=1 |
| `-vvv` | TRACE | + AIDB_ADAPTER_TRACE=1 + AIDB_CONSOLE_LOGGING=1 |
**What each level includes:**
- **INFO**: User-facing milestones (session started, breakpoint hit, etc.)
- **DEBUG**: Operation summaries, state transitions, adapter lifecycle
- **TRACE**: Full DAP/LSP JSON payloads, receiver timing, protocol details
**Examples:**
```bash
./dev-cli test run -s unit # INFO level
./dev-cli -v test run -s unit # DEBUG + adapter traces
./dev-cli -vvv test run -s unit # TRACE + protocol payloads
```
Use `-vvv` for maximum observability when debugging DAP/LSP protocol issues.
## Common Patterns
### Import Organization
Follow project standard: stdlib → third-party → AIDB core → CLI → logging. All imports at top unless avoiding circular dependency.
**Avoiding circular imports**: Use `TYPE_CHECKING` for type-only imports:
```python
from typing import TYPE_CHECKING
import click
if TYPE_CHECKING:
from aidb_cli.cli import Context # Only imported for type checking
```
See `CLAUDE.md` for full style details.
### Service Instantiation in Commands
```python
@group.command()
@click.pass_context
@handle_exceptions
def command(ctx: click.Context) -> None:
"""Command that uses a service."""
# Instantiate service with dependencies from context
from aidb_cli.services.adapter.adapter_build_service import AdapterBuildService
service = AdapterBuildService(
repo_root=ctx.obj.repo_root,
command_executor=ctx.obj.command_executor
)
# Call service method
result = service.build_locally(["python"], verbose=False)
# Output result
CliOutput.success(f"Built adapter: {result}")
```
## Common Pitfalls
### 1. Decorator Order
**Wrong**: `@handle_exceptions` before `@click.pass_context`
**Right**: `@click.pass_context` then `@handle_exceptions`
### 2. Using subprocess Directly
**Wrong**: `subprocess.run(["cmd"])`
**Right**: `ctx.obj.command_executor.execute(["cmd"])`
### 3. Mixing Output Types
**Wrong**: `print("Success")` or `logger.info("User message")`
**Right**: `CliOutput.success("Message")` for users, `logger.debug()` for debugging
### 4. Hardcoded Paths
**Wrong**: `Path("/path/to/repo")`
**Right**: `ctx.obj.repo_root / "relative/path"`
## Real Code Examples
### Example 1: Simple Command
From `src/aidb_cli/commands/docker.py`:
```python
@group.command()
@click.option("--profile", "-p", type=DockerProfileParamType(), default=None)
@click.option("--no-cache", is_flag=True, help="Build without cache")
@click.pass_context
@handle_exceptions
def build(ctx: click.Context, profile: str | None, no_cache: bool) -> None:
"""Build Docker images."""
from aidb_cli.services.docker.docker_build_service import DockerBuildService
service = DockerBuildService(
repo_root=ctx.obj.repo_root,
command_executor=ctx.obj.command_executor
)
result = service.build_images(profile=profile, no_cache=no_cache)
CliOutput.success(f"Build complete: {result}")
```
### Example 2: Service with CommandExecutor
**See real implementation**: `src/aidb_cli/services/docker/docker_build_service.py`
Key pattern - services delegate to CommandExecutor and raise exceptions on failure:
```python
class DockerBuildService:
def __init__(self, repo_root: Path, command_executor: CommandExecutor):
self.repo_root = repo_root
self.executor = command_executor
def build_images(self, profile: str | None = None) -> int:
"""Build Docker images."""
from aidb.common.errors import AidbError
cmd = ["docker-compose", "build"]
if profile:
cmd.extend(["--profile", profile])
result = self.executor.execute(cmd, cwd=self.repo_root)
if result.returncode != 0:
raise AidbError(f"Build failed: {result.stderr}")
return 0
```
## Testing Your Changes
### Run CLI Locally
```bash
./dev-cli mycommand subcommand --option value # Run command
./dev-cli -v mycommand subcommand # Verbose mode
./dev-cli --dry-run mycommand subcommand # Dry run (no execution)
```
### Unit Testing Services
Test services by mocking CommandExecutor:
```python
from unittest.mock import Mock
from pathlib import Path
def test_my_service():
"""Test service logic without executing commands."""
executor = Mock()
executor.execute.return_value = Mock(returncode=0, stdout="success")
service = MyService(Path("/tmp"), executor)
result = service.do_something("arg")
assert result == "success"
executor.execute.assert_called_once_with(["command", "arg"], cwd=Path("/tmp"))
```
**See actual test examples**: `src/tests/aidb_cli/commands/integration/` for command integration tests and `src/tests/aidb_cli/services/` for service unit tests
## Service Discovery Guide
Common tasks and which services to use:
| Task | Service | Location |
| ------------------------------ | ------------------------------ | ---------------------------------------------------- |
| Build Docker images | `DockerBuildService` | `services/docker/docker_build_service.py` |
| Generate docker-compose.yaml | `ComposeGeneratorService` | `services/docker/compose_generator_service.py` |
| Track Docker image checksums | `DockerImageChecksumService` | `services/docker/docker_image_checksum_service.py` |
| Track framework deps checksums | `FrameworkDepsChecksumService` | `services/docker/framework_deps_checksum_service.py` |
| Check Docker health | `DockerHealthService` | `services/docker/docker_health_service.py` |
| Run tests | `TestCoordinatorService` | `services/test/test_coordinator_service.py` |
| Build adapters | `AdapterBuildService` | `services/adapter/adapter_build_service.py` |
| Execute commands | `CommandExecutor` | `services/command_executor/__init__.py` |
| Generate test programs | `Generator` | `generators/core/generator.py` |
**Full service list**: See `src/aidb_cli/services/` subdirectories - `docker/` (Docker operations), `test/` (test execution), `adapter/` (adapter building), `docs/` (documentation), `command_executor/` (subprocess execution)
## Resource Files
For deeper dives into specific topics, see:
- **[service-patterns.md](resources/service-patterns.md)** - Comprehensive service architecture patterns, CommandExecutor guide, testing, real examples
- **[click-framework-patterns.md](resources/click-framework-patterns.md)** - Click decorator stacking, custom parameter types, context management, error handling
- **[docker-compose-generation.md](resources/docker-compose-generation.md)** - Template-based docker-compose.yaml generation, Jinja2 templates, languages.yaml configuration, hash-based cache invalidation
- **[checksum-services.md](resources/checksum-services.md)** - ChecksumServiceBase pattern, DockerImageChecksumService, FrameworkDepsChecksumService, container lifecycle tracking, creating custom checksum services
______________________________________________________________________