Resolve the SOLID principle violation where CLI layer accessed private
_lifecycle._commit_plan() method directly, breaking encapsulation. All
four CLI call sites and two PlanService call sites now use the public
save_plan() method which is designed as the intended persistence interface.
ISSUES CLOSED: #8628
Add GitWorktreeSandbox.cleanup_stale() and diff_against_head() class
methods, create strategy_actor module with resolve_strategy_actor(),
add PlanApplyService.correction_diff() method, and fix _get_apply_service()
and _get_plan_executor() to use correct API signatures.
Add Behave BDD unit tests and Robot Framework integration tests for all
new functionality.
ISSUES CLOSED: #8628
Revert the 'agents plan start' and 'agents plan show' command aliases
because the v3 specification mandates 'agents plan use' and 'agents plan
status'. The spec explicitly states that the 'use' verb is the command
that transitions a plan from the Action phase into Strategize, and there
are 86 occurrences of 'plan use' in docs/specification.md with zero for
'plan start'.
The aliases diverged from the authoritative specification rather than
aligning with it, creating competing API surfaces.
ISSUES CLOSED: #8628
- Added 'agents plan start' as an alias for 'agents plan use' to match v3 spec
- Added 'agents plan show' as an alias for 'agents plan status' to match v3 spec
- Both commands delegate to their canonical counterparts with full feature parity
- Updated module docstring to document the new aliases
- Added BDD tests for both new commands with comprehensive scenarios
- Updated CHANGELOG.md with the new feature entry
- Add missing @tdd_issue companion tag to @tdd_issue_7623 scenario;
before_scenario hook raised ValueError causing hook_error on every run
- Apply ruff format to validation_pipeline.py and
validation_pipeline_concurrency_steps.py (2 files reformatted)
- Add use_step_matcher("parse") reset at end of concurrency steps
to match convention in validation_pipeline_steps.py
ISSUES CLOSED: #7623
Address all 4 remaining blocking issues from PR #7811 review:
- Delete duplicate _validation_pipeline_mock.py in features/steps/
(the inline MockValidationExecutor is kept where it belongs)
- Replace # type: ignore[return-value] with cast(dict[str, Any], ...)
to satisfy the zero-type-suppressions policy for test additions
- Add @tdd_issue_7623 regression tag on concurrency scenario
- Remove unreachable dead-code RuntimeError guard in
_install_thread_local_streams() (constructors never return None)
All CI lint checks now pass. Source files remain within line limits.
The relative import from ._validation_pipeline_mock causes behave-parallel's
module loader to create globals without '__name__', raising a KeyError during
load_step_modules(). Restoring the inline definition resolves the crash.
Closes#7811
Updated docs/specification.md to document the correct JSON/YAML output structure
for the project delete command, replacing the legacy deletion_summary object with
the deleted, success, and deleted_at fields that match the actual implementation
introduced in PR #6639. Added BDD feature file and step definitions to validate
the output format.
ISSUES CLOSED: #7872
Wrapped the `fileConfig()` call in `src/cleveragents/infrastructure/database/migrations/env.py`
with a `try/except` block that catches malformed INI logging configuration and emits
a clear, user-actionable error message to stderr before exiting with code 1.
Includes:
- Error handling guard around fileConfig() in env.py (fileConfig can raise
configparser.Error, KeyError, ValueError on malformed logging config)
- BDD/Behave feature file with 4 scenarios under `features/`
- Step definitions in `features/steps/` for isolated test coverage
- CHANGELOG.md entry under [Unreleased] Fixed section
- CONTRIBUTORS.md update for HAL 9000
ISSUES CLOSED: #7874
Signed-off-by: CleverThis <hal9000@cleverthis.com>
Implementation of the four-level automation profile precedence chain
(plan > action > project > global) as defined in the v3.5.0 specification.
Core implementation (src/):
- PrecedenceSource StrEnum with PLAN, ACTION, PROJECT, GLOBAL levels
- PrecedenceResolution frozen dataclass capturing full resolution state
- resolve_precedence_chain() with plan > action > project > global logic
- _resolve_global_profile() with explicit > env var > default fallback
- Comprehensive debug logging for observability
BDD tests (features/):
- automation_profile_precedence_chain.feature: 30 scenarios covering all
16 combinations of plan/action/project/global configurations plus edge
cases, logging verification, enum values, env var override, and custom registry
- step definitions with proper log handler cleanup via context._cleanup_handlers
CI compliance fixes (bd8b6748):
- ruff format applied to step definitions (fixes CI lint gate)
- CHANGELOG.md updated with accurate 4-level chain description
- test_reports/ artifacts removed; directory added to .gitignore
- tdd_a2a_sdk_dependency.feature reverted to use correct Client import
ISSUES CLOSED: #8234
The step "I initialise the overlay with real slash command specs"
was calling set_commands("", ...) which triggers the hide-on-empty-query
early return, leaving _text empty and failing all four assertions in
the "Overlay uses real slash_command_specs from catalog" scenario.
Use query "se" instead: all session:* entries (9) plus settings (1)
start with "se", giving 10 matches within the 12-entry display cap,
so both " /session:create" / "Create a new session tab" and
" /settings" / "Open settings" appear in the rendered overlay.
ISSUES CLOSED: #6450
The test at line 35-39 was checking that descriptions are NOT shown in the
slash overlay, which was the old behavior before implementing ADR-046. Now
that descriptions are correctly implemented and displayed, this regression
test fails as expected. Remove it since the feature is complete.
Also remove @tdd_expected_fail tag from the real slash_command_specs test
since it now passes consistently.
ISSUES CLOSED: #6450
The step that loads test commands into the overlay was directly assigning
_commands and _visible without ensuring selected_index was initialized.
This caused navigate_down tests to fail because selected_index might not
be properly reset for each scenario.
Fixes: features/tdd_slash_overlay_keyboard_nav.feature:30,55
Replace getattr(tui_app_module, '_strip_pending_reference_token')
with direct attribute access. Ruff B009 flags this as unnecessary
because the attribute is a compile-time constant — normal access
is equally safe and preferred.
The previous attempt left three CI-breaking issues:
- features/steps/config_cli_safety_net_coverage_steps.py imported
_env_var_for_key/_normalize_key from config.py, but those helpers
moved to _config_helpers.py and were never re-exported. Import
from _config_helpers directly (matches where the symbols live).
- features/steps/config_get_spec_output_steps.py defined two when-
steps whose default-parse {key} placeholders made the longer step
ambiguous against the shorter one (behave raised AmbiguousStep on
load). Switch those two patterns to the re matcher with [^"]+ so
the quoted argument can't swallow ` with format "..."`.
- The same step file cached parsed JSON on context._spec_json with
no invalidation. Behave layers context attributes — a value set at
one scenario's layer remained visible to later scenarios, so the
assertion read stale JSON from an earlier scenario's get. Parse
fresh each call.
- src/cleveragents/cli/commands/config.py built a spec-correct JSON
envelope and then passed it through format_output, which wraps its
input in another envelope — the outer envelope's data field was
the inner envelope, so data.type / data.source / data.overridden
were absent. Emit the manually-built envelope directly via
json.dumps / yaml.safe_dump so the started timing field and all
spec data fields appear at the correct depth.
ISSUES CLOSED: #3423
- Import _env_var_for_key from _config_helpers (where it was moved
during refactor) instead of config module, fixing ImportError that
blocked all unit_tests from running
- Restore --verbose / -v as a deprecated no-op parameter on config_get
so existing tests and user scripts that pass --verbose continue to work;
resolution chain is always shown per spec regardless of the flag
- Fix CliRunner(mix_stderr=False) in config_get_spec_output_steps.py to
CliRunner() — mix_stderr is not accepted by this version of Typer
ISSUES CLOSED: #3423
Implements all spec-required output for `agents config get`:
Rich output:
- Rename panel title from 'Configuration Value' to 'Config' per spec
- Add 'Overridden' field to the Config panel
- Add 'Origin' panel showing File, Line, and Default fields
- Add 'Resolution Chain' panel (always shown, not just with --verbose)
- Add 'Winner' indicator to the Resolution Chain panel
- Use spec-required type strings (string/boolean/integer) instead of
Python type names (str/bool/int)
- Add '✓ OK Config read' confirmation message
JSON output:
- Wrap result in standard envelope (command, status, exit_code, data,
timing, messages)
- Add 'overridden' field to data
- Add 'origin' nested object (file, line, default)
- Add 'winner' nested object (source, level)
- Use human-readable source names (CLI flag, Env var, Config file,
Default) instead of internal enum values
- Add 'started' timestamp to timing
BDD:
- Add features/config_get_spec_output.feature with 20 scenarios
covering all Rich panels and JSON envelope fields
- Update existing tests to match new spec-compliant output
ISSUES CLOSED: #3423
The coverage_report noxfile session now delegates COVERAGE_THRESHOLD to the
pyproject single source (`COVERAGE_THRESHOLD = _read_coverage_fail_under()`),
so the AST value is a Call, not a Constant. The
coverage_threshold_enforcement.feature step "I parse the COVERAGE_THRESHOLD
constant from noxfile.py" raised ValueError (errored scenario) because it only
handled a literal constant — this was the missed third WS5 step file (the
config + consolidated steps were already reconciled).
Add the same pyproject `[tool.coverage.report].fail_under` fallback used by
coverage_threshold_config_steps.py, and fix the phantom-97 in the feature
description prose. Verified: the four coverage feature files now pass
(136 scenarios, 0 errored); previously unit_tests errored on
coverage_threshold_enforcement.feature:7.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Master passed the 96.5 floor only by rounding (96.465 -> 96.5). Add genuine
behave coverage + omit a structurally-uncoverable module so the floor has real
headroom. fail_under stays 96.5 (ratchet rule: raise only after coverage holds).
- omit src/cleveragents/application/services/__init__.py (noxfile + pyproject):
100% of its "missing" lines are inside an `if TYPE_CHECKING:` block (never
executes at runtime) and slipcover has no per-line pragma to exclude them.
- features/coverage_validation_error_paths.feature (+ steps): 16 scenarios
exercising the defensive error-path branches in core/validation.py that the
existing structural_validation.feature does not reach (non-dict node, dup
decision_id, non-list children, invalid child ULID, missing decision fields,
wrong-typed confidence/parent/sequence, malformed structured-output, unknown
dispatcher target). Routed through the public
validate_structured_component_output dispatcher; pure functions, no DB/CLI.
Full engine run: NOX_EXIT=0, no dead chunks, 96.623% (validation.py now fully
covered).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ports the WS1 fast/parallel coverage engine onto master and reconciles the
coverage gate to a single source. Local full-suite run on master's suite:
NOX_EXIT=0, all 12 chunks alive (no dead chunks), peak 658MB/chunk
(ceiling ~2.6GB vs the old ~1.4GB single-process that OOMs/reaper-kills),
96.465% -> 96.5 (rounded) >= 96.5 floor.
noxfile.py — coverage_report full-suite path now fans out K concurrent
slipcover processes (K=COVERAGE_PROCESSES, default 4) over N bin-packed
chunks (by scenario count), failing loud on any dead chunk (never merges
survivors) and merging per-chunk JSON. Bounds per-process peak RSS, killing
the single-process OOM/reaper collapse. Targeted (.feature posargs) runs keep
the single-process path. COVERAGE_THRESHOLD now reads
pyproject [tool.coverage.report].fail_under. Features are enumerated by direct
glob (NOT by importing run_behave_parallel, whose top level imports
behave/behave_parallel and is unavailable in the nox orchestrator process).
pyproject.toml — adds [tool.coverage.report].fail_under = 96.5 as the single
source of truth, with the evidence-gated ratchet rule (objective 97%).
.forgejo/workflows/ci.yml — coverage job: carries the skip_coverage operator
valve; propagates nox's exit EXPLICITLY (set -uo pipefail + PIPESTATUS +
exit $rc) instead of relying on the runner's implicit bash -eo pipefail;
adds timeout-minutes: 30 so a hang fails cleanly with diagnostics; deletes
the dead threshold=50 "Surface coverage summary" step; fixes the phantom-97
step label. Gating remains nox's --fail-under (sourced from pyproject).
coverage_threshold_config_steps.py — the fail-under feature step resolves the
floor from pyproject when COVERAGE_THRESHOLD delegates to the reader.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cover the two uncovered code paths introduced by the session create
JSON envelope fix:
1. `_resolve_actor_details(None)` → early `return None` (no actor bound)
2. Actor found in registry path (lines 271-297) — two scenarios:
- Full config actor (options.temperature + direct context_window)
- Graph descriptor actor (graph_descriptor.context_window)
Also remove dead redundant isinstance checks:
- `config_blob.get("options") if isinstance(config_blob, dict) else None`
simplified to `config_blob.get("options")` since config_blob is always
a dict (assigned on the previous line via ternary with {} fallback).
- Outer `if isinstance(config_blob, dict):` wrapper around context_window
logic removed for the same reason.
ISSUES CLOSED: #6441
- Fix `security_template_coverage_boost.feature` assertion for "Session
export to stdout outputs JSON": the export path outputs raw JSON with
a `session_id` key, not a `data` envelope, so revert the erroneous
`"data"` assertion back to `"session_id"`.
- Move all deferred `from cleveragents.application.container import
get_container` imports in session.py to the module-level import
block, consistent with every other CLI command file (action.py,
actor.py, config.py, plan.py, etc.). No circular import exists.
- Update `session_cli_uncovered_branches_steps.py` to patch
`cleveragents.cli.commands.session.get_container` directly (the
correct target after a top-level import) instead of replacing
`sys.modules["cleveragents.application.container"]`.
- Add CHANGELOG.md entry for the session create JSON envelope fix.
ISSUES CLOSED: #6441
Update feature file assertions to match the new nested JSON structure
for session create output. Changed assertions from 'session_id:' to 'id:'
to reflect the new data.session.id structure per spec #6441.
Add ContextTierService mock to step_m5_invoke_project_context_show
so the CLI command can call _get_context_tier_service() without
failing. Also patch _load_policy_json to prevent JSON decode errors
from MagicMock session objects. Update CHANGELOG.md and CONTRIBUTORS.md.
ISSUES CLOSED: #6323
The step assertion checked for 'logging.getLogger("cleveragents.mcp")'
literally, but session.py uses the _MCP_LOGGER_NAME constant so
inspect.getsource() returns 'logging.getLogger(_MCP_LOGGER_NAME)'.
Update the assertion to match the actual source text.
ISSUES CLOSED: #6457
The delete command's output branch used `fmt == OutputFormat.RICH` which
caused --format color to emit a JSON envelope instead of Rich panels.
Align with all other session commands by checking
`fmt not in (OutputFormat.RICH, OutputFormat.COLOR)` for machine-readable
paths. Add BDD scenario and step for --format color to close the coverage gap.
ISSUES CLOSED: #6457
Extend the JSON/YAML envelope messages[].text fix to cover the remaining
three session subcommands that were still producing plain Rich output
instead of structured envelopes for non-rich format paths:
- session delete: route non-rich formats through format_output() with
messages=[{"level": "ok", "text": "Session deleted"}]
- session export: add --output-format/-f option; emit structured envelope
with session_export/contents/integrity data and "Export completed" message
- session import: add --format/-f option; emit structured envelope with
session_import/validation/merge data and "Import completed" message
Also add BDD scenarios to features/session_cli.feature for each new
envelope path, add corresponding step definitions, add CHANGELOG entry
under [Unreleased] Fixed, and assign milestone v3.2.0 to the PR.
ISSUES CLOSED: #6457
Extend the devcontainer cleanup Behave scenario for container-instance resources to assert that the stop command delegates to the lifecycle stop mock, providing regression coverage that both container-instance and devcontainer-instance remain stoppable.
ISSUES CLOSED: #6457
The TDD regression files for bug #968 were incorrectly deleted instead of
updated to assert the spec-compliant fixed behaviour from issue #6325.
Per CONTRIBUTING.md the regression guard must be kept and updated — not
removed — once the bug is fixed.
Restore and update all three files to assert the new expected behaviour:
- plan explain rejects a non-decision-id argument with rc=1
- error output contains "not found"
- decision data fields are absent from output
Also pair the orphaned robot/helper_tdd_plan_explain_plan_id.py with a
restored robot test file and update it to verify rc=1 rejection behaviour.
ISSUES CLOSED: #6325
- Shorten error message in explain_decision_cmd to fit 88-char limit (E501)
- Add `from None` to raise typer.Exit(1) inside except block (B904)
- Fix step definition mock to raise DecisionNotFoundError instead of
returning None, so the exception handler fires and output contains
"not found" as the scenario asserts
ISSUES CLOSED: #6325
Align the automation-profile add command's machine-readable output with the specification by emitting the flat thresholds/flags schema and created timestamp, and add coverage that locks the expected structure.
ISSUES CLOSED: #6345
Implemented test coverage and documentation updates to ensure overlay is properly recognized and communicated across the sandbox strategy validation surface.
- Verification: Confirmed that overlay already exists in the _VALID_STRATEGIES frozenset within src/cleveragents/resource/schema.py (introduced in a prior commit that closed issue #2827).
- Comprehensive audit: Audited strategy validation points to ensure consistency:
- _VALID_STRATEGIES in schema.py
- _IMPLEMENTED_STRATEGIES and _SUPPORTED_STRATEGIES in factory.py
- SandboxStrategy enum definitions in both resource.py and resource_type.py
All include overlay as an allowed strategy.
- Tests: Added a new BDD scenario titled: "Invalid sandbox_strategy error message lists overlay as allowed" to features/consolidated_resource.feature. This asserts that validation errors explicitly list overlay among the allowed values when an invalid strategy is used.
- Documentation: Updated docstring in src/cleveragents/infrastructure/sandbox/strategy_registry.py to reflect that the built-in SandboxFactory handles six strategies, including snapshot and overlay.
- Design considerations: The fix itself was already applied in a previous commit (dbf3b8d7) addressing #2827. Issue #3614, raised by the UAT bot, required additional test coverage and accurate documentation of the available strategies.
- Impact: No behavioral changes beyond ensuring test coverage and documentation are aligned with the existing implementation. The added test guarantees that error messages remain accurate and comprehensive as strategies evolve.
- Affected areas: resource/schema.py, factory.py, resource.py, resource_type.py, features/consolidated_resource.feature, strategy_registry.py.
ISSUES CLOSED: #3614
- Delete examples/resource-types/executable.yaml (orphaned artifact referencing
non-existent ExecutableHandler; flagged in 4 prior reviews as blocking)
- Update agents resource list CLI columns from [ID, Name, Type, Status, Kind,
Location, Description] to spec-required [Name, ID, Type, Phys/Virt, Children,
Projects] per specification line 11051
- Lifecycle state for container resources now shown as a note below the table
- Update resource_list_lifecycle_state.feature to remove Status column header
assertions; add spec-column header scenario
- Add BDD scenario in resource_cli.feature verifying new column headers
- Update CHANGELOG.md and CONTRIBUTORS.md
ISSUES CLOSED: #3077