strictcli 0.22.0__tar.gz → 0.23.0__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 (74) hide show
  1. {strictcli-0.22.0 → strictcli-0.23.0}/CHANGELOG.md +9 -3
  2. {strictcli-0.22.0 → strictcli-0.23.0}/PKG-INFO +1 -1
  3. {strictcli-0.22.0 → strictcli-0.23.0}/package-lock.json +2 -2
  4. {strictcli-0.22.0 → strictcli-0.23.0}/package.json +1 -1
  5. {strictcli-0.22.0 → strictcli-0.23.0}/pyproject.toml +1 -1
  6. {strictcli-0.22.0 → strictcli-0.23.0}/strictcli/__init__.py +12 -1
  7. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_dump_schema.py +21 -21
  8. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_e2e.py +1 -1
  9. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_help.py +3 -3
  10. {strictcli-0.22.0 → strictcli-0.23.0}/uv.lock +1 -1
  11. {strictcli-0.22.0 → strictcli-0.23.0}/.claude/settings.json +0 -0
  12. {strictcli-0.22.0 → strictcli-0.23.0}/.github/workflows/ci.yml +0 -0
  13. {strictcli-0.22.0 → strictcli-0.23.0}/.github/workflows/publish.yml +0 -0
  14. {strictcli-0.22.0 → strictcli-0.23.0}/.gitignore +0 -0
  15. {strictcli-0.22.0 → strictcli-0.23.0}/.npmignore +0 -0
  16. {strictcli-0.22.0 → strictcli-0.23.0}/.rlsbl/config.json +0 -0
  17. {strictcli-0.22.0 → strictcli-0.23.0}/.rlsbl/hashes.json +0 -0
  18. {strictcli-0.22.0 → strictcli-0.23.0}/.rlsbl/version +0 -0
  19. {strictcli-0.22.0 → strictcli-0.23.0}/.strictcli/schema.json +0 -0
  20. {strictcli-0.22.0 → strictcli-0.23.0}/CLAUDE.md +0 -0
  21. {strictcli-0.22.0 → strictcli-0.23.0}/LICENSE +0 -0
  22. {strictcli-0.22.0 → strictcli-0.23.0}/README.md +0 -0
  23. {strictcli-0.22.0 → strictcli-0.23.0}/index.js +0 -0
  24. {strictcli-0.22.0 → strictcli-0.23.0}/postinstall.js +0 -0
  25. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_arg_default.py +0 -0
  26. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_at_prefix.py +0 -0
  27. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_auto_version.py +0 -0
  28. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_call.py +0 -0
  29. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_check_command.py +0 -0
  30. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_check_discovery.py +0 -0
  31. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_check_public_api.py +0 -0
  32. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_check_runner.py +0 -0
  33. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_check_schema.py +0 -0
  34. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_check_types.py +0 -0
  35. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_choices.py +0 -0
  36. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_command_help_suggestion.py +0 -0
  37. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_command_tags.py +0 -0
  38. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_compound_types.py +0 -0
  39. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_config.py +0 -0
  40. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_config_fields.py +0 -0
  41. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_config_file_path.py +0 -0
  42. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_config_set_bugs.py +0 -0
  43. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_context.py +0 -0
  44. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_deep_nesting.py +0 -0
  45. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_dependencies.py +0 -0
  46. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_deprecated.py +0 -0
  47. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_env.py +0 -0
  48. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_exit_codes.py +0 -0
  49. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_flag_sets.py +0 -0
  50. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_float_type.py +0 -0
  51. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_global_flags.py +0 -0
  52. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_int_type.py +0 -0
  53. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_invoke.py +0 -0
  54. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_keyword_flags.py +0 -0
  55. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_mcp.py +0 -0
  56. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_mutex.py +0 -0
  57. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_nesting.py +0 -0
  58. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_parser.py +0 -0
  59. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_passthrough.py +0 -0
  60. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_registration.py +0 -0
  61. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_repeatable.py +0 -0
  62. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_tagdsl.py +0 -0
  63. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_toml_loading.py +0 -0
  64. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_tool_export.py +0 -0
  65. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_typed_args.py +0 -0
  66. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_unique.py +0 -0
  67. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_utilities.py +0 -0
  68. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_validate.py +0 -0
  69. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_variadic.py +0 -0
  70. {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_visibility.py +0 -0
  71. {strictcli-0.22.0 → strictcli-0.23.0}/todo/.defer/deferred.md +0 -0
  72. {strictcli-0.22.0 → strictcli-0.23.0}/todo/.done/keyword-collision-in-flag-param-name.md +0 -0
  73. {strictcli-0.22.0 → strictcli-0.23.0}/todo/.done/original-idea.md +0 -0
  74. {strictcli-0.22.0 → strictcli-0.23.0}/todo/.done/public-check-runner-api.md +0 -0
@@ -2,17 +2,23 @@
2
2
 
3
3
  # Changelog
4
4
 
5
- ## 0.22.0
5
+ ## 0.23.0
6
6
 
7
- Required booleans, Context type, schema project_id guard, tag contract global flags fix
7
+ Registration-time ban on bare --force flag name and --no-* prefix
8
8
 
9
9
  <details>
10
10
  <summary>Context</summary>
11
11
 
12
- Breaking change: Bool flags no longer auto-default to false. BoolFlag without explicit Default(false) is now required. New Context type for structured handler communication. Schema dump now validates project_id. TagContract now checks global flags.
12
+ Flag names 'force' (exact) and names starting with 'no-' are now rejected at registration time. Bare --force encourages agents to bypass guardrails without thinking; qualified names like --force-overwrite make the intent explicit. The no- prefix is reserved for strictcli's auto-generated negation system (--no-flag for negatable bools).
13
13
 
14
14
  </details>
15
15
 
16
+ ### Features
17
+
18
+ - **New.** Registration-time ban on bare --force flag name and --no-* prefix. Flag names starting with no- are reserved for the negation system.
19
+
20
+ ## 0.22.0
21
+
16
22
  ### Breaking
17
23
 
18
24
  - **Breaking.** Bool flags no longer auto-default to false. BoolFlag without explicit Default(false) is now required — user must pass --flag or --no-flag.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: strictcli
3
- Version: 0.22.0
3
+ Version: 0.23.0
4
4
  Summary: A strict, zero-dependency 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
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "strictcli",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "strictcli",
9
- "version": "0.22.0",
9
+ "version": "0.23.0",
10
10
  "hasInstallScript": true,
11
11
  "license": "MIT"
12
12
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strictcli",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
4
4
  "description": "A strict, zero-dependency CLI framework for Python (npm wrapper)",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "strictcli"
7
- version = "0.22.0"
7
+ version = "0.23.0"
8
8
  description = "A strict, zero-dependency 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.22.0"
5
+ __version__ = "0.23.0"
6
6
 
7
7
  __all__ = [
8
8
  "App", "Flag", "Arg", "FlagSet", "MutexGroup", "CoRequired", "Requires",
@@ -1204,6 +1204,17 @@ class Flag:
1204
1204
 
1205
1205
  def __post_init__(self) -> None:
1206
1206
  _require_non_empty_str(self.help, "help", "Flag")
1207
+ if self.name == "force":
1208
+ raise ValueError(
1209
+ "flag 'force' is a reserved name; use a qualified name "
1210
+ "like 'force-overwrite' or 'force-delete'"
1211
+ )
1212
+ if self.name.startswith("no-"):
1213
+ raise ValueError(
1214
+ f"flag '{self.name}': names starting with 'no-' are "
1215
+ f"reserved for the negation system; use a positive "
1216
+ f"name instead"
1217
+ )
1207
1218
 
1208
1219
  # Parse compound types (list[T], dict[str, T])
1209
1220
  kind, item_t, val_t = _parse_compound_type(
@@ -120,8 +120,8 @@ class TestSchemaCommands:
120
120
  @app.command("deploy", help="Deploy the app")
121
121
  @strictcli.flag("target", type=str, help="Deploy target", short="t",
122
122
  choices=["prod", "staging"])
123
- @strictcli.flag("force", type=bool, default=False, help="Force deploy")
124
- def deploy(target, force):
123
+ @strictcli.flag("force-deploy", type=bool, default=False, help="Force deploy")
124
+ def deploy(target, force_deploy):
125
125
  pass
126
126
 
127
127
  app.test(["--dump-schema"])
@@ -141,7 +141,7 @@ class TestSchemaCommands:
141
141
  assert "hidden" not in target_flag # hidden=False is the default, omitted
142
142
 
143
143
  force_flag = cmd["flags"][1]
144
- assert force_flag["name"] == "force"
144
+ assert force_flag["name"] == "force-deploy"
145
145
  assert force_flag["type"] == "bool"
146
146
  assert force_flag["negatable"] is True
147
147
  assert force_flag["default"] is False
@@ -428,8 +428,8 @@ class TestSchemaFlagTypes:
428
428
  app = _make_app()
429
429
 
430
430
  @app.command("cmd", help="A command")
431
- @strictcli.flag("force", type=bool, default=False, help="Force it", negatable=False)
432
- def cmd(force):
431
+ @strictcli.flag("force-it", type=bool, default=False, help="Force it", negatable=False)
432
+ def cmd(force_it):
433
433
  pass
434
434
 
435
435
  app.test(["--dump-schema"])
@@ -848,10 +848,10 @@ class TestSchemaConstraints:
848
848
 
849
849
  @app.command("deploy", help="Deploy",
850
850
  dependencies=[strictcli.Implies(
851
- flag="force", implies="yes", value=True)])
852
- @strictcli.flag("force", type=bool, default=False, help="Force deploy")
851
+ flag="force-deploy", implies="yes", value=True)])
852
+ @strictcli.flag("force-deploy", type=bool, default=False, help="Force deploy")
853
853
  @strictcli.flag("yes", type=bool, default=False, help="Skip confirmation")
