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.
Files changed (203) hide show
  1. package/_shared/advisor/reference/sol-rung.md +5 -2
  2. package/_shared/advisor/reference/warm-up.md +1 -0
  3. package/_shared/advisor/scripts/codex_sol_advisor.py +96 -31
  4. package/_shared/advisor/scripts/config/advisor_scripts_constants/sol_advisor_constants.py +5 -0
  5. package/_shared/advisor/scripts/tests/test_codex_sol_advisor.py +173 -11
  6. package/_shared/pr-loop/scripts/code_rules_gate.py +2 -3
  7. package/_shared/pr-loop/scripts/test_code_rules_gate.py +0 -26
  8. package/agents/CLAUDE.md +1 -2
  9. package/agents/pr-description-writer.md +11 -81
  10. package/bin/install.mjs +48 -9
  11. package/bin/install.prune.test.mjs +26 -0
  12. package/bin/install.settings-defaults.test.mjs +60 -33
  13. package/bin/install.test.mjs +2 -4
  14. package/commands/CLAUDE.md +1 -0
  15. package/commands/sr-loop.md +52 -0
  16. package/docs/CLAUDE.md +0 -1
  17. package/docs/references/CLAUDE.md +0 -1
  18. package/hooks/CLAUDE.md +1 -1
  19. package/hooks/blocking/CLAUDE.md +2 -27
  20. package/hooks/blocking/config/CLAUDE.md +1 -13
  21. package/hooks/blocking/conventional_pr_title_gate.py +1 -2
  22. package/hooks/blocking/send_user_file_open_locally_blocker.py +1 -1
  23. package/hooks/blocking/stop_dispatcher.py +4 -4
  24. package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +0 -3
  25. package/hooks/blocking/test_pre_tool_use_dispatcher.py +6 -7
  26. package/hooks/blocking/test_send_user_file_open_locally_blocker.py +4 -3
  27. package/hooks/blocking/test_shared_stdin_adoption.py +0 -2
  28. package/hooks/diagnostic/CLAUDE.md +3 -3
  29. package/hooks/diagnostic/hook_log_extractor.py +2 -36
  30. package/hooks/diagnostic/hook_log_stop_wrapper.py +6 -155
  31. package/hooks/diagnostic/test_hook_log_extractor.py +8 -21
  32. package/hooks/diagnostic/test_hook_log_stop_wrapper.py +3 -331
  33. package/hooks/git-hooks/git_hooks_constants/__init__.py +0 -6
  34. package/hooks/git-hooks/pre_push.py +3 -85
  35. package/hooks/git-hooks/test_pre_push.py +0 -130
  36. package/hooks/hooks.json +0 -35
  37. package/hooks/hooks_constants/CLAUDE.md +0 -9
  38. package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +0 -11
  39. package/hooks/hooks_constants/convergence_branch_constants.py +0 -1
  40. package/hooks/hooks_constants/fable_spawn_gate_constants.py +3 -4
  41. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +0 -8
  42. package/hooks/hooks_constants/send_user_file_open_locally_blocker_constants.py +1 -6
  43. package/hooks/hooks_constants/stop_dispatcher_constants.py +0 -1
  44. package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +1 -26
  45. package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +0 -5
  46. package/hooks/hooks_constants/test_stop_dispatcher_constants.py +0 -1
  47. package/hooks/lifecycle/CLAUDE.md +1 -3
  48. package/package.json +1 -1
  49. package/rules/CLAUDE.md +0 -2
  50. package/rules/gh-cli-conventions.md +0 -1
  51. package/rules/git-workflow.md +1 -3
  52. package/scripts/CLAUDE.md +0 -1
  53. package/scripts/_code_review_test_support.py +0 -95
  54. package/scripts/dev_env_scripts_constants/code_review_constants.py +9 -98
  55. package/scripts/dev_env_scripts_constants/test_code_review_constants.py +1 -44
  56. package/scripts/invoke_code_review.py +8 -451
  57. package/scripts/test_invoke_code_review.py +5 -61
  58. package/scripts/test_invoke_code_review_cli.py +1 -45
  59. package/scripts/test_invoke_code_review_contract.py +1 -118
  60. package/settings.json +1 -10
  61. package/skills/CLAUDE.md +8 -0
  62. package/skills/autoconverge/reference/CLAUDE.md +1 -1
  63. package/skills/autoconverge/reference/convergence.md +7 -7
  64. package/skills/autoconverge/reference/gotchas.md +3 -10
  65. package/skills/autoconverge/workflow/converge.contract.test.mjs +53 -1388
  66. package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +4 -3
  67. package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +33 -493
  68. package/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +15 -97
  69. package/skills/autoconverge/workflow/converge.mjs +201 -238
  70. package/skills/autoconverge/workflow/converge.precatch.test.mjs +1 -1
  71. package/skills/bugteam/CONSTRAINTS.md +1 -1
  72. package/skills/bugteam/SKILL.md +9 -0
  73. package/skills/bugteam/reference/audit-and-teammates.md +10 -6
  74. package/skills/bugteam/test_skill_additions.py +9 -0
  75. package/skills/comments/SKILL.md +65 -0
  76. package/skills/descriptions/SKILL.md +51 -0
  77. package/skills/emergencies/SKILL.md +42 -0
  78. package/skills/imagegen/SKILL.md +21 -0
  79. package/skills/imagegen/scripts/config/__init__.py +1 -0
  80. package/skills/imagegen/scripts/config/constants.py +28 -0
  81. package/skills/imagegen/scripts/imagegen.py +45 -0
  82. package/skills/imagegen/scripts/imagegen_core.py +528 -0
  83. package/skills/imagegen/scripts/test_imagegen.py +204 -0
  84. package/skills/orchestrator/SKILL.md +2 -4
  85. package/skills/plan-to-pr/SKILL.md +6 -7
  86. package/skills/plan-to-pr/reference/final-validation-tasks.md +1 -1
  87. package/skills/plan-to-pr/reference/process-inventory.md +2 -2
  88. package/skills/plan-to-pr/reference/review-loop.md +2 -2
  89. package/skills/plan-to-pr/reference/run-record.schema.json +1 -1
  90. package/skills/plan-to-pr/reference/task-seeds.md +1 -1
  91. package/skills/plan-to-pr/reference/task-ticket.md +3 -4
  92. package/skills/plan-to-pr/scripts/config/constants.py +1 -2
  93. package/skills/plan-to-pr/scripts/test_validate_protocol.py +1 -1
  94. package/skills/plan-to-pr/test_skill_contract.py +2 -2
  95. package/skills/pr-converge/SKILL.md +13 -16
  96. package/skills/pr-converge/reference/progress-checklist.md +6 -5
  97. package/skills/pr-converge/reference/state-schema.md +1 -27
  98. package/skills/pr-loop-cloud-transport/SKILL.md +1 -1
  99. package/skills/pr-loop-cloud-transport/reference/identity-and-hooks.md +3 -3
  100. package/skills/pr-loop-lifecycle/SKILL.md +1 -1
  101. package/skills/pr-loop-lifecycle/reference/teardown-publish-permissions.md +1 -1
  102. package/skills/prototype/SKILL.md +4 -4
  103. package/skills/prototype/reference/honest-limitations.md +4 -4
  104. package/skills/prototype/reference/promotion-tasks.md +2 -2
  105. package/skills/prototype/workflows/promotion.md +4 -6
  106. package/skills/reviews/SKILL.md +85 -0
  107. package/skills/small-cl/SKILL.md +52 -0
  108. package/skills/usage-pause/SKILL.md +1 -1
  109. package/skills/usage-pause/scripts/resolve_usage_window.py +32 -4
  110. package/skills/usage-pause/scripts/test_resolve_usage_window.py +26 -0
  111. package/skills/usage-pause/scripts/usage_pause_constants/resolve_usage_window_constants.py +3 -1
  112. package/agents/code-verifier.md +0 -74
  113. package/docs/PR_DESCRIPTION_GUIDE.md +0 -157
  114. package/docs/references/code-review-enforcement.md +0 -132
  115. package/hooks/blocking/code_review_enforcement_config_bootstrap.py +0 -53
  116. package/hooks/blocking/code_review_gate_deny.py +0 -74
  117. package/hooks/blocking/code_review_pr_create_gate.py +0 -198
  118. package/hooks/blocking/code_review_push_gate.py +0 -145
  119. package/hooks/blocking/code_review_stamp_directory_write_blocker.py +0 -348
  120. package/hooks/blocking/code_review_stamp_store.py +0 -233
  121. package/hooks/blocking/code_review_stamp_write_blocker_parts/__init__.py +0 -7
  122. package/hooks/blocking/code_review_stamp_write_blocker_parts/conftest.py +0 -15
  123. package/hooks/blocking/code_review_stamp_write_blocker_parts/obfuscated_stamp_path_reference.py +0 -212
  124. package/hooks/blocking/code_review_stamp_write_blocker_parts/split_directory_change_into_stamp.py +0 -138
  125. package/hooks/blocking/code_review_stamp_write_blocker_parts/test_obfuscated_stamp_path_reference.py +0 -49
  126. package/hooks/blocking/code_review_stamp_write_blocker_parts/test_split_directory_change_into_stamp.py +0 -38
  127. package/hooks/blocking/code_verifier_spawn_preflight_gate.py +0 -483
  128. package/hooks/blocking/config/code_review_enforcement_constants.py +0 -143
  129. package/hooks/blocking/config/test_code_review_enforcement_constants.py +0 -166
  130. package/hooks/blocking/config/verified_commit_constants.py +0 -160
  131. package/hooks/blocking/config/verified_commit_context_constants.py +0 -21
  132. package/hooks/blocking/config/verified_commit_gate_output_constants.py +0 -14
  133. package/hooks/blocking/conftest.py +0 -123
  134. package/hooks/blocking/convergence_gate_blocker.py +0 -310
  135. package/hooks/blocking/pr_converge_bugteam_enforcer.py +0 -170
  136. package/hooks/blocking/pr_description_body_audit.py +0 -148
  137. package/hooks/blocking/pr_description_command_parser.py +0 -237
  138. package/hooks/blocking/pr_description_enforcer.py +0 -227
  139. package/hooks/blocking/pr_description_pr_number.py +0 -155
  140. package/hooks/blocking/pr_description_proof_of_work.py +0 -385
  141. package/hooks/blocking/pr_description_readability.py +0 -364
  142. package/hooks/blocking/reviewer_spawn_gate.py +0 -182
  143. package/hooks/blocking/test_code_review_enforcement_config_bootstrap.py +0 -62
  144. package/hooks/blocking/test_code_review_gate_deny.py +0 -54
  145. package/hooks/blocking/test_code_review_pr_create_gate.py +0 -199
  146. package/hooks/blocking/test_code_review_push_gate.py +0 -205
  147. package/hooks/blocking/test_code_review_stamp_directory_write_blocker.py +0 -199
  148. package/hooks/blocking/test_code_review_stamp_store.py +0 -205
  149. package/hooks/blocking/test_code_verifier_spawn_preflight_gate.py +0 -710
  150. package/hooks/blocking/test_code_verifier_tools_contract.py +0 -28
  151. package/hooks/blocking/test_convergence_gate_blocker.py +0 -428
  152. package/hooks/blocking/test_pr_converge_bugteam_enforcer.py +0 -311
  153. package/hooks/blocking/test_pr_converge_bugteam_enforcer_state_tolerance.py +0 -184
  154. package/hooks/blocking/test_pr_description_enforcer.py +0 -221
  155. package/hooks/blocking/test_pr_description_enforcer_body_audit.py +0 -247
  156. package/hooks/blocking/test_pr_description_enforcer_body_rules.py +0 -493
  157. package/hooks/blocking/test_pr_description_enforcer_command_parser.py +0 -366
  158. package/hooks/blocking/test_pr_description_enforcer_pr_number.py +0 -159
  159. package/hooks/blocking/test_pr_description_enforcer_proof_gate.py +0 -175
  160. package/hooks/blocking/test_pr_description_enforcer_readability.py +0 -443
  161. package/hooks/blocking/test_pr_description_proof_of_work.py +0 -313
  162. package/hooks/blocking/test_reviewer_spawn_gate.py +0 -230
  163. package/hooks/blocking/test_verdict_directory_write_blocker.py +0 -804
  164. package/hooks/blocking/test_verification_verdict_store.py +0 -974
  165. package/hooks/blocking/test_verified_commit_config_bootstrap.py +0 -67
  166. package/hooks/blocking/test_verified_commit_docs_delta.py +0 -176
  167. package/hooks/blocking/test_verified_commit_gate.py +0 -581
  168. package/hooks/blocking/test_verified_commit_gate_additional_context.py +0 -134
  169. package/hooks/blocking/test_verified_commit_message_accuracy_blocker.py +0 -131
  170. package/hooks/blocking/test_verifier_verdict_minter.py +0 -299
  171. package/hooks/blocking/tests/test_verified_commit_gate.py +0 -41
  172. package/hooks/blocking/verdict_directory_write_blocker.py +0 -687
  173. package/hooks/blocking/verification_verdict_store.py +0 -1039
  174. package/hooks/blocking/verified_commit_config_bootstrap.py +0 -63
  175. package/hooks/blocking/verified_commit_gate.py +0 -113
  176. package/hooks/blocking/verified_commit_gate_parts/CLAUDE.md +0 -28
  177. package/hooks/blocking/verified_commit_gate_parts/__init__.py +0 -1
  178. package/hooks/blocking/verified_commit_gate_parts/command_tokenization.py +0 -174
  179. package/hooks/blocking/verified_commit_gate_parts/deny_payload.py +0 -53
  180. package/hooks/blocking/verified_commit_gate_parts/deny_reason.py +0 -80
  181. package/hooks/blocking/verified_commit_gate_parts/directory_resolution.py +0 -170
  182. package/hooks/blocking/verified_commit_gate_parts/gated_invocations.py +0 -217
  183. package/hooks/blocking/verified_commit_gate_parts/tests/conftest.py +0 -10
  184. package/hooks/blocking/verified_commit_gate_parts/tests/test_command_tokenization.py +0 -94
  185. package/hooks/blocking/verified_commit_gate_parts/tests/test_deny_payload.py +0 -17
  186. package/hooks/blocking/verified_commit_gate_parts/tests/test_deny_reason.py +0 -38
  187. package/hooks/blocking/verified_commit_gate_parts/tests/test_directory_resolution.py +0 -71
  188. package/hooks/blocking/verified_commit_gate_parts/tests/test_gated_invocations.py +0 -96
  189. package/hooks/blocking/verified_commit_message_accuracy_blocker.py +0 -167
  190. package/hooks/blocking/verifier_verdict_minter.py +0 -280
  191. package/hooks/hooks_constants/code_verifier_spawn_preflight_gate_constants.py +0 -64
  192. package/hooks/hooks_constants/convergence_gate_blocker_constants.py +0 -53
  193. package/hooks/hooks_constants/pr_converge_bugteam_enforcer_constants.py +0 -55
  194. package/hooks/hooks_constants/pr_converge_bugteam_enforcer_state.py +0 -67
  195. package/hooks/hooks_constants/pr_description_enforcer_constants.py +0 -167
  196. package/hooks/hooks_constants/pr_description_proof_of_work_constants.py +0 -107
  197. package/hooks/hooks_constants/reviewer_spawn_gate_constants.py +0 -41
  198. package/hooks/hooks_constants/test_pr_description_enforcer_constants.py +0 -292
  199. package/hooks/lifecycle/pr_converge_bugteam_skill_tracker.py +0 -198
  200. package/hooks/lifecycle/test_pr_converge_bugteam_skill_tracker.py +0 -283
  201. package/rules/proof-of-work-pr-comments.md +0 -22
  202. package/rules/verified-commit-gate-skip.md +0 -4
  203. package/scripts/Show-Asset.ps1 +0 -106
