Renamed all 11 task-type confidence threshold fields in AutomationProfile
from phase-transition semantics to spec-defined task-type semantics.
Updated all 8 built-in profiles, CLI formatting, YAML schema, services,
and all Behave/Robot tests referencing the old field names.
Post-review fixes:
- Fixed 24 stale old field names in M6 fixture files
(automation_profiles.json, autonomy_guardrails.json)
- Added model_validator(mode='before') to detect legacy field names
and raise actionable ValueError with rename mapping
- Added semantic bridge comments in PlanLifecycleService mapping
task-type thresholds to phase-transition gates
- Added threshold_field to structured log messages for observability
- Restored categorised CLI automation-profile show output to match
spec (Phase Transitions / Decision Automation / Self-Repair /
Execution Controls) instead of flat list
- Added missing access_network field to spec show output examples
(Rich, Plain, JSON, YAML variants)
- Aligned ADR-017 profile fields table to all 11 fields with
descriptions matching spec Automatable Tasks table
- Aligned automation_profiles.md threshold descriptions with spec
- Added spec section references in phase_reversion.md, error_recovery.md,
and plan_execute.md for field naming context
- Extended repository roundtrip test to assert all 11 threshold fields
- Fixed benchmark _make_profile() passing safety fields as top-level
kwargs instead of via SafetyProfile sub-model (incompatible with
extra="forbid")
- Aligned CLI JSON/YAML output structure for automation-profile show
with the specification grouped format (phase_transitions,
decision_automation, self_repair, execution_controls)
- Moved safety boolean fields into the Execution Controls section
of Rich output per spec examples
- Reverted auto profile description to "Fully automatic except apply"
per specification (line 16703, line 28406)
- Improved bridge comments in test steps with semantic context for
threshold-to-gate mappings
ISSUES CLOSED: #902
Modified auto_progress() to complete the Apply phase immediately after
transitioning from Execute to Apply, since Apply is a metadata transition
with no LLM processing. This ensures `plan execute` drives the plan to
the terminal `applied` state when the automation profile permits (ci,
full-auto profiles with auto_apply < 1.0).
Extracted `_complete_apply_if_queued()` helper that consolidates the
Apply-completion pattern (start_apply + complete_apply) into a single
method with error recovery (calls `fail_apply` on failure) and async-job
guard (skips inline completion when async execution is enabled to avoid
orphaning enqueued jobs). Used by `auto_progress()`,
`lifecycle_apply_plan()`, and `try_auto_run()`.
Added PlanLifecycleService.try_auto_run() that drives plans through all
lifecycle phases (strategize → execute → apply) when automation-profile
thresholds allow automatic progression. Each phase checks the profile's
auto_* threshold before proceeding; a threshold of 1.0 stops the plan at
that phase boundary for human approval.
Fixed `lifecycle-apply` CLI leaving plans stuck in `apply/queued` without
completing. The command now calls `_complete_apply_if_queued()` when the
plan is in Apply/queued, driving it to the terminal `applied` state.
Fixed stale RICH output in `lifecycle_apply_plan` that printed
"Plan is now in Apply phase (queued)" after the plan had already reached
terminal `applied` state; now branches on `plan.is_terminal`.
Additional fixes:
- SQLite UNIQUE constraint violation in LifecyclePlanRepository.update():
added session.flush() after clear() on child collections (project_links,
arguments, invariants) before re-inserting rows
- Added 'state' alias in _plan_spec_dict() JSON output for spec §Example 7
jq compatibility
- Updated plan execute and lifecycle-apply reference documentation
Refs: #753
## Summary
Adds the `--yes`/`-y` flag to the `lifecycle-apply` CLI command as required by the specification (`agents plan apply [--yes|-y] <PLAN_ID>`). Without `--yes`, a confirmation prompt now displays before proceeding with the destructive Apply phase. With `--yes`, the apply proceeds immediately without prompting.
Closes#932
## Changes
### Source Code
- **`src/cleveragents/cli/commands/plan.py`**: Added `yes: Annotated[bool, typer.Option("--yes", "-y", help="Skip confirmation prompt")] = False` parameter to `lifecycle_apply_plan`. Added `typer.confirm()` prompt before the apply operation, consistent with the pattern used by `rollback_plan`, `correct_plan`, and other destructive commands.
- Confirmation prompt text matches spec exactly: `"Apply changes for plan {plan_id}?"` producing `Apply changes for plan <ID>? [y/N]:`.
- Fixed redundant plan ID display when `pre_plan` is `None` — now shows `"Apply changes for plan X?"` instead of `"Apply plan X (X)?"`.
- Added `except ValueError` handler consistent with sibling commands `lifecycle_execute_plan` and `_lifecycle_apply_with_id`.
- Added `except Exception` catch-all handler with `isinstance(e, (typer.Abort, typer.Exit))` re-raise guard, consistent with `lifecycle_execute_plan`.
- Moved `PlanPhase` and `ProcessingState` imports to module level per CONTRIBUTING.md §Import Guidelines.
### TDD Tag Removal (Bug Fix Workflow)
- **`features/tdd_plan_apply_yes_flag.feature`**: Removed `@tdd_expected_fail` tag (leaving `@tdd_bug` and `@tdd_bug_932` as permanent regression guards).
- **`robot/tdd_plan_apply_yes_flag.robot`**: Removed `tdd_expected_fail` tag (leaving `tdd_bug` and `tdd_bug_932`).
### Test Updates
Updated all existing `lifecycle-apply` invocations across 17 test/benchmark files to pass `--yes`, since the new confirmation prompt would otherwise abort in non-interactive test environments:
- 9 Behave step definition files
- 3 Robot Framework helper scripts
- 2 Robot Framework e2e acceptance tests
- 3 ASV benchmark files (4 invocations: `cli_robot_flow_bench.py` ×2, `m1_sourcecode_smoke_bench.py` ×1, `plan_cli_smoke_bench.py` ×1)
### Confirmation Prompt Tests (New + Strengthened)
- **`features/tdd_plan_apply_yes_flag.feature`**: 5 scenarios total:
- `lifecycle-apply recognises the --yes long flag` — verifies flag acceptance, prompt suppression, exit code 0, and `apply_plan` was called
- `lifecycle-apply recognises the -y short flag` — same as above for short flag
- `lifecycle-apply without --yes prompts for confirmation and user declines` — verifies `"Apply cancelled."` message, `exit_code == 0`, and `apply_plan` was NOT called
- `lifecycle-apply without --yes prompts for confirmation and user accepts` — verifies prompt appears, `exit_code == 0`, and `apply_plan` was called
- `lifecycle-apply catches unexpected exceptions cleanly` — verifies `"Unexpected error"` output, no traceback leak, non-zero exit code (exercises the `except Exception` catch-all)
- **`features/steps/tdd_plan_apply_yes_flag_steps.py`**: Refactored step definitions:
- `_make_mock_plan` uses `PlanPhase` and `ProcessingState` enum types instead of raw strings
- `_make_mock_plan` uses `datetime.now(tz=UTC)` instead of timezone-naive `datetime.now()`
- Unified prompt suppression step handles both `--yes` and `-y` via parameterised step pattern
- Added `When` step for unexpected error scenario with `RuntimeError` side_effect
- Added `Then` step for non-zero exit code assertion
- **Feature/Robot documentation**: Updated stale descriptions that said "implementation does not accept --yes" to reflect the flag is now implemented.
### Documentation
- **`docs/reference/plan_cli.md`**: Updated `lifecycle-apply` section with:
- `### Synopsis` heading with code block
- `### Options` table listing `--yes/-y` and `--format/-f` flags
- `### Arguments` table listing `PLAN_ID`
- Matches the style used by other command sections in the same file
## Review Fixes (Cycle 3 — Luis's review)
| ID | Severity | Issue | Resolution |
|----|----------|-------|------------|
| M1 | Medium | `typer.Abort()` on user decline produces exit code 1 and redundant "Aborted." | Changed to `raise typer.Exit(0)` — consistent with `correct_decision` and legacy `apply` |
| M2 | Medium | Missing exit code assertion on decline scenario | Added `And the lifecycle-apply exit code should be 0` to the decline scenario |
| M3 | Medium | Spec compliance: "summary of pending changes" not implemented | Deferred — spec example shows summary *after* confirmation, not before; implementation matches spec. Ticket-vs-spec ambiguity noted. |
| L1 | Low | Missing `except Exception` catch-all handler | Added catch-all matching `lifecycle_execute_plan` pattern; re-raises `typer.Abort`/`typer.Exit` |
| L2 | Low | Documentation description not updated | Expanded description in `plan_cli.md` to explain confirmation prompt and `--yes` |
| L3 | Low | Dead code `is not None` guards | Removed both guards — `get_plan()` raises `NotFoundError`, never returns `None` |
| I1 | Info | Duplicate `PlanPhase` import | Hoisted import to top of `try` block, eliminating duplicate at old line 2087 |
| I2 | Info | `typer.confirm` without explicit `default=False` | Added `default=False` for consistency with sibling commands |
| L4 | Low | No test for `--yes` after positional arg | Not addressed — Typer/Click handles both orderings; low risk |
| L5 | Low | No test for auto-select + interactive prompt | Not addressed — separate concern outside ticket scope |
| I3 | Info | Robot helper only tests flag recognition | By design — noted as informational |
## Review Fixes (Cycle 4 — Self-QA)
| ID | Severity | Issue | Resolution |
|----|----------|-------|------------|
| Major-1 | Major | No test for `except Exception` catch-all handler | Added new scenario `"lifecycle-apply catches unexpected exceptions cleanly"` with `RuntimeError` side_effect; asserts `"Unexpected error"` output, no traceback, non-zero exit |
| Minor-2 | Minor | Missing `ValueError` handler inconsistent with siblings | Added `except ValueError as e:` with `"[red]Execution Error:[/red]"` before catch-all, matching `lifecycle_execute_plan` and `_lifecycle_apply_with_id` |
| Minor-3 | Minor | Flag scenarios don't verify `apply_plan` called | Added `And the lifecycle-apply should have called apply` to both `--yes` and `-y` scenarios |
| Minor-4 | Minor | Stale docstring in Robot helper references `tdd_expected_fail` inversion | Updated to reflect bug is fixed and tests serve as regression guards |
| Minor-5 | Minor | `plan_cli.md` lacks Options table for `lifecycle-apply` | Added Synopsis, Options, and Arguments sections matching sibling command style |
| Nit-6 | Nit | Duplicated step defs for `--yes` vs `-y` prompt suppression | Unified into single parameterised step `"the lifecycle-apply {flag} output should not contain the confirmation prompt"` |
| Nit-7 | Nit | `datetime.now()` timezone-naive | Changed to `datetime.now(tz=UTC)` |
| Nit-8 | Nit | `_make_mock_plan` params use `str` instead of enum types | Changed to `PlanPhase` and `ProcessingState` enum types |
## Review Fixes (Cycle 5 — Jeff's approval note)
| ID | Severity | Issue | Resolution |
|----|----------|-------|------------|
| Import-1 | Minor | `PlanPhase`/`ProcessingState` imports inside function body instead of module level | Moved to module-level import per CONTRIBUTING.md §Import Guidelines |
## Known Limitations / Deferred Items
- **M3: Ticket AC mentions "summary of pending changes"** but the spec example only shows `"Apply changes for plan <ID>? [y/N]: y"` without a change summary. The implementation shows plan ID only (matching the spec), not a change summary. This is a ticket-vs-spec ambiguity; recommend discussing with ticket author.
- **Legacy `apply` command** accepts `--yes` but does not pass it to `_lifecycle_apply_with_id()`. This is a pre-existing issue outside the scope of this ticket.
- **`pre_plan is None` branch** has no explicit test. Pre-existing architectural issue; no action taken.
## Quality Gates
| Gate | Result |
|------|--------|
| `nox -s lint` | ✅ passed |
| `nox -s typecheck` | ✅ passed (0 errors) |
| `nox -s unit_tests` | ✅ passed (471 features, 12,424 scenarios, 0 failures) |
| `nox -s integration_tests` | ✅ passed (1,727 tests, 0 failures) |
| `nox -s e2e_tests` | ✅ passed (41 tests, 0 failures) |
| `nox -s coverage_report` | ✅ passed (≥97% coverage) |
Reviewed-on: cleveragents/cleveragents-core#1127
Reviewed-by: Jeffrey Phillips Freeman <jeffrey.freeman@cleverthis.com>
Co-authored-by: Rui Hu <rui.hu@cleverthis.com>
Co-committed-by: Rui Hu <rui.hu@cleverthis.com>
The A2A facade now exposes 42 operations (31 extension + 11 legacy)
after the spec-aligned _cleveragents/ extension methods were added.
Update the BDD assertion and docs to match the actual count.
Address review-driven fixes across actor schema, preflight guardrails, docs/examples,
and Behave/Robot coverage: unify preflight warning behavior with shared role-warning logic,
resolve actor-name to config payloads in production preflight flow, harden response_format
validation/coercion edge cases, extract duplicated helper logic, and expand negative-path
test coverage. Also fix cross-scenario patcher leakage in step modules to eliminate
full-run-only coverage failures.
- Add `role_hint` and `response_format` support to actor schema.
- Add non-fatal estimation-role compatibility warnings in actor registration CLI flows.
- Add preflight warning path when `estimation` actor is missing `response_format`.
- Add `examples/actors/estimator.yaml` and update actor examples documentation/tests.
- Update integration helper expectations (m1/m2/m3/m6) for missing provider config in local test env.
ISSUES CLOSED: #650
Add 3 deferred virtual resource types (remote, submodule, symlink) with
equivalence metadata for physical-to-virtual resource linking.
Depends on: #662 (child_types reference types introduced by #662)
- Type definitions extracted to _resource_registry_virtual_deferred.py
for consistency with _resource_registry_virtual.py (#329)
- YAML configs with equivalence criteria per spec, spec reference comments
- Bootstrap registration via BUILTIN_TYPES spread, hidden from
resource add scaffolding (user_addable: false)
- Equivalence structural validation in ResourceTypeSpec model validator:
criteria must be a non-empty list of non-empty strings; virtual types
must have sandbox_strategy=none, user_addable=false, handler=None,
all capabilities false
- Behave tests (52 scenarios), Robot tests (7), ASV benchmarks
- DB roundtrip tests verifying virtual types survive bootstrap persistence
- Negative tests: missing equivalence/name/kind, manual add rejection
for all 3 virtual types (register_resource guard), invalid criteria
elements (non-string, empty string)
ISSUES CLOSED: #331
_get_plan_executor() created a second PlanLifecycleService Factory
instance with its own in-memory _plans cache. After the executor's
run_strategize() advanced the plan to execute/queued (via
auto_progress), the CLI handler's separate service instance returned
stale strategize/queued state from its cache, causing spurious
"Plan is not in an executable state" errors.
Fix: _get_plan_executor() now accepts an optional lifecycle_service
parameter; the plan execute handler passes its own service instance so
both share the same cache.
Also addressed review feedback:
- Improved type safety: lifecycle_service parameter typed as
PlanLifecycleService | None instead of Any | None.
- Added BDD regression test verifying the lifecycle service is shared
between the CLI handler and the executor.
- Updated reference documentation to reflect the type annotation change.
ISSUES CLOSED: #1026
Updated CI pipeline to inject ANTHROPIC_API_KEY and OPENAI_API_KEY
secrets as environment variables during Robot Framework integration
test execution. Added CI setup documentation.
ISSUES CLOSED: #701
Implement ContainerToolExecutor for delegating tool invocations to
devcontainer environments with full I/O forwarding. Add PathMapper for
bidirectional host/container path translation. Wire container routing
into ToolRunner with graceful fallback when no executor is configured.
Add container_metadata field to ToolInvocation for tracking execution
context.
New modules:
- tool/container_executor.py: ContainerToolExecutor, ContainerConfig,
ContainerMetadata, ContainerExecutionError, ContainerTimeoutError
- tool/path_mapper.py: PathMapper with host_to_container/container_to_host
Modified:
- tool/runner.py: container execution routing via ExecutionEnvironment
- domain/models/core/change.py: container_metadata on ToolInvocation
- tool/__init__.py: new public exports
Review fixes applied:
- Add Alembic migration m6_004 for container_metadata_json column
- Enforce _MAX_OUTPUT_BYTES (50 MiB) truncation in _run_command()
- Fix path traversal in sync_results_to_host (Path.is_relative_to)
- Allow spaces in _looks_like_path() for valid filesystem paths
- Preserve negative exit codes from signal kills in metadata
- Add default=str to json.dumps(invocation.arguments) safety net
- Log warnings when path mapping recursion depth exceeded
- Warn when devcontainer binary not found on PATH
- Use default allow_nan for host-path JSON validation in runner.py
(only container path requires RFC 7159 strict mode)
- Reject URL-like patterns in _looks_like_path() to avoid false
positives on API routes, protocol-relative URIs, and query strings
- Add extract_container_metadata() static helper on
ContainerToolExecutor as bridge for ToolInvocation wiring
- Use raw_stdout bytes in sync_results_to_host to prevent
binary file corruption from text-mode decode/re-encode
- Apply posixpath.normpath() in workspace_folder validator and
reject path components containing '..'
- Check result.timed_out in sync_results_to_host and raise
ContainerTimeoutError instead of always raising ContainerExecutionError
- Detect overlapping host_root/container_root in PathMapper and
raise ValueError to prevent corrupt bidirectional mappings
- Wrap host-side I/O in sync_results_to_host with try/except
OSError to produce ContainerExecutionError on write failure
- Enforce int(timeout) in _build_exec_command to prevent shell
injection via malicious objects with __str__ methods
- Change ToolResult validator from 'not self.error' to
'self.error is None' so empty-string errors are accepted
- Iterate required list directly in ToolRunner schema validation
to detect fields listed in required but absent from properties
Closes#515
Wire per-service retry policies and circuit breakers into the service
layer via ServiceRetryWiring, backed by ServiceRetryPolicyRegistry and
configurable through Settings environment variables.
Production hardening from code review:
- Fix TOCTOU race in CircuitBreaker._on_success (half-open state)
- Add half-open probe limit to prevent unbounded concurrent requests
- Track all exception types for circuit breaker failure counting
- Detect async callables wrapped in functools.partial and callable objects
- Enforce spec-compliant 2s minimum for linear backoff strategy
- Enforce 0.1s floor for fixed backoff strategy
- Add retry amplification guard via contextvars nesting depth tracking
- Cap total retry wall-clock time at 300s (MAX_RETRY_TOTAL_TIMEOUT)
- Sanitize exception messages in retry logs to prevent secret leakage
- Fix wrap_service_method TOCTOU by holding cache lock for full operation
- Deep-copy default policies to prevent cross-policy mutation
- Warn on unknown override keys in apply_overrides
- Guard apply_overrides against non-dict and deeply nested JSON values
- Read circuit breaker state under lock in is_circuit_open
- Catch RecursionError in JSON config parsing
- Add total_timeout + nesting guard to retry_service_operation decorator
- Extend secret sanitization to Authorization headers, private_key,
connection_string, and access_key patterns
- Enforce 0.1s floor on jitter backoff strategy
- Cache wait strategies per service in ServiceRetryWiring (M3)
- Reset failure_count to 0 when entering half-open from open (M6)
- Use cached _get_wait_strategy() in execute()/async_execute()
- Move circuit-open logging out of _on_failure lock scope to prevent
holding the lock during potentially slow I/O (F1)
- Pass total_timeout=MAX_RETRY_TOTAL_TIMEOUT to wrap_service_method
retry_service_operation call for consistency with execute() (F4)
- Capture failure_count into local variable inside lock scope before
logging outside the lock, preventing stale reads from concurrent
threads in CircuitBreaker.call() and async_call() (F1)
- Deep-copy module-level DEFAULT_DATABASE_RETRY and DEFAULT_CIRCUIT_BREAKER
in ServiceRetryPolicyRegistry.get() for auto-generated unknown service
policies, preventing shared mutable state corruption (F1)
- Unify CircuitBreaker to a single threading.Lock for sync and async (P1-1)
- Restore BaseException permit in half-open path to prevent permit leak (P1-6)
- Prevent CircuitBreakerOpen cascading into failure_count (S2)
- Protect all logger calls with contextlib.suppress (S3, S4)
- Replace time.time() with time.monotonic() for monotonic timing (S5)
- Add distinct log events for half-open and closed transitions (S11, S12)
- Track pre-existing services so second apply_settings_defaults only
targets newly registered services (P1-2)
- Lazy circuit breaker creation via _get_or_create_cb() (P1-3)
- Reject async callables in sync execute() with TypeError (P1-5)
- Strengthen retry predicate to retry_if_exception_type(Exception) &
retry_if_not_exception_type(CircuitBreakerOpen) (S1)
- Add lock on _get_wait_strategy cache access (P2-16)
- Truncate raw JSON to 80 chars in override warning (P2-17)
- Warn on non-dict JSON overrides (P2-29)
- Debug log for nesting guard bypass (S13)
- Deep-copy from get() and all_policies() in registry (P1-4)
- Thread-safe registry with threading.Lock (P2-15)
- Robust exception handling in apply_overrides get() (P2-18)
- Log ValidationError details on override failure (P2-19)
- Sanitize service_name via _safe_service_name() (P2-28)
- Warn on non-dict sub-key values in overrides (P2-30)
- Allowlist for is_read_only_plan_operation phases (P2-10)
- Cap retry_auto_debug sleep at 60s (P2-11)
- Use is-not-None instead of falsy checks for error values (P2-12)
- Extend secret regex with bearer, session_id, auth_token,
refresh_token, client_secret patterns (P2-25)
- Pre-truncate error messages to 2000 chars before regex (P2-26)
- Add upper bounds on retry Settings fields (P2-7)
- Add cross-field validator max_delay >= base_delay (P2-8)
- Case-insensitive backoff strategy validation (P2-21)
- Add half_open_max_successes setting (S10)
- Remove phantom ContextFragment from services __all__ (ImportError fix)
- Export ServiceRetryWiring from application.services package
- Include sanitised error context in TypeError logging fallback
- Initialise RetryContext.attempt_count to 1 for bare context-manager usage
- Introduce CircuitBreakerState StrEnum replacing raw string literals
- Fix vacuous CircuitBreakerOpen propagation assertions in BDD steps
- Replace tautological logging test with structlog capture verification
- Assert circuit breaker existence instead of silently skipping on None
- Add Unicode control-char rejection validator to ServiceRetryPolicy.service_name
- Add name parameter with service= in all log calls
- Add extra="forbid" to all 3 Pydantic models
- Deep-copy _SERVICE_DEFAULTS construction
- Key normalisation (.strip()) in get() and apply_overrides()
- Add cooldown <= recovery_timeout validator
- Async guard on RetryContext.execute()
- Nesting guard on RetryContext.execute()/async_execute()
- stop_after_delay(300.0) on RetryContext
- retry_auto_debug async-only guard, dict result fix, sleep guard
- Retry-attempt logging in RetryContext
- Module-level docs for contextlib.suppress(TypeError) rationale
- Exhaustion log on retry failure
- Startup log in __init__; name=service_name to CircuitBreaker
- log_after_retry guarded to not fire on first-attempt success
- get_retry_decorator now includes logging callbacks
- Changed retry_backoff_strategy from str to RetryStrategy StrEnum
Closes#313
Restructure the TUI specification from a nested subsection under
"Behavior > UI / Interaction Model" to a top-level "## TUI" section,
giving it equal standing with Core Concepts, Behavior, Configuration,
and Architecture. All headings within the TUI section are promoted one
level (#### → ###, ##### → ####).
Fix mkdocs build nav warnings by adding ADR-044/045/046 to the
mkdocs.yml nav and registering three missing reference pages
(acms_fusion, context_indexing, resource_type_inheritance) in the
gen_ref_pages.py nav builder.
Add the complete TUI (Terminal User Interface) specification section to
docs/specification.md, covering architecture, layout, navigation, persona
system, reference picker, slash commands, shell mode, session management,
and keyboard shortcuts. The section includes 18 HTML-styled ASCII mockup
diagrams using the Dracula color palette that render with actual colors
in MkDocs.
Key specification areas:
- MainScreen layout with right-side collapsible sidebar (3 states:
hidden, visible, fullscreen cycled by shift+tab)
- Direct-to-chat launch with first-run actor selection overlay
- Persona system bundling actor + arguments + project/plan scope,
cycled with tab; argument presets cycled with ctrl+tab
- @ reference picker with fuzzy search for projects/plans/resources
- / slash commands mirroring CLI noun:verb patterns (session:new,
plan:list, project:show, actor:switch, etc.)
- ! shell mode for direct command execution
- Multi-session tabs with independent personas and conversations
- Escape-cascading navigation back to main screen
- Plan detail view with decision tree navigation
- Project detail view with resource DAG
- Permission approval workflow with diff view
- Complete keyboard shortcut reference table
New ADR files:
- ADR-044: TUI Architecture and Framework (Textual >= 1.0)
- ADR-045: TUI Persona System
- ADR-046: TUI Reference and Command System (CLI-aligned commands)
Also adds TUI mockup CSS classes to docs/stylesheets/extra.css for
Dracula-palette diagram rendering (.tui-primary, .tui-secondary,
.tui-success, .tui-warning, .tui-error, .tui-dim, .tui-bold,
.tui-bold-primary, .tui-bold-warning, .tui-primary-u, .tui-rainbow).
Temporal metadata fields (validFrom, validUntil, isCurrent, isRevisionOf)
on UKO InformationUnit nodes enable revision chain tracking: when code
changes, old nodes are marked historical and new revision nodes are
created with back-links. Three storage tiers (hot/warm/cold) filter
nodes by temporal scope (current/recent/all) with configurable retention
(warm_retention_hours default 24h, cold_retention_days default 90d).
Includes TemporalMetadata, TemporalNode, RevisionChain, TierQueryResult,
TierRetentionConfig frozen domain models, TemporalBackend protocol,
InMemoryTemporalBackend stub, TemporalService with structlog and DI,
BackendSet.temporal typing upgrade from object|None to TemporalBackend|None.
67 Behave scenarios, 8 Robot Framework tests, ASV benchmarks, and
reference documentation.
ISSUES CLOSED: #577
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
Three-pronged fix for intermittent pabot-parallel race condition in
M4 validation integration tests:
1. Composable Setup Database Isolation keyword in common.resource
gives each suite a unique CLEVERAGENTS_DATABASE_URL so concurrent
pabot workers never contend on the same SQLite file.
2. Per-suite CLEVERAGENTS_HOME directories prevent shared temp
directory cleanup from racing between workers.
3. Centralised reset_global_state() in robot/helpers_common.py clears
Settings singleton, DI container, provider registry, and engine
cache between chained CLI invocations in helper processes.
Also:
- Setup Test Environment now accepts optional mock_ai and
auto_apply_migrations arguments (default TRUE) for backward
compatibility while allowing suites to opt out.
- Added Suite Teardown to cli_plan_context_commands.robot.
- Fixed _COMMANDS typing in two helpers to eliminate type: ignore.
- Updated docs/development/testing.md to reflect helpers_common
delegation pattern.
- Added timeout=30s to all Run Process calls in
m4_e2e_verification.robot.
Fixes: #563
Implement StrategyCoordinator and FusionEngine as named facades over
the existing ACMS pipeline components, providing clean public APIs
for parallel strategy execution with proportional budget allocation
and fragment fusion with dedup/conflict resolution/knapsack packing.
Key changes:
- Add StrategyCoordinator with parallel execution and confidence-based budget allocation
- Add FusionEngine with URI+hash dedup, max-depth conflict resolution, greedy knapsack packing
- Add budget overage guard with lowest-relevance fragment dropping
- Add per-strategy max caps enforcement
- Wire into existing ContextAssemblyPipeline
- Add Behave BDD tests, Robot integration tests, ASV benchmarks
- Add docs/reference/acms_fusion.md
ISSUES CLOSED: #192
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
Add RepoIndexingService with incremental refresh, language detection,
SHA-256 hashing, and policy enforcement. Includes domain models,
DB persistence, DI wiring, Behave/Robot/ASV tests, and reference docs.
ISSUES CLOSED: #195