854
- def deploy(force, yes):
854
+ def deploy(force_deploy, yes):
855
855
  pass
856
856
 
857
857
  app.test(["--dump-schema"])
@@ -860,7 +860,7 @@ class TestSchemaConstraints:
860
860
  assert len(cmd["constraints"]) == 1
861
861
  c = cmd["constraints"][0]
862
862
  assert c["type"] == "implies"
863
- assert c["flag"] == "force"
863
+ assert c["flag"] == "force-deploy"
864
864
  assert c["implies"] == "yes"
865
865
  assert c["value"] is True
866
866
 
@@ -897,17 +897,17 @@ class TestSchemaConstraints:
897
897
 
898
898
  @app.command("deploy", help="Deploy",
899
899
  dependencies=[strictcli.CoRequired(
900
- flags=["dry-run", "no-confirm"])])
900
+ flags=["dry-run", "skip-confirm"])])
901
901
  @strictcli.flag("dry-run", type=bool, default=False, help="Dry run")
902
- @strictcli.flag("no-confirm", type=bool, default=False, help="Skip confirmation")
903
- def deploy(dry_run, no_confirm):
902
+ @strictcli.flag("skip-confirm", type=bool, default=False, help="Skip confirmation")
903
+ def deploy(dry_run, skip_confirm):
904
904
  pass
