claude-dev-env 1.77.0 → 1.79.0

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 (46) hide show
  1. package/audit-rubrics/category_rubrics/category-k-codebase-conflicts.md +1 -0
  2. package/bin/install.mjs +1 -0
  3. package/bin/install.test.mjs +3 -2
  4. package/hooks/blocking/CLAUDE.md +5 -2
  5. package/hooks/blocking/code_rules_dead_module_constant.py +215 -59
  6. package/hooks/blocking/code_rules_dead_split_branch.py +225 -0
  7. package/hooks/blocking/code_rules_docstrings.py +951 -6
  8. package/hooks/blocking/code_rules_enforcer.py +64 -0
  9. package/hooks/blocking/code_rules_naming_collection.py +76 -1
  10. package/hooks/blocking/code_rules_paired_test.py +517 -0
  11. package/hooks/blocking/code_rules_string_magic.py +71 -1
  12. package/hooks/blocking/code_rules_test_assertions.py +159 -1
  13. package/hooks/blocking/convergence_gate_blocker.py +24 -15
  14. package/hooks/blocking/env_var_table_code_drift_blocker.py +475 -0
  15. package/hooks/blocking/package_inventory_stale_blocker.py +54 -15
  16. package/hooks/blocking/test_code_rules_enforcer_dead_module_constant.py +89 -2
  17. package/hooks/blocking/test_code_rules_enforcer_dead_split_branch.py +105 -0
  18. package/hooks/blocking/test_code_rules_enforcer_docstring_field_runmode_outcome.py +129 -0
  19. package/hooks/blocking/test_code_rules_enforcer_docstring_length_constant_superlative.py +198 -0
  20. package/hooks/blocking/test_code_rules_enforcer_docstring_mark_glyph_enumeration.py +262 -0
  21. package/hooks/blocking/test_code_rules_enforcer_docstring_no_network.py +115 -0
  22. package/hooks/blocking/test_code_rules_enforcer_docstring_raises_largezipfile.py +226 -0
  23. package/hooks/blocking/test_code_rules_enforcer_docstring_unreferenced_param.py +160 -0
  24. package/hooks/blocking/test_code_rules_enforcer_module_docstring_data_schema_scope.py +82 -0
  25. package/hooks/blocking/test_code_rules_enforcer_paired_test.py +339 -0
  26. package/hooks/blocking/test_code_rules_enforcer_polarity_name_contradiction.py +76 -0
  27. package/hooks/blocking/test_code_rules_enforcer_vacuous_cleanup_assertion.py +132 -0
  28. package/hooks/blocking/test_code_rules_enforcer_whitespace_indentation_magic.py +74 -0
  29. package/hooks/blocking/test_convergence_gate_blocker.py +71 -0
  30. package/hooks/blocking/test_env_var_table_code_drift_blocker.py +94 -0
  31. package/hooks/blocking/test_package_inventory_stale_blocker.py +46 -0
  32. package/hooks/blocking/test_pre_tool_use_dispatcher.py +7 -7
  33. package/hooks/hooks_constants/CLAUDE.md +2 -0
  34. package/hooks/hooks_constants/blocking_check_limits.py +102 -0
  35. package/hooks/hooks_constants/code_rules_enforcer_constants.py +28 -0
  36. package/hooks/hooks_constants/env_var_table_code_drift_constants.py +64 -0
  37. package/hooks/hooks_constants/package_inventory_stale_blocker_constants.py +20 -9
  38. package/hooks/hooks_constants/paired_test_coverage_constants.py +35 -0
  39. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +4 -0
  40. package/package.json +1 -1
  41. package/rules/CLAUDE.md +2 -0
  42. package/rules/docstring-prose-matches-implementation.md +56 -53
  43. package/rules/env-var-table-code-drift.md +24 -0
  44. package/rules/file-global-constants.md +2 -2
  45. package/rules/package-inventory-stale-entry.md +4 -4
  46. package/rules/paired-test-coverage.md +35 -0
@@ -152,7 +152,7 @@ def test_does_not_flag_constant_imported_one_directory_up(neutral_root: Path) ->
152
152
  assert issues == [], f"No constant is dead when all are imported, got: {issues}"
153
153
 
154
154
 
