Files
cleveragents-core/features/actor_cli_coverage.feature
hurui200320 554d6889cc
CI / lint (push) Successful in 17s
CI / build (push) Successful in 17s
CI / quality (push) Successful in 28s
CI / security (push) Successful in 43s
CI / typecheck (push) Successful in 46s
CI / benchmark-regression (push) Has been skipped
CI / unit_tests (push) Successful in 3m34s
CI / integration_tests (push) Successful in 3m37s
CI / docker (push) Successful in 56s
CI / e2e_tests (push) Successful in 5m19s
CI / coverage (push) Successful in 7m4s
CI / benchmark-publish (push) Successful in 20m58s
fix(cli): add --skill flag to actor run command (#971)
## Summary

Add the missing `--skill` repeatable flag to `actor run` and `actor-run` CLI commands, aligning the implementation with the specification (CLI Synopsis line 277). The flag enables ad-hoc skill injection at runtime without modifying YAML configuration.

Closes #887

## Changes

### DI Container
- **`container.py`**: Added `_build_skill_service()` factory and `skill_service` Singleton provider, following the established `_build_*` pattern. Falls back to in-memory `SkillService()` when the database is unavailable. Exception handling narrowed to `(ImportError, OperationalError, DatabaseError, OSError)` with `exc_info=True` for traceability.

### CLI Layer
- **`actor.py`**: Added `--skill` Typer option (`list[str] | None`, repeatable, `metavar="NAME"`). Help text notes that skills only augment tool-bearing agents. Wrapped constructor in the existing `try/except` block so `CleverAgentsException` from skill resolution is properly caught.
- **`actor_run.py`**: Same `--skill` option with `metavar="NAME"`. Exception handler catches `CleverAgentsException` (matching master — not broadened to `CleverAgentsError`).
- **`skill.py`**: Removed module-level `_service` cache. `_get_skill_service()` now always delegates to `get_container().skill_service()` so that `reset_container()` correctly invalidates the cached instance. `_reset_skill_service()` now overrides the container's provider via `providers.Object()`. Removed dead `validate_skill_names()` function.

### Runtime Layer
- **`application.py`** (438 lines, down from 625): `ReactiveCleverAgentsApp` gains `skill_names` parameter with automatic deduplication via `dict.fromkeys`. `_resolve_skills()` obtains `SkillService` from the DI container (no CLI layer import). Separate `except KeyError` and `except ValueError` produce distinct error messages (`"not found in registry"` vs `"resolution failed: {exc}"`). Skill tools are only injected into agents that already have tools (`if self._resolved_skill_tools and tools:`), preventing LLM agents from being converted to pass-through `SimpleToolAgent` instances. When skill tools are skipped for tool-less agents, `logger.debug` emits a diagnostic message. `_sanitize_skill_name()` validates skill name format with tightened regex: `^[\w.-]{1,127}/[\w.-]{1,127}$` with `re.ASCII` flag. Zero-tool skill warning now uses `logger.warning` (not `print(stderr)`), ensuring structured log output and proper log-level filtering.
- **`graph_executor.py`** (334 lines): Extracted graph execution logic. Type annotations improved.

### Tests
- 24+ Behave scenarios across feature files covering: single/multiple/unknown skill flags, skill+context combined, duplicate deduplication, skill resolution, ValueError path, zero-tool resolution, error handling, tool merging, default behavior, overrides, LLM agent guard, `_sanitize_skill_name` edge cases (empty string, too-long name, ANSI escape codes, disallowed characters), `_build_skill_service` happy+fallback paths, `_get_skill_service` container delegation.
- CLI "unknown skill" tests for **both** `actor.py` and `actor_run.py` exercise the real error chain (mock only `get_container()`, not the entire `ReactiveCleverAgentsApp`), testing `_resolve_skills()` → `CleverAgentsException` → `except CleverAgentsException` → exit code 2 end-to-end.
- Combined skill+context tests assert `ContextManager` was instantiated and `exists()` was called in dedicated **Then** steps.
- `@coverage` tags added to all new scenarios.
- **Robot Framework smoke tests** added (`robot/skill_actor_run.robot` + `robot/helper_skill_actor_run.py`): unknown-skill error path and valid-skill acceptance path.

### Changelog
- Added entry under `## Unreleased` in `CHANGELOG.md`.

## Review Fixes Applied (Brent Edwards, Rounds 1 & 2)

| # | Finding | Resolution |
|---|---------|------------|
| **P1-1** | `print(stderr)` for zero-tool skill warning | **Fixed** — replaced with `logger.warning("Skill '%s' resolved to zero tools", name)`, removed unused `import sys` |
| **P2-2** | Skill tools silently skipped for tool-less agents | **Fixed** — added `logger.debug` when skipped; updated `--skill` help text to note "only augments tool-bearing agents" |
| **P2-3** | `container.py` at 739 lines | **Acknowledged** — pre-existing growth (+59 lines for `_build_skill_service`); extracting factories is a separate refactoring task |
| **P2-4↑** | `CleverAgentsException` → `CleverAgentsError` broadens catch scope | **Fixed** — reverted `actor_run.py` to `except CleverAgentsException` matching master |
| **P3-5** | No Robot Framework smoke test for `--skill` | **Fixed** — added `skill_actor_run.robot` with 2 test cases (unknown-skill error, valid-skill acceptance) |
| **P3-6** | `GraphExecutor._follow_chained_edges` static-calling-static | **Acknowledged** — cosmetic pattern that doesn't affect correctness; can address in a follow-up |

## Known Limitations / Deferred Items

| Item | Reason |
|------|--------|
| `actor.py` at 679 lines (500-line guideline) | Pre-existing (670 on master), +9 lines for `--skill`. Refactoring the shared `_execute()` closure is a separate task. |
| `container.py` at 739 lines (500-line guideline) | Was 680 lines on master, +59 lines for `_build_skill_service()` and `skill_service` provider. Refactoring into sub-modules is a separate task. |
| Code duplication between `actor.py` and `actor_run.py` `run()` | ~47 lines identical code. Coupled with the line-count issue above — both require extracting shared execution logic into a helper module. |
| `SimpleToolAgent` only executes `tools[0]` | Deferred to #974. Pre-existing architectural limitation, not introduced by this PR. |
| `GraphExecutor._follow_chained_edges` static-calling-static pattern | Cosmetic, doesn't affect behavior. |

## Quality Gates
- `nox -s lint`:  PASS
- `nox -s typecheck`:  PASS (0 errors)
- `nox -s unit_tests`:  PASS (11,130 scenarios, 0 failures)
- `nox -s integration_tests`:  PASS (1,559 tests, 0 failures)
- `nox -s coverage_report`:  97% (meets threshold)
- Branch rebased onto latest `master` (`ab1fd19b`)

Reviewed-on: #971
Co-authored-by: Rui Hu <rui.hu@cleverthis.com>
Co-committed-by: Rui Hu <rui.hu@cleverthis.com>
2026-03-19 09:30:35 +00:00

310 lines
12 KiB
Gherkin

Feature: Actor CLI coverage
As a developer
I want actor CLI commands covered
So that actor management paths stay stable
Scenario: Add actor fails without config
Given an actor CLI runner
When I run actor add without config
Then the actor command should fail with bad parameter
Scenario: Add actor with JSON config file
Given an actor CLI runner
And I have an actor JSON config file
When I run actor add with that config
Then the actor add should pass the loaded config
Scenario: Add actor requires unsafe confirmation when config is marked unsafe
Given an actor CLI runner
And I have an unsafe actor JSON config file
When I run actor add with that config
Then the actor command should fail with bad parameter
Scenario: Add actor allows unsafe config when flag is provided
Given an actor CLI runner
And I have an unsafe actor JSON config file
When I run actor add with that config and unsafe flag
Then the actor add should pass the loaded config
Scenario: Add actor fails when config is missing
Given an actor CLI runner
When I run actor add with missing config path
Then the actor command should fail with bad parameter
Scenario: Add actor rejects non-dictionary config input
Given an actor CLI runner
And I have an actor list config file
When I run actor add with that config
Then the actor command should fail with bad parameter
Scenario: Add actor falls back to YAML-only config
Given an actor CLI runner
And I have an actor YAML-only config file
When I run actor add with that config
Then the actor add should pass the loaded config
Scenario: Add actor uses service path with graph descriptor
Given an actor CLI runner
And I have an actor graph config file
When I run actor add via service with that config
Then the service actor add should include graph descriptor
Scenario: Add actor merges CLI options into config blob
Given an actor CLI runner
And I have an actor JSON config file
When I run actor add with that config and options overrides
Then the actor add should pass the loaded config
Scenario: Add actor rejects empty config file
Given an actor CLI runner
And I have an empty actor YAML config file
When I run actor add with that config
Then the actor command should fail with bad parameter
Scenario: Add actor aborts on business rule violation
Given an actor CLI runner
When I run actor add with business rule violation
Then the actor command should abort with error
Scenario: Add actor rejects unsafe service config without confirmation
Given an actor CLI runner
And I have an unsafe actor JSON config file
When I run actor add via service without unsafe flag
Then the actor command should fail with bad parameter
And the actor service should not be called
Scenario: Update actor from YAML config and safe flag
Given an actor CLI runner
And I have an actor YAML config file returning empty data
When I run actor update with safe flag and yaml config
Then the actor update should set safe and merge config
Scenario: Update actor toggles unsafe flag
Given an actor CLI runner
When I run actor update with unsafe flag
Then the actor update should set unsafe flag
Scenario: Update actor aborts on validation error
Given an actor CLI runner
When I run actor update with validation error
Then the actor command should abort with error
Scenario: Update actor aborts when not found
Given an actor CLI runner
When I run actor update for missing actor
Then the actor command should abort for missing actor
Scenario: Update actor rejects conflicting safety flags
Given an actor CLI runner
When I run actor update with conflicting flags
Then the actor command should fail with bad parameter
Scenario: Update actor requires unsafe confirmation when requested
Given an actor CLI runner
When I run actor update requiring unsafe confirmation
Then the actor command should fail with bad parameter
Scenario: Update actor rejects unsafe service config without confirmation
Given an actor CLI runner
And I have an unsafe actor JSON config file
When I run actor update via service without unsafe flag
Then the actor command should abort with error
And the actor service should not be called
Scenario: Remove actor succeeds
Given an actor CLI runner
When I run actor remove successfully
Then the actor remove should succeed
Scenario: Remove actor aborts on validation error
Given an actor CLI runner
When I run actor remove and it fails validation
Then the actor command should abort with error
Scenario: List actors when none exist
Given an actor CLI runner
When I run actor list with no actors
Then the actor list should report empty state
Scenario: List actors with entries
Given an actor CLI runner
When I run actor list with two actors
Then the actor list should render rows
Scenario: Show actor displays details
Given an actor CLI runner
When I run actor show successfully
Then the actor show should display details
Scenario: Show actor aborts on validation error
Given an actor CLI runner
When I run actor show with validation error
Then the actor command should abort with error
Scenario: Set default actor succeeds
Given an actor CLI runner
When I run set-default actor successfully
Then the set default actor should display details
Scenario: Set default actor aborts on business rule violation
Given an actor CLI runner
When I run set-default actor with violation
Then the actor command should abort with error
Scenario: Add actor merges options and parses boolean override
Given an actor CLI runner
And I have an actor JSON config file with options
When I run actor add with boolean option override
Then the actor add should pass the loaded config
Scenario: Add actor rejects option without separator
Given an actor CLI runner
And I have an actor JSON config file
When I run actor add with malformed option
Then the actor command should fail with bad parameter
Scenario: Add actor rejects empty option key
Given an actor CLI runner
And I have an actor JSON config file
When I run actor add with empty option key
Then the actor command should fail with bad parameter
Scenario: Add actor via service rejects unsafe canonical config
Given an actor CLI runner
And I have an actor JSON config file
When I run actor add via service with unsafe canonical blob
Then the actor command should abort with error
Scenario: Update actor includes option overrides in registry call
Given an actor CLI runner
When I run actor update with option overrides
Then the actor update should include option overrides
Scenario: Update actor parses uppercase boolean option override
Given an actor CLI runner
When I run actor update with uppercase boolean option override
Then the actor update should include option overrides
Scenario: Update actor via service rejects unsafe canonical config
Given an actor CLI runner
And I have an actor JSON config file
When I run actor update via service with unsafe canonical blob
Then the actor command should abort with error
Scenario: Update actor via service succeeds with safe config
Given an actor CLI runner
And I have an actor JSON config file
When I run actor update via service with safe config
Then the actor service update should succeed
Scenario: Remove actor falls back to service path
Given an actor CLI runner
When I run actor remove via service path
Then the actor service remove should be called
Scenario: Add actor parses uppercase boolean option override
Given an actor CLI runner
And I have an actor JSON config file with options
When I run actor add with uppercase boolean option override
Then the actor add should pass the loaded config
Scenario: Run actor uses load context with named context
Given an actor CLI runner
And I have an actor JSON config file
And I have a saved context JSON file
And I have an actor output file path
When I run actor run with load context and context name
Then the actor run should write output and persist context
Scenario: Run actor loads context without name
Given an actor CLI runner
And I have an actor JSON config file
And I have a saved context JSON file
When I run actor run with load context only
Then the actor run should update global context and echo result
Scenario: Run actor reuses named context
Given an actor CLI runner
And I have an actor JSON config file
When I run actor run with context only
Then the actor run should reuse saved context
Scenario: Run actor executes without context
Given an actor CLI runner
And I have an actor JSON config file
When I run actor run without context
Then the actor run should call single shot without context manager
Scenario: Run actor forwards allow-rxpy flag with named context and output
Given an actor CLI runner
And I have an actor JSON config file
And I have a saved context JSON file
And I have an actor output file path
When I run actor run with load context and context name allowing rxpy
Then the actor run should write output and persist context
And the actor run should pass allow rxpy flag
Scenario: Run actor forwards allow-rxpy flag when loading context only
Given an actor CLI runner
And I have an actor JSON config file
And I have a saved context JSON file
When I run actor run with load context only allowing rxpy
Then the actor run should update global context and echo result
And the actor run should pass allow rxpy flag
Scenario: Run actor forwards allow-rxpy flag when reusing context
Given an actor CLI runner
And I have an actor JSON config file
When I run actor run with context only allowing rxpy
Then the actor run should reuse saved context
And the actor run should pass allow rxpy flag
Scenario: Run actor forwards allow-rxpy flag without context
Given an actor CLI runner
And I have an actor JSON config file
When I run actor run without context allowing rxpy
Then the actor run should call single shot without context manager
And the actor run should pass allow rxpy flag
Scenario: Run actor reports unsafe configuration error
Given an actor CLI runner
And I have an actor JSON config file
When I run actor run with unsafe configuration error
Then the actor run should exit with error code 1
Scenario: Run actor reports clever agents error
Given an actor CLI runner
And I have an actor JSON config file
When I run actor run with clever agents error
Then the actor run should exit with error code 2
@coverage
Scenario: Run actor with single skill flag
Given an actor CLI runner
And I have an actor JSON config file
When I run actor run with a single skill flag
Then the actor run should pass skill names to the runtime
@coverage
Scenario: Run actor with multiple skill flags
Given an actor CLI runner
And I have an actor JSON config file
When I run actor run with multiple skill flags
Then the actor run should pass all skill names to the runtime
@coverage
Scenario: Run actor with unknown skill flag
Given an actor CLI runner
And I have an actor JSON config file
When I run actor run with an unknown skill flag
Then the actor run should exit with skill not found error
@coverage
Scenario: Run actor with skill and context flags combined
Given an actor CLI runner
And I have an actor JSON config file
When I run actor run with skill and context flags
Then the actor run should pass skill names to the runtime
And the context manager should have been instantiated
And the context manager exists should have been called