claude-dev-env 2.27.0 → 2.28.1

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 (109) hide show
  1. package/.agents/skills/hitl/SKILL.md +45 -0
  2. package/.agents/skills-archived/pr-loop-cloud-transport/reference/identity-and-hooks.md +1 -1
  3. package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +16 -29
  4. package/audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md +2 -2
  5. package/bin/ever-shipped-skills.mjs +1 -0
  6. package/docs/CODE_RULES.md +3 -3
  7. package/docs/agent-spawn-protocol.md +1 -1
  8. package/docs/references/prose-style-enforcement.md +5 -9
  9. package/hooks/blocking/AGENTS.md +3 -13
  10. package/hooks/blocking/code_rules_docstrings.py +10 -2138
  11. package/hooks/blocking/code_rules_enforcer.py +0 -121
  12. package/hooks/blocking/code_rules_imports_logging.py +1 -236
  13. package/hooks/blocking/code_rules_shared.py +23 -0
  14. package/hooks/blocking/code_rules_test_layout.py +8 -8
  15. package/hooks/blocking/config/prose_style_enforcement_constants.py +4 -2
  16. package/hooks/blocking/config/test_prose_style_enforcement_constants.py +5 -1
  17. package/hooks/blocking/precommit_code_rules_gate.py +8 -43
  18. package/hooks/blocking/state_description_blocker.py +1 -7
  19. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +0 -1
  20. package/hooks/blocking/test_code_rules_enforcer_dispatch_wiring.py +0 -8
  21. package/hooks/blocking/test_code_rules_enforcer_module_docstring_roster.py +11 -112
  22. package/hooks/blocking/test_code_rules_enforcer_narrow_edit.py +0 -1
  23. package/hooks/blocking/test_code_rules_enforcer_split_entry_1.py +1 -18
  24. package/hooks/blocking/test_code_rules_shared.py +12 -0
  25. package/hooks/blocking/test_precommit_code_rules_gate.py +32 -179
  26. package/hooks/blocking/test_precommit_code_rules_gate_native_owner.py +0 -1
  27. package/hooks/blocking/test_state_description_blocker.py +6 -4
  28. package/hooks/blocking/test_stop_dispatcher.py +5 -29
  29. package/hooks/hooks_constants/AGENTS.md +1 -6
  30. package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +0 -1
  31. package/hooks/hooks_constants/code_rules_enforcer_constants.py +3 -0
  32. package/hooks/hooks_constants/messages.py +0 -2
  33. package/hooks/hooks_constants/precommit_code_rules_gate_constants.py +3 -17
  34. package/hooks/hooks_constants/stop_dispatcher_constants.py +0 -2
  35. package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +0 -1
  36. package/hooks/hooks_constants/test_code_rules_enforcer_constants.py +7 -0
  37. package/hooks/hooks_constants/test_messages.py +5 -3
  38. package/hooks/hooks_constants/test_stop_dispatcher_constants.py +0 -2
  39. package/package.json +1 -1
  40. package/rules/AGENTS.md +3 -6
  41. package/rules/claims-as-quotes.md +0 -10
  42. package/rules/code-standards.md +6 -6
  43. package/rules/explore-thoroughly.md +0 -1
  44. package/rules/failure-blast-radius.md +0 -8
  45. package/rules/falsify-before-green.md +0 -8
  46. package/rules/file-global-constants.md +2 -2
  47. package/rules/filesystem-search.md +1 -1
  48. package/rules/git-workflow.md +1 -9
  49. package/rules/hedging-claims.md +2 -6
  50. package/rules/long-horizon-autonomy.md +1 -1
  51. package/rules/measurement-denominators.md +0 -9
  52. package/rules/research-mode.md +0 -6
  53. package/rules/verify-before-asking.md +0 -5
  54. package/rules/verify-runtime-state.md +0 -5
  55. package/scripts/codex_compat_materializer.py +99 -24
  56. package/scripts/sync_to_cursor/rules.py +0 -10
  57. package/scripts/tests/test_codex_compat_materializer.py +70 -19
  58. package/scripts/tests/test_engine.py +0 -1
  59. package/scripts/tests/test_rules.py +0 -1
  60. package/scripts/tests/test_sync_to_cursor.py +0 -1
  61. package/system-prompts/software-engineer.xml +3 -3
  62. package/hooks/blocking/code_rules_dead_argparse_argument.py +0 -554
  63. package/hooks/blocking/code_rules_dead_config_field.py +0 -568
  64. package/hooks/blocking/code_rules_dead_dataclass_field.py +0 -348
  65. package/hooks/blocking/code_rules_dead_module_constant.py +0 -757
  66. package/hooks/blocking/code_rules_dead_split_branch.py +0 -225
  67. package/hooks/blocking/code_rules_mock_completeness.py +0 -295
  68. package/hooks/blocking/code_rules_scope_binding.py +0 -151
  69. package/hooks/blocking/code_rules_unused_imports.py +0 -197
  70. package/hooks/blocking/hedging_language_blocker.py +0 -221
  71. package/hooks/blocking/intent_only_ending_blocker.py +0 -148
  72. package/hooks/blocking/test_code_rules_enforcer_dead_argparse_argument.py +0 -534
  73. package/hooks/blocking/test_code_rules_enforcer_dead_config_field.py +0 -846
  74. package/hooks/blocking/test_code_rules_enforcer_dead_dataclass_field.py +0 -507
  75. package/hooks/blocking/test_code_rules_enforcer_dead_module_constant.py +0 -679
  76. package/hooks/blocking/test_code_rules_enforcer_dead_module_constant_alias.py +0 -133
  77. package/hooks/blocking/test_code_rules_enforcer_dead_module_constant_read_cap.py +0 -103
  78. package/hooks/blocking/test_code_rules_enforcer_dead_split_branch.py +0 -105
  79. package/hooks/blocking/test_code_rules_enforcer_docstring_args_span_scope.py +0 -425
  80. package/hooks/blocking/test_code_rules_enforcer_docstring_cardinal_family.py +0 -176
  81. package/hooks/blocking/test_code_rules_enforcer_docstring_delegation_summary.py +0 -385
  82. package/hooks/blocking/test_code_rules_enforcer_docstring_fallback_branch.py +0 -398
  83. package/hooks/blocking/test_code_rules_enforcer_docstring_field_runmode_outcome.py +0 -129
  84. package/hooks/blocking/test_code_rules_enforcer_docstring_inline_literal_claim.py +0 -93
  85. package/hooks/blocking/test_code_rules_enforcer_docstring_length_constant_superlative.py +0 -198
  86. package/hooks/blocking/test_code_rules_enforcer_docstring_mark_glyph_enumeration.py +0 -262
  87. package/hooks/blocking/test_code_rules_enforcer_docstring_no_consumer.py +0 -93
  88. package/hooks/blocking/test_code_rules_enforcer_docstring_no_network.py +0 -115
  89. package/hooks/blocking/test_code_rules_enforcer_docstring_raises_largezipfile.py +0 -226
  90. package/hooks/blocking/test_code_rules_enforcer_docstring_returns_plural_cardinality.py +0 -207
  91. package/hooks/blocking/test_code_rules_enforcer_docstring_step_dispatch.py +0 -262
  92. package/hooks/blocking/test_code_rules_enforcer_docstring_type_checking_gate.py +0 -164
  93. package/hooks/blocking/test_code_rules_enforcer_docstring_unguarded_payload.py +0 -188
  94. package/hooks/blocking/test_code_rules_enforcer_import_block_sort.py +0 -157
  95. package/hooks/blocking/test_code_rules_enforcer_split_mocks_1.py +0 -303
  96. package/hooks/blocking/test_code_rules_enforcer_split_mocks_2.py +0 -111
  97. package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +0 -656
  98. package/hooks/blocking/test_hedging_language_blocker.py +0 -261
  99. package/hooks/blocking/test_intent_only_ending_blocker.py +0 -209
  100. package/hooks/hooks_constants/dead_argparse_argument_constants.py +0 -28
  101. package/hooks/hooks_constants/dead_config_field_constants.py +0 -39
  102. package/hooks/hooks_constants/dead_dataclass_field_constants.py +0 -25
  103. package/hooks/hooks_constants/dead_module_constant_constants.py +0 -30
  104. package/hooks/hooks_constants/hedging_uncertainty_constants.py +0 -42
  105. package/hooks/hooks_constants/test_dispatcher_constants_docstrings.py +0 -44
  106. package/rules/conservative-action.md +0 -17
  107. package/rules/context7.md +0 -8
  108. package/rules/few-words.md +0 -3
  109. package/rules/parallel-tools.md +0 -23