155
- def test_does_not_flag_when_module_declares_dunder_all(neutral_root: Path) -> None:
155
+ def test_does_not_flag_dunder_all_member_with_a_live_consumer(neutral_root: Path) -> None:
156
156
  constants_body = CONSTANTS_BODY + '__all__ = ["MEDIUM_TERMINAL"]\n'
157
157
  consumer_body = (
158
158
  "from report_constants.render_report_constants import MEDIUM_TERMINAL\n"
@@ -164,7 +164,35 @@ def test_does_not_flag_when_module_declares_dunder_all(neutral_root: Path) -> No
164
164
  neutral_root / "workflow", constants_body, consumer_body
165
165
  )
166
166
  issues = _check(constants_body, str(constants_path))
167
- assert issues == [], f"__all__ surface suppresses the check, got: {issues}"
167
+ assert issues == [], f"An __all__ member a consumer imports stays live, got: {issues}"
168
+
169
+
170
+ def test_dunder_all_narrows_check_to_exported_constants(neutral_root: Path) -> None:
171
+ constants_body = (
172
+ 'JSONL_APPEND_OPEN_MODE = "a"\n'
173
+ 'ZIPFILE_READ_OPEN_MODE = "r"\n'
174
+ "PRIVATE_BUFFER_BYTES = 1024\n"
175
+ '__all__ = ["JSONL_APPEND_OPEN_MODE", "ZIPFILE_READ_OPEN_MODE"]\n'
176
+ )
177
+ consumer_body = (
178
+ "from report_constants.render_report_constants import JSONL_APPEND_OPEN_MODE\n"
179
+ "\n"
180
+ "def open_mode() -> str:\n"
181
+ " return JSONL_APPEND_OPEN_MODE\n"
182
+ )
183
+ constants_path = _build_constants_package(
184
+ neutral_root / "workflow", constants_body, consumer_body
185
+ )
186
+ issues = _check(constants_body, str(constants_path))
187
+ assert any("ZIPFILE_READ_OPEN_MODE" in each_issue for each_issue in issues), (
188
+ f"An exported constant no module imports is dead, got: {issues}"
189
+ )
190
+ assert not any("JSONL_APPEND_OPEN_MODE" in each_issue for each_issue in issues), (
191
+ f"An exported constant a consumer imports must not be flagged, got: {issues}"
192
+ )
193
+ assert not any("PRIVATE_BUFFER_BYTES" in each_issue for each_issue in issues), (
194
+ f"A constant __all__ omits is the author's private value and is exempt, got: {issues}"
195
+ )
168
196
 
169
197
 
170
198
  def test_does_not_run_on_ordinary_production_module(neutral_root: Path) -> None:
@@ -264,6 +292,65 @@ def test_does_not_flag_constant_used_only_in_a_sibling_tree(neutral_root: Path)
264
292
  )
265
293
 
266
294
 
