fix(cli): require at least one scope flag for invariant add per spec

The invariant add command was silently defaulting to global scope when no scope
flag was provided, violating the specification which requires at least one scope
flag (--global, --project, --plan, or --action).

This fix:
- Updates _resolve_scope() to raise typer.BadParameter when no flags are set
- Updates the docstring to reflect the requirement
- Adds a new BDD scenario to test the missing scope flag error
- Updates the existing scenario that expected default to global behavior
This commit is contained in:
2026-04-14 12:28:26 +00:00
parent 0257841825
commit 1a0590405b
3 changed files with 28 additions and 8 deletions
+8 -3
View File
@@ -25,10 +25,9 @@ Feature: Invariant CLI commands coverage
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
Scenario: Resolve scope with no flags raises BadParameter
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"
Then a BadParameter error should be raised for invariant scope
Scenario: Resolve scope with conflicting flags raises BadParameter
When I resolve invariant scope with global and project flags
@@ -71,6 +70,12 @@ Feature: Invariant CLI commands coverage
Then the invariant CLI exit code should be 0
And the invariant CLI output should contain "JSON constraint"
Scenario: Add invariant without scope flag raises error
Given a mocked InvariantService for invariant CLI add
When I invoke invariant add with no scope flags and text "Missing scope"
Then the invariant CLI exit code should be non-zero
And the invariant CLI output should contain "At least one scope flag is required"
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"
@@ -91,9 +91,13 @@ def step_resolve_scope_action(context, action):
@when("I resolve invariant scope with no flags")
def step_resolve_scope_default(context):
context.resolved_scope, context.resolved_source = _resolve_scope(
is_global=False, project=None, plan=None, action=None
)
context.inv_bad_parameter_raised = False
try:
context.resolved_scope, context.resolved_source = _resolve_scope(
is_global=False, project=None, plan=None, action=None
)
except typer.BadParameter:
context.inv_bad_parameter_raised = True
@when("I resolve invariant scope with global and project flags")
@@ -214,6 +218,12 @@ def step_run_add(context, flags, text):
context.inv_result = _runner.invoke(app, args)
@when('I invoke invariant add with no scope flags and text "{text}"')
def step_run_add_no_scope(context, text):
args = ["add", text]
context.inv_result = _runner.invoke(app, args)
@then("the invariant CLI exit code should be 0")
def step_check_exit_zero(context):
assert context.inv_result.exit_code == 0, (
+7 -2
View File
@@ -20,7 +20,7 @@ Each invariant belongs to exactly one scope. Pass the matching flag:
- ``--action ACTION``: Action-template invariant
- ``--plan PLAN_ID``: Plan-specific invariant
If no scope flag is given, ``--global`` is assumed.
At least one scope flag must be provided.
## Examples
@@ -86,6 +86,12 @@ def _resolve_scope(
"Specify at most one scope flag: --global, --project, --plan, or --action"
)
if flags_set == 0:
raise typer.BadParameter(
"At least one scope flag is required: --global, --project, --plan, "
"or --action"
)
if project is not None:
return InvariantScope.PROJECT, project
if plan is not None:
@@ -93,7 +99,6 @@ def _resolve_scope(
if action is not None:
return InvariantScope.ACTION, action
# Default to global
return InvariantScope.GLOBAL, "system"