strictcli 0.35.2__tar.gz → 0.35.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. {strictcli-0.35.2 → strictcli-0.35.3}/PKG-INFO +1 -1
  2. {strictcli-0.35.2 → strictcli-0.35.3}/pyproject.toml +1 -1
  3. {strictcli-0.35.2 → strictcli-0.35.3}/strictcli/__init__.py +82 -10
  4. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_reserved_quartet.py +139 -5
  5. {strictcli-0.35.2 → strictcli-0.35.3}/uv.lock +1 -1
  6. {strictcli-0.35.2 → strictcli-0.35.3}/.claude/settings.json +0 -0
  7. {strictcli-0.35.2 → strictcli-0.35.3}/.github/workflows/ci.yml +0 -0
  8. {strictcli-0.35.2 → strictcli-0.35.3}/.github/workflows/publish.yml +0 -0
  9. {strictcli-0.35.2 → strictcli-0.35.3}/.gitignore +0 -0
  10. {strictcli-0.35.2 → strictcli-0.35.3}/.rlsbl/bases/.github/workflows/ci.yml +0 -0
  11. {strictcli-0.35.2 → strictcli-0.35.3}/.rlsbl/bases/.github/workflows/publish.yml +0 -0
  12. {strictcli-0.35.2 → strictcli-0.35.3}/.rlsbl/bases/.gitignore +0 -0
  13. {strictcli-0.35.2 → strictcli-0.35.3}/.rlsbl/bases/.rlsbl/lint/python.toml +0 -0
  14. {strictcli-0.35.2 → strictcli-0.35.3}/.rlsbl/config.json +0 -0
  15. {strictcli-0.35.2 → strictcli-0.35.3}/.rlsbl/lint/python.toml +0 -0
  16. {strictcli-0.35.2 → strictcli-0.35.3}/.rlsbl/managed-files.json +0 -0
  17. {strictcli-0.35.2 → strictcli-0.35.3}/.rlsbl/version +0 -0
  18. {strictcli-0.35.2 → strictcli-0.35.3}/.strictcli/schema.json +0 -0
  19. {strictcli-0.35.2 → strictcli-0.35.3}/CLAUDE.md +0 -0
  20. {strictcli-0.35.2 → strictcli-0.35.3}/LICENSE +0 -0
  21. {strictcli-0.35.2 → strictcli-0.35.3}/README.md +0 -0
  22. {strictcli-0.35.2 → strictcli-0.35.3}/scripts/add_effect_classification.py +0 -0
  23. {strictcli-0.35.2 → strictcli-0.35.3}/scripts/add_forwarding_declaration.py +0 -0
  24. {strictcli-0.35.2 → strictcli-0.35.3}/strictcli/py.typed +0 -0
  25. {strictcli-0.35.2 → strictcli-0.35.3}/tests/conftest.py +0 -0
  26. {strictcli-0.35.2 → strictcli-0.35.3}/tests/flagship_app.py +0 -0
  27. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_arg_default.py +0 -0
  28. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_arg_default_validation.py +0 -0
  29. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_at_prefix.py +0 -0
  30. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_auto_version.py +0 -0
  31. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_call.py +0 -0
  32. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_check_command.py +0 -0
  33. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_check_discovery.py +0 -0
  34. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_check_provider.py +0 -0
  35. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_check_public_api.py +0 -0
  36. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_check_runner.py +0 -0
  37. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_check_schema.py +0 -0
  38. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_check_types.py +0 -0
  39. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_choices.py +0 -0
  40. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_choices_none.py +0 -0
  41. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_classification.py +0 -0
  42. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_command_help_suggestion.py +0 -0
  43. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_command_tags.py +0 -0
  44. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_compound_types.py +0 -0
  45. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_config.py +0 -0
  46. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_config_fields.py +0 -0
  47. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_config_file_path.py +0 -0
  48. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_config_set_bugs.py +0 -0
  49. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_confirm.py +0 -0
  50. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_connection_env.py +0 -0
  51. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_context.py +0 -0
  52. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_coverage.py +0 -0
  53. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_deep_nesting.py +0 -0
  54. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_dependencies.py +0 -0
  55. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_deprecated.py +0 -0
  56. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_dump_schema.py +0 -0
  57. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_e2e.py +0 -0
  58. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_effects.py +0 -0
  59. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_effects_bypass_check.py +0 -0
  60. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_env.py +0 -0
  61. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_exit_codes.py +0 -0
  62. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_flag_sets.py +0 -0
  63. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_flagship_preview.py +0 -0
  64. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_float_format.py +0 -0
  65. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_float_type.py +0 -0
  66. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_float_vectors.py +0 -0
  67. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_global_flag_conflict_position.py +0 -0
  68. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_global_flags.py +0 -0
  69. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_guard_v2.py +0 -0
  70. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_help.py +0 -0
  71. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_hermetic.py +0 -0
  72. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_infra_env.py +0 -0
  73. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_int_type.py +0 -0
  74. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_invoke.py +0 -0
  75. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_keyword_flags.py +0 -0
  76. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_mcp.py +0 -0
  77. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_mutex.py +0 -0
  78. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_nesting.py +0 -0
  79. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_parser.py +0 -0
  80. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_passthrough.py +0 -0
  81. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_provenance.py +0 -0
  82. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_provenance_phase2.py +0 -0
  83. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_registration.py +0 -0
  84. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_repeatable.py +0 -0
  85. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_reserved_global_flags.py +0 -0
  86. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_tagdsl.py +0 -0
  87. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_toml_loading.py +0 -0
  88. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_tool_export.py +0 -0
  89. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_typed_args.py +0 -0
  90. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_unique.py +0 -0
  91. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_utilities.py +0 -0
  92. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_validate.py +0 -0
  93. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_variadic.py +0 -0
  94. {strictcli-0.35.2 → strictcli-0.35.3}/tests/test_visibility.py +0 -0
  95. {strictcli-0.35.2 → strictcli-0.35.3}/todo/.defer/deferred.md +0 -0
  96. {strictcli-0.35.2 → strictcli-0.35.3}/todo/.done/keyword-collision-in-flag-param-name.md +0 -0
  97. {strictcli-0.35.2 → strictcli-0.35.3}/todo/.done/original-idea.md +0 -0
  98. {strictcli-0.35.2 → strictcli-0.35.3}/todo/.done/public-check-runner-api.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: strictcli