295
+ def _build_name_collision_repository(
296
+ repository_root: Path,
297
+ promoter_timing_body: str,
298
+ promoter_consumer_body: str,
299
+ harness_constants_body: str,
300
+ harness_consumer_body: str,
301
+ ) -> Path:
302
+ promoter_config = repository_root / "promoter" / "config"
303
+ promoter_config.mkdir(parents=True)
304
+ timing_path = promoter_config / "timing.py"
305
+ timing_path.write_text(promoter_timing_body, encoding="utf-8")
306
+ (repository_root / "promoter" / "runtime.py").write_text(
307
+ promoter_consumer_body, encoding="utf-8"
308
+ )
309
+ harness_config = repository_root / "harness" / "config"
310
+ harness_config.mkdir(parents=True)
311
+ (harness_config / "lockfile_constants.py").write_text(
312
+ harness_constants_body, encoding="utf-8"
313
+ )
314
+ (repository_root / "harness" / "lockfile.py").write_text(
315
+ harness_consumer_body, encoding="utf-8"
316
+ )
317
+ return timing_path
318
+
319
+
320
+ def test_same_named_constant_in_an_unrelated_module_does_not_mask_a_dead_one(
321
+ neutral_root: Path,
322
+ ) -> None:
323
+ promoter_timing_body = "LOCKFILE_ACQUIRE_BYTE_LENGTH = 1\nSWEEP_INTERVAL_SECONDS = 30\n"
324
+ promoter_consumer_body = (
325
+ "from config.timing import SWEEP_INTERVAL_SECONDS\n"
326
+ "\n"
327
+ "def deadline() -> int:\n"
328
+ " return SWEEP_INTERVAL_SECONDS\n"
329
+ )
330
+ harness_constants_body = "LOCKFILE_ACQUIRE_BYTE_LENGTH = 1\n"
331
+ harness_consumer_body = (
332
+ "from harness.config.lockfile_constants import LOCKFILE_ACQUIRE_BYTE_LENGTH\n"
333
+ "\n"
334
+ "def acquire() -> int:\n"
335
+ " return LOCKFILE_ACQUIRE_BYTE_LENGTH\n"
336
+ )
337
+ timing_path = _build_name_collision_repository(
338
+ neutral_root,
339
+ promoter_timing_body,
340
+ promoter_consumer_body,
341
+ harness_constants_body,
342
+ harness_consumer_body,
343
+ )
344
+ issues = _check(promoter_timing_body, str(timing_path))
345
+ assert any("LOCKFILE_ACQUIRE_BYTE_LENGTH" in each_issue for each_issue in issues), (
346
+ "A constant whose only same-named twin lives in an unrelated module's "
347
+ f"import must still be flagged dead, got: {issues}"
348
+ )
349
+ assert not any("SWEEP_INTERVAL_SECONDS" in each_issue for each_issue in issues), (
350
+ f"A constant consumed within its own package must not be flagged, got: {issues}"
351
+ )
352
+
353
+
267
354
  def test_returns_empty_list_at_file_cap(
268
355
  neutral_root: Path, monkeypatch: pytest.MonkeyPatch
269
356
  ) -> None:
