claude-dev-env 8.24.0 → 8.25.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.
@@ -1,134 +0,0 @@
1
- """Tests for the printf-token check on str.format-logger (automation_logging) calls."""
2
-
3
- from __future__ import annotations
4
-
5
- import importlib.util
6
- from pathlib import Path
7
- from types import ModuleType
8
-
9
- ENFORCER_FILENAME = "code_rules_enforcer.py"
10
- ENFORCER_MODULE_NAME = "code_rules_enforcer_printf_under_test"
11
- PRODUCTION_FILE_PATH = "shared_utils/web_automation/sample.py"
12
- TEST_FILE_PATH = "shared_utils/web_automation/tests/test_sample.py"
13
- FORMAT_LOGGER_IMPORT = (
14
- "from shared_utils.automation_logging import log_error, log_info, log_debug\n"
15
- )
16
-
17
-
18
- def load_enforcer_module() -> ModuleType:
19
- enforcer_path = Path(__file__).parent / ENFORCER_FILENAME
20
- module_spec = importlib.util.spec_from_file_location(ENFORCER_MODULE_NAME, enforcer_path)
21
- assert module_spec is not None
22
- assert module_spec.loader is not None
23
- enforcer_module = importlib.util.module_from_spec(module_spec)
24
- module_spec.loader.exec_module(enforcer_module)
25
- return enforcer_module
26
-
27
-
28
- enforcer = load_enforcer_module()
29
-
30
-
31
- def test_should_flag_percent_s_in_format_logger_call() -> None:
32
- source = FORMAT_LOGGER_IMPORT + 'log_error("Skipping %s after error: %s", name, err)\n'
33
- issues = enforcer.check_logging_printf_tokens(source, PRODUCTION_FILE_PATH)
34
- assert len(issues) == 1
35
- assert "printf token" in issues[0]
36
-
37
-
38
- def test_should_flag_percent_d_in_format_logger_call() -> None:
39
- source = FORMAT_LOGGER_IMPORT + 'log_debug("attempt %d of %d", index, total)\n'
40
- issues = enforcer.check_logging_printf_tokens(source, PRODUCTION_FILE_PATH)
41
- assert len(issues) == 1
42
-
43
-
44
- def test_should_allow_brace_placeholders_in_format_logger_call() -> None:
45
- source = FORMAT_LOGGER_IMPORT + 'log_info("Processing {} of {}", index, total)\n'
46
- issues = enforcer.check_logging_printf_tokens(source, PRODUCTION_FILE_PATH)
47
- assert issues == []
48
-
49
-
50
- def test_should_ignore_percent_token_without_format_logger_import() -> None:
51
- source = 'log_error("Skipping %s", name)\n'
52
- issues = enforcer.check_logging_printf_tokens(source, PRODUCTION_FILE_PATH)
53
- assert issues == []
54
-
55
-
56
- def test_should_ignore_attribute_style_logger_call() -> None:
57
- source = FORMAT_LOGGER_IMPORT + 'logger.info("delivered %s", message_id)\n'
58
- issues = enforcer.check_logging_printf_tokens(source, PRODUCTION_FILE_PATH)
59
- assert issues == []
60
-
61
-
62
- def test_should_inspect_message_literal_not_argument_value() -> None:
63
- source = FORMAT_LOGGER_IMPORT + 'log_info("status: {}", "100%s done")\n'
64
- issues = enforcer.check_logging_printf_tokens(source, PRODUCTION_FILE_PATH)
65
- assert issues == []
66
-
67
-
68
- def test_should_exempt_test_files() -> None:
69
- source = FORMAT_LOGGER_IMPORT + 'log_error("Skipping %s", name)\n'
70
- issues = enforcer.check_logging_printf_tokens(source, TEST_FILE_PATH)
71
- assert issues == []
72
-
73
-
74
- def test_should_resolve_aliased_format_logger_import() -> None:
75
- source = (
76
- "from shared_utils.automation_logging import log_error as report_error\n"
77
- + 'report_error("failed %s", name)\n'
78
- )
79
- issues = enforcer.check_logging_printf_tokens(source, PRODUCTION_FILE_PATH)
80
- assert len(issues) == 1
81
-
82
-
83
- def test_should_allow_token_bearing_message_with_no_format_args() -> None:
84
- source = FORMAT_LOGGER_IMPORT + 'log_info("Use %s for string substitution")\n'
85
- issues = enforcer.check_logging_printf_tokens(source, PRODUCTION_FILE_PATH)
86
- assert issues == []
87
-
88
-
89
- def test_should_allow_documentation_style_token_message_with_no_args() -> None:
90
- source = FORMAT_LOGGER_IMPORT + 'log_warning("avoid %s-style tokens here")\n'
91
- issues = enforcer.check_logging_printf_tokens(source, PRODUCTION_FILE_PATH)
92
- assert issues == []
93
-
94
-
95
- def test_should_allow_percent_adjacent_to_word_in_format_message() -> None:
96
- source = FORMAT_LOGGER_IMPORT + 'log_info("memory 80%free now", host)\n'
97
- issues = enforcer.check_logging_printf_tokens(source, PRODUCTION_FILE_PATH)
98
- assert issues == []
99
-
100
-
101
- def test_should_allow_doubled_percent_in_format_message() -> None:
102
- source = FORMAT_LOGGER_IMPORT + 'log_info("100%% done", host)\n'
103
- issues = enforcer.check_logging_printf_tokens(source, PRODUCTION_FILE_PATH)
104
- assert issues == []
105
-
106
-
107
- def test_should_allow_trailing_percent_in_format_message() -> None:
108
- source = FORMAT_LOGGER_IMPORT + 'log_info("100% done", host)\n'
109
- issues = enforcer.check_logging_printf_tokens(source, PRODUCTION_FILE_PATH)
110
- assert issues == []
111
-
112
-
113
- def test_should_flag_width_token_in_format_logger_call() -> None:
114
- source = FORMAT_LOGGER_IMPORT + 'log_error("item %5d", index)\n'
115
- issues = enforcer.check_logging_printf_tokens(source, PRODUCTION_FILE_PATH)
116
- assert len(issues) == 1
117
-
118
-
119
- def test_should_flag_precision_token_in_format_logger_call() -> None:
120
- source = FORMAT_LOGGER_IMPORT + 'log_error("took %0.2f sec", elapsed)\n'
121
- issues = enforcer.check_logging_printf_tokens(source, PRODUCTION_FILE_PATH)
122
- assert len(issues) == 1
123
-
124
-
125
- def test_should_flag_float_general_token_in_format_logger_call() -> None:
126
- source = FORMAT_LOGGER_IMPORT + 'log_error("ratio %g", ratio)\n'
127
- issues = enforcer.check_logging_printf_tokens(source, PRODUCTION_FILE_PATH)
128
- assert len(issues) == 1
129
-
130
-
131
- def test_should_flag_scientific_token_in_format_logger_call() -> None:
132
- source = FORMAT_LOGGER_IMPORT + 'log_error("value %e", measurement)\n'
133
- issues = enforcer.check_logging_printf_tokens(source, PRODUCTION_FILE_PATH)
134
- assert len(issues) == 1
@@ -1,29 +0,0 @@
1
- # Running Commands on the NAS
2
-
3
- Reach the NAS through the `nas_ssh_key.py` runner, never through `ssh`, `scp` or `sftp`:
4
-
5
- ```
6
- python <runner-path> <command-script.sh> <private-key-path>
7
- ```
8
-
9
- The first argument is a **path to a file** holding a bash script, not a command string. Write that
10
- file with the Write tool. The whole script runs in one shell, so variables, `cd` and `source` carry
11
- from line to line.
12
-
13
- The runner ships with the automation project it serves; read its path from that project's own
14
- reference. Use the ops key under `~/.claude/keys` — keys under `~/.ssh` either carry a passphrase,
15
- which cannot be answered unattended, or belong to other hosts.
16
-
17
- The runner loads the key with paramiko and signs in the same process. The command-line clients check
18
- the key file's permissions first and refuse it, and Git Bash's `ssh` then falls back to a password
19
- prompt that hangs an unattended run. No hook denies an ssh-family word aimed at the NAS, so keep to
20
- the runner yourself. When the `System32/OpenSSH` binary is unavoidable, pass `-o BatchMode=yes` so it
21
- fails fast on a refused key.
22
-
23
- Host, ssh port and ssh user are constants inside the runner and are also under the `nas` key in
24
- `~/.claude/local-identity.json`. Keep all three out of anything committed or posted.
25
-
26
- To copy a file, write it inside the script with a quoted heredoc rather than reaching for `scp`.
27
-
28
- The `/tmp` size limit, how to tell a break in the code from a platform-only one, and a failure-to-cause
29
- table: `@~/.claude/docs/nas-ssh-invocation.md`.