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.
- {strictcli-0.22.0 → strictcli-0.23.0}/CHANGELOG.md +9 -3
- {strictcli-0.22.0 → strictcli-0.23.0}/PKG-INFO +1 -1
- {strictcli-0.22.0 → strictcli-0.23.0}/package-lock.json +2 -2
- {strictcli-0.22.0 → strictcli-0.23.0}/package.json +1 -1
- {strictcli-0.22.0 → strictcli-0.23.0}/pyproject.toml +1 -1
- {strictcli-0.22.0 → strictcli-0.23.0}/strictcli/__init__.py +12 -1
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_dump_schema.py +21 -21
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_e2e.py +1 -1
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_help.py +3 -3
- {strictcli-0.22.0 → strictcli-0.23.0}/uv.lock +1 -1
- {strictcli-0.22.0 → strictcli-0.23.0}/.claude/settings.json +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/.github/workflows/ci.yml +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/.github/workflows/publish.yml +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/.gitignore +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/.npmignore +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/.rlsbl/config.json +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/.rlsbl/hashes.json +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/.rlsbl/version +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/.strictcli/schema.json +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/CLAUDE.md +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/LICENSE +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/README.md +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/index.js +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/postinstall.js +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_arg_default.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_at_prefix.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_auto_version.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_call.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_check_command.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_check_discovery.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_check_public_api.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_check_runner.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_check_schema.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_check_types.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_choices.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_command_help_suggestion.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_command_tags.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_compound_types.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_config.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_config_fields.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_config_file_path.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_config_set_bugs.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_context.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_deep_nesting.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_dependencies.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_deprecated.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_env.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_exit_codes.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_flag_sets.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_float_type.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_global_flags.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_int_type.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_invoke.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_keyword_flags.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_mcp.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_mutex.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_nesting.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_parser.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_passthrough.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_registration.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_repeatable.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_tagdsl.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_toml_loading.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_tool_export.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_typed_args.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_unique.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_utilities.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_validate.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_variadic.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/tests/test_visibility.py +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/todo/.defer/deferred.md +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/todo/.done/keyword-collision-in-flag-param-name.md +0 -0
- {strictcli-0.22.0 → strictcli-0.23.0}/todo/.done/original-idea.md +0 -0
- {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.
|
|
5
|
+
## 0.23.0
|
|
6
6
|
|
|
7
|
-
|
|
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
|
-
|
|
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,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "strictcli",
|
|
3
|
-
"version": "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.
|
|
9
|
+
"version": "0.23.0",
|
|
10
10
|
"hasInstallScript": true,
|
|
11
11
|
"license": "MIT"
|
|
12
12
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
__version__ = "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,
|
|
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(
|
|
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(
|
|
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", "
|
|
900
|
+
flags=["dry-run", "skip-confirm"])])
|
|
901
901
|
@strictcli.flag("dry-run", type=bool, default=False, help="Dry run")
|
|
902
|
-
@strictcli.flag("
|
|
903
|
-
def deploy(dry_run,
|
|
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", "
|
|
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(
|
|
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(
|
|
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(
|
|
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():
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|