claude-dev-env 2.12.0 → 2.14.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.md +61 -0
- package/CLAUDE.md +1 -57
- package/_shared/AGENTS.md +15 -0
- package/_shared/CLAUDE.md +1 -15
- package/_shared/advisor/AGENTS.md +22 -0
- package/_shared/advisor/CLAUDE.md +1 -22
- package/_shared/pr-loop/AGENTS.md +27 -0
- package/_shared/pr-loop/CLAUDE.md +1 -27
- package/_shared/pr-loop/scripts/AGENTS.md +35 -0
- package/_shared/pr-loop/scripts/CLAUDE.md +1 -35
- package/_shared/pr-loop/scripts/code_rules_gate.py +2 -3
- package/_shared/pr-loop/scripts/code_rules_gate_parts/AGENTS.md +42 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/CLAUDE.md +1 -42
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/AGENTS.md +26 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +1 -26
- package/_shared/pr-loop/scripts/test_code_rules_gate.py +0 -26
- package/_shared/pr-loop/scripts/tests/AGENTS.md +44 -0
- package/_shared/pr-loop/scripts/tests/CLAUDE.md +1 -44
- package/_shared/process-tree/AGENTS.md +41 -0
- package/_shared/process-tree/CLAUDE.md +1 -41
- package/agents/AGENTS.md +31 -0
- package/agents/CLAUDE.md +1 -32
- package/agents/clean-coder.md +6 -6
- package/agents/pr-description-writer.md +11 -81
- package/agents/test_agent_frontmatter.py +4 -4
- package/audit-rubrics/AGENTS.md +43 -0
- package/audit-rubrics/CLAUDE.md +1 -43
- package/audit-rubrics/category_rubrics/AGENTS.md +37 -0
- package/audit-rubrics/category_rubrics/CLAUDE.md +1 -37
- package/audit-rubrics/prompts/AGENTS.md +37 -0
- package/audit-rubrics/prompts/CLAUDE.md +1 -37
- package/bin/AGENTS.md +112 -0
- package/bin/CLAUDE.md +1 -112
- package/bin/ever-shipped-skills.mjs +1 -0
- package/bin/install-constants.mjs +6 -7
- package/bin/install.mjs +71 -13
- package/bin/install.prune.test.mjs +26 -0
- package/bin/install.settings-defaults.test.mjs +60 -33
- package/bin/install.test.mjs +36 -4
- package/commands/AGENTS.md +23 -0
- package/commands/CLAUDE.md +1 -23
- package/commands/hook-log-extract.md +3 -3
- package/commands/sr-loop.md +9 -5
- package/docs/AGENTS.md +32 -0
- package/docs/CLAUDE.md +1 -33
- package/docs/CODE_RULES.md +1 -1
- package/docs/imagegen.md +22 -0
- package/docs/references/AGENTS.md +17 -0
- package/docs/references/CLAUDE.md +1 -18
- package/hooks/AGENTS.md +31 -0
- package/hooks/CLAUDE.md +1 -31
- package/hooks/_gh_pr_author_swap_utils.py +14 -0
- package/hooks/advisory/AGENTS.md +16 -0
- package/hooks/advisory/CLAUDE.md +1 -16
- package/hooks/blocking/AGENTS.md +123 -0
- package/hooks/blocking/CLAUDE.md +1 -148
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/AGENTS.md +28 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/CLAUDE.md +1 -28
- package/hooks/blocking/code_rules_banned_identifiers.py +1 -1
- package/hooks/blocking/code_rules_dead_module_constant.py +149 -37
- package/hooks/blocking/code_rules_probe_chains.py +6 -2
- package/hooks/blocking/config/AGENTS.md +10 -0
- package/hooks/blocking/config/CLAUDE.md +1 -22
- package/hooks/blocking/conventional_pr_title_gate.py +1 -2
- package/hooks/blocking/gh_pr_author_restore.py +7 -2
- package/hooks/blocking/intent_only_ending_blocker.py +11 -16
- package/hooks/blocking/inventory_intent_records/AGENTS.md +26 -0
- package/hooks/blocking/inventory_intent_records/CLAUDE.md +1 -26
- package/hooks/blocking/package_inventory_stale_blocker_parts/AGENTS.md +26 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/CLAUDE.md +1 -26
- package/hooks/blocking/pii_prevention_blocker_parts/AGENTS.md +24 -0
- package/hooks/blocking/pii_prevention_blocker_parts/CLAUDE.md +1 -24
- package/hooks/blocking/send_user_file_open_locally_blocker.py +1 -1
- package/hooks/blocking/stop_dispatcher.py +4 -4
- package/hooks/blocking/tdd_enforcer_parts/AGENTS.md +30 -0
- package/hooks/blocking/tdd_enforcer_parts/CLAUDE.md +1 -30
- package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +0 -3
- package/hooks/blocking/test_code_rules_enforcer_banned_prefixes.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_dead_module_constant.py +171 -0
- package/hooks/blocking/test_code_rules_enforcer_magic_allowlist.py +1 -1
- package/hooks/blocking/test_gh_pr_author_restore.py +2 -1
- package/hooks/blocking/test_intent_only_ending_blocker.py +4 -0
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +6 -7
- package/hooks/blocking/test_send_user_file_open_locally_blocker.py +4 -3
- package/hooks/blocking/test_shared_stdin_adoption.py +0 -2
- package/hooks/diagnostic/AGENTS.md +43 -0
- package/hooks/diagnostic/CLAUDE.md +1 -43
- package/hooks/diagnostic/hook_log_extractor.py +4 -38
- package/hooks/diagnostic/hook_log_stop_wrapper.py +6 -155
- package/hooks/diagnostic/migrations/AGENTS.md +16 -0
- package/hooks/diagnostic/migrations/CLAUDE.md +1 -16
- package/hooks/diagnostic/queries/AGENTS.md +19 -0
- package/hooks/diagnostic/queries/CLAUDE.md +1 -19
- package/hooks/diagnostic/test_hook_log_extractor.py +10 -23
- package/hooks/diagnostic/test_hook_log_stop_wrapper.py +3 -331
- package/hooks/git-hooks/AGENTS.md +31 -0
- package/hooks/git-hooks/CLAUDE.md +1 -31
- package/hooks/git-hooks/git_hooks_constants/AGENTS.md +21 -0
- package/hooks/git-hooks/git_hooks_constants/CLAUDE.md +1 -21
- package/hooks/git-hooks/git_hooks_constants/__init__.py +4 -12
- package/hooks/git-hooks/pre_push.py +49 -118
- package/hooks/git-hooks/test_pre_push.py +111 -137
- package/hooks/hooks.json +0 -35
- package/hooks/hooks_constants/AGENTS.md +104 -0
- package/hooks/hooks_constants/CLAUDE.md +1 -113
- package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +0 -11
- package/hooks/hooks_constants/convergence_branch_constants.py +0 -1
- package/hooks/hooks_constants/fable_spawn_gate_constants.py +3 -4
- package/hooks/hooks_constants/hook_log_extractor_constants.py +1 -2
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +0 -8
- package/hooks/hooks_constants/send_user_file_open_locally_blocker_constants.py +1 -6
- package/hooks/hooks_constants/stop_dispatcher_constants.py +0 -1
- package/hooks/hooks_constants/task_list_loop_starter_constants.py +4 -3
- package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +1 -26
- package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +0 -5
- package/hooks/hooks_constants/test_stop_dispatcher_constants.py +0 -1
- package/hooks/hooks_constants/working_style_prompt_constants.py +29 -20
- package/hooks/lifecycle/AGENTS.md +18 -0
- package/hooks/lifecycle/CLAUDE.md +1 -20
- package/hooks/observability/AGENTS.md +20 -0
- package/hooks/observability/CLAUDE.md +1 -20
- package/hooks/session/AGENTS.md +33 -0
- package/hooks/session/CLAUDE.md +1 -33
- package/hooks/session/gh_pr_author_session_cleanup.py +7 -2
- package/hooks/session/task_list_loop_starter.py +3 -3
- package/hooks/session/test_gh_pr_author_session_cleanup.py +2 -1
- package/hooks/session/test_task_list_loop_starter.py +10 -2
- package/hooks/session/test_working_style_prompt.py +9 -4
- package/hooks/validation/AGENTS.md +20 -0
- package/hooks/validation/CLAUDE.md +1 -20
- package/hooks/validators/AGENTS.md +51 -0
- package/hooks/validators/CLAUDE.md +1 -51
- package/hooks/validators/health_check.py +52 -19
- package/hooks/validators/python_style_checks.py +1 -1
- package/hooks/validators/test_health_check.py +112 -27
- package/hooks/workflow/AGENTS.md +16 -0
- package/hooks/workflow/CLAUDE.md +1 -16
- package/installable-surfaces.manifest.json +1 -0
- package/output-styles/AGENTS.md +15 -0
- package/output-styles/CLAUDE.md +1 -15
- package/package.json +2 -1
- package/rules/AGENTS.md +61 -0
- package/rules/CLAUDE.md +1 -63
- package/rules/code-standards.md +5 -9
- package/rules/doc-prose-cuts.md +1 -1
- package/rules/gh-cli-conventions.md +0 -1
- package/rules/git-workflow.md +1 -3
- package/scripts/AGENTS.md +62 -0
- package/scripts/CLAUDE.md +1 -63
- package/scripts/_code_review_test_support.py +0 -95
- package/scripts/codex_compat_materializer.py +4 -0
- package/scripts/dev_env_scripts_constants/AGENTS.md +21 -0
- package/scripts/dev_env_scripts_constants/CLAUDE.md +1 -21
- package/scripts/dev_env_scripts_constants/code_review_constants.py +9 -98
- package/scripts/dev_env_scripts_constants/test_code_review_constants.py +1 -44
- package/scripts/invoke_code_review.py +8 -451
- package/scripts/sync_to_cursor/AGENTS.md +23 -0
- package/scripts/sync_to_cursor/CLAUDE.md +1 -23
- package/scripts/test_invoke_code_review.py +5 -61
- package/scripts/test_invoke_code_review_cli.py +1 -45
- package/scripts/test_invoke_code_review_contract.py +1 -118
- package/scripts/test_validate_instruction_pairs.py +90 -0
- package/scripts/test_verify_installable_package.py +5 -1
- package/scripts/tests/AGENTS.md +33 -0
- package/scripts/tests/CLAUDE.md +1 -33
- package/scripts/tests/test_codex_compat_materializer.py +36 -1
- package/scripts/validate_instruction_pairs.py +183 -0
- package/settings.json +1 -10
- package/skills/AGENTS.md +72 -0
- package/skills/CLAUDE.md +1 -64
- package/skills/_shared/AGENTS.md +44 -0
- package/skills/_shared/CLAUDE.md +1 -44
- package/skills/_shared/advisor/AGENTS.md +9 -0
- package/skills/_shared/advisor/CLAUDE.md +1 -9
- package/skills/_shared/pr-loop/AGENTS.md +50 -0
- package/skills/_shared/pr-loop/CLAUDE.md +1 -50
- package/skills/_shared/pr-loop/prompts/AGENTS.md +9 -0
- package/skills/_shared/pr-loop/prompts/CLAUDE.md +1 -9
- package/skills/_shared/pr-loop/scripts/AGENTS.md +35 -0
- package/skills/_shared/pr-loop/scripts/CLAUDE.md +1 -35
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/AGENTS.md +24 -0
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/CLAUDE.md +1 -24
- package/skills/anthropic-plan/AGENTS.md +34 -0
- package/skills/anthropic-plan/CLAUDE.md +1 -34
- package/skills/anthropic-plan/scripts/AGENTS.md +11 -0
- package/skills/anthropic-plan/scripts/CLAUDE.md +1 -11
- package/skills/anthropic-plan/scripts/anthropic_plan_scripts_constants/AGENTS.md +16 -0
- package/skills/anthropic-plan/scripts/anthropic_plan_scripts_constants/CLAUDE.md +1 -16
- package/skills/anthropic-plan/templates/AGENTS.md +13 -0
- package/skills/anthropic-plan/templates/CLAUDE.md +1 -13
- package/skills/anthropic-plan/workflow/AGENTS.md +14 -0
- package/skills/anthropic-plan/workflow/CLAUDE.md +1 -14
- package/skills/auditing-claude-config/AGENTS.md +21 -0
- package/skills/auditing-claude-config/CLAUDE.md +1 -21
- package/skills/autoconverge/AGENTS.md +36 -0
- package/skills/autoconverge/CLAUDE.md +1 -36
- package/skills/autoconverge/reference/AGENTS.md +16 -0
- package/skills/autoconverge/reference/CLAUDE.md +1 -16
- package/skills/autoconverge/reference/convergence.md +7 -7
- package/skills/autoconverge/reference/gotchas.md +3 -10
- package/skills/autoconverge/workflow/AGENTS.md +26 -0
- package/skills/autoconverge/workflow/CLAUDE.md +1 -26
- package/skills/autoconverge/workflow/autoconverge_report_constants/AGENTS.md +16 -0
- package/skills/autoconverge/workflow/autoconverge_report_constants/CLAUDE.md +1 -16
- package/skills/autoconverge/workflow/converge.contract.test.mjs +53 -1388
- package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +4 -3
- package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +33 -493
- package/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +15 -97
- package/skills/autoconverge/workflow/converge.mjs +201 -238
- package/skills/autoconverge/workflow/converge.precatch.test.mjs +1 -1
- package/skills/bugteam/AGENTS.md +30 -0
- package/skills/bugteam/CLAUDE.md +1 -30
- package/skills/bugteam/CONSTRAINTS.md +1 -1
- package/skills/bugteam/SKILL.md +9 -0
- package/skills/bugteam/reference/AGENTS.md +20 -0
- package/skills/bugteam/reference/CLAUDE.md +1 -20
- package/skills/bugteam/reference/audit-and-teammates.md +10 -6
- package/skills/bugteam/reference/obstacles/AGENTS.md +24 -0
- package/skills/bugteam/reference/obstacles/CLAUDE.md +1 -24
- package/skills/bugteam/scripts/AGENTS.md +30 -0
- package/skills/bugteam/scripts/CLAUDE.md +1 -30
- package/skills/bugteam/scripts/bugteam_scripts_constants/AGENTS.md +18 -0
- package/skills/bugteam/scripts/bugteam_scripts_constants/CLAUDE.md +1 -18
- package/skills/bugteam/test_skill_additions.py +9 -0
- package/skills/closeout/reference/handoff-prompt-template.md +1 -1
- package/skills/codex-review/AGENTS.md +46 -0
- package/skills/codex-review/CLAUDE.md +1 -46
- package/skills/codex-review/reference/AGENTS.md +15 -0
- package/skills/codex-review/reference/CLAUDE.md +1 -15
- package/skills/codex-review/scripts/codex_review_scripts_constants/AGENTS.md +18 -0
- package/skills/codex-review/scripts/codex_review_scripts_constants/CLAUDE.md +1 -18
- package/skills/codex-review/test_skill_scaffold.py +3 -3
- package/skills/comments/SKILL.md +65 -0
- package/skills/copilot-review/AGENTS.md +18 -0
- package/skills/copilot-review/CLAUDE.md +1 -18
- package/skills/descriptions/SKILL.md +51 -0
- package/skills/emergencies/SKILL.md +42 -0
- package/skills/everything-search/AGENTS.md +17 -0
- package/skills/everything-search/CLAUDE.md +1 -17
- package/skills/fresh-branch/AGENTS.md +14 -0
- package/skills/fresh-branch/CLAUDE.md +1 -14
- package/skills/grok-spawn/AGENTS.md +28 -0
- package/skills/grok-spawn/CLAUDE.md +1 -28
- package/skills/orchestrator/SKILL.md +2 -4
- package/skills/plan-to-pr/SKILL.md +6 -7
- package/skills/plan-to-pr/reference/final-validation-tasks.md +1 -1
- package/skills/plan-to-pr/reference/process-inventory.md +2 -2
- package/skills/plan-to-pr/reference/review-loop.md +2 -2
- package/skills/plan-to-pr/reference/run-record.schema.json +1 -1
- package/skills/plan-to-pr/reference/task-seeds.md +1 -1
- package/skills/plan-to-pr/reference/task-ticket.md +3 -4
- package/skills/plan-to-pr/scripts/config/constants.py +1 -2
- package/skills/plan-to-pr/scripts/test_validate_protocol.py +1 -1
- package/skills/plan-to-pr/test_skill_contract.py +2 -2
- package/skills/pr-converge/AGENTS.md +32 -0
- package/skills/pr-converge/CLAUDE.md +1 -32
- package/skills/pr-converge/SKILL.md +13 -16
- package/skills/pr-converge/pr_converge_skill_constants/AGENTS.md +26 -0
- package/skills/pr-converge/pr_converge_skill_constants/CLAUDE.md +1 -26
- package/skills/pr-converge/reference/AGENTS.md +28 -0
- package/skills/pr-converge/reference/CLAUDE.md +1 -28
- package/skills/pr-converge/reference/obstacles/AGENTS.md +23 -0
- package/skills/pr-converge/reference/obstacles/CLAUDE.md +1 -23
- package/skills/pr-converge/reference/progress-checklist.md +6 -5
- package/skills/pr-converge/reference/state-schema.md +1 -27
- package/skills/pr-converge/scripts/AGENTS.md +46 -0
- package/skills/pr-converge/scripts/CLAUDE.md +1 -46
- package/skills/pr-converge/scripts/pr_converge_scripts_constants/AGENTS.md +18 -0
- package/skills/pr-converge/scripts/pr_converge_scripts_constants/CLAUDE.md +1 -18
- package/skills/pr-converge/workflows/AGENTS.md +16 -0
- package/skills/pr-converge/workflows/CLAUDE.md +1 -16
- package/skills/pr-loop-cloud-transport/SKILL.md +1 -1
- package/skills/pr-loop-cloud-transport/reference/identity-and-hooks.md +3 -3
- package/skills/pr-loop-lifecycle/SKILL.md +1 -1
- package/skills/pr-loop-lifecycle/reference/teardown-publish-permissions.md +1 -1
- package/skills/prototype/SKILL.md +4 -4
- package/skills/prototype/reference/honest-limitations.md +5 -5
- package/skills/prototype/reference/promotion-tasks.md +2 -2
- package/skills/prototype/workflows/promotion.md +4 -6
- package/skills/rebase/AGENTS.md +32 -0
- package/skills/rebase/CLAUDE.md +1 -32
- package/skills/recall/AGENTS.md +30 -0
- package/skills/recall/CLAUDE.md +1 -30
- package/skills/remember/AGENTS.md +31 -0
- package/skills/remember/CLAUDE.md +1 -31
- package/skills/reviews/SKILL.md +85 -0
- package/skills/session-log/AGENTS.md +32 -0
- package/skills/session-log/CLAUDE.md +1 -32
- package/skills/session-tidy/AGENTS.md +36 -0
- package/skills/session-tidy/CLAUDE.md +1 -36
- package/skills/skill-builder/AGENTS.md +49 -0
- package/skills/skill-builder/CLAUDE.md +1 -49
- package/skills/skill-builder/references/AGENTS.md +25 -0
- package/skills/skill-builder/references/CLAUDE.md +1 -25
- package/skills/skill-builder/references/deterministic-elements.md +1 -1
- package/skills/skill-builder/templates/AGENTS.md +14 -0
- package/skills/skill-builder/templates/CLAUDE.md +1 -14
- package/skills/skill-builder/workflows/AGENTS.md +19 -0
- package/skills/skill-builder/workflows/CLAUDE.md +1 -19
- package/skills/small-cl/SKILL.md +52 -0
- package/skills/task-build/AGENTS.md +29 -0
- package/skills/task-build/CLAUDE.md +1 -29
- package/skills/update/AGENTS.md +38 -0
- package/skills/update/CLAUDE.md +1 -38
- package/system-prompts/AGENTS.md +17 -0
- package/system-prompts/CLAUDE.md +1 -17
- package/system-prompts/software-engineer.xml +3 -3
- package/agents/code-verifier.md +0 -74
- package/docs/PR_DESCRIPTION_GUIDE.md +0 -157
- package/docs/references/code-review-enforcement.md +0 -132
- package/hooks/blocking/code_review_enforcement_config_bootstrap.py +0 -53
- package/hooks/blocking/code_review_gate_deny.py +0 -74
- package/hooks/blocking/code_review_pr_create_gate.py +0 -198
- package/hooks/blocking/code_review_push_gate.py +0 -145
- package/hooks/blocking/code_review_stamp_directory_write_blocker.py +0 -348
- package/hooks/blocking/code_review_stamp_store.py +0 -233
- package/hooks/blocking/code_review_stamp_write_blocker_parts/__init__.py +0 -7
- package/hooks/blocking/code_review_stamp_write_blocker_parts/conftest.py +0 -15
- package/hooks/blocking/code_review_stamp_write_blocker_parts/obfuscated_stamp_path_reference.py +0 -212
- package/hooks/blocking/code_review_stamp_write_blocker_parts/split_directory_change_into_stamp.py +0 -138
- package/hooks/blocking/code_review_stamp_write_blocker_parts/test_obfuscated_stamp_path_reference.py +0 -49
- package/hooks/blocking/code_review_stamp_write_blocker_parts/test_split_directory_change_into_stamp.py +0 -38
- package/hooks/blocking/code_verifier_spawn_preflight_gate.py +0 -483
- package/hooks/blocking/config/code_review_enforcement_constants.py +0 -143
- package/hooks/blocking/config/test_code_review_enforcement_constants.py +0 -166
- package/hooks/blocking/config/verified_commit_constants.py +0 -160
- package/hooks/blocking/config/verified_commit_context_constants.py +0 -21
- package/hooks/blocking/config/verified_commit_gate_output_constants.py +0 -14
- package/hooks/blocking/conftest.py +0 -123
- package/hooks/blocking/convergence_gate_blocker.py +0 -310
- package/hooks/blocking/pr_converge_bugteam_enforcer.py +0 -170
- package/hooks/blocking/pr_description_body_audit.py +0 -148
- package/hooks/blocking/pr_description_command_parser.py +0 -237
- package/hooks/blocking/pr_description_enforcer.py +0 -227
- package/hooks/blocking/pr_description_pr_number.py +0 -155
- package/hooks/blocking/pr_description_proof_of_work.py +0 -385
- package/hooks/blocking/pr_description_readability.py +0 -364
- package/hooks/blocking/reviewer_spawn_gate.py +0 -182
- package/hooks/blocking/test_code_review_enforcement_config_bootstrap.py +0 -62
- package/hooks/blocking/test_code_review_gate_deny.py +0 -54
- package/hooks/blocking/test_code_review_pr_create_gate.py +0 -199
- package/hooks/blocking/test_code_review_push_gate.py +0 -205
- package/hooks/blocking/test_code_review_stamp_directory_write_blocker.py +0 -199
- package/hooks/blocking/test_code_review_stamp_store.py +0 -205
- package/hooks/blocking/test_code_verifier_spawn_preflight_gate.py +0 -710
- package/hooks/blocking/test_code_verifier_tools_contract.py +0 -28
- package/hooks/blocking/test_convergence_gate_blocker.py +0 -428
- package/hooks/blocking/test_pr_converge_bugteam_enforcer.py +0 -311
- package/hooks/blocking/test_pr_converge_bugteam_enforcer_state_tolerance.py +0 -184
- package/hooks/blocking/test_pr_description_enforcer.py +0 -221
- package/hooks/blocking/test_pr_description_enforcer_body_audit.py +0 -247
- package/hooks/blocking/test_pr_description_enforcer_body_rules.py +0 -493
- package/hooks/blocking/test_pr_description_enforcer_command_parser.py +0 -366
- package/hooks/blocking/test_pr_description_enforcer_pr_number.py +0 -159
- package/hooks/blocking/test_pr_description_enforcer_proof_gate.py +0 -175
- package/hooks/blocking/test_pr_description_enforcer_readability.py +0 -443
- package/hooks/blocking/test_pr_description_proof_of_work.py +0 -313
- package/hooks/blocking/test_reviewer_spawn_gate.py +0 -230
- package/hooks/blocking/test_verdict_directory_write_blocker.py +0 -804
- package/hooks/blocking/test_verification_verdict_store.py +0 -974
- package/hooks/blocking/test_verified_commit_config_bootstrap.py +0 -67
- package/hooks/blocking/test_verified_commit_docs_delta.py +0 -176
- package/hooks/blocking/test_verified_commit_gate.py +0 -581
- package/hooks/blocking/test_verified_commit_gate_additional_context.py +0 -134
- package/hooks/blocking/test_verified_commit_message_accuracy_blocker.py +0 -131
- package/hooks/blocking/test_verifier_verdict_minter.py +0 -299
- package/hooks/blocking/tests/test_verified_commit_gate.py +0 -41
- package/hooks/blocking/verdict_directory_write_blocker.py +0 -687
- package/hooks/blocking/verification_verdict_store.py +0 -1039
- package/hooks/blocking/verified_commit_config_bootstrap.py +0 -63
- package/hooks/blocking/verified_commit_gate.py +0 -113
- package/hooks/blocking/verified_commit_gate_parts/CLAUDE.md +0 -28
- package/hooks/blocking/verified_commit_gate_parts/__init__.py +0 -1
- package/hooks/blocking/verified_commit_gate_parts/command_tokenization.py +0 -174
- package/hooks/blocking/verified_commit_gate_parts/deny_payload.py +0 -53
- package/hooks/blocking/verified_commit_gate_parts/deny_reason.py +0 -80
- package/hooks/blocking/verified_commit_gate_parts/directory_resolution.py +0 -170
- package/hooks/blocking/verified_commit_gate_parts/gated_invocations.py +0 -217
- package/hooks/blocking/verified_commit_gate_parts/tests/conftest.py +0 -10
- package/hooks/blocking/verified_commit_gate_parts/tests/test_command_tokenization.py +0 -94
- package/hooks/blocking/verified_commit_gate_parts/tests/test_deny_payload.py +0 -17
- package/hooks/blocking/verified_commit_gate_parts/tests/test_deny_reason.py +0 -38
- package/hooks/blocking/verified_commit_gate_parts/tests/test_directory_resolution.py +0 -71
- package/hooks/blocking/verified_commit_gate_parts/tests/test_gated_invocations.py +0 -96
- package/hooks/blocking/verified_commit_message_accuracy_blocker.py +0 -167
- package/hooks/blocking/verifier_verdict_minter.py +0 -280
- package/hooks/hooks_constants/code_verifier_spawn_preflight_gate_constants.py +0 -64
- package/hooks/hooks_constants/convergence_gate_blocker_constants.py +0 -53
- package/hooks/hooks_constants/pr_converge_bugteam_enforcer_constants.py +0 -55
- package/hooks/hooks_constants/pr_converge_bugteam_enforcer_state.py +0 -67
- package/hooks/hooks_constants/pr_description_enforcer_constants.py +0 -167
- package/hooks/hooks_constants/pr_description_proof_of_work_constants.py +0 -107
- package/hooks/hooks_constants/reviewer_spawn_gate_constants.py +0 -41
- package/hooks/hooks_constants/test_pr_description_enforcer_constants.py +0 -292
- package/hooks/lifecycle/pr_converge_bugteam_skill_tracker.py +0 -198
- package/hooks/lifecycle/test_pr_converge_bugteam_skill_tracker.py +0 -283
- package/rules/proof-of-work-pr-comments.md +0 -22
- package/rules/verified-commit-gate-skip.md +0 -4
- package/scripts/Show-Asset.ps1 +0 -106
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# hooks/blocking
|
|
2
|
+
|
|
3
|
+
PreToolUse hooks that deny (block) tool calls when a rule is violated. The main enforcer is `code_rules_enforcer.py`, which routes each Write/Edit through a suite of focused check modules. Every other file in this directory is either a standalone blocker, a check module, or a test.
|
|
4
|
+
|
|
5
|
+
## Subdirectory
|
|
6
|
+
|
|
7
|
+
| Directory | Role |
|
|
8
|
+
|---|---|
|
|
9
|
+
| `tdd_enforcer_parts/` | Concern modules the `tdd_enforcer.py` entry hook wires together: path classification, content analysis, candidate-path resolution, freshness, git-tracking restore detection, decisions, and constants |
|
|
10
|
+
| `claude_md_orphan_file_blocker_parts/` | Concern modules the `claude_md_orphan_file_blocker.py` entry hook wires together: reference extraction, subtree scan, scan plan, decision, and constants |
|
|
11
|
+
| `package_inventory_stale_blocker_parts/` | Concern modules the `package_inventory_stale_blocker.py` entry hook wires together: inventory detection, decision, and constants |
|
|
12
|
+
| `inventory_intent_records/` | The shared per-session pending-intent store both inventory blockers read to break the file/row add-order deadlock |
|
|
13
|
+
| `pii_prevention_blocker_parts/` | Concern modules the `pii_prevention_blocker.py` entry hook wires together: per-repository scan exemption, the per-repository allowlist of exact values a commit may carry, and resolving the repository a commit command targets (with `config/` for the resolution deny-message constants) |
|
|
14
|
+
| `tests/` | pytest suite for `pii_prevention_blocker.py` repository resolution and the `pii_prevention_blocker_parts` modules |
|
|
15
|
+
|
|
16
|
+
## Core enforcer
|
|
17
|
+
|
|
18
|
+
| File | What it does |
|
|
19
|
+
|---|---|
|
|
20
|
+
| `code_rules_enforcer.py` | Entry point — reads PreToolUse stdin, reconstructs post-edit content, dispatches all check modules, and returns a block with a per-issue list when any check fails |
|
|
21
|
+
|
|
22
|
+
The check modules it calls are the `code_rules_<concern>.py` files below.
|
|
23
|
+
|
|
24
|
+
## Check modules (imported by `code_rules_enforcer.py`)
|
|
25
|
+
|
|
26
|
+
| Module | Concern |
|
|
27
|
+
|---|---|
|
|
28
|
+
| `code_rules_annotations_length.py` | Parameter/return annotations, function length, pytest fixture annotation requirements |
|
|
29
|
+
| `code_rules_banned_identifiers.py` | Banned short names (`ctx`, `cfg`, `msg`, etc.), banned prefixes (`handle_`, `process_`, etc.) |
|
|
30
|
+
| `code_rules_boolean_mustcheck.py` | Boolean naming (`is_`/`has_`/… prefixes) and must-check return values |
|
|
31
|
+
| `code_rules_command_dispatch.py` | A `hooks/blocking/` command classifier matching a multi-word command regex without a start anchor or first-word tokenization |
|
|
32
|
+
| `code_rules_comments.py` | No new inline comments; advisory on deletion of existing ones |
|
|
33
|
+
| `code_rules_constants_config.py` | Constants must live in `config/`; file-global constant use-count |
|
|
34
|
+
| `code_rules_dead_argparse_argument.py` | Argparse arguments with no references in the same file |
|
|
35
|
+
| `code_rules_dead_config_field.py` | `*Config` / `*Selectors` dataclass fields with no live references |
|
|
36
|
+
| `code_rules_dead_dataclass_field.py` | Dataclass fields with no consuming references |
|
|
37
|
+
| `code_rules_dead_module_constant.py` | `UPPER_SNAKE` constants in `*_constants.py` modules with no importers |
|
|
38
|
+
| `code_rules_dead_split_branch.py` | A conditional whose falsy branch is unreachable because the tested value comes from a separator `str.split()`, which never returns an empty list |
|
|
39
|
+
| `code_rules_docstrings.py` | Google-style docstrings; `Args:` section matches signature; fallback-branch coverage |
|
|
40
|
+
| `code_rules_duplicate_body.py` | A function body copied from a sibling module, or a helper body inlined as a block inside a larger function in the same file |
|
|
41
|
+
| `code_rules_imports_logging.py` | Imports at top of file; logging format-arg style; printf tokens in `str.format`-logger messages |
|
|
42
|
+
| `code_rules_js_conventions.py` | Boolean-prefix naming and banned identifiers for JavaScript/TypeScript declarations and `@param {boolean}` JSDoc, scoped to changed lines |
|
|
43
|
+
| `code_rules_magic_values.py` | No magic numbers or strings in production code bodies |
|
|
44
|
+
| `code_rules_mock_completeness.py` | Mock calls that skip required arguments |
|
|
45
|
+
| `code_rules_naming_collection.py` | Collection names must use `all_*` prefix |
|
|
46
|
+
| `code_rules_optional_params.py` | No optional parameters where a required one would do |
|
|
47
|
+
| `code_rules_orphan_css_class.py` | CSS class attributes in Python markup with no matching `.<class>` selector |
|
|
48
|
+
| `code_rules_paired_test.py` | A public function omitted by a module's established paired test suite must get a behavioral test — checked on both the production-module write and the stem-matched test-file write |
|
|
49
|
+
| `code_rules_path_utils.py` | Path utility helpers shared across check modules |
|
|
50
|
+
| `code_rules_paths_syspath.py` | `sys.path.insert` must be guarded |
|
|
51
|
+
| `code_rules_probe_chains.py` | Probe-chain detection logic |
|
|
52
|
+
| `code_rules_probe_detection.py` | Probe pattern detection helpers |
|
|
53
|
+
| `code_rules_probe_recording.py` | Probe recording utilities |
|
|
54
|
+
| `code_rules_scope_binding.py` | Scope/binding analysis utilities |
|
|
55
|
+
| `code_rules_shared.py` | Shared dataclasses and helpers used by multiple check modules |
|
|
56
|
+
| `code_rules_string_magic.py` | Magic string detection with masking and f-string support; whitespace-only indentation literals in function bodies |
|
|
57
|
+
| `code_rules_test_assertions.py` | Test assertion style rules |
|
|
58
|
+
| `code_rules_test_layout.py` | Dead scaffolding in a test module: a private module constant read by no other line, and an unused parameter on a private test helper |
|
|
59
|
+
| `code_rules_test_branching_except.py` | No bare or broad `except` in test branches |
|
|
60
|
+
| `code_rules_test_isolation.py` | Tests must not rely on home-dir or temp-dir side effects |
|
|
61
|
+
| `code_rules_type_escape.py` | No `Any` imports, `cast()`, or `# type: ignore` outside boundary files |
|
|
62
|
+
| `code_rules_typeddict_stub.py` | TypedDict pairs (`_encode_*`/`_decode_*`) must both exist in the same module |
|
|
63
|
+
| `code_rules_unused_imports.py` | Unused module-level imports |
|
|
64
|
+
|
|
65
|
+
## Other standalone blockers
|
|
66
|
+
|
|
67
|
+
| File | Event | What it blocks |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| `orchestrator_refresh_reschedule_gate.py` | PreToolUse (ScheduleWakeup/CronCreate) | `/orchestrator-refresh` re-arm when run status is not `active` |
|
|
70
|
+
| `block_main_commit.py` | PreToolUse (Bash) | `git commit`/`git push` directly to `main` |
|
|
71
|
+
| `bot_mention_comment_blocker.py` | PreToolUse (Write/Edit) | PR review comments that @-mention a bot |
|
|
72
|
+
| `claude_md_orphan_file_blocker.py` | PreToolUse (Write/Edit/MultiEdit) | Per-directory `CLAUDE.md` table cells naming a bare filename absent from the directory subtree |
|
|
73
|
+
| `conventional_pr_title_gate.py` | PreToolUse (Bash) | `gh pr create`/`gh pr edit` with a `--title` that is not a Conventional Commit, in a repo whose CI runs a semantic-pull-request title check |
|
|
74
|
+
| `destructive_command_blocker.py` | PreToolUse (Bash/PowerShell) | Shell commands with destructive literals (`rm -rf`, `git reset --hard`, etc.) |
|
|
75
|
+
| `docstring_rule_gate_count_blocker.py` | PreToolUse (Write/Edit/MultiEdit) | A stale spelled-out gate-validator count in `docstring-prose-matches-implementation.md` — the "N more gate validators" / "M gated slices" count drifting from the `check_docstring_*` validators the prose names |
|
|
76
|
+
| `duplicate_rmtree_helper_blocker.py` | PreToolUse (Write/Edit) | A local re-definition of the Windows-safe rmtree helper trio (`_strip_read_only_and_retry`, `_force_remove_tree` / `force_rmtree`) in place of importing a shared helper |
|
|
77
|
+
| `eli11_reply_enforcer.py` | Stop | Final replies breaking the `eli11-replies` shape — more than 6 bullet lines, more than 2 list lines over 20 words each, or multi-line instructions with no numbered step among the lead lines. Code fences, inline code, blockquotes, table rows, and link targets come off before the reply is judged. |
|
|
78
|
+
| `env_var_table_code_drift_blocker.py` | PreToolUse (Write/Edit/MultiEdit) | A markdown env-var summary table row attributing an environment variable to a code file whose source never references that variable name |
|
|
79
|
+
| `es_exe_path_rewriter.py` | PreToolUse | Rewrites paths referencing `.exe` under the Everything search path |
|
|
80
|
+
| `fable_spawn_gate.py` | PreToolUse (Agent/Task) | An `Agent` or `Task` spawn whose prompt carries no `FABLE-SPAWN-AUTHORIZED` token and whose model field reads `fable` in any letter case — the bare alias, or a delimiter segment of a full model id, so `claude-fable-5` is denied too |
|
|
81
|
+
| `gh_body_arg_blocker.py` | PreToolUse (Bash) | `gh` commands passing `--body`/`-b` directly (requires `--body-file` instead) |
|
|
82
|
+
| `gh_pr_author_enforcer.py` | PreToolUse | Enforces PR author identity rules |
|
|
83
|
+
| `gh_pr_author_restore.py` | PostToolUse | Restores PR author after a tool call |
|
|
84
|
+
| `hedging_language_blocker.py` | Stop | Responses with hedging words (`likely`, `probably`, `appears to`); armed only when `CLAUDE_PROSE_STYLE_ENFORCEMENT` is on (default off) |
|
|
85
|
+
| `hook_prose_detector_consistency.py` | PreToolUse (Write/Edit) | Hook docstrings/messages that claim a trigger the detector cannot fire on; armed only when `CLAUDE_PROSE_STYLE_ENFORCEMENT` is on (default off) |
|
|
86
|
+
| `intent_only_ending_blocker.py` | Stop | Responses that end on a plan or intent without doing the work; armed only when `CLAUDE_PROSE_STYLE_ENFORCEMENT` is on (default off) |
|
|
87
|
+
| `open_questions_in_plans_blocker.py` | PreToolUse (Write/Edit) | Plan documents with unresolved open questions |
|
|
88
|
+
| `nas_ssh_binary_enforcer.py` | PreToolUse (Bash) | A bare `ssh`/`scp`/`sftp` command word targeting the NAS (Git Bash's MSYS ssh stalls on an interactive password prompt), or the full `System32/OpenSSH` binary to that host without `-o BatchMode=yes` |
|
|
89
|
+
| `package_inventory_stale_blocker.py` | PreToolUse (Write) | A new production code file created in a directory whose `README.md`/`CLAUDE.md` inventory (or a parent skill's `SKILL.md` Layout table mapping the `scripts/` subdirectory) names two or more sibling files but no entry for the new file |
|
|
90
|
+
| `pii_commit_command.py` | library | Token-aware git-commit detection reused by `pii_prevention_blocker.py` |
|
|
91
|
+
| `pii_payload_scan.py` | library | Write/Edit and durable post-body PII evaluation reused by `pii_prevention_blocker.py` |
|
|
92
|
+
| `pii_prevention_blocker.py` | PreToolUse (Write/Edit/MultiEdit/Bash/PowerShell/MCP GitHub) | Entry hook — content that carries high-confidence personal data or secrets (real emails, home-dir paths, private IPs, credential material) on write, durable GitHub posts, or staged commit paths; resolves the staged-commit repository from the command it gates (via `pii_prevention_blocker_parts`), not the session working directory |
|
|
93
|
+
| `pii_scanner.py` | library | Pure text scanners shared by `pii_prevention_blocker.py` |
|
|
94
|
+
| `piped_pytest_blocker.py` | PreToolUse (Bash) | A pytest run whose output feeds a pipe, where the pipeline reports the exit code of the command on the right |
|
|
95
|
+
| `plain_language_blocker.py` | PreToolUse (Write/Edit/AskUserQuestion) | Heavy or jargon words in user-facing prose when `CLAUDE_PROSE_STYLE_ENFORCEMENT` is on (default off); AskUserQuestion lean-block structure stays always on |
|
|
96
|
+
| `precommit_code_rules_gate.py` | PreToolUse (Bash) | Staged changes that fail the CODE_RULES gate at commit time |
|
|
97
|
+
| `pytest_testpaths_orphan_blocker.py` | PreToolUse (Write/Edit/MultiEdit) | New `test_*.py` files created under a directory absent from a package's explicit pytest `testpaths` allowlist |
|
|
98
|
+
| `question_to_user_enforcer.py` | Stop | User-directed questions not routed through `AskUserQuestion` |
|
|
99
|
+
| `send_user_file_open_locally_blocker.py` | PreToolUse (SendUserFile) | A desk-side file attach (`SendUserFile` with `status` not `proactive`); points to opening the file with its native Windows app |
|
|
100
|
+
| `sensitive_file_protector.py` | PreToolUse (Write/Edit) | Writes to sensitive credential or config files |
|
|
101
|
+
| `session_edit_stage_gate.py` | PreToolUse (Bash) | A `git commit` that would drop files edited this session because they are tracked but left unstaged |
|
|
102
|
+
| `session_handoff_blocker.py` | Stop | Responses suggesting a new session mid-task |
|
|
103
|
+
| `shell_substitution_blocker.py` | PreToolUse (Bash) | A command carrying `$(...)`, a live backtick, or `<(...)`/`>(...)` process substitution, which the allowlist matcher cannot descend into |
|
|
104
|
+
| `stale_comment_reference_blocker.py` | PreToolUse (Edit) | An Edit that rewrites a Python code line while keeping the standalone comment directly above it, when that comment names an identifier the rewrite removes from the line |
|
|
105
|
+
| `state_description_blocker.py` | PreToolUse (Write/Edit) | Historical/comparative language in documentation; armed only when `CLAUDE_PROSE_STYLE_ENFORCEMENT` is on (default off) |
|
|
106
|
+
| `subprocess_budget_completeness.py` | PreToolUse | Subprocess calls missing required budget arguments |
|
|
107
|
+
| `tdd_enforcer.py` | PreToolUse (Write/Edit) | Production code written without a matching failing test |
|
|
108
|
+
| `unscoped_search_blocker.py` | PreToolUse (Bash/PowerShell) | A `find` or recursive listing that walks from the filesystem root, a drive root, bare home, or a network share root |
|
|
109
|
+
| `volatile_path_in_post_blocker.py` | PreToolUse (Bash/MCP GitHub) | `gh` post commands and GitHub MCP post tools whose body references a volatile path (job scratch dir, worktree, or system temp) that outlives the durable post |
|
|
110
|
+
| `windows_rmtree_blocker.py` | PreToolUse (Write/Edit) | `shutil.rmtree` with `ignore_errors=True` on Windows |
|
|
111
|
+
| `workflow_substitution_slot_blocker.py` | PreToolUse (Write/Edit) | Workflow templates with bare per-iteration tokens missing angle-bracket slots |
|
|
112
|
+
| `write_existing_file_blocker.py` | PreToolUse (Write) | Write to a path where a file already exists |
|
|
113
|
+
|
|
114
|
+
## Supporting modules
|
|
115
|
+
|
|
116
|
+
| File | Role |
|
|
117
|
+
|---|---|
|
|
118
|
+
| `_gh_body_arg_utils.py` | Parsing helpers for `gh_body_arg_blocker.py` |
|
|
119
|
+
|
|
120
|
+
## Conventions
|
|
121
|
+
|
|
122
|
+
- Tests live beside each hook as `test_<hookname>.py` or `test_<hookname>_<suffix>.py`. Run with `python -m pytest <test_file>`.
|
|
123
|
+
- Tunable constants live in `hooks_constants/<hook_name>_constants.py`.
|
package/hooks/blocking/CLAUDE.md
CHANGED
|
@@ -1,148 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
PreToolUse hooks that deny (block) tool calls when a rule is violated. The main enforcer is `code_rules_enforcer.py`, which routes each Write/Edit through a suite of focused check modules. Every other file in this directory is either a standalone blocker, a check module, or a test.
|
|
4
|
-
|
|
5
|
-
## Subdirectory
|
|
6
|
-
|
|
7
|
-
| Directory | Role |
|
|
8
|
-
|---|---|
|
|
9
|
-
| `config/` | Shared constants for the verified-commit gate family (`verified_commit_constants.py`) |
|
|
10
|
-
| `tdd_enforcer_parts/` | Concern modules the `tdd_enforcer.py` entry hook wires together: path classification, content analysis, candidate-path resolution, freshness, git-tracking restore detection, decisions, and constants |
|
|
11
|
-
| `verified_commit_gate_parts/` | Concern modules the `verified_commit_gate.py` entry hook wires together: command tokenization, directory-change resolution, gated git-invocation resolution, deny-reason resolution, and deny-payload assembly |
|
|
12
|
-
| `code_review_stamp_write_blocker_parts/` | Concern modules the `code_review_stamp_directory_write_blocker.py` entry hook wires together: the split directory-change-into-stamp matcher and the obfuscated-stamp-path-write matcher |
|
|
13
|
-
| `claude_md_orphan_file_blocker_parts/` | Concern modules the `claude_md_orphan_file_blocker.py` entry hook wires together: reference extraction, subtree scan, scan plan, decision, and constants |
|
|
14
|
-
| `package_inventory_stale_blocker_parts/` | Concern modules the `package_inventory_stale_blocker.py` entry hook wires together: inventory detection, decision, and constants |
|
|
15
|
-
| `inventory_intent_records/` | The shared per-session pending-intent store both inventory blockers read to break the file/row add-order deadlock |
|
|
16
|
-
| `pii_prevention_blocker_parts/` | Concern modules the `pii_prevention_blocker.py` entry hook wires together: per-repository scan exemption, the per-repository allowlist of exact values a commit may carry, and resolving the repository a commit command targets (with `config/` for the resolution deny-message constants) |
|
|
17
|
-
| `tests/` | pytest suite for `pii_prevention_blocker.py` repository resolution and the `pii_prevention_blocker_parts` modules |
|
|
18
|
-
|
|
19
|
-
## Core enforcer
|
|
20
|
-
|
|
21
|
-
| File | What it does |
|
|
22
|
-
|---|---|
|
|
23
|
-
| `code_rules_enforcer.py` | Entry point — reads PreToolUse stdin, reconstructs post-edit content, dispatches all check modules, and returns a block with a per-issue list when any check fails |
|
|
24
|
-
|
|
25
|
-
The check modules it calls are the `code_rules_<concern>.py` files below.
|
|
26
|
-
|
|
27
|
-
## Check modules (imported by `code_rules_enforcer.py`)
|
|
28
|
-
|
|
29
|
-
| Module | Concern |
|
|
30
|
-
|---|---|
|
|
31
|
-
| `code_rules_annotations_length.py` | Parameter/return annotations, function length, pytest fixture annotation requirements |
|
|
32
|
-
| `code_rules_banned_identifiers.py` | Banned short names (`ctx`, `cfg`, `msg`, etc.), banned prefixes (`handle_`, `process_`, etc.) |
|
|
33
|
-
| `code_rules_boolean_mustcheck.py` | Boolean naming (`is_`/`has_`/… prefixes) and must-check return values |
|
|
34
|
-
| `code_rules_command_dispatch.py` | A `hooks/blocking/` command classifier matching a multi-word command regex without a start anchor or first-word tokenization |
|
|
35
|
-
| `code_rules_comments.py` | No new inline comments; advisory on deletion of existing ones |
|
|
36
|
-
| `code_rules_constants_config.py` | Constants must live in `config/`; file-global constant use-count |
|
|
37
|
-
| `code_rules_dead_argparse_argument.py` | Argparse arguments with no references in the same file |
|
|
38
|
-
| `code_rules_dead_config_field.py` | `*Config` / `*Selectors` dataclass fields with no live references |
|
|
39
|
-
| `code_rules_dead_dataclass_field.py` | Dataclass fields with no consuming references |
|
|
40
|
-
| `code_rules_dead_module_constant.py` | `UPPER_SNAKE` constants in `*_constants.py` modules with no importers |
|
|
41
|
-
| `code_rules_dead_split_branch.py` | A conditional whose falsy branch is unreachable because the tested value comes from a separator `str.split()`, which never returns an empty list |
|
|
42
|
-
| `code_rules_docstrings.py` | Google-style docstrings; `Args:` section matches signature; fallback-branch coverage |
|
|
43
|
-
| `code_rules_duplicate_body.py` | A function body copied from a sibling module, or a helper body inlined as a block inside a larger function in the same file |
|
|
44
|
-
| `code_rules_imports_logging.py` | Imports at top of file; logging format-arg style; printf tokens in `str.format`-logger messages |
|
|
45
|
-
| `code_rules_js_conventions.py` | Boolean-prefix naming and banned identifiers for JavaScript/TypeScript declarations and `@param {boolean}` JSDoc, scoped to changed lines |
|
|
46
|
-
| `code_rules_magic_values.py` | No magic numbers or strings in production code bodies |
|
|
47
|
-
| `code_rules_mock_completeness.py` | Mock calls that skip required arguments |
|
|
48
|
-
| `code_rules_naming_collection.py` | Collection names must use `all_*` prefix |
|
|
49
|
-
| `code_rules_optional_params.py` | No optional parameters where a required one would do |
|
|
50
|
-
| `code_rules_orphan_css_class.py` | CSS class attributes in Python markup with no matching `.<class>` selector |
|
|
51
|
-
| `code_rules_paired_test.py` | A public function omitted by a module's established paired test suite must get a behavioral test — checked on both the production-module write and the stem-matched test-file write |
|
|
52
|
-
| `code_rules_path_utils.py` | Path utility helpers shared across check modules |
|
|
53
|
-
| `code_rules_paths_syspath.py` | `sys.path.insert` must be guarded |
|
|
54
|
-
| `code_rules_probe_chains.py` | Probe-chain detection logic |
|
|
55
|
-
| `code_rules_probe_detection.py` | Probe pattern detection helpers |
|
|
56
|
-
| `code_rules_probe_recording.py` | Probe recording utilities |
|
|
57
|
-
| `code_rules_scope_binding.py` | Scope/binding analysis utilities |
|
|
58
|
-
| `code_rules_shared.py` | Shared dataclasses and helpers used by multiple check modules |
|
|
59
|
-
| `code_rules_string_magic.py` | Magic string detection with masking and f-string support; whitespace-only indentation literals in function bodies |
|
|
60
|
-
| `code_rules_test_assertions.py` | Test assertion style rules |
|
|
61
|
-
| `code_rules_test_layout.py` | Dead scaffolding in a test module: a private module constant read by no other line, and an unused parameter on a private test helper |
|
|
62
|
-
| `code_rules_test_branching_except.py` | No bare or broad `except` in test branches |
|
|
63
|
-
| `code_rules_test_isolation.py` | Tests must not rely on home-dir or temp-dir side effects |
|
|
64
|
-
| `code_rules_type_escape.py` | No `Any` imports, `cast()`, or `# type: ignore` outside boundary files |
|
|
65
|
-
| `code_rules_typeddict_stub.py` | TypedDict pairs (`_encode_*`/`_decode_*`) must both exist in the same module |
|
|
66
|
-
| `code_rules_unused_imports.py` | Unused module-level imports |
|
|
67
|
-
|
|
68
|
-
## Other standalone blockers
|
|
69
|
-
|
|
70
|
-
| File | Event | What it blocks |
|
|
71
|
-
|---|---|---|
|
|
72
|
-
| `orchestrator_refresh_reschedule_gate.py` | PreToolUse (ScheduleWakeup/CronCreate) | `/orchestrator-refresh` re-arm when run status is not `active` |
|
|
73
|
-
| `block_main_commit.py` | PreToolUse (Bash) | `git commit`/`git push` directly to `main` |
|
|
74
|
-
| `bot_mention_comment_blocker.py` | PreToolUse (Write/Edit) | PR review comments that @-mention a bot |
|
|
75
|
-
| `claude_md_orphan_file_blocker.py` | PreToolUse (Write/Edit/MultiEdit) | Per-directory `CLAUDE.md` table cells naming a bare filename absent from the directory subtree |
|
|
76
|
-
| `code_review_pr_create_gate.py` | PreToolUse (Bash/MCP GitHub) | `gh pr create` or the MCP `create_pull_request` tool without a clean `xhigh` code-review stamp covering the branch surface |
|
|
77
|
-
| `code_review_push_gate.py` | PreToolUse (Bash/PowerShell) | `git push` without a clean `low` code-review stamp covering the branch surface |
|
|
78
|
-
| `code_review_stamp_directory_write_blocker.py` | PreToolUse (Bash/PowerShell/Write/Edit/MultiEdit) | Shell or file-tool writes into `~/.claude/code-review-stamps/`, and shell references to the stamp store module or its mint call, outside the sanctioned invoker |
|
|
79
|
-
| `code_verifier_spawn_preflight_gate.py` | PreToolUse (Agent) | Spawning the `code-verifier` subagent when the branch has a merge conflict vs its base or a CODE_RULES violation on a working-tree-added line, or the CODE_RULES engine fails to load |
|
|
80
|
-
| `convergence_gate_blocker.py` | PreToolUse (Bash) | Convergence workflow actions on a conflicting PR |
|
|
81
|
-
| `conventional_pr_title_gate.py` | PreToolUse (Bash) | `gh pr create`/`gh pr edit` with a `--title` that is not a Conventional Commit, in a repo whose CI runs a semantic-pull-request title check |
|
|
82
|
-
| `destructive_command_blocker.py` | PreToolUse (Bash/PowerShell) | Shell commands with destructive literals (`rm -rf`, `git reset --hard`, etc.) |
|
|
83
|
-
| `docstring_rule_gate_count_blocker.py` | PreToolUse (Write/Edit/MultiEdit) | A stale spelled-out gate-validator count in `docstring-prose-matches-implementation.md` — the "N more gate validators" / "M gated slices" count drifting from the `check_docstring_*` validators the prose names |
|
|
84
|
-
| `duplicate_rmtree_helper_blocker.py` | PreToolUse (Write/Edit) | A local re-definition of the Windows-safe rmtree helper trio (`_strip_read_only_and_retry`, `_force_remove_tree` / `force_rmtree`) in place of importing a shared helper |
|
|
85
|
-
| `eli11_reply_enforcer.py` | Stop | Final replies breaking the `eli11-replies` shape — more than 6 bullet lines, more than 2 list lines over 20 words each, or multi-line instructions with no numbered step among the lead lines. Code fences, inline code, blockquotes, table rows, and link targets come off before the reply is judged. |
|
|
86
|
-
| `env_var_table_code_drift_blocker.py` | PreToolUse (Write/Edit/MultiEdit) | A markdown env-var summary table row attributing an environment variable to a code file whose source never references that variable name |
|
|
87
|
-
| `es_exe_path_rewriter.py` | PreToolUse | Rewrites paths referencing `.exe` under the Everything search path |
|
|
88
|
-
| `fable_spawn_gate.py` | PreToolUse (Agent/Task) | An `Agent` or `Task` spawn whose prompt carries no `FABLE-SPAWN-AUTHORIZED` token and whose model field reads `fable` in any letter case — the bare alias, or a delimiter segment of a full model id, so `claude-fable-5` is denied too |
|
|
89
|
-
| `gh_body_arg_blocker.py` | PreToolUse (Bash) | `gh` commands passing `--body`/`-b` directly (requires `--body-file` instead) |
|
|
90
|
-
| `gh_pr_author_enforcer.py` | PreToolUse | Enforces PR author identity rules |
|
|
91
|
-
| `gh_pr_author_restore.py` | PostToolUse | Restores PR author after a tool call |
|
|
92
|
-
| `hedging_language_blocker.py` | Stop | Responses with hedging words (`likely`, `probably`, `appears to`); armed only when `CLAUDE_PROSE_STYLE_ENFORCEMENT` is on (default off) |
|
|
93
|
-
| `hook_prose_detector_consistency.py` | PreToolUse (Write/Edit) | Hook docstrings/messages that claim a trigger the detector cannot fire on; armed only when `CLAUDE_PROSE_STYLE_ENFORCEMENT` is on (default off) |
|
|
94
|
-
| `intent_only_ending_blocker.py` | Stop | Responses that end on a plan or intent without doing the work; armed only when `CLAUDE_PROSE_STYLE_ENFORCEMENT` is on (default off) |
|
|
95
|
-
| `open_questions_in_plans_blocker.py` | PreToolUse (Write/Edit) | Plan documents with unresolved open questions |
|
|
96
|
-
| `nas_ssh_binary_enforcer.py` | PreToolUse (Bash) | A bare `ssh`/`scp`/`sftp` command word targeting the NAS (Git Bash's MSYS ssh stalls on an interactive password prompt), or the full `System32/OpenSSH` binary to that host without `-o BatchMode=yes` |
|
|
97
|
-
| `package_inventory_stale_blocker.py` | PreToolUse (Write) | A new production code file created in a directory whose `README.md`/`CLAUDE.md` inventory (or a parent skill's `SKILL.md` Layout table mapping the `scripts/` subdirectory) names two or more sibling files but no entry for the new file |
|
|
98
|
-
| `pii_commit_command.py` | library | Token-aware git-commit detection reused by `pii_prevention_blocker.py` |
|
|
99
|
-
| `pii_payload_scan.py` | library | Write/Edit and durable post-body PII evaluation reused by `pii_prevention_blocker.py` |
|
|
100
|
-
| `pii_prevention_blocker.py` | PreToolUse (Write/Edit/MultiEdit/Bash/PowerShell/MCP GitHub) | Entry hook — content that carries high-confidence personal data or secrets (real emails, home-dir paths, private IPs, credential material) on write, durable GitHub posts, or staged commit paths; resolves the staged-commit repository from the command it gates (via `pii_prevention_blocker_parts`), not the session working directory |
|
|
101
|
-
| `pii_scanner.py` | library | Pure text scanners shared by `pii_prevention_blocker.py` |
|
|
102
|
-
| `piped_pytest_blocker.py` | PreToolUse (Bash) | A pytest run whose output feeds a pipe, where the pipeline reports the exit code of the command on the right |
|
|
103
|
-
| `plain_language_blocker.py` | PreToolUse (Write/Edit/AskUserQuestion) | Heavy or jargon words in user-facing prose when `CLAUDE_PROSE_STYLE_ENFORCEMENT` is on (default off); AskUserQuestion lean-block structure stays always on |
|
|
104
|
-
| `pr_converge_bugteam_enforcer.py` | PreToolUse | Enforces that bugteam runs in parallel with bugbot in pr-converge loops |
|
|
105
|
-
| `pr_description_enforcer.py` | PreToolUse (Bash) | `gh pr create`/`edit`/`comment` bodies that fail the Anthropic claude-code style audit, proof-shaped `gh pr comment` bodies missing proof-of-work parts, and `gh pr ready` while the PR carries no passing proof comment |
|
|
106
|
-
| `precommit_code_rules_gate.py` | PreToolUse (Bash) | Staged changes that fail the CODE_RULES gate at commit time |
|
|
107
|
-
| `pytest_testpaths_orphan_blocker.py` | PreToolUse (Write/Edit/MultiEdit) | New `test_*.py` files created under a directory absent from a package's explicit pytest `testpaths` allowlist |
|
|
108
|
-
| `question_to_user_enforcer.py` | Stop | User-directed questions not routed through `AskUserQuestion` |
|
|
109
|
-
| `reviewer_spawn_gate.py` | PreToolUse (Bash) | A sentinel-marked autoconverge reviewer-spawn command (Copilot review request, Bugbot rerun comment) run while `reviewer_availability.py` reports that reviewer down or out of quota |
|
|
110
|
-
| `send_user_file_open_locally_blocker.py` | PreToolUse (SendUserFile) | A desk-side file attach (`SendUserFile` with `status` not `proactive`); points to opening the file locally via `Show-Asset.ps1` |
|
|
111
|
-
| `sensitive_file_protector.py` | PreToolUse (Write/Edit) | Writes to sensitive credential or config files |
|
|
112
|
-
| `session_edit_stage_gate.py` | PreToolUse (Bash) | A `git commit` that would drop files edited this session because they are tracked but left unstaged |
|
|
113
|
-
| `session_handoff_blocker.py` | Stop | Responses suggesting a new session mid-task |
|
|
114
|
-
| `shell_substitution_blocker.py` | PreToolUse (Bash) | A command carrying `$(...)`, a live backtick, or `<(...)`/`>(...)` process substitution, which the allowlist matcher cannot descend into |
|
|
115
|
-
| `stale_comment_reference_blocker.py` | PreToolUse (Edit) | An Edit that rewrites a Python code line while keeping the standalone comment directly above it, when that comment names an identifier the rewrite removes from the line |
|
|
116
|
-
| `state_description_blocker.py` | PreToolUse (Write/Edit) | Historical/comparative language in documentation; armed only when `CLAUDE_PROSE_STYLE_ENFORCEMENT` is on (default off) |
|
|
117
|
-
| `subprocess_budget_completeness.py` | PreToolUse | Subprocess calls missing required budget arguments |
|
|
118
|
-
| `tdd_enforcer.py` | PreToolUse (Write/Edit) | Production code written without a matching failing test |
|
|
119
|
-
| `unscoped_search_blocker.py` | PreToolUse (Bash/PowerShell) | A `find` or recursive listing that walks from the filesystem root, a drive root, bare home, or a network share root |
|
|
120
|
-
| `verdict_directory_write_blocker.py` | PreToolUse (Bash/PowerShell) | Shell writes into `~/.claude/verification/` |
|
|
121
|
-
| `verified_commit_gate.py` | PreToolUse (Bash/PowerShell) | `git commit`/`git push` without a passing verifier verdict |
|
|
122
|
-
| `verified_commit_message_accuracy_blocker.py` | PreToolUse | Commit messages that misstate what the diff has |
|
|
123
|
-
| `verifier_verdict_minter.py` | SubagentStop | Mints a passing verdict file when a `code-verifier` agent finishes cleanly |
|
|
124
|
-
| `volatile_path_in_post_blocker.py` | PreToolUse (Bash/MCP GitHub) | `gh` post commands and GitHub MCP post tools whose body references a volatile path (job scratch dir, worktree, or system temp) that outlives the durable post |
|
|
125
|
-
| `windows_rmtree_blocker.py` | PreToolUse (Write/Edit) | `shutil.rmtree` with `ignore_errors=True` on Windows |
|
|
126
|
-
| `workflow_substitution_slot_blocker.py` | PreToolUse (Write/Edit) | Workflow templates with bare per-iteration tokens missing angle-bracket slots |
|
|
127
|
-
| `write_existing_file_blocker.py` | PreToolUse (Write) | Write to a path where a file already exists |
|
|
128
|
-
|
|
129
|
-
## Supporting modules
|
|
130
|
-
|
|
131
|
-
| File | Role |
|
|
132
|
-
|---|---|
|
|
133
|
-
| `_gh_body_arg_utils.py` | Parsing helpers for `gh_body_arg_blocker.py` |
|
|
134
|
-
| `code_review_enforcement_config_bootstrap.py` | Binds `config.code_review_enforcement_constants` to the sibling `config/` file by explicit location, so the code-review gate family resolves its constants regardless of a foreign `config` package's `sys.path` order |
|
|
135
|
-
| `code_review_gate_deny.py` | Shared deny scaffold for the push and PR-create code-review gates: the `hookSpecificOutput` deny-payload builder and the log-and-emit helper, so both gates share one deny shape |
|
|
136
|
-
| `code_review_stamp_store.py` | Reads and writes the per-work-tree code-review stamp files under `~/.claude/code-review-stamps/`, and decides whether a clean stamp at the needed effort covers the live branch surface |
|
|
137
|
-
| `pr_description_body_audit.py` | Body audit logic for `pr_description_enforcer.py` |
|
|
138
|
-
| `pr_description_command_parser.py` | `gh` command parsing for `pr_description_enforcer.py` |
|
|
139
|
-
| `pr_description_pr_number.py` | PR number extraction logic |
|
|
140
|
-
| `pr_description_proof_of_work.py` | Proof-of-work comment audit and `gh pr ready` gate logic for `pr_description_enforcer.py` |
|
|
141
|
-
| `pr_description_readability.py` | Readability checks on PR description bodies |
|
|
142
|
-
| `verification_verdict_store.py` | Reads and writes verdict files under `~/.claude/verification/` |
|
|
143
|
-
| `verified_commit_config_bootstrap.py` | Binds `config.verified_commit_constants` to the sibling `config/` file by explicit location, so the gate family resolves its constants regardless of a foreign `config` package's `sys.path` order |
|
|
144
|
-
|
|
145
|
-
## Conventions
|
|
146
|
-
|
|
147
|
-
- Tests live beside each hook as `test_<hookname>.py` or `test_<hookname>_<suffix>.py`. Run with `python -m pytest <test_file>`.
|
|
148
|
-
- Tunable constants live in `hooks_constants/<hook_name>_constants.py`; the verified-commit family uses `blocking/config/verified_commit_constants.py`.
|
|
1
|
+
@AGENTS.md
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# claude_md_orphan_file_blocker_parts
|
|
2
|
+
|
|
3
|
+
The concern modules `claude_md_orphan_file_blocker.py` wires together to block a
|
|
4
|
+
per-directory `CLAUDE.md` that names a file absent from its subtree. The entry
|
|
5
|
+
hook imports them and re-exports their surface for the test suite.
|
|
6
|
+
|
|
7
|
+
## Modules
|
|
8
|
+
|
|
9
|
+
| File | Purpose |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `references.py` | Extracts the bare filenames a table cell names and the scripts a fenced run command invokes, honoring the `../` relative-path exemptions |
|
|
12
|
+
| `subtree_scan.py` | Resolves the scan root and reports which referenced filenames are absent from it, with a bounded walk plus a direct probe fallback |
|
|
13
|
+
| `scan_plan.py` | Builds the Write/Edit/MultiEdit scan plan and collects the orphan filenames the change introduces, excluding pre-existing orphans |
|
|
14
|
+
| `decision.py` | Builds the deny payload listing the missing files and closing with the retry hint, and emits the decision JSON |
|
|
15
|
+
| `__init__.py` | Package marker |
|
|
16
|
+
|
|
17
|
+
## Subdirectories
|
|
18
|
+
|
|
19
|
+
| Entry | Description |
|
|
20
|
+
|---|---|
|
|
21
|
+
| `config/` | The region-join newline, the retry hint the deny reason closes with, and the missing-name join separator (`orphan_blocker_constants.py`) |
|
|
22
|
+
| `tests/` | pytest suite with one test module per module above |
|
|
23
|
+
|
|
24
|
+
## Running tests
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
python -m pytest packages/claude-dev-env/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/
|
|
28
|
+
```
|
|
@@ -1,28 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
The concern modules `claude_md_orphan_file_blocker.py` wires together to block a
|
|
4
|
-
per-directory `CLAUDE.md` that names a file absent from its subtree. The entry
|
|
5
|
-
hook imports them and re-exports their surface for the test suite.
|
|
6
|
-
|
|
7
|
-
## Modules
|
|
8
|
-
|
|
9
|
-
| File | Purpose |
|
|
10
|
-
|---|---|
|
|
11
|
-
| `references.py` | Extracts the bare filenames a table cell names and the scripts a fenced run command invokes, honoring the `../` relative-path exemptions |
|
|
12
|
-
| `subtree_scan.py` | Resolves the scan root and reports which referenced filenames are absent from it, with a bounded walk plus a direct probe fallback |
|
|
13
|
-
| `scan_plan.py` | Builds the Write/Edit/MultiEdit scan plan and collects the orphan filenames the change introduces, excluding pre-existing orphans |
|
|
14
|
-
| `decision.py` | Builds the deny payload listing the missing files and closing with the retry hint, and emits the decision JSON |
|
|
15
|
-
| `__init__.py` | Package marker |
|
|
16
|
-
|
|
17
|
-
## Subdirectories
|
|
18
|
-
|
|
19
|
-
| Entry | Description |
|
|
20
|
-
|---|---|
|
|
21
|
-
| `config/` | The region-join newline, the retry hint the deny reason closes with, and the missing-name join separator (`orphan_blocker_constants.py`) |
|
|
22
|
-
| `tests/` | pytest suite with one test module per module above |
|
|
23
|
-
|
|
24
|
-
## Running tests
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
python -m pytest packages/claude-dev-env/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/
|
|
28
|
-
```
|
|
1
|
+
@AGENTS.md
|
|
@@ -390,7 +390,7 @@ def check_banned_noun_word_boundary(
|
|
|
390
390
|
def check_banned_prefixes(content: str, file_path: str) -> list[str]:
|
|
391
391
|
"""Flag function and method names using generic banned prefixes.
|
|
392
392
|
|
|
393
|
-
Per CODE_RULES.md
|
|
393
|
+
Per CODE_RULES.md Naming, function names use specific verbs.
|
|
394
394
|
Generic prefixes ``handle_``, ``process_``, ``manage_``, ``do_`` are
|
|
395
395
|
placeholders that hide the actual responsibility and are flagged so the
|
|
396
396
|
author renames the function to a specific verb.
|
|
@@ -29,13 +29,20 @@ The scan is deliberately conservative to keep false positives near zero:
|
|
|
29
29
|
exported constant live.
|
|
30
30
|
- When the package-tree scan leaves a constant unreferenced, the scan widens to
|
|
31
31
|
the repository root (the nearest ``.git`` ancestor). The widened pass counts a
|
|
32
|
-
sibling-tree reference
|
|
33
|
-
``from <module> import`` whose final dotted segment equals the
|
|
34
|
-
module's filename stem
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
sibling-tree reference in two shapes. The first is an import of the name
|
|
33
|
+
through a ``from <module> import`` whose final dotted segment equals the
|
|
34
|
+
written module's filename stem. The second is an attribute read on a binding
|
|
35
|
+
of the written module itself: a consumer that writes
|
|
36
|
+
``import pkg.config.constants``, the same with an alias,
|
|
37
|
+
``from pkg.config import constants``, or the same with an alias, and then
|
|
38
|
+
reads each constant as an attribute on that binding (``alias.NAME``,
|
|
39
|
+
``pkg.config.constants.NAME``), keeps every name it reads that way live. Both
|
|
40
|
+
shapes are bound to the written module's stem, so a genuine cross-tree
|
|
41
|
+
consumer of this constants module keeps the constant live while a same-named
|
|
42
|
+
constant exported by an unrelated module never masks a dead one. The widened
|
|
43
|
+
pass reads a repository file only to test whether its text names the written
|
|
44
|
+
module's filename stem; neither shape can appear in a file that never spells
|
|
45
|
+
the stem, so such a file is
|
|
39
46
|
skipped without spending scan-cap budget, keeping the widened pass bounded to
|
|
40
47
|
the handful of candidate importer files even in a large repository. A module
|
|
41
48
|
outside any repository is
|
|
@@ -65,6 +72,7 @@ if _blocking_directory not in sys.path:
|
|
|
65
72
|
if _hooks_directory not in sys.path:
|
|
66
73
|
sys.path.insert(0, _hooks_directory)
|
|
67
74
|
|
|
75
|
+
from code_rules_probe_chains import _dotted_attribute_chain # noqa: E402
|
|
68
76
|
from code_rules_shared import ( # noqa: E402
|
|
69
77
|
is_migration_file,
|
|
70
78
|
is_test_file,
|
|
@@ -268,13 +276,116 @@ def _module_final_segment(module_path: str | None) -> str:
|
|
|
268
276
|
return module_path.rsplit(".", 1)[-1]
|
|
269
277
|
|
|
270
278
|
|
|
271
|
-
def
|
|
272
|
-
"""Return
|
|
279
|
+
def _stem_import_member_names(import_node: ast.ImportFrom, module_stem: str) -> set[str]:
|
|
280
|
+
"""Return the member names a stem-matched ``from ... import`` brings in.
|
|
273
281
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
282
|
+
::
|
|
283
|
+
|
|
284
|
+
from pkg.config.constants import NAME -> "NAME"
|
|
285
|
+
from pkg.config.other import NAME -> nothing (stem mismatch)
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
import_node: A ``from ... import`` statement node.
|
|
289
|
+
module_stem: The filename stem of the constants module being judged.
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
The imported member names when the module path's final dotted segment
|
|
293
|
+
equals ``module_stem``, empty otherwise.
|
|
294
|
+
"""
|
|
295
|
+
if _module_final_segment(import_node.module) != module_stem:
|
|
296
|
+
return set()
|
|
297
|
+
return {each_alias.name for each_alias in import_node.names}
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _import_module_bindings(all_import_aliases: list[ast.alias], module_stem: str) -> set[str]:
|
|
301
|
+
"""Return the names an import's aliases bind to the constants module object.
|
|
302
|
+
|
|
303
|
+
::
|
|
304
|
+
|
|
305
|
+
import pkg.config.constants -> "pkg.config.constants"
|
|
306
|
+
import pkg.config.constants as sg -> "sg"
|
|
307
|
+
from pkg.config import constants -> "constants"
|
|
308
|
+
from . import constants as sg -> "sg"
|
|
309
|
+
import pkg.config.other -> nothing (stem mismatch)
|
|
310
|
+
|
|
311
|
+
A plain ``import`` names the module by its dotted path, so the final
|
|
312
|
+
segment is matched against the stem. A ``from ... import`` alias name is a
|
|
313
|
+
single identifier, which the same final-segment match covers unchanged.
|
|
314
|
+
|
|
315
|
+
Args:
|
|
316
|
+
all_import_aliases: The ``names`` list of an ``Import`` or
|
|
317
|
+
``ImportFrom`` node.
|
|
318
|
+
module_stem: The filename stem of the constants module being judged.
|
|
319
|
+
|
|
320
|
+
Returns:
|
|
321
|
+
The dotted paths and aliases through which the module object is
|
|
322
|
+
reachable.
|
|
323
|
+
"""
|
|
324
|
+
binding_names: set[str] = set()
|
|
325
|
+
for each_alias in all_import_aliases:
|
|
326
|
+
if _module_final_segment(each_alias.name) != module_stem:
|
|
327
|
+
continue
|
|
328
|
+
binding_names.add(each_alias.asname or each_alias.name)
|
|
329
|
+
return binding_names
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def _collect_widened_scan_facts(
|
|
333
|
+
tree: ast.Module, module_stem: str
|
|
334
|
+
) -> tuple[set[str], set[str], list[ast.Attribute]]:
|
|
335
|
+
"""Collect a candidate module's import and attribute facts in one walk.
|
|
336
|
+
|
|
337
|
+
Args:
|
|
338
|
+
tree: The parsed candidate module.
|
|
339
|
+
module_stem: The filename stem of the constants module being judged.
|
|
340
|
+
|
|
341
|
+
Returns:
|
|
342
|
+
The member names imported through a stem-matched ``from ... import``,
|
|
343
|
+
the names bound to the constants module object itself, and every
|
|
344
|
+
attribute node in the module.
|
|
345
|
+
"""
|
|
346
|
+
member_names: set[str] = set()
|
|
347
|
+
binding_names: set[str] = set()
|
|
348
|
+
all_attribute_nodes: list[ast.Attribute] = []
|
|
349
|
+
for each_node in ast.walk(tree):
|
|
350
|
+
if isinstance(each_node, ast.ImportFrom):
|
|
351
|
+
member_names |= _stem_import_member_names(each_node, module_stem)
|
|
352
|
+
if isinstance(each_node, (ast.Import, ast.ImportFrom)):
|
|
353
|
+
binding_names |= _import_module_bindings(each_node.names, module_stem)
|
|
354
|
+
if isinstance(each_node, ast.Attribute):
|
|
355
|
+
all_attribute_nodes.append(each_node)
|
|
356
|
+
return member_names, binding_names, all_attribute_nodes
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def _attribute_read_names(
|
|
360
|
+
all_attribute_nodes: list[ast.Attribute], all_binding_names: set[str]
|
|
361
|
+
) -> set[str]:
|
|
362
|
+
"""Return the constant names read as attributes on a module-object binding.
|
|
363
|
+
|
|
364
|
+
The expression an attribute is read on is spelled back to its dotted source
|
|
365
|
+
form and matched against the bindings, so an alias read (``sg.NAME``) and a
|
|
366
|
+
full dotted path (``pkg.config.constants.NAME``) both resolve, while a read
|
|
367
|
+
on a binding of some other module counts for nothing.
|
|
368
|
+
|
|
369
|
+
Args:
|
|
370
|
+
all_attribute_nodes: Every attribute node in the candidate module.
|
|
371
|
+
all_binding_names: The names bound to the constants module object.
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
The attribute names read on a binding of the constants module.
|
|
375
|
+
"""
|
|
376
|
+
if not all_binding_names:
|
|
377
|
+
return set()
|
|
378
|
+
return {
|
|
379
|
+
each_attribute.attr
|
|
380
|
+
for each_attribute in all_attribute_nodes
|
|
381
|
+
if _dotted_attribute_chain(each_attribute.value) in all_binding_names
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def _widened_scan_reference_names(source: str, module_stem: str) -> set[str]:
|
|
386
|
+
"""Return the names a repository-wide candidate contributes, parsing it once.
|
|
387
|
+
|
|
388
|
+
Both counted shapes are bound to the written module's filename stem, so a
|
|
278
389
|
same-named constant exported by an unrelated module never masks a dead one.
|
|
279
390
|
|
|
280
391
|
Args:
|
|
@@ -282,22 +393,18 @@ def _qualified_import_member_names(source: str, module_stem: str) -> set[str]:
|
|
|
282
393
|
module_stem: The filename stem of the constants module being judged.
|
|
283
394
|
|
|
284
395
|
Returns:
|
|
285
|
-
The
|
|
286
|
-
|
|
396
|
+
The members imported through a stem-matched ``from <module> import``,
|
|
397
|
+
plus the constant names read as attributes on an imported binding of the
|
|
398
|
+
constants module. A module that fails to parse contributes no names.
|
|
287
399
|
"""
|
|
288
400
|
try:
|
|
289
401
|
tree = ast.parse(source)
|
|
290
402
|
except SyntaxError:
|
|
291
403
|
return set()
|
|
292
|
-
member_names
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
if _module_final_segment(each_node.module) != module_stem:
|
|
297
|
-
continue
|
|
298
|
-
for each_alias in each_node.names:
|
|
299
|
-
member_names.add(each_alias.name)
|
|
300
|
-
return member_names
|
|
404
|
+
member_names, binding_names, all_attribute_nodes = _collect_widened_scan_facts(
|
|
405
|
+
tree, module_stem
|
|
406
|
+
)
|
|
407
|
+
return member_names | _attribute_read_names(all_attribute_nodes, binding_names)
|
|
301
408
|
|
|
302
409
|
|
|
303
410
|
def _scan_root_for_constants_module(file_path: str) -> Path:
|
|
@@ -350,11 +457,13 @@ def _read_candidate_source(file_path: Path, required_substring: str | None) -> s
|
|
|
350
457
|
required_substring = None -> read and keep every file
|
|
351
458
|
required_substring = the module stem -> keep only files naming it
|
|
352
459
|
from pkg.foo_constants import BAR -> names the stem -> candidate
|
|
460
|
+
from pkg import foo_constants as c -> names the stem -> candidate
|
|
353
461
|
def unrelated() -> int: ... -> no stem mention -> skipped
|
|
354
462
|
|
|
355
|
-
The widened repository pass looks only for
|
|
356
|
-
|
|
357
|
-
|
|
463
|
+
The widened repository pass looks only for an import bound to the constants
|
|
464
|
+
module's filename stem — a ``from <module> import`` whose final dotted segment
|
|
465
|
+
equals the stem, or an import of the module object itself — and every such
|
|
466
|
+
import spells that stem in the file's text. A file whose text never
|
|
358
467
|
mentions the stem cannot carry the import, so returning None for it lets the
|
|
359
468
|
caller skip the file without spending scan-cap budget, which keeps the widened
|
|
360
469
|
pass bounded to the candidate importer files even under a large repository.
|
|
@@ -422,7 +531,9 @@ def _collect_names_under_root(
|
|
|
422
531
|
in before the walk begins.
|
|
423
532
|
extract_names: Maps one module's source text to the set of names it
|
|
424
533
|
contributes — the generous reference collector for the package-tree
|
|
425
|
-
pass, the stem-bound
|
|
534
|
+
pass, and for the widened pass the stem-bound collector that counts
|
|
535
|
+
both a stem-matched from-import and an attribute read on a binding
|
|
536
|
+
of the written module.
|
|
426
537
|
already_scanned_count: The parsed-file count accumulated by a prior
|
|
427
538
|
pass, so the parse cap bounds the combined work of the
|
|
428
539
|
package-tree and widened passes.
|
|
@@ -552,12 +663,13 @@ def check_dead_module_constants(
|
|
|
552
663
|
are governed by the use-count rule instead. A constant is dead when its name
|
|
553
664
|
appears in no ``.py`` module under the enclosing package tree — not imported,
|
|
554
665
|
not read, not listed in another module's ``__all__`` literal, not named in a
|
|
555
|
-
string annotation
|
|
556
|
-
the
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
unrelated module
|
|
666
|
+
string annotation. When that leaves a constant unreferenced, the scan widens
|
|
667
|
+
to the repository, where two shapes count: a ``from <module> import`` whose
|
|
668
|
+
final dotted segment equals this module's filename stem, and an attribute
|
|
669
|
+
read on a binding of this module (``import pkg.config.constants as alias``
|
|
670
|
+
then the alias followed by the constant name). Binding both to the stem
|
|
671
|
+
keeps a same-named constant in an unrelated module from masking a dead one.
|
|
672
|
+
A module declaring ``__all__``
|
|
561
673
|
narrows the check to the constants its ``__all__`` list names: each must be
|
|
562
674
|
imported or read by another module, and the module's own ``__all__`` entry
|
|
563
675
|
never counts as that consumer, so an exported constant no module consumes is
|
|
@@ -616,14 +728,14 @@ def check_dead_module_constants(
|
|
|
616
728
|
if has_unreferenced_constant:
|
|
617
729
|
repository_root = _repository_root_for(written_path)
|
|
618
730
|
if repository_root is not None and repository_root != scan_root:
|
|
619
|
-
|
|
620
|
-
|
|
731
|
+
collect_widened_scan_names = partial(
|
|
732
|
+
_widened_scan_reference_names, module_stem=written_path.stem
|
|
621
733
|
)
|
|
622
734
|
widened_names, _widened_count, widened_cap_was_hit = _collect_names_under_root(
|
|
623
735
|
repository_root,
|
|
624
736
|
written_path,
|
|
625
737
|
set(),
|
|
626
|
-
|
|
738
|
+
collect_widened_scan_names,
|
|
627
739
|
already_scanned_count=scanned_file_count,
|
|
628
740
|
excluded_subtree=scan_root,
|
|
629
741
|
required_substring=written_path.stem,
|