@@ -0,0 +1,105 @@
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_PATH = "C:/project/pkg/menu_info_colors.py"
13
+
14
+
15
+ def _check(source: str) -> list[str]:
16
+ return code_rules_enforcer.check_dead_split_truthiness_branch(source, PRODUCTION_PATH)
17
+
18
+
19
+ def test_flags_split_truthiness_else_arm() -> None:
20
+ source = (
21
+ "def _extract_uid_prefix(uid: str) -> str:\n"
22
+ ' all_parts = uid.split("_")\n'
23
+ " if len(all_parts) > 2:\n"
24
+ ' return "_".join(all_parts[:3])\n'
25
+ " return all_parts[0] if all_parts else uid\n"
26
+ )
27
+ issues = _check(source)
28
+ assert len(issues) == 1
29
+ assert "all_parts" in issues[0]
30
+ assert "Line 5" in issues[0]
31
+
32
+
33
+ def test_flags_negated_guard_dead_body() -> None:
34
+ source = (
35
+ "def first_segment(path: str) -> str:\n"
36
+ ' all_segments = path.split("/")\n'
37
+ " if not all_segments:\n"
38
+ " return path\n"
39
+ " return all_segments[0]\n"
40
+ )
41
+ issues = _check(source)
42
+ assert len(issues) == 1
43
+ assert "all_segments" in issues[0]
44
+
45
+
46
+ def test_does_not_flag_split_without_separator() -> None:
47
+ source = (
48
+ "def first_token(text: str) -> str:\n"
49
+ " all_parts = text.split()\n"
50
+ " return all_parts[0] if all_parts else text\n"
51
+ )
52
+ assert _check(source) == []
53
+
54
+
55
+ def test_does_not_flag_length_comparison_guard() -> None:
56
+ source = (
57
+ "def prefix(uid: str) -> str:\n"
58
+ ' all_parts = uid.split("_")\n'
59
+ " if len(all_parts) > 1:\n"
60
+ " return all_parts[0]\n"
61
+ " return uid\n"
62
+ )
63
+ assert _check(source) == []
64
+
65
+
66
+ def test_does_not_flag_when_name_reassigned() -> None:
67
+ source = (
68
+ "def normalize(uid: str) -> str:\n"
69
+ ' all_parts = uid.split("_")\n'
70
+ " all_parts = [each_part.strip() for each_part in all_parts]\n"
71
+ " return all_parts[0] if all_parts else uid\n"
72
+ )
73
+ assert _check(source) == []
74
+
75
+
76
+ def test_does_not_flag_when_name_is_parameter() -> None:
77
+ source = (
78
+ "def shape(all_parts: list[str]) -> str:\n"
79
+ ' all_parts = "x".split("_")\n'
80
+ ' return all_parts[0] if all_parts else ""\n'
81
+ )
82
+ assert _check(source) == []
83
+
84
+
85
+ def test_does_not_flag_attribute_receiver_split() -> None:
86
+ source = (
87
+ "def first(s: object) -> object:\n"
88
+ ' parts = s.str.split(",")\n'
89
+ " return parts[0] if parts else 0\n"
90
+ )
91
+ assert _check(source) == []
92
+
93
+
94
+ def test_does_not_flag_in_test_file() -> None:
95
+ source = (
96
+ "def helper(uid: str) -> str:\n"
97
+ ' all_parts = uid.split("_")\n'
98
+ " return all_parts[0] if all_parts else uid\n"
99
+ )
100
+ assert (
101
+ code_rules_enforcer.check_dead_split_truthiness_branch(
102
+ source, "C:/project/pkg/test_thing.py"
103
+ )
104
+ == []
105
+ )
@@ -0,0 +1,129 @@
1
+ """Tests for check_docstring_field_runmode_outcome — O6 run-mode-vs-per-record drift.
2
+
3
+ A dataclass field whose name marks a run-mode flag (``is_dry_run``) is documented
4
+ in the class Attributes block with per-record write-outcome prose ("True when no
5
+ STP was written"), while the value is set the same way for every record from the
6
+ run mode (``is_dry_run=not is_execute``). An already-OK record in an execute run
7
+ writes no file yet still stores ``False``, so the per-record prose misleads every
8
+ reader. This is the deterministic single-file slice of Category O6 drift.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import importlib.util
14
+ from pathlib import Path
15
+ from types import ModuleType
16
+
17
+
18
+ def _load_enforcer_module() -> ModuleType:
19
+ module_path = Path(__file__).parent / "code_rules_enforcer.py"
20
+ spec = importlib.util.spec_from_file_location("code_rules_enforcer", module_path)
21
+ assert spec is not None
22
+ assert spec.loader is not None
23
+ module = importlib.util.module_from_spec(spec)
24
+ spec.loader.exec_module(module)
25
+ return module
26
+
27
+
28
+ code_rules_enforcer = _load_enforcer_module()
29
+
30
+
31
+ def check_docstring_field_runmode_outcome(content: str, file_path: str) -> list[str]:
32
+ return code_rules_enforcer.check_docstring_field_runmode_outcome(content, file_path)
33
+
34
+
35
+ PRODUCTION_FILE_PATH = "/project/src/jsonl_writer.py"
36
+ TEST_FILE_PATH = "/project/src/test_jsonl_writer.py"
37
+ HOOK_INFRASTRUCTURE_PATH = "/home/user/.claude/hooks/blocking/example.py"
38
+
39
+
40
+ def _drifted_record() -> str:
41
+ return (
42
+ "@dataclass(frozen=True)\n"
43
+ "class PerThemeJsonlRecord:\n"
44
+ ' """Exact schema for the per-theme JSONL line.\n'
45
+ "\n"
46
+ " Attributes:\n"
47
+ " theme_name: Theme directory basename.\n"
48
+ " is_dry_run: True when no STP was written.\n"
49
+ ' """\n'
50
+ "\n"
51
+ " theme_name: str\n"
52
+ " is_dry_run: bool\n"
53
+ )
54
+
55
+
56
+ def _run_mode_record() -> str:
57
+ return (
58
+ "@dataclass(frozen=True)\n"
59
+ "class PerThemeJsonlRecord:\n"
60
+ ' """Exact schema for the per-theme JSONL line.\n'
61
+ "\n"
62
+ " Attributes:\n"
63
+ " theme_name: Theme directory basename.\n"
64
+ " is_dry_run: True for a dry run; False for an execute run.\n"
65
+ ' """\n'
66
+ "\n"
67
+ " theme_name: str\n"
68
+ " is_dry_run: bool\n"
69
+ )
70
+
71
+
72
+ def test_flags_run_mode_field_documented_as_per_record_write_outcome() -> None:
73
+ issues = check_docstring_field_runmode_outcome(_drifted_record(), PRODUCTION_FILE_PATH)
74
+ assert len(issues) == 1
75
+ assert "is_dry_run" in issues[0]
76
+ assert "run-mode" in issues[0]
77
+
78
+
79
+ def test_run_mode_phrasing_is_left_alone() -> None:
80
+ issues = check_docstring_field_runmode_outcome(_run_mode_record(), PRODUCTION_FILE_PATH)
81
+ assert issues == []
82
+
83
+
84
+ def test_multiline_description_continuation_still_flags() -> None:
85
+ source = (
86
+ "@dataclass\n"
87
+ "class Record:\n"
88
+ ' """Schema.\n'
89
+ "\n"
90
+ " Attributes:\n"
91
+ " is_dry_run: True for the record when\n"
92
+ " no STP was written.\n"
93
+ ' """\n'
94
+ "\n"
95
+ " is_dry_run: bool\n"
96
+ )
97
+ issues = check_docstring_field_runmode_outcome(source, PRODUCTION_FILE_PATH)
98
+ assert len(issues) == 1
99
+
100
+
101
+ def test_non_run_mode_field_with_write_outcome_is_left_alone() -> None:
102
+ source = (
103
+ "@dataclass\n"
104
+ "class Record:\n"
105
+ ' """Schema.\n'
106
+ "\n"
107
+ " Attributes:\n"
108
+ " stp_path: Path the STP was written to disk.\n"
109
+ ' """\n'
110
+ "\n"
111
+ " stp_path: str\n"
112
+ )
113
+ issues = check_docstring_field_runmode_outcome(source, PRODUCTION_FILE_PATH)
114
+ assert issues == []
115
+
116
+
117
+ def test_test_file_is_exempt() -> None:
118
+ issues = check_docstring_field_runmode_outcome(_drifted_record(), TEST_FILE_PATH)
119
+ assert issues == []
120
+
121
+
122
+ def test_hook_infrastructure_is_exempt() -> None:
123
+ issues = check_docstring_field_runmode_outcome(_drifted_record(), HOOK_INFRASTRUCTURE_PATH)
124
+ assert issues == []
125
+
126
+
127
+ def test_syntax_error_returns_no_issues() -> None:
128
+ issues = check_docstring_field_runmode_outcome("class Record(\n", PRODUCTION_FILE_PATH)
129
+ assert issues == []
@@ -0,0 +1,198 @@
1
+ """Tests for check_docstring_length_constant_superlative_vs_exact_gate.
2
+
3
+ A config module defines an integer ``*_LENGTH`` constant and its docstring
4
+ describes it with a superlative word ("the longest color string the swatch
5
+ accepts"), while the only consumer treats the constant as an exact-length
6
+ equality gate ("len(hex_color) != COLOR_AARRGGBB_LENGTH") in a sibling module.
7
+ A shorter valid string is rejected, not accepted at a shorter length, so the
8
+ "longest" prose claims a range of accepted lengths the code never allows. This
9
+ is the deterministic exact-gate slice of Category O6/O8
10
+ docstring-vs-implementation drift.
11
+
12
+ The package trees these tests build live under a neutral OS temp directory
13
+ rather than the pytest ``tmp_path`` fixture: the production check exempts any
14
+ path containing "test", and the pytest fixture root carries that substring, so a
15
+ fixture-rooted package would be skipped as test code and prove nothing.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import importlib.util
21
+ import tempfile
22
+ from pathlib import Path
23
+ from types import ModuleType
24
+
25
+
26
+ def _load_enforcer_module() -> ModuleType:
27
+ module_path = Path(__file__).parent / "code_rules_enforcer.py"
28
+ spec = importlib.util.spec_from_file_location("code_rules_enforcer", module_path)
29
+ assert spec is not None
30
+ assert spec.loader is not None
31
+ module = importlib.util.module_from_spec(spec)
32
+ spec.loader.exec_module(module)
33
+ return module
34
+
35
+
36
+ code_rules_enforcer = _load_enforcer_module()
37
+
38
+
39
+ def check_length_gate(content: str, file_path: str) -> list[str]:
40
+ return code_rules_enforcer.check_docstring_length_constant_superlative_vs_exact_gate(
41
+ content, file_path
42
+ )
43
+
44
+
45
+ SUPERLATIVE_SWATCH_MODULE = '''"""Color-swatch rendering constants for the HTML run report.
46
+
47
+ These constants name the longest color string the swatch accepts and the
48
+ channel maximum the alpha byte is divided by.
49
+ """
50
+
51
+ from typing import Final
52
+
53
+ COLOR_AARRGGBB_LENGTH: Final[int] = 9
54
+ '''
55
+
56
+
57
+ def _run_gate_over_package(
58
+ swatch_content: str, consumer_body: str, swatch_basename: str = "color_swatch.py"
59
+ ) -> list[str]:
60
+ with tempfile.TemporaryDirectory() as temp_root:
61
+ config_directory = Path(temp_root) / "stp_contrast_fix" / "config"
62
+ config_directory.mkdir(parents=True)
63
+ swatch_path = config_directory / swatch_basename
64
+ swatch_path.write_text(swatch_content, encoding="utf-8")
65
+ consumer_path = config_directory.parent / "html_report_fragments.py"
66
+ consumer_path.write_text(consumer_body, encoding="utf-8")
67
+ return check_length_gate(swatch_content, str(swatch_path))
68
+
69
+
70
+ def test_superlative_describing_exact_length_gate_is_flagged() -> None:
71
+ consumer_body = (
72
+ "from config.color_swatch import COLOR_AARRGGBB_LENGTH\n\n\n"
73
+ "def to_css(hex_color: str) -> str | None:\n"
74
+ ' if not hex_color.startswith("#") or len(hex_color) != '
75
+ "COLOR_AARRGGBB_LENGTH:\n"
76
+ " return None\n"
77
+ " return hex_color\n"
78
+ )
79
+ issues = _run_gate_over_package(SUPERLATIVE_SWATCH_MODULE, consumer_body)
80
+ assert len(issues) == 1
81
+ assert "COLOR_AARRGGBB_LENGTH" in issues[0]
82
+
83
+
84
+ def test_ordered_comparison_treated_as_maximum_is_not_flagged() -> None:
85
+ consumer_body = (
86
+ "from config.color_swatch import COLOR_AARRGGBB_LENGTH\n\n\n"
87
+ "def truncated(hex_color: str) -> bool:\n"
88
+ " return len(hex_color) <= COLOR_AARRGGBB_LENGTH\n"
89
+ )
90
+ assert _run_gate_over_package(SUPERLATIVE_SWATCH_MODULE, consumer_body) == []
91
+
92
+
93
+ def test_no_superlative_phrase_is_not_flagged() -> None:
94
+ plain_module = (
95
+ '"""The exact required #AARRGGBB length the swatch renders."""\n\n'
96
+ "from typing import Final\n\n"
97
+ "COLOR_AARRGGBB_LENGTH: Final[int] = 9\n"
98
+ )
99
+ consumer_body = (
100
+ "from config.color_swatch import COLOR_AARRGGBB_LENGTH\n\n\n"
101
+ "def to_css(hex_color: str) -> bool:\n"
102
+ " return len(hex_color) == COLOR_AARRGGBB_LENGTH\n"
103
+ )
104
+ assert _run_gate_over_package(plain_module, consumer_body) == []
105
+
106
+
107
+ def test_constant_without_length_suffix_is_not_flagged() -> None:
108
+ non_length_module = (
109
+ '"""The longest swatch threshold value used by the renderer."""\n\n'
110
+ "from typing import Final\n\n"
111
+ "COLOR_CHANNEL_MAXIMUM: Final[int] = 255\n"
112
+ )
113
+ consumer_body = (
114
+ "from config.color_swatch import COLOR_CHANNEL_MAXIMUM\n\n\n"
115
+ "def saturated(channel: str) -> bool:\n"
116
+ " return len(channel) == COLOR_CHANNEL_MAXIMUM\n"
117
+ )
118
+ assert _run_gate_over_package(non_length_module, consumer_body) == []
119
+
120
+
121
+ def test_constant_with_no_length_comparison_is_not_flagged() -> None:
122
+ consumer_body = (
123
+ "from config.color_swatch import COLOR_AARRGGBB_LENGTH\n\n\n"
124
+ "def pad(hex_color: str) -> str:\n"
125
+ " return hex_color.ljust(COLOR_AARRGGBB_LENGTH)\n"
126
+ )
127
+ assert _run_gate_over_package(SUPERLATIVE_SWATCH_MODULE, consumer_body) == []
128
+
129
+
130
+ def test_superlative_in_function_docstring_is_not_flagged() -> None:
131
+ function_docstring_module = (
132
+ '"""The exact #AARRGGBB length the swatch renders."""\n\n'
133
+ "from typing import Final\n\n"
134
+ "COLOR_AARRGGBB_LENGTH: Final[int] = 9\n\n\n"
135
+ "def describe() -> str:\n"
136
+ ' """Return the longest color string the swatch accepts."""\n'
137
+ ' return "swatch"\n'
138
+ )
139
+ consumer_body = (
140
+ "from config.color_swatch import COLOR_AARRGGBB_LENGTH\n\n\n"
141
+ "def to_css(hex_color: str) -> bool:\n"
142
+ " return len(hex_color) == COLOR_AARRGGBB_LENGTH\n"
143
+ )
144
+ assert _run_gate_over_package(function_docstring_module, consumer_body) == []
145
+
146
+
147
+ def test_superlative_bound_to_other_constant_is_not_flagged() -> None:
148
+ two_constant_module = (
149
+ '"""Color-swatch rendering constants for the HTML run report.\n\n'
150
+ "COLOR_AARRGGBB_LENGTH is the exact #AARRGGBB length the swatch renders.\n"
151
+ "MAX_NAME_LENGTH is the longest swatch label the legend lays out.\n"
152
+ '"""\n\n'
153
+ "from typing import Final\n\n"
154
+ "COLOR_AARRGGBB_LENGTH: Final[int] = 9\n"
155
+ "MAX_NAME_LENGTH: Final[int] = 32\n"
156
+ )
157
+ consumer_body = (
158
+ "from config.color_swatch import COLOR_AARRGGBB_LENGTH, MAX_NAME_LENGTH\n\n\n"
159
+ "def to_css(hex_color: str) -> str | None:\n"
160
+ " if len(hex_color) != COLOR_AARRGGBB_LENGTH:\n"
161
+ " return None\n"
162
+ " return hex_color\n\n\n"
163
+ "def fits(label: str) -> bool:\n"
164
+ " return len(label) <= MAX_NAME_LENGTH\n"
165
+ )
166
+ assert _run_gate_over_package(two_constant_module, consumer_body) == []
167
+
168
+
169
+ def test_superlative_bound_to_exact_gated_constant_is_flagged() -> None:
170
+ two_constant_module = (
171
+ '"""Color-swatch rendering constants for the HTML run report.\n\n'
172
+ "COLOR_AARRGGBB_LENGTH is the longest #AARRGGBB string the swatch accepts.\n"
173
+ "MAX_NAME_LENGTH is the exact swatch label width the legend lays out.\n"
174
+ '"""\n\n'
175
+ "from typing import Final\n\n"
176
+ "COLOR_AARRGGBB_LENGTH: Final[int] = 9\n"
177
+ "MAX_NAME_LENGTH: Final[int] = 32\n"
178
+ )
179
+ consumer_body = (
180
+ "from config.color_swatch import COLOR_AARRGGBB_LENGTH, MAX_NAME_LENGTH\n\n\n"
181
+ "def to_css(hex_color: str) -> str | None:\n"
182
+ " if len(hex_color) != COLOR_AARRGGBB_LENGTH:\n"
183
+ " return None\n"
184
+ " return hex_color\n\n\n"
185
+ "def fits(label: str) -> bool:\n"
186
+ " return len(label) <= MAX_NAME_LENGTH\n"
187
+ )
188
+ issues = _run_gate_over_package(two_constant_module, consumer_body)
189
+ assert len(issues) == 1
190
+ assert "COLOR_AARRGGBB_LENGTH" in issues[0]
191
+ assert "MAX_NAME_LENGTH" not in issues[0]
192
+
193
+
194
+ def test_syntax_error_returns_empty() -> None:
195
+ issues = check_length_gate(
196
+ "def broken(\n", "/stp_contrast_fix/config/color_swatch.py"
197
+ )
198
+ assert issues == []