Commit Graph

10 Commits

Author SHA1 Message Date
freemo a0df5a4cd0 fix(cli): wrap format_output() in spec-required JSON/YAML envelope across all CLI commands
Implements the spec-required JSON/YAML output envelope for all CLI commands
that use format_output(). The envelope structure is:

  {
    "command": "<command that was run>",
    "status": "ok" | "warn" | "error",
    "exit_code": 0,
    "data": { ... command-specific payload ... },
    "timing": { "duration_ms": 123 },
    "messages": [{ "level": "ok", "text": "..." }]
  }

Changes:
- Add _build_envelope() helper to construct the spec-required envelope
- Add optional command, status, exit_code, messages parameters to format_output()
- Wrap json/yaml output in the envelope; plain/table/rich/color unchanged
- Add timing measurement (duration_ms) to all json/yaml outputs
- Add new BDD feature file (cli_json_envelope.feature) with 14 scenarios
  testing envelope field presence, values, and data payload
- Update 14 existing step files to unwrap the envelope when checking
  specific data keys (backward-compatible via _unwrap_envelope() helper)

Closes #3431
2026-04-05 19:48:40 +00:00
freemo 48cff5cfe0 refactor(cli): rename plan lifecycle-list and lifecycle-apply to match specification
Renames `plan lifecycle-list` to `plan list` and `plan lifecycle-apply` to `plan apply` to align with the specification's canonical command names. Removes legacy V2 plan commands that occupied those names.

- Renamed CLI command registrations from lifecycle-list/lifecycle-apply to list/apply
- Removed legacy V2 apply and list commands (~200 lines)
- Updated apply shortcut in main.py to delegate to v3 lifecycle
- Added defensive null check for plan existence in apply command
- Updated 63+ test, doc, and benchmark files for consistency

Closes #881

