Feature: Consolidated Plan Misc
  Combined scenarios from: plan_apply_service_coverage, plan_cli_legacy_r2, plan_cli_spec_print_r2, plan_execute_runtime

  # ============================================================
  # Originally from: plan_apply_service_coverage.feature
  # Feature: PlanApplyService full coverage
  # ============================================================

  Scenario: operation label returns correct label for "create"
    When pas_cov I call _operation_label with "create"
    Then pas_cov the label should be "new file"


  Scenario: operation label returns correct label for "modify"
    When pas_cov I call _operation_label with "modify"
    Then pas_cov the label should be "modified"


  Scenario: operation label returns correct label for "delete"
    When pas_cov I call _operation_label with "delete"
    Then pas_cov the label should be "deleted"


  Scenario: operation label returns correct label for "rename"
    When pas_cov I call _operation_label with "rename"
    Then pas_cov the label should be "renamed"


  Scenario: operation label returns the raw op for unknown operations
    When pas_cov I call _operation_label with "unknown_op"
    Then pas_cov the label should be "unknown_op"

    # ======================================================================
    # Helper function coverage: _render_diff_plain
    # ======================================================================


  Scenario: render diff plain with empty changeset
    Given pas_cov a SpecChangeSet with no entries
    When pas_cov I render diff plain
    Then pas_cov the plain diff should be "No changes in changeset."


  Scenario: render diff plain with entries having both hashes
    Given pas_cov a SpecChangeSet with a modify entry having both hashes
    When pas_cov I render diff plain
    Then pas_cov the plain diff should contain "ChangeSet:"
    And pas_cov the plain diff should contain "Total changes: 1"
    And pas_cov the plain diff should contain "--- a/src/app.py"
    And pas_cov the plain diff should contain "+++ b/src/app.py"
    And pas_cov the plain diff should contain "@@ modified @@"
    And pas_cov the plain diff should contain "- hash: abcdef012345..."
    And pas_cov the plain diff should contain "+ hash: 123456abcdef..."


  Scenario: render diff plain with create entry missing before_hash
    Given pas_cov a SpecChangeSet with a create entry having only after_hash
    When pas_cov I render diff plain
    Then pas_cov the plain diff should contain "@@ new file @@"
    And pas_cov the plain diff should contain "+ hash:"
    And pas_cov the plain diff should not contain "- hash:"


  Scenario: render diff plain with delete entry missing after_hash
    Given pas_cov a SpecChangeSet with a delete entry having only before_hash
    When pas_cov I render diff plain
    Then pas_cov the plain diff should contain "@@ deleted @@"
    And pas_cov the plain diff should contain "- hash:"
    And pas_cov the plain diff should not contain "+ hash:"

    # ======================================================================
    # Helper function coverage: _render_diff_rich
    # ======================================================================


  Scenario: render diff rich with empty changeset
    Given pas_cov a SpecChangeSet with no entries
    When pas_cov I render diff rich
    Then pas_cov the rich diff should contain "[yellow]No changes in changeset.[/yellow]"


  Scenario: render diff rich with known operation colors
    Given pas_cov a SpecChangeSet with a modify entry having both hashes
    When pas_cov I render diff rich
    Then pas_cov the rich diff should contain "[yellow]modified[/yellow]"
    And pas_cov the rich diff should contain "[bold]ChangeSet:[/bold]"
    And pas_cov the rich diff should contain "[bold]Total changes:[/bold] 1"
    And pas_cov the rich diff should contain "[red]- abcdef012345...[/red]"
    And pas_cov the rich diff should contain "[green]+ 123456abcdef...[/green]"


  Scenario: render diff rich with unknown operation uses white color
    Given pas_cov a SpecChangeSet with an entry having operation "rename"
    When pas_cov I render diff rich
    Then pas_cov the rich diff should contain "[cyan]renamed[/cyan]"


  Scenario: render diff rich with create entry hides before_hash
    Given pas_cov a SpecChangeSet with a create entry having only after_hash
    When pas_cov I render diff rich
    Then pas_cov the rich diff should contain "[green]"
    And pas_cov the rich diff should not contain "[red]- "

    # ======================================================================
    # Helper function coverage: _render_diff_json
    # ======================================================================


  Scenario: render diff json returns correct structure
    Given pas_cov a SpecChangeSet with a modify entry having both hashes
    When pas_cov I render diff json
    Then pas_cov the json diff should have key "changeset_id"
    And pas_cov the json diff should have key "plan_id"
    And pas_cov the json diff should have key "total_changes" with value 1
    And pas_cov the json diff should have key "summary"
    And pas_cov the json diff should have key "entries"
    And pas_cov the json diff entries should have length 1


  Scenario: render diff json entry contains all expected fields
    Given pas_cov a SpecChangeSet with a modify entry having both hashes
    When pas_cov I render diff json
    Then pas_cov the json diff first entry should have "path" equal to "src/app.py"
    And pas_cov the json diff first entry should have "operation" equal to "modify"
    And pas_cov the json diff first entry should have key "timestamp"
    And pas_cov the json diff first entry should have key "resource_id"
    And pas_cov the json diff first entry should have key "tool_name"

    # ======================================================================
    # Helper function coverage: _build_artifacts_dict
    # ======================================================================


  Scenario: build artifacts dict with changeset present
    Given pas_cov a mock plan with changeset_id "CS001"
    And pas_cov a SpecChangeSet with a modify entry having both hashes
    When pas_cov I build artifacts dict
    Then pas_cov the artifacts dict should have key "plan_id"
    And pas_cov the artifacts dict should have key "changeset_summary"
    And pas_cov the artifacts dict files_changed list should have length 1


  Scenario: build artifacts dict with no changeset
    Given pas_cov a mock plan with changeset_id "CS001"
    When pas_cov I build artifacts dict with no changeset
    Then pas_cov the artifacts dict changeset_summary should be null
    And pas_cov the artifacts dict files_changed list should have length 0


  Scenario: build artifacts dict with validation summary
    Given pas_cov a mock plan with changeset_id "CS001" and validation summary
    When pas_cov I build artifacts dict with no changeset
    Then pas_cov the artifacts dict should have key "validation_summary"


  Scenario: build artifacts dict with apply summary in error_details
    Given pas_cov a mock plan with apply summary in error_details
    When pas_cov I build artifacts dict with no changeset
    Then pas_cov the artifacts dict should have key "apply_summary"
    And pas_cov the artifacts dict apply_summary files_changed should be "5"

    # ======================================================================
    # Service initialization
    # ======================================================================


  Scenario: PlanApplyService raises ValidationError when lifecycle is None
    When pas_cov I create PlanApplyService with None lifecycle service
    Then pas_cov a ValidationError should be raised


  Scenario: PlanApplyService initializes with valid lifecycle and no store
    Given pas_cov a mock lifecycle service
    When pas_cov I create PlanApplyService with valid lifecycle and no store
    Then pas_cov the service should be created successfully


  Scenario: PlanApplyService initializes with valid lifecycle and a store
    Given pas_cov a mock lifecycle service
    And pas_cov a mock changeset store
    When pas_cov I create PlanApplyService with valid lifecycle and store
    Then pas_cov the service should be created successfully

    # ======================================================================
    # diff method
    # ======================================================================


  Scenario: diff raises PlanError when plan has no changeset
    Given pas_cov a service with a plan that has no changeset_id
    When pas_cov I call diff on the service
    Then pas_cov a PlanError should be raised with message containing "no ChangeSet"


  Scenario: diff returns rich format by default
    Given pas_cov a service with a plan that has a changeset with entries
    When pas_cov I call diff with format "rich"
    Then pas_cov the diff result should contain "[bold]ChangeSet:[/bold]"


  Scenario: diff returns plain format
    Given pas_cov a service with a plan that has a changeset with entries
    When pas_cov I call diff with format "plain"
    Then pas_cov the diff result should contain "ChangeSet:"
    And pas_cov the diff result should not contain "[bold]"


  Scenario: diff returns json format
    Given pas_cov a service with a plan that has a changeset with entries
    When pas_cov I call diff with format "json"
    Then pas_cov the diff result should be valid JSON


  Scenario: diff returns yaml format
    Given pas_cov a service with a plan that has a changeset with entries
    When pas_cov I call diff with format "yaml"
    Then pas_cov the diff result should contain "changeset_id:"

    # ======================================================================
    # artifacts method
    # ======================================================================


  Scenario: artifacts returns json format output
    Given pas_cov a service with a plan that has a changeset with entries
    When pas_cov I call artifacts with format "json"
    Then pas_cov the artifacts result should contain "plan_id"


  Scenario: artifacts returns plain format output
    Given pas_cov a service with a plan that has a changeset with entries
    When pas_cov I call artifacts with format "plain"
    Then pas_cov the artifacts result should be a non-empty string


  Scenario: artifacts falls back to json for rich format
    Given pas_cov a service with a plan that has a changeset with entries
    When pas_cov I call artifacts with format "rich"
    Then pas_cov the artifacts result should contain "plan_id"

    # ======================================================================
    # persist_apply_summary
    # ======================================================================


  Scenario: persist apply summary stores metadata on plan with no prior error_details
    Given pas_cov a service with a plan that has no error_details
    When pas_cov I persist apply summary with 3 files and 2 validations
    Then pas_cov the plan error_details should contain "apply_files_changed" with value "3"
    And pas_cov the plan error_details should contain "apply_validations_run" with value "2"
    And pas_cov the plan error_details should contain key "apply_completed_at"
    And pas_cov the lifecycle _commit_plan should have been called


  Scenario: persist apply summary preserves existing error_details
    Given pas_cov a service with a plan that has existing error_details
    When pas_cov I persist apply summary with 1 files and 0 validations
    Then pas_cov the plan error_details should contain "existing_key" with value "existing_value"
    And pas_cov the plan error_details should contain "apply_files_changed" with value "1"

    # ======================================================================
    # handle_merge_failure
    # ======================================================================


  Scenario: handle merge failure stores conflict details with no prior error_details
    Given pas_cov a service with a plan that has no error_details
    When pas_cov I handle merge failure with conflict "file.py has conflicts"
    Then pas_cov the lifecycle fail_apply should have been called
    And pas_cov the returned plan should reflect errored state


  Scenario: handle merge failure preserves existing error_details
    Given pas_cov a service with a plan that has existing error_details
    When pas_cov I handle merge failure with conflict "merge.txt conflict"
    Then pas_cov the committed plan error_details should contain "existing_key"
    And pas_cov the committed plan error_details should contain "merge_conflict"
    And pas_cov the committed plan error_details should contain "sandbox_rollback"

    # ======================================================================
    # guard_empty_changeset
    # ======================================================================


  Scenario: guard passes when changeset has entries
    Given pas_cov a service with a plan that has a changeset with entries
    When pas_cov I call guard_empty_changeset
    Then pas_cov the guard should return true


  Scenario: guard passes when changeset is empty but allow_empty is true
    Given pas_cov a service with a plan that has an empty changeset
    When pas_cov I call guard_empty_changeset with allow_empty true
    Then pas_cov the guard should return true


  Scenario: guard raises PlanError when changeset is empty
    Given pas_cov a service with a plan that has an empty changeset
    When pas_cov I call guard_empty_changeset
    Then pas_cov a PlanError should be raised with message containing "empty ChangeSet"


  Scenario: guard raises PlanError when changeset is None
    Given pas_cov a service with a plan that has no changeset_id
    When pas_cov I call guard_empty_changeset
    Then pas_cov a PlanError should be raised with message containing "empty ChangeSet"

    # ======================================================================
    # _extract_validation_counts
    # ======================================================================


  Scenario: extract validation counts from None returns zeros
    When pas_cov I extract validation counts from None
    Then pas_cov the counts should be 0 passed 0 failed 0 total


  Scenario: extract validation counts from full summary
    When pas_cov I extract validation counts from full summary 3 passed 1 failed 6 total
    Then pas_cov the counts should be 3 passed 1 failed 6 total


  Scenario: extract validation counts computes total when missing
    When pas_cov I extract validation counts from partial summary 2 passed 1 failed
    Then pas_cov the counts should be 2 passed 1 failed 3 total

    # ======================================================================
    # _resolve_changeset
    # ======================================================================


  Scenario: resolve changeset returns None when plan has no changeset_id
    Given pas_cov a service with a plan that has no changeset_id
    When pas_cov I call _resolve_changeset
    Then pas_cov the resolved changeset should be None


  Scenario: resolve changeset returns from store when available
    Given pas_cov a service with a plan that has changeset_id and store returns a changeset
    When pas_cov I call _resolve_changeset
    Then pas_cov the resolved changeset should have entries


  Scenario: resolve changeset falls back to empty stub when store returns None
    Given pas_cov a service with a plan that has changeset_id but store returns None
    When pas_cov I call _resolve_changeset
    Then pas_cov the resolved changeset should have no entries
    And pas_cov the resolved changeset should have the plan changeset_id


  Scenario: resolve changeset falls back to empty stub when no store configured
    Given pas_cov a service with a plan that has changeset_id and no store
    When pas_cov I call _resolve_changeset
    Then pas_cov the resolved changeset should have no entries

    # ======================================================================
    # cleanup_changeset
    # ======================================================================


  Scenario: cleanup changeset raises ValidationError for empty plan_id
    Given pas_cov a service with a mock lifecycle
    When pas_cov I call cleanup_changeset with empty plan_id
    Then pas_cov a ValidationError should be raised


  Scenario: cleanup changeset delegates to store delete_for_plan
    Given pas_cov a service with a store that has delete_for_plan returning 5
    When pas_cov I call cleanup_changeset with plan_id "01PLANTEST000000000000001"
    Then pas_cov cleanup should return 5


  Scenario: cleanup changeset returns 0 when store is None
    Given pas_cov a service with no changeset store
    When pas_cov I call cleanup_changeset with plan_id "01PLANTEST000000000000001"
    Then pas_cov cleanup should return 0


  Scenario: cleanup changeset returns 0 when store has no delete_for_plan
    Given pas_cov a service with a store that lacks delete_for_plan
    When pas_cov I call cleanup_changeset with plan_id "01PLANTEST000000000000001"
    Then pas_cov cleanup should return 0

    # ======================================================================
    # apply_with_validation_gate: constrain_apply raises exception branch
    # ======================================================================


  Scenario: apply with validation gate handles constrain_apply exception
    Given pas_cov a service where constrain_apply raises PlanError
    And pas_cov the plan has failed validations
    When pas_cov I call apply_with_validation_gate
    Then pas_cov the result outcome should be "constrained"
    And pas_cov the result message should contain "Apply refused"

    # ======================================================================
    # apply_with_validation_gate: complete_apply raises exception branch
    # ======================================================================


  Scenario: apply with validation gate handles complete_apply exception
    Given pas_cov a service where complete_apply raises PlanError
    And pas_cov the plan has passing validations and entries
    When pas_cov I call apply_with_validation_gate
    Then pas_cov the result outcome should be "applied"
    And pas_cov the result message should contain "applied successfully"

    # ======================================================================
    # ApplyOutcome and ApplyResult model
    # ======================================================================


  Scenario: ApplyOutcome enum has all expected values
    Then pas_cov ApplyOutcome should have value "applied"
    And pas_cov ApplyOutcome should have value "constrained"
    And pas_cov ApplyOutcome should have value "already_applied"
    And pas_cov ApplyOutcome should have value "blocked_empty"


  Scenario: ApplyResult model validates and strips whitespace
    When pas_cov I create an ApplyResult with whitespace in message
    Then pas_cov the ApplyResult message should be stripped


  Scenario: OP_COLORS dict has expected keys
    Then pas_cov OP_COLORS should map "create" to "green"
    And pas_cov OP_COLORS should map "modify" to "yellow"
    And pas_cov OP_COLORS should map "delete" to "red"
    And pas_cov OP_COLORS should map "rename" to "cyan"


  # ============================================================
  # Originally from: plan_cli_legacy_r2.feature
  # Feature: Plan CLI legacy wrappers and resolve branch coverage (round 2)
  # ============================================================

  Scenario: r2plan tell_command raises when no project
    When r2plan-I call tell_command with no project
    Then r2plan-a CleverAgentsError should be raised with message "No project found"


  Scenario: r2plan build_command raises when no project
    When r2plan-I call build_command with no project
    Then r2plan-a CleverAgentsError should be raised with message "No project found"


  Scenario: r2plan apply_command raises when no project
    When r2plan-I call apply_command with no project
    Then r2plan-a CleverAgentsError should be raised with message "No project found"


  Scenario: r2plan new_command raises when no project
    When r2plan-I call new_command with no project
    Then r2plan-a CleverAgentsError should be raised with message "No project found"


  Scenario: r2plan current_command raises when no project
    When r2plan-I call current_command with no project
    Then r2plan-a CleverAgentsError should be raised with message "No project found"


  Scenario: r2plan list_command raises when no project
    When r2plan-I call list_command with no project
    Then r2plan-a CleverAgentsError should be raised with message "No project found"


  Scenario: r2plan cd_command raises when no project
    When r2plan-I call cd_command with no project
    Then r2plan-a CleverAgentsError should be raised with message "No project found"


  Scenario: r2plan continue_command raises when no project
    When r2plan-I call continue_command with no project
    Then r2plan-a CleverAgentsError should be raised with message "No project found"

    # ===================================================================
    # continue_command – with prompt vs no-prompt + no current plan
    # ===================================================================


  Scenario: r2plan continue_command with prompt calls continue_plan
    When r2plan-I call continue_command with prompt "add tests"
    Then r2plan-the continue_plan service method should be called


  Scenario: r2plan continue_command no prompt and no current plan raises
    When r2plan-I call continue_command with no prompt and no current plan
    Then r2plan-a CleverAgentsError should be raised with message "No current plan"

    # ===================================================================
    # build_command – returns empty list when changes is None
    # ===================================================================


  Scenario: r2plan build_command returns empty list when None
    When r2plan-I call build_command with build returning None
    Then r2plan-the build result should be an empty list

    # ===================================================================
    # list_command – returns empty list when plans is None
    # ===================================================================


  Scenario: r2plan list_command returns empty list when None
    When r2plan-I call list_command with list returning None
    Then r2plan-the list result should be an empty list

    # ===================================================================
    # _resolve_active_plan_id – no active plans / service error
    # ===================================================================


  Scenario: r2plan resolve_active_plan_id aborts when no active plans
    When r2plan-I call _resolve_active_plan_id with no active plans
    Then r2plan-a typer Abort should be raised


  Scenario: r2plan resolve_active_plan_id aborts on service error
    When r2plan-I call _resolve_active_plan_id with service error
    Then r2plan-a typer Abort should be raised


  Scenario: r2plan resolve_active_plan_id skips home fallback when DB env override is set
    When r2plan-I call _resolve_active_plan_id with explicit DB env override and no active plans
    Then r2plan-a typer Abort should be raised
    And r2plan-the home DB fallback should not be attempted


  Scenario: r2plan resolve_active_plan_id surfaces unexpected fallback errors
    When r2plan-I call _resolve_active_plan_id with unexpected fallback error
    Then r2plan-a RuntimeError should be raised


  # ============================================================
  # Originally from: plan_cli_spec_print_r2.feature
  # Feature: Plan CLI spec dict and print branch coverage (round 2)
  # ============================================================

  Scenario: r2plan spec dict includes alias in project link
    Given r2plan-a v3 Plan with a project link that has alias "backend"
    When r2plan-I call _plan_spec_dict
    Then r2plan-the spec dict project_links should include alias "backend"


  Scenario: r2plan spec dict includes read_only in project link
    Given r2plan-a v3 Plan with a project link that is read_only
    When r2plan-I call _plan_spec_dict
    Then r2plan-the spec dict project_links should include read_only true


  Scenario: r2plan spec dict omits alias and read_only when unset
    Given r2plan-a v3 Plan with a plain project link
    When r2plan-I call _plan_spec_dict
    Then r2plan-the spec dict project_links should not include alias
    And r2plan-the spec dict project_links should not include read_only

    # ===================================================================
    # _plan_spec_dict – automation_profile truthy / falsy
    # ===================================================================


  Scenario: r2plan spec dict includes automation_profile when set
    Given r2plan-a v3 Plan with automation_profile "review"
    When r2plan-I call _plan_spec_dict
    Then r2plan-the spec dict automation_profile should be "review"


  Scenario: r2plan spec dict has null automation_profile when unset
    Given r2plan-a v3 Plan without automation_profile
    When r2plan-I call _plan_spec_dict
    Then r2plan-the spec dict automation_profile should be null

    # ===================================================================
    # _plan_spec_dict – invariants, validation_summary/dod
    # ===================================================================


  Scenario: r2plan spec dict includes invariants when present
    Given r2plan-a v3 Plan with invariants
    When r2plan-I call _plan_spec_dict
    Then r2plan-the spec dict should contain key "invariants"
    And r2plan-the spec dict invariants count should be 2


  Scenario: r2plan spec dict omits invariants when empty
    Given r2plan-a v3 Plan without invariants
    When r2plan-I call _plan_spec_dict
    Then r2plan-the spec dict should not contain key "invariants"


  Scenario: r2plan spec dict includes dod_evaluation when validated
    Given r2plan-a v3 Plan with dod validation summary
    When r2plan-I call _plan_spec_dict
    Then r2plan-the spec dict should contain key "dod_evaluation"
    And r2plan-the spec dict dod_evaluation all_passed should be true


  Scenario: r2plan spec dict omits dod_evaluation when no validation
    Given r2plan-a v3 Plan without validation_summary
    When r2plan-I call _plan_spec_dict
    Then r2plan-the spec dict should not contain key "dod_evaluation"

    # ===================================================================
    # _plan_spec_dict – last_completed_step / last_checkpoint_id
    # ===================================================================


  Scenario: r2plan spec dict includes last_completed_step when >= 0
    Given r2plan-a v3 Plan with last_completed_step 3
    When r2plan-I call _plan_spec_dict
    Then r2plan-the spec dict should contain key "last_completed_step"
    And r2plan-the spec dict last_completed_step should be 3


  Scenario: r2plan spec dict omits last_completed_step when -1
    Given r2plan-a v3 Plan with last_completed_step default
    When r2plan-I call _plan_spec_dict
    Then r2plan-the spec dict should not contain key "last_completed_step"


  Scenario: r2plan spec dict includes last_checkpoint_id when set
    Given r2plan-a v3 Plan with last_checkpoint_id "01CHKPT001"
    When r2plan-I call _plan_spec_dict
    Then r2plan-the spec dict should contain key "last_checkpoint_id"


  Scenario: r2plan spec dict omits last_checkpoint_id when None
    Given r2plan-a v3 Plan without last_checkpoint_id
    When r2plan-I call _plan_spec_dict
    Then r2plan-the spec dict should not contain key "last_checkpoint_id"

    # ===================================================================
    # _print_lifecycle_plan – definition_of_done branches
    # ===================================================================


  Scenario: r2plan print shows definition_of_done when short
    Given r2plan-a v3 Plan with definition_of_done "All tests pass"
    When r2plan-I call _print_lifecycle_plan
    Then r2plan-the printed output should contain "Definition of Done"
    And r2plan-the printed output should contain "All tests pass"


  Scenario: r2plan print truncates long definition_of_done
    Given r2plan-a v3 Plan with definition_of_done longer than 200 chars
    When r2plan-I call _print_lifecycle_plan
    Then r2plan-the printed output should contain "Definition of Done"
    And r2plan-the printed output should contain "..."


  Scenario: r2plan print omits definition_of_done when None
    Given r2plan-a v3 Plan without definition_of_done
    When r2plan-I call _print_lifecycle_plan
    Then r2plan-the printed output should not contain "Definition of Done"

    # ===================================================================
    # _print_lifecycle_plan – validation_summary dod pass / fail
    # ===================================================================


  Scenario: r2plan print shows DoD PASSED evaluation
    Given r2plan-a v3 Plan with dod evaluated as passed
    When r2plan-I call _print_lifecycle_plan
    Then r2plan-the printed output should contain "DoD Evaluation"
    And r2plan-the printed output should contain "PASSED"


  Scenario: r2plan print shows DoD FAILED evaluation with failures
    Given r2plan-a v3 Plan with dod evaluated as failed
    When r2plan-I call _print_lifecycle_plan
    Then r2plan-the printed output should contain "DoD Evaluation"
    And r2plan-the printed output should contain "FAILED"
    And r2plan-the printed output should contain "failed"

    # ===================================================================
    # _print_lifecycle_plan – arguments with/without order
    # ===================================================================


  Scenario: r2plan print shows arguments using arguments_order
    Given r2plan-a v3 Plan with arguments and arguments_order
    When r2plan-I call _print_lifecycle_plan
    Then r2plan-the printed output should contain "Arguments"
    And r2plan-the printed output should contain "target_coverage = 80"


  Scenario: r2plan print shows arguments sorted when no order
    Given r2plan-a v3 Plan with arguments but no arguments_order
    When r2plan-I call _print_lifecycle_plan
    Then r2plan-the printed output should contain "Arguments"

    # ===================================================================
    # _print_lifecycle_plan – long description > 200 chars
    # ===================================================================


  Scenario: r2plan print truncates long description
    Given r2plan-a v3 Plan with description longer than 200 chars
    When r2plan-I call _print_lifecycle_plan
    Then r2plan-the printed output should contain "..."

    # ===================================================================
    # _print_lifecycle_plan – automation_profile
    # ===================================================================


  Scenario: r2plan print shows automation profile
    Given r2plan-a v3 Plan with automation_profile "review"
    When r2plan-I call _print_lifecycle_plan
    Then r2plan-the printed output should contain "Automation Profile"
    And r2plan-the printed output should contain "review"

    # ===================================================================
    # _print_lifecycle_plan – invariants display
    # ===================================================================


  Scenario: r2plan print shows invariants
    Given r2plan-a v3 Plan with invariants
    When r2plan-I call _print_lifecycle_plan
    Then r2plan-the printed output should contain "Invariants"

    # ===================================================================
    # _print_lifecycle_plan – resume metadata
    # ===================================================================


  Scenario: r2plan print shows resume metadata
    Given r2plan-a v3 Plan with resume metadata
    When r2plan-I call _print_lifecycle_plan
    Then r2plan-the printed output should contain "Last Completed Step"
    And r2plan-the printed output should contain "Last Checkpoint"

    # ===================================================================
    # _print_lifecycle_plan – project link alias and read_only
    # ===================================================================


  Scenario: r2plan print shows project link with alias and read_only
    Given r2plan-a v3 Plan with project link alias and read_only
    When r2plan-I call _print_lifecycle_plan
    Then r2plan-the printed output should contain "alias:"
    And r2plan-the printed output should contain "local/ref-data"


  # ============================================================
  # Originally from: plan_execute_runtime.feature
  # Feature: Plan Execute Runtime Integration
  # ============================================================

  @context
  Scenario: Create PlanExecutionContext with required plan_id
    Given a fresh in-memory changeset store
    And a valid plan ID
    When I create a PlanExecutionContext with a valid plan_id
    Then the context plan_id should match the provided value


  @context
  Scenario: Create PlanExecutionContext with all optional fields
    Given a fresh in-memory changeset store
    And a valid plan ID
    When I create a PlanExecutionContext with all optional fields
    Then the context should have the correct decision_root_id
    And the context should have the correct sandbox_root
    And the context should have the correct automation_profile


  @context @error
  Scenario: PlanExecutionContext rejects empty plan_id
    Given a fresh in-memory changeset store
    And a valid plan ID
    When I attempt to create a PlanExecutionContext with empty plan_id
    Then the plan execution context should raise a ValidationError containing "plan_id"


  @context @error
  Scenario: PlanExecutionContext rejects None plan_id
    Given a fresh in-memory changeset store
    And a valid plan ID
    When I attempt to create a PlanExecutionContext with None plan_id
    Then the plan execution context should raise a ValidationError containing "plan_id"


  @context
  Scenario: PlanExecutionContext defaults to InMemoryChangeSetStore
    Given a fresh in-memory changeset store
    And a valid plan ID
    When I create a PlanExecutionContext without specifying a changeset store
    Then the context changeset_store should be an InMemoryChangeSetStore


  @context
  Scenario: PlanExecutionContext accepts custom changeset store
    Given a fresh in-memory changeset store
    And a valid plan ID
    When I create a PlanExecutionContext with a custom changeset store
    Then the context changeset_store should be the provided store


  @context
  Scenario: PlanExecutionContext defaults project_resources to empty dict
    Given a fresh in-memory changeset store
    And a valid plan ID
    When I create a PlanExecutionContext with a valid plan_id
    Then the context project_resources should be an empty dict


  @context
  Scenario: PlanExecutionContext defaults resource_bindings to empty dict
    Given a fresh in-memory changeset store
    And a valid plan ID
    When I create a PlanExecutionContext with a valid plan_id
    Then the context resource_bindings should be an empty dict

    # -- Changeset operations --


  @changeset
  Scenario: start_changeset creates a changeset with ULID
    Given a fresh in-memory changeset store
    And a valid plan ID
    Given a PlanExecutionContext
    When I call start_changeset
    Then the returned changeset_id should be a valid ULID string
    And the active_changeset_ids should contain the new ID


  @changeset
  Scenario: record_change persists an entry into the active changeset
    Given a fresh in-memory changeset store
    And a valid plan ID
    Given a PlanExecutionContext with an active changeset
    When I record a ChangeEntry into the context
    Then the changeset should contain the recorded entry


  @changeset
  Scenario: record_change without active changeset raises PlanError
    Given a fresh in-memory changeset store
    And a valid plan ID
    Given a PlanExecutionContext without an active changeset
    When I attempt to record a ChangeEntry
    Then a PlanError should be raised about no active changeset


  @changeset
  Scenario: Multiple changes recorded into same changeset
    Given a fresh in-memory changeset store
    And a valid plan ID
    Given a PlanExecutionContext with an active changeset
    When I record three ChangeEntry instances
    Then the changeset should contain exactly three entries


  @changeset
  Scenario: get_changeset retrieves existing changeset
    Given a fresh in-memory changeset store
    And a valid plan ID
    Given a PlanExecutionContext with an active changeset and one recorded entry
    When I call get_changeset with the active changeset_id
    Then the returned changeset should not be None
    And the changeset plan_id should match


  @changeset
  Scenario: get_changeset returns None for unknown ID
    Given a fresh in-memory changeset store
    And a valid plan ID
    Given a PlanExecutionContext
    When I call get_changeset with a nonexistent ID
    Then the returned changeset should be None

    # -- Summarize --


  @summarize
  Scenario: summarize includes all expected fields
    Given a fresh in-memory changeset store
    And a valid plan ID
    Given a PlanExecutionContext with an active changeset and one recorded entry
    When I call summarize on the context
    Then the summary should contain plan_id
    And the summary should contain resource_binding_count of 0
    And the summary should contain decision_root_id


  @summarize
  Scenario: summarize with no changesets shows zero resource counts
    Given a fresh in-memory changeset store
    And a valid plan ID
    Given a PlanExecutionContext
    When I call summarize on the context
    Then the summary resource_binding_count should be 0

    # -- RuntimeExecuteResult model --


  @model
  Scenario: RuntimeExecuteResult validates with valid data
    Given a fresh in-memory changeset store
    And a valid plan ID
    When I create a RuntimeExecuteResult with valid fields
    Then the runtime execute result should have a correct changeset_id
    And the runtime execute result tool_call_count should be non-negative


  @model @error
  Scenario: RuntimeExecuteResult rejects negative tool_call_count
    Given a fresh in-memory changeset store
    And a valid plan ID
    When I attempt to create a RuntimeExecuteResult with negative tool_call_count
    Then the runtime result should fail validation


  @model @error
  Scenario: RuntimeExecuteResult rejects empty changeset_id
    Given a fresh in-memory changeset store
    And a valid plan ID
    When I attempt to create a RuntimeExecuteResult with empty changeset_id
    Then the runtime result should fail validation

    # -- RuntimeExecuteActor --


  @actor
  Scenario: RuntimeExecuteActor executes with stub decisions
    Given a fresh in-memory changeset store
    And a valid plan ID
    Given a PlanExecutionContext
    And a ToolRunner with an empty registry
    And a RuntimeExecuteActor
    When I execute with a list of two stub decisions
    Then the runtime execute result should be a RuntimeExecuteResult
    And the runtime execute result changeset_id should be a valid ULID
    And the runtime execute result decision_ids_processed should have two entries


  @actor
  Scenario: RuntimeExecuteActor with streaming callback
    Given a fresh in-memory changeset store
    And a valid plan ID
    Given a PlanExecutionContext
    And a ToolRunner with an empty registry
    And a RuntimeExecuteActor
    And a stream callback collector
    When I execute with one stub decision and the stream callback
    Then the callback should have received runtime_execute_started event
    And the callback should have received runtime_execute_step event
    And the callback should have received runtime_execute_complete event


  @actor
  Scenario: RuntimeExecuteActor records sandbox_refs when sandbox_root is set
    Given a fresh in-memory changeset store
    And a valid plan ID
    Given a PlanExecutionContext with sandbox_root "/tmp/sandbox"
    And a ToolRunner with an empty registry
    And a RuntimeExecuteActor
    When I execute with one stub decision
    Then the runtime execute result sandbox_refs should contain "/tmp/sandbox"


  @actor @error
  Scenario: RuntimeExecuteActor rejects None tool_runner
    Given a fresh in-memory changeset store
    And a valid plan ID
    Given a PlanExecutionContext
    When I attempt to create a RuntimeExecuteActor with None tool_runner
    Then the plan execution context should raise a ValidationError


  @actor @error
  Scenario: RuntimeExecuteActor rejects None execution_context
    Given a fresh in-memory changeset store
    And a valid plan ID
    Given a ToolRunner with an empty registry
    When I attempt to create a RuntimeExecuteActor with None execution_context
    Then the plan execution context should raise a ValidationError

    # -- PlanExecutor integration --


  @executor
  Scenario: PlanExecutor has_runtime is True with tool_calling_runtime
    Given a fresh in-memory changeset store
    And a valid plan ID
    Given a mock lifecycle service
    And a PlanExecutionContext
    And a ToolRunner with an empty registry
    When I create a PlanExecutor with a mock tool_calling_runtime
    Then has_runtime should be True


  @executor
  Scenario: PlanExecutor has_runtime is False without execution_context
    Given a fresh in-memory changeset store
    And a valid plan ID
    Given a mock lifecycle service
    When I create a PlanExecutor without execution_context
    Then has_runtime should be False


  @executor
  Scenario: PlanExecutor changeset_store returns store from context
    Given a fresh in-memory changeset store
    And a valid plan ID
    Given a mock lifecycle service
    And a PlanExecutionContext with a known changeset store
    When I create a PlanExecutor with the execution_context
    Then changeset_store should be the same object as the context store


  @executor
  Scenario: PlanExecutor changeset_store returns None without context
    Given a fresh in-memory changeset store
    And a valid plan ID
    Given a mock lifecycle service
    When I create a PlanExecutor without execution_context
    Then changeset_store should be None


  @executor
  Scenario: PlanExecutor execution_context property returns context
    Given a fresh in-memory changeset store
    And a valid plan ID
    Given a mock lifecycle service
    And a PlanExecutionContext
    When I create a PlanExecutor with the execution_context
    Then execution_context property should return the context

    # -- Error handling --


  @error @executor
  Scenario: PlanExecutor rejects None lifecycle_service
    Given a fresh in-memory changeset store
    And a valid plan ID
    When I attempt to create a PlanExecutor with None lifecycle_service
    Then the plan execution context should raise a ValidationError containing "lifecycle_service"


  @error @executor
  Scenario: PlanExecutor run_execute rejects empty plan_id
    Given a fresh in-memory changeset store
    And a valid plan ID
    Given a mock lifecycle service
    And a PlanExecutor without execution_context
    When I attempt to run execute with an empty plan_id
    Then the plan execution context should raise a ValidationError containing "plan_id"
