Feature: Invariant CLI commands coverage As a developer I want full test coverage for the invariant CLI commands module So that all commands and helper functions are verified # === _resolve_scope helper === Scenario: Resolve scope with --global flag returns GLOBAL scope When I resolve invariant scope with global flag set Then the resolved invariant scope should be "global" And the resolved invariant source name should be "system" Scenario: Resolve scope with --project flag returns PROJECT scope When I resolve invariant scope with project "myapp" Then the resolved invariant scope should be "project" And the resolved invariant source name should be "myapp" Scenario: Resolve scope with --plan flag returns PLAN scope When I resolve invariant scope with plan "PLAN_01HX" Then the resolved invariant scope should be "plan" And the resolved invariant source name should be "PLAN_01HX" Scenario: Resolve scope with --action flag returns ACTION scope When I resolve invariant scope with action "deploy-service" Then the resolved invariant scope should be "action" And the resolved invariant source name should be "deploy-service" Scenario: Resolve scope with no flags defaults to GLOBAL scope When I resolve invariant scope with no flags Then the resolved invariant scope should be "global" And the resolved invariant source name should be "system" Scenario: Resolve scope with conflicting flags raises BadParameter When I resolve invariant scope with global and project flags Then a BadParameter error should be raised for invariant scope # === _invariant_dict helper === Scenario: Invariant dict serializes an invariant correctly Given a sample invariant with known fields for dict test When I call invariant_dict on the sample invariant Then the invariant dict should contain the correct id and text And the invariant dict should contain scope and source_name And the invariant dict should contain active and created_at ISO string # === _get_service singleton === Scenario: Get service creates InvariantService lazily When I call get_service with invariant module service reset to None Then an InvariantService instance should be returned from get_service And calling get_service again returns the same InvariantService instance # === invariant add command === Scenario: Add invariant with --global flag via CLI Given a mocked InvariantService for invariant CLI add When I invoke invariant add with "--global" and text "Never delete prod data" Then the invariant CLI exit code should be 0 And the invariant CLI output should contain "Invariant added" And the invariant CLI output should contain "Never delete prod data" Scenario: Add invariant with --project flag via CLI Given a mocked InvariantService for invariant CLI add When I invoke invariant add with "--project myapp" and text "All APIs need tests" Then the invariant CLI exit code should be 0 And the invariant CLI output should contain "Invariant added" Scenario: Add invariant without scope flag via CLI Given a mocked InvariantService for invariant CLI add When I invoke invariant add without scope flags and text "Missing scope flag" Then the invariant CLI exit code should be 0 And the invariant CLI output should contain "Invariant added" Scenario: Add invariant with --format json via CLI Given a mocked InvariantService for invariant CLI add When I invoke invariant add with "--global --format json" and text "JSON constraint" Then the invariant CLI exit code should be 0 And the invariant CLI output should contain "JSON constraint" Scenario: Add invariant handles CleverAgentsError Given a mocked InvariantService that raises CleverAgentsError on add When I invoke invariant add with "--global" and text "will fail" Then the invariant CLI exit code should be non-zero And the invariant CLI output should contain "Error" # === invariant list command === Scenario: List invariants with no results Given a mocked InvariantService that returns empty invariant list When I invoke invariant list with no filters Then the invariant CLI exit code should be 0 And the invariant CLI output should contain "No invariants found" Scenario: List invariants with results in rich format Given a mocked InvariantService that returns two invariants for list When I invoke invariant list with no filters Then the invariant CLI exit code should be 0 And the invariant CLI output should contain "Invariants" And the invariant CLI output should contain "Never delete prod" Scenario: List invariants with --global filter Given a mocked InvariantService that returns two invariants for list When I invoke invariant list with flags "--global" Then the invariant CLI exit code should be 0 And the invariant service list was called with global scope Scenario: List invariants with --project filter Given a mocked InvariantService that returns two invariants for list When I invoke invariant list with flags "--project myapp" Then the invariant CLI exit code should be 0 And the invariant service list was called with project scope and source "myapp" Scenario: List invariants with --plan filter Given a mocked InvariantService that returns two invariants for list When I invoke invariant list with flags "--plan PLAN_01HX" Then the invariant CLI exit code should be 0 And the invariant service list was called with plan scope and source "PLAN_01HX" Scenario: List invariants with --action filter Given a mocked InvariantService that returns two invariants for list When I invoke invariant list with flags "--action deploy" Then the invariant CLI exit code should be 0 And the invariant service list was called with action scope and source "deploy" Scenario: List invariants with --effective flag Given a mocked InvariantService that returns two invariants for list When I invoke invariant list with flags "--effective" Then the invariant CLI exit code should be 0 And the invariant service list was called with effective true Scenario: List invariants with regex filter matching Given a mocked InvariantService that returns two invariants for list When I invoke invariant list with regex "prod" Then the invariant CLI exit code should be 0 And the invariant CLI output should contain "Never delete prod" Scenario: List invariants with invalid regex Given a mocked InvariantService that returns two invariants for list When I invoke invariant list with regex "[invalid" Then the invariant CLI exit code should be non-zero And the invariant CLI output should contain "Invalid regex" Scenario: List invariants with --format json Given a mocked InvariantService that returns two invariants for list When I invoke invariant list with flags "--format json" Then the invariant CLI exit code should be 0 And the invariant CLI output should contain "Never delete prod" Scenario: List invariants with no flags passes no scope filter Given a mocked InvariantService that returns two invariants for list When I invoke invariant list with no filters Then the invariant CLI exit code should be 0 And the invariant service list was called with no scope filter Scenario: List invariants with conflicting scope flags rejected Given a mocked InvariantService that returns two invariants for list When I invoke invariant list with flags "--global --project myapp" Then the invariant CLI exit code should be non-zero # === invariant remove command === Scenario: Remove invariant with --yes flag Given a mocked InvariantService for invariant CLI remove When I invoke invariant remove with "--yes" for id "INV_01HX" Then the invariant CLI exit code should be 0 And the invariant CLI output should contain "Invariant removed" Scenario: Remove invariant without --yes cancelled by user Given a mocked InvariantService for invariant CLI remove When I invoke invariant remove without --yes for id "INV_01HX" and answer no Then the invariant CLI exit code should be non-zero And the invariant CLI output should contain "Cancelled" Scenario: Remove invariant raises NotFoundError Given a mocked InvariantService that raises NotFoundError on remove When I invoke invariant remove with "--yes" for id "MISSING_ID" Then the invariant CLI exit code should be non-zero And the invariant CLI output should contain "not found"