Co-authored-by: Jeffrey Phillips Freeman <the@jeffreyfreeman.me>
Co-committed-by: Jeffrey Phillips Freeman <the@jeffreyfreeman.me>
2026-04-02 19:09:04 +00:00
freemo 38e05ac45a feat(correction): wire checkpoint rollback into correction service revert flow
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
2026-03-30 17:06:49 -04:00
hurui200320 414abb1396 fix(cli): add missing --yes flag to plan apply command (#1127)
## 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>
2026-03-26 07:50:09 +00:00
brent.edwards 60af2cae0c fix(cli): make plan explain accept plan_id for plan-level explanation (#1057)
## Summary

Fixes `plan explain` to accept both decision IDs and plan IDs as the positional argument, matching the M3 acceptance test usage pattern `plan explain <plan_id>`.

### Problem
`explain_decision_cmd` only accepted a decision ULID. When the M3 acceptance test passed a plan ID, `svc.get_decision(plan_id)` returned `None`, causing "Decision not found" error with exit code 1.

### Fix
1. Renamed parameter `decision_id` → `identifier`
2. Tries `svc.get_decision(identifier)` first (backward compat)
3. Falls back to `svc.list_decisions(identifier)` treating it as a plan_id, explaining the root decision
4. Clear error if neither resolves

### Quality Gates
| Session | Result |
|---------|--------|
| `nox -s lint` | PASS |
| `nox -s typecheck` | PASS (0 errors) |
| `nox -s unit_tests` (explain features) | 46/46 PASS |

Closes #968

Reviewed-on: cleveragents/cleveragents-core#1057
Co-authored-by: Brent Edwards <brent.edwards@cleverthis.com>
Co-committed-by: Brent Edwards <brent.edwards@cleverthis.com>
2026-03-19 22:10:23 +00:00
Brent E. Edwards 5e625b22e1 fix(test): convert M1-M6 E2E suites to real subprocess CLI invocations (closes #658)
Replace CliRunner + unittest.mock.patch with subprocess.run for all
21 CLI-facing test functions across the M1-M6 E2E verification helpers.

Application code fixes:
- action.py: _get_lifecycle_service() uses container.plan_lifecycle_service()
- plan.py: _get_lifecycle_service() uses container.plan_lifecycle_service()
- plan.py: three container.resolve(DecisionService) → container.decision_service()

Test infrastructure:
- New robot/helper_e2e_common.py with shared subprocess utilities
  (run_cli, setup_workspace with DB migrations, cleanup_workspace)
- M1-M4, M6 helpers refactored to use run_cli() with real SQLite DB
- M5 unchanged (0 CLI tests, all domain-level)
- TDD detection updated to recognise run_cli() as subprocess invocation
- Remove @tdd_expected_fail from TDD feature + robot tags
- Update 8 Behave step files that mocked container.resolve() to use
  container.decision_service() / container.plan_lifecycle_service()
2026-03-12 20:42:14 +00:00
freemo 61fc70d84b fix(cli): pass decision tree and influence edges to CorrectionService in plan correct handler (#639)
## Summary

Fixes the `plan correct` CLI handler which never passes the decision tree or influence edges to `CorrectionService`, producing single-node impact analysis regardless of the plan's actual decision structure.

Closes #606

## Root Cause

The `correct_decision()` handler in `plan.py` created a bare `CorrectionService()` and called `analyze_impact()` and `execute_correction()` without passing `decision_tree` or `influence_edges`. Both parameters default to `None` -> empty dicts, causing `_compute_affected_subtree()` BFS to return only the single target decision, ignoring all descendants and influence-DAG dependents.

## Changes

### Production Fix (`src/cleveragents/cli/commands/plan.py`)
- Resolve `DecisionService` via `get_container()` (following the pattern used by `plan explain` and `plan tree`)
- Build structural tree adjacency list from `decision_svc.list_decisions(plan_id)` using `parent_decision_id` relationships
- Fetch influence edges from `decision_svc.get_influence_edges(plan_id)`
- Pass both `decision_tree` and `influence_edges` to `svc.analyze_impact()` and `svc.execute_correction()`

### Existing Test Fixups
- Updated 4 step definition files that mock `CorrectionService` to also mock the new `DecisionService` resolution path

### New Tests
- **Behave BDD**: 3 scenarios verifying tree/edge forwarding (dry-run subtree, execution subtree, leaf node)
- **Robot Framework**: 3 integration smoke tests
- **ASV Benchmark**: Tree building and analyze_impact overhead benchmarks

## Quality Gates

- `nox -s lint` — PASSED
- `nox -s typecheck` — 0 errors
- `nox -s unit_tests` — 9,109 scenarios, 0 failures
- `nox -s coverage_report` — 97%

ISSUES CLOSED: #606

Reviewed-on: cleveragents/cleveragents-core#639
Co-authored-by: Jeffrey Phillips Freeman <jeffrey.freeman@cleverthis.com>
Co-committed-by: Jeffrey Phillips Freeman <jeffrey.freeman@cleverthis.com>
2026-03-08 23:28:05 +00:00
khyari hamza 0e36755db9 fix(service): address review findings for decision service
- Rehydrate sequence counter from DB on restart (BUG-1)
- Add uniqueness guard raising SequenceConflictError (BUG-2)
- Fix delete_decision consistency between persisted/in-memory (BUG-3)
- Validate new_decision_id exists in mark_superseded (BUG-4)
- Include orphaned subtrees in get_tree output (BUG-5)
- Remove dead _decision_seq/_next_seq from plan_lifecycle (BUG-7)
- Export SequenceConflictError from services __init__ (SPEC-2)
- Replace list[Any] with list[ArtifactRef] typing (SPEC-4)
- Add actor_reasoning max_length validation (SEC-1)
- Use SELECT COUNT(*) in count_decisions (PERF-1)
- Replace MagicMock with create_autospec(Settings) (TEST-3)
- Eliminate UnitOfWork.__new__() anti-pattern (TEST-4)
- Use exact assertion counts in DI tests (TEST-5)
- Add restart rehydration, BFS order, invalid type, and
  confidence boundary test scenarios (TEST-1/2/6/7)
- Fix decision_service_coverage.feature to match actual API

ISSUES CLOSED: #172
2026-03-03 12:53:28 +00:00
khyari hamza f66cb8d68e fix(cli): address review findings for plan explain and tree commands
- Remove global _PLAN_ID; generate per-step plan IDs on context (#8)
- Fix sham orphan test; build children_map from all decisions (#1)
- Delete dead constants; use _PATCH_RESUME_SVC_MOD in resume steps (#2)
- Remove dead _resolve_active_plan_id mock from _invoke_correct (#5)
- Make --mode/--guidance required Typer options (#3)
- Show alternatives by default; remove --show-alternatives flag (#4)
- Strengthen weak assertions on depth-limit and show-superseded (#6)
- Add negative assertions for error type conflation (#7)

ISSUES CLOSED: #174
2026-03-03 12:10:25 +00:00
khyari hamza 1524f8d25f test(cli): add 32 behave scenarios to close plan.py diff-coverage gap
Cover CLI-level code paths for plan explain, tree, correct, resume,
revert, and read-only guards that were unreached by existing unit tests.
Raises plan.py diff-coverage from 69% to 99.7% (1 line remaining).
2026-03-03 12:10:25 +00:00