@@ -1,534 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import importlib.util
4
- from pathlib import Path
5
-
6
- ENFORCER_PATH = Path(__file__).resolve().parent / "code_rules_enforcer.py"
7
- specification = importlib.util.spec_from_file_location("code_rules_enforcer", ENFORCER_PATH)
8
- assert specification is not None and specification.loader is not None
9
- code_rules_enforcer = importlib.util.module_from_spec(specification)
10
- specification.loader.exec_module(code_rules_enforcer)
11
-
12
- PRODUCTION_FILE_PATH = "packages/app/services/render_report.py"
13
- TEST_FILE_PATH = "packages/app/services/test_render_report.py"
14
- MIGRATION_FILE_PATH = "packages/app/migrations/0001_initial.py"
15
-
16
-
17
- def _check(source: str, file_path: str) -> list[str]:
18
- return code_rules_enforcer.check_dead_argparse_arguments(source, file_path)
19
-
20
-
21
- def test_should_flag_optional_argument_whose_dest_is_never_read() -> None:
22
- source = (
23
- "import argparse\n"
24
- "\n"
25
- "def build() -> None:\n"
26
- " argument_parser = argparse.ArgumentParser()\n"
27
- " argument_parser.add_argument('--repo', default='.')\n"
28
- " parsed_arguments = argument_parser.parse_args()\n"
29
- " print('done')\n"
30
- )
31
- issues = _check(source, PRODUCTION_FILE_PATH)
32
- assert any("'repo'" in each_issue for each_issue in issues), (
33
- f"Expected dead '--repo' argument flagged, got: {issues}"
34
- )
35
- assert any("Line 5" in each_issue for each_issue in issues), (
36
- f"Expected the add_argument line reported, got: {issues}"
37
- )
38
-
39
-
40
- def test_should_not_flag_when_dest_is_read_via_attribute_access() -> None:
41
- source = (
42
- "import argparse\n"
43
- "\n"
44
- "def build() -> str:\n"
45
- " argument_parser = argparse.ArgumentParser()\n"
46
- " argument_parser.add_argument('--repo', default='.')\n"
47
- " parsed_arguments = argument_parser.parse_args()\n"
48
- " return parsed_arguments.repo\n"
49
- )
50
- assert _check(source, PRODUCTION_FILE_PATH) == []
51
-
52
-
53
- def test_should_not_flag_a_positional_argument() -> None:
54
- source = (
55
- "import argparse\n"
56
- "\n"
57
- "def build() -> None:\n"
58
- " argument_parser = argparse.ArgumentParser()\n"
59
- " argument_parser.add_argument('path')\n"
60
- " parsed_arguments = argument_parser.parse_args()\n"
61
- " print('done')\n"
62
- )
63
- assert _check(source, PRODUCTION_FILE_PATH) == []
64
-
65
-
66
- def test_should_not_flag_help_action_argument() -> None:
67
- source = (
68
- "import argparse\n"
69
- "\n"
70
- "def build() -> None:\n"
71
- " argument_parser = argparse.ArgumentParser(add_help=False)\n"
72
- " argument_parser.add_argument('--help', action='help')\n"
73
- " parsed_arguments = argument_parser.parse_args()\n"
74
- " print('done')\n"
75
- )
76
- assert _check(source, PRODUCTION_FILE_PATH) == []
77
-
78
-
79
- def test_should_not_flag_version_action_argument() -> None:
80
- source = (
81
- "import argparse\n"
82
- "\n"
83
- "def build() -> None:\n"
84
- " argument_parser = argparse.ArgumentParser()\n"
85
- " argument_parser.add_argument('--version', action='version', version='1.0')\n"
86
- " parsed_arguments = argument_parser.parse_args()\n"
87
- " print('done')\n"
88
- )
89
- assert _check(source, PRODUCTION_FILE_PATH) == []
90
-
91
-
92
- def test_should_respect_explicit_dest_when_unread() -> None:
93
- source = (
94
- "import argparse\n"
95
- "\n"
96
- "def build() -> None:\n"
97
- " argument_parser = argparse.ArgumentParser()\n"
98
- " argument_parser.add_argument('--repo', dest='repository')\n"
99
- " parsed_arguments = argument_parser.parse_args()\n"
100
- " print('done')\n"
101
- )
102
- issues = _check(source, PRODUCTION_FILE_PATH)
103
- assert any("'repository'" in each_issue for each_issue in issues), (
104
- f"Expected the explicit dest flagged, got: {issues}"
105
- )
106
- assert not any("'repo'" in each_issue for each_issue in issues), (
107
- f"The option string must not be flagged when dest is explicit, got: {issues}"
108
- )
109
-
110
-
111
- def test_should_respect_explicit_dest_when_read() -> None:
112
- source = (
113
- "import argparse\n"
114
- "\n"
115
- "def build() -> str:\n"
116
- " argument_parser = argparse.ArgumentParser()\n"
117
- " argument_parser.add_argument('--repo', dest='repository')\n"
118
- " parsed_arguments = argument_parser.parse_args()\n"
119
- " return parsed_arguments.repository\n"
120
- )
121
- assert _check(source, PRODUCTION_FILE_PATH) == []
122
-
123
-
124
- def test_should_skip_argument_with_non_literal_dest() -> None:
125
- source = (
126
- "import argparse\n"
127
- "\n"
128
- "DEST_NAME = 'repository'\n"
129
- "\n"
130
- "def build() -> str:\n"
131
- " argument_parser = argparse.ArgumentParser()\n"
132
- " argument_parser.add_argument('--repo', dest=DEST_NAME)\n"
133
- " parsed_arguments = argument_parser.parse_args()\n"
134
- " return parsed_arguments.repository\n"
135
- )
136
- assert _check(source, PRODUCTION_FILE_PATH) == []
137
-
138
-
139
- def test_should_derive_dest_from_dashed_long_option() -> None:
140
- source = (
141
- "import argparse\n"
142
- "\n"
143
- "def build() -> bool:\n"
144
- " argument_parser = argparse.ArgumentParser()\n"
145
- " argument_parser.add_argument('--dry-run', action='store_true')\n"
146
- " parsed_arguments = argument_parser.parse_args()\n"
147
- " return parsed_arguments.dry_run\n"
148
- )
149
- assert _check(source, PRODUCTION_FILE_PATH) == []
150
-
151
-
152
- def test_should_flag_dashed_long_option_when_dest_unread() -> None:
153
- source = (
154
- "import argparse\n"
155
- "\n"
156
- "def build() -> None:\n"
157
- " argument_parser = argparse.ArgumentParser()\n"
158
- " argument_parser.add_argument('--dry-run', action='store_true')\n"
159
- " parsed_arguments = argument_parser.parse_args()\n"
160
- " print('done')\n"
161
- )
162
- issues = _check(source, PRODUCTION_FILE_PATH)
163
- assert any("'dry_run'" in each_issue for each_issue in issues), (
164
- f"Expected the dashed long option dest flagged, got: {issues}"
165
- )
166
-
167
-
168
- def test_should_derive_dest_from_long_option_when_short_precedes() -> None:
169
- source = (
170
- "import argparse\n"
171
- "\n"
172
- "def build() -> None:\n"
173
- " argument_parser = argparse.ArgumentParser()\n"
174
- " argument_parser.add_argument('-r', '--repo')\n"
175
- " parsed_arguments = argument_parser.parse_args()\n"
176
- " print('done')\n"
177
- )
178
- issues = _check(source, PRODUCTION_FILE_PATH)
179
- assert any("'repo'" in each_issue for each_issue in issues), (
180
- f"Expected dest derived from the long option, got: {issues}"
181
- )
182
-
183
-
184
- def test_should_suppress_when_namespace_is_forwarded_to_a_call() -> None:
185
- source = (
186
- "import argparse\n"
187
- "\n"
188
- "def run(parsed_arguments: argparse.Namespace) -> None:\n"
189
- " print('run')\n"
190
- "\n"
191
- "def build() -> None:\n"
192
- " argument_parser = argparse.ArgumentParser()\n"
193
- " argument_parser.add_argument('--repo', default='.')\n"
194
- " parsed_arguments = argument_parser.parse_args()\n"
195
- " run(parsed_arguments)\n"
196
- )
197
- assert _check(source, PRODUCTION_FILE_PATH) == []
198
-
199
-
200
- def test_should_suppress_when_tuple_unpacked_namespace_is_forwarded() -> None:
201
- source = (
202
- "import argparse\n"
203
- "\n"
204
- "def main(parsed_arguments: argparse.Namespace) -> None:\n"
205
- " print('run')\n"
206
- "\n"
207
- "def build() -> None:\n"
208
- " argument_parser = argparse.ArgumentParser()\n"
209
- " argument_parser.add_argument('--verbose', action='store_true')\n"
210
- " parsed_arguments, remaining = argument_parser.parse_known_args()\n"
211
- " main(parsed_arguments)\n"
212
- )
213
- assert _check(source, PRODUCTION_FILE_PATH) == []
214
-
215
-
216
- def test_should_suppress_when_aliased_namespace_is_forwarded() -> None:
217
- source = (
218
- "import argparse\n"
219
- "\n"
220
- "def run(parsed_arguments: argparse.Namespace) -> None:\n"
221
- " print('run')\n"
222
- "\n"
223
- "def build() -> None:\n"
224
- " argument_parser = argparse.ArgumentParser()\n"
225
- " argument_parser.add_argument('--repo', default='.')\n"
226
- " parsed_arguments = argument_parser.parse_args()\n"
227
- " alias = parsed_arguments\n"
228
- " run(alias)\n"
229
- )
230
- assert _check(source, PRODUCTION_FILE_PATH) == []
231
-
232
-
233
- def test_should_suppress_when_namespace_consumed_by_vars() -> None:
234
- source = (
235
- "import argparse\n"
236
- "\n"
237
- "def build() -> dict:\n"
238
- " argument_parser = argparse.ArgumentParser()\n"
239
- " argument_parser.add_argument('--repo', default='.')\n"
240
- " parsed_arguments = argument_parser.parse_args()\n"
241
- " return vars(parsed_arguments)\n"
242
- )
243
- assert _check(source, PRODUCTION_FILE_PATH) == []
244
-
245
-
246
- def test_should_suppress_when_namespace_dict_accessed() -> None:
247
- source = (
248
- "import argparse\n"
249
- "\n"
250
- "def build() -> dict:\n"
251
- " argument_parser = argparse.ArgumentParser()\n"
252
- " argument_parser.add_argument('--repo', default='.')\n"
253
- " parsed_arguments = argument_parser.parse_args()\n"
254
- " return parsed_arguments.__dict__\n"
255
- )
256
- assert _check(source, PRODUCTION_FILE_PATH) == []
257
-
258
-
259
- def test_should_be_silent_for_test_files() -> None:
260
- source = (
261
- "import argparse\n"
262
- "\n"
263
- "def build() -> None:\n"
264
- " argument_parser = argparse.ArgumentParser()\n"
265
- " argument_parser.add_argument('--repo', default='.')\n"
266
- " parsed_arguments = argument_parser.parse_args()\n"
267
- " print('done')\n"
268
- )
269
- assert _check(source, TEST_FILE_PATH) == []
270
-
271
-
272
- def test_should_be_silent_for_migration_files() -> None:
273
- source = (
274
- "import argparse\n"
275
- "\n"
276
- "def build() -> None:\n"
277
- " argument_parser = argparse.ArgumentParser()\n"
278
- " argument_parser.add_argument('--repo', default='.')\n"
279
- " parsed_arguments = argument_parser.parse_args()\n"
280
- " print('done')\n"
281
- )
282
- assert _check(source, MIGRATION_FILE_PATH) == []
283
-
284
-
285
- def test_should_tolerate_syntax_error() -> None:
286
- assert _check("def broken(:\n", PRODUCTION_FILE_PATH) == []
287
-
288
-
289
- def test_should_return_empty_when_no_add_argument_present() -> None:
290
- source = "def build() -> int:\n return 1\n"
291
- assert _check(source, PRODUCTION_FILE_PATH) == []
292
-
293
-
294
- def test_should_suppress_when_namespace_unpacked_with_double_star() -> None:
295
- source = (
296
- "import argparse\n"
297
- "\n"
298
- "def run(repo: str) -> None:\n"
299
- " print(repo)\n"
300
- "\n"
301
- "def build() -> None:\n"
302
- " argument_parser = argparse.ArgumentParser()\n"
303
- " argument_parser.add_argument('--repo', default='.')\n"
304
- " parsed_arguments = argument_parser.parse_args()\n"
305
- " run(**vars(parsed_arguments))\n"
306
- )
307
- assert _check(source, PRODUCTION_FILE_PATH) == []
308
-
309
-
310
- def test_should_not_flag_when_dest_read_via_literal_getattr() -> None:
311
- source = (
312
- "import argparse\n"
313
- "\n"
314
- "def build() -> str:\n"
315
- " argument_parser = argparse.ArgumentParser()\n"
316
- " argument_parser.add_argument('--repo', default='.')\n"
317
- " parsed_arguments = argument_parser.parse_args()\n"
318
- " return getattr(parsed_arguments, 'repo')\n"
319
- )
320
- assert _check(source, PRODUCTION_FILE_PATH) == []
321
-
322
-
323
- def test_should_suppress_when_namespace_stored_on_attribute_target() -> None:
324
- source = (
325
- "import argparse\n"
326
- "\n"
327
- "class App:\n"
328
- " def build(self) -> None:\n"
329
- " argument_parser = argparse.ArgumentParser()\n"
330
- " argument_parser.add_argument('--repo', default='.')\n"
331
- " argument_parser.add_argument('--verbose', action='store_true')\n"
332
- " self.parsed_arguments = argument_parser.parse_args()\n"
333
- "\n"
334
- " def run(self) -> dict:\n"
335
- " return dict(**vars(self.parsed_arguments))\n"
336
- )
337
- assert _check(source, PRODUCTION_FILE_PATH) == []
338
-
339
-
340
- def test_should_suppress_when_parse_method_is_aliased() -> None:
341
- source = (
342
- "import argparse\n"
343
- "\n"
344
- "def run(parsed_arguments: argparse.Namespace) -> None:\n"
345
- " print('run')\n"
346
- "\n"
347
- "def build() -> None:\n"
348
- " argument_parser = argparse.ArgumentParser()\n"
349
- " argument_parser.add_argument('--repo', default='.')\n"
350
- " parse = argument_parser.parse_args\n"
351
- " parsed_arguments = parse()\n"
352
- " run(parsed_arguments)\n"
353
- )
354
- assert _check(source, PRODUCTION_FILE_PATH) == []
355
-
356
-
357
- def test_should_suppress_when_namespace_forwarded_inside_container() -> None:
358
- source = (
359
- "import argparse\n"
360
- "\n"
361
- "def run(payload: dict) -> None:\n"
362
- " print('run')\n"
363
- "\n"
364
- "def build() -> None:\n"
365
- " argument_parser = argparse.ArgumentParser()\n"
366
- " argument_parser.add_argument('--repo', default='.')\n"
367
- " parsed_arguments = argument_parser.parse_args()\n"
368
- " run({'args': parsed_arguments})\n"
369
- )
370
- assert _check(source, PRODUCTION_FILE_PATH) == []
371
-
372
-
373
- def test_should_suppress_when_namespace_keyword_object_is_consumed() -> None:
374
- source = (
375
- "import argparse\n"
376
- "\n"
377
- "def build() -> dict:\n"
378
- " options = argparse.Namespace()\n"
379
- " argument_parser = argparse.ArgumentParser()\n"
380
- " argument_parser.add_argument('--repo', default='.')\n"
381
- " argument_parser.parse_args(namespace=options)\n"
382
- " return vars(options)\n"
383
- )
384
- assert _check(source, PRODUCTION_FILE_PATH) == []
385
-
386
-
387
- def test_should_suppress_when_namespace_keyword_object_is_forwarded() -> None:
388
- source = (
389
- "import argparse\n"
390
- "\n"
391
- "def run(parsed_arguments: argparse.Namespace) -> None:\n"
392
- " print('run')\n"
393
- "\n"
394
- "def build() -> None:\n"
395
- " options = argparse.Namespace()\n"
396
- " argument_parser = argparse.ArgumentParser()\n"
397
- " argument_parser.add_argument('--repo', default='.')\n"
398
- " argument_parser.parse_args(namespace=options)\n"
399
- " run(options)\n"
400
- )
401
- assert _check(source, PRODUCTION_FILE_PATH) == []
402
-
403
-
404
- def test_should_flag_when_namespace_keyword_object_only_attribute_read() -> None:
405
- source = (
406
- "import argparse\n"
407
- "\n"
408
- "def build() -> str:\n"
409
- " options = argparse.Namespace()\n"
410
- " argument_parser = argparse.ArgumentParser()\n"
411
- " argument_parser.add_argument('--repo', default='.')\n"
412
- " argument_parser.add_argument('--verbose', action='store_true')\n"
413
- " argument_parser.parse_args(namespace=options)\n"
414
- " return options.repo\n"
415
- )
416
- issues = _check(source, PRODUCTION_FILE_PATH)
417
- assert any("'verbose'" in each_issue for each_issue in issues), (
418
- f"Expected the unread '--verbose' flag flagged, got: {issues}"
419
- )
420
- assert not any("'repo'" in each_issue for each_issue in issues), (
421
- f"The read namespace= attribute must not be flagged, got: {issues}"
422
- )
423
-
424
-
425
- def test_should_suppress_when_parse_result_consumed_by_vars_inline() -> None:
426
- source = (
427
- "import argparse\n"
428
- "\n"
429
- "def build() -> dict:\n"
430
- " argument_parser = argparse.ArgumentParser()\n"
431
- " argument_parser.add_argument('--repo', default='.')\n"
432
- " return vars(argument_parser.parse_args())\n"
433
- )
434
- assert _check(source, PRODUCTION_FILE_PATH) == []
435
-
436
-
437
- def test_should_suppress_when_parse_result_returned_inline() -> None:
438
- source = (
439
- "import argparse\n"
440
- "\n"
441
- "def build() -> argparse.Namespace:\n"
442
- " argument_parser = argparse.ArgumentParser()\n"
443
- " argument_parser.add_argument('--repo', default='.')\n"
444
- " return argument_parser.parse_args()\n"
445
- )
446
- assert _check(source, PRODUCTION_FILE_PATH) == []
447
-
448
-
449
- def test_should_suppress_when_parse_result_double_star_unpacked_inline() -> None:
450
- source = (
451
- "import argparse\n"
452
- "\n"
453
- "def run(repo: str) -> None:\n"
454
- " print(repo)\n"
455
- "\n"
456
- "def build() -> None:\n"
457
- " argument_parser = argparse.ArgumentParser()\n"
458
- " argument_parser.add_argument('--repo', default='.')\n"
459
- " run(**vars(argument_parser.parse_args()))\n"
460
- )
461
- assert _check(source, PRODUCTION_FILE_PATH) == []
462
-
463
-
464
- def test_should_flag_when_annotated_namespace_dest_is_unread() -> None:
465
- source = (
466
- "import argparse\n"
467
- "\n"
468
- "def build() -> None:\n"
469
- " argument_parser = argparse.ArgumentParser()\n"
470
- " argument_parser.add_argument('--repo', default='.')\n"
471
- " parsed_arguments: argparse.Namespace = argument_parser.parse_args()\n"
472
- " print('done')\n"
473
- )
474
- issues = _check(source, PRODUCTION_FILE_PATH)
475
- assert any("'repo'" in each_issue for each_issue in issues), (
476
- f"Expected the dead flag flagged through the annotated binding, got: {issues}"
477
- )
478
-
479
-
480
- def test_should_not_flag_when_annotated_namespace_dest_is_read() -> None:
481
- source = (
482
- "import argparse\n"
483
- "\n"
484
- "def build() -> str:\n"
485
- " argument_parser = argparse.ArgumentParser()\n"
486
- " argument_parser.add_argument('--repo', default='.')\n"
487
- " parsed_arguments: argparse.Namespace = argument_parser.parse_args()\n"
488
- " return parsed_arguments.repo\n"
489
- )
490
- assert _check(source, PRODUCTION_FILE_PATH) == []
491
-
492
-
493
- def test_should_suppress_when_annotated_namespace_is_forwarded() -> None:
494
- source = (
495
- "import argparse\n"
496
- "\n"
497
- "def run(parsed_arguments: argparse.Namespace) -> None:\n"
498
- " print('run')\n"
499
- "\n"
500
- "def build() -> None:\n"
501
- " argument_parser = argparse.ArgumentParser()\n"
502
- " argument_parser.add_argument('--repo', default='.')\n"
503
- " parsed_arguments: argparse.Namespace = argument_parser.parse_args()\n"
504
- " run(parsed_arguments)\n"
505
- )
506
- assert _check(source, PRODUCTION_FILE_PATH) == []
507
-
508
-
509
- def test_should_suppress_when_parse_result_bound_by_walrus() -> None:
510
- source = (
511
- "import argparse\n"
512
- "\n"
513
- "def build() -> dict:\n"
514
- " argument_parser = argparse.ArgumentParser()\n"
515
- " argument_parser.add_argument('--repo', default='.')\n"
516
- " return vars(parsed_arguments := argument_parser.parse_args())\n"
517
- )
518
- assert _check(source, PRODUCTION_FILE_PATH) == []
519
-
520
-
521
- def test_validate_content_runs_dead_argparse_check() -> None:
522
- source = (
523
- "import argparse\n"
524
- "\n"
525
- "def build() -> None:\n"
526
- " argument_parser = argparse.ArgumentParser()\n"
527
- " argument_parser.add_argument('--repo', default='.')\n"
528
- " parsed_arguments = argument_parser.parse_args()\n"
529
- " print('done')\n"
530
- )
531
- issues = code_rules_enforcer.validate_content(source, PRODUCTION_FILE_PATH)
532
- assert any("'repo'" in each_issue for each_issue in issues), (
533
- f"Expected the enforcer dispatch to surface the dead argument, got: {issues}"
534
- )