claude-dev-env 2.4.0 → 2.7.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 +26 -59
- package/_shared/pr-loop/scripts/_claude_permissions_common.py +84 -0
- package/_shared/pr-loop/scripts/code_rules_gate.py +6 -3
- package/_shared/pr-loop/scripts/code_rules_gate_parts/CLAUDE.md +12 -2
- package/_shared/pr-loop/scripts/code_rules_gate_parts/baseline_import_isolation.py +309 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/staged_test_regression.py +540 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/staged_test_running.py +206 -70
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/__init__.py +1 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/_repo_test_helpers.py +76 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_baseline_import_isolation.py +248 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_staged_test_regression.py +309 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_staged_test_running.py +91 -58
- 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/code_rules_gate_constants.py +202 -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 -2
- 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 +36 -8
- 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/test_agent_frontmatter.py +67 -18
- package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +143 -141
- package/bin/CLAUDE.md +68 -5
- package/bin/codex-compat.mjs +104 -0
- package/bin/codex-compat.test.mjs +51 -0
- 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/codex-capability-map.json +13 -0
- package/commands/implement.md +1 -1
- package/commands/right-size.md +1 -1
- package/docs/CLAUDE.md +1 -0
- package/docs/CODE_RULES.md +2 -0
- package/docs/codex-compatibility.md +25 -0
- package/docs/host-pool-health-monitor.md +102 -0
- package/docs/nas-ssh-invocation.md +96 -12
- package/docs/references/CLAUDE.md +4 -2
- package/docs/references/advisor-tool.md +13 -0
- package/docs/references/code-review-enforcement.md +35 -0
- package/docs/references/team-advisor-skill.md +14 -0
- package/hooks/blocking/CLAUDE.md +4 -0
- package/hooks/blocking/code_review_pr_create_gate.py +7 -3
- package/hooks/blocking/code_review_push_gate.py +9 -4
- package/hooks/blocking/code_review_stamp_directory_write_blocker.py +8 -0
- package/hooks/blocking/config/__init__.py +5 -5
- package/hooks/blocking/config/code_review_enforcement_constants.py +40 -7
- package/hooks/blocking/config/test_code_review_enforcement_constants.py +58 -0
- package/hooks/blocking/config/verified_commit_constants.py +160 -159
- package/hooks/blocking/eli11_reply_enforcer.py +479 -0
- package/hooks/blocking/gh_body_arg_blocker.py +1 -1
- package/hooks/blocking/nas_ssh_binary_enforcer.py +8 -46
- package/hooks/blocking/orchestrator_refresh_reschedule_gate.py +256 -0
- package/hooks/blocking/pre_tool_use_dispatcher.py +24 -24
- package/hooks/blocking/shell_substitution_blocker.py +129 -0
- package/hooks/blocking/state_description_blocker.py +1 -1
- package/hooks/blocking/stop_dispatcher.py +1 -1
- package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +2 -3
- package/hooks/blocking/test_code_review_pr_create_gate.py +14 -0
- package/hooks/blocking/test_code_review_push_gate.py +16 -0
- package/hooks/blocking/test_code_review_stamp_directory_write_blocker.py +19 -0
- package/hooks/blocking/test_eli11_reply_enforcer.py +457 -0
- package/hooks/blocking/test_orchestrator_refresh_reschedule_gate.py +231 -0
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +10 -1
- package/hooks/blocking/test_shell_substitution_blocker.py +124 -0
- package/hooks/blocking/test_stop_dispatcher.py +23 -0
- package/hooks/blocking/test_unscoped_search_blocker.py +102 -0
- package/hooks/blocking/test_verdict_directory_write_blocker.py +804 -808
- package/hooks/blocking/test_verification_verdict_store.py +54 -0
- 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/unscoped_search_blocker.py +391 -0
- package/hooks/blocking/verdict_directory_write_blocker.py +687 -687
- package/hooks/blocking/verification_verdict_store.py +1039 -1036
- package/hooks/blocking/verified_commit_message_accuracy_blocker.py +167 -167
- package/hooks/blocking/verifier_verdict_minter.py +280 -280
- package/hooks/git-hooks/CLAUDE.md +3 -0
- package/hooks/git-hooks/conftest.py +30 -0
- package/hooks/git-hooks/gate_utils.py +2 -2
- package/hooks/git-hooks/git_hooks_constants/__init__.py +41 -2
- package/hooks/git-hooks/pre_push.py +75 -4
- package/hooks/git-hooks/pre_push_base_reference.py +166 -0
- package/hooks/git-hooks/test_config.py +0 -15
- package/hooks/git-hooks/test_gate_utils.py +3 -15
- package/hooks/git-hooks/test_pre_commit.py +1 -15
- package/hooks/git-hooks/test_pre_push.py +257 -23
- package/hooks/git-hooks/test_pre_push_base_reference.py +339 -0
- package/hooks/hooks.json +10 -12
- package/hooks/hooks_constants/CLAUDE.md +7 -2
- package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +4 -4
- package/hooks/hooks_constants/eli11_reply_enforcer_constants.py +101 -0
- package/hooks/hooks_constants/enter_worktree_prefetch_constants.py +18 -18
- package/hooks/hooks_constants/nas_ssh_binary_enforcer_constants.py +2 -8
- package/hooks/hooks_constants/orchestrator_refresh_reschedule_gate_constants.py +48 -0
- package/hooks/hooks_constants/ruff_integration_constants.py +16 -0
- package/hooks/hooks_constants/shell_command_segments.py +82 -0
- package/hooks/hooks_constants/shell_substitution_blocker_constants.py +67 -0
- package/hooks/hooks_constants/stop_dispatcher_constants.py +1 -0
- package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +5 -6
- package/hooks/hooks_constants/test_stop_dispatcher_constants.py +1 -0
- package/hooks/hooks_constants/unscoped_search_blocker_constants.py +153 -0
- 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 +1 -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/ruff_integration.py +49 -5
- package/hooks/validators/run_all_validators.py +206 -9
- package/hooks/validators/test_directory_exemption_constants.py +185 -0
- package/hooks/validators/test_python_antipattern_checks.py +110 -5
- package/hooks/validators/test_ruff_integration.py +92 -1
- package/hooks/validators/test_run_all_validators.py +115 -68
- package/hooks/validators/test_run_all_validators_pretooluse.py +159 -1
- package/package.json +13 -3
- package/rules/CLAUDE.md +17 -22
- package/rules/agent-spawn-protocol.md +6 -6
- package/rules/anti-corollary-tests.md +1 -1
- package/rules/bdd.md +1 -1
- package/rules/cleanup-temp-files.md +10 -4
- package/rules/code-standards.md +7 -0
- package/rules/conservative-action.md +1 -5
- package/rules/context7.md +0 -4
- package/rules/destructive-commands.md +47 -0
- package/rules/doc-inventory-integrity.md +48 -0
- package/rules/doc-prose-cuts.md +58 -0
- package/rules/docstring-prose-matches-implementation.md +53 -44
- package/rules/durable-post-artifacts.md +0 -4
- package/rules/eli11-replies.md +31 -0
- package/rules/explore-thoroughly.md +4 -4
- package/rules/falsify-before-green.md +68 -0
- package/rules/file-global-constants.md +1 -1
- package/rules/filesystem-search.md +51 -0
- package/rules/gh-cli-conventions.md +27 -0
- package/rules/git-workflow.md +26 -0
- package/rules/hedging-claims.md +9 -0
- package/rules/long-horizon-autonomy.md +0 -4
- package/rules/measurement-denominators.md +48 -0
- package/rules/nas-ssh-invocation.md +23 -5
- package/rules/parallel-tools.md +2 -2
- package/rules/plain-illustrative-docstrings.md +3 -7
- package/rules/plain-language.md +2 -0
- package/rules/proof-of-work-pr-comments.md +0 -4
- package/rules/re-stage-before-commit.md +2 -0
- package/rules/research-mode.md +10 -0
- package/rules/shell-invocation.md +21 -0
- package/rules/testing.md +4 -0
- package/rules/verified-commit-gate-skip.md +3 -27
- package/rules/verify-before-asking.md +5 -0
- package/rules/windows-filesystem-safe.md +1 -1
- package/rules/workers-done-before-complete.md +4 -0
- package/scripts/CLAUDE.md +1 -0
- package/scripts/Capture-PoolHealth.ps1 +410 -0
- package/scripts/Migrate-ShellPolicy.ps1 +1 -1
- package/scripts/_code_review_test_support.py +404 -0
- package/scripts/claude_chain_runner.py +141 -1
- package/scripts/codex_capability_bridge.py +171 -0
- package/scripts/codex_compat_materializer.py +1087 -0
- package/scripts/codex_compat_watcher.py +502 -0
- package/scripts/conftest.py +16 -1
- package/scripts/dev_env_scripts_constants/CLAUDE.md +1 -1
- package/scripts/dev_env_scripts_constants/claude_chain_constants.py +9 -0
- package/scripts/dev_env_scripts_constants/code_review_constants.py +37 -0
- package/scripts/invoke_code_review.py +11 -4
- package/scripts/resolve_worker_spawn.py +626 -626
- package/scripts/spawn_grok_batch.py +672 -672
- package/scripts/sync_to_cursor/rules.py +0 -10
- package/scripts/test_claude_chain_runner.py +131 -0
- package/scripts/test_invoke_code_review.py +85 -908
- 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_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/tests/test_code_review_constants.py +80 -0
- package/scripts/tests/test_codex_capability_bridge.py +91 -0
- package/scripts/tests/test_codex_compat_materializer.py +632 -0
- package/scripts/tests/test_codex_compat_watcher.py +599 -0
- package/scripts/tests/test_sync_to_cursor.py +0 -1
- package/skills/CLAUDE.md +2 -0
- package/skills/auditing-claude-config/SKILL.md +114 -114
- package/skills/autoconverge/SKILL.md +427 -427
- package/skills/autoconverge/reference/convergence.md +24 -3
- package/skills/autoconverge/workflow/CLAUDE.md +1 -0
- package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +3 -3
- package/skills/autoconverge/workflow/converge.contract.test.mjs +1263 -1263
- package/skills/autoconverge/workflow/converge.mjs +168 -1
- package/skills/autoconverge/workflow/converge.p2-advance.test.mjs +202 -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/bugteam/reference/copilot-gap-analysis.md +1 -1
- 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 +72 -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 +1 -1
- package/skills/fresh-branch/SKILL.md +5 -6
- package/skills/fresh-branch/scripts/create_fresh_branch.py +42 -24
- package/skills/fresh-branch/scripts/fresh_branch_scripts_constants/fresh_branch_cli_constants.py +1 -3
- package/skills/fresh-branch/scripts/test_create_fresh_branch.py +30 -126
- 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 +177 -22
- 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 +129 -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/privacy-hygiene/reference/sweep-procedure.md +1 -1
- package/skills/prototype/workflows/promotion.md +1 -1
- package/skills/release-notes-html/SKILL.md +164 -0
- package/skills/session-log/SKILL.md +1 -1
- 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/rules/claude-md-orphan-file.md +0 -28
- package/rules/cleanup-command-forms.md +0 -23
- package/rules/code-reviews.md +0 -11
- package/rules/env-var-table-code-drift.md +0 -10
- package/rules/gh-body-file.md +0 -5
- package/rules/gh-paginate.md +0 -3
- package/rules/hook-prose-matches-detector.md +0 -15
- package/rules/no-historical-clutter.md +0 -26
- package/rules/no-inline-destructive-literals.md +0 -9
- package/rules/no-justification-noise.md +0 -61
- package/rules/package-inventory-stale-entry.md +0 -25
- package/rules/right-sized-engineering.md +0 -28
- package/rules/self-contained-docs.md +0 -17
- package/rules/shell-invocation-policy.md +0 -5
- package/rules/tdd.md +0 -7
- package/skills/closeout/reference/issue-body-templates.md +0 -108
|
@@ -1,167 +1,167 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""PreToolUse hook: keep verified-commit exemption messages accurate.
|
|
3
|
-
|
|
4
|
-
The verified-commit gate exempts a branch surface only when every changed path
|
|
5
|
-
matches one of two narrow rules, implemented in ``is_verification_exempt_diff``:
|
|
6
|
-
|
|
7
|
-
1. file extension in ``DOCS_ONLY_EXTENSIONS`` (docs and image extensions), or
|
|
8
|
-
2. a ``.py`` file whose docstring/comment-stripped AST is unchanged.
|
|
9
|
-
|
|
10
|
-
A comment-only change to a non-Python, non-doc file (for example ``.sh``,
|
|
11
|
-
``.json``, ``.yaml``) is therefore NOT exempt: comments are ignored for
|
|
12
|
-
exemption purposes only inside Python files via the AST path. A corrective or
|
|
13
|
-
guard message that claims comment-only or docs-only surfaces are blanket exempt
|
|
14
|
-
overstates the rule and misleads users into expecting such a change to skip
|
|
15
|
-
verification.
|
|
16
|
-
|
|
17
|
-
This hook fires on Write/Edit of any verified-commit constants module and denies
|
|
18
|
-
content whose exemption-claim wording asserts a blanket comment-only or docs-only
|
|
19
|
-
exemption. It guards the message constants at authoring time, before the change
|
|
20
|
-
reaches review.
|
|
21
|
-
"""
|
|
22
|
-
|
|
23
|
-
import json
|
|
24
|
-
import os
|
|
25
|
-
import re
|
|
26
|
-
import sys
|
|
27
|
-
from pathlib import Path
|
|
28
|
-
|
|
29
|
-
_hooks_dir = str(Path(__file__).resolve().parent.parent)
|
|
30
|
-
if _hooks_dir not in sys.path:
|
|
31
|
-
sys.path.insert(0, _hooks_dir)
|
|
32
|
-
|
|
33
|
-
from hooks_constants.hook_block_logger import log_hook_block # noqa: E402
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def is_guarded_file(file_path: str) -> bool:
|
|
37
|
-
"""Return True for any verified-commit constants module carrying messages."""
|
|
38
|
-
all_guarded_file_names = frozenset({"verified_commit_constants.py"})
|
|
39
|
-
return os.path.basename(file_path) in all_guarded_file_names
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def join_adjacent_string_literals(written_text: str) -> str:
|
|
43
|
-
"""Collapse a closing quote, inter-literal whitespace, and an opening quote.
|
|
44
|
-
|
|
45
|
-
Write/Edit content carries the message constant as it appears in source: a
|
|
46
|
-
long sentence split across adjacent Python string literals. Between two
|
|
47
|
-
literals sit a closing quote, a newline, indentation, and an opening quote.
|
|
48
|
-
Replacing that run with a single space rejoins the prose so a phrase wrapped
|
|
49
|
-
across a literal boundary — for example ``exempt "`` then ``"automatically``
|
|
50
|
-
— reads as one continuous clause for matching.
|
|
51
|
-
"""
|
|
52
|
-
inter_literal_noise_pattern = re.compile(r"[\"']\s*[\"']")
|
|
53
|
-
return inter_literal_noise_pattern.sub(" ", written_text)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def claims_blanket_comment_exemption(written_text: str) -> bool:
|
|
57
|
-
"""Return True when the text claims comment surfaces are blanket-exempt.
|
|
58
|
-
|
|
59
|
-
Rejoins source-wrapped string literals first, then matches only a genuine
|
|
60
|
-
blanket form in which a comment surface is the grammatical subject of
|
|
61
|
-
"exempt automatically". A comment surface is the bare noun ``comments`` or
|
|
62
|
-
the ``comment-only`` category — never the ``comment`` stem inside an
|
|
63
|
-
unrelated word such as ``commentary`` or the ``comment-stripped`` AST
|
|
64
|
-
qualifier.
|
|
65
|
-
|
|
66
|
-
Two blanket shapes match. The direct shape names a comment surface as the
|
|
67
|
-
immediate subject of "(are|is) exempt automatically" across a bridge that
|
|
68
|
-
crosses no period, semicolon, or comma, so an intervening predicate (for
|
|
69
|
-
example "Comments are handled, and docs are exempt automatically") leaves
|
|
70
|
-
docs, not comments, as the exemption subject and does not match. The
|
|
71
|
-
enumerated shape names a comment item inside an "...-only surfaces are
|
|
72
|
-
exempt automatically" list (for example "Docs-, comment-, and test-only
|
|
73
|
-
surfaces are exempt automatically").
|
|
74
|
-
|
|
75
|
-
An accurate sentence whose true exemption subject is docs, or that qualifies
|
|
76
|
-
comments as the stripped input to the AST comparison, does not match. This
|
|
77
|
-
isolates the overstated form the verified-commit gate does not honor for
|
|
78
|
-
non-Python files.
|
|
79
|
-
"""
|
|
80
|
-
comment_surface = r"comments?-only|comments?\b(?!-)"
|
|
81
|
-
blanket_direct_claim = (
|
|
82
|
-
"(?:" + comment_surface + r")(?:[^.;,]*?\b)?(?:are|is)\s+exempt\s+automatically"
|
|
83
|
-
)
|
|
84
|
-
blanket_enumerated_claim = (
|
|
85
|
-
"(?:" + comment_surface + "|comment-)"
|
|
86
|
-
r"[^.;]*?-only\s+surfaces\s+are\s+exempt\s+automatically"
|
|
87
|
-
)
|
|
88
|
-
blanket_exemption_claim_pattern = re.compile(
|
|
89
|
-
"(?:" + blanket_direct_claim + ")|(?:" + blanket_enumerated_claim + ")",
|
|
90
|
-
re.IGNORECASE,
|
|
91
|
-
)
|
|
92
|
-
joined_text = join_adjacent_string_literals(written_text)
|
|
93
|
-
return bool(blanket_exemption_claim_pattern.search(joined_text))
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def build_corrective_message() -> str:
|
|
97
|
-
"""Return the deny reason explaining the real, narrower exemption rules."""
|
|
98
|
-
accurate_exemption_phrasing = (
|
|
99
|
-
"Docs and images are exempt by extension, and Python files whose "
|
|
100
|
-
"docstring- and comment-stripped AST is unchanged; a comment-only "
|
|
101
|
-
"change to a non-Python file still needs a verdict."
|
|
102
|
-
)
|
|
103
|
-
return (
|
|
104
|
-
"BLOCKED [verified-commit-message-accuracy]: this exemption message "
|
|
105
|
-
"claims comment-only surfaces are exempt automatically, but the "
|
|
106
|
-
"verified-commit gate exempts comments only inside Python files (via the "
|
|
107
|
-
"docstring/comment stripped AST path). A comment-only change to a "
|
|
108
|
-
"non-Python file is NOT exempt and still needs a verifier verdict, so "
|
|
109
|
-
"the blanket wording misleads users.\n\nDescribe the real exemption "
|
|
110
|
-
"rules instead, for example:\n " + accurate_exemption_phrasing
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
def extract_written_text(all_written_fields: dict[str, str]) -> str:
|
|
115
|
-
"""Return the Write ``content`` and Edit ``new_string`` joined for scanning."""
|
|
116
|
-
return (
|
|
117
|
-
all_written_fields.get("content", "")
|
|
118
|
-
+ "\n"
|
|
119
|
-
+ all_written_fields.get("new_string", "")
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
def main() -> None:
|
|
124
|
-
all_write_edit_tools = frozenset({"Write", "Edit"})
|
|
125
|
-
try:
|
|
126
|
-
hook_input = json.load(sys.stdin)
|
|
127
|
-
except json.JSONDecodeError:
|
|
128
|
-
sys.exit(0)
|
|
129
|
-
|
|
130
|
-
tool_name = hook_input.get("tool_name", "")
|
|
131
|
-
if tool_name not in all_write_edit_tools:
|
|
132
|
-
sys.exit(0)
|
|
133
|
-
|
|
134
|
-
tool_input = hook_input.get("tool_input", {})
|
|
135
|
-
file_path = tool_input.get("file_path", "")
|
|
136
|
-
if not file_path:
|
|
137
|
-
sys.exit(0)
|
|
138
|
-
|
|
139
|
-
if not is_guarded_file(file_path):
|
|
140
|
-
sys.exit(0)
|
|
141
|
-
|
|
142
|
-
written_text = extract_written_text(tool_input)
|
|
143
|
-
if not claims_blanket_comment_exemption(written_text):
|
|
144
|
-
sys.exit(0)
|
|
145
|
-
|
|
146
|
-
corrective_message = build_corrective_message()
|
|
147
|
-
deny_response = {
|
|
148
|
-
"hookSpecificOutput": {
|
|
149
|
-
"hookEventName": "PreToolUse",
|
|
150
|
-
"permissionDecision": "deny",
|
|
151
|
-
"permissionDecisionReason": corrective_message,
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
log_hook_block(
|
|
155
|
-
calling_hook_name="verified_commit_message_accuracy_blocker.py",
|
|
156
|
-
hook_event="PreToolUse",
|
|
157
|
-
block_reason=corrective_message,
|
|
158
|
-
tool_name=tool_name,
|
|
159
|
-
offending_input_preview=file_path,
|
|
160
|
-
)
|
|
161
|
-
print(json.dumps(deny_response))
|
|
162
|
-
sys.stdout.flush()
|
|
163
|
-
sys.exit(0)
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
if __name__ == "__main__":
|
|
167
|
-
main()
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""PreToolUse hook: keep verified-commit exemption messages accurate.
|
|
3
|
+
|
|
4
|
+
The verified-commit gate exempts a branch surface only when every changed path
|
|
5
|
+
matches one of two narrow rules, implemented in ``is_verification_exempt_diff``:
|
|
6
|
+
|
|
7
|
+
1. file extension in ``DOCS_ONLY_EXTENSIONS`` (docs and image extensions), or
|
|
8
|
+
2. a ``.py`` file whose docstring/comment-stripped AST is unchanged.
|
|
9
|
+
|
|
10
|
+
A comment-only change to a non-Python, non-doc file (for example ``.sh``,
|
|
11
|
+
``.json``, ``.yaml``) is therefore NOT exempt: comments are ignored for
|
|
12
|
+
exemption purposes only inside Python files via the AST path. A corrective or
|
|
13
|
+
guard message that claims comment-only or docs-only surfaces are blanket exempt
|
|
14
|
+
overstates the rule and misleads users into expecting such a change to skip
|
|
15
|
+
verification.
|
|
16
|
+
|
|
17
|
+
This hook fires on Write/Edit of any verified-commit constants module and denies
|
|
18
|
+
content whose exemption-claim wording asserts a blanket comment-only or docs-only
|
|
19
|
+
exemption. It guards the message constants at authoring time, before the change
|
|
20
|
+
reaches review.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
import os
|
|
25
|
+
import re
|
|
26
|
+
import sys
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
|
|
29
|
+
_hooks_dir = str(Path(__file__).resolve().parent.parent)
|
|
30
|
+
if _hooks_dir not in sys.path:
|
|
31
|
+
sys.path.insert(0, _hooks_dir)
|
|
32
|
+
|
|
33
|
+
from hooks_constants.hook_block_logger import log_hook_block # noqa: E402
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def is_guarded_file(file_path: str) -> bool:
|
|
37
|
+
"""Return True for any verified-commit constants module carrying messages."""
|
|
38
|
+
all_guarded_file_names = frozenset({"verified_commit_constants.py"})
|
|
39
|
+
return os.path.basename(file_path) in all_guarded_file_names
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def join_adjacent_string_literals(written_text: str) -> str:
|
|
43
|
+
"""Collapse a closing quote, inter-literal whitespace, and an opening quote.
|
|
44
|
+
|
|
45
|
+
Write/Edit content carries the message constant as it appears in source: a
|
|
46
|
+
long sentence split across adjacent Python string literals. Between two
|
|
47
|
+
literals sit a closing quote, a newline, indentation, and an opening quote.
|
|
48
|
+
Replacing that run with a single space rejoins the prose so a phrase wrapped
|
|
49
|
+
across a literal boundary — for example ``exempt "`` then ``"automatically``
|
|
50
|
+
— reads as one continuous clause for matching.
|
|
51
|
+
"""
|
|
52
|
+
inter_literal_noise_pattern = re.compile(r"[\"']\s*[\"']")
|
|
53
|
+
return inter_literal_noise_pattern.sub(" ", written_text)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def claims_blanket_comment_exemption(written_text: str) -> bool:
|
|
57
|
+
"""Return True when the text claims comment surfaces are blanket-exempt.
|
|
58
|
+
|
|
59
|
+
Rejoins source-wrapped string literals first, then matches only a genuine
|
|
60
|
+
blanket form in which a comment surface is the grammatical subject of
|
|
61
|
+
"exempt automatically". A comment surface is the bare noun ``comments`` or
|
|
62
|
+
the ``comment-only`` category — never the ``comment`` stem inside an
|
|
63
|
+
unrelated word such as ``commentary`` or the ``comment-stripped`` AST
|
|
64
|
+
qualifier.
|
|
65
|
+
|
|
66
|
+
Two blanket shapes match. The direct shape names a comment surface as the
|
|
67
|
+
immediate subject of "(are|is) exempt automatically" across a bridge that
|
|
68
|
+
crosses no period, semicolon, or comma, so an intervening predicate (for
|
|
69
|
+
example "Comments are handled, and docs are exempt automatically") leaves
|
|
70
|
+
docs, not comments, as the exemption subject and does not match. The
|
|
71
|
+
enumerated shape names a comment item inside an "...-only surfaces are
|
|
72
|
+
exempt automatically" list (for example "Docs-, comment-, and test-only
|
|
73
|
+
surfaces are exempt automatically").
|
|
74
|
+
|
|
75
|
+
An accurate sentence whose true exemption subject is docs, or that qualifies
|
|
76
|
+
comments as the stripped input to the AST comparison, does not match. This
|
|
77
|
+
isolates the overstated form the verified-commit gate does not honor for
|
|
78
|
+
non-Python files.
|
|
79
|
+
"""
|
|
80
|
+
comment_surface = r"comments?-only|comments?\b(?!-)"
|
|
81
|
+
blanket_direct_claim = (
|
|
82
|
+
"(?:" + comment_surface + r")(?:[^.;,]*?\b)?(?:are|is)\s+exempt\s+automatically"
|
|
83
|
+
)
|
|
84
|
+
blanket_enumerated_claim = (
|
|
85
|
+
"(?:" + comment_surface + "|comment-)"
|
|
86
|
+
r"[^.;]*?-only\s+surfaces\s+are\s+exempt\s+automatically"
|
|
87
|
+
)
|
|
88
|
+
blanket_exemption_claim_pattern = re.compile(
|
|
89
|
+
"(?:" + blanket_direct_claim + ")|(?:" + blanket_enumerated_claim + ")",
|
|
90
|
+
re.IGNORECASE,
|
|
91
|
+
)
|
|
92
|
+
joined_text = join_adjacent_string_literals(written_text)
|
|
93
|
+
return bool(blanket_exemption_claim_pattern.search(joined_text))
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def build_corrective_message() -> str:
|
|
97
|
+
"""Return the deny reason explaining the real, narrower exemption rules."""
|
|
98
|
+
accurate_exemption_phrasing = (
|
|
99
|
+
"Docs and images are exempt by extension, and Python files whose "
|
|
100
|
+
"docstring- and comment-stripped AST is unchanged; a comment-only "
|
|
101
|
+
"change to a non-Python file still needs a verdict."
|
|
102
|
+
)
|
|
103
|
+
return (
|
|
104
|
+
"BLOCKED [verified-commit-message-accuracy]: this exemption message "
|
|
105
|
+
"claims comment-only surfaces are exempt automatically, but the "
|
|
106
|
+
"verified-commit gate exempts comments only inside Python files (via the "
|
|
107
|
+
"docstring/comment stripped AST path). A comment-only change to a "
|
|
108
|
+
"non-Python file is NOT exempt and still needs a verifier verdict, so "
|
|
109
|
+
"the blanket wording misleads users.\n\nDescribe the real exemption "
|
|
110
|
+
"rules instead, for example:\n " + accurate_exemption_phrasing
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def extract_written_text(all_written_fields: dict[str, str]) -> str:
|
|
115
|
+
"""Return the Write ``content`` and Edit ``new_string`` joined for scanning."""
|
|
116
|
+
return (
|
|
117
|
+
all_written_fields.get("content", "")
|
|
118
|
+
+ "\n"
|
|
119
|
+
+ all_written_fields.get("new_string", "")
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def main() -> None:
|
|
124
|
+
all_write_edit_tools = frozenset({"Write", "Edit"})
|
|
125
|
+
try:
|
|
126
|
+
hook_input = json.load(sys.stdin)
|
|
127
|
+
except json.JSONDecodeError:
|
|
128
|
+
sys.exit(0)
|
|
129
|
+
|
|
130
|
+
tool_name = hook_input.get("tool_name", "")
|
|
131
|
+
if tool_name not in all_write_edit_tools:
|
|
132
|
+
sys.exit(0)
|
|
133
|
+
|
|
134
|
+
tool_input = hook_input.get("tool_input", {})
|
|
135
|
+
file_path = tool_input.get("file_path", "")
|
|
136
|
+
if not file_path:
|
|
137
|
+
sys.exit(0)
|
|
138
|
+
|
|
139
|
+
if not is_guarded_file(file_path):
|
|
140
|
+
sys.exit(0)
|
|
141
|
+
|
|
142
|
+
written_text = extract_written_text(tool_input)
|
|
143
|
+
if not claims_blanket_comment_exemption(written_text):
|
|
144
|
+
sys.exit(0)
|
|
145
|
+
|
|
146
|
+
corrective_message = build_corrective_message()
|
|
147
|
+
deny_response = {
|
|
148
|
+
"hookSpecificOutput": {
|
|
149
|
+
"hookEventName": "PreToolUse",
|
|
150
|
+
"permissionDecision": "deny",
|
|
151
|
+
"permissionDecisionReason": corrective_message,
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
log_hook_block(
|
|
155
|
+
calling_hook_name="verified_commit_message_accuracy_blocker.py",
|
|
156
|
+
hook_event="PreToolUse",
|
|
157
|
+
block_reason=corrective_message,
|
|
158
|
+
tool_name=tool_name,
|
|
159
|
+
offending_input_preview=file_path,
|
|
160
|
+
)
|
|
161
|
+
print(json.dumps(deny_response))
|
|
162
|
+
sys.stdout.flush()
|
|
163
|
+
sys.exit(0)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
if __name__ == "__main__":
|
|
167
|
+
main()
|