claude-dev-env 2.29.0 → 2.30.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/pr-description-writer.md +21 -6
- package/.agents/agents/reference/README.md +6 -0
- package/.agents/agents/reference/pr-description-illustrative-voice.md +45 -0
- package/.agents/agents/reference/pr-description-verification.md +19 -0
- package/.agents/agents/test_pr_description_writer_style.py +47 -0
- package/_shared/pr-loop/code-rules-gate.md +1 -1
- package/_shared/pr-loop/scripts/code_rules_gate.py +2 -2
- package/_shared/pr-loop/scripts/code_rules_gate_parts/enforcer_loading.py +8 -8
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_enforcer_loading.py +2 -2
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_gate_running.py +1 -1
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +15 -15
- package/docs/CODE_RULES.md +10 -0
- package/hooks/blocking/code_rules_enforcer.py +433 -313
- package/hooks/blocking/test_code_rules_enforcer_banned_noun_word.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_class_docstring_methods.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_dispatch_wiring.py +117 -22
- package/hooks/blocking/test_code_rules_enforcer_docstring_args_signature.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_docstring_prose_wall_illustration.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_docstring_runon_sentence.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_docstring_unreferenced_param.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_fstring_scan.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_ignored_must_check_return.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_inline_tuple_string_magic.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_join_separator_magic.py +2 -2
- package/hooks/blocking/test_code_rules_enforcer_js_returns_object.py +3 -3
- package/hooks/blocking/test_code_rules_enforcer_module_docstring_roster.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_naming_pattern.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_narrow_edit.py +117 -38
- package/hooks/blocking/test_code_rules_enforcer_output_snapshot.py +121 -0
- package/hooks/blocking/test_code_rules_enforcer_precheck_forecast.py +17 -15
- package/hooks/blocking/test_code_rules_enforcer_split_entry_1.py +4 -2
- package/hooks/blocking/test_code_rules_enforcer_split_entry_2.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_stage.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_string_magic.py +4 -4
- package/hooks/blocking/test_code_rules_enforcer_thin_wrapper_files.py +2 -2
- package/hooks/blocking/test_code_rules_enforcer_zero_payload_alias.py +2 -2
- package/hooks/blocking/test_code_rules_enforcer_zero_payload_alias_hook_routing.py +2 -2
- package/hooks/blocking/test_code_rules_validation_phase.py +281 -0
- package/hooks/blocking/test_fable_spawn_gate.py +1 -1
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +26 -11
- package/hooks/hooks_constants/code_rules_enforcer_constants.py +8 -0
- package/hooks/hooks_constants/test_code_rules_enforcer_constants.py +21 -0
- package/hooks/hooks_constants/validation_phase_constants.py +34 -0
- package/hooks/validators/README.md +1 -1
- package/hooks/validators/config/hook_timing_harness_constants.py +16 -0
- package/hooks/validators/hook_timing_harness.py +287 -0
- package/hooks/validators/test_hook_timing_harness.py +251 -0
- package/package.json +1 -1
|
@@ -1,19 +1,34 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pr-description-writer
|
|
3
|
-
description: "Optional agent that
|
|
3
|
+
description: "Optional agent that writes factual pull request titles, descriptions, and comments in plain illustrative language from the current diff."
|
|
4
4
|
tools: Read,Grep,Glob,Bash
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# PR Description Writer
|
|
8
8
|
|
|
9
|
-
Write
|
|
9
|
+
Write from the current pull request diff and task. Resolve the active managed root and active agents home before reading guides: `~/.claude` is the default root, `CLAUDE_CONFIG_DIR` selects another root, and `--target DIR` takes precedence; the default `.claude` root uses sibling `~/.agents`, while another root uses sibling `<root-name>.agents`. Use `<agents-home>/skills/pr-title-description/SKILL.md` (source fallback: `packages/claude-dev-env/.agents/skills/pr-title-description/SKILL.md`) for required content and shape. Use the current review findings and task guidance for a review comment. Do not assume `~/.claude` or `~/.agents` for a named profile or explicit target.
|
|
10
|
+
|
|
11
|
+
## Voice
|
|
12
|
+
|
|
13
|
+
Explain the change so a smart reader who knows nothing about the code can picture it on the first read.
|
|
14
|
+
|
|
15
|
+
- Start with a concrete scene or action.
|
|
16
|
+
- Prefer a tiny Before / After story when the change is about how something is found, asked for, checked, saved, retried, or stopped.
|
|
17
|
+
- Use small words and picture words.
|
|
18
|
+
- Replace jargon with the action it represents. When one technical term matters, explain it immediately in plain words.
|
|
19
|
+
- Keep the main behavior in `What this adds`, the reason in `Why`, and proof in `Verification`.
|
|
20
|
+
- Keep titles concrete. Say what someone gives it, what happens, or what comes back.
|
|
21
|
+
|
|
22
|
+
For a concrete voice sample only when needed, read `reference/pr-description-illustrative-voice.md` beside this agent. For a concrete verification sample only when needed, read `reference/pr-description-verification.md`. These samples guide the shape and voice; they are not content to copy into unrelated pull requests.
|
|
10
23
|
|
|
11
24
|
## Draft the body
|
|
12
25
|
|
|
13
26
|
1. Inspect the cumulative diff, the current pull request body, and the validation results.
|
|
14
|
-
2.
|
|
15
|
-
3.
|
|
16
|
-
4.
|
|
27
|
+
2. Find the main behavior a user, caller, operator, or reviewer can see.
|
|
28
|
+
3. Write that behavior first in plain illustrative language.
|
|
29
|
+
4. Preserve accurate issue links and curated sections from the current body when they remain useful.
|
|
30
|
+
5. Use headings when they help a reviewer scan the change.
|
|
31
|
+
6. In `Verification`, lead with what a person can open, see, click, compare, or try for themselves. Put test results in one short supporting line when useful.
|
|
17
32
|
|
|
18
33
|
## Publish through GitHub CLI
|
|
19
34
|
|
|
@@ -21,4 +36,4 @@ Place markdown in a BOM-free temporary file and pass its path with `--body-file`
|
|
|
21
36
|
|
|
22
37
|
## Check the draft
|
|
23
38
|
|
|
24
|
-
Confirm every statement matches the diff and current pull request state. Return the final markdown and the file path ready for publication.
|
|
39
|
+
Confirm every statement matches the diff and current pull request state. Confirm the title and body stay clear without requiring the reader to decode repository jargon. Confirm `Verification` gives the reviewer a visible check they can perform themselves whenever the change has one. Return the final markdown and the file path ready for publication.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# PR writer references
|
|
2
|
+
|
|
3
|
+
These files are optional examples for `pr-description-writer.md`.
|
|
4
|
+
|
|
5
|
+
- `pr-description-illustrative-voice.md` — plain, picture-first title and description examples.
|
|
6
|
+
- `pr-description-verification.md` — human-checkable verification examples.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# PR description illustrative voice
|
|
2
|
+
|
|
3
|
+
Use this only when the PR writer needs a concrete voice sample.
|
|
4
|
+
|
|
5
|
+
## Core pattern
|
|
6
|
+
|
|
7
|
+
Start with something a person can picture.
|
|
8
|
+
|
|
9
|
+
Before: show the old action in a tiny scene.
|
|
10
|
+
|
|
11
|
+
After: show the new action in a tiny scene.
|
|
12
|
+
|
|
13
|
+
Keep machine words out when a plain action works. If one technical term matters, explain it in the same breath with a simple picture.
|
|
14
|
+
|
|
15
|
+
## Sample 1 — call the right helper directly
|
|
16
|
+
|
|
17
|
+
The theme package has a phone icon somewhere. We need to find it.
|
|
18
|
+
|
|
19
|
+
Before: yell, “anyone know how to find icons?” and wait for a hand.
|
|
20
|
+
|
|
21
|
+
After: call the finder by its name tag: “icon finder, go.”
|
|
22
|
+
|
|
23
|
+
That is all the operation name is here — the finder’s name tag.
|
|
24
|
+
|
|
25
|
+
## Sample 2 — ask for a deeper review
|
|
26
|
+
|
|
27
|
+
You start the review loop. It looks at the code, then looks again, until it has nothing new to say.
|
|
28
|
+
|
|
29
|
+
Before: each look could be a quick glance.
|
|
30
|
+
|
|
31
|
+
After: each look uses the deepest review setting the model has.
|
|
32
|
+
|
|
33
|
+
The person running the loop asked for a full look, so every pass gets a full look.
|
|
34
|
+
|
|
35
|
+
## Writing rules
|
|
36
|
+
|
|
37
|
+
- Few words. Small words. Clear on first read.
|
|
38
|
+
- Lead with the thing someone can see or do.
|
|
39
|
+
- Prefer Before / After when the change is about how something is asked, found, checked, saved, retried, or stopped.
|
|
40
|
+
- Use picture words: hand it, open it, find it, check it, save it, skip it, stop, call by name.
|
|
41
|
+
- Prefer spoken results: “found it,” “missing,” “duplicated,” “couldn’t.”
|
|
42
|
+
- Keep the main behavior in `What this adds`.
|
|
43
|
+
- Keep `Why` short and concrete.
|
|
44
|
+
- Put proof only in `Verification`.
|
|
45
|
+
- Explain jargon immediately or replace it.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Human-checkable PR verification
|
|
2
|
+
|
|
3
|
+
Use this only when the PR writer needs a concrete verification sample.
|
|
4
|
+
|
|
5
|
+
Verification should lead with something a reviewer can open, see, click, compare, or try.
|
|
6
|
+
|
|
7
|
+
Good:
|
|
8
|
+
|
|
9
|
+
Open the generated PR description. The first paragraph should tell the story in plain words before it names any code detail. If the change has a visible result, point to the exact file, screen, output, or behavior the reviewer can inspect.
|
|
10
|
+
|
|
11
|
+
Tests: focused checks pass.
|
|
12
|
+
|
|
13
|
+
Weak:
|
|
14
|
+
|
|
15
|
+
- Contract test passed.
|
|
16
|
+
- Static analysis passed.
|
|
17
|
+
- Internal schema assertion passed.
|
|
18
|
+
|
|
19
|
+
Machine checks can support the claim. They should not be the whole proof when a person can inspect the result directly.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Contract tests for the PR description writer voice."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
AGENT_DIRECTORY = Path(__file__).parent
|
|
7
|
+
AGENT_PATH = AGENT_DIRECTORY / "pr-description-writer.md"
|
|
8
|
+
VOICE_REFERENCE_PATH = AGENT_DIRECTORY / "reference" / "pr-description-illustrative-voice.md"
|
|
9
|
+
VERIFICATION_REFERENCE_PATH = AGENT_DIRECTORY / "reference" / "pr-description-verification.md"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_pr_description_writer_uses_repo_owned_progressive_samples() -> None:
|
|
13
|
+
agent_text = AGENT_PATH.read_text(encoding="utf-8")
|
|
14
|
+
|
|
15
|
+
assert "reference/pr-description-illustrative-voice.md" in agent_text
|
|
16
|
+
assert "reference/pr-description-verification.md" in agent_text
|
|
17
|
+
assert "only when needed" in agent_text
|
|
18
|
+
assert "PR #2562" not in agent_text
|
|
19
|
+
assert "PR #1150" not in agent_text
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_voice_reference_keeps_the_plain_illustrative_shape() -> None:
|
|
23
|
+
reference_text = VOICE_REFERENCE_PATH.read_text(encoding="utf-8")
|
|
24
|
+
|
|
25
|
+
for required_text in (
|
|
26
|
+
"Before:",
|
|
27
|
+
"After:",
|
|
28
|
+
"finder’s name tag",
|
|
29
|
+
"found it",
|
|
30
|
+
"missing",
|
|
31
|
+
"duplicated",
|
|
32
|
+
"What this adds",
|
|
33
|
+
"Why",
|
|
34
|
+
"Verification",
|
|
35
|
+
):
|
|
36
|
+
assert required_text in reference_text
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_verification_leads_with_a_human_visible_check() -> None:
|
|
40
|
+
agent_text = AGENT_PATH.read_text(encoding="utf-8")
|
|
41
|
+
reference_text = VERIFICATION_REFERENCE_PATH.read_text(encoding="utf-8")
|
|
42
|
+
|
|
43
|
+
for visible_action in ("open", "see", "click", "compare", "try"):
|
|
44
|
+
assert visible_action in agent_text
|
|
45
|
+
assert visible_action in reference_text
|
|
46
|
+
assert "Tests: focused checks pass." in reference_text
|
|
47
|
+
assert "Machine checks can support the claim." in reference_text
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# CODE_RULES gate
|
|
2
2
|
|
|
3
|
-
Pre-audit validator run before each AUDIT (and pre-commit when applicable). Wraps `
|
|
3
|
+
Pre-audit validator run before each AUDIT (and pre-commit when applicable). Wraps `validate_content_for_full_gate` from `~/.claude/hooks/blocking/code_rules_enforcer.py`.
|
|
4
4
|
|
|
5
5
|
## Script location
|
|
6
6
|
|
|
@@ -63,7 +63,7 @@ except ImportError as import_error:
|
|
|
63
63
|
subprocess = git_file_sets.subprocess
|
|
64
64
|
|
|
65
65
|
resolve_claude_dev_env_root = enforcer_loading.resolve_claude_dev_env_root
|
|
66
|
-
|
|
66
|
+
load_validate_content_for_full_gate = enforcer_loading.load_validate_content_for_full_gate
|
|
67
67
|
ValidateContentCallable = enforcer_loading.ValidateContentCallable
|
|
68
68
|
|
|
69
69
|
resolve_merge_base = git_file_sets.resolve_merge_base
|
|
@@ -357,7 +357,7 @@ def main(all_arguments: list[str]) -> int:
|
|
|
357
357
|
repository_root = (
|
|
358
358
|
arguments.repo_root.resolve() if arguments.repo_root is not None else Path.cwd().resolve()
|
|
359
359
|
)
|
|
360
|
-
validate_content =
|
|
360
|
+
validate_content = load_validate_content_for_full_gate()
|
|
361
361
|
if arguments.paths:
|
|
362
362
|
return _run_explicit_paths_mode(validate_content, arguments, repository_root)
|
|
363
363
|
if arguments.immediate:
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
"""Load the code-rules enforcer's ``
|
|
1
|
+
"""Load the code-rules enforcer's ``validate_content_for_full_gate`` for in-process use.
|
|
2
2
|
|
|
3
|
-
The gate runs the
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
The gate runs the enforcer's full-gate validation phase, so it locates the
|
|
4
|
+
``hooks/blocking/code_rules_enforcer.py`` module from disk, executes it with
|
|
5
|
+
the hooks directory on ``sys.path``, and hands back its callable.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import importlib.machinery
|
|
@@ -138,11 +138,11 @@ def _exec_enforcer_with_hooks_on_path(
|
|
|
138
138
|
_restore_hooks_constants_modules(all_saved_modules, hooks_root_path)
|
|
139
139
|
|
|
140
140
|
|
|
141
|
-
def
|
|
142
|
-
"""Load ``code_rules_enforcer.
|
|
141
|
+
def load_validate_content_for_full_gate() -> ValidateContentCallable:
|
|
142
|
+
"""Load ``code_rules_enforcer.validate_content_for_full_gate`` for in-process use.
|
|
143
143
|
|
|
144
144
|
Returns:
|
|
145
|
-
The ``
|
|
145
|
+
The ``validate_content_for_full_gate`` callable from the enforcer module.
|
|
146
146
|
|
|
147
147
|
Raises:
|
|
148
148
|
SystemExit: When the package root or the enforcer module cannot load.
|
|
@@ -153,7 +153,7 @@ def load_validate_content() -> ValidateContentCallable:
|
|
|
153
153
|
)
|
|
154
154
|
module = importlib.util.module_from_spec(specification)
|
|
155
155
|
_exec_enforcer_with_hooks_on_path(specification, module)
|
|
156
|
-
return module.
|
|
156
|
+
return module.validate_content_for_full_gate
|
|
157
157
|
|
|
158
158
|
|
|
159
159
|
def _restore_hooks_constants_modules(
|
|
@@ -10,8 +10,8 @@ def test_resolve_claude_dev_env_root_finds_enforcer_bearing_root() -> None:
|
|
|
10
10
|
assert (resolved_root / "hooks" / "blocking" / "code_rules_enforcer.py").is_file()
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
def
|
|
14
|
-
validate_content = enforcer_loading.
|
|
13
|
+
def test_load_validate_content_for_full_gate_returns_callable_passing_a_clean_module() -> None:
|
|
14
|
+
validate_content = enforcer_loading.load_validate_content_for_full_gate()
|
|
15
15
|
clean_module = '"""Clean module."""\n\n\ndef ping() -> str:\n return "pong"\n'
|
|
16
16
|
issues = validate_content(clean_module, "sample.py", "")
|
|
17
17
|
assert issues == []
|
|
@@ -130,7 +130,7 @@ def test_gate_caller_scans_staged_content_with_commit_stage_scope(
|
|
|
130
130
|
|
|
131
131
|
resolved_file_path = file_path.resolve()
|
|
132
132
|
exit_code = gate_running.run_gate(
|
|
133
|
-
validate_content=enforcer_loading.
|
|
133
|
+
validate_content=enforcer_loading.load_validate_content_for_full_gate(),
|
|
134
134
|
all_file_paths=[resolved_file_path],
|
|
135
135
|
repository_root=repository_root,
|
|
136
136
|
all_added_lines_by_path={resolved_file_path: {2, 3}},
|
|
@@ -735,7 +735,7 @@ def test_run_gate_flags_copied_sibling_when_cwd_is_outside_repo_root(
|
|
|
735
735
|
copied_file = package_directory / "copied_helper.py"
|
|
736
736
|
existing_file.write_text(_DUPLICATE_HELPER_SOURCE, encoding="utf-8")
|
|
737
737
|
copied_file.write_text(_DUPLICATE_HELPER_SOURCE, encoding="utf-8")
|
|
738
|
-
validate_content = gate_module.
|
|
738
|
+
validate_content = gate_module.load_validate_content_for_full_gate()
|
|
739
739
|
|
|
740
740
|
monkeypatch.chdir(temporary_git_repository.parent)
|
|
741
741
|
blocking_by_file, _advisory_by_file, _skipped = (
|
|
@@ -813,7 +813,7 @@ def test_duplicate_body_span_range_covers_the_definition_through_last_body_line(
|
|
|
813
813
|
assert span == range(definition_line, last_body_line + 1)
|
|
814
814
|
|
|
815
815
|
|
|
816
|
-
def
|
|
816
|
+
def test_split_violations_by_scope_blocks_duplicate_body_when_span_intersects_added_lines(
|
|
817
817
|
tmp_path: Path,
|
|
818
818
|
) -> None:
|
|
819
819
|
"""A duplicate-body issue whose copied-function span overlaps the diff's added
|
|
@@ -829,7 +829,7 @@ def test_split_violations_blocks_duplicate_body_when_span_intersects_added_lines
|
|
|
829
829
|
assert advisory == []
|
|
830
830
|
|
|
831
831
|
|
|
832
|
-
def
|
|
832
|
+
def test_split_violations_by_scope_advises_duplicate_body_when_span_misses_added_lines(
|
|
833
833
|
tmp_path: Path,
|
|
834
834
|
) -> None:
|
|
835
835
|
"""A duplicate-body issue for an untouched pre-existing copy — whose span does
|
|
@@ -1002,7 +1002,7 @@ def test_collect_partitioned_violations_advises_pre_existing_sibling_duplicate(
|
|
|
1002
1002
|
_DUPLICATE_HELPER_SOURCE + "unrelated_constant = 1\n", encoding="utf-8"
|
|
1003
1003
|
)
|
|
1004
1004
|
unrelated_added_line = _DUPLICATE_HELPER_SOURCE.count("\n") + 1
|
|
1005
|
-
validate_content = gate_module.
|
|
1005
|
+
validate_content = gate_module.load_validate_content_for_full_gate()
|
|
1006
1006
|
|
|
1007
1007
|
resolved_copied = copied_file.resolve()
|
|
1008
1008
|
blocking_by_file, advisory_by_file, _skipped = (
|
|
@@ -1055,7 +1055,7 @@ def test_collect_partitioned_violations_blocks_sibling_duplicate_in_added_region
|
|
|
1055
1055
|
definition_line = 3
|
|
1056
1056
|
last_body_line = 15
|
|
1057
1057
|
all_copied_function_lines = set(range(definition_line, last_body_line + 1))
|
|
1058
|
-
validate_content = gate_module.
|
|
1058
|
+
validate_content = gate_module.load_validate_content_for_full_gate()
|
|
1059
1059
|
|
|
1060
1060
|
resolved_copied = copied_file.resolve()
|
|
1061
1061
|
blocking_by_file, _advisory_by_file, _skipped = (
|
|
@@ -1549,10 +1549,10 @@ def test_main_blocks_when_function_body_grows_past_threshold_with_def_line_untou
|
|
|
1549
1549
|
assert exit_code == 1
|
|
1550
1550
|
|
|
1551
1551
|
|
|
1552
|
-
def
|
|
1552
|
+
def test_split_violations_by_scope_blocks_function_length_when_span_intersects_added_lines() -> None:
|
|
1553
1553
|
"""A function-length issue whose declared span overlaps the diff's added
|
|
1554
1554
|
lines is blocking — the body grew, which is exactly Finding B's intent."""
|
|
1555
|
-
validate_content = gate_module.
|
|
1555
|
+
validate_content = gate_module.load_validate_content_for_full_gate()
|
|
1556
1556
|
long_function = _build_function_module(
|
|
1557
1557
|
"oversized", body_line_count=70, leading_lines=3
|
|
1558
1558
|
)
|
|
@@ -1571,12 +1571,12 @@ def test_split_violations_blocks_function_length_when_span_intersects_added_line
|
|
|
1571
1571
|
assert advisory == []
|
|
1572
1572
|
|
|
1573
1573
|
|
|
1574
|
-
def
|
|
1574
|
+
def test_split_violations_by_scope_advises_function_length_when_span_misses_added_lines() -> None:
|
|
1575
1575
|
"""A function-length issue for an untouched pre-existing function — whose
|
|
1576
1576
|
declared span does not overlap any added line — is advisory, not blocking.
|
|
1577
1577
|
This prevents the over-block regression where every pre-existing >=60-line
|
|
1578
1578
|
function in a touched file was forced into the blocking payload."""
|
|
1579
|
-
validate_content = gate_module.
|
|
1579
|
+
validate_content = gate_module.load_validate_content_for_full_gate()
|
|
1580
1580
|
long_function = _build_function_module(
|
|
1581
1581
|
"oversized", body_line_count=70, leading_lines=3
|
|
1582
1582
|
)
|
|
@@ -1595,7 +1595,7 @@ def test_split_violations_advises_function_length_when_span_misses_added_lines()
|
|
|
1595
1595
|
|
|
1596
1596
|
|
|
1597
1597
|
def _isolation_issues_for_home_probe_test() -> list[str]:
|
|
1598
|
-
validate_content = gate_module.
|
|
1598
|
+
validate_content = gate_module.load_validate_content_for_full_gate()
|
|
1599
1599
|
header = "from pathlib import Path\n"
|
|
1600
1600
|
test_body = (
|
|
1601
1601
|
"def test_reads_home() -> None:\n"
|
|
@@ -1606,7 +1606,7 @@ def _isolation_issues_for_home_probe_test() -> list[str]:
|
|
|
1606
1606
|
return [each_issue for each_issue in issues if "probes" in each_issue]
|
|
1607
1607
|
|
|
1608
1608
|
|
|
1609
|
-
def
|
|
1609
|
+
def test_split_violations_by_scope_blocks_isolation_when_function_span_intersects_added_lines() -> None:
|
|
1610
1610
|
"""An isolation issue whose enclosing test-function span overlaps the diff's
|
|
1611
1611
|
added lines is blocking — a signature-line change that un-isolates an
|
|
1612
1612
|
unchanged-body probe must block, matching the enforcer's terminal path."""
|
|
@@ -1621,7 +1621,7 @@ def test_split_violations_blocks_isolation_when_function_span_intersects_added_l
|
|
|
1621
1621
|
assert advisory == []
|
|
1622
1622
|
|
|
1623
1623
|
|
|
1624
|
-
def
|
|
1624
|
+
def test_split_violations_by_scope_advises_isolation_when_function_span_misses_added_lines() -> None:
|
|
1625
1625
|
"""An isolation issue for an untouched pre-existing probe — whose enclosing
|
|
1626
1626
|
test-function span does not overlap any added line — is advisory, not
|
|
1627
1627
|
blocking, mirroring the function-length scope contract."""
|
|
@@ -1814,7 +1814,7 @@ def test_check_wrapper_plumb_through_stays_under_function_length_threshold() ->
|
|
|
1814
1814
|
|
|
1815
1815
|
|
|
1816
1816
|
def _banned_noun_parameter_issues() -> list[str]:
|
|
1817
|
-
validate_content = gate_module.
|
|
1817
|
+
validate_content = gate_module.load_validate_content_for_full_gate()
|
|
1818
1818
|
source = (
|
|
1819
1819
|
"def aggregate(canned_results: int) -> int:\n"
|
|
1820
1820
|
" doubled = canned_results * 2\n"
|
|
@@ -1824,7 +1824,7 @@ def _banned_noun_parameter_issues() -> list[str]:
|
|
|
1824
1824
|
return [each_issue for each_issue in issues if "banned noun" in each_issue]
|
|
1825
1825
|
|
|
1826
1826
|
|
|
1827
|
-
def
|
|
1827
|
+
def test_split_violations_by_scope_blocks_banned_noun_when_binding_line_is_added() -> None:
|
|
1828
1828
|
"""A banned-noun binding is blocking when its own binding line is among the
|
|
1829
1829
|
added lines. The gate reconstructs the one-line binding span through the
|
|
1830
1830
|
same shared extractor registry it uses for function-length and isolation,
|
|
@@ -1840,7 +1840,7 @@ def test_split_violations_blocks_banned_noun_when_binding_line_is_added() -> Non
|
|
|
1840
1840
|
assert advisory == []
|
|
1841
1841
|
|
|
1842
1842
|
|
|
1843
|
-
def
|
|
1843
|
+
def test_split_violations_by_scope_advises_banned_noun_when_binding_line_untouched() -> None:
|
|
1844
1844
|
"""A banned-noun binding whose own line is not among the added lines is
|
|
1845
1845
|
advisory — editing an unrelated body line does not pull a pre-existing
|
|
1846
1846
|
binding into scope, mirroring the companion exact-match identifier check."""
|
package/docs/CODE_RULES.md
CHANGED
|
@@ -109,3 +109,13 @@ If you already have the data, don't fetch it again.
|
|
|
109
109
|
## 11. ENFORCEMENT SURFACES
|
|
110
110
|
|
|
111
111
|
⚡ **Hooks** block pattern-matchable violations at Write/Edit time. 🤖 **Prompt context** carries judgment principles (SRP, Right-Sized Engineering, research-first action on ambiguous intent, BDD discovery, docstring-prose-matches-implementation). 👥 **Audit rubrics** (`/check`, `packages/claude-dev-env/audit-rubrics/` categories A–Q) cover cross-file architectural concerns. Rules with documented-but-pending hook coverage live in `~/.claude/rules/*.md`; each names its own promotion path. The docstring-prose standard (free-form enumerations match the body) lives in `packages/claude-dev-env/rules/docstring-prose-matches-implementation.md`, enforced via Category O6 audit. The diagram-first docstring standard (a summary line, then a `::` example or doctest, then a couple of short prose lines) lives in `packages/claude-dev-env/rules/plain-illustrative-docstrings.md`, enforced by the `check_docstring_runon_sentence` and `check_docstring_prose_wall_without_illustration` backstop hooks and Category O9 audit.
|
|
112
|
+
|
|
113
|
+
## 11.5 VALIDATION-PHASE PRECEDENCE
|
|
114
|
+
|
|
115
|
+
`code_rules_enforcer.py` decides what a run checks and reports along three independent axes. Each axis filters a narrower scope than the one before it; none widens what the axis before it already decided.
|
|
116
|
+
|
|
117
|
+
1. **Phase selects the roster.** `EDIT_LANE_PHASE` or `FULL_GATE_PHASE` decides which checks exist in the lane at all. `validate_content_for_phase` takes `phase` keyword-only with no default, so every caller names its lane explicitly.
|
|
118
|
+
2. **Target classification filters within a lane.** The hook-infrastructure patterns and the ephemeral-path check decide whether a target is validated, and with which subset. Classification narrows a lane; it never adds a check the phase already excluded.
|
|
119
|
+
3. **Changed-line scope filters only the report.** `defer_scope_to_caller` and the changed-line set decide which found violations block. Scope filters findings after every check in the roster already ran; it adds or removes no check.
|
|
120
|
+
|
|
121
|
+
`hooks/hooks_constants/validation_phase_constants.py` is the single source for all three axes: the phase names (`EDIT_LANE_PHASE`, `FULL_GATE_PHASE`), the full-gate-only roster (`ALL_FULL_GATE_ONLY_CHECK_NAMES`), and the hook-infrastructure edit-lane roster (`ALL_HOOK_INFRASTRUCTURE_EDIT_LANE_CHECK_NAMES`).
|