claude-dev-env 1.92.1 → 1.93.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/CLAUDE.md +6 -2
- package/_shared/CLAUDE.md +2 -1
- package/_shared/advisor/CLAUDE.md +21 -0
- package/_shared/advisor/advisor-protocol.md +142 -0
- package/_shared/advisor/scripts/config/advisor_scripts_constants/__init__.py +1 -0
- package/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py +86 -0
- package/_shared/advisor/scripts/model_tier_run_validator.py +246 -0
- package/_shared/advisor/scripts/pyproject.toml +3 -0
- package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +302 -0
- package/_shared/advisor/scripts/tests/test_tier_model_ids.py +180 -0
- package/_shared/advisor/scripts/tier_model_ids.py +164 -0
- package/_shared/pr-loop/CLAUDE.md +1 -0
- package/_shared/pr-loop/precatch-rubric.md +65 -0
- package/_shared/pr-loop/scripts/CLAUDE.md +1 -1
- package/_shared/pr-loop/scripts/code_rules_gate.py +137 -5
- package/_shared/pr-loop/scripts/post_audit_thread.py +1 -1
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +1 -1
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/code_rules_gate_constants.py +16 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/reviews_disabled_constants.py +2 -1
- package/_shared/pr-loop/scripts/reviewer_availability.py +42 -12
- package/_shared/pr-loop/scripts/reviews_disabled.py +65 -21
- package/_shared/pr-loop/scripts/terminology_sweep.py +69 -21
- package/_shared/pr-loop/scripts/tests/CLAUDE.md +1 -1
- package/_shared/pr-loop/scripts/tests/fixtures/{copilot_internal_user_jonecho.json → copilot_internal_user_example.json} +1 -1
- package/_shared/pr-loop/scripts/tests/test_agent_config_carveout.py +1 -1
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +146 -2
- package/_shared/pr-loop/scripts/tests/test_copilot_quota.py +8 -8
- package/_shared/pr-loop/scripts/tests/test_reviewer_availability.py +18 -5
- package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +56 -5
- package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +104 -17
- package/agents/CLAUDE.md +3 -2
- package/agents/clasp-deployment-orchestrator.md +2 -2
- package/agents/code-advisor.md +7 -5
- package/agents/code-verifier.md +6 -2
- package/agents/session-advisor.md +27 -0
- package/audit-rubrics/category_rubrics/category-j-code-rules-compliance.md +1 -1
- package/audit-rubrics/category_rubrics/category-k-codebase-conflicts.md +1 -1
- package/audit-rubrics/prompts/category-a-api-contracts.md +2 -2
- package/audit-rubrics/prompts/category-b-selector-engine-compat.md +2 -2
- package/audit-rubrics/prompts/category-c-resource-cleanup.md +2 -2
- package/audit-rubrics/prompts/category-d-scoping-and-ordering.md +2 -2
- package/audit-rubrics/prompts/category-e-dead-code.md +2 -2
- package/audit-rubrics/prompts/category-f-silent-failures.md +2 -2
- package/audit-rubrics/prompts/category-g-bounds-and-overflow.md +2 -2
- package/audit-rubrics/prompts/category-h-security-boundaries.md +2 -2
- package/audit-rubrics/prompts/category-i-concurrency.md +2 -2
- package/audit-rubrics/prompts/category-j-code-rules-compliance.md +2 -2
- package/audit-rubrics/prompts/category-k-codebase-conflicts.md +2 -2
- package/audit-rubrics/prompts/category-l-behavior-equivalence.md +2 -2
- package/audit-rubrics/prompts/category-m-producer-consumer-cardinality.md +2 -2
- package/audit-rubrics/prompts/category-n-test-name-scenario-verifier.md +2 -2
- package/audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md +2 -2
- package/audit-rubrics/prompts/category-p-name-vs-behavior-contract.md +2 -2
- package/bin/CLAUDE.md +1 -0
- package/bin/expand_home_directory_tokens.mjs +75 -0
- package/bin/install.mjs +21 -7
- package/bin/install.test.mjs +220 -10
- package/hooks/CLAUDE.md +1 -1
- package/hooks/blocking/CLAUDE.md +4 -1
- package/hooks/blocking/_path_setup.py +13 -0
- package/hooks/blocking/bash_pre_tool_use_dispatcher.py +281 -0
- package/hooks/blocking/code_rules_dead_module_constant.py +88 -16
- package/hooks/blocking/code_rules_enforcer.py +6 -0
- package/hooks/blocking/code_rules_imports_logging.py +34 -1
- package/hooks/blocking/code_rules_paths_syspath.py +1 -1
- package/hooks/blocking/code_rules_test_layout.py +272 -0
- package/hooks/blocking/nas_ssh_binary_enforcer.py +22 -12
- package/hooks/blocking/pii_prevention_blocker.py +781 -0
- package/hooks/blocking/pii_scanner.py +340 -0
- package/hooks/blocking/pre_tool_use_dispatcher.py +3 -3
- package/hooks/blocking/stop_dispatcher.py +84 -0
- package/hooks/blocking/test__path_setup.py +39 -0
- package/hooks/blocking/test_bash_dispatcher_interpreter_starts.py +61 -0
- package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +294 -0
- package/hooks/blocking/test_code_rules_enforcer_cross_skill_duplicate.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_dead_module_constant.py +51 -0
- package/hooks/blocking/test_code_rules_enforcer_dead_module_constant_read_cap.py +103 -0
- package/hooks/blocking/test_code_rules_enforcer_dot_test_pattern.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_file_global_constants.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_hardcoded_user_path.py +11 -11
- package/hooks/blocking/test_code_rules_enforcer_naive_datetime.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_same_file_inline_duplicate.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_test_layout.py +111 -0
- package/hooks/blocking/test_code_rules_enforcer_type_checking_scope.py +76 -0
- package/hooks/blocking/test_destructive_command_blocker.py +29 -9
- package/hooks/blocking/test_gh_body_arg_blocker.py +3 -3
- package/hooks/blocking/test_nas_ssh_binary_enforcer.py +96 -39
- package/hooks/blocking/test_pii_prevention_blocker.py +905 -0
- package/hooks/blocking/test_pii_prevention_windows_git_path.py +41 -0
- package/hooks/blocking/test_pii_scanner.py +165 -0
- package/hooks/blocking/test_pr_description_enforcer_pr_number.py +1 -1
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +14 -13
- package/hooks/blocking/test_stop_dispatcher.py +187 -0
- package/hooks/blocking/test_verdict_directory_write_blocker.py +1 -1
- package/hooks/blocking/test_volatile_path_in_post_blocker.py +3 -3
- package/hooks/blocking/volatile_path_in_post_blocker.py +54 -5
- package/hooks/diagnostic/migrations/README.md +25 -26
- package/hooks/diagnostic/test_hook_log_extractor.py +10 -10
- package/hooks/hooks.json +10 -120
- package/hooks/hooks_constants/CLAUDE.md +12 -3
- package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +78 -0
- package/hooks/hooks_constants/dead_module_constant_constants.py +1 -0
- package/hooks/hooks_constants/hosted_hook_runner.py +73 -0
- package/hooks/hooks_constants/local_identity.py +182 -0
- package/hooks/hooks_constants/nas_ssh_binary_enforcer_constants.py +4 -37
- package/hooks/hooks_constants/pii_prevention_constants.py +299 -0
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +4 -0
- package/hooks/hooks_constants/python_style_checks_constants.py +15 -0
- package/hooks/hooks_constants/stop_dispatcher_constants.py +26 -0
- package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +87 -0
- package/hooks/hooks_constants/test_hardcoded_user_path_constants.py +2 -2
- package/hooks/hooks_constants/test_hosted_hook_runner.py +101 -0
- package/hooks/hooks_constants/test_layout_constants.py +28 -0
- package/hooks/hooks_constants/test_local_identity.py +88 -0
- package/hooks/hooks_constants/test_session_env_cleanup_constants.py +1 -1
- package/hooks/hooks_constants/test_stop_dispatcher_constants.py +32 -0
- package/hooks/validators/README.md +5 -6
- package/hooks/validators/__init__.py +2 -2
- package/hooks/validators/python_style_checks.py +298 -243
- package/hooks/validators/run_all_validators.py +6 -0
- package/hooks/validators/test_python_style_checks.py +278 -163
- package/package.json +2 -2
- package/rules/CLAUDE.md +2 -1
- package/rules/bdd.md +1 -1
- package/rules/nas-ssh-invocation.md +6 -4
- package/rules/no-justification-noise.md +61 -0
- package/scripts/test_setup_project_paths.py +1 -1
- package/skills/CLAUDE.md +6 -3
- package/skills/_shared/pr-loop/scripts/_path_resolver.py +2 -2
- package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +1 -1
- package/skills/_shared/pr-loop/scripts/build_fix_prompt.py +1 -1
- package/skills/_shared/pr-loop/scripts/test__path_resolver.py +42 -3
- package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +11 -11
- package/skills/_shared/pr-loop/scripts/test_build_fix_prompt.py +26 -3
- package/skills/_shared/pr-loop/scripts/test_preflight_worktree.py +49 -25
- package/skills/autoconverge/CLAUDE.md +3 -3
- package/skills/autoconverge/SKILL.md +113 -35
- package/skills/autoconverge/reference/CLAUDE.md +2 -2
- package/skills/autoconverge/reference/convergence.md +44 -22
- package/skills/autoconverge/reference/gotchas.md +11 -0
- package/skills/autoconverge/reference/stop-conditions.md +23 -13
- package/skills/autoconverge/workflow/CLAUDE.md +2 -1
- package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +15 -19
- package/skills/autoconverge/workflow/converge.contract.test.mjs +43 -42
- package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +141 -16
- package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +2 -2
- package/skills/autoconverge/workflow/converge.mjs +329 -68
- package/skills/autoconverge/workflow/converge.path-aware.test.mjs +1 -1
- package/skills/autoconverge/workflow/converge.precatch.test.mjs +152 -0
- package/skills/autoconverge/workflow/converge.run-input.test.mjs +3 -3
- package/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +15 -15
- package/skills/autoconverge/workflow/test_convergence_summary.py +6 -6
- package/skills/bdd-protocol/SKILL.md +4 -5
- package/skills/bdd-protocol/references/anti-patterns.md +1 -1
- package/skills/bugteam/SKILL.md +8 -0
- package/skills/bugteam/reference/copilot-gap-analysis.md +20 -20
- package/skills/bugteam/reference/obstacles/audit-walk-categories.md +1 -1
- package/skills/bugteam/reference/team-setup.md +7 -5
- package/skills/bugteam/scripts/reflow_skill_md.py +1 -1
- package/skills/copilot-finding-triage/SKILL.md +124 -0
- package/skills/copilot-finding-triage/reference/tier-rubric.md +49 -0
- package/skills/copilot-finding-triage/scripts/conftest.py +8 -0
- package/skills/copilot-finding-triage/scripts/copilot_finding_triage_constants/__init__.py +0 -0
- package/skills/copilot-finding-triage/scripts/copilot_finding_triage_constants/config/__init__.py +0 -0
- package/skills/copilot-finding-triage/scripts/copilot_finding_triage_constants/config/notify_ntfy_constants.py +32 -0
- package/skills/copilot-finding-triage/scripts/notify_ntfy.py +192 -0
- package/skills/copilot-finding-triage/scripts/test_notify_ntfy.py +141 -0
- package/skills/copilot-finding-triage/templates/notification.md +34 -0
- package/skills/copilot-review/SKILL.md +8 -6
- package/skills/findbugs/SKILL.md +4 -0
- package/skills/fixbugs/SKILL.md +8 -7
- package/skills/gotcha/CLAUDE.md +2 -2
- package/skills/gotcha/SKILL.md +4 -4
- package/skills/log-audit/SKILL.md +4 -6
- package/skills/monitor-open-prs/CLAUDE.md +1 -1
- package/skills/monitor-open-prs/SKILL.md +6 -1
- package/skills/orchestrator/SKILL.md +74 -78
- package/skills/orchestrator-refresh/SKILL.md +24 -12
- package/skills/post-audit-findings/SKILL.md +5 -9
- package/skills/pr-consistency-audit/SKILL.md +5 -1
- package/skills/pr-converge/CLAUDE.md +1 -1
- package/skills/pr-converge/SKILL.md +86 -47
- package/skills/pr-converge/reference/CLAUDE.md +1 -1
- package/skills/pr-converge/reference/convergence-gates.md +20 -17
- package/skills/pr-converge/reference/examples.md +63 -47
- package/skills/pr-converge/reference/fix-protocol.md +15 -15
- package/skills/pr-converge/reference/ground-rules.md +11 -7
- package/skills/pr-converge/reference/multi-pr-orchestration.md +6 -5
- package/skills/pr-converge/reference/per-tick.md +129 -107
- package/skills/pr-converge/reference/state-schema.md +15 -10
- package/skills/pr-converge/scripts/check_convergence.py +5 -4
- package/skills/pr-converge/scripts/test_check_convergence.py +6 -4
- package/skills/pr-fix-protocol/SKILL.md +3 -8
- package/skills/pr-loop-cloud-transport/SKILL.md +102 -0
- package/skills/pr-loop-cloud-transport/reference/identity-and-hooks.md +40 -0
- package/skills/pr-loop-cloud-transport/reference/substitution-matrix.md +48 -0
- package/skills/pr-loop-lifecycle/SKILL.md +5 -9
- package/skills/pr-scope-resolve/SKILL.md +3 -6
- package/skills/privacy-hygiene/SKILL.md +114 -0
- package/skills/qbug/SKILL.md +8 -8
- package/skills/rebase/SKILL.md +5 -1
- package/skills/refine/SKILL.md +4 -5
- package/skills/reviewer-gates/SKILL.md +7 -11
- package/skills/session-log/SKILL.md +4 -1
- package/skills/skill-builder/SKILL.md +3 -6
- package/skills/structure-prompt/SKILL.md +4 -5
- package/skills/team-advisor/SKILL.md +56 -0
- package/skills/test_markdown_link_integrity.py +10 -6
- package/skills/update/SKILL.md +5 -1
- package/skills/usage-pause/SKILL.md +14 -5
- package/skills/usage-pause/scripts/resolve_usage_window.py +83 -5
- package/skills/usage-pause/scripts/test_resolve_usage_window.py +185 -17
- package/skills/usage-pause/scripts/usage_pause_constants/resolve_usage_window_constants.py +4 -3
- package/skills/verified-build/SKILL.md +4 -9
|
@@ -1,27 +1,26 @@
|
|
|
1
1
|
"""Tests for Python style checks."""
|
|
2
2
|
|
|
3
3
|
import ast
|
|
4
|
-
import
|
|
4
|
+
import os
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
5
7
|
from pathlib import Path
|
|
6
|
-
from typing import List
|
|
7
8
|
|
|
8
9
|
import pytest
|
|
9
10
|
|
|
10
11
|
from .python_style_checks import (
|
|
11
12
|
Violation,
|
|
13
|
+
check_blank_lines_between_functions,
|
|
12
14
|
check_imports_at_top,
|
|
13
15
|
check_no_empty_line_after_decorators,
|
|
14
|
-
check_single_empty_line_between_functions,
|
|
15
16
|
check_view_function_naming,
|
|
16
17
|
fix_file,
|
|
18
|
+
fix_function_spacing,
|
|
17
19
|
validate_file,
|
|
18
20
|
)
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
# Test data: Code samples
|
|
22
22
|
GOOD_IMPORTS = '''import os
|
|
23
23
|
import sys
|
|
24
|
-
from typing import List
|
|
25
24
|
|
|
26
25
|
def foo() -> None:
|
|
27
26
|
pass
|
|
@@ -38,6 +37,13 @@ BAD_IMPORTS_AFTER_CONSTANT = '''MY_CONSTANT = 42
|
|
|
38
37
|
import os
|
|
39
38
|
'''
|
|
40
39
|
|
|
40
|
+
ASYNC_BAD_INLINE_IMPORT = '''import asyncio
|
|
41
|
+
|
|
42
|
+
async def foo() -> None:
|
|
43
|
+
import json
|
|
44
|
+
await asyncio.sleep(0)
|
|
45
|
+
'''
|
|
46
|
+
|
|
41
47
|
GOOD_DECORATOR_NO_BLANK = '''@decorator
|
|
42
48
|
def foo() -> None:
|
|
43
49
|
pass
|
|
@@ -49,9 +55,18 @@ def foo() -> None:
|
|
|
49
55
|
pass
|
|
50
56
|
'''
|
|
51
57
|
|
|
52
|
-
|
|
58
|
+
ASYNC_BAD_DECORATOR_WITH_BLANK = '''import asyncio
|
|
59
|
+
|
|
60
|
+
@asyncio.coroutine
|
|
61
|
+
|
|
62
|
+
async def foo() -> None:
|
|
63
|
+
pass
|
|
64
|
+
'''
|
|
65
|
+
|
|
66
|
+
GOOD_TWO_LINES_BETWEEN_FUNCTIONS = '''def foo() -> None:
|
|
53
67
|
pass
|
|
54
68
|
|
|
69
|
+
|
|
55
70
|
def bar() -> None:
|
|
56
71
|
pass
|
|
57
72
|
'''
|
|
@@ -62,14 +77,30 @@ def bar() -> None:
|
|
|
62
77
|
pass
|
|
63
78
|
'''
|
|
64
79
|
|
|
65
|
-
|
|
80
|
+
BAD_ONE_LINE_BETWEEN_FUNCTIONS = '''def foo() -> None:
|
|
66
81
|
pass
|
|
67
82
|
|
|
83
|
+
def bar() -> None:
|
|
84
|
+
pass
|
|
85
|
+
'''
|
|
86
|
+
|
|
87
|
+
BAD_THREE_LINES_BETWEEN_FUNCTIONS = '''def foo() -> None:
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
|
|
68
91
|
|
|
69
92
|
def bar() -> None:
|
|
70
93
|
pass
|
|
71
94
|
'''
|
|
72
95
|
|
|
96
|
+
ASYNC_GOOD_TWO_LINES_BETWEEN = '''async def foo() -> None:
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
async def bar() -> None:
|
|
101
|
+
pass
|
|
102
|
+
'''
|
|
103
|
+
|
|
73
104
|
GOOD_VIEW_NAMING = '''def user_profile_view(request):
|
|
74
105
|
pass
|
|
75
106
|
|
|
@@ -80,77 +111,75 @@ def get_tasks_view(request, user_id):
|
|
|
80
111
|
BAD_VIEW_NAMING = '''def user_profile(request):
|
|
81
112
|
pass
|
|
82
113
|
|
|
83
|
-
def
|
|
114
|
+
def get_tasks(request, user_id):
|
|
84
115
|
pass
|
|
85
116
|
'''
|
|
86
117
|
|
|
87
|
-
GOOD_NON_VIEW_FUNCTION = '''def helper_function(
|
|
118
|
+
GOOD_NON_VIEW_FUNCTION = '''def helper_function(payload):
|
|
88
119
|
pass
|
|
89
120
|
|
|
90
|
-
def
|
|
121
|
+
def process_records(items):
|
|
91
122
|
pass
|
|
92
123
|
'''
|
|
93
124
|
|
|
94
|
-
|
|
95
|
-
|
|
125
|
+
ASYNC_BAD_VIEW_NAMING = '''async def user_profile(request):
|
|
126
|
+
pass
|
|
96
127
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
await asyncio.sleep(0)
|
|
128
|
+
async def get_tasks(request, user_id):
|
|
129
|
+
pass
|
|
100
130
|
'''
|
|
101
131
|
|
|
102
|
-
|
|
132
|
+
DECORATED_NEXT_TWO_LINES = '''def foo() -> None:
|
|
133
|
+
pass
|
|
103
134
|
|
|
104
|
-
@asyncio.coroutine
|
|
105
135
|
|
|
106
|
-
|
|
107
|
-
|
|
136
|
+
@decorator
|
|
137
|
+
def bar() -> None:
|
|
138
|
+
pass
|
|
108
139
|
'''
|
|
109
140
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
async def foo() -> None:
|
|
113
|
-
await asyncio.sleep(0)
|
|
141
|
+
ONE_BLANK_BEFORE_DECORATED = '''def foo() -> None:
|
|
142
|
+
pass
|
|
114
143
|
|
|
115
|
-
|
|
116
|
-
|
|
144
|
+
@decorator
|
|
145
|
+
def bar() -> None:
|
|
146
|
+
pass
|
|
117
147
|
'''
|
|
118
148
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
async def foo() -> None:
|
|
122
|
-
await asyncio.sleep(0)
|
|
149
|
+
COMMENT_BETWEEN_FUNCTIONS = '''def foo() -> None:
|
|
150
|
+
pass
|
|
123
151
|
|
|
124
152
|
|
|
125
|
-
|
|
126
|
-
|
|
153
|
+
# helper comment
|
|
154
|
+
def bar() -> None:
|
|
155
|
+
pass
|
|
127
156
|
'''
|
|
128
157
|
|
|
129
|
-
|
|
158
|
+
CLASS_BETWEEN_FUNCTIONS = '''def foo() -> None:
|
|
159
|
+
pass
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class Middle:
|
|
163
|
+
pass
|
|
130
164
|
|
|
131
|
-
async def user_profile_view(request: HttpRequest) -> HttpResponse:
|
|
132
|
-
return HttpResponse("profile")
|
|
133
165
|
|
|
134
|
-
|
|
135
|
-
|
|
166
|
+
def bar() -> None:
|
|
167
|
+
pass
|
|
136
168
|
'''
|
|
137
169
|
|
|
138
|
-
|
|
170
|
+
MODULE_STATEMENT_BETWEEN_FUNCTIONS = '''def foo() -> None:
|
|
171
|
+
pass
|
|
139
172
|
|
|
140
|
-
async def user_profile(request: HttpRequest) -> HttpResponse:
|
|
141
|
-
return HttpResponse("profile")
|
|
142
173
|
|
|
143
|
-
|
|
144
|
-
return HttpResponse("tasks")
|
|
145
|
-
'''
|
|
174
|
+
answer = 42
|
|
146
175
|
|
|
147
|
-
ASYNC_BAD_INLINE_IMPORT = '''import asyncio
|
|
148
176
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
await asyncio.sleep(0)
|
|
177
|
+
def bar() -> None:
|
|
178
|
+
pass
|
|
152
179
|
'''
|
|
153
180
|
|
|
181
|
+
FORM_FEED_BETWEEN_FUNCTIONS = "def foo():\n pass\n\x0c\n\ndef bar():\n pass\n"
|
|
182
|
+
|
|
154
183
|
|
|
155
184
|
class TestImportsAtTop:
|
|
156
185
|
"""Test import positioning validation."""
|
|
@@ -203,13 +232,6 @@ class TestNoEmptyLineAfterDecorators:
|
|
|
203
232
|
assert violations[0].line == 1
|
|
204
233
|
assert "decorator" in violations[0].message.lower()
|
|
205
234
|
|
|
206
|
-
def test_async_no_blank_line_valid(self) -> None:
|
|
207
|
-
"""Decorator directly above async function should pass."""
|
|
208
|
-
violations = check_no_empty_line_after_decorators(
|
|
209
|
-
ASYNC_GOOD_DECORATOR_NO_BLANK, "test.py"
|
|
210
|
-
)
|
|
211
|
-
assert violations == []
|
|
212
|
-
|
|
213
235
|
def test_async_blank_line_after_decorator_fails(self) -> None:
|
|
214
236
|
"""Blank line after decorator on async function should fail."""
|
|
215
237
|
violations = check_no_empty_line_after_decorators(
|
|
@@ -220,46 +242,74 @@ class TestNoEmptyLineAfterDecorators:
|
|
|
220
242
|
assert "decorator" in violations[0].message.lower()
|
|
221
243
|
|
|
222
244
|
|
|
223
|
-
class
|
|
245
|
+
class TestBlankLinesBetweenFunctions:
|
|
224
246
|
"""Test function spacing validation."""
|
|
225
247
|
|
|
226
|
-
def
|
|
227
|
-
"""Exactly
|
|
228
|
-
violations =
|
|
229
|
-
|
|
248
|
+
def test_two_lines_valid(self) -> None:
|
|
249
|
+
"""Exactly two blank lines should pass."""
|
|
250
|
+
violations = check_blank_lines_between_functions(
|
|
251
|
+
GOOD_TWO_LINES_BETWEEN_FUNCTIONS, "test.py"
|
|
230
252
|
)
|
|
231
253
|
assert violations == []
|
|
232
254
|
|
|
233
255
|
def test_no_line_between_functions_fails(self) -> None:
|
|
234
256
|
"""No blank line should fail."""
|
|
235
|
-
violations =
|
|
257
|
+
violations = check_blank_lines_between_functions(
|
|
236
258
|
BAD_NO_LINE_BETWEEN_FUNCTIONS, "test.py"
|
|
237
259
|
)
|
|
238
260
|
assert len(violations) == 1
|
|
239
261
|
assert "empty line" in violations[0].message.lower()
|
|
240
262
|
|
|
241
|
-
def
|
|
242
|
-
"""
|
|
243
|
-
violations =
|
|
244
|
-
|
|
263
|
+
def test_one_line_between_functions_fails(self) -> None:
|
|
264
|
+
"""A single blank line should fail under the two-line rule."""
|
|
265
|
+
violations = check_blank_lines_between_functions(
|
|
266
|
+
BAD_ONE_LINE_BETWEEN_FUNCTIONS, "test.py"
|
|
267
|
+
)
|
|
268
|
+
assert len(violations) == 1
|
|
269
|
+
assert "empty line" in violations[0].message.lower()
|
|
270
|
+
|
|
271
|
+
def test_three_lines_between_functions_fails(self) -> None:
|
|
272
|
+
"""Three or more blank lines should fail."""
|
|
273
|
+
violations = check_blank_lines_between_functions(
|
|
274
|
+
BAD_THREE_LINES_BETWEEN_FUNCTIONS, "test.py"
|
|
245
275
|
)
|
|
246
276
|
assert len(violations) == 1
|
|
247
277
|
assert "empty line" in violations[0].message.lower()
|
|
248
278
|
|
|
249
|
-
def
|
|
250
|
-
"""Exactly
|
|
251
|
-
violations =
|
|
252
|
-
|
|
279
|
+
def test_async_two_lines_valid(self) -> None:
|
|
280
|
+
"""Exactly two blank lines between async functions should pass."""
|
|
281
|
+
violations = check_blank_lines_between_functions(
|
|
282
|
+
ASYNC_GOOD_TWO_LINES_BETWEEN, "test.py"
|
|
253
283
|
)
|
|
254
284
|
assert violations == []
|
|
255
285
|
|
|
256
|
-
def
|
|
257
|
-
"""
|
|
258
|
-
violations =
|
|
259
|
-
|
|
286
|
+
def test_decorated_next_function_two_lines_valid(self) -> None:
|
|
287
|
+
"""Two blanks before a decorated next function should pass."""
|
|
288
|
+
violations = check_blank_lines_between_functions(
|
|
289
|
+
DECORATED_NEXT_TWO_LINES, "test.py"
|
|
260
290
|
)
|
|
261
|
-
assert
|
|
262
|
-
|
|
291
|
+
assert violations == []
|
|
292
|
+
|
|
293
|
+
def test_comment_between_functions_passes(self) -> None:
|
|
294
|
+
"""A comment in the gap leaves the surrounding functions unflagged."""
|
|
295
|
+
violations = check_blank_lines_between_functions(
|
|
296
|
+
COMMENT_BETWEEN_FUNCTIONS, "test.py"
|
|
297
|
+
)
|
|
298
|
+
assert violations == []
|
|
299
|
+
|
|
300
|
+
def test_class_between_functions_passes(self) -> None:
|
|
301
|
+
"""An interposed class leaves the surrounding functions unflagged."""
|
|
302
|
+
violations = check_blank_lines_between_functions(
|
|
303
|
+
CLASS_BETWEEN_FUNCTIONS, "test.py"
|
|
304
|
+
)
|
|
305
|
+
assert violations == []
|
|
306
|
+
|
|
307
|
+
def test_module_statement_between_functions_passes(self) -> None:
|
|
308
|
+
"""A module-level statement in the gap leaves the pair unflagged."""
|
|
309
|
+
violations = check_blank_lines_between_functions(
|
|
310
|
+
MODULE_STATEMENT_BETWEEN_FUNCTIONS, "test.py"
|
|
311
|
+
)
|
|
312
|
+
assert violations == []
|
|
263
313
|
|
|
264
314
|
|
|
265
315
|
class TestViewFunctionNaming:
|
|
@@ -276,7 +326,7 @@ class TestViewFunctionNaming:
|
|
|
276
326
|
tree = ast.parse(BAD_VIEW_NAMING)
|
|
277
327
|
violations = check_view_function_naming(tree, "views.py")
|
|
278
328
|
assert len(violations) == 2
|
|
279
|
-
assert all("_view" in
|
|
329
|
+
assert all("_view" in each_violation.message for each_violation in violations)
|
|
280
330
|
|
|
281
331
|
def test_non_view_file_ignored(self) -> None:
|
|
282
332
|
"""Non-views.py files should be ignored."""
|
|
@@ -290,63 +340,51 @@ class TestViewFunctionNaming:
|
|
|
290
340
|
violations = check_view_function_naming(tree, "views.py")
|
|
291
341
|
assert violations == []
|
|
292
342
|
|
|
293
|
-
def test_async_view_functions_named_correctly(self) -> None:
|
|
294
|
-
"""Async view functions ending with _view should pass."""
|
|
295
|
-
tree = ast.parse(ASYNC_GOOD_VIEW_NAMING)
|
|
296
|
-
violations = check_view_function_naming(tree, "views.py")
|
|
297
|
-
assert violations == []
|
|
298
|
-
|
|
299
343
|
def test_async_view_functions_without_suffix_fail(self) -> None:
|
|
300
344
|
"""Async view functions not ending with _view should fail."""
|
|
301
345
|
tree = ast.parse(ASYNC_BAD_VIEW_NAMING)
|
|
302
346
|
violations = check_view_function_naming(tree, "views.py")
|
|
303
347
|
assert len(violations) == 2
|
|
304
|
-
assert all("_view" in
|
|
348
|
+
assert all("_view" in each_violation.message for each_violation in violations)
|
|
305
349
|
|
|
306
350
|
|
|
307
351
|
class TestValidateFile:
|
|
308
352
|
"""Test file validation integration."""
|
|
309
353
|
|
|
310
|
-
def test_valid_file_passes(
|
|
354
|
+
def test_valid_file_passes(
|
|
355
|
+
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
356
|
+
) -> None:
|
|
311
357
|
"""File with no violations should pass."""
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
def test_invalid_file_returns_violations(self) -> None:
|
|
358
|
+
monkeypatch.setenv("HOME", str(tmp_path))
|
|
359
|
+
monkeypatch.setenv("TMPDIR", str(tmp_path))
|
|
360
|
+
temp_path = tmp_path / "valid_module.py"
|
|
361
|
+
temp_path.write_text(GOOD_IMPORTS, encoding="utf-8")
|
|
362
|
+
violations = validate_file(temp_path)
|
|
363
|
+
assert violations == []
|
|
364
|
+
|
|
365
|
+
def test_invalid_file_returns_violations(
|
|
366
|
+
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
367
|
+
) -> None:
|
|
324
368
|
"""File with violations should return them."""
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
def test_syntax_error_returns_violation(self) -> None:
|
|
369
|
+
monkeypatch.setenv("HOME", str(tmp_path))
|
|
370
|
+
monkeypatch.setenv("TMPDIR", str(tmp_path))
|
|
371
|
+
temp_path = tmp_path / "invalid_module.py"
|
|
372
|
+
temp_path.write_text(BAD_IMPORTS_AFTER_CODE, encoding="utf-8")
|
|
373
|
+
violations = validate_file(temp_path)
|
|
374
|
+
assert len(violations) > 0
|
|
375
|
+
assert all(isinstance(each_violation, Violation) for each_violation in violations)
|
|
376
|
+
|
|
377
|
+
def test_syntax_error_returns_violation(
|
|
378
|
+
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
379
|
+
) -> None:
|
|
338
380
|
"""File with syntax error should return violation."""
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
assert len(violations) == 1
|
|
347
|
-
assert "syntax error" in violations[0].message.lower()
|
|
348
|
-
finally:
|
|
349
|
-
temp_path.unlink()
|
|
381
|
+
monkeypatch.setenv("HOME", str(tmp_path))
|
|
382
|
+
monkeypatch.setenv("TMPDIR", str(tmp_path))
|
|
383
|
+
temp_path = tmp_path / "broken_module.py"
|
|
384
|
+
temp_path.write_text("def foo(\n", encoding="utf-8")
|
|
385
|
+
violations = validate_file(temp_path)
|
|
386
|
+
assert len(violations) == 1
|
|
387
|
+
assert "syntax error" in violations[0].message.lower()
|
|
350
388
|
|
|
351
389
|
|
|
352
390
|
class TestViolationClass:
|
|
@@ -354,22 +392,26 @@ class TestViolationClass:
|
|
|
354
392
|
|
|
355
393
|
def test_violation_creation(self) -> None:
|
|
356
394
|
"""Violation should store file, line, message."""
|
|
357
|
-
|
|
358
|
-
assert
|
|
359
|
-
assert
|
|
360
|
-
assert
|
|
395
|
+
violation = Violation("test.py", 42, "Test message")
|
|
396
|
+
assert violation.file == "test.py"
|
|
397
|
+
assert violation.line == 42
|
|
398
|
+
assert violation.message == "Test message"
|
|
361
399
|
|
|
362
400
|
def test_violation_str_format(self) -> None:
|
|
363
401
|
"""Violation should format as file:line: message."""
|
|
364
|
-
|
|
365
|
-
assert str(
|
|
402
|
+
violation = Violation("test.py", 42, "Test message")
|
|
403
|
+
assert str(violation) == "test.py:42: Test message"
|
|
366
404
|
|
|
367
405
|
|
|
368
406
|
class TestAutoFix:
|
|
369
407
|
"""Test auto-fix capabilities."""
|
|
370
408
|
|
|
371
|
-
def test_fix_empty_line_after_decorator(
|
|
409
|
+
def test_fix_empty_line_after_decorator(
|
|
410
|
+
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
411
|
+
) -> None:
|
|
372
412
|
"""Auto-fix should remove blank line between decorator and function."""
|
|
413
|
+
monkeypatch.setenv("HOME", str(tmp_path))
|
|
414
|
+
monkeypatch.setenv("TMPDIR", str(tmp_path))
|
|
373
415
|
code = '''@decorator
|
|
374
416
|
|
|
375
417
|
def foo():
|
|
@@ -379,59 +421,132 @@ def foo():
|
|
|
379
421
|
def foo():
|
|
380
422
|
pass
|
|
381
423
|
'''
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
"""Auto-fix should collapse multiple blank lines to single."""
|
|
424
|
+
temp_path = tmp_path / "decorator_module.py"
|
|
425
|
+
temp_path.write_text(code, encoding="utf-8")
|
|
426
|
+
fixed = fix_file(temp_path)
|
|
427
|
+
assert fixed is True
|
|
428
|
+
result_text = temp_path.read_text()
|
|
429
|
+
assert result_text.strip() == expected.strip()
|
|
430
|
+
|
|
431
|
+
def test_fix_collapses_three_blank_lines_to_two(
|
|
432
|
+
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
433
|
+
) -> None:
|
|
434
|
+
"""Auto-fix should collapse three or more blank lines down to two."""
|
|
435
|
+
monkeypatch.setenv("HOME", str(tmp_path))
|
|
436
|
+
monkeypatch.setenv("TMPDIR", str(tmp_path))
|
|
396
437
|
code = '''def foo():
|
|
397
438
|
pass
|
|
398
439
|
|
|
399
440
|
|
|
441
|
+
|
|
400
442
|
def bar():
|
|
401
443
|
pass
|
|
402
444
|
'''
|
|
403
445
|
expected = '''def foo():
|
|
404
446
|
pass
|
|
405
447
|
|
|
448
|
+
|
|
406
449
|
def bar():
|
|
407
450
|
pass
|
|
408
451
|
'''
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
452
|
+
temp_path = tmp_path / "spacing_module.py"
|
|
453
|
+
temp_path.write_text(code, encoding="utf-8")
|
|
454
|
+
fixed = fix_file(temp_path)
|
|
455
|
+
assert fixed is True
|
|
456
|
+
result_text = temp_path.read_text()
|
|
457
|
+
assert result_text.strip() == expected.strip()
|
|
458
|
+
|
|
459
|
+
def test_fix_inserts_missing_blank_lines(
|
|
460
|
+
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
461
|
+
) -> None:
|
|
462
|
+
"""Auto-fix should insert blank lines for under-spaced functions."""
|
|
463
|
+
monkeypatch.setenv("HOME", str(tmp_path))
|
|
464
|
+
monkeypatch.setenv("TMPDIR", str(tmp_path))
|
|
465
|
+
temp_path = tmp_path / "underspaced_module.py"
|
|
466
|
+
temp_path.write_text(BAD_ONE_LINE_BETWEEN_FUNCTIONS, encoding="utf-8")
|
|
467
|
+
fixed = fix_file(temp_path)
|
|
468
|
+
assert fixed is True
|
|
469
|
+
assert temp_path.read_text() == GOOD_TWO_LINES_BETWEEN_FUNCTIONS
|
|
470
|
+
|
|
471
|
+
def test_no_fix_needed_returns_false(
|
|
472
|
+
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
473
|
+
) -> None:
|
|
474
|
+
"""Auto-fix should return False when the file already uses two blank lines."""
|
|
475
|
+
monkeypatch.setenv("HOME", str(tmp_path))
|
|
476
|
+
monkeypatch.setenv("TMPDIR", str(tmp_path))
|
|
423
477
|
code = '''def foo():
|
|
424
478
|
pass
|
|
425
479
|
|
|
480
|
+
|
|
426
481
|
def bar():
|
|
427
482
|
pass
|
|
428
483
|
'''
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
484
|
+
temp_path = tmp_path / "clean_module.py"
|
|
485
|
+
temp_path.write_text(code, encoding="utf-8")
|
|
486
|
+
fixed = fix_file(temp_path)
|
|
487
|
+
assert fixed is False
|
|
488
|
+
|
|
489
|
+
def test_form_feed_gap_stays_check_clean_after_fix(
|
|
490
|
+
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
491
|
+
) -> None:
|
|
492
|
+
"""A form feed in a blank gap converges without corrupting the file."""
|
|
493
|
+
monkeypatch.setenv("HOME", str(tmp_path))
|
|
494
|
+
monkeypatch.setenv("TMPDIR", str(tmp_path))
|
|
495
|
+
assert (
|
|
496
|
+
check_blank_lines_between_functions(FORM_FEED_BETWEEN_FUNCTIONS, "test.py")
|
|
497
|
+
== []
|
|
498
|
+
)
|
|
499
|
+
temp_path = tmp_path / "form_feed_module.py"
|
|
500
|
+
temp_path.write_text(FORM_FEED_BETWEEN_FUNCTIONS, encoding="utf-8")
|
|
501
|
+
fix_file(temp_path)
|
|
502
|
+
fixed_text = temp_path.read_text()
|
|
503
|
+
assert check_blank_lines_between_functions(fixed_text, "test.py") == []
|
|
504
|
+
assert fix_file(temp_path) is False
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
class TestFixFunctionSpacing:
|
|
508
|
+
"""Test blank-line normalization between top-level functions."""
|
|
509
|
+
|
|
510
|
+
def test_inserts_two_blank_lines_when_adjacent(self) -> None:
|
|
511
|
+
"""Adjacent functions gain exactly two blank lines."""
|
|
512
|
+
fixed_source = fix_function_spacing(BAD_NO_LINE_BETWEEN_FUNCTIONS)
|
|
513
|
+
assert fixed_source == GOOD_TWO_LINES_BETWEEN_FUNCTIONS
|
|
514
|
+
|
|
515
|
+
def test_inserts_second_blank_line_when_one(self) -> None:
|
|
516
|
+
"""A single blank line grows to exactly two."""
|
|
517
|
+
fixed_source = fix_function_spacing(BAD_ONE_LINE_BETWEEN_FUNCTIONS)
|
|
518
|
+
assert fixed_source == GOOD_TWO_LINES_BETWEEN_FUNCTIONS
|
|
519
|
+
|
|
520
|
+
def test_collapses_three_blank_lines_to_two(self) -> None:
|
|
521
|
+
"""Three blank lines collapse to exactly two."""
|
|
522
|
+
fixed_source = fix_function_spacing(BAD_THREE_LINES_BETWEEN_FUNCTIONS)
|
|
523
|
+
assert fixed_source == GOOD_TWO_LINES_BETWEEN_FUNCTIONS
|
|
524
|
+
|
|
525
|
+
def test_normalizes_before_decorated_function(self) -> None:
|
|
526
|
+
"""Blank lines before a decorated function normalize to two."""
|
|
527
|
+
fixed_source = fix_function_spacing(ONE_BLANK_BEFORE_DECORATED)
|
|
528
|
+
assert fixed_source == DECORATED_NEXT_TWO_LINES
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
class TestDirectInvocation:
|
|
532
|
+
"""Test running the checker as a standalone script."""
|
|
533
|
+
|
|
534
|
+
def test_direct_invocation_resolves_hooks_constants(
|
|
535
|
+
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
536
|
+
) -> None:
|
|
537
|
+
"""Direct invocation bootstraps hooks_constants without PYTHONPATH."""
|
|
538
|
+
monkeypatch.setenv("HOME", str(tmp_path))
|
|
539
|
+
monkeypatch.setenv("TMPDIR", str(tmp_path))
|
|
540
|
+
script_path = Path(__file__).resolve().parent / "python_style_checks.py"
|
|
541
|
+
target_path = tmp_path / "clean_sample.py"
|
|
542
|
+
target_path.write_text(GOOD_IMPORTS, encoding="utf-8")
|
|
543
|
+
scrubbed_environment = dict(os.environ)
|
|
544
|
+
scrubbed_environment.pop("PYTHONPATH", None)
|
|
545
|
+
completed_process = subprocess.run(
|
|
546
|
+
[sys.executable, "-S", str(script_path), str(target_path)],
|
|
547
|
+
capture_output=True,
|
|
548
|
+
text=True,
|
|
549
|
+
env=scrubbed_environment,
|
|
550
|
+
)
|
|
551
|
+
assert "ModuleNotFoundError" not in completed_process.stderr
|
|
552
|
+
assert completed_process.returncode == 0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-dev-env",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.93.0",
|
|
4
4
|
"description": "Claude Code development standards — rules, hooks, agents, commands, and skills",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"license": "MIT",
|
|
37
37
|
"repository": {
|
|
38
38
|
"type": "git",
|
|
39
|
-
"url": "git+https://github.com/jl-cmd/claude-
|
|
39
|
+
"url": "git+https://github.com/jl-cmd/claude-dev-env.git",
|
|
40
40
|
"directory": "packages/claude-dev-env"
|
|
41
41
|
}
|
|
42
42
|
}
|
package/rules/CLAUDE.md
CHANGED
|
@@ -30,10 +30,11 @@ Rule files installed into `~/.claude/rules/` by `bin/install.mjs`. Claude Code l
|
|
|
30
30
|
| `git-workflow.md` | PR workflow: always create as draft, one commit per review stage, never commit working docs or images |
|
|
31
31
|
| `hook-prose-matches-detector.md` | Hook prose descriptions match what the hook actually detects |
|
|
32
32
|
| `long-horizon-autonomy.md` | Autonomous-run behaviors: act on what you have, do not end on a promise, delegate and keep working |
|
|
33
|
-
| `nas-ssh-invocation.md` | Reach the NAS
|
|
33
|
+
| `nas-ssh-invocation.md` | Reach the NAS through the `System32/OpenSSH` binary with `-o BatchMode=yes`; bare `ssh`/`scp`/`sftp` stalls on an interactive password prompt |
|
|
34
34
|
| `no-cross-skill-duplicate-helpers.md` | No duplicating shared helpers across skills; use `_shared/` |
|
|
35
35
|
| `no-historical-clutter.md` | Documentation describes current state only; no historical or transitional language |
|
|
36
36
|
| `no-inline-destructive-literals.md` | No destructive-command literals in Bash tool command strings, even as data |
|
|
37
|
+
| `no-justification-noise.md` | Markdown states facts a reader can act on; cut a present-tense sentence that only justifies a stated choice or restates a gain the reader already works out from the behavior or from a rule enforced elsewhere |
|
|
37
38
|
| `env-var-table-code-drift.md` | Every env-var summary table row in a `.md` file names a code file whose source references the variable |
|
|
38
39
|
| `es-exe-file-search.md` | File search on Windows routes through the `es.exe` CLI with a scoped query; the Everything HTTP server stays off |
|
|
39
40
|
| `orphan-css-class.md` | Every `class="..."` attribute in Python-generated markup has a matching selector in the `<style>` block |
|
package/rules/bdd.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Canonical detail:** `~/.claude/system-prompts/software-engineer.xml` → `<behavior_protocol>`.
|
|
4
4
|
|
|
5
|
-
**On-demand depth:** `@~/.claude/skills/bdd-protocol/SKILL.md` (Example Mapping §6.4, §7.6 catalog, solo patterns). Tracking design: [jl-cmd/claude-
|
|
5
|
+
**On-demand depth:** `@~/.claude/skills/bdd-protocol/SKILL.md` (Example Mapping §6.4, §7.6 catalog, solo patterns). Tracking design: [jl-cmd/claude-dev-env#82](https://github.com/jl-cmd/claude-dev-env/issues/82).
|
|
6
6
|
|
|
7
7
|
**Optional long-form references (load when needed):**
|
|
8
8
|
|