claude-dev-env 2.11.0 → 2.13.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/_shared/advisor/reference/sol-rung.md +5 -2
- package/_shared/advisor/reference/warm-up.md +1 -0
- package/_shared/advisor/scripts/codex_sol_advisor.py +96 -31
- package/_shared/advisor/scripts/config/advisor_scripts_constants/sol_advisor_constants.py +5 -0
- package/_shared/advisor/scripts/tests/test_codex_sol_advisor.py +173 -11
- package/_shared/pr-loop/scripts/code_rules_gate.py +2 -3
- package/_shared/pr-loop/scripts/test_code_rules_gate.py +0 -26
- package/agents/CLAUDE.md +1 -2
- package/agents/pr-description-writer.md +11 -81
- package/bin/install.mjs +48 -9
- package/bin/install.prune.test.mjs +26 -0
- package/bin/install.settings-defaults.test.mjs +60 -33
- package/bin/install.test.mjs +2 -4
- package/commands/CLAUDE.md +1 -0
- package/commands/sr-loop.md +52 -0
- package/docs/CLAUDE.md +0 -1
- package/docs/references/CLAUDE.md +0 -1
- package/hooks/CLAUDE.md +1 -1
- package/hooks/blocking/CLAUDE.md +2 -27
- package/hooks/blocking/config/CLAUDE.md +1 -13
- package/hooks/blocking/conventional_pr_title_gate.py +1 -2
- package/hooks/blocking/send_user_file_open_locally_blocker.py +1 -1
- package/hooks/blocking/stop_dispatcher.py +4 -4
- package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +0 -3
- 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/CLAUDE.md +3 -3
- package/hooks/diagnostic/hook_log_extractor.py +2 -36
- package/hooks/diagnostic/hook_log_stop_wrapper.py +6 -155
- package/hooks/diagnostic/test_hook_log_extractor.py +8 -21
- package/hooks/diagnostic/test_hook_log_stop_wrapper.py +3 -331
- package/hooks/git-hooks/git_hooks_constants/__init__.py +0 -6
- package/hooks/git-hooks/pre_push.py +3 -85
- package/hooks/git-hooks/test_pre_push.py +0 -130
- package/hooks/hooks.json +0 -35
- package/hooks/hooks_constants/CLAUDE.md +0 -9
- 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/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/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/lifecycle/CLAUDE.md +1 -3
- package/package.json +1 -1
- package/rules/CLAUDE.md +0 -2
- package/rules/gh-cli-conventions.md +0 -1
- package/rules/git-workflow.md +1 -3
- package/scripts/CLAUDE.md +0 -1
- package/scripts/_code_review_test_support.py +0 -95
- 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/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/settings.json +1 -10
- package/skills/CLAUDE.md +8 -0
- package/skills/autoconverge/reference/CLAUDE.md +1 -1
- package/skills/autoconverge/reference/convergence.md +7 -7
- package/skills/autoconverge/reference/gotchas.md +3 -10
- 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/CONSTRAINTS.md +1 -1
- package/skills/bugteam/SKILL.md +9 -0
- package/skills/bugteam/reference/audit-and-teammates.md +10 -6
- package/skills/bugteam/test_skill_additions.py +9 -0
- package/skills/comments/SKILL.md +65 -0
- package/skills/descriptions/SKILL.md +51 -0
- package/skills/emergencies/SKILL.md +42 -0
- package/skills/imagegen/SKILL.md +21 -0
- package/skills/imagegen/scripts/config/__init__.py +1 -0
- package/skills/imagegen/scripts/config/constants.py +28 -0
- package/skills/imagegen/scripts/imagegen.py +45 -0
- package/skills/imagegen/scripts/imagegen_core.py +528 -0
- package/skills/imagegen/scripts/test_imagegen.py +204 -0
- 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/SKILL.md +13 -16
- package/skills/pr-converge/reference/progress-checklist.md +6 -5
- package/skills/pr-converge/reference/state-schema.md +1 -27
- 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 +4 -4
- package/skills/prototype/reference/promotion-tasks.md +2 -2
- package/skills/prototype/workflows/promotion.md +4 -6
- package/skills/reviews/SKILL.md +85 -0
- package/skills/small-cl/SKILL.md +52 -0
- package/skills/usage-pause/SKILL.md +1 -1
- package/skills/usage-pause/scripts/resolve_usage_window.py +32 -4
- package/skills/usage-pause/scripts/test_resolve_usage_window.py +26 -0
- package/skills/usage-pause/scripts/usage_pause_constants/resolve_usage_window_constants.py +3 -1
- 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
|
@@ -17,8 +17,6 @@ SCRIPT_DIRECTORY = Path(__file__).resolve().parent
|
|
|
17
17
|
REAL_RUN_GIT_TEXT_COMMAND = pre_push.run_git_text_command
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
CODE_REVIEW_ENFORCEMENT_ENV_VAR: str = "CLAUDE_CODE_REVIEW_ENFORCEMENT"
|
|
21
|
-
ENFORCEMENT_CONSTANTS_MODULE_NAME: str = "config.code_review_enforcement_constants"
|
|
22
20
|
ALL_ZEROS_OBJECT_NAME: str = "0" * 40
|
|
23
21
|
NON_ZERO_LOCAL_SHA: str = "a" * 40
|
|
24
22
|
NON_ZERO_REMOTE_SHA_ONE: str = "1" * 40
|
|
@@ -111,7 +109,6 @@ def _write_gate_that_allows_every_push(
|
|
|
111
109
|
passing_gate_path = tmp_path / "code_rules_gate.py"
|
|
112
110
|
passing_gate_path.write_text("import sys\nsys.exit(0)\n", encoding="utf-8")
|
|
113
111
|
monkeypatch.setenv("CODE_RULES_GATE_PATH", str(passing_gate_path))
|
|
114
|
-
_isolate_code_review_gate(tmp_path, monkeypatch)
|
|
115
112
|
monkeypatch.setattr(sys, "stdin", io.StringIO(NEW_BRANCH_PUSH_STDIN))
|
|
116
113
|
|
|
117
114
|
|
|
@@ -244,11 +241,6 @@ def resolve_remote_head_reference(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
|
244
241
|
monkeypatch.setattr(pre_push, "run_git_text_command", fake_run_git_text_command)
|
|
245
242
|
|
|
246
243
|
|
|
247
|
-
def _isolate_code_review_gate(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
248
|
-
monkeypatch.setenv(
|
|
249
|
-
"CODE_REVIEW_PUSH_GATE_PATH", str(tmp_path / "no_code_review_gate.py")
|
|
250
|
-
)
|
|
251
|
-
|
|
252
244
|
|
|
253
245
|
def test_resolve_base_reference_from_stdin_uses_remote_object_when_non_zero() -> None:
|
|
254
246
|
stdin_text = (
|
|
@@ -295,7 +287,6 @@ def test_main_exits_zero_when_gate_script_missing(
|
|
|
295
287
|
"CODE_RULES_GATE_PATH",
|
|
296
288
|
str(tmp_path / "does_not_exist.py"),
|
|
297
289
|
)
|
|
298
|
-
_isolate_code_review_gate(tmp_path, monkeypatch)
|
|
299
290
|
monkeypatch.setattr(sys, "stdin", io.StringIO(""))
|
|
300
291
|
|
|
301
292
|
exit_code = pre_push.main()
|
|
@@ -317,7 +308,6 @@ def test_main_invokes_gate_with_resolved_base_reference(
|
|
|
317
308
|
encoding="utf-8",
|
|
318
309
|
)
|
|
319
310
|
monkeypatch.setenv("CODE_RULES_GATE_PATH", str(recording_gate_script_path))
|
|
320
|
-
_isolate_code_review_gate(tmp_path, monkeypatch)
|
|
321
311
|
_isolate_from_default_branch_refs(tmp_path, monkeypatch)
|
|
322
312
|
remote_sha = "9" * 40
|
|
323
313
|
monkeypatch.setattr(
|
|
@@ -345,7 +335,6 @@ def test_main_passes_the_pushed_remote_name_to_base_resolution(
|
|
|
345
335
|
# No default-branch ref so the gate base is the stdin remote object, not a
|
|
346
336
|
# merge-base skip; usable-base resolution then sees the pushed remote name.
|
|
347
337
|
_isolate_repository_and_write_passing_code_rules_gate(tmp_path, monkeypatch)
|
|
348
|
-
_isolate_code_review_gate(tmp_path, monkeypatch)
|
|
349
338
|
all_recorded_remote_names: list[str] = []
|
|
350
339
|
|
|
351
340
|
def recording_resolve_usable_base_reference(
|
|
@@ -566,7 +555,6 @@ def test_invoke_gate_uses_resolved_path(
|
|
|
566
555
|
symlink_gate_path.symlink_to(real_gate_path)
|
|
567
556
|
resolved_path = symlink_gate_path.resolve()
|
|
568
557
|
monkeypatch.setenv("CODE_RULES_GATE_PATH", str(symlink_gate_path))
|
|
569
|
-
_isolate_code_review_gate(tmp_path, monkeypatch)
|
|
570
558
|
_isolate_from_default_branch_refs(tmp_path, monkeypatch)
|
|
571
559
|
monkeypatch.setattr(sys, "stdin", io.StringIO(
|
|
572
560
|
f"refs/heads/feature {NON_ZERO_LOCAL_SHA} refs/heads/feature {NON_ZERO_REMOTE_SHA_ONE}\n"
|
|
@@ -684,7 +672,6 @@ def test_main_allows_main_onto_main_push(
|
|
|
684
672
|
passing_gate_path = tmp_path / "gate.py"
|
|
685
673
|
passing_gate_path.write_text("import sys\nsys.exit(0)\n", encoding="utf-8")
|
|
686
674
|
monkeypatch.setenv("CODE_RULES_GATE_PATH", str(passing_gate_path))
|
|
687
|
-
_isolate_code_review_gate(tmp_path, monkeypatch)
|
|
688
675
|
monkeypatch.setattr(
|
|
689
676
|
sys,
|
|
690
677
|
"stdin",
|
|
@@ -698,8 +685,6 @@ def test_main_allows_main_onto_main_push(
|
|
|
698
685
|
assert exit_code == 0
|
|
699
686
|
|
|
700
687
|
|
|
701
|
-
CODE_REVIEW_STUB_BLOCK_REASON: str = "CODE_REVIEW_STUB_BLOCK_REASON"
|
|
702
|
-
|
|
703
688
|
|
|
704
689
|
def _write_passing_code_rules_gate(
|
|
705
690
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
@@ -725,83 +710,6 @@ def _isolate_repository_and_write_passing_code_rules_gate(
|
|
|
725
710
|
_write_passing_code_rules_gate(tmp_path, monkeypatch)
|
|
726
711
|
|
|
727
712
|
|
|
728
|
-
def _write_code_review_gate_stub(tmp_path: Path, returned_reason_literal: str) -> Path:
|
|
729
|
-
stub_gate_path = tmp_path / "code_review_push_gate.py"
|
|
730
|
-
stub_gate_path.write_text(
|
|
731
|
-
"def deny_reason_for_directory(target_directory):\n"
|
|
732
|
-
f" return {returned_reason_literal}\n",
|
|
733
|
-
encoding="utf-8",
|
|
734
|
-
)
|
|
735
|
-
return stub_gate_path
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
def test_main_blocks_when_code_review_gate_returns_reason(
|
|
739
|
-
tmp_path: Path,
|
|
740
|
-
monkeypatch: pytest.MonkeyPatch,
|
|
741
|
-
capsys: pytest.CaptureFixture[str],
|
|
742
|
-
) -> None:
|
|
743
|
-
_isolate_repository_and_write_passing_code_rules_gate(tmp_path, monkeypatch)
|
|
744
|
-
stub_gate_path = _write_code_review_gate_stub(
|
|
745
|
-
tmp_path, repr(CODE_REVIEW_STUB_BLOCK_REASON)
|
|
746
|
-
)
|
|
747
|
-
monkeypatch.setenv("CODE_REVIEW_PUSH_GATE_PATH", str(stub_gate_path))
|
|
748
|
-
|
|
749
|
-
exit_code = pre_push.main()
|
|
750
|
-
|
|
751
|
-
assert exit_code == git_hooks_constants.CODE_REVIEW_STAMP_BLOCK_EXIT_CODE
|
|
752
|
-
captured = capsys.readouterr()
|
|
753
|
-
assert CODE_REVIEW_STUB_BLOCK_REASON in captured.err
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
def test_main_allows_when_code_review_gate_returns_none(
|
|
757
|
-
tmp_path: Path,
|
|
758
|
-
monkeypatch: pytest.MonkeyPatch,
|
|
759
|
-
) -> None:
|
|
760
|
-
_isolate_repository_and_write_passing_code_rules_gate(tmp_path, monkeypatch)
|
|
761
|
-
stub_gate_path = _write_code_review_gate_stub(tmp_path, "None")
|
|
762
|
-
monkeypatch.setenv("CODE_REVIEW_PUSH_GATE_PATH", str(stub_gate_path))
|
|
763
|
-
|
|
764
|
-
exit_code = pre_push.main()
|
|
765
|
-
|
|
766
|
-
assert exit_code == 0
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
def test_main_code_review_check_fails_open_when_gate_module_absent(
|
|
770
|
-
tmp_path: Path,
|
|
771
|
-
monkeypatch: pytest.MonkeyPatch,
|
|
772
|
-
) -> None:
|
|
773
|
-
_isolate_repository_and_write_passing_code_rules_gate(tmp_path, monkeypatch)
|
|
774
|
-
monkeypatch.setenv(
|
|
775
|
-
"CODE_REVIEW_PUSH_GATE_PATH", str(tmp_path / "does_not_exist.py")
|
|
776
|
-
)
|
|
777
|
-
|
|
778
|
-
exit_code = pre_push.main()
|
|
779
|
-
|
|
780
|
-
assert exit_code == 0
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
def test_main_allows_deletion_push_even_when_code_review_gate_would_block(
|
|
784
|
-
tmp_path: Path,
|
|
785
|
-
monkeypatch: pytest.MonkeyPatch,
|
|
786
|
-
) -> None:
|
|
787
|
-
passing_gate_path = tmp_path / "code_rules_gate.py"
|
|
788
|
-
passing_gate_path.write_text("import sys\nsys.exit(0)\n", encoding="utf-8")
|
|
789
|
-
monkeypatch.setenv("CODE_RULES_GATE_PATH", str(passing_gate_path))
|
|
790
|
-
stub_gate_path = _write_code_review_gate_stub(
|
|
791
|
-
tmp_path, repr(CODE_REVIEW_STUB_BLOCK_REASON)
|
|
792
|
-
)
|
|
793
|
-
monkeypatch.setenv("CODE_REVIEW_PUSH_GATE_PATH", str(stub_gate_path))
|
|
794
|
-
deletion_stdin = (
|
|
795
|
-
f"refs/heads/feature {ALL_ZEROS_OBJECT_NAME} "
|
|
796
|
-
f"refs/heads/feature {ALL_ZEROS_OBJECT_NAME}\n"
|
|
797
|
-
)
|
|
798
|
-
monkeypatch.setattr(sys, "stdin", io.StringIO(deletion_stdin))
|
|
799
|
-
|
|
800
|
-
exit_code = pre_push.main()
|
|
801
|
-
|
|
802
|
-
assert exit_code == 0
|
|
803
|
-
|
|
804
|
-
|
|
805
713
|
DEFAULT_BRANCH_NAME: str = "main"
|
|
806
714
|
FEATURE_BRANCH_NAME: str = "feature/example"
|
|
807
715
|
UNPROTECTED_DEFAULT_BRANCH_NAME: str = "develop"
|
|
@@ -1267,7 +1175,6 @@ def test_main_allows_the_push_when_origin_head_names_an_absent_reference(
|
|
|
1267
1175
|
encoding="utf-8",
|
|
1268
1176
|
)
|
|
1269
1177
|
monkeypatch.setenv("CODE_RULES_GATE_PATH", str(recording_gate_script_path))
|
|
1270
|
-
_isolate_code_review_gate(tmp_path, monkeypatch)
|
|
1271
1178
|
monkeypatch.chdir(repository)
|
|
1272
1179
|
monkeypatch.setattr(
|
|
1273
1180
|
sys,
|
|
@@ -1307,7 +1214,6 @@ def test_main_skips_the_gate_when_no_merge_base_resolves(
|
|
|
1307
1214
|
encoding="utf-8",
|
|
1308
1215
|
)
|
|
1309
1216
|
monkeypatch.setenv("CODE_RULES_GATE_PATH", str(recording_gate_script_path))
|
|
1310
|
-
_isolate_code_review_gate(tmp_path, monkeypatch)
|
|
1311
1217
|
monkeypatch.chdir(repository)
|
|
1312
1218
|
monkeypatch.setattr(
|
|
1313
1219
|
sys,
|
|
@@ -1342,7 +1248,6 @@ def test_main_allows_default_branch_file_the_rebase_replayed_onto(
|
|
|
1342
1248
|
)
|
|
1343
1249
|
_set_origin_head(repository, ORIGIN_DEFAULT_BRANCH_REFERENCE)
|
|
1344
1250
|
_write_diff_scoped_gate(tmp_path, monkeypatch)
|
|
1345
|
-
_isolate_code_review_gate(tmp_path, monkeypatch)
|
|
1346
1251
|
monkeypatch.chdir(repository)
|
|
1347
1252
|
monkeypatch.setattr(
|
|
1348
1253
|
sys,
|
|
@@ -1370,7 +1275,6 @@ def test_main_blocks_violation_the_branch_itself_adds(
|
|
|
1370
1275
|
)
|
|
1371
1276
|
_set_origin_head(repository, ORIGIN_DEFAULT_BRANCH_REFERENCE)
|
|
1372
1277
|
_write_diff_scoped_gate(tmp_path, monkeypatch)
|
|
1373
|
-
_isolate_code_review_gate(tmp_path, monkeypatch)
|
|
1374
1278
|
monkeypatch.chdir(repository)
|
|
1375
1279
|
monkeypatch.setattr(
|
|
1376
1280
|
sys,
|
|
@@ -1407,7 +1311,6 @@ def test_main_uses_remote_object_when_no_default_branch_reference_resolves(
|
|
|
1407
1311
|
encoding="utf-8",
|
|
1408
1312
|
)
|
|
1409
1313
|
monkeypatch.setenv("CODE_RULES_GATE_PATH", str(recording_gate_script_path))
|
|
1410
|
-
_isolate_code_review_gate(tmp_path, monkeypatch)
|
|
1411
1314
|
monkeypatch.chdir(repository)
|
|
1412
1315
|
monkeypatch.setattr(
|
|
1413
1316
|
sys,
|
|
@@ -1446,7 +1349,6 @@ def test_main_uses_remote_object_when_the_default_branch_is_pushed(
|
|
|
1446
1349
|
)
|
|
1447
1350
|
_set_origin_head(repository, ORIGIN_DEFAULT_BRANCH_REFERENCE)
|
|
1448
1351
|
_write_diff_scoped_gate(tmp_path, monkeypatch)
|
|
1449
|
-
_isolate_code_review_gate(tmp_path, monkeypatch)
|
|
1450
1352
|
monkeypatch.chdir(repository)
|
|
1451
1353
|
monkeypatch.setattr(
|
|
1452
1354
|
sys,
|
|
@@ -1481,7 +1383,6 @@ def test_main_uses_merge_base_when_a_default_named_local_branch_updates_a_topic_
|
|
|
1481
1383
|
)
|
|
1482
1384
|
_set_origin_head(repository, ORIGIN_UNPROTECTED_DEFAULT_BRANCH_REFERENCE)
|
|
1483
1385
|
_write_diff_scoped_gate(tmp_path, monkeypatch)
|
|
1484
|
-
_isolate_code_review_gate(tmp_path, monkeypatch)
|
|
1485
1386
|
monkeypatch.chdir(repository)
|
|
1486
1387
|
monkeypatch.setattr(
|
|
1487
1388
|
sys,
|
|
@@ -1517,7 +1418,6 @@ def test_main_uses_remote_object_when_a_renamed_local_branch_updates_the_default
|
|
|
1517
1418
|
)
|
|
1518
1419
|
_set_origin_head(repository, ORIGIN_UNPROTECTED_DEFAULT_BRANCH_REFERENCE)
|
|
1519
1420
|
_write_diff_scoped_gate(tmp_path, monkeypatch)
|
|
1520
|
-
_isolate_code_review_gate(tmp_path, monkeypatch)
|
|
1521
1421
|
monkeypatch.chdir(repository)
|
|
1522
1422
|
monkeypatch.setattr(
|
|
1523
1423
|
sys,
|
|
@@ -1547,7 +1447,6 @@ def test_main_reads_default_branch_tracking_reference_when_origin_head_is_unset(
|
|
|
1547
1447
|
branch_file_name=BRANCH_FILE_NAME,
|
|
1548
1448
|
)
|
|
1549
1449
|
_write_diff_scoped_gate(tmp_path, monkeypatch)
|
|
1550
|
-
_isolate_code_review_gate(tmp_path, monkeypatch)
|
|
1551
1450
|
monkeypatch.chdir(repository)
|
|
1552
1451
|
monkeypatch.setattr(
|
|
1553
1452
|
sys,
|
|
@@ -1575,7 +1474,6 @@ def test_main_skips_a_deletion_line_to_reach_the_branch_update(
|
|
|
1575
1474
|
)
|
|
1576
1475
|
_set_origin_head(repository, ORIGIN_DEFAULT_BRANCH_REFERENCE)
|
|
1577
1476
|
_write_diff_scoped_gate(tmp_path, monkeypatch)
|
|
1578
|
-
_isolate_code_review_gate(tmp_path, monkeypatch)
|
|
1579
1477
|
monkeypatch.chdir(repository)
|
|
1580
1478
|
deletion_line = (
|
|
1581
1479
|
f"(delete) {ALL_ZEROS_OBJECT_NAME} refs/heads/retired {NON_ZERO_REMOTE_SHA_ONE}\n"
|
|
@@ -1594,31 +1492,3 @@ def test_main_skips_a_deletion_line_to_reach_the_branch_update(
|
|
|
1594
1492
|
exit_code = pre_push.main()
|
|
1595
1493
|
|
|
1596
1494
|
assert exit_code == 0
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
def test_main_allows_when_code_review_enforcement_flag_is_off(
|
|
1600
|
-
tmp_path: Path,
|
|
1601
|
-
monkeypatch: pytest.MonkeyPatch,
|
|
1602
|
-
) -> None:
|
|
1603
|
-
"""Native pre-push honors the real gate with enforcement off.
|
|
1604
|
-
|
|
1605
|
-
Points at the production push gate (not a reason stub). The gate resolves
|
|
1606
|
-
its flag when its config module first executes, so this test clears the
|
|
1607
|
-
environment variable and evicts that one module if a sibling suite cached
|
|
1608
|
-
it earlier in the same session. The gate then returns no deny reason and
|
|
1609
|
-
the backstop allows the push.
|
|
1610
|
-
"""
|
|
1611
|
-
monkeypatch.delenv(CODE_REVIEW_ENFORCEMENT_ENV_VAR, raising=False)
|
|
1612
|
-
monkeypatch.delitem(sys.modules, ENFORCEMENT_CONSTANTS_MODULE_NAME, raising=False)
|
|
1613
|
-
_isolate_repository_and_write_passing_code_rules_gate(tmp_path, monkeypatch)
|
|
1614
|
-
real_gate_path = (
|
|
1615
|
-
Path(__file__).resolve().parent.parent
|
|
1616
|
-
/ "blocking"
|
|
1617
|
-
/ "code_review_push_gate.py"
|
|
1618
|
-
)
|
|
1619
|
-
assert real_gate_path.is_file()
|
|
1620
|
-
monkeypatch.setenv("CODE_REVIEW_PUSH_GATE_PATH", str(real_gate_path))
|
|
1621
|
-
|
|
1622
|
-
exit_code = pre_push.main()
|
|
1623
|
-
|
|
1624
|
-
assert exit_code == 0
|
package/hooks/hooks.json
CHANGED
|
@@ -42,16 +42,6 @@
|
|
|
42
42
|
}
|
|
43
43
|
]
|
|
44
44
|
},
|
|
45
|
-
{
|
|
46
|
-
"matcher": "mcp__plugin_github_github__create_pull_request",
|
|
47
|
-
"hooks": [
|
|
48
|
-
{
|
|
49
|
-
"type": "command",
|
|
50
|
-
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/code_review_pr_create_gate.py",
|
|
51
|
-
"timeout": 20
|
|
52
|
-
}
|
|
53
|
-
]
|
|
54
|
-
},
|
|
55
45
|
{
|
|
56
46
|
"matcher": "mcp__plugin_github_github__.*",
|
|
57
47
|
"hooks": [
|
|
@@ -67,24 +57,9 @@
|
|
|
67
57
|
}
|
|
68
58
|
]
|
|
69
59
|
},
|
|
70
|
-
{
|
|
71
|
-
"matcher": "Agent",
|
|
72
|
-
"hooks": [
|
|
73
|
-
{
|
|
74
|
-
"type": "command",
|
|
75
|
-
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/pr_converge_bugteam_enforcer.py",
|
|
76
|
-
"timeout": 10
|
|
77
|
-
}
|
|
78
|
-
]
|
|
79
|
-
},
|
|
80
60
|
{
|
|
81
61
|
"matcher": "Agent|Task",
|
|
82
62
|
"hooks": [
|
|
83
|
-
{
|
|
84
|
-
"type": "command",
|
|
85
|
-
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/code_verifier_spawn_preflight_gate.py",
|
|
86
|
-
"timeout": 30
|
|
87
|
-
},
|
|
88
63
|
{
|
|
89
64
|
"type": "command",
|
|
90
65
|
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/fable_spawn_gate.py",
|
|
@@ -102,16 +77,6 @@
|
|
|
102
77
|
}
|
|
103
78
|
]
|
|
104
79
|
},
|
|
105
|
-
{
|
|
106
|
-
"matcher": "Skill",
|
|
107
|
-
"hooks": [
|
|
108
|
-
{
|
|
109
|
-
"type": "command",
|
|
110
|
-
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/lifecycle/pr_converge_bugteam_skill_tracker.py",
|
|
111
|
-
"timeout": 10
|
|
112
|
-
}
|
|
113
|
-
]
|
|
114
|
-
},
|
|
115
80
|
{
|
|
116
81
|
"matcher": "AskUserQuestion",
|
|
117
82
|
"hooks": [
|
|
@@ -18,10 +18,7 @@ Shared constant modules imported by hooks throughout the `hooks/` tree. Each fil
|
|
|
18
18
|
| `enter_worktree_prefetch_constants.py` | Tool name, origin-remote ref names, and git-command timeouts for the EnterWorktree origin-prefetch hook |
|
|
19
19
|
| `env_var_table_code_drift_constants.py` | Table patterns, env-var-name and code-file recognizers, scan budget, and block-message text for the env-var-table code-drift blocker |
|
|
20
20
|
| `code_rules_path_utils_constants.py` | Path-matching helpers used by the code-rules check modules |
|
|
21
|
-
| `code_verifier_spawn_preflight_gate_constants.py` | Subagent type, merge-tree command flags, timeouts, and deny-message text for the code-verifier spawn pre-flight gate |
|
|
22
21
|
| `command_dispatch_constants.py` | Command-word regex, command-key access pattern, tokenization pattern, and anchors for the unanchored command-dispatch meta-gate |
|
|
23
|
-
| `convergence_branch_constants.py` | Branch and worktree naming patterns for the convergence gate |
|
|
24
|
-
| `convergence_gate_blocker_constants.py` | The full-PR-URL and `--repo`/`-R` flag parse patterns that key the convergence gate to the PR its command names |
|
|
25
22
|
| `conventional_pr_title_gate_constants.py` | Bash tool name, gh executable basenames and pr create/edit subcommand tokens, title/repo flag names, semantic-title-CI workflow markers, the Conventional Commits type list and title pattern, the semantic-pull-request action `types:` input pattern, and block-message text for the conventional-PR-title gate |
|
|
26
23
|
| `dead_argparse_argument_constants.py` | Patterns for detecting unused argparse arguments |
|
|
27
24
|
| `dead_config_field_constants.py` | Patterns for detecting unused `*Config` / `*Selectors` dataclass fields |
|
|
@@ -59,10 +56,6 @@ Shared constant modules imported by hooks throughout the `hooks/` tree. Each fil
|
|
|
59
56
|
| `piped_pytest_blocker_constants.py` | Bash tool name, pytest program basenames and the python-interpreter basename pattern, the pipe and segment-reset operator token sets, and the deny message for the piped-pytest blocker; classification that consumes those program and wrapper constants lives in `pytest_invocation.py` |
|
|
60
57
|
| `pytest_invocation.py` | Pure parsed-segment classifier for pytest runs through interpreters, shims, wrappers, and shell string-exec forms; no pipeline deny decision |
|
|
61
58
|
| `plain_language_blocker_constants.py` | The list of heavy words and their everyday replacements |
|
|
62
|
-
| `pr_converge_bugteam_enforcer_constants.py` | State keys and timing config for the bugteam-parallel enforcer |
|
|
63
|
-
| `pr_converge_bugteam_enforcer_state.py` | State-file helpers for the bugteam enforcer |
|
|
64
|
-
| `pr_description_enforcer_constants.py` | PR-description shape rules and command patterns |
|
|
65
|
-
| `pr_description_proof_of_work_constants.py` | Proof-part detection patterns, gh command tokens, and corrective messages for the proof-of-work comment audit |
|
|
66
59
|
| `prose_matcher_precision_constants.py` | Sample floor, keep/narrow precision floors, matcher ids, label decisions, advisory log path, emit caps, and context fingerprint length for OP-07B prose-matcher advisory telemetry |
|
|
67
60
|
| `pre_tool_use_stdin.py` | `read_hook_input_dictionary_from_stdin()` — shared stdin parser for PreToolUse hooks |
|
|
68
61
|
| `precommit_code_rules_gate_constants.py` | Scope argument and exit-code constants for the precommit gate |
|
|
@@ -70,7 +63,6 @@ Shared constant modules imported by hooks throughout the `hooks/` tree. Each fil
|
|
|
70
63
|
| `pyproject_config_discovery_constants.py` | Table names (``tool`` key, ``mypy``, ``ruff``) for resolving a validator's config from a pyproject.toml `[tool.<name>]` table |
|
|
71
64
|
| `pytest_testpaths_orphan_blocker_constants.py` | Marker filename, section and key names, test-file pattern, search budget, and block-message text for the pytest unregistered-test-directory blocker |
|
|
72
65
|
| `python_style_checks_constants.py` | Command-line argument count and blank-line count between top-level functions for the style validator |
|
|
73
|
-
| `reviewer_spawn_gate_constants.py` | Bash tool name, the sentinel marker, the Copilot and Bugbot trigger markers, the availability-script relative path and override env-var name, and the deny-message template for the reviewer-spawn gate |
|
|
74
66
|
| `ruff_integration_constants.py` | ``NO_COLOR`` / ``FORCE_COLOR`` environment variable names for plain ruff diagnostics |
|
|
75
67
|
| `send_user_file_open_locally_blocker_constants.py` | Tool name, proactive status, and the block message for the open-locally attach blocker |
|
|
76
68
|
| `sensitive_file_protector_constants.py` | Sensitive filename patterns, the committed-template suffixes that earn an exemption, the write/edit tool names, and the deny decision and message template for `sensitive_file_protector` |
|
|
@@ -110,4 +102,3 @@ Shared constant modules imported by hooks throughout the `hooks/` tree. Each fil
|
|
|
110
102
|
- Tests for these modules live beside them as `test_<module>.py`. Run with `python -m pytest hooks_constants/test_<name>.py`.
|
|
111
103
|
- `dynamic_stderr_handler.py`, `pre_tool_use_stdin.py`, `multi_edit_reconstruction.py`, `hosted_hook_runner.py`, and `text_stripping.py` are utility modules (not pure constants) but live here because they hold logic shared across hook surfaces.
|
|
112
104
|
- `local_identity.py` is a loader: it reads the environment or `~/.claude/local-identity.json` (its path overridable via `CLAUDE_LOCAL_IDENTITY_PATH`) to resolve private NAS values (with committed placeholder defaults), the PII commit-scan exempt-repo slug set (`CLAUDE_PII_EXEMPT_REPOS` / `pii_exempt_repositories`), and the per-repository allowlisted-values mapping (`pii_allowlisted_values`).
|
|
113
|
-
|
|
@@ -67,21 +67,10 @@ ALL_BASH_HOSTED_HOOK_ENTRIES: tuple[BashHostedHookEntry, ...] = (
|
|
|
67
67
|
"blocking/pii_prevention_blocker.py", ALL_BASH_AND_POWERSHELL_TOOL_NAMES
|
|
68
68
|
),
|
|
69
69
|
BashHostedHookEntry("blocking/conventional_pr_title_gate.py", ALL_BASH_ONLY_TOOL_NAMES),
|
|
70
|
-
BashHostedHookEntry("blocking/reviewer_spawn_gate.py", ALL_BASH_ONLY_TOOL_NAMES),
|
|
71
70
|
BashHostedHookEntry("blocking/block_main_commit.py", ALL_BASH_ONLY_TOOL_NAMES),
|
|
72
71
|
BashHostedHookEntry("blocking/precommit_code_rules_gate.py", ALL_BASH_ONLY_TOOL_NAMES),
|
|
73
72
|
BashHostedHookEntry("blocking/session_edit_stage_gate.py", ALL_BASH_ONLY_TOOL_NAMES),
|
|
74
|
-
BashHostedHookEntry("blocking/pr_description_enforcer.py", ALL_BASH_ONLY_TOOL_NAMES),
|
|
75
73
|
BashHostedHookEntry("blocking/test_preflight_check.py", ALL_BASH_ONLY_TOOL_NAMES),
|
|
76
|
-
BashHostedHookEntry("blocking/convergence_gate_blocker.py", ALL_BASH_ONLY_TOOL_NAMES),
|
|
77
74
|
BashHostedHookEntry("blocking/windows_rmtree_blocker.py", ALL_BASH_ONLY_TOOL_NAMES),
|
|
78
75
|
BashHostedHookEntry("blocking/gh_pr_author_enforcer.py", ALL_BASH_ONLY_TOOL_NAMES),
|
|
79
|
-
BashHostedHookEntry("blocking/code_review_push_gate.py", ALL_BASH_AND_POWERSHELL_TOOL_NAMES),
|
|
80
|
-
BashHostedHookEntry(
|
|
81
|
-
"blocking/code_review_pr_create_gate.py", ALL_BASH_AND_POWERSHELL_TOOL_NAMES
|
|
82
|
-
),
|
|
83
|
-
BashHostedHookEntry(
|
|
84
|
-
"blocking/code_review_stamp_directory_write_blocker.py",
|
|
85
|
-
ALL_BASH_AND_POWERSHELL_TOOL_NAMES,
|
|
86
|
-
),
|
|
87
76
|
)
|
|
@@ -11,16 +11,15 @@ into a retry prompt authorizes nothing.
|
|
|
11
11
|
claude-sonnet-4 -> ['claude', 'sonnet', '4'] ok: another tier
|
|
12
12
|
|
|
13
13
|
Every literal the gate body reads lives here. ``AGENT_TOOL_NAME`` and
|
|
14
|
-
``TASK_TOOL_NAME``
|
|
15
|
-
gates share one source for both tool names.
|
|
14
|
+
``TASK_TOOL_NAME`` name the spawn tools the gate evaluates.
|
|
16
15
|
"""
|
|
17
16
|
|
|
18
17
|
from __future__ import annotations
|
|
19
18
|
|
|
20
19
|
import re
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
AGENT_TOOL_NAME: str = "Agent"
|
|
22
|
+
TASK_TOOL_NAME: str = "Task"
|
|
24
23
|
|
|
25
24
|
ALL_SPAWN_TOOL_NAMES: frozenset[str] = frozenset({AGENT_TOOL_NAME, TASK_TOOL_NAME})
|
|
26
25
|
|
|
@@ -86,10 +86,6 @@ ALL_HOSTED_HOOK_ENTRIES: tuple[HostedHookEntry, ...] = (
|
|
|
86
86
|
script_relative_path="blocking/sensitive_file_protector.py",
|
|
87
87
|
applicable_tool_names=ALL_WRITE_AND_EDIT_TOOL_NAMES,
|
|
88
88
|
),
|
|
89
|
-
HostedHookEntry(
|
|
90
|
-
script_relative_path="blocking/code_review_stamp_directory_write_blocker.py",
|
|
91
|
-
applicable_tool_names=ALL_WRITE_EDIT_MULTI_EDIT_TOOL_NAMES,
|
|
92
|
-
),
|
|
93
89
|
HostedHookEntry(
|
|
94
90
|
script_relative_path="blocking/pii_prevention_blocker.py",
|
|
95
91
|
applicable_tool_names=ALL_WRITE_EDIT_MULTI_EDIT_TOOL_NAMES,
|
|
@@ -131,10 +127,6 @@ ALL_HOSTED_HOOK_ENTRIES: tuple[HostedHookEntry, ...] = (
|
|
|
131
127
|
script_relative_path="blocking/hook_prose_detector_consistency.py",
|
|
132
128
|
applicable_tool_names=ALL_WRITE_AND_EDIT_TOOL_NAMES,
|
|
133
129
|
),
|
|
134
|
-
HostedHookEntry(
|
|
135
|
-
script_relative_path="blocking/verified_commit_message_accuracy_blocker.py",
|
|
136
|
-
applicable_tool_names=ALL_WRITE_AND_EDIT_TOOL_NAMES,
|
|
137
|
-
),
|
|
138
130
|
HostedHookEntry(
|
|
139
131
|
script_relative_path="blocking/workflow_substitution_slot_blocker.py",
|
|
140
132
|
applicable_tool_names=ALL_WRITE_EDIT_MULTI_EDIT_TOOL_NAMES,
|
|
@@ -7,12 +7,7 @@ PROACTIVE_STATUS: str = "proactive"
|
|
|
7
7
|
CORRECTIVE_MESSAGE: str = (
|
|
8
8
|
"BLOCKED [open-locally]: SendUserFile attaches a file to the session, which "
|
|
9
9
|
"does not let the user see it while they are at the terminal. Open the file on "
|
|
10
|
-
"screen
|
|
11
|
-
" Start-Process pwsh -WindowStyle Hidden -ArgumentList "
|
|
12
|
-
"'-NoProfile','-File',\"$HOME\\.claude\\scripts\\Show-Asset.ps1\","
|
|
13
|
-
"'<path 1>','<path 2>'\n"
|
|
14
|
-
"Show-Asset.ps1 sizes each image window to the image and opens every other file "
|
|
15
|
-
"type in its default app. Pass every path the user named.\n"
|
|
10
|
+
"screen with its native Windows app. Open every path the user named.\n"
|
|
16
11
|
"The one allowed attach is a phone push: when the user has stepped away and you "
|
|
17
12
|
'want the file to reach their phone, call SendUserFile with status "proactive".'
|
|
18
13
|
)
|
|
@@ -32,26 +32,17 @@ _EXPECTED_BASH_ORDER = (
|
|
|
32
32
|
"blocking/volatile_path_in_post_blocker.py",
|
|
33
33
|
"blocking/pii_prevention_blocker.py",
|
|
34
34
|
"blocking/conventional_pr_title_gate.py",
|
|
35
|
-
"blocking/reviewer_spawn_gate.py",
|
|
36
35
|
"blocking/block_main_commit.py",
|
|
37
36
|
"blocking/precommit_code_rules_gate.py",
|
|
38
37
|
"blocking/session_edit_stage_gate.py",
|
|
39
|
-
"blocking/pr_description_enforcer.py",
|
|
40
38
|
"blocking/test_preflight_check.py",
|
|
41
|
-
"blocking/convergence_gate_blocker.py",
|
|
42
39
|
"blocking/windows_rmtree_blocker.py",
|
|
43
40
|
"blocking/gh_pr_author_enforcer.py",
|
|
44
|
-
"blocking/code_review_push_gate.py",
|
|
45
|
-
"blocking/code_review_pr_create_gate.py",
|
|
46
|
-
"blocking/code_review_stamp_directory_write_blocker.py",
|
|
47
41
|
)
|
|
48
42
|
|
|
49
43
|
_POWERSHELL_APPLICABLE = (
|
|
50
44
|
"blocking/unscoped_search_blocker.py",
|
|
51
45
|
"blocking/pii_prevention_blocker.py",
|
|
52
|
-
"blocking/code_review_push_gate.py",
|
|
53
|
-
"blocking/code_review_pr_create_gate.py",
|
|
54
|
-
"blocking/code_review_stamp_directory_write_blocker.py",
|
|
55
46
|
)
|
|
56
47
|
|
|
57
48
|
|
|
@@ -70,7 +61,7 @@ def test_every_hook_applies_to_the_bash_tool() -> None:
|
|
|
70
61
|
|
|
71
62
|
|
|
72
63
|
def test_powershell_applicable_hooks_include_the_shared_gates() -> None:
|
|
73
|
-
"""PowerShell runs the unscoped-search and PII gates
|
|
64
|
+
"""PowerShell runs the shared unscoped-search and PII gates."""
|
|
74
65
|
powershell_hooks = tuple(
|
|
75
66
|
each_entry.script_relative_path
|
|
76
67
|
for each_entry in ALL_BASH_HOSTED_HOOK_ENTRIES
|
|
@@ -86,22 +77,6 @@ def test_powershell_hooks_carry_the_shared_tool_set() -> None:
|
|
|
86
77
|
assert each_entry.applicable_tool_names == ALL_BASH_AND_POWERSHELL_TOOL_NAMES
|
|
87
78
|
|
|
88
79
|
|
|
89
|
-
def test_code_review_gate_family_registered_for_bash_and_powershell() -> None:
|
|
90
|
-
"""The three code-review shell gates run on both Bash and PowerShell."""
|
|
91
|
-
code_review_gate_paths = (
|
|
92
|
-
"blocking/code_review_push_gate.py",
|
|
93
|
-
"blocking/code_review_pr_create_gate.py",
|
|
94
|
-
"blocking/code_review_stamp_directory_write_blocker.py",
|
|
95
|
-
)
|
|
96
|
-
entry_by_path = {
|
|
97
|
-
each_entry.script_relative_path: each_entry
|
|
98
|
-
for each_entry in ALL_BASH_HOSTED_HOOK_ENTRIES
|
|
99
|
-
}
|
|
100
|
-
for each_gate_path in code_review_gate_paths:
|
|
101
|
-
assert each_gate_path in entry_by_path
|
|
102
|
-
assert entry_by_path[each_gate_path].applicable_tool_names == (
|
|
103
|
-
ALL_BASH_AND_POWERSHELL_TOOL_NAMES
|
|
104
|
-
)
|
|
105
80
|
|
|
106
81
|
|
|
107
82
|
def test_every_roster_path_points_at_an_existing_hook() -> None:
|
|
@@ -63,11 +63,6 @@ def test_duplicate_rmtree_helper_blocker_runs_via_runpy() -> None:
|
|
|
63
63
|
assert entry.native_module_name is None
|
|
64
64
|
|
|
65
65
|
|
|
66
|
-
def test_code_review_stamp_write_blocker_registered_for_all_file_tools() -> None:
|
|
67
|
-
entry = _entry_for("blocking/code_review_stamp_directory_write_blocker.py")
|
|
68
|
-
assert entry is not None
|
|
69
|
-
assert {"Write", "Edit", "MultiEdit"} <= entry.applicable_tool_names
|
|
70
|
-
|
|
71
66
|
|
|
72
67
|
def test_windows_rmtree_blocker_still_registered() -> None:
|
|
73
68
|
entry = _entry_for("blocking/windows_rmtree_blocker.py")
|
|
@@ -8,13 +8,11 @@ Hooks that run at session or config-change boundaries rather than on individual
|
|
|
8
8
|
|---|---|---|
|
|
9
9
|
| `config_change_guard.py` | PostToolUse (Write/Edit on `settings.json`) | Counts hooks in the edited `settings.json` and logs any change to `~/.claude/cache/config-change-audit.log`; alerts when the hook count drops below the last known value |
|
|
10
10
|
| `enter_worktree_origin_prefetch.py` | PreToolUse (EnterWorktree) | Fetches origin's default branch before a worktree creation call, keeping the `refs/remotes/origin/<default-branch>` ref that `fresh` mode reads current; never blocks on fetch failure |
|
|
11
|
-
| `pr_converge_bugteam_skill_tracker.py` | PreToolUse (Skill) | Tracks which bugteam skill runs have completed within a pr-converge loop, so the enforcer can verify parallel execution |
|
|
12
11
|
| `session_end_cleanup.py` | SessionEnd | Purges stale cache entries from `~/.claude/cache/` (entries older than the configured threshold) and old backup files |
|
|
13
12
|
| `test_config_change_guard.py` | — | Tests for `config_change_guard.py` |
|
|
14
13
|
| `test_enter_worktree_origin_prefetch.py` | — | Tests for `enter_worktree_origin_prefetch.py` |
|
|
15
|
-
| `test_pr_converge_bugteam_skill_tracker.py` | — | Tests for `pr_converge_bugteam_skill_tracker.py` |
|
|
16
14
|
|
|
17
15
|
## Conventions
|
|
18
16
|
|
|
19
|
-
- Constants for these hooks (stale-age threshold, cache directory, known-hook-count file, EnterWorktree prefetch tuning) live in `hooks_constants/session_env_cleanup_constants.py
|
|
17
|
+
- Constants for these hooks (stale-age threshold, cache directory, known-hook-count file, EnterWorktree prefetch tuning) live in `hooks_constants/session_env_cleanup_constants.py` and `hooks_constants/enter_worktree_prefetch_constants.py`.
|
|
20
18
|
- Tests run with `python -m pytest lifecycle/test_<name>.py`.
|
package/package.json
CHANGED
package/rules/CLAUDE.md
CHANGED
|
@@ -45,13 +45,11 @@ Rule files installed into `~/.claude/rules/` by `bin/install.mjs`. A rule withou
|
|
|
45
45
|
| `plain-illustrative-docstrings.md` | Public docstring narrative reads plainly and shows behavior with a diagram block (a `::` example or a doctest), painting a concrete scene a general developer follows on first read; a run-on backstop hook, a prose-wall backstop hook, and Category O9 audit enforce it |
|
|
46
46
|
| `plain-language.md` | Everyday words, short active sentences, lead with the answer |
|
|
47
47
|
| `prompt-workflow-context-controls.md` | Keep prompt-workflow instruction layers small and stable; load heavy skills on demand |
|
|
48
|
-
| `proof-of-work-pr-comments.md` | Every PR carries one five-part proof-of-work comment before it leaves draft; the `pr_description_enforcer` hook audits proof-shaped comments and gates `gh pr ready` |
|
|
49
48
|
| `re-stage-before-commit.md` | Stage the files edited this session before `git commit`; the session edit stage gate denies a commit that leaves a tracked session edit unstaged, with `-a`, a pathspec, a preceding `git add`, and `# partial-commit` as escapes |
|
|
50
49
|
| `research-mode.md` | Three anti-hallucination constraints: say "I don't know", verify with citations, quote for factual grounding |
|
|
51
50
|
| `shell-invocation.md` | Windows shell commands run through `pwsh`; no `$(...)`, backtick, or process substitution in a Bash tool command |
|
|
52
51
|
| `testing.md` | Test quality and infrastructure standards |
|
|
53
52
|
| `vault-context.md` | Search Obsidian vault for prior sessions and decisions before substantive project work |
|
|
54
|
-
| `verified-commit-gate-skip.md` | Code-verifier review is optional; commits and pushes use the checks selected for the task |
|
|
55
53
|
| `verify-before-asking.md` | Answer questions by inspecting files or running tools before asking; recalled facts expire until re-checked this session |
|
|
56
54
|
| `verify-runtime-state.md` | A "component is fine / not at fault" verdict rests on a live probe this session, never code reading or prior-session memory |
|
|
57
55
|
| `windows-filesystem-safe.md` | Use safe `rmtree` patterns on Windows; `mkdirSync` with `recursive: true` on possibly-existing paths |
|
|
@@ -24,4 +24,3 @@ Single-object endpoints (`pulls/<n>`, `issues/<n>`) skip pagination and may use
|
|
|
24
24
|
|
|
25
25
|
- [`destructive-commands.md`](destructive-commands.md) — why a body describing `rm -rf` must travel by file path.
|
|
26
26
|
- [`durable-post-artifacts.md`](durable-post-artifacts.md) — what a post body may reference.
|
|
27
|
-
- [`proof-of-work-pr-comments.md`](proof-of-work-pr-comments.md) — what the proof comment must contain.
|
package/rules/git-workflow.md
CHANGED
|
@@ -41,7 +41,7 @@ User-level rule: applies to **every** git repo that uses GitHub with `gh` (no ex
|
|
|
41
41
|
| `SESSION_STATE.md` | Local session state |
|
|
42
42
|
| `*.png *.jpg *.jpeg *.gif *.webp *.avif *.svg *.ico` | Images go to external storage, not GitHub |
|
|
43
43
|
|
|
44
|
-
An image a PR needs as visual evidence is not an exception to that row. Upload it to the repository's durable `artifacts` release with `python3 ~/.claude/scripts/gh_artifact_upload.py <file> <owner/repo>` and embed the permanent URL in the
|
|
44
|
+
An image a PR needs as visual evidence is not an exception to that row. Upload it to the repository's durable `artifacts` release with `python3 ~/.claude/scripts/gh_artifact_upload.py <file> <owner/repo>` and embed the permanent URL in the PR comment. The image lives on GitHub without entering the repository tree.
|
|
45
45
|
|
|
46
46
|
## Responding to review feedback
|
|
47
47
|
|
|
@@ -60,9 +60,7 @@ Repair only reported findings, then re-verify after every repair.
|
|
|
60
60
|
| Rule | Covers |
|
|
61
61
|
|---|---|
|
|
62
62
|
| [`gh-cli-conventions.md`](gh-cli-conventions.md) | `--body-file` for post bodies; `--paginate --slurp` for list reads |
|
|
63
|
-
| [`proof-of-work-pr-comments.md`](proof-of-work-pr-comments.md) | The five-part proof comment every PR carries before leaving draft |
|
|
64
63
|
| [`re-stage-before-commit.md`](re-stage-before-commit.md) | Staging session edits so a commit does not drop them |
|
|
65
|
-
| [`verified-commit-gate-skip.md`](verified-commit-gate-skip.md) | Optional code-verifier review guidance |
|
|
66
64
|
| [`durable-post-artifacts.md`](durable-post-artifacts.md) | Keeping volatile scratch paths out of a post body |
|
|
67
65
|
| [`destructive-commands.md`](destructive-commands.md) | Allowed removal forms; destructive literals in commit and post bodies |
|
|
68
66
|
| [`code-standards.md`](code-standards.md) | The code standards a PR's diff is reviewed against |
|
package/scripts/CLAUDE.md
CHANGED
|
@@ -28,7 +28,6 @@ Utility scripts installed into `~/.claude/scripts/` by `bin/install.mjs`. Each s
|
|
|
28
28
|
| `Install-SweepEmptyDirs.ps1` | Registers `sweep_empty_dirs.py` as a scheduled task on Windows |
|
|
29
29
|
| `Sync-RepoMain.ps1` | Fast-forwards a main-tracking mirror (`-RepoPath`, required) to its remote main using `merge --ff-only`; refuses a different checkout or a diverged branch, logs each run to `-LogPath`, and exits non-zero so a scheduled-task history row surfaces the problem. `-StashDirty` stashes tracked changes right before the fast-forward under a dated `pre-sync stash` message and leaves the stash in the list |
|
|
30
30
|
| `check.ps1` | Runs the full code-quality check suite |
|
|
31
|
-
| `Show-Asset.ps1` | Opens files on screen, sizing each image window to the image's pixel dimensions (scaled to fit the screen); non-image files open in their default application |
|
|
32
31
|
| `Get-SessionAccount.ps1` | Reports which Claude account the current session is actually logged into by comparing `~/.claude.json`'s CLI login against a `CLAUDE_USER_DATA_DIR` desktop profile's `lastKnownAccountUuid`, recovering the desktop account's email from profile storage when the two accounts differ |
|
|
33
32
|
| `Capture-PoolHealth.ps1` | Captures Windows memory pool counters, high-handle processes, and kernel pool tags (via `NtQuerySystemInformation` class 22), prints a threshold verdict with a remediation map, and exits non-zero when any alert threshold fires |
|
|
34
33
|
|