claude-dev-env 2.28.0 → 2.29.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.
- package/.agents/agents/AGENTS.md +0 -31
- package/.agents/agents/clean-coder.md +1 -1
- package/.agents/agents/test_agent_frontmatter.py +3 -1
- package/.agents/skills/AGENTS.md +0 -70
- package/.agents/skills/_shared/AGENTS.md +0 -44
- package/.agents/skills/_shared/advisor/AGENTS.md +0 -8
- package/.agents/skills/_shared/pr-loop/AGENTS.md +0 -57
- package/.agents/skills/_shared/pr-loop/prompts/AGENTS.md +0 -8
- package/.agents/skills/_shared/pr-loop/scripts/AGENTS.md +0 -34
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/AGENTS.md +0 -23
- package/.agents/skills/autoconverge/AGENTS.md +0 -35
- package/.agents/skills/autoconverge/reference/AGENTS.md +0 -15
- package/.agents/skills/autoconverge/workflow/AGENTS.md +0 -25
- package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/AGENTS.md +0 -15
- package/.agents/skills/everything-search/AGENTS.md +0 -16
- package/.agents/skills/fresh-branch/AGENTS.md +0 -13
- package/.agents/skills/grok-spawn/AGENTS.md +0 -27
- package/.agents/skills/orchestrator/.claude/CLAUDE.md +1 -0
- package/.agents/skills/orchestrator/AGENTS.md +1 -0
- package/.agents/skills/orchestrator/SKILL.md +44 -53
- package/.agents/skills/orchestrator/reference/.claude/CLAUDE.md +1 -0
- package/.agents/skills/orchestrator/reference/AGENTS.md +1 -0
- package/.agents/skills/orchestrator/reference/consult-the-orchestrator.md +70 -0
- package/.agents/skills/orchestrator/reference/executor-consult-block.md +62 -0
- package/.agents/skills/orchestrator/reference/host-detect.md +15 -0
- package/.agents/skills/orchestrator/test_orchestrator_skill_contract.py +62 -0
- package/.agents/skills/orchestrator-refresh/SKILL.md +16 -34
- package/.agents/skills/rebase/AGENTS.md +0 -31
- package/.agents/skills/session-log/AGENTS.md +0 -31
- package/.agents/skills/session-tidy/AGENTS.md +0 -35
- package/.agents/skills/skill-builder/AGENTS.md +0 -48
- package/.agents/skills/skill-builder/references/AGENTS.md +0 -24
- package/.agents/skills/skill-builder/templates/AGENTS.md +0 -13
- package/.agents/skills/skill-builder/workflows/AGENTS.md +0 -18
- package/.agents/skills/task-build/AGENTS.md +0 -28
- package/.agents/skills/update/AGENTS.md +0 -37
- package/.agents/skills-archived/AGENTS.md +0 -44
- package/.agents/skills-archived/anthropic-plan/AGENTS.md +0 -33
- package/.agents/skills-archived/anthropic-plan/scripts/AGENTS.md +0 -10
- package/.agents/skills-archived/anthropic-plan/scripts/anthropic_plan_scripts_constants/AGENTS.md +0 -15
- package/.agents/skills-archived/anthropic-plan/templates/AGENTS.md +0 -12
- package/.agents/skills-archived/anthropic-plan/workflow/AGENTS.md +0 -13
- package/.agents/skills-archived/auditing-claude-config/AGENTS.md +0 -20
- package/.agents/skills-archived/bugteam/AGENTS.md +0 -29
- package/.agents/skills-archived/bugteam/reference/AGENTS.md +0 -19
- package/.agents/skills-archived/bugteam/reference/obstacles/AGENTS.md +0 -23
- package/.agents/skills-archived/bugteam/scripts/AGENTS.md +0 -29
- package/.agents/skills-archived/bugteam/scripts/bugteam_scripts_constants/AGENTS.md +0 -17
- package/.agents/skills-archived/codex-review/AGENTS.md +0 -45
- package/.agents/skills-archived/codex-review/reference/AGENTS.md +0 -14
- package/.agents/skills-archived/codex-review/scripts/codex_review_scripts_constants/AGENTS.md +0 -17
- package/.agents/skills-archived/codex-review/test_skill_scaffold.py +13 -3
- package/.agents/skills-archived/copilot-review/AGENTS.md +0 -17
- package/.agents/skills-archived/pr-converge/AGENTS.md +0 -31
- package/.agents/skills-archived/pr-converge/pr_converge_skill_constants/AGENTS.md +0 -25
- package/.agents/skills-archived/pr-converge/reference/AGENTS.md +0 -27
- package/.agents/skills-archived/pr-converge/reference/obstacles/AGENTS.md +0 -22
- package/.agents/skills-archived/pr-converge/scripts/AGENTS.md +0 -45
- package/.agents/skills-archived/pr-converge/scripts/pr_converge_scripts_constants/AGENTS.md +0 -17
- package/.agents/skills-archived/pr-converge/workflows/AGENTS.md +0 -15
- package/.agents/skills-archived/pr-loop-cloud-transport/reference/identity-and-hooks.md +1 -1
- package/.agents/skills-archived/recall/AGENTS.md +0 -29
- package/.agents/skills-archived/remember/AGENTS.md +0 -30
- package/AGENTS.md +0 -112
- package/_shared/AGENTS.md +0 -16
- package/_shared/advisor/AGENTS.md +0 -21
- package/_shared/advisor/advisor-protocol.md +4 -1
- package/_shared/advisor/reference/consult-format.md +4 -2
- package/_shared/pr-loop/AGENTS.md +0 -27
- package/_shared/pr-loop/scripts/AGENTS.md +0 -49
- package/_shared/pr-loop/scripts/code_rules_gate_parts/AGENTS.md +0 -41
- package/_shared/pr-loop/scripts/codex_review_scripts_constants/AGENTS.md +0 -17
- package/_shared/pr-loop/scripts/pr_converge_scripts_constants/AGENTS.md +0 -17
- package/_shared/pr-loop/scripts/pr_converge_skill_constants/AGENTS.md +0 -25
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/AGENTS.md +0 -25
- package/_shared/pr-loop/scripts/tests/AGENTS.md +0 -43
- package/_shared/process-tree/AGENTS.md +0 -40
- package/audit-rubrics/AGENTS.md +0 -42
- package/audit-rubrics/category_rubrics/AGENTS.md +0 -36
- package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +16 -29
- package/audit-rubrics/prompts/AGENTS.md +0 -36
- package/audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md +2 -2
- package/bin/AGENTS.md +0 -118
- package/bin/install.test.mjs +1 -1
- package/commands/AGENTS.md +0 -14
- package/docs/AGENTS.md +0 -31
- package/docs/CODE_RULES.md +3 -3
- package/docs/agent-spawn-protocol.md +1 -1
- package/docs/references/AGENTS.md +0 -16
- package/docs/references/prose-style-enforcement.md +5 -9
- package/hooks/AGENTS.md +0 -27
- package/hooks/advisory/AGENTS.md +0 -15
- package/hooks/blocking/AGENTS.md +0 -122
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/AGENTS.md +0 -27
- 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/AGENTS.md +0 -9
- 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/inventory_intent_records/AGENTS.md +0 -25
- package/hooks/blocking/package_inventory_stale_blocker_parts/AGENTS.md +0 -25
- package/hooks/blocking/pii_prevention_blocker_parts/AGENTS.md +0 -23
- package/hooks/blocking/precommit_code_rules_gate.py +8 -43
- package/hooks/blocking/state_description_blocker.py +1 -7
- package/hooks/blocking/tdd_enforcer_parts/AGENTS.md +0 -29
- 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/git-hooks/AGENTS.md +0 -31
- package/hooks/git-hooks/git_hooks_constants/AGENTS.md +0 -20
- package/hooks/hooks_constants/AGENTS.md +0 -104
- 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/hooks/lifecycle/AGENTS.md +0 -17
- package/hooks/observability/AGENTS.md +0 -19
- package/hooks/session/AGENTS.md +0 -37
- package/hooks/validation/AGENTS.md +0 -19
- package/hooks/validators/AGENTS.md +0 -51
- package/hooks/workflow/AGENTS.md +0 -15
- package/output-styles/AGENTS.md +0 -14
- package/package.json +1 -1
- package/rules/AGENTS.md +0 -58
- 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/AGENTS.md +0 -55
- package/scripts/codex_compat_materializer.py +0 -12
- package/scripts/dev_env_scripts_constants/AGENTS.md +0 -20
- package/scripts/sync_to_cursor/AGENTS.md +0 -22
- package/scripts/sync_to_cursor/rules.py +0 -10
- package/scripts/tests/AGENTS.md +0 -34
- 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/AGENTS.md +0 -24
- 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,679 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import importlib.util
|
|
4
|
-
import os
|
|
5
|
-
import shutil
|
|
6
|
-
import stat
|
|
7
|
-
import tempfile
|
|
8
|
-
from collections.abc import Callable, Iterator
|
|
9
|
-
from pathlib import Path
|
|
10
|
-
|
|
11
|
-
import pytest
|
|
12
|
-
|
|
13
|
-
ENFORCER_PATH = Path(__file__).resolve().parent / "code_rules_enforcer.py"
|
|
14
|
-
specification = importlib.util.spec_from_file_location("code_rules_enforcer", ENFORCER_PATH)
|
|
15
|
-
assert specification is not None and specification.loader is not None
|
|
16
|
-
code_rules_enforcer = importlib.util.module_from_spec(specification)
|
|
17
|
-
specification.loader.exec_module(code_rules_enforcer)
|
|
18
|
-
|
|
19
|
-
CONSTANTS_BODY = 'MEDIUM_TERMINAL = "terminal"\nMEDIUM_CODE = "code"\nMEDIUM_TEXT = "text"\n'
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def _strip_read_only_and_retry(
|
|
23
|
-
removal_function: Callable[[str], object],
|
|
24
|
-
target_path: str,
|
|
25
|
-
_exc_info: BaseException,
|
|
26
|
-
) -> None:
|
|
27
|
-
try:
|
|
28
|
-
os.chmod(target_path, stat.S_IWRITE)
|
|
29
|
-
removal_function(target_path)
|
|
30
|
-
except OSError:
|
|
31
|
-
pass
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
@pytest.fixture
|
|
35
|
-
def neutral_root() -> Iterator[Path]:
|
|
36
|
-
"""Yield a temp directory whose path carries no ``test_`` segment.
|
|
37
|
-
|
|
38
|
-
The enforcer's ``is_test_file`` keys on the full path string, and pytest's
|
|
39
|
-
own ``tmp_path`` directory name embeds the test name, which would make every
|
|
40
|
-
synthetic constants path look like a test file. A neutral ``mkdtemp`` root
|
|
41
|
-
mirrors how a production constants module path looks.
|
|
42
|
-
|
|
43
|
-
A ``.git`` marker is planted at the root so the cross-tree widening resolves
|
|
44
|
-
the repository root to this synthetic tree, never an enclosing real
|
|
45
|
-
checkout, keeping every test bounded and deterministic.
|
|
46
|
-
"""
|
|
47
|
-
neutral_directory = Path(tempfile.mkdtemp(prefix="deadconst-")).resolve()
|
|
48
|
-
(neutral_directory / ".git").mkdir()
|
|
49
|
-
try:
|
|
50
|
-
yield neutral_directory
|
|
51
|
-
finally:
|
|
52
|
-
shutil.rmtree(neutral_directory, onexc=_strip_read_only_and_retry)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def _check(source: str, file_path: str) -> list[str]:
|
|
56
|
-
return code_rules_enforcer.check_dead_module_constants(source, file_path)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def _check_edit(fragment: str, full_file_content: str, file_path: str) -> list[str]:
|
|
60
|
-
return code_rules_enforcer.check_dead_module_constants(
|
|
61
|
-
fragment, file_path, full_file_content
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def _build_constants_package(
|
|
66
|
-
workflow_directory: Path,
|
|
67
|
-
constants_body: str,
|
|
68
|
-
consumer_body: str,
|
|
69
|
-
) -> Path:
|
|
70
|
-
constants_package = workflow_directory / "report_constants"
|
|
71
|
-
constants_package.mkdir(parents=True)
|
|
72
|
-
(constants_package / "__init__.py").write_text("", encoding="utf-8")
|
|
73
|
-
constants_path = constants_package / "render_report_constants.py"
|
|
74
|
-
constants_path.write_text(constants_body, encoding="utf-8")
|
|
75
|
-
(workflow_directory / "render_report.py").write_text(consumer_body, encoding="utf-8")
|
|
76
|
-
return constants_path
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def test_flags_constant_imported_by_no_module_in_the_tree(neutral_root: Path) -> None:
|
|
80
|
-
consumer_body = (
|
|
81
|
-
"from report_constants.render_report_constants import (\n"
|
|
82
|
-
" MEDIUM_CODE,\n"
|
|
83
|
-
" MEDIUM_TERMINAL,\n"
|
|
84
|
-
")\n"
|
|
85
|
-
"\n"
|
|
86
|
-
"def panel_class(medium: str) -> str:\n"
|
|
87
|
-
" if medium == MEDIUM_TERMINAL:\n"
|
|
88
|
-
" return 'terminal'\n"
|
|
89
|
-
" return 'code-panel' if medium == MEDIUM_CODE else 'text-panel'\n"
|
|
90
|
-
)
|
|
91
|
-
constants_path = _build_constants_package(
|
|
92
|
-
neutral_root / "workflow", CONSTANTS_BODY, consumer_body
|
|
93
|
-
)
|
|
94
|
-
issues = _check(CONSTANTS_BODY, str(constants_path))
|
|
95
|
-
assert any("MEDIUM_TEXT" in each_issue for each_issue in issues), (
|
|
96
|
-
f"Expected dead MEDIUM_TEXT flagged, got: {issues}"
|
|
97
|
-
)
|
|
98
|
-
assert not any(
|
|
99
|
-
"MEDIUM_TERMINAL" in each_issue or "MEDIUM_CODE" in each_issue for each_issue in issues
|
|
100
|
-
), f"Imported constants must not be flagged, got: {issues}"
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def test_flags_a_dead_constant_added_by_an_edit_to_an_existing_module(
|
|
104
|
-
neutral_root: Path,
|
|
105
|
-
) -> None:
|
|
106
|
-
consumer_body = (
|
|
107
|
-
"from report_constants.render_report_constants import (\n"
|
|
108
|
-
" MEDIUM_CODE,\n"
|
|
109
|
-
" MEDIUM_TERMINAL,\n"
|
|
110
|
-
")\n"
|
|
111
|
-
"\n"
|
|
112
|
-
"def panel_class(medium: str) -> str:\n"
|
|
113
|
-
" if medium == MEDIUM_TERMINAL:\n"
|
|
114
|
-
" return 'terminal'\n"
|
|
115
|
-
" return 'code-panel' if medium == MEDIUM_CODE else 'text-panel'\n"
|
|
116
|
-
)
|
|
117
|
-
prior_body = 'MEDIUM_TERMINAL = "terminal"\nMEDIUM_CODE = "code"\n'
|
|
118
|
-
constants_path = _build_constants_package(
|
|
119
|
-
neutral_root / "workflow", prior_body, consumer_body
|
|
120
|
-
)
|
|
121
|
-
edit_fragment = 'MEDIUM_CODE = "code"\nMEDIUM_TEXT = "text"\n'
|
|
122
|
-
post_edit_body = prior_body + 'MEDIUM_TEXT = "text"\n'
|
|
123
|
-
issues = _check_edit(edit_fragment, post_edit_body, str(constants_path))
|
|
124
|
-
assert any("MEDIUM_TEXT" in each_issue for each_issue in issues), (
|
|
125
|
-
f"An Edit that inserts a dead constant must be flagged, got: {issues}"
|
|
126
|
-
)
|
|
127
|
-
assert not any(
|
|
128
|
-
"MEDIUM_TERMINAL" in each_issue or "MEDIUM_CODE" in each_issue
|
|
129
|
-
for each_issue in issues
|
|
130
|
-
), f"Imported constants must not be flagged on an edit, got: {issues}"
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
def test_does_not_flag_constant_imported_one_directory_up(neutral_root: Path) -> None:
|
|
134
|
-
consumer_uses_text = (
|
|
135
|
-
"from report_constants.render_report_constants import (\n"
|
|
136
|
-
" MEDIUM_CODE,\n"
|
|
137
|
-
" MEDIUM_TERMINAL,\n"
|
|
138
|
-
" MEDIUM_TEXT,\n"
|
|
139
|
-
")\n"
|
|
140
|
-
"\n"
|
|
141
|
-
"def panel_class(medium: str) -> str:\n"
|
|
142
|
-
" if medium == MEDIUM_TERMINAL:\n"
|
|
143
|
-
" return 'terminal'\n"
|
|
144
|
-
" if medium == MEDIUM_TEXT:\n"
|
|
145
|
-
" return 'text-panel'\n"
|
|
146
|
-
" return 'code-panel' if medium == MEDIUM_CODE else 'text-panel'\n"
|
|
147
|
-
)
|
|
148
|
-
constants_path = _build_constants_package(
|
|
149
|
-
neutral_root / "workflow", CONSTANTS_BODY, consumer_uses_text
|
|
150
|
-
)
|
|
151
|
-
issues = _check(CONSTANTS_BODY, str(constants_path))
|
|
152
|
-
assert issues == [], f"No constant is dead when all are imported, got: {issues}"
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
def test_does_not_flag_dunder_all_member_with_a_live_consumer(neutral_root: Path) -> None:
|
|
156
|
-
constants_body = CONSTANTS_BODY + '__all__ = ["MEDIUM_TERMINAL"]\n'
|
|
157
|
-
consumer_body = (
|
|
158
|
-
"from report_constants.render_report_constants import MEDIUM_TERMINAL\n"
|
|
159
|
-
"\n"
|
|
160
|
-
"def label() -> str:\n"
|
|
161
|
-
" return MEDIUM_TERMINAL\n"
|
|
162
|
-
)
|
|
163
|
-
constants_path = _build_constants_package(
|
|
164
|
-
neutral_root / "workflow", constants_body, consumer_body
|
|
165
|
-
)
|
|
166
|
-
issues = _check(constants_body, str(constants_path))
|
|
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
|
-
)
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
def test_does_not_run_on_ordinary_production_module(neutral_root: Path) -> None:
|
|
199
|
-
workflow_directory = neutral_root / "workflow"
|
|
200
|
-
workflow_directory.mkdir(parents=True)
|
|
201
|
-
ordinary_path = workflow_directory / "render_report.py"
|
|
202
|
-
body = "WIDGET_LIMIT = 5\n\ndef widgets() -> int:\n return WIDGET_LIMIT\n"
|
|
203
|
-
ordinary_path.write_text(body, encoding="utf-8")
|
|
204
|
-
issues = _check(body, str(ordinary_path))
|
|
205
|
-
assert issues == [], (
|
|
206
|
-
f"The dead-constant check runs only on dedicated constants modules, got: {issues}"
|
|
207
|
-
)
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
def test_runs_on_config_directory_module(neutral_root: Path) -> None:
|
|
211
|
-
package_directory = neutral_root / "app"
|
|
212
|
-
config_directory = package_directory / "config"
|
|
213
|
-
config_directory.mkdir(parents=True)
|
|
214
|
-
constants_body = "TIMEOUT_SECONDS = 30\nUNUSED_THRESHOLD = 99\n"
|
|
215
|
-
constants_path = config_directory / "timing.py"
|
|
216
|
-
constants_path.write_text(constants_body, encoding="utf-8")
|
|
217
|
-
consumer_body = (
|
|
218
|
-
"from config.timing import TIMEOUT_SECONDS\n"
|
|
219
|
-
"\n"
|
|
220
|
-
"def deadline() -> int:\n"
|
|
221
|
-
" return TIMEOUT_SECONDS\n"
|
|
222
|
-
)
|
|
223
|
-
(package_directory / "service.py").write_text(consumer_body, encoding="utf-8")
|
|
224
|
-
issues = _check(constants_body, str(constants_path))
|
|
225
|
-
assert any("UNUSED_THRESHOLD" in each_issue for each_issue in issues), (
|
|
226
|
-
f"Expected dead UNUSED_THRESHOLD flagged in config module, got: {issues}"
|
|
227
|
-
)
|
|
228
|
-
assert not any("TIMEOUT_SECONDS" in each_issue for each_issue in issues), (
|
|
229
|
-
f"Consumed config constant must not be flagged, got: {issues}"
|
|
230
|
-
)
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
def test_counts_a_reference_from_a_test_module(neutral_root: Path) -> None:
|
|
234
|
-
workflow_directory = neutral_root / "workflow"
|
|
235
|
-
constants_body = 'ONLY_TESTS_USE_THIS = "x"\n'
|
|
236
|
-
constants_path = workflow_directory / "render_report_constants.py"
|
|
237
|
-
workflow_directory.mkdir(parents=True)
|
|
238
|
-
constants_path.write_text(constants_body, encoding="utf-8")
|
|
239
|
-
test_body = (
|
|
240
|
-
"from render_report_constants import ONLY_TESTS_USE_THIS\n"
|
|
241
|
-
"\n"
|
|
242
|
-
"def test_value() -> None:\n"
|
|
243
|
-
" assert ONLY_TESTS_USE_THIS == 'x'\n"
|
|
244
|
-
)
|
|
245
|
-
(workflow_directory / "test_render_report.py").write_text(test_body, encoding="utf-8")
|
|
246
|
-
issues = _check(constants_body, str(constants_path))
|
|
247
|
-
assert issues == [], f"A constant used only by a test under the tree stays live, got: {issues}"
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
def test_is_skipped_on_a_constants_test_file(neutral_root: Path) -> None:
|
|
251
|
-
workflow_directory = neutral_root / "workflow"
|
|
252
|
-
workflow_directory.mkdir(parents=True)
|
|
253
|
-
test_constants_path = workflow_directory / "test_render_report_constants.py"
|
|
254
|
-
body = 'UNREFERENCED = "y"\n'
|
|
255
|
-
test_constants_path.write_text(body, encoding="utf-8")
|
|
256
|
-
issues = _check(body, str(test_constants_path))
|
|
257
|
-
assert issues == [], f"Test files are exempt, got: {issues}"
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
def _build_cross_tree_repository(
|
|
261
|
-
repository_root: Path,
|
|
262
|
-
constants_body: str,
|
|
263
|
-
sibling_consumer_body: str,
|
|
264
|
-
) -> Path:
|
|
265
|
-
config_directory = repository_root / "shared" / "theme_db" / "config"
|
|
266
|
-
config_directory.mkdir(parents=True)
|
|
267
|
-
constants_path = config_directory / "constants.py"
|
|
268
|
-
constants_path.write_text(constants_body, encoding="utf-8")
|
|
269
|
-
sibling_directory = repository_root / "cdp"
|
|
270
|
-
sibling_directory.mkdir(parents=True)
|
|
271
|
-
(sibling_directory / "tally.py").write_text(sibling_consumer_body, encoding="utf-8")
|
|
272
|
-
return constants_path
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
def test_does_not_flag_constant_used_only_in_a_sibling_tree(neutral_root: Path) -> None:
|
|
276
|
-
constants_body = 'CROSS_TREE_CONSTANT = "cross"\nLOCALLY_DEAD_CONSTANT = "dead"\n'
|
|
277
|
-
sibling_consumer_body = (
|
|
278
|
-
"from shared.theme_db.config.constants import CROSS_TREE_CONSTANT\n"
|
|
279
|
-
"\n"
|
|
280
|
-
"def tally() -> str:\n"
|
|
281
|
-
" return CROSS_TREE_CONSTANT\n"
|
|
282
|
-
)
|
|
283
|
-
constants_path = _build_cross_tree_repository(
|
|
284
|
-
neutral_root, constants_body, sibling_consumer_body
|
|
285
|
-
)
|
|
286
|
-
issues = _check(constants_body, str(constants_path))
|
|
287
|
-
assert not any("CROSS_TREE_CONSTANT" in each_issue for each_issue in issues), (
|
|
288
|
-
f"A constant consumed by a sibling tree in the repository must not be flagged, got: {issues}"
|
|
289
|
-
)
|
|
290
|
-
assert any("LOCALLY_DEAD_CONSTANT" in each_issue for each_issue in issues), (
|
|
291
|
-
f"A constant referenced nowhere in the repository stays flagged, got: {issues}"
|
|
292
|
-
)
|
|
293
|
-
|
|
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
|
-
|
|
354
|
-
def test_returns_empty_list_at_file_cap(
|
|
355
|
-
neutral_root: Path, monkeypatch: pytest.MonkeyPatch
|
|
356
|
-
) -> None:
|
|
357
|
-
monkeypatch.setattr("code_rules_dead_module_constant.MAX_SCAN_ROOT_FILE_COUNT", 0)
|
|
358
|
-
constants_path = _build_constants_package(
|
|
359
|
-
neutral_root / "workflow",
|
|
360
|
-
CONSTANTS_BODY,
|
|
361
|
-
"def noop() -> None:\n pass\n",
|
|
362
|
-
)
|
|
363
|
-
issues = _check(CONSTANTS_BODY, str(constants_path))
|
|
364
|
-
assert issues == [], f"File cap hit must return [] (cannot prove dead), got: {issues}"
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
def _write_noise_modules(directory: Path, count: int) -> None:
|
|
368
|
-
directory.mkdir(parents=True)
|
|
369
|
-
for each_index in range(count):
|
|
370
|
-
(directory / f"mod_{each_index}.py").write_text(
|
|
371
|
-
"def noop() -> int:\n return 1\n", encoding="utf-8"
|
|
372
|
-
)
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
def test_dead_exported_constant_flagged_despite_many_noncandidate_siblings(
|
|
376
|
-
neutral_root: Path, monkeypatch: pytest.MonkeyPatch
|
|
377
|
-
) -> None:
|
|
378
|
-
monkeypatch.setattr("code_rules_dead_module_constant.MAX_SCAN_ROOT_FILE_COUNT", 3)
|
|
379
|
-
package_directory = neutral_root / "pkg"
|
|
380
|
-
package_directory.mkdir(parents=True)
|
|
381
|
-
constants_body = 'DEAD_EXPORTED = "x"\n__all__ = ["DEAD_EXPORTED"]\n'
|
|
382
|
-
constants_path = package_directory / "settings_constants.py"
|
|
383
|
-
constants_path.write_text(constants_body, encoding="utf-8")
|
|
384
|
-
_write_noise_modules(neutral_root / "noise", 10)
|
|
385
|
-
issues = _check(constants_body, str(constants_path))
|
|
386
|
-
assert any("DEAD_EXPORTED" in each_issue for each_issue in issues), (
|
|
387
|
-
"A dead exported constant must stay flagged when the widened scan meets "
|
|
388
|
-
f"many sibling modules that never import from this module, got: {issues}"
|
|
389
|
-
)
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
def test_live_qualified_importer_found_despite_many_noncandidate_siblings(
|
|
393
|
-
neutral_root: Path, monkeypatch: pytest.MonkeyPatch
|
|
394
|
-
) -> None:
|
|
395
|
-
monkeypatch.setattr("code_rules_dead_module_constant.MAX_SCAN_ROOT_FILE_COUNT", 3)
|
|
396
|
-
package_directory = neutral_root / "pkg"
|
|
397
|
-
package_directory.mkdir(parents=True)
|
|
398
|
-
constants_body = 'LIVE_EXPORTED = "y"\n__all__ = ["LIVE_EXPORTED"]\n'
|
|
399
|
-
constants_path = package_directory / "settings_constants.py"
|
|
400
|
-
constants_path.write_text(constants_body, encoding="utf-8")
|
|
401
|
-
_write_noise_modules(neutral_root / "noise", 10)
|
|
402
|
-
consumer_directory = neutral_root / "app"
|
|
403
|
-
consumer_directory.mkdir(parents=True)
|
|
404
|
-
consumer_body = (
|
|
405
|
-
"from pkg.settings_constants import LIVE_EXPORTED\n"
|
|
406
|
-
"\n"
|
|
407
|
-
"def use() -> str:\n"
|
|
408
|
-
" return LIVE_EXPORTED\n"
|
|
409
|
-
)
|
|
410
|
-
(consumer_directory / "consumer.py").write_text(consumer_body, encoding="utf-8")
|
|
411
|
-
issues = _check(constants_body, str(constants_path))
|
|
412
|
-
assert issues == [], (
|
|
413
|
-
"A constant imported through a qualified from-import must stay live even "
|
|
414
|
-
f"when many non-importing siblings surround it, got: {issues}"
|
|
415
|
-
)
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
def test_widened_scan_reads_each_file_at_most_once(
|
|
419
|
-
neutral_root: Path, monkeypatch: pytest.MonkeyPatch
|
|
420
|
-
) -> None:
|
|
421
|
-
constants_body = 'CROSS_TREE_CONSTANT = "cross"\nLOCALLY_DEAD_CONSTANT = "dead"\n'
|
|
422
|
-
sibling_consumer_body = (
|
|
423
|
-
"from shared.theme_db.config.constants import CROSS_TREE_CONSTANT\n"
|
|
424
|
-
"\n"
|
|
425
|
-
"def tally() -> str:\n"
|
|
426
|
-
" return CROSS_TREE_CONSTANT\n"
|
|
427
|
-
)
|
|
428
|
-
constants_path = _build_cross_tree_repository(
|
|
429
|
-
neutral_root, constants_body, sibling_consumer_body
|
|
430
|
-
)
|
|
431
|
-
package_tree_neighbor = constants_path.parent.parent / "neighbor.py"
|
|
432
|
-
package_tree_neighbor.write_text(
|
|
433
|
-
"def neighbor() -> int:\n return 1\n", encoding="utf-8"
|
|
434
|
-
)
|
|
435
|
-
read_counts: dict[str, int] = {}
|
|
436
|
-
original_read_text = Path.read_text
|
|
437
|
-
|
|
438
|
-
def counting_read_text(self: Path, *positional: object, **keyword: object) -> str:
|
|
439
|
-
normalized_key = os.path.normcase(str(self.resolve()))
|
|
440
|
-
read_counts[normalized_key] = read_counts.get(normalized_key, 0) + 1
|
|
441
|
-
return original_read_text(self, *positional, **keyword) # type: ignore[arg-type] # forwards args
|
|
442
|
-
|
|
443
|
-
monkeypatch.setattr(Path, "read_text", counting_read_text)
|
|
444
|
-
_check(constants_body, str(constants_path))
|
|
445
|
-
over_read_paths = {
|
|
446
|
-
each_path: each_count for each_path, each_count in read_counts.items() if each_count > 1
|
|
447
|
-
}
|
|
448
|
-
assert not over_read_paths, (
|
|
449
|
-
f"Widening must read each .py file at most once, got over-reads: {over_read_paths}"
|
|
450
|
-
)
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
def _build_settings_package(workflow_directory: Path, constants_body: str) -> Path:
|
|
454
|
-
constants_package = workflow_directory / "app_settings"
|
|
455
|
-
constants_package.mkdir(parents=True)
|
|
456
|
-
(constants_package / "__init__.py").write_text("", encoding="utf-8")
|
|
457
|
-
constants_path = constants_package / "app_settings_constants.py"
|
|
458
|
-
constants_path.write_text(constants_body, encoding="utf-8")
|
|
459
|
-
return constants_path
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
def test_deny_once_records_intent_then_retry_passes_after_consumer(
|
|
463
|
-
neutral_root: Path,
|
|
464
|
-
) -> None:
|
|
465
|
-
constants_body = 'PATH_ENV_NAME = "THEME_PATH"\n'
|
|
466
|
-
workflow_directory = neutral_root / "workflow"
|
|
467
|
-
constants_path = _build_settings_package(workflow_directory, constants_body)
|
|
468
|
-
first_issues = _check(constants_body, str(constants_path))
|
|
469
|
-
assert any("PATH_ENV_NAME" in each_issue for each_issue in first_issues), first_issues
|
|
470
|
-
assert any(
|
|
471
|
-
"app_settings_constants.py" in each_issue for each_issue in first_issues
|
|
472
|
-
), f"Deny message must record the target constants module, got: {first_issues}"
|
|
473
|
-
assert any(
|
|
474
|
-
"re-issue this write" in each_issue for each_issue in first_issues
|
|
475
|
-
), f"Deny message must instruct the retry escape, got: {first_issues}"
|
|
476
|
-
consumer_body = (
|
|
477
|
-
"from app_settings.app_settings_constants import PATH_ENV_NAME\n"
|
|
478
|
-
"\n"
|
|
479
|
-
"def resolve() -> str:\n"
|
|
480
|
-
" return PATH_ENV_NAME\n"
|
|
481
|
-
)
|
|
482
|
-
(workflow_directory / "consumer_service.py").write_text(
|
|
483
|
-
consumer_body, encoding="utf-8"
|
|
484
|
-
)
|
|
485
|
-
retry_issues = _check(constants_body, str(constants_path))
|
|
486
|
-
assert retry_issues == [], (
|
|
487
|
-
f"Once the consumer is on disk the retry passes, got: {retry_issues}"
|
|
488
|
-
)
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
def test_dead_constant_stays_denied_on_every_attempt(neutral_root: Path) -> None:
|
|
492
|
-
constants_body = 'PATH_ENV_NAME = "THEME_PATH"\n'
|
|
493
|
-
workflow_directory = neutral_root / "workflow"
|
|
494
|
-
constants_path = _build_settings_package(workflow_directory, constants_body)
|
|
495
|
-
(workflow_directory / "unrelated.py").write_text(
|
|
496
|
-
"def noop() -> int:\n return 1\n", encoding="utf-8"
|
|
497
|
-
)
|
|
498
|
-
first_issues = _check(constants_body, str(constants_path))
|
|
499
|
-
second_issues = _check(constants_body, str(constants_path))
|
|
500
|
-
for each_attempt in (first_issues, second_issues):
|
|
501
|
-
assert any("PATH_ENV_NAME" in each_issue for each_issue in each_attempt), each_attempt
|
|
502
|
-
assert any(
|
|
503
|
-
"app_settings_constants.py" in each_issue for each_issue in each_attempt
|
|
504
|
-
), f"Every deny attempt records the target module, got: {each_attempt}"
|
|
505
|
-
assert any(
|
|
506
|
-
"re-issue this write" in each_issue for each_issue in each_attempt
|
|
507
|
-
), f"A dead constant stays denied with the retry escape, got: {each_attempt}"
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
ALIAS_CONSTANTS_BODY = 'BASE_PROMPTS_DIRECTORY_NAME = "prompts"\nSTYLE_SHEET_FILENAME = "style.css"\n'
|
|
511
|
-
|
|
512
|
-
ALIASED_IMPORT_CONSUMER_BODY = (
|
|
513
|
-
"from style_guide_gen_constants.config import constants as sg\n"
|
|
514
|
-
"\n"
|
|
515
|
-
"def prompts_directory() -> str:\n"
|
|
516
|
-
" return sg.BASE_PROMPTS_DIRECTORY_NAME\n"
|
|
517
|
-
"\n"
|
|
518
|
-
"def style_sheet() -> str:\n"
|
|
519
|
-
" return sg.STYLE_SHEET_FILENAME\n"
|
|
520
|
-
)
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
def _build_alias_repository(
|
|
524
|
-
package_parent: Path,
|
|
525
|
-
consumer_directory: Path,
|
|
526
|
-
consumer_body: str,
|
|
527
|
-
) -> Path:
|
|
528
|
-
package_directory = package_parent / "style_guide_gen_constants"
|
|
529
|
-
config_directory = package_directory / "config"
|
|
530
|
-
config_directory.mkdir(parents=True)
|
|
531
|
-
(package_directory / "__init__.py").write_text("", encoding="utf-8")
|
|
532
|
-
(config_directory / "__init__.py").write_text("", encoding="utf-8")
|
|
533
|
-
constants_path = config_directory / "constants.py"
|
|
534
|
-
constants_path.write_text(ALIAS_CONSTANTS_BODY, encoding="utf-8")
|
|
535
|
-
consumer_directory.mkdir(parents=True, exist_ok=True)
|
|
536
|
-
(consumer_directory / "style_guide_gen.py").write_text(consumer_body, encoding="utf-8")
|
|
537
|
-
return constants_path
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
def test_does_not_flag_constant_read_through_an_aliased_module_import(
|
|
541
|
-
neutral_root: Path,
|
|
542
|
-
) -> None:
|
|
543
|
-
constants_path = _build_alias_repository(
|
|
544
|
-
neutral_root,
|
|
545
|
-
neutral_root / "style_guide_gen_constants",
|
|
546
|
-
ALIASED_IMPORT_CONSUMER_BODY,
|
|
547
|
-
)
|
|
548
|
-
issues = _check(ALIAS_CONSTANTS_BODY, str(constants_path))
|
|
549
|
-
assert issues == [], (
|
|
550
|
-
f"A constant read as an attribute on an aliased module import stays live, got: {issues}"
|
|
551
|
-
)
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
def test_does_not_flag_constant_read_through_an_alias_outside_the_package_tree(
|
|
555
|
-
neutral_root: Path,
|
|
556
|
-
) -> None:
|
|
557
|
-
constants_path = _build_alias_repository(
|
|
558
|
-
neutral_root / "scripts",
|
|
559
|
-
neutral_root / "scripts",
|
|
560
|
-
ALIASED_IMPORT_CONSUMER_BODY,
|
|
561
|
-
)
|
|
562
|
-
issues = _check(ALIAS_CONSTANTS_BODY, str(constants_path))
|
|
563
|
-
assert issues == [], (
|
|
564
|
-
"A constant an aliased module import reads from outside the package tree "
|
|
565
|
-
f"stays live through the widened scan, got: {issues}"
|
|
566
|
-
)
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
def test_does_not_flag_constant_read_through_an_unaliased_module_import_outside_the_tree(
|
|
570
|
-
neutral_root: Path,
|
|
571
|
-
) -> None:
|
|
572
|
-
consumer_body = (
|
|
573
|
-
"from style_guide_gen_constants.config import constants\n"
|
|
574
|
-
"\n"
|
|
575
|
-
"def prompts_directory() -> str:\n"
|
|
576
|
-
" return constants.BASE_PROMPTS_DIRECTORY_NAME\n"
|
|
577
|
-
"\n"
|
|
578
|
-
"def style_sheet() -> str:\n"
|
|
579
|
-
" return constants.STYLE_SHEET_FILENAME\n"
|
|
580
|
-
)
|
|
581
|
-
constants_path = _build_alias_repository(
|
|
582
|
-
neutral_root / "scripts",
|
|
583
|
-
neutral_root / "scripts",
|
|
584
|
-
consumer_body,
|
|
585
|
-
)
|
|
586
|
-
issues = _check(ALIAS_CONSTANTS_BODY, str(constants_path))
|
|
587
|
-
assert issues == [], (
|
|
588
|
-
f"A constant read on an unaliased imported module object stays live, got: {issues}"
|
|
589
|
-
)
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
def test_does_not_flag_constant_read_through_a_dotted_module_path_outside_the_tree(
|
|
593
|
-
neutral_root: Path,
|
|
594
|
-
) -> None:
|
|
595
|
-
consumer_body = (
|
|
596
|
-
"import style_guide_gen_constants.config.constants\n"
|
|
597
|
-
"\n"
|
|
598
|
-
"def prompts_directory() -> str:\n"
|
|
599
|
-
" return style_guide_gen_constants.config.constants.BASE_PROMPTS_DIRECTORY_NAME\n"
|
|
600
|
-
"\n"
|
|
601
|
-
"def style_sheet() -> str:\n"
|
|
602
|
-
" return style_guide_gen_constants.config.constants.STYLE_SHEET_FILENAME\n"
|
|
603
|
-
)
|
|
604
|
-
constants_path = _build_alias_repository(
|
|
605
|
-
neutral_root / "scripts",
|
|
606
|
-
neutral_root / "scripts",
|
|
607
|
-
consumer_body,
|
|
608
|
-
)
|
|
609
|
-
issues = _check(ALIAS_CONSTANTS_BODY, str(constants_path))
|
|
610
|
-
assert issues == [], (
|
|
611
|
-
f"A constant read through the full dotted module path stays live, got: {issues}"
|
|
612
|
-
)
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
def test_does_not_flag_constant_read_through_an_alias_in_a_sibling_tree(
|
|
616
|
-
neutral_root: Path,
|
|
617
|
-
) -> None:
|
|
618
|
-
constants_body = 'CROSS_TREE_CONSTANT = "cross"\nLOCALLY_DEAD_CONSTANT = "dead"\n'
|
|
619
|
-
sibling_consumer_body = (
|
|
620
|
-
"from shared.theme_db.config import constants as theme_constants\n"
|
|
621
|
-
"\n"
|
|
622
|
-
"def tally() -> str:\n"
|
|
623
|
-
" return theme_constants.CROSS_TREE_CONSTANT\n"
|
|
624
|
-
)
|
|
625
|
-
constants_path = _build_cross_tree_repository(
|
|
626
|
-
neutral_root, constants_body, sibling_consumer_body
|
|
627
|
-
)
|
|
628
|
-
issues = _check(constants_body, str(constants_path))
|
|
629
|
-
assert not any("CROSS_TREE_CONSTANT" in each_issue for each_issue in issues), (
|
|
630
|
-
"A constant a sibling tree reads as an attribute on an aliased module import "
|
|
631
|
-
f"must not be flagged, got: {issues}"
|
|
632
|
-
)
|
|
633
|
-
assert any("LOCALLY_DEAD_CONSTANT" in each_issue for each_issue in issues), (
|
|
634
|
-
f"A constant referenced nowhere in the repository stays flagged, got: {issues}"
|
|
635
|
-
)
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
def _build_alias_collision_repository(
|
|
639
|
-
repository_root: Path,
|
|
640
|
-
promoter_constants_body: str,
|
|
641
|
-
harness_constants_body: str,
|
|
642
|
-
harness_consumer_body: str,
|
|
643
|
-
) -> Path:
|
|
644
|
-
promoter_config = repository_root / "promoter" / "config"
|
|
645
|
-
promoter_config.mkdir(parents=True)
|
|
646
|
-
promoter_constants_path = promoter_config / "constants.py"
|
|
647
|
-
promoter_constants_path.write_text(promoter_constants_body, encoding="utf-8")
|
|
648
|
-
harness_config = repository_root / "harness" / "config"
|
|
649
|
-
harness_config.mkdir(parents=True)
|
|
650
|
-
(harness_config / "other_constants.py").write_text(harness_constants_body, encoding="utf-8")
|
|
651
|
-
(repository_root / "harness" / "lockfile.py").write_text(
|
|
652
|
-
harness_consumer_body, encoding="utf-8"
|
|
653
|
-
)
|
|
654
|
-
return promoter_constants_path
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
def test_alias_attribute_read_on_an_unrelated_module_does_not_mask_a_dead_constant(
|
|
658
|
-
neutral_root: Path,
|
|
659
|
-
) -> None:
|
|
660
|
-
promoter_constants_body = 'NEVER_READ_CONSTANT = "dead"\n'
|
|
661
|
-
harness_constants_body = 'NEVER_READ_CONSTANT = "twin"\n'
|
|
662
|
-
harness_consumer_body = (
|
|
663
|
-
"from harness.config import other_constants as oc\n"
|
|
664
|
-
"\n"
|
|
665
|
-
"def acquire() -> str:\n"
|
|
666
|
-
" return oc.NEVER_READ_CONSTANT\n"
|
|
667
|
-
)
|
|
668
|
-
promoter_constants_path = _build_alias_collision_repository(
|
|
669
|
-
neutral_root,
|
|
670
|
-
promoter_constants_body,
|
|
671
|
-
harness_constants_body,
|
|
672
|
-
harness_consumer_body,
|
|
673
|
-
)
|
|
674
|
-
issues = _check(promoter_constants_body, str(promoter_constants_path))
|
|
675
|
-
assert any("NEVER_READ_CONSTANT" in each_issue for each_issue in issues), (
|
|
676
|
-
"A constant whose only same-named twin is read as an attribute on an "
|
|
677
|
-
f"unrelated aliased module must still be flagged dead, got: {issues}"
|
|
678
|
-
)
|
|
679
|
-
|