claude-dev-env 2.3.0 → 2.5.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 +53 -48
- package/_shared/pr-loop/scripts/_claude_permissions_common.py +84 -0
- package/_shared/pr-loop/scripts/code_rules_gate.py +4 -2
- package/_shared/pr-loop/scripts/grant_project_claude_permissions.py +306 -306
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/claude_permissions_constants.py +44 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/copilot_quota_constants.py +24 -24
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/stale_worktree_rule_sweep_constants.py +107 -107
- package/_shared/pr-loop/scripts/revoke_project_claude_permissions.py +290 -48
- package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +42 -2
- package/_shared/pr-loop/scripts/tests/test_claude_permissions_constants.py +36 -0
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +100 -1
- package/_shared/pr-loop/scripts/tests/test_fix_hookspath.py +497 -497
- package/_shared/pr-loop/scripts/tests/test_revoke_project_claude_permissions.py +311 -2
- package/_shared/pr-loop/scripts/tests/test_stale_worktree_rule_sweep.py +301 -301
- package/_shared/pr-loop/scripts/tests/test_stale_worktree_rule_sweep_constants.py +85 -85
- package/_shared/pr-loop/worker-spawn.md +1 -1
- package/agents/CLAUDE.md +3 -1
- package/agents/caveman.md +0 -1
- package/agents/clasp-deployment-orchestrator.md +0 -1
- package/agents/clean-coder.md +0 -1
- package/agents/code-advisor.md +0 -1
- package/agents/code-quality-agent.md +1 -2
- package/agents/code-verifier.md +3 -4
- package/agents/deep-research.md +0 -1
- package/agents/docs-agent.md +0 -1
- package/agents/git-commit-crafter.md +0 -1
- package/agents/issue-tracker.md +42 -0
- package/agents/plan-packet-validator.md +0 -1
- package/agents/pr-description-writer.md +0 -1
- package/agents/skill-writer-agent.md +84 -0
- package/agents/test_agent_frontmatter.py +67 -18
- package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +105 -3
- package/audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md +29 -13
- package/bin/CLAUDE.md +68 -5
- package/bin/ever-shipped-skills.mjs +1 -0
- package/bin/install-constants.mjs +88 -0
- package/bin/install.mjs +1138 -114
- package/bin/install.prune.test.mjs +869 -19
- package/bin/install.test.mjs +906 -2
- package/commands/implement.md +1 -1
- package/commands/right-size.md +1 -1
- package/docs/CLAUDE.md +2 -0
- package/docs/host-pool-health-monitor.md +102 -0
- package/docs/references/CLAUDE.md +5 -2
- package/docs/references/advisor-tool.md +13 -0
- package/docs/references/code-review-enforcement.md +107 -0
- package/docs/references/team-advisor-skill.md +14 -0
- package/docs/wsl-docker-cowork-starter-matrix.md +89 -0
- package/hooks/blocking/CLAUDE.md +9 -1
- package/hooks/blocking/code_review_enforcement_config_bootstrap.py +53 -0
- package/hooks/blocking/code_review_gate_deny.py +74 -0
- package/hooks/blocking/code_review_pr_create_gate.py +198 -0
- package/hooks/blocking/code_review_push_gate.py +145 -0
- package/hooks/blocking/code_review_stamp_directory_write_blocker.py +348 -0
- package/hooks/blocking/code_review_stamp_store.py +233 -0
- package/hooks/blocking/code_review_stamp_write_blocker_parts/__init__.py +7 -0
- package/hooks/blocking/code_review_stamp_write_blocker_parts/conftest.py +15 -0
- package/hooks/blocking/code_review_stamp_write_blocker_parts/obfuscated_stamp_path_reference.py +212 -0
- package/hooks/blocking/code_review_stamp_write_blocker_parts/split_directory_change_into_stamp.py +138 -0
- package/hooks/blocking/code_review_stamp_write_blocker_parts/test_obfuscated_stamp_path_reference.py +49 -0
- package/hooks/blocking/code_review_stamp_write_blocker_parts/test_split_directory_change_into_stamp.py +38 -0
- package/hooks/blocking/code_verifier_spawn_preflight_gate.py +39 -27
- package/hooks/blocking/config/__init__.py +5 -5
- package/hooks/blocking/config/code_review_enforcement_constants.py +113 -0
- package/hooks/blocking/config/test_code_review_enforcement_constants.py +113 -0
- package/hooks/blocking/config/verified_commit_constants.py +160 -155
- package/hooks/blocking/conftest.py +2 -0
- package/hooks/blocking/convergence_gate_blocker.py +112 -23
- package/hooks/blocking/destructive_command_blocker.py +19 -6
- package/hooks/blocking/orchestrator_refresh_reschedule_gate.py +256 -0
- package/hooks/blocking/pr_description_proof_of_work.py +52 -34
- package/hooks/blocking/pre_tool_use_dispatcher.py +24 -24
- package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +4 -1
- package/hooks/blocking/test_code_review_enforcement_config_bootstrap.py +62 -0
- package/hooks/blocking/test_code_review_gate_deny.py +54 -0
- package/hooks/blocking/test_code_review_pr_create_gate.py +199 -0
- package/hooks/blocking/test_code_review_push_gate.py +205 -0
- package/hooks/blocking/test_code_review_stamp_directory_write_blocker.py +199 -0
- package/hooks/blocking/test_code_review_stamp_store.py +205 -0
- package/hooks/blocking/test_code_verifier_spawn_preflight_gate.py +124 -2
- package/hooks/blocking/test_convergence_gate_blocker.py +153 -5
- package/hooks/blocking/test_destructive_command_blocker.py +1 -1
- package/hooks/blocking/test_destructive_command_blocker_deny_mode.py +45 -0
- package/hooks/blocking/test_orchestrator_refresh_reschedule_gate.py +231 -0
- package/hooks/blocking/test_pr_description_proof_of_work.py +151 -0
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +17 -8
- package/hooks/blocking/test_verdict_directory_write_blocker.py +808 -808
- package/hooks/blocking/test_verification_verdict_store.py +974 -903
- package/hooks/blocking/test_verified_commit_gate.py +581 -581
- package/hooks/blocking/test_verified_commit_message_accuracy_blocker.py +131 -131
- package/hooks/blocking/test_volatile_path_in_post_blocker.py +114 -2
- package/hooks/blocking/verdict_directory_write_blocker.py +687 -687
- package/hooks/blocking/verification_verdict_store.py +1039 -1014
- package/hooks/blocking/verified_commit_gate_parts/gated_invocations.py +29 -17
- package/hooks/blocking/verified_commit_gate_parts/tests/test_gated_invocations.py +35 -0
- package/hooks/blocking/verified_commit_message_accuracy_blocker.py +167 -167
- package/hooks/blocking/verifier_verdict_minter.py +280 -280
- package/hooks/blocking/volatile_path_in_post_blocker.py +69 -8
- package/hooks/git-hooks/git_hooks_constants/__init__.py +6 -0
- package/hooks/git-hooks/pre_push.py +89 -2
- package/hooks/git-hooks/test_pre_push.py +128 -0
- package/hooks/hooks.json +26 -1
- package/hooks/hooks_constants/CLAUDE.md +3 -1
- package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +8 -0
- package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -0
- package/hooks/hooks_constants/code_verifier_spawn_preflight_gate_constants.py +26 -11
- package/hooks/hooks_constants/convergence_gate_blocker_constants.py +20 -3
- package/hooks/hooks_constants/destructive_command_segment_constants.py +3 -1
- package/hooks/hooks_constants/enter_worktree_prefetch_constants.py +18 -18
- package/hooks/hooks_constants/orchestrator_refresh_reschedule_gate_constants.py +48 -0
- package/hooks/hooks_constants/pr_description_proof_of_work_constants.py +0 -4
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +4 -0
- package/hooks/hooks_constants/pyproject_config_discovery_constants.py +16 -0
- package/hooks/hooks_constants/ruff_integration_constants.py +16 -0
- package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +24 -0
- package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +6 -0
- package/hooks/hooks_constants/volatile_path_in_post_blocker_constants.py +8 -1
- package/hooks/lifecycle/enter_worktree_origin_prefetch.py +163 -146
- package/hooks/lifecycle/test_enter_worktree_origin_prefetch.py +185 -178
- package/hooks/pyproject.toml +1 -0
- package/hooks/validators/CLAUDE.md +2 -0
- package/hooks/validators/config/__init__.py +0 -0
- package/hooks/validators/config/directory_exemption_constants.py +183 -0
- package/hooks/validators/config/test_directory_exemption_constants.py +21 -0
- package/hooks/validators/conftest.py +4 -0
- package/hooks/validators/mypy_integration.py +63 -50
- package/hooks/validators/pyproject_config_discovery.py +101 -0
- package/hooks/validators/ruff_integration.py +257 -25
- package/hooks/validators/run_all_validators.py +223 -19
- package/hooks/validators/test_directory_exemption_constants.py +185 -0
- package/hooks/validators/test_mypy_integration.py +32 -0
- package/hooks/validators/test_pyproject_config_discovery.py +94 -0
- package/hooks/validators/test_python_antipattern_checks.py +110 -5
- package/hooks/validators/test_ruff_integration.py +160 -2
- package/hooks/validators/test_run_all_validators.py +140 -68
- package/hooks/validators/test_run_all_validators_config_discovery.py +123 -0
- package/hooks/validators/test_run_all_validators_pretooluse.py +159 -1
- package/package.json +10 -2
- package/rules/CLAUDE.md +1 -0
- package/rules/docstring-prose-matches-implementation.md +45 -67
- package/rules/durable-post-artifacts.md +7 -0
- package/rules/state-what-is.md +25 -0
- package/rules/verified-commit-gate-skip.md +1 -1
- package/scripts/CLAUDE.md +1 -0
- package/scripts/Capture-PoolHealth.ps1 +410 -0
- package/scripts/_code_review_test_support.py +404 -0
- package/scripts/claude_chain_runner.py +141 -1
- package/scripts/codec_forwarding_test_support.py +83 -0
- package/scripts/conftest.py +23 -0
- package/scripts/dev_env_scripts_constants/CLAUDE.md +2 -2
- package/scripts/dev_env_scripts_constants/claude_chain_constants.py +53 -1
- package/scripts/dev_env_scripts_constants/code_review_constants.py +129 -12
- package/scripts/dev_env_scripts_constants/test_code_review_constants.py +55 -0
- package/scripts/invoke_code_review.py +550 -38
- package/scripts/resolve_worker_spawn.py +626 -619
- package/scripts/spawn_grok_batch.py +672 -672
- package/scripts/test_claude_chain_runner.py +131 -0
- package/scripts/test_invoke_code_review_chain.py +70 -0
- package/scripts/test_invoke_code_review_cli.py +192 -0
- package/scripts/test_invoke_code_review_codec.py +77 -0
- package/scripts/test_invoke_code_review_contract.py +256 -0
- package/scripts/test_invoke_code_review_git.py +123 -0
- package/scripts/test_invoke_code_review_mode.py +99 -0
- package/scripts/test_resolve_worker_spawn.py +1014 -1014
- package/scripts/test_resolve_worker_spawn_codec.py +101 -0
- package/skills/CLAUDE.md +2 -0
- package/skills/auditing-claude-config/SKILL.md +114 -114
- package/skills/autoconverge/SKILL.md +427 -421
- package/skills/autoconverge/reference/convergence.md +26 -4
- package/skills/autoconverge/reference/multi-pr.md +6 -1
- package/skills/autoconverge/reference/stop-conditions.md +16 -10
- package/skills/autoconverge/workflow/CLAUDE.md +1 -0
- package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +4 -4
- package/skills/autoconverge/workflow/converge.codex-gate.test.mjs +175 -3
- package/skills/autoconverge/workflow/converge.contract.test.mjs +1263 -1244
- package/skills/autoconverge/workflow/converge.mjs +191 -8
- package/skills/autoconverge/workflow/converge.p2-advance.test.mjs +202 -0
- package/skills/autoconverge/workflow/converge_multi.mjs +7 -3
- package/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +5 -0
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a11d903476b803493.jsonl +2 -2
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a26213978adeef6fb.jsonl +2 -2
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a3def0d15ed9d9110.jsonl +2 -2
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a41f41b1b708ee3b7.jsonl +2 -2
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a758b880abecc3ff7.jsonl +2 -2
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a8897b89656b1bd16.jsonl +2 -2
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-abd463d744a1437bc.jsonl +2 -2
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ad19d027ae8ee1816.jsonl +2 -2
- package/skills/autoconverge/workflow/fixtures/wf_run/workflows/wf_881252e6-700.json +265 -265
- package/skills/closeout/SKILL.md +33 -50
- package/skills/codex-review/scripts/codex_review_scripts_constants/run_constants.py +8 -0
- package/skills/codex-review/scripts/run_codex_review.py +233 -1
- package/skills/codex-review/scripts/test_run_codex_review.py +189 -0
- package/skills/condensing-instructions/SKILL.md +81 -0
- package/skills/copilot-review/SKILL.md +119 -119
- package/skills/e-code-review/SKILL.md +52 -0
- package/skills/e-code-review/reference/fix.md +54 -0
- package/skills/e-code-review/reference/loop.md +43 -0
- package/skills/e-code-review/reference/low.md +57 -0
- package/skills/e-code-review/reference/medium.md +153 -0
- package/skills/e-code-review/reference/xhigh.md +182 -0
- package/skills/e-simplify/SKILL.md +97 -0
- package/skills/fresh-branch/CLAUDE.md +2 -0
- package/skills/fresh-branch/SKILL.md +2 -0
- package/skills/fresh-branch/scripts/create_fresh_branch.py +78 -180
- package/skills/fresh-branch/scripts/fresh_branch_git_commands.py +285 -0
- package/skills/fresh-branch/scripts/fresh_branch_scripts_constants/fresh_branch_cli_constants.py +1 -0
- package/skills/fresh-branch/scripts/pytest.ini +4 -0
- package/skills/fresh-branch/scripts/test_create_fresh_branch.py +98 -0
- package/skills/fresh-branch/scripts/test_fresh_branch_git_commands.py +310 -0
- package/skills/issue-tracker/SKILL.md +92 -0
- package/skills/issue-tracker/reference/epic-and-sub-issue-model.md +55 -0
- package/skills/issue-tracker/reference/handoff-schema.md +64 -0
- package/skills/issue-tracker/reference/operation-matrix.md +41 -0
- package/skills/orchestrator/SKILL.md +162 -21
- package/skills/orchestrator/scripts/status_gate.py +625 -0
- package/skills/orchestrator/scripts/status_gate_constants/__init__.py +1 -0
- package/skills/orchestrator/scripts/status_gate_constants/config/__init__.py +1 -0
- package/skills/orchestrator/scripts/status_gate_constants/config/constants.py +47 -0
- package/skills/orchestrator/scripts/test_status_gate.py +439 -0
- package/skills/orchestrator-refresh/SKILL.md +110 -35
- package/skills/plan-to-pr/SKILL.md +155 -0
- package/skills/plan-to-pr/reference/final-validation-tasks.md +15 -0
- package/skills/plan-to-pr/reference/model-routing.md +36 -0
- package/skills/plan-to-pr/reference/packet-contract.md +43 -0
- package/skills/plan-to-pr/reference/packet-schema.json +57 -0
- package/skills/plan-to-pr/reference/process-inventory.md +22 -0
- package/skills/plan-to-pr/reference/review-loop.md +33 -0
- package/skills/plan-to-pr/reference/run-record.schema.json +27 -0
- package/skills/plan-to-pr/reference/self-audit-tasks.md +15 -0
- package/skills/plan-to-pr/reference/task-seeds.md +14 -0
- package/skills/plan-to-pr/reference/task-ticket.md +38 -0
- package/skills/plan-to-pr/scripts/config/__init__.py +1 -0
- package/skills/plan-to-pr/scripts/config/constants.py +193 -0
- package/skills/plan-to-pr/scripts/create_packet.py +173 -0
- package/skills/plan-to-pr/scripts/test_create_packet.py +102 -0
- package/skills/plan-to-pr/scripts/test_validate_packet.py +256 -0
- package/skills/plan-to-pr/scripts/test_validate_protocol.py +135 -0
- package/skills/plan-to-pr/scripts/test_validate_run.py +158 -0
- package/skills/plan-to-pr/scripts/validate_packet.py +655 -0
- package/skills/plan-to-pr/scripts/validate_protocol.py +622 -0
- package/skills/plan-to-pr/scripts/validate_run.py +173 -0
- package/skills/plan-to-pr/test_skill_contract.py +207 -0
- package/skills/plan-to-pr/test_task_ticket_contract.py +151 -0
- package/skills/pr-converge/SKILL.md +472 -469
- package/skills/pr-converge/reference/examples.md +3 -3
- package/skills/pr-converge/reference/fix-protocol.md +1 -1
- package/skills/pr-converge/reference/ground-rules.md +7 -4
- package/skills/pr-converge/reference/multi-pr-orchestration.md +4 -1
- package/skills/pr-converge/reference/per-tick.md +5 -5
- package/skills/pr-converge/reference/progress-checklist.md +1 -1
- package/skills/pr-converge/scripts/check_convergence_gates.py +279 -279
- package/skills/pr-converge/scripts/test_check_convergence_codex.py +507 -507
- package/skills/pr-converge/scripts/test_check_convergence_gates.py +84 -84
- package/skills/pr-converge/test_step5_host_branch.py +1 -1
- package/skills/pr-fix-protocol/SKILL.md +1 -1
- package/skills/privacy-hygiene/SKILL.md +68 -68
- package/skills/prototype/SKILL.md +86 -0
- package/skills/prototype/reference/honest-limitations.md +23 -0
- package/skills/prototype/reference/promotion-tasks.md +23 -0
- package/skills/prototype/scripts/build_sandbox_settings.py +249 -0
- package/skills/prototype/scripts/conftest.py +15 -0
- package/skills/prototype/scripts/launch_sandbox.py +205 -0
- package/skills/prototype/scripts/probe_sandbox_safety.py +311 -0
- package/skills/prototype/scripts/prototype_scripts_constants/__init__.py +1 -0
- package/skills/prototype/scripts/prototype_scripts_constants/config/__init__.py +0 -0
- package/skills/prototype/scripts/prototype_scripts_constants/config/build_sandbox_settings_constants.py +41 -0
- package/skills/prototype/scripts/prototype_scripts_constants/config/launch_sandbox_constants.py +23 -0
- package/skills/prototype/scripts/prototype_scripts_constants/config/probe_sandbox_safety_constants.py +45 -0
- package/skills/prototype/scripts/prototype_scripts_constants/config/prototype_common_constants.py +10 -0
- package/skills/prototype/scripts/test_build_sandbox_settings.py +275 -0
- package/skills/prototype/scripts/test_launch_sandbox.py +303 -0
- package/skills/prototype/scripts/test_probe_sandbox_safety.py +284 -0
- package/skills/prototype/workflows/promotion.md +27 -0
- package/skills/prototype/workflows/sandbox.md +35 -0
- package/skills/release-notes-html/SKILL.md +164 -0
- package/skills/skill-builder/CLAUDE.md +3 -3
- package/skills/skill-builder/SKILL.md +5 -5
- package/skills/skill-builder/references/CLAUDE.md +1 -1
- package/skills/skill-builder/references/delegation-map.md +3 -3
- package/skills/skill-builder/references/description-field.md +1 -1
- package/skills/skill-builder/references/skill-modularity.md +2 -3
- package/skills/skill-builder/workflows/CLAUDE.md +1 -1
- package/skills/skill-builder/workflows/improve-skill.md +1 -1
- package/skills/skill-builder/workflows/new-skill.md +2 -2
- package/skills/task-build/CLAUDE.md +8 -7
- package/skills/task-build/SKILL.md +16 -8
- package/skills/task-build/reference/tool-routing.md +19 -0
- package/skills/team-advisor/SKILL.md +2 -2
- package/scripts/test_invoke_code_review.py +0 -672
- package/skills/closeout/reference/issue-body-templates.md +0 -108
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Regression tests: staged validation resolves ruff config from the original path.
|
|
2
|
+
|
|
3
|
+
The PreToolUse gate stages proposed content to an OS-temp file, so ruff would
|
|
4
|
+
discover its config by walking up from that temp path and never reach
|
|
5
|
+
``packages/claude-dev-env/hooks/pyproject.toml``. Its ``[tool.ruff.lint]`` selects
|
|
6
|
+
B and PL, so a dropped config leaves the staged run less strict than the project.
|
|
7
|
+
|
|
8
|
+
``validate_proposed_file`` resolves the config from the ORIGINAL target path, so
|
|
9
|
+
the project ruff config applies to the staged copy from any working directory.
|
|
10
|
+
|
|
11
|
+
::
|
|
12
|
+
|
|
13
|
+
original path .../validators/config_probe_module.py, DIRTY_SOURCE (assert False)
|
|
14
|
+
flag (defect): staged temp copy -> no [tool.ruff] up-tree -> B011 not selected
|
|
15
|
+
ok (fixed): config resolved from original path -> B011 fires
|
|
16
|
+
|
|
17
|
+
original path .../validators/test_config_probe_module.py, same DIRTY_SOURCE
|
|
18
|
+
ok: the test_*.py per-file-ignore reaches the staged copy -> B011 suppressed
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
import pytest
|
|
24
|
+
|
|
25
|
+
from .run_all_validators import ValidatorResult, get_project_root, validate_proposed_file
|
|
26
|
+
|
|
27
|
+
VALIDATORS_DIRECTORY = Path(__file__).parent
|
|
28
|
+
NON_TEST_PROBE_PATH = VALIDATORS_DIRECTORY / "config_probe_module.py"
|
|
29
|
+
TEST_PROBE_PATH = VALIDATORS_DIRECTORY / "test_config_probe_module.py"
|
|
30
|
+
|
|
31
|
+
RUN_ALL_VALIDATORS_PATH = VALIDATORS_DIRECTORY / "run_all_validators.py"
|
|
32
|
+
EXEMPT_PATHS_PATH = VALIDATORS_DIRECTORY / "exempt_paths.py"
|
|
33
|
+
|
|
34
|
+
DIRTY_SOURCE = (
|
|
35
|
+
"def probe_condition(observed_total: int) -> None:\n assert False, observed_total\n"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
UNCHECKED_SUBPROCESS_SOURCE = (
|
|
39
|
+
"import subprocess\n\n\ndef run_listing() -> None:\n subprocess.run([\"ls\"])\n"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
UNUSED_IMPORT_SOURCE = "import os\n"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _ruff_result(all_results: list[ValidatorResult]) -> ValidatorResult:
|
|
46
|
+
"""Return the Ruff validator result from a validate_proposed_file run."""
|
|
47
|
+
for each_result in all_results:
|
|
48
|
+
if each_result.name == "Ruff":
|
|
49
|
+
return each_result
|
|
50
|
+
raise AssertionError("no Ruff validator result was produced")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _working_directory_for(cwd_kind: str, outside_repo_directory: Path) -> Path:
|
|
54
|
+
"""Resolve the cwd a parametrized case runs from — repo root or outside it."""
|
|
55
|
+
if cwd_kind == "repo_root":
|
|
56
|
+
project_root = get_project_root()
|
|
57
|
+
assert project_root is not None, "repo root must resolve for this test"
|
|
58
|
+
return project_root
|
|
59
|
+
return outside_repo_directory
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@pytest.mark.parametrize("cwd_kind", ["repo_root", "outside_repo"])
|
|
63
|
+
def test_staged_non_test_file_reports_b011_from_every_cwd(
|
|
64
|
+
cwd_kind: str, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
65
|
+
) -> None:
|
|
66
|
+
monkeypatch.chdir(_working_directory_for(cwd_kind, tmp_path))
|
|
67
|
+
|
|
68
|
+
ruff_result = _ruff_result(validate_proposed_file(str(NON_TEST_PROBE_PATH), DIRTY_SOURCE))
|
|
69
|
+
|
|
70
|
+
assert "B011" in ruff_result.output
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_staged_test_file_suppresses_b011_via_per_file_ignore(
|
|
74
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
75
|
+
) -> None:
|
|
76
|
+
monkeypatch.chdir(tmp_path)
|
|
77
|
+
|
|
78
|
+
non_test_ruff_result = _ruff_result(
|
|
79
|
+
validate_proposed_file(str(NON_TEST_PROBE_PATH), DIRTY_SOURCE)
|
|
80
|
+
)
|
|
81
|
+
test_ruff_result = _ruff_result(validate_proposed_file(str(TEST_PROBE_PATH), DIRTY_SOURCE))
|
|
82
|
+
|
|
83
|
+
assert "B011" in non_test_ruff_result.output
|
|
84
|
+
assert "B011" not in test_ruff_result.output
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@pytest.mark.parametrize("cwd_kind", ["repo_root", "outside_repo"])
|
|
88
|
+
def test_staged_run_all_validators_suppresses_plw1510_via_path_ignore(
|
|
89
|
+
cwd_kind: str, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
90
|
+
) -> None:
|
|
91
|
+
monkeypatch.chdir(_working_directory_for(cwd_kind, tmp_path))
|
|
92
|
+
|
|
93
|
+
ruff_result = _ruff_result(
|
|
94
|
+
validate_proposed_file(str(RUN_ALL_VALIDATORS_PATH), UNCHECKED_SUBPROCESS_SOURCE)
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
assert "PLW1510" not in ruff_result.output
|
|
98
|
+
assert ruff_result.passed
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_staged_exempt_paths_suppresses_f401_via_path_ignore(
|
|
102
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
103
|
+
) -> None:
|
|
104
|
+
monkeypatch.chdir(tmp_path)
|
|
105
|
+
|
|
106
|
+
ruff_result = _ruff_result(
|
|
107
|
+
validate_proposed_file(str(EXEMPT_PATHS_PATH), UNUSED_IMPORT_SOURCE)
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
assert "F401" not in ruff_result.output
|
|
111
|
+
assert ruff_result.passed
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def test_staged_non_carved_path_still_reports_plw1510(
|
|
115
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
116
|
+
) -> None:
|
|
117
|
+
monkeypatch.chdir(tmp_path)
|
|
118
|
+
|
|
119
|
+
ruff_result = _ruff_result(
|
|
120
|
+
validate_proposed_file(str(NON_TEST_PROBE_PATH), UNCHECKED_SUBPROCESS_SOURCE)
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
assert "PLW1510" in ruff_result.output
|
|
@@ -8,6 +8,7 @@ when that content violates a validator, rather than grading the whole branch.
|
|
|
8
8
|
import json
|
|
9
9
|
import subprocess
|
|
10
10
|
import sys
|
|
11
|
+
import tempfile
|
|
11
12
|
from collections import Counter
|
|
12
13
|
from pathlib import Path
|
|
13
14
|
from unittest.mock import patch
|
|
@@ -16,12 +17,21 @@ import pytest
|
|
|
16
17
|
|
|
17
18
|
from .run_all_validators import (
|
|
18
19
|
ValidatorResult,
|
|
20
|
+
_escapes_temporary_root,
|
|
21
|
+
_hooks_subprocess_working_directory_and_environment,
|
|
19
22
|
_scope_new_and_preexisting,
|
|
23
|
+
_temporary_path_preserving_directory_signal,
|
|
20
24
|
_violation_line_number,
|
|
21
25
|
main,
|
|
22
26
|
run_validators_entrypoint_subprocess,
|
|
23
27
|
)
|
|
24
28
|
|
|
29
|
+
CONFIG_DIR_TARGET_PATH = (
|
|
30
|
+
"CDP Automations/os_update_workflow/config/submission_constants.py"
|
|
31
|
+
)
|
|
32
|
+
PARENT_TRAVERSAL_TARGET_PATH = "../../escape_target.py"
|
|
33
|
+
RELATIVE_CONFIG_TARGET_PATH = "config/x.py"
|
|
34
|
+
|
|
25
35
|
CLEAN_PYTHON_SOURCE = (
|
|
26
36
|
"def add_two_numbers(first_number: int, second_number: int) -> int:\n"
|
|
27
37
|
" return first_number + second_number\n"
|
|
@@ -85,7 +95,9 @@ class TestPreToolUseGate:
|
|
|
85
95
|
def test_edit_validates_reconstructed_post_edit_content(
|
|
86
96
|
self, tmp_path: Path
|
|
87
97
|
) -> None:
|
|
88
|
-
|
|
98
|
+
target_directory = tmp_path / "neutral_edit_target"
|
|
99
|
+
target_directory.mkdir(exist_ok=True)
|
|
100
|
+
target_file = target_directory / "calculate.py"
|
|
89
101
|
target_file.write_text(CLEAN_PYTHON_SOURCE, encoding="utf-8")
|
|
90
102
|
completed = run_gate(
|
|
91
103
|
{
|
|
@@ -107,6 +119,152 @@ class TestPreToolUseGate:
|
|
|
107
119
|
assert completed.returncode == 0, completed.stderr
|
|
108
120
|
assert "deny" not in completed.stdout
|
|
109
121
|
|
|
122
|
+
def test_write_to_config_dir_path_is_not_denied(self) -> None:
|
|
123
|
+
completed = run_gate(
|
|
124
|
+
{
|
|
125
|
+
"tool_name": "Write",
|
|
126
|
+
"tool_input": {
|
|
127
|
+
"file_path": CONFIG_DIR_TARGET_PATH,
|
|
128
|
+
"content": VIOLATING_PYTHON_SOURCE,
|
|
129
|
+
},
|
|
130
|
+
}
|
|
131
|
+
)
|
|
132
|
+
assert completed.returncode == 0, completed.stderr
|
|
133
|
+
assert "deny" not in completed.stdout
|
|
134
|
+
|
|
135
|
+
def test_write_relative_config_path_not_denied_when_cwd_under_system_temp(
|
|
136
|
+
self, monkeypatch: pytest.MonkeyPatch
|
|
137
|
+
) -> None:
|
|
138
|
+
system_temp_root = Path(tempfile.gettempdir()).resolve()
|
|
139
|
+
monkeypatch.setenv("TEMP", str(system_temp_root))
|
|
140
|
+
monkeypatch.setenv("TMP", str(system_temp_root))
|
|
141
|
+
monkeypatch.setenv("TMPDIR", str(system_temp_root))
|
|
142
|
+
with tempfile.TemporaryDirectory(
|
|
143
|
+
dir=str(system_temp_root), prefix="gate_cwd_"
|
|
144
|
+
) as temporary_root_string:
|
|
145
|
+
temporary_cwd = Path(temporary_root_string) / "gate_cwd"
|
|
146
|
+
temporary_cwd.mkdir()
|
|
147
|
+
|
|
148
|
+
def working_directory_under_system_temp() -> tuple[str, dict[str, str]]:
|
|
149
|
+
_working_directory, environment = (
|
|
150
|
+
_hooks_subprocess_working_directory_and_environment()
|
|
151
|
+
)
|
|
152
|
+
return str(temporary_cwd), environment
|
|
153
|
+
|
|
154
|
+
with patch(
|
|
155
|
+
"validators.run_all_validators._hooks_subprocess_working_directory_and_environment",
|
|
156
|
+
side_effect=working_directory_under_system_temp,
|
|
157
|
+
):
|
|
158
|
+
completed = run_gate(
|
|
159
|
+
{
|
|
160
|
+
"tool_name": "Write",
|
|
161
|
+
"tool_input": {
|
|
162
|
+
"file_path": RELATIVE_CONFIG_TARGET_PATH,
|
|
163
|
+
"content": VIOLATING_PYTHON_SOURCE,
|
|
164
|
+
},
|
|
165
|
+
}
|
|
166
|
+
)
|
|
167
|
+
assert completed.returncode == 0, completed.stderr
|
|
168
|
+
assert "deny" not in completed.stdout
|
|
169
|
+
|
|
170
|
+
def test_write_to_parent_traversal_path_still_validates(self) -> None:
|
|
171
|
+
completed = run_gate(
|
|
172
|
+
{
|
|
173
|
+
"tool_name": "Write",
|
|
174
|
+
"tool_input": {
|
|
175
|
+
"file_path": PARENT_TRAVERSAL_TARGET_PATH,
|
|
176
|
+
"content": VIOLATING_PYTHON_SOURCE,
|
|
177
|
+
},
|
|
178
|
+
}
|
|
179
|
+
)
|
|
180
|
+
assert completed.returncode == 0, completed.stderr
|
|
181
|
+
assert '"permissionDecision": "deny"' in completed.stdout
|
|
182
|
+
assert "Magic Values" in completed.stdout
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class TestTemporaryPathPreservingDirectorySignal:
|
|
186
|
+
def test_strips_parent_traversal_segments(self, tmp_path: Path) -> None:
|
|
187
|
+
assert _escapes_temporary_root("..") is True
|
|
188
|
+
staged_path = _temporary_path_preserving_directory_signal(
|
|
189
|
+
tmp_path, PARENT_TRAVERSAL_TARGET_PATH
|
|
190
|
+
)
|
|
191
|
+
assert staged_path == tmp_path / "escape_target.py"
|
|
192
|
+
assert staged_path.resolve().is_relative_to(tmp_path.resolve())
|
|
193
|
+
|
|
194
|
+
def test_keeps_config_directory_segment(self, tmp_path: Path) -> None:
|
|
195
|
+
staged_path = _temporary_path_preserving_directory_signal(
|
|
196
|
+
tmp_path, CONFIG_DIR_TARGET_PATH
|
|
197
|
+
)
|
|
198
|
+
assert staged_path.name == "submission_constants.py"
|
|
199
|
+
assert "config" in staged_path.parts
|
|
200
|
+
assert staged_path == tmp_path / "config" / "submission_constants.py"
|
|
201
|
+
|
|
202
|
+
def test_drops_leading_anchor(self, tmp_path: Path) -> None:
|
|
203
|
+
absolute_target = tmp_path / "config" / "submission_constants.py"
|
|
204
|
+
staging_root = tmp_path / "staging_root"
|
|
205
|
+
staging_root.mkdir()
|
|
206
|
+
staged_path = _temporary_path_preserving_directory_signal(
|
|
207
|
+
staging_root, str(absolute_target)
|
|
208
|
+
)
|
|
209
|
+
relative_parts = staged_path.relative_to(staging_root).parts
|
|
210
|
+
assert relative_parts == ("config", "submission_constants.py")
|
|
211
|
+
assert absolute_target.anchor not in relative_parts
|
|
212
|
+
assert staged_path.resolve().is_relative_to(staging_root.resolve())
|
|
213
|
+
|
|
214
|
+
def test_path_without_exemption_directory_stages_flat_basename(
|
|
215
|
+
self, tmp_path: Path
|
|
216
|
+
) -> None:
|
|
217
|
+
pytest_shaped_target = (
|
|
218
|
+
tmp_path / "test_edit_introducing_new_viol0" / "legacy_module.py"
|
|
219
|
+
)
|
|
220
|
+
pytest_shaped_target.parent.mkdir(parents=True, exist_ok=True)
|
|
221
|
+
pytest_shaped_target.write_text(CLEAN_MARKER_FUNCTION, encoding="utf-8")
|
|
222
|
+
staging_root = tmp_path / "staging_root"
|
|
223
|
+
staging_root.mkdir()
|
|
224
|
+
staged_path = _temporary_path_preserving_directory_signal(
|
|
225
|
+
staging_root, str(pytest_shaped_target)
|
|
226
|
+
)
|
|
227
|
+
assert staged_path == staging_root / "legacy_module.py"
|
|
228
|
+
|
|
229
|
+
def test_relative_config_path_preserves_config_under_system_temp_cwd(
|
|
230
|
+
self, monkeypatch: pytest.MonkeyPatch
|
|
231
|
+
) -> None:
|
|
232
|
+
system_temp_root = Path(tempfile.gettempdir()).resolve()
|
|
233
|
+
monkeypatch.setenv("TEMP", str(system_temp_root))
|
|
234
|
+
monkeypatch.setenv("TMP", str(system_temp_root))
|
|
235
|
+
monkeypatch.setenv("TMPDIR", str(system_temp_root))
|
|
236
|
+
with tempfile.TemporaryDirectory(
|
|
237
|
+
dir=str(system_temp_root), prefix="staging_cwd_"
|
|
238
|
+
) as temporary_root_string:
|
|
239
|
+
temporary_root = Path(temporary_root_string)
|
|
240
|
+
previous_cwd = Path.cwd()
|
|
241
|
+
try:
|
|
242
|
+
monkeypatch.chdir(temporary_root)
|
|
243
|
+
staging_root = temporary_root / "staging_root"
|
|
244
|
+
staging_root.mkdir()
|
|
245
|
+
staged_path = _temporary_path_preserving_directory_signal(
|
|
246
|
+
staging_root, RELATIVE_CONFIG_TARGET_PATH
|
|
247
|
+
)
|
|
248
|
+
assert staged_path == staging_root / "config" / "x.py"
|
|
249
|
+
assert "config" in staged_path.parts
|
|
250
|
+
assert staged_path.name == "x.py"
|
|
251
|
+
finally:
|
|
252
|
+
monkeypatch.chdir(previous_cwd)
|
|
253
|
+
|
|
254
|
+
def test_escapes_temporary_root_rejects_absolute_and_parent_parts(
|
|
255
|
+
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
256
|
+
) -> None:
|
|
257
|
+
sandbox_temp_root = tmp_path / "system_temp"
|
|
258
|
+
sandbox_temp_root.mkdir()
|
|
259
|
+
monkeypatch.setenv("TEMP", str(sandbox_temp_root))
|
|
260
|
+
monkeypatch.setenv("TMP", str(sandbox_temp_root))
|
|
261
|
+
monkeypatch.setenv("TMPDIR", str(sandbox_temp_root))
|
|
262
|
+
assert _escapes_temporary_root("..") is True
|
|
263
|
+
assert _escapes_temporary_root("scripts") is False
|
|
264
|
+
assert _escapes_temporary_root("config") is False
|
|
265
|
+
absolute_part = str(Path(tempfile.gettempdir()).resolve().anchor) or "/"
|
|
266
|
+
assert _escapes_temporary_root(absolute_part) is True
|
|
267
|
+
|
|
110
268
|
|
|
111
269
|
class TestCliModeRegression:
|
|
112
270
|
def test_cli_mode_reports_violations_and_exits_one(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-dev-env",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"description": "Claude Code development standards — rules, hooks, agents, commands, and skills",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -21,7 +21,15 @@
|
|
|
21
21
|
"scripts/",
|
|
22
22
|
"_shared/",
|
|
23
23
|
"audit-rubrics/",
|
|
24
|
-
"CLAUDE.md"
|
|
24
|
+
"CLAUDE.md",
|
|
25
|
+
"!**/__pycache__/**",
|
|
26
|
+
"!**/*.py[cod]",
|
|
27
|
+
"!**/.pytest_cache/**",
|
|
28
|
+
"!**/.mypy_cache/**",
|
|
29
|
+
"!**/.ruff_cache/**",
|
|
30
|
+
"!**/*.log",
|
|
31
|
+
"!**/debug-*.log",
|
|
32
|
+
"!**/*.egg-info/**"
|
|
25
33
|
],
|
|
26
34
|
"keywords": [
|
|
27
35
|
"claude-code",
|
package/rules/CLAUDE.md
CHANGED
|
@@ -51,6 +51,7 @@ Rule files installed into `~/.claude/rules/` by `bin/install.mjs`. A rule withou
|
|
|
51
51
|
| `right-sized-engineering.md` | Simple over clever; functions over classes; concrete over abstract |
|
|
52
52
|
| `self-contained-docs.md` | Every document is fully self-contained; no references to the conversation that produced it |
|
|
53
53
|
| `shell-invocation-policy.md` | All Windows shell commands use `pwsh`; `Audit-ShellPolicy.ps1` reports the non-`pwsh` forms in the `settings.json` permission rules and `Migrate-ShellPolicy.ps1` rewrites them to `pwsh`, both run on demand, not as a live gate |
|
|
54
|
+
| `state-what-is.md` | Assert claims, decisions, and actions directly; replace exclusion (naming what a claim is not) and elimination (walking rejected candidates) with the claim or answer itself |
|
|
54
55
|
| `tdd.md` | Test-driven development: red → green → refactor, no production code before a failing test |
|
|
55
56
|
| `testing.md` | Test quality and infrastructure standards |
|
|
56
57
|
| `vault-context.md` | Search Obsidian vault for prior sessions and decisions before substantive project work |
|
|
@@ -1,67 +1,45 @@
|
|
|
1
|
-
---
|
|
2
|
-
paths:
|
|
3
|
-
- "**/*.py"
|
|
4
|
-
- "**/*.md"
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Docstring Prose Matches Implementation
|
|
8
|
-
|
|
9
|
-
**When this applies:** Any Write or Edit to a public function, method, class, or module whose docstring prose makes an enumerable claim about behavior — a list of inputs the code handles, the conditions it treats as a match, the cases it skips, or the order of its steps. It applies equally to a skill's companion `SKILL.md` (or any sibling `.md`) that describes a producer the skill's `scripts/` carry out: a doc sentence that claims a produced artifact's ordering or content is the prose this rule governs, and it tracks the producer function's own docstring and body.
|
|
10
|
-
|
|
11
|
-
## Rule
|
|
12
|
-
|
|
13
|
-
When a docstring enumerates the behaviors a body applies, the enumeration covers every behavior the body applies. A reader trusts the list to be complete: an item the code applies but the prose omits is a silent gap that misleads every future reader and reviewer.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
##
|
|
18
|
-
|
|
19
|
-
Read the body and the docstring side by side:
|
|
20
|
-
|
|
21
|
-
- **
|
|
22
|
-
- **
|
|
23
|
-
- **
|
|
24
|
-
- **
|
|
25
|
-
- **
|
|
26
|
-
- **
|
|
27
|
-
- **
|
|
28
|
-
- **
|
|
29
|
-
- **
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
A `@dataclass` dead-field check builds its set of "field counts as read" sources by union:
|
|
47
|
-
|
|
48
|
-
```python
|
|
49
|
-
read_names = (
|
|
50
|
-
attribute_read_names
|
|
51
|
-
| dynamic_literal_names
|
|
52
|
-
| _match_pattern_attribute_names(tree)
|
|
53
|
-
| _exported_names(tree)
|
|
54
|
-
)
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
A docstring that enumerates "attribute read, augmented-assignment target, class-pattern keyword, literal `getattr`/`attrgetter`" but omits the `__all__` source (`_exported_names`) is drifted: a field whose name appears in `__all__` is treated as read, and the prose hides that. The fix adds the missing source to the enumeration so the list matches the union.
|
|
58
|
-
|
|
59
|
-
## Enforcement (audit lane)
|
|
60
|
-
|
|
61
|
-
This drift class is sub-bucket **O6** in `packages/claude-dev-env/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md` (free-form `Note:` / `Returns:` / responsibility-list claims). The audit teammate lists every prose enumeration in a changed docstring and verifies each item against the body, and lists every union member / suppressor / step in the body and verifies each appears in the prose. A union member or suppressor in the body that the prose omits is an O6 finding. The single-condition shared-fallback shape of this drift is gated deterministically by `check_docstring_fallback_branch_coverage` (`packages/claude-dev-env/hooks/blocking/code_rules_docstrings.py`); the audit lane covers every O6 shape the gate cannot match.
|
|
62
|
-
|
|
63
|
-
When a changed PR touches a producer function whose ordering or union shifts, the O8 audit lane also reads that skill's companion `SKILL.md` and sibling `.md` docs for any sentence naming the same produced artifact. A doc sentence that claims the artifact is `sorted` or holds `just the at-risk names` while the producer merges prior names and appends without re-sorting is an O8 finding, even when the PR diff never touched the `.md` file — the behavior change orphaned the doc claim.
|
|
64
|
-
|
|
65
|
-
## Why
|
|
66
|
-
|
|
67
|
-
A docstring enumeration earns its place by being trustworthy. A complete list lets a reader reason about the function without scanning the body; a list missing one item is worse than no list, because it asserts completeness it does not have. Naming this standard makes the gap a first-class finding at write time and at audit, rather than a surprise a reader hits months later.
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.py"
|
|
4
|
+
- "**/*.md"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Docstring Prose Matches Implementation
|
|
8
|
+
|
|
9
|
+
**When this applies:** Any Write or Edit to a public function, method, class, or module whose docstring prose makes an enumerable claim about behavior — a list of inputs the code handles, the conditions it treats as a match, the cases it skips, or the order of its steps. It applies equally to a skill's companion `SKILL.md` (or any sibling `.md`) that describes a producer the skill's `scripts/` carry out: a doc sentence that claims a produced artifact's ordering or content is the prose this rule governs, and it tracks the producer function's own docstring and body.
|
|
10
|
+
|
|
11
|
+
## Rule
|
|
12
|
+
|
|
13
|
+
When a docstring enumerates the behaviors a body applies, the enumeration covers every behavior the body applies. A reader trusts the list to be complete: an item the code applies but the prose omits is a silent gap that misleads every future reader and reviewer.
|
|
14
|
+
|
|
15
|
+
When the body changes the set of behaviors it applies, the same edit updates the prose enumeration. The two move together in one commit.
|
|
16
|
+
|
|
17
|
+
## Write-time checks
|
|
18
|
+
|
|
19
|
+
Read the body and the docstring side by side. Apply each check that matches the prose:
|
|
20
|
+
|
|
21
|
+
- **Unions / match sources** — every member of a "what counts" union appears in the prose.
|
|
22
|
+
- **Suppressors / skip lists** — every early-return suppressor appears in the prose.
|
|
23
|
+
- **Step order** — named order matches call order; branch-guarded corrective steps are named too.
|
|
24
|
+
- **Shared fallbacks** — every condition that reaches a fallback call is named.
|
|
25
|
+
- **Predicate breadth** — the body accepts only the inputs the prose names.
|
|
26
|
+
- **Exclusion axis** — an exclusion clause keys on the same axis the body classifies on.
|
|
27
|
+
- **Companion docs** — a `SKILL.md` (or sibling) order/content claim matches the producer body.
|
|
28
|
+
- **Gate-outcome status flags** — an outcome routed to a blocker (`blocker = ...; break`) reads as blocked everywhere, never as a bypass.
|
|
29
|
+
- **Returns / Raises / Note claims** — each free-form claim matches the body.
|
|
30
|
+
|
|
31
|
+
Many deterministic shapes of this drift have Write/Edit gates in `packages/claude-dev-env/hooks/blocking/code_rules_docstrings.py` (and the JS/`.mjs` slices in `code_rules_imports_logging.py`). Free-form rest is judgment.
|
|
32
|
+
|
|
33
|
+
## Full standard
|
|
34
|
+
|
|
35
|
+
The full Category O judgment standard — sub-buckets O1–O9, the complete write-time gate inventory, free-form checklists, and worked examples — lives in:
|
|
36
|
+
|
|
37
|
+
`packages/claude-dev-env/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md`
|
|
38
|
+
|
|
39
|
+
## Division of labor
|
|
40
|
+
|
|
41
|
+
| Surface | Role |
|
|
42
|
+
|---|---|
|
|
43
|
+
| **This rule** | Always-on write-time policy and the compact checklist above. |
|
|
44
|
+
| Category O rubric | Single thick source for the full standard (on demand). |
|
|
45
|
+
| Category O prompt | Audit template; points at the rubric for judgment. |
|
|
@@ -26,6 +26,13 @@ Handle the two kinds of content differently:
|
|
|
26
26
|
|
|
27
27
|
Both slash directions count.
|
|
28
28
|
|
|
29
|
+
The worktree and job-scratch entries count as a path when either:
|
|
30
|
+
|
|
31
|
+
- A `/` or `\` sits right before them — a drive-letter path (`C:\Users\me\.claude\worktrees\wt\f.py`), a home path (`~/.claude/worktrees/wt`), or a POSIX absolute path (`/home/me/.claude-editor/jobs/j/log.txt`).
|
|
32
|
+
- A path segment follows them — a relative path that names a child under that directory (`see .claude/worktrees/wt-1/notes.md`, a markdown link target, or `cd .claude/worktrees/wt-199`).
|
|
33
|
+
|
|
34
|
+
With neither anchor the text names the directory rather than something inside it, and it posts — a quoted config constant, a backticked directory name, or a placeholder form such as `.claude/worktrees/<name>`. Un-backticked prose that puts a word immediately after the marker reads the same as a relative path and is blocked; the placeholder form is the documented escape.
|
|
35
|
+
|
|
29
36
|
## Enforcement
|
|
30
37
|
|
|
31
38
|
The `volatile_path_in_post_blocker` PreToolUse hook reads the body of each `gh` post command and each GitHub MCP post call, scans it for these markers, and blocks the post when it finds one. For a `--body-file`, the hook reads the file and scans its contents, so writing the body to a temp file and passing it with `--body-file` stays allowed — what the hook rejects is a volatile path inside the text that gets posted.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.md"
|
|
4
|
+
- "**/*.py"
|
|
5
|
+
- "**/*.mjs"
|
|
6
|
+
- "**/*.js"
|
|
7
|
+
- "**/*.ts"
|
|
8
|
+
- "**/*.ps1"
|
|
9
|
+
- "**/*.sh"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# State What Is
|
|
13
|
+
|
|
14
|
+
Write settled facts, decisions, and actions that are load-bearing for the reader now. Assert each claim, decision, or action directly, and let it stand on its own evidence.
|
|
15
|
+
|
|
16
|
+
A detail earns its place by mattering now, or by being timeless enough to matter for future work or plans. Omit everything else as noise.
|
|
17
|
+
|
|
18
|
+
## Two patterns to replace on sight
|
|
19
|
+
|
|
20
|
+
- **Exclusion** — establishing a claim by naming what it is not, or by listing what is out of bounds. Replace it with the claim itself.
|
|
21
|
+
- **Elimination** — walking rejected candidates (causes, options, paths, explanations) to reach the answer. Replace it with the answer and the evidence that supports it.
|
|
22
|
+
|
|
23
|
+
## Sibling rules
|
|
24
|
+
|
|
25
|
+
This rule sits beside the other prose-quality rules in this directory: [`no-historical-clutter.md`](no-historical-clutter.md) cuts references to old state, [`self-contained-docs.md`](self-contained-docs.md) cuts references to the conversation that produced a doc, [`no-justification-noise.md`](no-justification-noise.md) cuts sentences that only justify a stated choice, and [`plain-language.md`](plain-language.md) swaps heavy words for everyday ones. This rule governs how a claim gets established — directly, not by elimination or exclusion.
|
|
@@ -17,7 +17,7 @@ That situation arises when the verdict fails to cover the current surface even t
|
|
|
17
17
|
|
|
18
18
|
## When the marker is not allowed
|
|
19
19
|
|
|
20
|
-
Every other case runs the verification: spawn the `code-verifier` agent and let the SubagentStop hook mint the verdict. In particular, never use the marker to:
|
|
20
|
+
Every other case runs the verification: spawn the `code-verifier` agent — `model: sonnet`, worker-model routing per [`skills/orchestrator/SKILL.md`](../skills/orchestrator/SKILL.md#workflow-agent-routing); resolver-supplied sonnet-equivalent on third-party hosts — and let the SubagentStop hook mint the verdict. In particular, never use the marker to:
|
|
21
21
|
|
|
22
22
|
- Skip a branch's first verification.
|
|
23
23
|
- Commit or push after any real code change since the last clean verdict — one changed line means a fresh verification.
|
package/scripts/CLAUDE.md
CHANGED
|
@@ -27,6 +27,7 @@ Utility scripts installed into `~/.claude/scripts/` by `bin/install.mjs`. Each s
|
|
|
27
27
|
| `check.ps1` | Runs the full code-quality check suite |
|
|
28
28
|
| `Show-Asset.ps1` | Opens files on screen, sizing each image window to the image's pixel dimensions (scaled to fit the screen); non-image files open in their default application |
|
|
29
29
|
| `Get-SessionAccount.ps1` | Reports which Claude account the current session is actually logged into by comparing `~/.claude.json`'s CLI login against a `CLAUDE_USER_DATA_DIR` desktop profile's `lastKnownAccountUuid`, recovering the desktop account's email from profile storage when the two accounts differ |
|
|
30
|
+
| `Capture-PoolHealth.ps1` | Captures Windows memory pool counters, high-handle processes, and kernel pool tags (via `NtQuerySystemInformation` class 22), prints a threshold verdict with a remediation map, and exits non-zero when any alert threshold fires |
|
|
30
31
|
|
|
31
32
|
## Subdirectories
|
|
32
33
|
|