@@ -1,166 +0,0 @@
1
- """Behavioral tests for the code-review enforcement constants and comparator.
2
-
3
- These exercise ``effort_meets_threshold`` against the ordered effort tokens and
4
- assert the push and pull-request thresholds carry the push-based names the gate
5
- family reads, so a rename that reintroduces commit terminology fails loudly.
6
- """
7
-
8
- import importlib.util
9
- import pathlib
10
- import re
11
-
12
- import pytest
13
-
14
- _CONFIG_DIR = pathlib.Path(__file__).parent
15
-
16
- SAMPLE_ROOT_KEY_HEX_LENGTH = 16
17
-
18
- _constants_spec = importlib.util.spec_from_file_location(
19
- "code_review_enforcement_constants",
20
- _CONFIG_DIR / "code_review_enforcement_constants.py",
21
- )
22
- assert _constants_spec is not None
23
- assert _constants_spec.loader is not None
24
- _constants_module = importlib.util.module_from_spec(_constants_spec)
25
- _constants_spec.loader.exec_module(_constants_module)
26
-
27
- effort_meets_threshold = _constants_module.effort_meets_threshold
28
- CODE_REVIEW_ENFORCEMENT_ENV_VAR = _constants_module.CODE_REVIEW_ENFORCEMENT_ENV_VAR
29
- code_review_enforcement_enabled_in_environment = (
30
- _constants_module.code_review_enforcement_enabled_in_environment
31
- )
32
- PUSH_REQUIRED_EFFORT = _constants_module.PUSH_REQUIRED_EFFORT
33
- PR_CREATE_REQUIRED_EFFORT = _constants_module.PR_CREATE_REQUIRED_EFFORT
34
- GATED_PUSH_SUBCOMMANDS = _constants_module.GATED_PUSH_SUBCOMMANDS
35
- ALL_EFFORT_TOKENS_IN_ASCENDING_ORDER = _constants_module.ALL_EFFORT_TOKENS_IN_ASCENDING_ORDER
36
- PUSH_GATE_CORRECTIVE_MESSAGE = _constants_module.PUSH_GATE_CORRECTIVE_MESSAGE
37
- SANCTIONED_STAMP_MINTER_FLAG = _constants_module.SANCTIONED_STAMP_MINTER_FLAG
38
- STAMP_DIRECTORY_GUARD_MESSAGE = _constants_module.STAMP_DIRECTORY_GUARD_MESSAGE
39
- STAMP_DIRECTORY_NAME = _constants_module.STAMP_DIRECTORY_NAME
40
- GH_PR_CREATE_INVOCATION_PATTERN = _constants_module.GH_PR_CREATE_INVOCATION_PATTERN
41
- STAMP_FILE_REFERENCE_BOUNDARY_PREFIX_PATTERN = (
42
- _constants_module.STAMP_FILE_REFERENCE_BOUNDARY_PREFIX_PATTERN
43
- )
44
- STAMP_FILE_ROOT_KEY_JSON_SUFFIX_PATTERN = _constants_module.STAMP_FILE_ROOT_KEY_JSON_SUFFIX_PATTERN
45
-
46
-
47
- def test_gh_pr_create_pattern_matches_create_but_not_edit() -> None:
48
- invocation_pattern = re.compile(GH_PR_CREATE_INVOCATION_PATTERN, re.IGNORECASE)
49
- assert invocation_pattern.search("gh pr create --title T --body-file b.md") is not None
50
- assert invocation_pattern.search("gh pr edit 1 --title T") is None
51
-
52
-
53
- def test_stamp_file_pattern_fragments_match_a_stamp_file_reference() -> None:
54
- stamp_file_pattern = re.compile(
55
- STAMP_FILE_REFERENCE_BOUNDARY_PREFIX_PATTERN
56
- + re.escape(STAMP_DIRECTORY_NAME)
57
- + STAMP_FILE_ROOT_KEY_JSON_SUFFIX_PATTERN % SAMPLE_ROOT_KEY_HEX_LENGTH,
58
- re.IGNORECASE,
59
- )
60
- stamp_file_reference = f"cat {STAMP_DIRECTORY_NAME}/{'a' * SAMPLE_ROOT_KEY_HEX_LENGTH}.json"
61
- assert stamp_file_pattern.search(stamp_file_reference) is not None
62
- assert stamp_file_pattern.search("cat notes.json") is None
63
-
64
-
65
- def test_higher_effort_meets_a_lower_threshold() -> None:
66
- assert effort_meets_threshold("xhigh", "low") is True
67
-
68
-
69
- def test_lower_effort_fails_a_higher_threshold() -> None:
70
- assert effort_meets_threshold("low", "xhigh") is False
71
-
72
-
73
- def test_equal_effort_meets_the_threshold() -> None:
74
- assert effort_meets_threshold("low", "low") is True
75
-
76
-
77
- def test_ultra_is_unranked_and_meets_nothing() -> None:
78
- assert effort_meets_threshold("ultra", "low") is False
79
-
80
-
81
- def test_unknown_required_effort_meets_nothing() -> None:
82
- assert effort_meets_threshold("low", "ultra") is False
83
-
84
-
85
- def test_push_requires_the_lowest_effort_token() -> None:
86
- every_token_meets_push = all(
87
- effort_meets_threshold(each_token, PUSH_REQUIRED_EFFORT)
88
- for each_token in ALL_EFFORT_TOKENS_IN_ASCENDING_ORDER
89
- )
90
- assert every_token_meets_push
91
-
92
-
93
- def test_pr_create_threshold_admits_xhigh_but_rejects_high() -> None:
94
- assert effort_meets_threshold("xhigh", PR_CREATE_REQUIRED_EFFORT) is True
95
- assert effort_meets_threshold("high", PR_CREATE_REQUIRED_EFFORT) is False
96
-
97
-
98
- def test_gated_push_subcommands_gates_push_not_commit() -> None:
99
- assert "push" in GATED_PUSH_SUBCOMMANDS
100
- assert "commit" not in GATED_PUSH_SUBCOMMANDS
101
-
102
-
103
- def test_ultra_absent_from_the_ordered_tokens() -> None:
104
- assert "ultra" not in ALL_EFFORT_TOKENS_IN_ASCENDING_ORDER
105
-
106
-
107
- def test_push_gate_message_names_the_push_action_and_low_effort() -> None:
108
- assert "git push" in PUSH_GATE_CORRECTIVE_MESSAGE
109
- assert "CODE_REVIEW_PUSH_GATE" in PUSH_GATE_CORRECTIVE_MESSAGE
110
- assert "'low'" in PUSH_GATE_CORRECTIVE_MESSAGE
111
-
112
-
113
- def test_guard_message_directs_users_to_the_sanctioned_minter_flag() -> None:
114
- assert SANCTIONED_STAMP_MINTER_FLAG in STAMP_DIRECTORY_GUARD_MESSAGE
115
-
116
-
117
- def test_enforcement_defaults_to_off(monkeypatch: pytest.MonkeyPatch) -> None:
118
- monkeypatch.delenv(CODE_REVIEW_ENFORCEMENT_ENV_VAR, raising=False)
119
- assert code_review_enforcement_enabled_in_environment() is False
120
-
121
-
122
- @pytest.mark.parametrize("enabling_value", ["1", "true", "TRUE", " yes ", "on"])
123
- def test_enforcement_turns_on_for_each_enabling_environment_value(
124
- monkeypatch: pytest.MonkeyPatch, enabling_value: str
125
- ) -> None:
126
- monkeypatch.setenv(CODE_REVIEW_ENFORCEMENT_ENV_VAR, enabling_value)
127
- assert code_review_enforcement_enabled_in_environment() is True
128
-
129
-
130
- @pytest.mark.parametrize("non_enabling_value", ["0", "false", "off", "", "maybe"])
131
- def test_enforcement_stays_off_for_a_non_enabling_environment_value(
132
- monkeypatch: pytest.MonkeyPatch, non_enabling_value: str
133
- ) -> None:
134
- monkeypatch.setenv(CODE_REVIEW_ENFORCEMENT_ENV_VAR, non_enabling_value)
135
- assert code_review_enforcement_enabled_in_environment() is False
136
-
137
-
138
- def _module_flag_after_a_fresh_import(
139
- monkeypatch: pytest.MonkeyPatch, environment_value: str | None
140
- ) -> bool:
141
- if environment_value is None:
142
- monkeypatch.delenv(CODE_REVIEW_ENFORCEMENT_ENV_VAR, raising=False)
143
- else:
144
- monkeypatch.setenv(CODE_REVIEW_ENFORCEMENT_ENV_VAR, environment_value)
145
- fresh_spec = importlib.util.spec_from_file_location(
146
- "code_review_enforcement_constants_fresh_import",
147
- _CONFIG_DIR / "code_review_enforcement_constants.py",
148
- )
149
- assert fresh_spec is not None
150
- assert fresh_spec.loader is not None
151
- fresh_module = importlib.util.module_from_spec(fresh_spec)
152
- fresh_spec.loader.exec_module(fresh_module)
153
- flag_value: bool = fresh_module.CODE_REVIEW_ENFORCEMENT_ENABLED
154
- return flag_value
155
-
156
-
157
- def test_module_flag_turns_on_when_the_environment_asks_for_enforcement(
158
- monkeypatch: pytest.MonkeyPatch,
159
- ) -> None:
160
- assert _module_flag_after_a_fresh_import(monkeypatch, "true") is True
161
-
162
-
163
- def test_module_flag_stays_off_when_the_environment_is_unset(
164
- monkeypatch: pytest.MonkeyPatch,
165
- ) -> None:
166
- assert _module_flag_after_a_fresh_import(monkeypatch, None) is False
@@ -1,160 +0,0 @@
1
- """Constants for the verified-commit gate hook family.
2
-
3
- Shared by ``verification_verdict_store.py``, ``verified_commit_gate.py``,
4
- and ``verifier_verdict_minter.py`` so every tunable lives in one place.
5
- """
6
-
7
- from __future__ import annotations
8
-
9
- GIT_TIMEOUT_SECONDS = 30
10
- ROOT_KEY_HEX_LENGTH = 16
11
- VERDICT_JSON_INDENT = 2
12
- CLAUDE_HOME_DIRECTORY_NAME = ".claude"
13
- VERDICT_DIRECTORY_NAME = "verification"
14
- VERDICT_DIRECTORY_NAME_SEPARATOR_PATTERN = r"['\"\\/,\s]+"
15
- VERDICT_DIRECTORY_PATH_BOUNDARY_PATTERN = r"(?=['\"]*[\\/,])"
16
- RELATIVE_VERDICT_DIRECTORY_PATTERN = r"(?:^|(?<=[\s;&|(='\"]))verification[\\/]"
17
- VERDICT_PATH_GLUE_PATTERN = r"['\"+\\/\s]*[\\/]['\"+\\/\s]*"
18
- VERDICT_DIRECTORY_CHANGE_TARGET_PATTERN = r"[ \t]+['\"]?verification[\\/]?['\"]?(?=[\s;&|]|$)"
19
- VERDICT_PATH_JOINED_VARIABLE_PATTERN = r"\$\{?(\w+)\}?[\\/]|[\\/]\$\{?(\w+)\}?"
20
- VERDICT_PATH_VARIABLE_ASSIGNMENT_PATTERN = r"(?:^|(?<=[\s;&|(]))%s=(\S+)"
21
- VERDICT_FILE_RELATIVE_REFERENCE_PATTERN = (
22
- rf"(?:^|(?<=[\s;&|(='\"\\/]))verification[\\/][0-9a-f]{{{ROOT_KEY_HEX_LENGTH}}}\.json"
23
- )
24
- PATH_OBFUSCATION_PRIMITIVE_PATTERN = (
25
- r"chr\s*\(|bytes\.fromhex\s*\(|b64decode\s*\(|codecs\.decode\s*\("
26
- r"|(?:bytes|bytearray)\s*\(\s*\[|\[char\[?\]?\]"
27
- )
28
- ALL_VERDICT_PATH_SEGMENT_NAMES = (".claude", "verification")
29
- ALL_VERDICT_PATH_SEGMENT_BODIES = ("claude", "verification")
30
- HEX_TOKEN_PATTERN = r"(?<![0-9a-fx])([0-9a-f]{6,})(?![0-9a-f])"
31
- BASE64_TOKEN_PATTERN = r"[A-Za-z0-9+/]{8,}={0,2}"
32
- CHARACTER_CODE_SEQUENCE_PATTERN = r"\d{1,3}(?:\s*,\s*\d{1,3})+"
33
- CHR_CALL_CHAIN_PATTERN = r"chr\(\s*\d{1,3}\s*\)(?:\s*\+\s*chr\(\s*\d{1,3}\s*\))+"
34
- CHR_CALL_CODE_PATTERN = r"chr\(\s*(\d{1,3})\s*\)"
35
- HEX_DIGITS_PER_BYTE = 2
36
- FILE_WRITE_PRIMITIVE_PATTERN = (
37
- r"\bopen\s*\(|\.write_text\s*\(|\.write_bytes\s*\("
38
- r"|Out-File|Set-Content|Add-Content|\btee\b|>"
39
- )
40
- NON_REDIRECT_FILE_WRITE_PRIMITIVE_PATTERN = (
41
- r"\bopen\s*\(|\.write_text\s*\(|\.write_bytes\s*\("
42
- r"|Out-File|Set-Content|Add-Content|\btee\b"
43
- )
44
- WRITE_CALL_REGION_PATTERN = (
45
- r"(?:\bopen\s*\(|\.write_text\s*\(|\.write_bytes\s*\("
46
- r"|Out-File|Set-Content|Add-Content|\btee\b)[^;&|\n]*"
47
- )
48
- VERDICT_KEY_ALL_PASS = "all_pass"
49
- VERDICT_KEY_MANIFEST_SHA256 = "manifest_sha256"
50
- VERDICT_KEY_FINDINGS = "findings"
51
- VERDICT_FINDING_CHECK_KEY = "check"
52
- LOGS_DIRECTORY_NAME = "logs"
53
- VERIFIER_VERDICTS_JSONL_FILENAME = "verifier-verdicts.jsonl"
54
- LEDGER_KEY_TIMESTAMP = "timestamp"
55
- LEDGER_KEY_REPO_ROOT = "repo_root"
56
- LEDGER_KEY_BRANCH = "branch"
57
- LEDGER_KEY_MANIFEST_HASH = "manifest_hash"
58
- LEDGER_KEY_ALL_PASS = "all_pass"
59
- LEDGER_KEY_FINDING_COUNT = "finding_count"
60
- LEDGER_KEY_FINDING_CHECK_NAMES = "finding_check_names"
61
- LEDGER_KEY_MINTED_FROM_AGENT_ID = "minted_from_agent_id"
62
- VERDICT_FILE_GLOB = "*.json"
63
- SUBAGENTS_DIRECTORY_NAME = "subagents"
64
- AGENT_TRANSCRIPT_GLOB = "agent-*.jsonl"
65
- AGENT_META_SIDECAR_SUFFIX = ".meta.json"
66
- AGENT_META_TYPE_KEY = "agentType"
67
- TRANSCRIPT_ENTRY_TYPE_KEY = "type"
68
- TRANSCRIPT_ASSISTANT_ENTRY_TYPE = "assistant"
69
- TRANSCRIPT_MESSAGE_KEY = "message"
70
- TRANSCRIPT_CONTENT_KEY = "content"
71
- TRANSCRIPT_CONTENT_TYPE_KEY = "type"
72
- TRANSCRIPT_TEXT_CONTENT_TYPE = "text"
73
- TRANSCRIPT_TEXT_KEY = "text"
74
- VERDICT_FENCE_PATTERN = r"```verdict\s*\n(.*?)```"
75
- MANIFEST_HASH_CLI_FLAG = "--manifest-hash"
76
- MANIFEST_HASH_FOR_BRANCH_CLI_FLAG = "--manifest-hash-for-branch"
77
- WORKTREE_LIST_PATH_PREFIX = "worktree "
78
- WORKTREE_LIST_BRANCH_PREFIX = "branch "
79
- BRANCH_REFERENCE_PREFIX = "refs/heads/"
80
- EMPTY_SURFACE_GUARD_MESSAGE = (
81
- "ERROR: The work tree at {repo_root} has no changed or untracked files "
82
- "versus origin/main (empty change surface). This work tree holds nothing "
83
- "to verify — you are pointed at the wrong work tree. Run `git worktree list` "
84
- "to see all checked-out work trees and target the one on the branch under review."
85
- )
86
- BRANCH_WORKTREE_ABSENT_MESSAGE = (
87
- "ERROR: No work tree has branch '{branch}' checked out. "
88
- "Check it out in a work tree first: "
89
- "`git worktree add <path> <branch>` or `git checkout <branch>`."
90
- )
91
- DOCS_ONLY_EXTENSIONS = frozenset(
92
- {".md", ".txt", ".rst", ".png", ".jpg", ".jpeg", ".gif", ".svg", ".webp", ".ico"}
93
- )
94
- PYTHON_EXTENSION = ".py"
95
- TEST_FILE_PREFIX = "test_"
96
- TEST_FILE_SUFFIX = "_test.py"
97
- CONFTEST_FILE_NAME = "conftest.py"
98
- MINIMUM_STATUS_FIELD_COUNT = 2
99
- ALL_FALLBACK_BASE_REFERENCES = ("origin/main", "origin/master")
100
- DETACHED_HEAD_LABEL = "HEAD"
101
- BRANCH_REMOTE_CONFIG_KEY_TEMPLATE = "branch.{branch_name}.remote"
102
- ALL_TOOLING_STATE_PREFIXES = (
103
- ".claude/verification/",
104
- ".claude/worktrees/",
105
- ".claude/daemon/",
106
- ".claude/teams/",
107
- ".claude/sessions/",
108
- ".cursor/worktrees/",
109
- ".claude-plugin-data/",
110
- )
111
- GATED_GIT_SUBCOMMANDS = frozenset({"commit", "push"})
112
- ALL_GIT_BINARY_NAMES = frozenset({"git", "git.exe"})
113
- VALUE_TAKING_GIT_OPTIONS = frozenset({"-C", "-c", "--git-dir", "--work-tree", "--namespace"})
114
- REPO_DIRECTORY_OPTION = "-C"
115
- WORK_TREE_OPTION = "--work-tree"
116
- DIRECTORY_CHANGE_VERBS = frozenset({"cd", "pushd", "set-location", "sl"})
117
- DIRECTORY_CHANGE_PATH_OPTIONS = frozenset({"-path", "-literalpath"})
118
- DIRECTORY_CHANGE_OPTION_TERMINATOR = "--"
119
- DIRECTORY_CHANGE_PATTERN_PREFIX = r"(?:^|(?<=[\s;&|(]))(?:"
120
- DIRECTORY_CHANGE_PATTERN_SUFFIX = r")(?=\s|$)"
121
- DIRECTORY_CHANGE_OPTION_PREFIX_PATTERN = r"(?:[ \t]+(?:%s)(?=\s|$))*"
122
- DIRECTORY_CHANGE_TARGET_PATTERN = r"[ \t]+['\"]?\S*"
123
- CLAUDE_HOME_TARGET_BOUNDARY_PATTERN = r"[\\/]?['\"]?(?=[\s;&|]|$)"
124
- VERDICT_DIRECTORY_TARGET_BOUNDARY_PATTERN = r"[\\/]?['\"]?(?=[\s;&|]|$)"
125
- COMMAND_AFTER_DIRECTORY_CHANGE_PATTERN = r"[;&|\n][\s]*\S"
126
- OPTION_WITH_VALUE_STEP = 2
127
- ALL_GATED_TOOL_NAMES = ("Bash", "PowerShell")
128
- HASH_PREVIEW_LENGTH = 16
129
- VERIFICATION_BYPASS_MARKER = "# verify-skip"
130
- MINTING_AGENT_TYPE = "code-verifier"
131
- VERDICT_DIRECTORY_GUARD_MESSAGE = (
132
- "BLOCKED: [VERDICT_DIRECTORY_GUARD] Shell access to the verification "
133
- "verdict directory (~/.claude/verification/) is denied. Only the "
134
- "verifier_verdict_minter.py SubagentStop hook writes verdict files; a "
135
- "shell write here would forge a passing verdict and defeat the "
136
- "verified-commit gate. Spawn the code-verifier agent to earn a verdict "
137
- "instead of writing one."
138
- )
139
- CORRECTIVE_MESSAGE = (
140
- "BLOCKED: [VERIFIED_COMMIT_GATE] This branch surface has no passing "
141
- "verification verdict. Clear commit-committability gates first "
142
- "(CODE_RULES on changed lines and merge conflicts vs base — the "
143
- "code_verifier_spawn_preflight_gate enforces these on the spawn). If a "
144
- "prior commit failed on CODE_RULES or staging, fix those before "
145
- "re-verifying. Then spawn the code-verifier agent (Agent or Task tool, "
146
- "subagent_type 'code-verifier') with the task texts, the diff scope, "
147
- "and recorded baselines; when it finishes with a clean verdict the "
148
- "SubagentStop hook mints the verdict and this command will pass. Any "
149
- "file change after verification invalidates the verdict, so verify "
150
- "last only after commit gates are green. The verdict binds to the exact "
151
- "surface content (every changed and untracked file's bytes), not to a "
152
- "work-tree path, so a commit from a different work tree passes only "
153
- "when its surface is byte-identical; a work tree carrying any extra or "
154
- "later edit has a surface no verdict covers. To land this change, run "
155
- "this command from the work tree that holds the verified surface, or "
156
- "verify this surface here. Exempt automatically: docs/image files, "
157
- "pytest test files, and "
158
- "Python files whose docstring- and comment-stripped AST is unchanged "
159
- "(comment-only edits in non-Python files are not exempt)."
160
- )
@@ -1,21 +0,0 @@
1
- """The additionalContext text the verified-commit gate attaches to a deny.
2
-
3
- The gate injects this next to the blocked tool result so the agent learns,
4
- at the moment of the block, when the '# verify-skip' marker is legitimate
5
- and when a fresh verification is required instead.
6
- """
7
-
8
- VERIFY_SKIP_ADDITIONAL_CONTEXT = (
9
- "The verified-commit gate honors a one-command escape hatch: appending the "
10
- "marker '# verify-skip' as a trailing shell comment (outside every quoted "
11
- "region) to the blocked command bypasses the gate for that command only. "
12
- "It is allowed only when the branch surface is the "
13
- "same code a code-verifier already passed clean and the gate is "
14
- "blocking on a verdict that does not cover it (an unminted verdict, "
15
- "staging churn, or a reverted concurrent write) — confirm the verified "
16
- "suite still passes and the diff holds nothing beyond the clean verdict "
17
- "before using it. Any real code change since that clean verdict, a "
18
- "first verification, or unrepaired findings all require a fresh "
19
- "verification by the code-verifier agent instead. Full rule: "
20
- "~/.claude/rules/verified-commit-gate-skip.md."
21
- )
@@ -1,14 +0,0 @@
1
- """Output-shape constants for the verified-commit gate's deny payload.
2
-
3
- Shared by ``verified_commit_gate.py`` and its ``verified_commit_gate_parts``
4
- package so the PreToolUse event name, the deny decision string, the hook's
5
- own module name for block logging, and the regex-alternation join separator
6
- live in one place.
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- PRE_TOOL_USE_HOOK_EVENT_NAME = "PreToolUse"
12
- DENY_PERMISSION_DECISION = "deny"
13
- GATE_HOOK_MODULE_NAME = "verified_commit_gate.py"
14
- REGEX_ALTERNATION_SEPARATOR = "|"
@@ -1,123 +0,0 @@
1
- """Pytest fixture that reproduces foreign-``config`` shadowing for the gate family.
2
-
3
- The verified-commit gate hooks import shared constants as
4
- ``from config.verified_commit_constants import ...``. This fixture mirrors the
5
- installed layout that breaks that import -- a real ``hooks/blocking`` package
6
- beside a decoy ``hooks/config`` regular package -- and runs a caller-supplied
7
- driver as a subprocess whose ``sys.path`` places the decoy ahead of
8
- ``blocking``, the ordering under which the wrong ``config`` wins resolution.
9
- """
10
-
11
- import shutil
12
- import subprocess
13
- import sys
14
- import textwrap
15
- from collections.abc import Callable
16
- from pathlib import Path
17
-
18
- import pytest
19
-
20
- _BLOCKING_SOURCE_DIRECTORY = Path(__file__).resolve().parent
21
- _HOOKS_CONSTANTS_SOURCE_DIRECTORY = _BLOCKING_SOURCE_DIRECTORY.parent / "hooks_constants"
22
- _SUBPROCESS_TIMEOUT_SECONDS = 60
23
- _MIRRORED_BLOCKING_FILE_NAMES = (
24
- "verified_commit_gate.py",
25
- "verification_verdict_store.py",
26
- "verifier_verdict_minter.py",
27
- "verdict_directory_write_blocker.py",
28
- "verified_commit_config_bootstrap.py",
29
- "code_review_enforcement_config_bootstrap.py",
30
- "code_review_stamp_store.py",
31
- )
32
- _DECOY_CONFIG_INIT_SOURCE = '"""Foreign config package that must never win resolution."""\n'
33
- _DECOY_CONFIG_CONSTANTS_SOURCE = (
34
- '"""Stale stand-in carrying none of the real gate constants."""\n\nIS_DECOY_CONFIG = True\n'
35
- )
36
-
37
-
38
- def _copy_mirrored_packages(mirrored_hooks_directory: Path, mirrored_blocking_directory: Path) -> None:
39
- """Copy the ``config``, gate-parts, and ``hooks_constants`` packages into the mirror."""
40
- shutil.copytree(_BLOCKING_SOURCE_DIRECTORY / "config", mirrored_blocking_directory / "config")
41
- shutil.copytree(
42
- _BLOCKING_SOURCE_DIRECTORY / "verified_commit_gate_parts",
43
- mirrored_blocking_directory / "verified_commit_gate_parts",
44
- ignore=shutil.ignore_patterns("tests", "__pycache__"),
45
- )
46
- shutil.copytree(_HOOKS_CONSTANTS_SOURCE_DIRECTORY, mirrored_hooks_directory / "hooks_constants")
47
-
48
-
49
- def _write_decoy_config_package(mirrored_hooks_directory: Path) -> None:
50
- """Write the decoy ``config`` package that must never win resolution."""
51
- decoy_config_directory = mirrored_hooks_directory / "config"
52
- decoy_config_directory.mkdir()
53
- (decoy_config_directory / "__init__.py").write_text(_DECOY_CONFIG_INIT_SOURCE, encoding="utf-8")
54
- (decoy_config_directory / "verified_commit_constants.py").write_text(
55
- _DECOY_CONFIG_CONSTANTS_SOURCE, encoding="utf-8"
56
- )
57
-
58
-
59
- def _build_shadowed_hook_tree(tmp_path: Path) -> tuple[Path, Path]:
60
- """Mirror the hook tree with a decoy ``config`` package beside ``blocking``.
61
-
62
- Args:
63
- tmp_path: The directory the mirrored tree is built under.
64
-
65
- Returns:
66
- The mirrored ``hooks`` directory and its ``blocking`` subdirectory.
67
- """
68
- mirrored_hooks_directory = tmp_path / "hooks"
69
- mirrored_blocking_directory = mirrored_hooks_directory / "blocking"
70
- mirrored_blocking_directory.mkdir(parents=True)
71
-
72
- for each_file_name in _MIRRORED_BLOCKING_FILE_NAMES:
73
- source_file = _BLOCKING_SOURCE_DIRECTORY / each_file_name
74
- if source_file.exists():
75
- shutil.copy2(source_file, mirrored_blocking_directory / each_file_name)
76
-
77
- _copy_mirrored_packages(mirrored_hooks_directory, mirrored_blocking_directory)
78
- _write_decoy_config_package(mirrored_hooks_directory)
79
- return mirrored_hooks_directory, mirrored_blocking_directory
80
-
81
-
82
- def _probe_command(mirrored_hooks_directory: Path, mirrored_blocking_directory: Path) -> str:
83
- """Build the ``sys.path`` preamble that shadows ``blocking`` with the decoy config."""
84
- return textwrap.dedent(
85
- f"""\
86
- import sys
87
- sys.path.insert(0, {str(mirrored_blocking_directory)!r})
88
- sys.path.insert(0, {str(mirrored_hooks_directory)!r})
89
- """
90
- )
91
-
92
-
93
- @pytest.fixture
94
- def run_under_config_shadow(
95
- tmp_path: Path,
96
- ) -> Callable[[str], subprocess.CompletedProcess[str]]:
97
- """Return a runner that executes a driver under a foreign-``config`` sys.path.
98
-
99
- ``run_under_config_shadow("import verified_commit_gate")`` returns 0 once
100
- the bootstrap seeds the real module, nonzero while the decoy config wins.
101
-
102
- Args:
103
- tmp_path: Pytest's per-test temporary directory, fixture-injected.
104
-
105
- Returns:
106
- A function taking driver source text and returning the finished
107
- subprocess result.
108
- """
109
- mirrored_hooks_directory, mirrored_blocking_directory = _build_shadowed_hook_tree(tmp_path)
110
- sys_path_preamble = _probe_command(mirrored_hooks_directory, mirrored_blocking_directory)
111
-
112
- def run_driver(driver_body: str) -> subprocess.CompletedProcess[str]:
113
- probe_script = tmp_path / "probe.py"
114
- probe_script.write_text(sys_path_preamble + textwrap.dedent(driver_body), encoding="utf-8")
115
- return subprocess.run(
116
- [sys.executable, str(probe_script)],
117
- check=False,
118
- capture_output=True,
119
- text=True,
120
- timeout=_SUBPROCESS_TIMEOUT_SECONDS,
121
- )
122
-
123
- return run_driver