MCPToolAdapter.invoke() was reading error messages from result.get('error',
'unknown error'), but the MCP 1.4.0 protocol returns errors in the content
field as a list of content items with type and text keys. This caused every
error from a real MCP 1.4.0-compliant server to be silently replaced with
the string 'unknown error'.
Changes:
- src/cleveragents/mcp/adapter.py: extract error_text from content[0].text
with safe guards (isinstance check, length check) and fallback to
'unknown error' when content is absent or empty
- features/mocks/mock_mcp_transport.py: return MCP 1.4.0-compliant error
responses using content list format instead of the non-standard error key
- features/tdd_mcp_error_content_key.feature: Behave scenario verifying
correct error extraction from MCP 1.4.0 content arrays (written as TDD
issue-capture, @tdd_expected_fail removed after fix applied)
- features/steps/tdd_mcp_error_content_key_steps.py: step definitions for
the new scenario including _MCP14ErrorTransport mock subclass
All 51 MCP adapter scenarios pass. Typecheck: 0 errors. Lint: clean.
ISSUES CLOSED: #2158
Add 8 BDD scenarios in features/fast_init_upgrade.feature that directly
exercise the _fast_init_or_upgrade closure installed by
_install_template_db_patch in features/environment.py.
Scenarios cover all code paths:
- Non-empty DB with matching prefix → early return (original NOT invoked)
- Non-existent DB with matching prefix → template copied (original NOT invoked)
- Existing empty DB with matching prefix → template copied (original NOT invoked)
- Non-matching prefix → delegates to original init_or_upgrade
- In-memory SQLite → delegates to original init_or_upgrade
- Non-SQLite URL → delegates to original init_or_upgrade
- Bare sqlite:// URL → delegates to original init_or_upgrade
- Delegation forwards runner instance and keyword arguments correctly
Test infrastructure:
- features/mocks/fast_init_test_helpers.py provides a context manager
(patch_original_init_or_upgrade) that replaces the _original_init_or_upgrade
reference inside the closure via cell_contents manipulation, enabling
precise call-tracking without recreating the function under test.
- All scenarios tagged @mock_only to skip unnecessary DB setup.
- Cleanup registered via context._cleanup_handlers for temp file removal.
- Works in both sequential and parallel execution modes.
Review fix round:
- Added bare sqlite:// URL scenario covering the second branch of the
in-memory disjunction (L1).
- Added scenario verifying runner instance and keyword argument forwarding
through the delegation path (L2, L3).
- Replaced hardcoded test credentials with clearly synthetic pattern (I4).
- Fixed CHANGELOG wording that incorrectly claimed mktemp replacement (L5).
ISSUES CLOSED: #733
Implement the full correction-checkpoint rollback pipeline:
- Workspace snapshots: CheckpointService.create_workspace_snapshot()
creates diff-based checkpoints before decision execution, storing
only changed file paths in metadata.extra["diff_paths"]
- CorrectionService.revert_decisions(): new high-level entry point
that creates a correction, computes impact, invokes checkpoint
rollback, and archives artifacts in a single call
- Physical artifact archival: CheckpointService.archive_artifacts()
moves files to .cleveragents/archived_artifacts/ instead of just
flagging metadata. CorrectionService._archive_decision_artifacts()
delegates to this during revert execution
- Selective rollback: CheckpointService.selective_rollback() wraps
rollback_to_checkpoint with atomic semantics — captures HEAD before
rollback and recovers on failure
- Diff-based storage: _compute_diff_snapshot() computes changed paths
between checkpoints via git diff; snapshots store diff manifest and
SHA-256 hash in metadata
- CLI: plan rollback now accepts --to-checkpoint <id> in addition to
the positional checkpoint_id argument; uses selective_rollback for
atomic execution
- DI wiring: Container now injects checkpoint_service into
CorrectionService; CLI correct command uses container-provided
service instead of ad-hoc instance (fixes bug #986)
- Checkpoint model: pre_decision added to allowed checkpoint_type
values
- TDD: Removed @tdd_expected_fail from wiring test feature since the
DI bug is now fixed
ISSUES CLOSED: #943
Rename the TDD tag system from tdd_bug/tdd_bug_<N> to tdd_issue/tdd_issue_<N>
across the entire codebase. The tdd_expected_fail tag is unchanged.
The TDD expected-failure workflow is not limited to bug fixes — it applies
equally to any issue type (features, tasks, refactors). The _bug suffix was
misleading and narrowed the perceived scope. The new _issue suffix accurately
reflects that the TDD tagging system applies to any Forgejo issue.
Changes span 92 files:
- features/environment.py: validate_tdd_tags(), should_invert_result(), and
apply_tdd_inversion() updated — regex, variables, error messages
- robot/tdd_expected_fail_listener.py: _validate_tdd_tags(), _should_invert_result(),
start_test(), end_test() updated consistently
- 33 Behave .feature files: all @tdd_bug/@tdd_bug_<N> tags renamed
- 29 Robot .robot files: all tdd_bug/tdd_bug_<N> tags renamed
- 3 Robot fixture files renamed (tdd_bug_alone, tdd_missing_tdd_bug,
tdd_expected_fail_missing_bug_n) with content and references updated
- Tag validation tests and helpers updated (function names, command dispatch
keys, output strings, fixture references)
- CONTRIBUTING.md: section renamed from 'TDD Bug Test Tags' to
'TDD Issue Test Tags', all tag references and examples updated
- noxfile.py: comment references updated
- Step definition files, mock helpers, and benchmark files: docstring
references updated
ISSUES CLOSED: #965
## Summary
- **`plan correct` now accepts a plan_id** as its positional argument (in addition to decision_id). When a plan_id is given, the root decision is automatically selected as the correction target.
- The positional parameter is renamed from `decision_id` to `identifier` with updated help text reflecting dual use.
- Backward compatibility is fully preserved: decision_id inputs continue to work exactly as before.
## How it works
1. Try `container.plan_lifecycle_service().get_plan(identifier)` to check if the identifier is a plan_id
2. If it resolves to a real `Plan` object, use it as `resolved_plan_id` and auto-select the root decision (`parent_decision_id is None`)
3. If lookup fails (`ResourceNotFoundError`) or the result is not a `Plan` instance, fall back to treating the identifier as a decision_id (original behavior)
## Verification
- `nox -s lint` — All checks passed
- `nox -s typecheck` — 0 errors, 1 pre-existing warning
- `nox -s unit_tests` (correction features) — 150 scenarios passed, 683 steps passed, 0 failures
ISSUES CLOSED: #969
Reviewed-on: cleveragents/cleveragents-core#1055
Co-authored-by: Brent E. Edwards <brent.edwards@cleverthis.com>
Co-committed-by: Brent E. Edwards <brent.edwards@cleverthis.com>
## Summary
Add TDD bug-capture tests for bug #969 (`plan correct` expects `decision_id` but M3 acceptance test passes `plan_id`). These tests prove the bug exists and will serve as regression guards once the fix in #969 is merged.
### Changes
- **Behave test** (`features/tdd_plan_correct_plan_id.feature`): Two scenarios tagged `@tdd_expected_fail @tdd_bug @tdd_bug_969` — one for `--mode revert` and one for `--mode append` — that invoke `plan correct <plan_id>` (without `--plan` flag) and assert the command resolves the plan_id to its root decision as `target_decision_id`. Both modes are tested because the bug affects `target_decision_id` resolution **before** mode-specific branching.
- **Step definitions** (`features/steps/tdd_plan_correct_plan_id_steps.py`): Mock setup for DI container (DecisionService), CorrectionService, and `_resolve_active_plan_id`. Uses `tpcpid` step prefix per project conventions.
- **Shared fixtures** (`features/mocks/tdd_plan_correct_plan_id_fixtures.py`): Centralised constants, patch targets, mock builders (`make_decision_ns`, `make_mock_container`, `make_correction_svc`, `make_default_decisions`, `make_default_container`), and `build_cli_args` helper. Both the Behave steps and Robot helper import from this shared module, eliminating code duplication and drift risk.
- **Robot test** (`robot/tdd_plan_correct_plan_id.robot`): Two integration-level tests (revert + append) with `tdd_expected_fail tdd_bug tdd_bug_969` tags, exercising the same code paths via the helper script.
- **Robot helper** (`robot/helper_tdd_plan_correct_plan_id.py`): Standalone helper that exits 0 with sentinel when the bug is fixed, exits 1 when the bug is present. Imports shared fixtures from `features/mocks/`.
- **Changelog** (`CHANGELOG.md`): Added entry under Unreleased for #979.
### Bug Description
The `correct_decision` function in `cleveragents.cli.commands.plan` declares `decision_id` as its first positional argument. When the M3 acceptance test calls `plan correct <plan_id> --mode revert --guidance "..."`, the plan_id is captured as `decision_id` and used directly as `target_decision_id` in `svc.request_correction()`. Since the plan_id is not a valid decision_id, the correction service cannot find the targeted decision. The same bug path is exercised by `--mode append`.
### How TDD Expected-Fail Works
- The `@tdd_expected_fail` tag causes the test framework to invert the result: the test passes CI when the underlying assertion fails (proving the bug exists) and fails CI if the assertion passes (bug was fixed without removing the tag).
- When bug #969 is fixed, the developer removes the `@tdd_expected_fail` tag, and the test runs normally as a regression guard.
### Quality Gates
All nox sessions pass:
- `nox -s lint` ✅
- `nox -s typecheck` ✅ (0 errors)
- `nox -s unit_tests` ✅ (387 features, 11121 scenarios, 0 failures)
- `nox -s integration_tests` ✅ (1561 tests, 0 failures)
- `nox -s e2e_tests` ✅ (16 tests, 0 failures)
- `nox -s coverage_report` ✅ (≥97%)
Closes#979
Reviewed-on: cleveragents/cleveragents-core#1051
Co-authored-by: Rui Hu <rui.hu@cleverthis.com>
Co-committed-by: Rui Hu <rui.hu@cleverthis.com>
The re-export of make_mock_scenario from features/mocks/__init__.py
caused ASV benchmark discovery to fail because tdd_test_helpers imports
behave.model.Status, which is unavailable in the ASV benchmark
virtualenv. All callers already import via the full module path
(features.mocks.tdd_test_helpers), so the re-export was unnecessary.
ISSUES CLOSED: #628
Implements the three-tag TDD bug-capture system in Robot Framework via a
Listener v3 module, paralleling the Behave implementation. Tests tagged
tdd_expected_fail that fail have their result inverted to PASS (bug still
exists); tests that unexpectedly pass are inverted to FAIL with guidance.
Addresses all 15 findings from code review (PR !673, reviewer hamza.khyari):
P2 fixes:
- Added idempotency guard (_processed_tests set) to prevent double-inversion
when the listener is loaded twice in the same process.
- Rewrote normal-test-unaffected check to run alongside a tdd_expected_fail
fixture in a single Robot invocation, proving the listener is loaded and
selectively applies rather than being a tautological pass.
P3 fixes:
- Added output.xml existence guard with clear diagnostics in _run_fixture.
- Documented intentional use of data.tags (static definition) vs result.tags
(runtime-modifiable) in end_test docstring.
- Added SKIP status test fixture and integration test case.
- Added message content assertion in cmd_expected_fail_inverted.
- Tightened substring assertions to match specific error text.
- Added tdd_expected_fail-alone fixture (both companions missing).
- Added close() hook to clear _validation_errors and _processed_tests.
- Simplified _run_fixture return type to tuple[str, str].
- Changed listener path resolution from CWD-relative to __file__-relative
in noxfile.py (integration_tests, slow_integration_tests, e2e_tests).
P4 fixes:
- Added __all__ declaration to helper module.
- Changed module docstring from "mirroring" to "paralleling".
- Added comment documenting accepted XML parsing risk (self-generated XML).
Additional fixes:
- Increased M4 E2E plan-tree test timeout from 30s to 120s (pre-existing
timeout failure unrelated to this feature).
Quality gates (post-rebase onto latest master):
- nox -s lint: PASS
- nox -s typecheck: PASS (0 errors)
- nox -s unit_tests: PASS (10,700 scenarios)
- nox -s integration_tests: PASS (1,505 tests)
- nox -s coverage_report: PASS (97.9% >= 97% threshold)
- nox -s benchmark: PASS
- nox -s docs: PASS
- nox -s build: PASS
- nox -s security_scan: PASS
- nox -s dead_code: PASS
ISSUES CLOSED: #628
Implements AuditEventSubscriber that subscribes to all 9 security-relevant
EventType members and persists redacted audit entries via AuditService.record().
Key components:
- AuditEventSubscriber: bridges EventBus and AuditService (SEC7)
- SECURITY_EVENT_MAP: maps EventType enum to audit type strings
- Redaction via redact_dict() on event details before persistence
- Graceful error handling: failures logged, never propagated
Post-review fixes applied:
- BUG-1: Remove dead correlation_id null-check guard (DomainEvent.correlation_id
is always non-None via ULID default_factory)
- SEC-2: Redact exception messages in warning logs via redact_value() to prevent
potential leakage of sensitive internal state (e.g. DB connection strings)
- PERF-3: Pre-generate unique DomainEvent instances in ASV benchmark setup to
avoid skew from reusing a single frozen object
- Wire event_bus from the DI container into CorrectionService (plan.py),
ConfigService (config.py, skill.py x2, server.py), and
PersistentSessionService (session.py) at their CLI construction sites.
Closes#581
Implement the UKOIndexer service that produces UKO triples from resources
using pluggable domain-specific analyzers, wraps each triple with provenance
metadata, and simultaneously indexes into text, vector, and graph backends.
Key design decisions and components:
- UKOIndexer orchestrates the full index lifecycle: add_resource,
update_resource (remove-then-add), remove_resource, and maintenance
triggers. Each operation fires lifecycle hooks (on_indexed, on_removed,
on_error) so callers can observe progress.
- Analyzer selection is pluggable via ContentAnalyzer protocol. The indexer
accepts a registry mapping resource types to analyzers. PythonAnalyzer
and MarkdownAnalyzer are provided as built-in implementations.
- LocationContentReader protocol abstracts file I/O with a base_dir
parameter for path-traversal prevention (post-resolve validation rejects
paths escaping the base directory and non-regular files).
- UKOTriple model includes a @model_validator ensuring at least one of
object_uri or object_value is populated, preventing empty triples at
construction time.
- Triple removal uses scoped deletion via uko:sourceResource predicate to
avoid shared-subject collision — only triples originating from the
specific resource are removed, not all triples for a shared subject.
- _resource_subjects.pop is deferred until after all backend removal
operations succeed, preventing inconsistent state on partial failure.
- analyzer.analyze() is wrapped in try/except so that analyzer errors
produce an IndexResult with error details rather than propagating
exceptions to callers.
- All lifecycle hook calls are guarded via _fire_on_indexed,
_fire_on_removed, and _fire_on_error helpers that catch and log hook
exceptions without disrupting the indexing pipeline.
- max_triples parameter (default 50,000) bounds analyzer output size to
prevent runaway resource consumption.
- ResourceFileWatcher monitors filesystem paths via watchdog and triggers
re-indexing callbacks on file changes with configurable debouncing.
Emits RESOURCE_MODIFIED domain events via EventBus when file changes
are detected. Debounce timers coalesce rapid edits into a single
callback invocation. Thread-safe design with daemon threads for clean
shutdown.
- SearchResult.__post_init__ validates score is in [0.0, 1.0], correctly
rejecting NaN values.
- Placeholder embedding uses [1.0] instead of [float(len(content))] to
avoid leaking content size information.
- isinstance check on graph_backend ensures GraphIndexBackend protocol
compliance at runtime.
- Test doubles extracted to features/mocks/uko_indexer_mocks.py for reuse
across BDD steps and Robot helpers.
Spec reference: Architecture > ACMS > Real-time Index Synchronization
(specification.md lines ~43205-43300).
ISSUES CLOSED: #578
ActorRegistry._actor_name() built names via f"{provider}/{model}", which
produced names with multiple slashes when providers included models
containing "/" (e.g. OpenRouter's "anthropic/claude-sonnet-4-20250514").
The resulting name violated the spec pattern ^[a-z0-9_-]+/[a-z0-9_-]+$
and triggered a ValidationError during actor upsert.
Now sanitises both provider and model components by replacing "/" with "-"
and lowercasing, so multi-slash provider models no longer break actor
listing.
Includes 6 Behave BDD regression scenarios (covering zero-provider,
multi-slash, consecutive-slash, leading-slash, and name-validation
cases), Robot Framework integration smoke tests, and ASV benchmarks.
ISSUES CLOSED: #592
Behave BDD scenarios (3) tagged @tdd_bug @tdd_bug_592 @tdd_expected_fail
exercise the real ActorRegistry._actor_name() code path with a provider
whose default model contains '/' separators. The tests assert correct
behaviour (exit 0, single-slash names, valid JSON) and fail while the
bug is present; the @tdd_expected_fail handler inverts results so CI
stays green.
Includes Robot Framework integration smoke tests (3), ASV benchmarks (3),
and a shared FakeProviderInfo/FakeProviderRegistry mock in
features/mocks/fake_provider.py.
ISSUES CLOSED: #634
Implemented lazy container activation for devcontainer-instance resources
with ContainerLifecycleState enum tracking six states (inactive, starting,
active, stopping, stopped, error) with validated transitions. Extended
DevcontainerHandler with devcontainer up CLI integration and JSON output
parsing for container start. Added periodic health checking via
devcontainer exec ping with configurable interval. Added agents resource
stop and agents resource rebuild CLI commands for manual lifecycle
control. Wired session close and plan completion hooks to automatic
container cleanup. Includes lifecycle state persistence in resource
registry with timestamped transitions. Added Behave BDD tests, Robot
integration tests, and ASV activation latency benchmarks.
- Added remoteWorkspaceFolder absolute-path validation
- Aligned spec: handler name, rebuild types, --yes flag on stop/rebuild
- Added registry re-read in stop_container success path for consistency
- Added session_id field to ContainerLifecycleTracker for scoped cleanup
- Scoped stop_all_active_containers to session_id when provided
- Wired _cleanup_devcontainers into fail_apply and fail_execute
- Wired start_health_check into activate_container success path
- Restructured facade session close to always run container cleanup
even without session service (F4)
- Re-read tracker from registry in activate_container success path
- Added evict_terminal_trackers to cap registry growth
- Updated devcontainer_resources.md: health check auto-start, scoped
cleanup hooks, known limitations for eviction and sandbox_strategy
- Wired evict_terminal_trackers into stop_all_active_containers so
terminal-state trackers are actually evicted in production
- Made stop_container idempotent: returns early when container is
already in a terminal state instead of raising ValueError
- Fixed benchmark health check thread leak in TimeActivationLatency
by clearing registry after each timing loop
- Added rebuild pass-through (--reset-container flag to devcontainer up)
- Added host_workspace_path field on ContainerLifecycleTracker so
health probes use the host-side path for devcontainer exec
- Wired lazy activation into DevcontainerHandler.resolve() for
devcontainer-instance resources in non-running states
- Changed _default_strategy from SNAPSHOT to NONE (container
itself provides isolation; SandboxFactory raises NotImplementedError
for snapshot)
- Restricted _STOPPABLE_TYPES to devcontainer-instance only
(container-instance is not directly stoppable via CLI)
ISSUES CLOSED: #514
Added minimal LSP server entrypoint supporting initialize/shutdown/exit
handshake over JSON-RPC stdin/stdout transport with Content-Length
framing. Unsupported methods return MethodNotFound error with descriptive
message. Wired LSP requests through ACP facade in local mode. Added
agents lsp serve CLI command with --log-level flag, PID output, and
startup banner. Created reference documentation for the stub server.
Includes Behave BDD tests for protocol handshake, Robot smoke test, and
ASV startup latency benchmark.
ISSUES CLOSED: #203
- Extract MockMCPTransport to features/mocks/mock_mcp_transport.py
- Use ToolRegistry type with TYPE_CHECKING in register_tools()
- Move transport config validation into __init__ via _validate_config()