905
905
 
906
906
  app.test(["--dump-schema"])
907
907
  data = json.loads((tmp_path / ".strictcli" / "schema.json").read_text())
908
908
  cmd = data["commands"]["deploy"]
909
909
  c = cmd["constraints"][0]
910
- assert c["flags"] == ["dry-run", "no-confirm"]
910
+ assert c["flags"] == ["dry-run", "skip-confirm"]
911
911
 
912
912
 
913
913
  class TestSchemaTagContracts:
@@ -928,34 +928,34 @@ class TestSchemaTagContracts:
928
928
  def test_tag_contracts_serialized(self, tmp_path, monkeypatch):
929
929
  monkeypatch.chdir(tmp_path)
930
930
  app = _make_app()
931
- app.tag_contract("dangerous", requires_flag="force")
931
+ app.tag_contract("dangerous", requires_flag="force-deploy")
932
932
 
933
933
  @app.command("deploy", help="Deploy", tags=["dangerous"])
934
- @strictcli.flag("force", type=bool, default=False, help="Force it")
935
- def deploy(force):
934
+ @strictcli.flag("force-deploy", type=bool, default=False, help="Force it")
935
+ def deploy(force_deploy):
936
936
  pass
937
937
 
938
938
  app.test(["--dump-schema"])
939
939
  data = json.loads((tmp_path / ".strictcli" / "schema.json").read_text())
