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.
- package/.agents/skills/hitl/SKILL.md +45 -0
- package/.agents/skills-archived/pr-loop-cloud-transport/reference/identity-and-hooks.md +1 -1
- package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +16 -29
- package/audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md +2 -2
- package/bin/ever-shipped-skills.mjs +1 -0
- package/docs/CODE_RULES.md +3 -3
- package/docs/agent-spawn-protocol.md +1 -1
- package/docs/references/prose-style-enforcement.md +5 -9
- package/hooks/blocking/AGENTS.md +3 -13
- package/hooks/blocking/code_rules_docstrings.py +10 -2138
- package/hooks/blocking/code_rules_enforcer.py +0 -121
- package/hooks/blocking/code_rules_imports_logging.py +1 -236
- package/hooks/blocking/code_rules_shared.py +23 -0
- package/hooks/blocking/code_rules_test_layout.py +8 -8
- package/hooks/blocking/config/prose_style_enforcement_constants.py +4 -2
- package/hooks/blocking/config/test_prose_style_enforcement_constants.py +5 -1
- package/hooks/blocking/precommit_code_rules_gate.py +8 -43
- package/hooks/blocking/state_description_blocker.py +1 -7
- package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +0 -1
- package/hooks/blocking/test_code_rules_enforcer_dispatch_wiring.py +0 -8
- package/hooks/blocking/test_code_rules_enforcer_module_docstring_roster.py +11 -112
- package/hooks/blocking/test_code_rules_enforcer_narrow_edit.py +0 -1
- package/hooks/blocking/test_code_rules_enforcer_split_entry_1.py +1 -18
- package/hooks/blocking/test_code_rules_shared.py +12 -0
- package/hooks/blocking/test_precommit_code_rules_gate.py +32 -179
- package/hooks/blocking/test_precommit_code_rules_gate_native_owner.py +0 -1
- package/hooks/blocking/test_state_description_blocker.py +6 -4
- package/hooks/blocking/test_stop_dispatcher.py +5 -29
- package/hooks/hooks_constants/AGENTS.md +1 -6
- package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +0 -1
- package/hooks/hooks_constants/code_rules_enforcer_constants.py +3 -0
- package/hooks/hooks_constants/messages.py +0 -2
- package/hooks/hooks_constants/precommit_code_rules_gate_constants.py +3 -17
- package/hooks/hooks_constants/stop_dispatcher_constants.py +0 -2
- package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +0 -1
- package/hooks/hooks_constants/test_code_rules_enforcer_constants.py +7 -0
- package/hooks/hooks_constants/test_messages.py +5 -3
- package/hooks/hooks_constants/test_stop_dispatcher_constants.py +0 -2
- package/package.json +1 -1
- package/rules/AGENTS.md +3 -6
- package/rules/claims-as-quotes.md +0 -10
- package/rules/code-standards.md +6 -6
- package/rules/explore-thoroughly.md +0 -1
- package/rules/failure-blast-radius.md +0 -8
- package/rules/falsify-before-green.md +0 -8
- package/rules/file-global-constants.md +2 -2
- package/rules/filesystem-search.md +1 -1
- package/rules/git-workflow.md +1 -9
- package/rules/hedging-claims.md +2 -6
- package/rules/long-horizon-autonomy.md +1 -1
- package/rules/measurement-denominators.md +0 -9
- package/rules/research-mode.md +0 -6
- package/rules/verify-before-asking.md +0 -5
- package/rules/verify-runtime-state.md +0 -5
- package/scripts/codex_compat_materializer.py +99 -24
- package/scripts/sync_to_cursor/rules.py +0 -10
- package/scripts/tests/test_codex_compat_materializer.py +70 -19
- package/scripts/tests/test_engine.py +0 -1
- package/scripts/tests/test_rules.py +0 -1
- package/scripts/tests/test_sync_to_cursor.py +0 -1
- package/system-prompts/software-engineer.xml +3 -3
- package/hooks/blocking/code_rules_dead_argparse_argument.py +0 -554
- package/hooks/blocking/code_rules_dead_config_field.py +0 -568
- package/hooks/blocking/code_rules_dead_dataclass_field.py +0 -348
- package/hooks/blocking/code_rules_dead_module_constant.py +0 -757
- package/hooks/blocking/code_rules_dead_split_branch.py +0 -225
- package/hooks/blocking/code_rules_mock_completeness.py +0 -295
- package/hooks/blocking/code_rules_scope_binding.py +0 -151
- package/hooks/blocking/code_rules_unused_imports.py +0 -197
- package/hooks/blocking/hedging_language_blocker.py +0 -221
- package/hooks/blocking/intent_only_ending_blocker.py +0 -148
- package/hooks/blocking/test_code_rules_enforcer_dead_argparse_argument.py +0 -534
- package/hooks/blocking/test_code_rules_enforcer_dead_config_field.py +0 -846
- package/hooks/blocking/test_code_rules_enforcer_dead_dataclass_field.py +0 -507
- package/hooks/blocking/test_code_rules_enforcer_dead_module_constant.py +0 -679
- package/hooks/blocking/test_code_rules_enforcer_dead_module_constant_alias.py +0 -133
- package/hooks/blocking/test_code_rules_enforcer_dead_module_constant_read_cap.py +0 -103
- package/hooks/blocking/test_code_rules_enforcer_dead_split_branch.py +0 -105
- package/hooks/blocking/test_code_rules_enforcer_docstring_args_span_scope.py +0 -425
- package/hooks/blocking/test_code_rules_enforcer_docstring_cardinal_family.py +0 -176
- package/hooks/blocking/test_code_rules_enforcer_docstring_delegation_summary.py +0 -385
- package/hooks/blocking/test_code_rules_enforcer_docstring_fallback_branch.py +0 -398
- package/hooks/blocking/test_code_rules_enforcer_docstring_field_runmode_outcome.py +0 -129
- package/hooks/blocking/test_code_rules_enforcer_docstring_inline_literal_claim.py +0 -93
- package/hooks/blocking/test_code_rules_enforcer_docstring_length_constant_superlative.py +0 -198
- package/hooks/blocking/test_code_rules_enforcer_docstring_mark_glyph_enumeration.py +0 -262
- package/hooks/blocking/test_code_rules_enforcer_docstring_no_consumer.py +0 -93
- package/hooks/blocking/test_code_rules_enforcer_docstring_no_network.py +0 -115
- package/hooks/blocking/test_code_rules_enforcer_docstring_raises_largezipfile.py +0 -226
- package/hooks/blocking/test_code_rules_enforcer_docstring_returns_plural_cardinality.py +0 -207
- package/hooks/blocking/test_code_rules_enforcer_docstring_step_dispatch.py +0 -262
- package/hooks/blocking/test_code_rules_enforcer_docstring_type_checking_gate.py +0 -164
- package/hooks/blocking/test_code_rules_enforcer_docstring_unguarded_payload.py +0 -188
- package/hooks/blocking/test_code_rules_enforcer_import_block_sort.py +0 -157
- package/hooks/blocking/test_code_rules_enforcer_split_mocks_1.py +0 -303
- package/hooks/blocking/test_code_rules_enforcer_split_mocks_2.py +0 -111
- package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +0 -656
- package/hooks/blocking/test_hedging_language_blocker.py +0 -261
- package/hooks/blocking/test_intent_only_ending_blocker.py +0 -209
- package/hooks/hooks_constants/dead_argparse_argument_constants.py +0 -28
- package/hooks/hooks_constants/dead_config_field_constants.py +0 -39
- package/hooks/hooks_constants/dead_dataclass_field_constants.py +0 -25
- package/hooks/hooks_constants/dead_module_constant_constants.py +0 -30
- package/hooks/hooks_constants/hedging_uncertainty_constants.py +0 -42
- package/hooks/hooks_constants/test_dispatcher_constants_docstrings.py +0 -44
- package/rules/conservative-action.md +0 -17
- package/rules/context7.md +0 -8
- package/rules/few-words.md +0 -3
- package/rules/parallel-tools.md +0 -23
|
@@ -1,198 +0,0 @@
|
|
|
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 == []
|
|
@@ -1,262 +0,0 @@
|
|
|
1
|
-
"""Tests for check_docstring_punctuation_mark_enumeration_coverage — O6 glyph drift.
|
|
2
|
-
|
|
3
|
-
A module defines a tuple of punctuation-mark glyphs (an em-dash, a spaced
|
|
4
|
-
double-hyphen, a semicolon) and a docstring enumerates those marks by their
|
|
5
|
-
English names while omitting one the tuple holds. The prose names two marks but
|
|
6
|
-
the detection set holds three, so a reader who trusts the enumeration believes a
|
|
7
|
-
mark that is active never triggers the check. This is the deterministic
|
|
8
|
-
glyph-prose slice of Category O6 docstring-prose-vs-implementation drift, the
|
|
9
|
-
companion to check_docstring_tuple_enumeration_match for glyph members named in
|
|
10
|
-
prose rather than identifier members named in inline code.
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
from __future__ import annotations
|
|
14
|
-
|
|
15
|
-
import importlib.util
|
|
16
|
-
from pathlib import Path
|
|
17
|
-
from types import ModuleType
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def _load_enforcer_module() -> ModuleType:
|
|
21
|
-
module_path = Path(__file__).parent / "code_rules_enforcer.py"
|
|
22
|
-
spec = importlib.util.spec_from_file_location("code_rules_enforcer", module_path)
|
|
23
|
-
assert spec is not None
|
|
24
|
-
assert spec.loader is not None
|
|
25
|
-
module = importlib.util.module_from_spec(spec)
|
|
26
|
-
spec.loader.exec_module(module)
|
|
27
|
-
return module
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
code_rules_enforcer = _load_enforcer_module()
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def check_docstring_punctuation_mark_enumeration_coverage(
|
|
34
|
-
content: str, file_path: str
|
|
35
|
-
) -> list[str]:
|
|
36
|
-
return code_rules_enforcer.check_docstring_punctuation_mark_enumeration_coverage(
|
|
37
|
-
content, file_path
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def validate_content(content: str, file_path: str, old_content: str) -> list[str]:
|
|
42
|
-
return code_rules_enforcer.validate_content(content, file_path, old_content)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
PRODUCTION_FILE_PATH = "/project/src/runon_detector.py"
|
|
46
|
-
TEST_FILE_PATH = "/project/src/test_runon_detector.py"
|
|
47
|
-
HOOK_INFRASTRUCTURE_PATH = "/home/user/.claude/hooks/blocking/code_rules_docstrings.py"
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def _drifted_two_marks_named_function() -> str:
|
|
51
|
-
return (
|
|
52
|
-
'ALL_RUNON_JOINER_MARKERS = ("—", " -- ", ";")\n'
|
|
53
|
-
"\n"
|
|
54
|
-
"\n"
|
|
55
|
-
"def _sentence_carries_joiner(sentence_text: str) -> bool:\n"
|
|
56
|
-
' """Report whether the sentence chains clauses with a joiner mark.\n'
|
|
57
|
-
"\n"
|
|
58
|
-
" The run-on marks are an em-dash or a semicolon, the two glyphs that\n"
|
|
59
|
-
" fuse independent clauses into one dense sentence.\n"
|
|
60
|
-
' """\n'
|
|
61
|
-
" return any(each in sentence_text for each in ALL_RUNON_JOINER_MARKERS)\n"
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def _complete_three_marks_named_function() -> str:
|
|
66
|
-
return (
|
|
67
|
-
'ALL_RUNON_JOINER_MARKERS = ("—", " -- ", ";")\n'
|
|
68
|
-
"\n"
|
|
69
|
-
"\n"
|
|
70
|
-
"def _sentence_carries_joiner(sentence_text: str) -> bool:\n"
|
|
71
|
-
' """Report whether the sentence chains clauses with a joiner mark.\n'
|
|
72
|
-
"\n"
|
|
73
|
-
" The run-on marks are an em-dash, a spaced double-hyphen, or a\n"
|
|
74
|
-
" semicolon, the glyphs that fuse independent clauses into one\n"
|
|
75
|
-
" dense sentence.\n"
|
|
76
|
-
' """\n'
|
|
77
|
-
" return any(each in sentence_text for each in ALL_RUNON_JOINER_MARKERS)\n"
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def _single_mark_named_function() -> str:
|
|
82
|
-
return (
|
|
83
|
-
'ALL_RUNON_JOINER_MARKERS = ("—", " -- ", ";")\n'
|
|
84
|
-
"\n"
|
|
85
|
-
"\n"
|
|
86
|
-
"def _sentence_carries_joiner(sentence_text: str) -> bool:\n"
|
|
87
|
-
' """Report whether the sentence chains clauses with an em-dash.\n'
|
|
88
|
-
"\n"
|
|
89
|
-
" The em-dash is the glyph that fuses clauses into one sentence.\n"
|
|
90
|
-
' """\n'
|
|
91
|
-
" return any(each in sentence_text for each in ALL_RUNON_JOINER_MARKERS)\n"
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def _non_glyph_tuple_function() -> str:
|
|
96
|
-
return (
|
|
97
|
-
'ALL_OUTCOME_LABELS = ("alpha", "beta", "gamma")\n'
|
|
98
|
-
"\n"
|
|
99
|
-
"\n"
|
|
100
|
-
"def describe_outcome(label_text: str) -> bool:\n"
|
|
101
|
-
' """Report the outcome named by an em-dash or a semicolon label.\n'
|
|
102
|
-
"\n"
|
|
103
|
-
" The labels carry a semicolon between the segments of each name.\n"
|
|
104
|
-
' """\n'
|
|
105
|
-
" return label_text in ALL_OUTCOME_LABELS\n"
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def test_should_flag_omitted_double_hyphen_mark() -> None:
|
|
110
|
-
issues = check_docstring_punctuation_mark_enumeration_coverage(
|
|
111
|
-
_drifted_two_marks_named_function(), PRODUCTION_FILE_PATH
|
|
112
|
-
)
|
|
113
|
-
assert any("--" in each for each in issues), (
|
|
114
|
-
f"The docstring naming em-dash and semicolon but omitting the double-hyphen "
|
|
115
|
-
f"that ALL_RUNON_JOINER_MARKERS holds must be flagged, got: {issues!r}"
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
def test_should_report_category_o6_in_the_message() -> None:
|
|
120
|
-
issues = check_docstring_punctuation_mark_enumeration_coverage(
|
|
121
|
-
_drifted_two_marks_named_function(), PRODUCTION_FILE_PATH
|
|
122
|
-
)
|
|
123
|
-
assert any("O6" in each for each in issues), (
|
|
124
|
-
f"Expected the Category O6 label in the message, got: {issues!r}"
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
def test_should_not_flag_complete_enumeration() -> None:
|
|
129
|
-
issues = check_docstring_punctuation_mark_enumeration_coverage(
|
|
130
|
-
_complete_three_marks_named_function(), PRODUCTION_FILE_PATH
|
|
131
|
-
)
|
|
132
|
-
assert issues == [], f"A docstring naming all three marks must not be flagged, got: {issues!r}"
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
def test_should_not_flag_single_named_mark_below_threshold() -> None:
|
|
136
|
-
issues = check_docstring_punctuation_mark_enumeration_coverage(
|
|
137
|
-
_single_mark_named_function(), PRODUCTION_FILE_PATH
|
|
138
|
-
)
|
|
139
|
-
assert issues == [], f"A docstring naming a single mark is not an enumeration, got: {issues!r}"
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
def test_should_not_flag_non_glyph_tuple() -> None:
|
|
143
|
-
issues = check_docstring_punctuation_mark_enumeration_coverage(
|
|
144
|
-
_non_glyph_tuple_function(), PRODUCTION_FILE_PATH
|
|
145
|
-
)
|
|
146
|
-
assert issues == [], f"A tuple of non-punctuation members must not be flagged, got: {issues!r}"
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
def test_should_flag_on_hook_infrastructure_where_the_drift_lives() -> None:
|
|
150
|
-
issues = check_docstring_punctuation_mark_enumeration_coverage(
|
|
151
|
-
_drifted_two_marks_named_function(), HOOK_INFRASTRUCTURE_PATH
|
|
152
|
-
)
|
|
153
|
-
assert any("--" in each for each in issues), (
|
|
154
|
-
f"The drift lives in hook modules, so the gate must run there, got: {issues!r}"
|
|
155
|
-
)
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
def test_should_skip_test_file() -> None:
|
|
159
|
-
issues = check_docstring_punctuation_mark_enumeration_coverage(
|
|
160
|
-
_drifted_two_marks_named_function(), TEST_FILE_PATH
|
|
161
|
-
)
|
|
162
|
-
assert issues == [], f"Test files exempt, got: {issues!r}"
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
def test_should_handle_syntax_error_gracefully() -> None:
|
|
166
|
-
unparseable_naming_marks = 'def fetch(\n """An em-dash and a semicolon."""\n'
|
|
167
|
-
issues = check_docstring_punctuation_mark_enumeration_coverage(
|
|
168
|
-
unparseable_naming_marks, PRODUCTION_FILE_PATH
|
|
169
|
-
)
|
|
170
|
-
assert issues == [], f"Syntax error must yield no issues, got: {issues!r}"
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
def _drifted_annotated_tuple_function() -> str:
|
|
174
|
-
return (
|
|
175
|
-
'ALL_RUNON_JOINER_MARKERS: tuple[str, ...] = ("—", " -- ", ";")\n'
|
|
176
|
-
"\n"
|
|
177
|
-
"\n"
|
|
178
|
-
"def _sentence_carries_joiner(sentence_text: str) -> bool:\n"
|
|
179
|
-
' """Report whether the sentence chains clauses with a joiner mark.\n'
|
|
180
|
-
"\n"
|
|
181
|
-
" The run-on marks are an em-dash or a semicolon.\n"
|
|
182
|
-
' """\n'
|
|
183
|
-
" return any(each in sentence_text for each in ALL_RUNON_JOINER_MARKERS)\n"
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
def test_should_flag_annotated_same_module_tuple_drift() -> None:
|
|
188
|
-
issues = check_docstring_punctuation_mark_enumeration_coverage(
|
|
189
|
-
_drifted_annotated_tuple_function(), PRODUCTION_FILE_PATH
|
|
190
|
-
)
|
|
191
|
-
assert any("--" in each for each in issues), (
|
|
192
|
-
f"An annotated marker tuple in the same module must be checked, got: {issues!r}"
|
|
193
|
-
)
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
def test_should_flag_drift_against_imported_companion_tuple(tmp_path: Path) -> None:
|
|
197
|
-
companion_directory = tmp_path / "marks_package"
|
|
198
|
-
companion_directory.mkdir()
|
|
199
|
-
(companion_directory / "joiner_marks.py").write_text(
|
|
200
|
-
'ALL_RUNON_JOINER_MARKERS: tuple[str, ...] = ("—", " -- ", ";")\n',
|
|
201
|
-
encoding="utf-8",
|
|
202
|
-
)
|
|
203
|
-
consumer_directory = tmp_path / "blocking"
|
|
204
|
-
consumer_directory.mkdir()
|
|
205
|
-
consumer_source = (
|
|
206
|
-
"from marks_package.joiner_marks import ALL_RUNON_JOINER_MARKERS\n"
|
|
207
|
-
"\n"
|
|
208
|
-
"\n"
|
|
209
|
-
"def _sentence_carries_joiner(sentence_text: str) -> bool:\n"
|
|
210
|
-
' """Report whether the sentence chains clauses with a joiner mark.\n'
|
|
211
|
-
"\n"
|
|
212
|
-
" The run-on marks are an em-dash or a semicolon, the two glyphs that\n"
|
|
213
|
-
" fuse independent clauses into one dense sentence.\n"
|
|
214
|
-
' """\n'
|
|
215
|
-
" return any(each in sentence_text for each in ALL_RUNON_JOINER_MARKERS)\n"
|
|
216
|
-
)
|
|
217
|
-
consumer_path = consumer_directory / "runon_consumer.py"
|
|
218
|
-
consumer_path.write_text(consumer_source, encoding="utf-8")
|
|
219
|
-
issues = check_docstring_punctuation_mark_enumeration_coverage(
|
|
220
|
-
consumer_source, str(consumer_path)
|
|
221
|
-
)
|
|
222
|
-
assert any("--" in each for each in issues), (
|
|
223
|
-
f"A docstring drifting from an imported companion marker tuple must be "
|
|
224
|
-
f"flagged so the split-file shape is caught, got: {issues!r}"
|
|
225
|
-
)
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
def test_should_not_resolve_companion_when_no_marks_named(tmp_path: Path) -> None:
|
|
229
|
-
companion_directory = tmp_path / "marks_package"
|
|
230
|
-
companion_directory.mkdir()
|
|
231
|
-
(companion_directory / "joiner_marks.py").write_text(
|
|
232
|
-
'ALL_RUNON_JOINER_MARKERS: tuple[str, ...] = ("—", " -- ", ";")\n',
|
|
233
|
-
encoding="utf-8",
|
|
234
|
-
)
|
|
235
|
-
consumer_directory = tmp_path / "blocking"
|
|
236
|
-
consumer_directory.mkdir()
|
|
237
|
-
consumer_source = (
|
|
238
|
-
"from marks_package.joiner_marks import ALL_RUNON_JOINER_MARKERS\n"
|
|
239
|
-
"\n"
|
|
240
|
-
"\n"
|
|
241
|
-
"def _sentence_carries_joiner(sentence_text: str) -> bool:\n"
|
|
242
|
-
' """Report whether the sentence carries any joiner glyph."""\n'
|
|
243
|
-
" return any(each in sentence_text for each in ALL_RUNON_JOINER_MARKERS)\n"
|
|
244
|
-
)
|
|
245
|
-
consumer_path = consumer_directory / "runon_consumer.py"
|
|
246
|
-
consumer_path.write_text(consumer_source, encoding="utf-8")
|
|
247
|
-
issues = check_docstring_punctuation_mark_enumeration_coverage(
|
|
248
|
-
consumer_source, str(consumer_path)
|
|
249
|
-
)
|
|
250
|
-
assert issues == [], (
|
|
251
|
-
f"A docstring naming no marks must not be flagged, got: {issues!r}"
|
|
252
|
-
)
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
def test_validate_content_surfaces_mark_glyph_drift() -> None:
|
|
256
|
-
issues = validate_content(
|
|
257
|
-
_drifted_two_marks_named_function(), PRODUCTION_FILE_PATH, old_content=""
|
|
258
|
-
)
|
|
259
|
-
matching_issues = [each for each in issues if "--" in each and "O6" in each]
|
|
260
|
-
assert matching_issues, (
|
|
261
|
-
f"Expected validate_content to surface the O6 mark-glyph drift, got: {issues!r}"
|
|
262
|
-
)
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
"""Tests for check_docstring_no_consumer_claim — Category O8 producer/consumer drift.
|
|
2
|
-
|
|
3
|
-
A producer docstring claiming "no consumer reads it yet" or "producer-only
|
|
4
|
-
artifact" is a transitional statement that drifts the moment a reader lands and
|
|
5
|
-
contradicts any companion SKILL.md that documents the consumer. This is the
|
|
6
|
-
deterministic slice of Category O8 (docstring / companion-doc producer-consumer
|
|
7
|
-
drift) and a no-transitional-language violation in its own right.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
from __future__ import annotations
|
|
11
|
-
|
|
12
|
-
import importlib.util
|
|
13
|
-
from pathlib import Path
|
|
14
|
-
from types import ModuleType
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def _load_enforcer_module() -> ModuleType:
|
|
18
|
-
module_path = Path(__file__).parent / "code_rules_enforcer.py"
|
|
19
|
-
spec = importlib.util.spec_from_file_location("code_rules_enforcer", module_path)
|
|
20
|
-
assert spec is not None
|
|
21
|
-
assert spec.loader is not None
|
|
22
|
-
module = importlib.util.module_from_spec(spec)
|
|
23
|
-
spec.loader.exec_module(module)
|
|
24
|
-
return module
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
code_rules_enforcer = _load_enforcer_module()
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def check_docstring_no_consumer_claim(content: str, file_path: str) -> list[str]:
|
|
31
|
-
return code_rules_enforcer.check_docstring_no_consumer_claim(content, file_path)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
PRODUCTION_FILE_PATH = "/project/scripts/scan_priority_queue.py"
|
|
35
|
-
TEST_FILE_PATH = "/project/scripts/test_scan_priority_queue.py"
|
|
36
|
-
HOOK_INFRASTRUCTURE_PATH = "/home/user/.claude/hooks/blocking/example.py"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def test_flags_producer_only_artifact_claim() -> None:
|
|
40
|
-
content = (
|
|
41
|
-
"def write_skip_list(skip_list_path: str) -> int:\n"
|
|
42
|
-
' """Merge the at-risk names into the skip-list JSON.\n'
|
|
43
|
-
"\n"
|
|
44
|
-
" This is a producer-only artifact; no submission-run consumer reads it yet.\n"
|
|
45
|
-
"\n"
|
|
46
|
-
" Returns:\n"
|
|
47
|
-
" How many names the merged list holds.\n"
|
|
48
|
-
' """\n'
|
|
49
|
-
" return 0\n"
|
|
50
|
-
)
|
|
51
|
-
issues = check_docstring_no_consumer_claim(content, PRODUCTION_FILE_PATH)
|
|
52
|
-
assert len(issues) == 1
|
|
53
|
-
assert "write_skip_list" in issues[0]
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def test_flags_no_submission_run_consumer_phrase() -> None:
|
|
57
|
-
content = (
|
|
58
|
-
"def write_skip_list(skip_list_path: str) -> int:\n"
|
|
59
|
-
' """Write the JSON. No submission-run consumer reads it yet."""\n'
|
|
60
|
-
" return 0\n"
|
|
61
|
-
)
|
|
62
|
-
assert len(check_docstring_no_consumer_claim(content, PRODUCTION_FILE_PATH)) == 1
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def test_passes_when_docstring_names_the_consumer() -> None:
|
|
66
|
-
content = (
|
|
67
|
-
"def write_skip_list(skip_list_path: str) -> int:\n"
|
|
68
|
-
' """Write the JSON. The new-theme submission run reads it and applies the skip.\n'
|
|
69
|
-
"\n"
|
|
70
|
-
" Returns:\n"
|
|
71
|
-
" How many names the merged list holds.\n"
|
|
72
|
-
' """\n'
|
|
73
|
-
" return 0\n"
|
|
74
|
-
)
|
|
75
|
-
assert check_docstring_no_consumer_claim(content, PRODUCTION_FILE_PATH) == []
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def test_test_files_are_exempt() -> None:
|
|
79
|
-
content = (
|
|
80
|
-
"def write_skip_list(skip_list_path: str) -> int:\n"
|
|
81
|
-
' """This is a producer-only artifact; no consumer reads it yet."""\n'
|
|
82
|
-
" return 0\n"
|
|
83
|
-
)
|
|
84
|
-
assert check_docstring_no_consumer_claim(content, TEST_FILE_PATH) == []
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
def test_hook_infrastructure_is_exempt() -> None:
|
|
88
|
-
content = (
|
|
89
|
-
"def write_skip_list(skip_list_path: str) -> int:\n"
|
|
90
|
-
' """This is a producer-only artifact; no consumer reads it yet."""\n'
|
|
91
|
-
" return 0\n"
|
|
92
|
-
)
|
|
93
|
-
assert check_docstring_no_consumer_claim(content, HOOK_INFRASTRUCTURE_PATH) == []
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
"""Tests for check_docstring_no_network_claim_with_metadata_access — Category O6.
|
|
2
|
-
|
|
3
|
-
A docstring promising a code path returns "without touching the network" drifts
|
|
4
|
-
when the body calls a path-metadata method (``is_file``, ``stat``, ...): on a
|
|
5
|
-
network share each metadata call is a round-trip over the wire, so the no-network
|
|
6
|
-
claim is false. This is the deterministic slice of Category O6 (docstring prose
|
|
7
|
-
versus implementation drift) for a no-network claim.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
from __future__ import annotations
|
|
11
|
-
|
|
12
|
-
import importlib.util
|
|
13
|
-
from pathlib import Path
|
|
14
|
-
from types import ModuleType
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def _load_enforcer_module() -> ModuleType:
|
|
18
|
-
module_path = Path(__file__).parent / "code_rules_enforcer.py"
|
|
19
|
-
spec = importlib.util.spec_from_file_location("code_rules_enforcer", module_path)
|
|
20
|
-
assert spec is not None
|
|
21
|
-
assert spec.loader is not None
|
|
22
|
-
module = importlib.util.module_from_spec(spec)
|
|
23
|
-
spec.loader.exec_module(module)
|
|
24
|
-
return module
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
code_rules_enforcer = _load_enforcer_module()
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def check_docstring_no_network_claim(content: str, file_path: str) -> list[str]:
|
|
31
|
-
return code_rules_enforcer.check_docstring_no_network_claim_with_metadata_access(
|
|
32
|
-
content, file_path
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
PRODUCTION_FILE_PATH = "/project/shared_utils/bws/secret_fetch.py"
|
|
37
|
-
TEST_FILE_PATH = "/project/shared_utils/bws/tests/test_secret_fetch.py"
|
|
38
|
-
HOOK_INFRASTRUCTURE_PATH = "/home/user/.claude/hooks/blocking/example.py"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def test_flags_no_network_claim_with_is_file_and_stat() -> None:
|
|
42
|
-
content = (
|
|
43
|
-
"def _ensure_local_bws_cache() -> str:\n"
|
|
44
|
-
' """Return the local cache path, repopulating when stale.\n'
|
|
45
|
-
"\n"
|
|
46
|
-
" An existing cache is returned without touching the network; a\n"
|
|
47
|
-
" missing or size-mismatched cache is repopulated from the bundled\n"
|
|
48
|
-
" executable.\n"
|
|
49
|
-
' """\n'
|
|
50
|
-
" if BUNDLED_BWS_EXECUTABLE_PATH.is_file():\n"
|
|
51
|
-
" bundled_size = BUNDLED_BWS_EXECUTABLE_PATH.stat().st_size\n"
|
|
52
|
-
" return str(LOCAL_BWS_CACHE_PATH)\n"
|
|
53
|
-
" return str(LOCAL_BWS_CACHE_PATH)\n"
|
|
54
|
-
)
|
|
55
|
-
issues = check_docstring_no_network_claim(content, PRODUCTION_FILE_PATH)
|
|
56
|
-
assert len(issues) == 1
|
|
57
|
-
assert "_ensure_local_bws_cache" in issues[0]
|
|
58
|
-
assert "without touching the network" in issues[0]
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def test_flags_no_network_access_phrase_with_exists() -> None:
|
|
62
|
-
content = (
|
|
63
|
-
"def read_warm_cache(cache_path) -> str:\n"
|
|
64
|
-
' """Serve the warm cache with no network access."""\n'
|
|
65
|
-
" if cache_path.exists():\n"
|
|
66
|
-
" return cache_path.read_text()\n"
|
|
67
|
-
" return ''\n"
|
|
68
|
-
)
|
|
69
|
-
assert len(check_docstring_no_network_claim(content, PRODUCTION_FILE_PATH)) == 1
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def test_passes_when_claim_present_but_no_metadata_access() -> None:
|
|
73
|
-
content = (
|
|
74
|
-
"def read_warm_cache(cache_path) -> str:\n"
|
|
75
|
-
' """Serve the warm cache without touching the network."""\n'
|
|
76
|
-
" return cache_path.read_text()\n"
|
|
77
|
-
)
|
|
78
|
-
assert check_docstring_no_network_claim(content, PRODUCTION_FILE_PATH) == []
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def test_passes_when_metadata_access_but_no_network_claim() -> None:
|
|
82
|
-
content = (
|
|
83
|
-
"def _ensure_local_bws_cache(share_path) -> str:\n"
|
|
84
|
-
' """Return the local cache path, repopulating when stale.\n'
|
|
85
|
-
"\n"
|
|
86
|
-
" The bundled share is stat-checked on every call to validate the\n"
|
|
87
|
-
" cache against the bundled executable size.\n"
|
|
88
|
-
' """\n'
|
|
89
|
-
" if share_path.is_file():\n"
|
|
90
|
-
" return str(share_path)\n"
|
|
91
|
-
" return ''\n"
|
|
92
|
-
)
|
|
93
|
-
assert check_docstring_no_network_claim(content, PRODUCTION_FILE_PATH) == []
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def test_test_files_are_exempt() -> None:
|
|
97
|
-
content = (
|
|
98
|
-
"def _ensure_local_bws_cache(share_path) -> str:\n"
|
|
99
|
-
' """Return the cache without touching the network."""\n'
|
|
100
|
-
" if share_path.is_file():\n"
|
|
101
|
-
" return str(share_path)\n"
|
|
102
|
-
" return ''\n"
|
|
103
|
-
)
|
|
104
|
-
assert check_docstring_no_network_claim(content, TEST_FILE_PATH) == []
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def test_hook_infrastructure_is_exempt() -> None:
|
|
108
|
-
content = (
|
|
109
|
-
"def _ensure_local_bws_cache(share_path) -> str:\n"
|
|
110
|
-
' """Return the cache without touching the network."""\n'
|
|
111
|
-
" if share_path.is_file():\n"
|
|
112
|
-
" return str(share_path)\n"
|
|
113
|
-
" return ''\n"
|
|
114
|
-
)
|
|
115
|
-
assert check_docstring_no_network_claim(content, HOOK_INFRASTRUCTURE_PATH) == []
|