claude-dev-env 1.72.0 → 1.73.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/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +2 -2
- package/bin/install.mjs +73 -5
- package/bin/install.test.mjs +360 -4
- package/hooks/blocking/CLAUDE.md +3 -1
- package/hooks/blocking/claude_md_orphan_file_blocker.py +5 -6
- package/hooks/blocking/code_rules_dead_config_field.py +69 -56
- package/hooks/blocking/code_rules_docstrings.py +616 -0
- package/hooks/blocking/code_rules_enforcer.py +22 -0
- package/hooks/blocking/code_rules_shared.py +19 -0
- package/hooks/blocking/code_verifier_spawn_preflight_gate.py +420 -0
- package/hooks/blocking/md_to_html_blocker.py +7 -8
- package/hooks/blocking/open_questions_in_plans_blocker.py +5 -6
- package/hooks/blocking/plain_language_blocker.py +51 -16
- package/hooks/blocking/pr_converge_bugteam_enforcer.py +5 -5
- package/hooks/blocking/pre_tool_use_dispatcher.py +545 -0
- package/hooks/blocking/pytest_testpaths_orphan_blocker.py +358 -0
- package/hooks/blocking/state_description_blocker.py +75 -36
- package/hooks/blocking/test_code_rules_enforcer_dead_config_field.py +81 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_inline_literal_claim.py +93 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_step_dispatch.py +262 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_undefined_constant.py +253 -0
- package/hooks/blocking/test_code_rules_enforcer_module_docstring_roster.py +279 -0
- package/hooks/blocking/test_code_verifier_spawn_preflight_gate.py +456 -0
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +816 -0
- package/hooks/blocking/test_pre_tool_use_dispatcher_native.py +341 -0
- package/hooks/blocking/test_pytest_testpaths_orphan_blocker.py +247 -0
- package/hooks/blocking/test_shared_stdin_adoption.py +166 -0
- package/hooks/blocking/verdict_directory_write_blocker.py +12 -7
- package/hooks/hooks.json +9 -79
- package/hooks/hooks_constants/CLAUDE.md +3 -1
- package/hooks/hooks_constants/blocking_check_limits.py +61 -0
- package/hooks/hooks_constants/code_rules_enforcer_constants.py +6 -0
- package/hooks/hooks_constants/code_verifier_spawn_preflight_gate_constants.py +45 -0
- package/hooks/hooks_constants/dead_config_field_constants.py +5 -5
- package/hooks/hooks_constants/mypy_validator_cache_constants.py +36 -0
- package/hooks/hooks_constants/post_tool_use_dispatcher_constants.py +69 -0
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +135 -0
- package/hooks/hooks_constants/pytest_testpaths_orphan_blocker_constants.py +79 -0
- package/hooks/validation/mypy_validator.py +215 -17
- package/hooks/validation/post_tool_use_dispatcher.py +344 -0
- package/hooks/validation/test_mypy_validator.py +184 -1
- package/hooks/validation/test_post_tool_use_dispatcher.py +610 -0
- package/hooks/workflow/test_auto_formatter.py +10 -9
- package/package.json +1 -1
- package/rules/docstring-prose-matches-implementation.md +2 -1
- package/skills/autoconverge/SKILL.md +93 -0
- package/skills/autoconverge/workflow/converge.mjs +27 -2
- package/skills/autoconverge/workflow/converge.path-aware.test.mjs +47 -0
- package/skills/autoconverge/workflow/converge_multi.mjs +161 -0
- package/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +100 -0
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
"""Dead config-dataclass field check: cross-module scan for
|
|
2
|
-
|
|
3
|
-
A config ``@dataclass``
|
|
4
|
-
in one module but constructed and consumed in
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
"""Dead config-dataclass field check: cross-module scan for config-like @dataclass fields.
|
|
2
|
+
|
|
3
|
+
A config-like ``@dataclass`` — any class whose name ends in ``"Config"`` or
|
|
4
|
+
``"Selectors"`` — is defined in one module but constructed and consumed in
|
|
5
|
+
others, so the per-file dead-field check in
|
|
6
|
+
``code_rules_dead_dataclass_field`` cannot judge its fields — it skips any class
|
|
7
|
+
that is not constructed in the same file. This check resolves the enclosing
|
|
8
|
+
package tree — the scan root — and flags a config-like dataclass field whose
|
|
9
|
+
name appears as an attribute read (``obj.field``) in no production module
|
|
10
|
+
anywhere under that root. A selectors dataclass is the same shape as a config
|
|
11
|
+
dataclass: it is bound to a module-level singleton (``binary_selectors =
|
|
12
|
+
BinarySelectors()``) and its fields are read across files, so an unwired
|
|
13
|
+
selector field is caught the same way as a dead config field.
|
|
10
14
|
|
|
11
15
|
The scan is deliberately conservative to keep false positives near zero:
|
|
12
16
|
|
|
13
|
-
- Only ``@dataclass`` classes whose name ends in ``"Config"``
|
|
14
|
-
dataclasses are covered by the per-file check.
|
|
17
|
+
- Only ``@dataclass`` classes whose name ends in ``"Config"`` or ``"Selectors"``
|
|
18
|
+
participate; other dataclasses are covered by the per-file check.
|
|
15
19
|
- Test and migration files are exempt as write destinations, so a field added to
|
|
16
20
|
a config dataclass inside a test is never flagged.
|
|
17
21
|
- Production modules under the scan root are scanned for attribute reads; test
|
|
@@ -24,11 +28,11 @@ The scan is deliberately conservative to keep false positives near zero:
|
|
|
24
28
|
``replace(cfg, debug_port=1)``), and match-pattern keyword attribute names
|
|
25
29
|
(``case Config(field=found)``). Two field-write forms are excluded because they
|
|
26
30
|
name a field without consuming it: a keyword that writes a field in a constructor
|
|
27
|
-
of a known
|
|
31
|
+
of a known config-like dataclass defined under the scan root
|
|
28
32
|
(``ThemeUpdateConfig(debug_port=1)``, excluded per keyword node so a same-named
|
|
29
33
|
keyword on a ``replace`` call stays a read, and a factory function whose name
|
|
30
34
|
merely ends in ``"Config"`` is not excluded), and a self-referential attribute
|
|
31
|
-
read inside a
|
|
35
|
+
read inside a config-like field's own default-value expression in the class body
|
|
32
36
|
whose attribute name equals the field being defined
|
|
33
37
|
(``debug_port: int = source.debug_port``) — a field written only these ways and
|
|
34
38
|
read by no module is the dead-config case this check exists to catch. A default
|
|
@@ -54,7 +58,7 @@ suppress on a dataclass-dunder whole-instance read — instance comparison
|
|
|
54
58
|
(``str(cfg)``/``repr(cfg)``/``format(cfg)``). Those syntactic forms are not bound
|
|
55
59
|
to a config instance, and tree-wide one incidental match anywhere would disable
|
|
56
60
|
the check on any realistic package. The consequence is a documented, rare
|
|
57
|
-
limitation: a
|
|
61
|
+
limitation: a config-like field read ONLY via whole-instance dunder comparison or
|
|
58
62
|
stringification, and never read directly anywhere in production, may be flagged.
|
|
59
63
|
The augmented-assignment read mechanism (``cfg.field += 1`` reads ``field``
|
|
60
64
|
before writing it) is precise and remains a counted read.
|
|
@@ -87,8 +91,8 @@ from code_rules_shared import ( # noqa: E402
|
|
|
87
91
|
)
|
|
88
92
|
|
|
89
93
|
from hooks_constants.dead_config_field_constants import ( # noqa: E402
|
|
94
|
+
ALL_CONFIG_CLASS_NAME_SUFFIXES,
|
|
90
95
|
ALL_REFLECTIVE_FIELD_CONSUMER_NAMES,
|
|
91
|
-
CONFIG_CLASS_NAME_SUFFIX,
|
|
92
96
|
DATACLASSES_MODULE_NAME,
|
|
93
97
|
DEAD_CONFIG_FIELD_GUIDANCE,
|
|
94
98
|
MAX_DEAD_CONFIG_FIELD_ISSUES,
|
|
@@ -99,16 +103,23 @@ from hooks_constants.dead_config_field_constants import ( # noqa: E402
|
|
|
99
103
|
|
|
100
104
|
|
|
101
105
|
def _is_config_dataclass(class_node: ast.ClassDef) -> bool:
|
|
102
|
-
"""Return whether a class is a @dataclass whose name ends in
|
|
106
|
+
"""Return whether a class is a @dataclass whose name ends in a config-like suffix.
|
|
107
|
+
|
|
108
|
+
A config-like surface is a ``@dataclass`` whose name ends in ``"Config"`` or
|
|
109
|
+
``"Selectors"``. Both shapes are defined in one module, bound to a
|
|
110
|
+
module-level singleton, and read across files, so the per-file dead-field
|
|
111
|
+
check cannot judge their fields and the cross-module scan covers them here.
|
|
103
112
|
|
|
104
113
|
Args:
|
|
105
114
|
class_node: The class definition node to test.
|
|
106
115
|
|
|
107
116
|
Returns:
|
|
108
117
|
True when the class carries a ``@dataclass`` decorator and its name ends
|
|
109
|
-
in ``
|
|
118
|
+
in one of ``ALL_CONFIG_CLASS_NAME_SUFFIXES``.
|
|
110
119
|
"""
|
|
111
|
-
return _is_dataclass(class_node) and class_node.name.endswith(
|
|
120
|
+
return _is_dataclass(class_node) and class_node.name.endswith(
|
|
121
|
+
ALL_CONFIG_CLASS_NAME_SUFFIXES
|
|
122
|
+
)
|
|
112
123
|
|
|
113
124
|
|
|
114
125
|
def _reads_whole_instance_reflectively(tree: ast.Module) -> bool:
|
|
@@ -152,18 +163,18 @@ def _reads_whole_instance_reflectively(tree: ast.Module) -> bool:
|
|
|
152
163
|
|
|
153
164
|
|
|
154
165
|
def _config_dataclass_names(tree: ast.Module) -> set[str]:
|
|
155
|
-
"""Return names of
|
|
166
|
+
"""Return names of config-like ``@dataclass`` classes defined in a module.
|
|
156
167
|
|
|
157
168
|
A constructor-keyword exclusion fires only for a callee that names a genuine
|
|
158
|
-
config dataclass, so the caller first gathers the config dataclass
|
|
159
|
-
module defines, then unions those names across the scan root.
|
|
169
|
+
config-like dataclass, so the caller first gathers the config-like dataclass
|
|
170
|
+
names a module defines, then unions those names across the scan root.
|
|
160
171
|
|
|
161
172
|
Args:
|
|
162
173
|
tree: The parsed module to inspect.
|
|
163
174
|
|
|
164
175
|
Returns:
|
|
165
176
|
Every class name in the module that is a ``@dataclass`` whose name ends in
|
|
166
|
-
|
|
177
|
+
one of ``ALL_CONFIG_CLASS_NAME_SUFFIXES``.
|
|
167
178
|
"""
|
|
168
179
|
config_dataclass_names: set[str] = set()
|
|
169
180
|
for each_node in ast.walk(tree):
|
|
@@ -175,22 +186,22 @@ def _config_dataclass_names(tree: ast.Module) -> set[str]:
|
|
|
175
186
|
def _call_constructs_config_class(
|
|
176
187
|
call_node: ast.Call, all_known_config_class_names: set[str]
|
|
177
188
|
) -> bool:
|
|
178
|
-
"""Return whether a call constructs a known
|
|
189
|
+
"""Return whether a call constructs a known config-like dataclass.
|
|
179
190
|
|
|
180
|
-
A call whose callee names a
|
|
191
|
+
A call whose callee names a config-like dataclass defined under the scan root —
|
|
181
192
|
``AppInfoConfig(...)`` or a qualified ``module.AppInfoConfig(...)`` —
|
|
182
|
-
constructs
|
|
193
|
+
constructs the instance, and its keyword arguments write the named fields
|
|
183
194
|
rather than read them. A factory function whose name merely ends in
|
|
184
|
-
``"Config"`` (``getThemeConfig(...)``) is not a known config dataclass, so
|
|
185
|
-
keyword arguments stay genuine reads.
|
|
195
|
+
``"Config"`` (``getThemeConfig(...)``) is not a known config-like dataclass, so
|
|
196
|
+
its keyword arguments stay genuine reads.
|
|
186
197
|
|
|
187
198
|
Args:
|
|
188
199
|
call_node: The call expression to test.
|
|
189
|
-
all_known_config_class_names: Names of
|
|
200
|
+
all_known_config_class_names: Names of config-like dataclasses defined under
|
|
190
201
|
the scan root.
|
|
191
202
|
|
|
192
203
|
Returns:
|
|
193
|
-
True when the callee names a known
|
|
204
|
+
True when the callee names a known config-like dataclass.
|
|
194
205
|
"""
|
|
195
206
|
callee_node = call_node.func
|
|
196
207
|
if isinstance(callee_node, ast.Name):
|
|
@@ -203,7 +214,7 @@ def _call_constructs_config_class(
|
|
|
203
214
|
def _config_constructor_keyword_node_ids(
|
|
204
215
|
tree: ast.Module, all_known_config_class_names: set[str]
|
|
205
216
|
) -> set[int]:
|
|
206
|
-
"""Return ids of keyword nodes that write fields in a known
|
|
217
|
+
"""Return ids of keyword nodes that write fields in a known config-like constructor.
|
|
207
218
|
|
|
208
219
|
A keyword in an ``AppInfoConfig(field=value)`` call sets ``field`` rather than
|
|
209
220
|
reading it, so its node id is collected for the caller to exclude. The
|
|
@@ -213,11 +224,11 @@ def _config_constructor_keyword_node_ids(
|
|
|
213
224
|
|
|
214
225
|
Args:
|
|
215
226
|
tree: The parsed module to inspect.
|
|
216
|
-
all_known_config_class_names: Names of
|
|
227
|
+
all_known_config_class_names: Names of config-like dataclasses defined under
|
|
217
228
|
the scan root.
|
|
218
229
|
|
|
219
230
|
Returns:
|
|
220
|
-
The ``id()`` of every keyword node passed to a known
|
|
231
|
+
The ``id()`` of every keyword node passed to a known config-like
|
|
221
232
|
constructor call.
|
|
222
233
|
"""
|
|
223
234
|
constructor_keyword_node_ids: set[int] = set()
|
|
@@ -237,7 +248,7 @@ def _self_referential_default_attribute_node_ids(
|
|
|
237
248
|
) -> set[int]:
|
|
238
249
|
"""Return ids of attribute reads in a default whose name equals the field.
|
|
239
250
|
|
|
240
|
-
Walks a
|
|
251
|
+
Walks a config-like field's default-value expression and collects the ``id()``
|
|
241
252
|
of each ``ast.Attribute`` read whose ``.attr`` equals ``field_name``. Such a
|
|
242
253
|
read — ``sound_upload_timeout_ms: int = submission_timing.sound_upload_timeout_ms``
|
|
243
254
|
— names the field being defined inside the class body, so it is not a consumer
|
|
@@ -261,15 +272,15 @@ def _self_referential_default_attribute_node_ids(
|
|
|
261
272
|
|
|
262
273
|
|
|
263
274
|
def _config_field_default_value_nodes(tree: ast.Module) -> set[int]:
|
|
264
|
-
"""Return ids of self-referential attribute reads in
|
|
275
|
+
"""Return ids of self-referential attribute reads in config-like field defaults.
|
|
265
276
|
|
|
266
277
|
A field default such as ``sound_upload_timeout_ms: int =``
|
|
267
278
|
``submission_timing.sound_upload_timeout_ms`` is an attribute read whose name
|
|
268
|
-
matches the field being defined. That self-referential read inside the
|
|
269
|
-
class body is not a consumer of the field, so its node id is
|
|
270
|
-
the caller to exclude from the attribute-read set. Only the
|
|
271
|
-
whose ``.attr`` equals the field name is collected; a default
|
|
272
|
-
differently-named field on another object
|
|
279
|
+
matches the field being defined. That self-referential read inside the
|
|
280
|
+
config-like class body is not a consumer of the field, so its node id is
|
|
281
|
+
collected here for the caller to exclude from the attribute-read set. Only the
|
|
282
|
+
attribute read whose ``.attr`` equals the field name is collected; a default
|
|
283
|
+
that sources a differently-named field on another object
|
|
273
284
|
(``timeout_ms: int = other_config.base_timeout``) leaves that read counted, so
|
|
274
285
|
``base_timeout`` stays a live consumer.
|
|
275
286
|
|
|
@@ -278,7 +289,7 @@ def _config_field_default_value_nodes(tree: ast.Module) -> set[int]:
|
|
|
278
289
|
|
|
279
290
|
Returns:
|
|
280
291
|
The ``id()`` of every self-referential attribute read within the
|
|
281
|
-
default-value expression of a field declared in a
|
|
292
|
+
default-value expression of a field declared in a config-like dataclass
|
|
282
293
|
body.
|
|
283
294
|
"""
|
|
284
295
|
default_value_node_ids: set[int] = set()
|
|
@@ -311,14 +322,14 @@ def _attribute_read_names_in_tree(
|
|
|
311
322
|
contributes ``"debug_port"``), and ``ast.MatchClass.kwd_attrs`` names (so
|
|
312
323
|
``case Config(field=x)`` contributes ``"field"``). Two field-write forms are
|
|
313
324
|
excluded because they name a field without consuming it: a keyword that writes
|
|
314
|
-
a field in a known
|
|
325
|
+
a field in a known config-like constructor (``AppInfoConfig(field=value)``,
|
|
315
326
|
excluded per keyword node so a same-named ``replace`` keyword stays a read),
|
|
316
|
-
and a self-referential attribute read inside a
|
|
327
|
+
and a self-referential attribute read inside a config-like dataclass field's
|
|
317
328
|
own default-value expression (``field: int = source.field`` in the class body,
|
|
318
329
|
excluded only when the read name equals the field name) — counting either
|
|
319
330
|
would hide a field that is written but read by no module. A keyword passed to a
|
|
320
|
-
factory function whose name merely ends in ``"Config"`` is not a known
|
|
321
|
-
constructor, so it stays a read. The boolean reports whether the module
|
|
331
|
+
factory function whose name merely ends in ``"Config"`` is not a known
|
|
332
|
+
config-like constructor, so it stays a read. The boolean reports whether the module
|
|
322
333
|
suppresses the dead-field check, which it does only when it reflectively reads a
|
|
323
334
|
whole instance — a bare or ``dataclasses``-qualified
|
|
324
335
|
``asdict``/``astuple``/``fields``/``replace``/``vars`` call, or an
|
|
@@ -328,14 +339,14 @@ def _attribute_read_names_in_tree(
|
|
|
328
339
|
|
|
329
340
|
Args:
|
|
330
341
|
tree: The parsed module to inspect.
|
|
331
|
-
all_known_config_class_names: Names of
|
|
342
|
+
all_known_config_class_names: Names of config-like dataclasses defined under
|
|
332
343
|
the scan root, used to scope the constructor-keyword exclusion to
|
|
333
|
-
genuine config constructors.
|
|
344
|
+
genuine config-like constructors.
|
|
334
345
|
|
|
335
346
|
Returns:
|
|
336
347
|
A (read_names, suppresses_dead_field_check) pair. The name set is every
|
|
337
348
|
attribute name the module reads via the mechanisms above, excluding known
|
|
338
|
-
|
|
349
|
+
config-like constructor keyword nodes and self-referential config-like-field
|
|
339
350
|
default-value attribute reads; suppresses_dead_field_check is True only when
|
|
340
351
|
a reflective whole-instance read is present.
|
|
341
352
|
"""
|
|
@@ -418,8 +429,8 @@ def _all_production_read_names_under_root(
|
|
|
418
429
|
Reads and AST-parses every production ``.py`` module under ``scan_root``
|
|
419
430
|
(excluding test and migration files) at most once: the module sources are
|
|
420
431
|
materialized once, a first pass over the cached sources gathers every
|
|
421
|
-
|
|
422
|
-
exclusion fires only for a genuine config constructor, and a second pass over
|
|
432
|
+
config-like dataclass name defined under the root so the constructor-keyword
|
|
433
|
+
exclusion fires only for a genuine config-like constructor, and a second pass over
|
|
423
434
|
the same cached sources collects attribute reads. The written module's
|
|
424
435
|
post-edit content replaces its on-disk text so the current edit is included.
|
|
425
436
|
Scanning stops at the configured file cap. A module that reflectively reads a
|
|
@@ -472,15 +483,17 @@ def _all_production_read_names_under_root(
|
|
|
472
483
|
def check_dead_config_dataclass_fields(
|
|
473
484
|
content: str, file_path: str, full_file_content: str | None = None
|
|
474
485
|
) -> list[str]:
|
|
475
|
-
"""Flag a
|
|
486
|
+
"""Flag a config-like @dataclass field read by no production module in the package tree.
|
|
476
487
|
|
|
477
488
|
Runs a cross-module scan restricted to ``@dataclass`` classes whose name ends
|
|
478
|
-
in ``"Config"
|
|
489
|
+
in ``"Config"`` or ``"Selectors"`` — both are config-like surfaces bound to a
|
|
490
|
+
module-level singleton and read across files. For each such dataclass in the
|
|
491
|
+
written file, every
|
|
479
492
|
instance field whose name does not appear as an attribute read (``obj.field``),
|
|
480
493
|
augmented-assignment target (``cfg.field += 1``), string literal,
|
|
481
494
|
non-constructor keyword-argument name (``replace`` keyword), or match-pattern
|
|
482
495
|
keyword attribute in any production module under the enclosing scan root is
|
|
483
|
-
flagged as dead. A keyword that writes a field in a
|
|
496
|
+
flagged as dead. A keyword that writes a field in a config-like constructor
|
|
484
497
|
(``ThemeUpdateConfig(debug_port=1)``) is a write, not a read, so it does not
|
|
485
498
|
clear a field — a field set by a constructor keyword and read by no module is
|
|
486
499
|
flagged. When any production module under the scan root reflectively
|
|
@@ -507,10 +520,10 @@ def check_dead_config_dataclass_fields(
|
|
|
507
520
|
Edit, or None for a Write where ``content`` is already the whole file.
|
|
508
521
|
|
|
509
522
|
Returns:
|
|
510
|
-
One violation message per dead config dataclass field, capped at the
|
|
523
|
+
One violation message per dead config-like dataclass field, capped at the
|
|
511
524
|
configured maximum. Returns an empty list when the file is exempt, no
|
|
512
|
-
qualifying config dataclass is found, the scan root exceeds the file
|
|
513
|
-
or a SyntaxError prevents parsing.
|
|
525
|
+
qualifying config-like dataclass is found, the scan root exceeds the file
|
|
526
|
+
cap, or a SyntaxError prevents parsing.
|
|
514
527
|
"""
|
|
515
528
|
if is_test_file(file_path):
|
|
516
529
|
return []
|
|
@@ -547,7 +560,7 @@ def check_dead_config_dataclass_fields(
|
|
|
547
560
|
if each_field_name in all_read_names:
|
|
548
561
|
continue
|
|
549
562
|
all_issues.append(
|
|
550
|
-
f"Line {each_field_line}:
|
|
563
|
+
f"Line {each_field_line}: dataclass field {each_field_name!r}"
|
|
551
564
|
f" on {each_class.name} - {DEAD_CONFIG_FIELD_GUIDANCE}"
|
|
552
565
|
)
|
|
553
566
|
if len(all_issues) >= MAX_DEAD_CONFIG_FIELD_ISSUES:
|