940
940
  assert "tag_contracts" in data
941
- assert data["tag_contracts"] == {"dangerous": "force"}
941
+ assert data["tag_contracts"] == {"dangerous": "force-deploy"}
942
942
 
943
943
  def test_multiple_tag_contracts(self, tmp_path, monkeypatch):
944
944
  monkeypatch.chdir(tmp_path)
945
945
  app = _make_app()
946
- app.tag_contract("dangerous", requires_flag="force")
946
+ app.tag_contract("dangerous", requires_flag="force-deploy")
947
947
  app.tag_contract("slow", requires_flag="timeout")
948
948
 
949
949
  @app.command("deploy", help="Deploy", tags=["dangerous", "slow"])
950
- @strictcli.flag("force", type=bool, default=False, help="Force it")
950
+ @strictcli.flag("force-deploy", type=bool, default=False, help="Force it")
951
951
  @strictcli.flag("timeout", type=int, help="Timeout", default=30)
952
- def deploy(force, timeout):
952
+ def deploy(force_deploy, timeout):
953
953
  pass
954
954
 
955
955
  app.test(["--dump-schema"])
956
956
  data = json.loads((tmp_path / ".strictcli" / "schema.json").read_text())
957
957
  assert data["tag_contracts"] == {
958
- "dangerous": "force",
958
+ "dangerous": "force-deploy",
959
959
  "slow": "timeout",
960
960
  }
961
961
 
@@ -317,7 +317,7 @@ def test_e2e_kwargs_handler_registration_no_error():
317
317
  args=[strictcli.Arg(name="name", help="a name")],
318
318
  )
319
319
  @strictcli.flag("count", type=int, help="a count", default=0)
320
- @strictcli.flag("force", type=bool, default=False, help="force it")
320
+ @strictcli.flag("force-it", type=bool, default=False, help="force it")
321
321
  def cmd_handler(**kwargs):
322
322
  return 0
323
323
 
@@ -8,8 +8,8 @@ def _make_full_app():
8
8
  app = strictcli.App(name="myapp", version="2.0.0", help="a great app")
9
9
 
10
10
  @app.command("init", help="initialize the project")
11
- @strictcli.flag("force", type=bool, default=False, help="overwrite existing files")
12
- def init(force):
11
+ @strictcli.flag("force-overwrite", type=bool, default=False, help="overwrite existing files")
12
+ def init(force_overwrite):
13
13
  pass
14
14
 
15
15
  @app.command(
@@ -117,7 +117,7 @@ def test_help_at_command_level():
117
117
  assert r.exit_code == 0
118
118
  assert "init" in r.stdout
119
119
  assert "initialize the project" in r.stdout
120
- assert "--force" in r.stdout
120
+ assert "--force-overwrite" in r.stdout
121
121
 
122
122
 
123
123
  def test_help_at_group_level():
@@ -232,7 +232,7 @@ wheels = [
232
232
 
233
233
  [[package]]
234
234
  name = "strictcli"
235
- version = "0.22.0"
235
+ version = "0.23.0"
236
236
  source = { editable = "." }
237
237
 
238
238
  [package.dev-dependencies]
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
File without changes