claude-dev-env 8.46.26 → 8.46.28
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/docs/code-rules/core-principles-and-config.md +2 -2
- package/docs/code-rules/lint-enforced-rules.md +7 -7
- package/hooks/blocking/test_code_rules_enforcer_library_print.py +26 -0
- package/hooks/blocking/test_code_rules_enforcer_windows_api_none.py +17 -0
- package/package.json +1 -1
- package/scripts/account_broker_support.py +10 -1
- package/scripts/test_account_broker.py +15 -0
- package/scripts/test_account_broker_support.py +29 -0
- package/scripts/tests/test_code_rules_index.py +24 -0
|
@@ -30,9 +30,9 @@ Search config for the exact value and a semantic match. Add timing to config/tim
|
|
|
30
30
|
|
|
31
31
|
Run the named pytest node or command with the described input. A breach produces a rule finding; advisory checks write to stderr.
|
|
32
32
|
|
|
33
|
-
- check_config_duplicate_path_anchor: two config constants pointing at the same path; `python -m pytest packages/claude-dev-env/hooks/blocking` reports a named violation.
|
|
33
|
+
- check_config_duplicate_path_anchor: two config constants pointing at the same path; `python -m pytest packages/claude-dev-env/hooks/blocking/test_code_rules_config_duplicate_path_anchor.py::test_should_flag_reanchored_base_already_built_by_sibling` reports a named violation.
|
|
34
34
|
- check_constants_outside_config: UPPER_SNAKE = 3 in a production module; `python -m pytest packages/claude-dev-env/hooks/blocking/test_code_rules_enforcer_config_path.py::test_should_produce_blocking_for_module_level_upper_snake_outside_config` reports a named violation.
|
|
35
|
-
- check_constants_outside_config_advisory: a function-local UPPER_SNAKE constant; `python -m pytest packages/claude-dev-env/hooks/blocking` reports a stderr advisory.
|
|
35
|
+
- check_constants_outside_config_advisory: a function-local UPPER_SNAKE constant; `python -m pytest packages/claude-dev-env/hooks/blocking/test_code_rules_enforcer_split_constants_config.py::test_advisory_should_flag_annotated_function_body_constant` reports a stderr advisory.
|
|
36
36
|
- check_fstring_structural_literals: an f-string that embeds a URL path fragment; `python -m pytest packages/claude-dev-env/hooks/blocking/test_code_rules_enforcer_fstring_scan.py::test_should_flag_fstring_with_url_path` reports a named violation.
|
|
37
37
|
- check_magic_values: a production function comparing a value with 2; `python -m pytest packages/claude-dev-env/hooks/blocking/test_code_rules_enforcer_magic_allowlist.py::test_check_magic_values_should_flag_literal_two_in_function_body` reports a named violation.
|
|
38
38
|
- check_duplicated_format_patterns: the same f-string skeleton at three call sites; `python -m pytest packages/claude-dev-env/hooks/blocking/test_code_rules_enforcer_split_optional_params.py::test_should_advise_when_fstring_skeleton_appears_three_or_more_times` reports a stderr advisory.
|
|
@@ -46,15 +46,15 @@ Run the named pytest node or command with the described input. A breach produces
|
|
|
46
46
|
- check_module_docstring_names_public_checks: a module summary omitting a public check; `python -m pytest packages/claude-dev-env/hooks/blocking/test_code_rules_enforcer_module_docstring_roster.py::test_should_flag_module_docstring_omitting_a_public_check` reports a named violation.
|
|
47
47
|
- check_module_docstring_scope_omits_data_schema_constants: a module summary listing schema constants as behavior; `python -m pytest packages/claude-dev-env/hooks/blocking/test_code_rules_enforcer_module_docstring_data_schema_scope.py` reports a named violation.
|
|
48
48
|
- check_imports_at_top: an ordinary import inside a function; `python -m pytest packages/claude-dev-env/hooks/blocking/test_code_rules_enforcer_type_checking_scope.py::test_should_flag_runtime_import_inside_function_even_if_file_uses_type_checking` reports a named violation.
|
|
49
|
-
- check_js_bare_flag_return_directive: a JavaScript return directive with an unexplained bare flag; `python -m pytest packages/claude-dev-env/hooks/blocking` reports a named violation.
|
|
50
|
-
- check_js_resume_task_enumeration_coverage: a resume task branch missing an enumerated state; `python -m pytest packages/claude-dev-env/hooks/blocking` reports a named violation.
|
|
51
|
-
- check_js_returns_object_schemaless_branch: sibling JavaScript return paths with an ad hoc object; `python -m pytest packages/claude-dev-env/hooks/blocking` reports a named violation.
|
|
52
|
-
- check_js_sibling_return_object_key_drift: sibling return objects with different keys; `python -m pytest packages/claude-dev-env/hooks/blocking` reports a named violation.
|
|
53
|
-
- check_library_print: a print call in library code; `python -m pytest packages/claude-dev-env/hooks/blocking` reports a named violation.
|
|
54
|
-
- check_logging_adjacent_string_literals: adjacent string literals in a logger call; `python -m pytest packages/claude-dev-env/hooks/blocking` reports a named violation.
|
|
49
|
+
- check_js_bare_flag_return_directive: a JavaScript return directive with an unexplained bare flag; `python -m pytest packages/claude-dev-env/hooks/blocking/test_code_rules_js_bare_flag_return_directive.py::test_flags_bare_down_directive_when_the_workflow_states_the_contract` reports a named violation.
|
|
50
|
+
- check_js_resume_task_enumeration_coverage: a resume task branch missing an enumerated state; `python -m pytest packages/claude-dev-env/hooks/blocking/test_code_rules_js_resume_task_enumeration.py::should_flag_resume_branch_absent_from_spawn_enumeration` reports a named violation.
|
|
51
|
+
- check_js_returns_object_schemaless_branch: sibling JavaScript return paths with an ad hoc object; `python -m pytest packages/claude-dev-env/hooks/blocking/test_code_rules_js_returns_object_schemaless.py::test_flags_schema_less_branch_under_returns_object_claim` reports a named violation.
|
|
52
|
+
- check_js_sibling_return_object_key_drift: sibling return objects with different keys; `python -m pytest packages/claude-dev-env/hooks/blocking/test_code_rules_js_sibling_return_object_key_drift.py::test_flags_blocker_return_that_omits_one_documented_field` reports a named violation.
|
|
53
|
+
- check_library_print: a print call in library code; `python -m pytest packages/claude-dev-env/hooks/blocking/test_code_rules_enforcer_library_print.py::test_should_flag_print_call_in_library_module` reports a named violation.
|
|
54
|
+
- check_logging_adjacent_string_literals: adjacent string literals in a logger call; `python -m pytest packages/claude-dev-env/hooks/blocking/test_code_rules_logging_adjacent_literals.py::test_should_flag_adjacent_literals_in_attribute_logger_call` reports a named violation.
|
|
55
55
|
- check_logging_fstrings: an f-string passed to logger.info; `python -m pytest packages/claude-dev-env/hooks/blocking/test_code_rules_enforcer_logger_fstring.py::test_should_flag_logger_info_fstring` reports a named violation.
|
|
56
56
|
- check_naive_datetime_construction: datetime.fromtimestamp without a timezone; `python -m pytest packages/claude-dev-env/hooks/blocking/test_code_rules_enforcer_naive_datetime.py::test_flags_fromtimestamp_without_timezone` reports a named violation.
|
|
57
|
-
- check_windows_api_none: a Windows API call passed None for a required argument; `python -m pytest packages/claude-dev-env/hooks/blocking` reports a named violation.
|
|
57
|
+
- check_windows_api_none: a Windows API call passed None for a required argument; `python -m pytest packages/claude-dev-env/hooks/blocking/test_code_rules_enforcer_windows_api_none.py::test_should_flag_win32gui_call_passing_none` reports a named violation.
|
|
58
58
|
|
|
59
59
|
## Gotchas
|
|
60
60
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Tests for check_library_print."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from code_rules_enforcer import check_library_print
|
|
6
|
+
|
|
7
|
+
LIBRARY_FILE_PATH = "shared_utils/web_automation/sample.py"
|
|
8
|
+
CLI_FILE_PATH = "shared_utils/web_automation/sample_cli.py"
|
|
9
|
+
PRINTING_SOURCE = "def report_status(status_text: str) -> None:\n print(status_text)\n"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_should_flag_print_call_in_library_module() -> None:
|
|
13
|
+
issues = check_library_print(PRINTING_SOURCE, LIBRARY_FILE_PATH)
|
|
14
|
+
assert issues == [
|
|
15
|
+
"Line 2: Library print() - route through logger or accept an explicit stream parameter"
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_should_flag_sys_stderr_write_in_library_module() -> None:
|
|
20
|
+
writing_source = "import sys\n\nsys.stderr.write('done')\n"
|
|
21
|
+
issues = check_library_print(writing_source, LIBRARY_FILE_PATH)
|
|
22
|
+
assert issues == ["Line 3: sys.stderr.write - route through logger"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_should_allow_print_call_in_cli_entry_point() -> None:
|
|
26
|
+
assert check_library_print(PRINTING_SOURCE, CLI_FILE_PATH) == []
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Tests for check_windows_api_none."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from code_rules_enforcer import check_windows_api_none
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_should_flag_win32gui_call_passing_none() -> None:
|
|
9
|
+
source = "win32gui.SetWindowPos(window_handle, " + "None)\n"
|
|
10
|
+
assert check_windows_api_none(source) == [
|
|
11
|
+
"Line 1: win32gui call with None - use 0 for unused int params"
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_should_allow_win32gui_call_passing_zero() -> None:
|
|
16
|
+
source = "win32gui.SetWindowPos(window_handle, 0)\n"
|
|
17
|
+
assert check_windows_api_none(source) == []
|
package/package.json
CHANGED
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import errno
|
|
6
6
|
import json
|
|
7
7
|
import os
|
|
8
|
+
import shutil
|
|
8
9
|
import subprocess
|
|
9
10
|
import sys
|
|
10
11
|
import tempfile
|
|
@@ -428,13 +429,21 @@ def _resume_id(all_argv: Sequence[str]) -> str | None:
|
|
|
428
429
|
return None
|
|
429
430
|
|
|
430
431
|
|
|
432
|
+
def _resolve_command(all_argv: Sequence[str]) -> list[str]:
|
|
433
|
+
command_name, *all_arguments = all_argv
|
|
434
|
+
resolved_command = shutil.which(command_name)
|
|
435
|
+
if resolved_command is None and not os.path.dirname(command_name):
|
|
436
|
+
raise FileNotFoundError(errno.ENOENT, "command not found on PATH", command_name)
|
|
437
|
+
return [resolved_command or command_name, *all_arguments]
|
|
438
|
+
|
|
439
|
+
|
|
431
440
|
def _run_captured_subprocess(all_argv: Sequence[str], **options: object) -> subprocess.CompletedProcess[str]:
|
|
432
441
|
encoding = str(options.get("encoding") or "utf-8")
|
|
433
442
|
errors = str(options.get("errors") or "replace")
|
|
434
443
|
stdin_bytes = options.get("input")
|
|
435
444
|
with tempfile.TemporaryFile() as stdout_file, tempfile.TemporaryFile() as stderr_file:
|
|
436
445
|
completion = subprocess.run(
|
|
437
|
-
|
|
446
|
+
_resolve_command(all_argv),
|
|
438
447
|
input=stdin_bytes,
|
|
439
448
|
stdout=stdout_file,
|
|
440
449
|
stderr=stderr_file,
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
import io
|
|
5
|
+
import shutil
|
|
5
6
|
import subprocess
|
|
6
7
|
from datetime import datetime, timedelta, timezone
|
|
7
8
|
from pathlib import Path
|
|
@@ -696,6 +697,20 @@ def test_should_run_job_through_override(
|
|
|
696
697
|
assert calls == [b"input"]
|
|
697
698
|
|
|
698
699
|
|
|
700
|
+
def test_should_record_start_failure_without_launching_a_command_missing_from_path(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
701
|
+
monkeypatch.setitem(account_broker.all_product_adapters, Product.CLAUDE, _adapter(
|
|
702
|
+
(_account("extra", Product.CLAUDE),), {"extra": _meters(80, 80)}
|
|
703
|
+
))
|
|
704
|
+
monkeypatch.setattr(shutil, "which", lambda name: None)
|
|
705
|
+
monkeypatch.setattr(account_broker.support.subprocess, "run", lambda *arguments, **options: pytest.fail("a missing command ran"))
|
|
706
|
+
|
|
707
|
+
outcome, _ = account_broker._execute(Product.CLAUDE, ("claude", "-p"), now=NOW)
|
|
708
|
+
|
|
709
|
+
assert outcome.attempts == (("extra", "start_failed"),)
|
|
710
|
+
assert outcome.status == "exhausted"
|
|
711
|
+
assert outcome.returncode == WAIT_EXIT_CODE
|
|
712
|
+
|
|
713
|
+
|
|
699
714
|
@pytest.mark.parametrize("marked_name", ["retired", "first"])
|
|
700
715
|
def test_should_run_past_a_stored_spent_mark_beyond_the_platform_range(
|
|
701
716
|
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str], marked_name: str
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import errno
|
|
4
4
|
import json
|
|
5
5
|
import os
|
|
6
|
+
import shutil
|
|
6
7
|
import subprocess
|
|
7
8
|
from datetime import datetime, timezone
|
|
8
9
|
from pathlib import Path
|
|
@@ -161,3 +162,31 @@ def test_should_raise_windows_lock_error_other_than_contention(tmp_path: Path) -
|
|
|
161
162
|
os.close(lock_descriptor)
|
|
162
163
|
|
|
163
164
|
assert all_calls == [lock_descriptor]
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def test_should_launch_the_path_resolved_command_file(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
168
|
+
resolved_command = r"C:\Users\someone\AppData\Roaming\npm\claude.cmd"
|
|
169
|
+
monkeypatch.setattr(shutil, "which", lambda name: resolved_command if name == "claude" else None)
|
|
170
|
+
all_launched_argv: list[list[str]] = []
|
|
171
|
+
|
|
172
|
+
def fake_run(all_argv: list[str], **options: object) -> subprocess.CompletedProcess[bytes]:
|
|
173
|
+
all_launched_argv.append(list(all_argv))
|
|
174
|
+
return subprocess.CompletedProcess(all_argv, 0)
|
|
175
|
+
|
|
176
|
+
monkeypatch.setattr(support.subprocess, "run", fake_run)
|
|
177
|
+
|
|
178
|
+
completion = support.subprocess_runner(["claude", "-p"], input=b"", encoding="utf-8", errors="replace")
|
|
179
|
+
|
|
180
|
+
assert all_launched_argv == [[resolved_command, "-p"]]
|
|
181
|
+
assert completion.returncode == 0
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def test_should_refuse_to_launch_a_bare_command_missing_from_path(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
185
|
+
monkeypatch.setattr(shutil, "which", lambda name: None)
|
|
186
|
+
monkeypatch.setattr(support.subprocess, "run", lambda *arguments, **options: pytest.fail("a missing command ran"))
|
|
187
|
+
|
|
188
|
+
with pytest.raises(FileNotFoundError) as raised:
|
|
189
|
+
support.subprocess_runner(["claude", "-p"])
|
|
190
|
+
|
|
191
|
+
assert raised.value.errno == errno.ENOENT
|
|
192
|
+
assert raised.value.filename == "claude"
|
|
@@ -103,6 +103,30 @@ def test_should_assign_each_enforcer_check_to_one_family() -> None:
|
|
|
103
103
|
assert documented_checks == imported_checks
|
|
104
104
|
|
|
105
105
|
|
|
106
|
+
def _assert_proof_target_resolves(proof_label: str, proof_target: str) -> None:
|
|
107
|
+
module_text, _, test_name = proof_target.partition("::")
|
|
108
|
+
module_path = _REPOSITORY_ROOT / module_text
|
|
109
|
+
assert module_path.is_file(), proof_label
|
|
110
|
+
if test_name:
|
|
111
|
+
assert re.search(
|
|
112
|
+
rf"^def {re.escape(test_name)}\(",
|
|
113
|
+
module_path.read_text(encoding="utf-8"),
|
|
114
|
+
re.MULTILINE,
|
|
115
|
+
), proof_label
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_should_name_a_test_module_in_each_proof_command() -> None:
|
|
119
|
+
for each_family_path in _FAMILY_DIRECTORY.glob("*.md"):
|
|
120
|
+
family_text = each_family_path.read_text(encoding="utf-8")
|
|
121
|
+
all_proofs = re.findall(
|
|
122
|
+
r"^- (check_\w+):.*?`python -m pytest ([^`\s]+)", family_text, re.MULTILINE
|
|
123
|
+
)
|
|
124
|
+
for each_check, each_target in all_proofs:
|
|
125
|
+
_assert_proof_target_resolves(
|
|
126
|
+
f"{each_family_path.name}: {each_check} -> {each_target}", each_target
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
106
130
|
def _collect_citation_texts() -> list[tuple[Path, str]]:
|
|
107
131
|
all_citations: list[tuple[Path, str]] = []
|
|
108
132
|
for each_root in _CITATION_ROOTS:
|