3
- Version: 0.35.2
3
+ Version: 0.35.3
4
4
  Summary: A strict CLI framework for Python
5
5
  Project-URL: Homepage, https://github.com/smm-h/strictcli
6
6
  Project-URL: Repository, https://github.com/smm-h/strictcli
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "strictcli"
7
- version = "0.35.2"
7
+ version = "0.35.3"
8
8
  description = "A strict CLI framework for Python"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- __version__ = "0.35.2"
5
+ __version__ = "0.35.3"
6
6
 
7
7
  __all__ = [
8
8
  "App", "Flag", "Arg", "FlagSet", "MutexGroup", "CoRequired", "Requires",
@@ -6004,17 +6004,20 @@ class App:
6004
6004
  self._groups["config"] = config_grp
6005
6005
 
6006
6006
  def _pre_scan_reserved_flags(self, argv: list[str]) -> dict:
6007
- """Position-aware pre-scan for the framework-owned reserved flags.
6007
+ """Pre-scan for the framework-owned reserved flags.
6008
6008
 
6009
6009
  Handles --dump-schema, --mcp, --config, --hermetic and the effects-regime
6010
6010
  quartet --dry-run/--yes/--quiet/--verbose.
6011
6011
 
6012
- Scans the pre-command region of argv (before the first non-flag
6013
- token, before ``--``). Known global flags and their values are
6014
- skipped so that a global-flag value matching a command name does
6015
- not terminate the scan early. Everything at or after the command token
6016
- is left untouched -- which is what keeps a passthrough command's args
6017
- opaque to the framework.
6012
+ Two regions, two rulesets (contract §7.2, amended):
6013
+
6014
+ - The **pre-command region** (before the first non-flag token, before
6015
+ ``--``) recognizes every reserved flag. Known global flags and their
6016
+ values are skipped so that a global-flag value matching a command name
6017
+ does not terminate the region early.
6018
+ - The **command region** recognizes ONLY the quartet, anywhere, exactly
6019
+ like --help/-h. --hermetic/--config/--dump-schema/--mcp stay
6020
+ pre-command-only. See _scan_command_region_quartet.
6018
6021
 
6019
6022
  Returns a dict with keys: dump_schema, serve_mcp, hermetic, config_path,
6020
6023
  dry_run, yes, quiet, verbose, err, cleaned_argv.
@@ -6030,16 +6033,20 @@ class App:
6030
6033
 
6031
6034
  result: dict = {}
6032
6035
  exclude_indices: set[int] = set()
6036
+ # Index where the command region begins; -1 means "never reached one"
6037
+ # (a bare -- or an unknown flag-like token ended the scan for good).
6038
+ command_region_from = -1
6033
6039
  i = 0
6034
6040
  while i < len(argv):
6035
6041
  tok = argv[i]
6036
6042
 
6037
- # -- terminates the pre-command region
6043
+ # -- terminates the whole scan: everything after it is data
6038
6044
  if tok == "--":
6039
6045
  break
6040
6046
 
6041
- # Non-flag token = command name: stop scanning
6047
+ # Non-flag token = the command token: the command region starts here
6042
6048
  if not tok.startswith("-") or tok == "-":
6049
+ command_region_from = i
6043
6050
  break
6044
6051
 
6045
6052
  # --dump-schema
@@ -6120,6 +6127,11 @@ class App:
6120
6127
  # Unknown flag-like token: stop
6121
6128
  break
6122
6129
 
6130
+ if command_region_from >= 0:
6131
+ self._scan_command_region_quartet(
6132
+ argv, command_region_from, result, exclude_indices,
6133
+ )
6134
+
6123
6135
  if exclude_indices:
6124
6136
  result["cleaned_argv"] = [
6125
6137
  tok for j, tok in enumerate(argv) if j not in exclude_indices
@@ -6129,6 +6141,66 @@ class App:
6129
6141
 
6130
6142
  return result
6131
6143
 
6144
+ def _scan_command_region_quartet(
6145
+ self,
6146
+ argv: list[str],
6147
+ start: int,
6148
+ result: dict,
6149
+ exclude_indices: set[int],
6150
+ ) -> None:
6151
+ """Recognize the reserved quartet in the command region of argv.
6152
+
6153
+ Contract §7.2 (amended 2026-08-04): --dry-run/--yes/--quiet/--verbose are
6154
+ recognized ANYWHERE in argv, exactly like --help/-h, because their
6155
+ applicability is per-command -- requiring them before the command name
6156
+ was backwards. Only the quartet is recognized here; --hermetic,
6157
+ --config, --dump-schema and --mcp remain pre-command-only.
6158
+
6159
+ The scan stops for good at two boundaries:
6160
+
6161
+ - a bare ``--``, after which every token is positional data;
6162
+ - a **passthrough** command's name, after which every token belongs to
6163
+ the child process and is forwarded byte-for-byte. Eating a child's own
6164
+ --verbose would silently change what the child does.
6165
+
6166
+ Routing tokens are walked through the group/command tree so a quartet
6167
+ token may sit anywhere among them. Nothing here raises: routing errors
6168
+ are the real parse's job.
6169
+ """
6170
+ groups = self._groups
6171
+ commands = self._commands
6172
+ routing_done = False
6173
+ i = start
6174
+ while i < len(argv):
6175
+ tok = argv[i]
6176
+
6177
+ if tok == "--":
6178
+ return
6179
+
6180
+ if tok.startswith("-") and tok != "-":
6181
+ if tok in _RESERVED_QUARTET_TOKENS:
6182
+ result[_RESERVED_QUARTET_TOKENS[tok]] = True
6183
+ exclude_indices.add(i)
6184
+ i += 1
6185
+ continue
6186
+
6187
+ # A non-flag token: a routing token until routing resolves.
6188
+ if not routing_done:
6189
+ grp = groups.get(tok)
6190
+ if grp is not None:
6191
+ groups = grp._groups
6192
+ commands = grp.commands
6193
+ i += 1
6194
+ continue
6195
+ cmd = commands.get(tok)
6196
+ if cmd is not None and cmd.passthrough is not None:
6197
+ return
6198
+ # Resolved a normal command, or hit an unknown/deprecated token
6199
+ # the real parse will report: routing is over either way.
6200
+ routing_done = True
6201
+
6202
+ i += 1
6203
+
6132
6204
  def _parse(self, argv: list[str]) -> tuple[Command, dict[str, object] | list[str], dict[str, str]]:
6133
6205
  """Parse argv (without program name) into a resolved Command and kwargs.
6134
6206
 
@@ -127,13 +127,107 @@ class TestDelivery:
127
127
  assert r.exit_code == 0
128
128
  assert seen == {"ok": True}
129
129
 
130
- def test_pre_scan_is_position_aware(self):
131
- """The quartet is extracted from the pre-command region only, which is
132
- what keeps a passthrough command's args opaque to the framework."""
130
+ @pytest.mark.parametrize("token,key", [
131
+ ("--dry-run", "dry_run"),
132
+ ("--yes", "yes"),
133
+ ("--quiet", "quiet"),
134
+ ("--verbose", "verbose"),
135
+ ])
136
+ def test_each_flag_is_recognized_after_the_command(self, token, key):
137
+ """The quartet is recognized anywhere in argv, exactly like --help."""
138
+ r = _quartet_app().test(["run", token])
139
+ assert r.exit_code == 0
140
+ assert r.data[key] is True
141
+
142
+ def test_all_four_together_after_the_command(self):
143
+ r = _quartet_app().test(["run", "--dry-run", "--yes", "--quiet", "--verbose"])
144
+ assert r.exit_code == 0
145
+ assert r.data == {"dry_run": True, "yes": True, "quiet": True, "verbose": True}
146
+
147
+ def test_mixed_positions_are_unioned(self):
148
+ r = _quartet_app().test(["--dry-run", "run", "--verbose"])
149
+ assert r.exit_code == 0
150
+ assert r.data["dry_run"] is True
151
+ assert r.data["verbose"] is True
152
+
153
+ def test_recognized_after_a_nested_group_subcommand(self):
154
+ app = strictcli.App(name="app", version="1.0.0", help="app")
155
+ outer = app.group("outer", help="outer")
156
+ inner = outer.group("inner", help="inner")
157
+
158
+ @inner.command("run", effect="read_only", help="run")
159
+ def _run(ctx):
160
+ return strictcli.outcome(data={"dry_run": ctx.dry_run})
161
+
162
+ r = app.test(["outer", "inner", "run", "--dry-run"])
163
+ assert r.exit_code == 0
164
+ assert r.data == {"dry_run": True}
165
+
166
+ def test_recognized_between_a_group_and_its_subcommand(self):
167
+ app = strictcli.App(name="app", version="1.0.0", help="app")
168
+ grp = app.group("grp", help="grp")
169
+
170
+ @grp.command("run", effect="read_only", help="run")
171
+ def _run(ctx):
172
+ return strictcli.outcome(data={"dry_run": ctx.dry_run})
173
+
174
+ r = app.test(["grp", "--dry-run", "run"])
175
+ assert r.exit_code == 0
176
+ assert r.data == {"dry_run": True}
177
+
178
+ def test_stripped_from_argv_after_the_command(self):
179
+ """The quartet never reaches the command parser as an argument."""
180
+ app = strictcli.App(name="app", version="1.0.0", help="app")
181
+
182
+ @app.command("run", effect="read_only", help="run",
183
+ args=[strictcli.Arg(name="name", help="a positional")])
184
+ def _run(ctx, name):
185
+ return strictcli.outcome(data={"name": name, "quiet": ctx.quiet})
186
+
187
+ r = app.test(["run", "--quiet", "value"])
188
+ assert r.exit_code == 0
189
+ assert r.data == {"name": "value", "quiet": True}
190
+
191
+ def test_a_token_after_double_dash_is_data(self):
192
+ """A bare -- ends the scan: what follows is positional data."""
193
+ app = strictcli.App(name="app", version="1.0.0", help="app")
194
+
195
+ @app.command("run", effect="read_only", help="run",
196
+ args=[strictcli.Arg(name="rest", help="trailing args",
197
+ variadic=True)])
198
+ def _run(ctx, rest):
199
+ return strictcli.outcome(data={"rest": rest, "dry_run": ctx.dry_run})
200
+
201
+ r = app.test(["run", "--", "--dry-run"])
202
+ assert r.exit_code == 0
203
+ assert r.data == {"rest": ["--dry-run"], "dry_run": False}
204
+
205
+ def test_hermetic_stays_pre_command_only(self):
206
+ """Only the quartet moved; --hermetic is still pre-command-only."""
133
207
  app = _quartet_app()
134
- r = app.test(["run", "--dry-run"])
208
+ r = app.test(["run", "--hermetic"])
135
209
  assert r.exit_code == 1
136
- assert "unknown flag '--dry-run'" in r.stderr
210
+ assert "unknown flag '--hermetic'" in r.stderr
211
+
212
+ def test_read_only_accepts_a_post_command_dry_run(self):
213
+ app = _quartet_app()
214
+ r = app.test(["run", "--dry-run"])
215
+ assert r.exit_code == 0
216
+ assert "DRY RUN — no changes were made. Would do:" in r.stdout
217
+
218
+ def test_read_only_still_rejects_a_mutating_effect_post_command(self):
219
+ """Per-command applicability is unchanged, wherever --dry-run appeared."""
220
+ app = strictcli.App(name="app", version="1.0.0", help="app")
221
+
222
+ @app.command("look", effect="read_only", help="look")
223
+ def _look(ctx):
224
+ ctx.effects.mkdir("d")
225
+ return 0
226
+
227
+ with pytest.raises(ValueError) as exc:
228
+ app.test(["look", "--dry-run"])
229
+ assert ('command "look" is classified read_only; '
230
+ "effects.mkdir is a mutating operation") in str(exc.value)
137
231
 
138
232
  def test_passthrough_args_stay_opaque(self):
139
233
  app = strictcli.App(name="app", version="1.0.0", help="app")
@@ -152,6 +246,46 @@ class TestDelivery:
152
246
  assert r.exit_code == 0
153
247
  assert seen["args"] == ["--quiet", "--verbose"]
154
248
 
249
+ def test_passthrough_under_a_group_stays_opaque(self):
250
+ app = strictcli.App(name="app", version="1.0.0", help="app")
251
+ seen = {}
252
+
253
+ def _pt(ctx, name, args, globals):
254
+ seen["args"] = args
255
+ seen["verbose"] = ctx.verbose
256
+ return 0
257
+
258
+ grp = app.group("grp", help="grp")
259
+
260
+ @grp.command("exec", effect="read_only", help="exec",
261
+ passthrough=strictcli.Passthrough(handler=_pt))
262
+ def _exec(ctx, **kw):
263
+ return 0
264
+
265
+ r = app.test(["grp", "exec", "--verbose", "child"])
266
+ assert r.exit_code == 0
267
+ assert seen == {"args": ["--verbose", "child"], "verbose": False}
268
+
269
+ def test_pre_command_position_is_the_passthrough_escape_hatch(self):
270
+ """A pre-command quartet token reaches the Context AND leaves the
271
+ child's identically-spelled argument untouched."""
272
+ app = strictcli.App(name="app", version="1.0.0", help="app")
273
+ seen = {}
274
+
275
+ def _pt(ctx, name, args, globals):
276
+ seen["args"] = args
277
+ seen["verbose"] = ctx.verbose
278
+ return 0
279
+
280
+ @app.command("exec", effect="read_only", help="exec",
281
+ passthrough=strictcli.Passthrough(handler=_pt))
282
+ def _exec(ctx, **kw):
283
+ return 0
284
+
285
+ r = app.test(["--verbose", "exec", "--verbose", "child"])
286
+ assert r.exit_code == 0
287
+ assert seen == {"args": ["--verbose", "child"], "verbose": True}
288
+
155
289
 
156
290
  class TestGating:
157
291
  """The --quiet/--verbose gating table (quiet dominates verbose)."""
@@ -232,7 +232,7 @@ wheels = [
232
232
 
233
233
  [[package]]
234
234
  name = "strictcli"
235
- version = "0.35.2"
235
+ version = "0.35.3"
236
236
  source = { editable = "." }
237
237
  dependencies = [
238
238
  { name = "tomlkit" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes