claude-dev-env 7.0.0 → 7.2.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 (210) hide show
  1. package/.agents/skills/pull-request/scripts/pull_request.py +2 -0
  2. package/.agents/skills/pull-request/scripts/test_pull_request.py +34 -0
  3. package/.agents/skills/session-continuity/AGENTS.md +1 -0
  4. package/.agents/skills/session-continuity/CLAUDE.md +1 -0
  5. package/.agents/skills/session-continuity/README.md +213 -0
  6. package/.agents/skills/session-continuity/SKILL.md +125 -0
  7. package/.agents/skills/session-continuity/continuity.mjs +447 -0
  8. package/.agents/skills/session-continuity/continuity.test.mjs +373 -0
  9. package/.agents/skills/syncing-submodules/scripts/test_sync_parent_pointer.py +0 -7
  10. package/.agents/skills/windows-scheduled-task/SKILL.md +153 -0
  11. package/bin/cde.mjs +36 -8
  12. package/bin/cde.test.mjs +48 -0
  13. package/bin/ever-shipped-skills.mjs +2 -20
  14. package/bin/ever-shipped-skills.test.mjs +8 -0
  15. package/bin/expand_home_directory_tokens.mjs +4 -5
  16. package/bin/expand_home_directory_tokens.test.mjs +128 -0
  17. package/bin/git_hooks_installer.mjs +22 -3
  18. package/bin/git_hooks_installer.test.mjs +159 -5
  19. package/bin/install-session-continuity.mjs +88 -0
  20. package/bin/install-session-continuity.test.mjs +55 -0
  21. package/bin/install.codex-rules.test.mjs +5 -2
  22. package/bin/install.cursor-rules.test.mjs +5 -2
  23. package/bin/install.mjs +478 -63
  24. package/bin/install.policy-lint.test.mjs +56 -4
  25. package/bin/install.profiles.test.mjs +10 -0
  26. package/bin/install.prune.test.mjs +227 -17
  27. package/bin/install.test.mjs +500 -120
  28. package/bin/install.transaction.test.mjs +3 -3
  29. package/docs/CODE_RULES.md +1 -1
  30. package/hooks/blocking/code_rules_banned_identifiers.py +3 -0
  31. package/hooks/blocking/precommit_code_rules_gate.py +5 -8
  32. package/hooks/blocking/test_bash_dispatcher_interpreter_starts.py +7 -52
  33. package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +0 -69
  34. package/hooks/blocking/test_code_rules_banned_identifiers.py +64 -0
  35. package/hooks/blocking/test_pre_tool_use_dispatcher.py +10 -244
  36. package/hooks/blocking/test_shared_stdin_adoption.py +1 -47
  37. package/hooks/git-hooks/git_hooks_constants/verification_notice_constants.py +116 -0
  38. package/hooks/git-hooks/post_commit.py +33 -0
  39. package/hooks/git-hooks/pre_commit.py +18 -90
  40. package/hooks/git-hooks/pre_push.py +45 -541
  41. package/hooks/git-hooks/test_installed_policy_lint.py +55 -6
  42. package/hooks/git-hooks/test_native_hook_support.py +129 -0
  43. package/hooks/git-hooks/test_native_policy_commit.py +39 -6
  44. package/hooks/git-hooks/test_post_commit.py +43 -0
  45. package/hooks/git-hooks/test_pre_commit.py +36 -392
  46. package/hooks/git-hooks/test_pre_push.py +48 -1631
  47. package/hooks/git-hooks/test_verification_notice.py +60 -0
  48. package/hooks/git-hooks/test_verification_start.py +173 -0
  49. package/hooks/git-hooks/verification_notice.py +192 -0
  50. package/hooks/git-hooks/verification_notice_context.py +253 -0
  51. package/hooks/git-hooks/verification_notice_state.py +237 -0
  52. package/hooks/git-hooks/verification_start.py +100 -0
  53. package/hooks/hooks.json +0 -119
  54. package/hooks/hooks_constants/banned_identifiers_constants.py +4 -0
  55. package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +1 -15
  56. package/hooks/hooks_constants/post_tool_use_dispatcher_constants.py +0 -4
  57. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +0 -50
  58. package/hooks/hooks_constants/stop_dispatcher_constants.py +1 -4
  59. package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +3 -66
  60. package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +42 -185
  61. package/hooks/hooks_constants/test_stop_dispatcher_constants.py +3 -25
  62. package/hooks/validation/test_post_tool_use_dispatcher.py +35 -298
  63. package/hooks/validators/run_all_validators.py +5 -1
  64. package/hooks/validators/security_checks.py +57 -2
  65. package/hooks/validators/test_hook_timing_harness.py +2 -5
  66. package/hooks/validators/test_run_all_validators_mutation_tool_parity.py +3 -11
  67. package/hooks/validators/test_run_all_validators_policy_lint.py +36 -0
  68. package/hooks/validators/test_security_checks.py +57 -0
  69. package/package.json +1 -1
  70. package/rules/confirm-implementation-forks.md +2 -0
  71. package/rules/git-workflow.md +12 -0
  72. package/rules/pstack-host-mapping.md +48 -0
  73. package/rules/vault-context.md +3 -5
  74. package/rules/verify-runtime-state.md +7 -0
  75. package/scripts/audit-hooks-cli.mjs +5 -0
  76. package/scripts/audit-hooks.mjs +72 -16
  77. package/scripts/automatic_advisory/__init__.py +1 -0
  78. package/scripts/automatic_advisory/checkout.py +51 -0
  79. package/scripts/automatic_advisory/cli.py +334 -0
  80. package/scripts/automatic_advisory/config/__init__.py +1 -0
  81. package/scripts/automatic_advisory/config/constants.py +93 -0
  82. package/scripts/automatic_advisory/config/timing.py +4 -0
  83. package/scripts/automatic_advisory/configuration.py +197 -0
  84. package/scripts/automatic_advisory/execution.py +350 -0
  85. package/scripts/automatic_advisory/git.py +69 -0
  86. package/scripts/automatic_advisory/model.py +107 -0
  87. package/scripts/automatic_advisory/process_host.py +32 -0
  88. package/scripts/automatic_advisory/publisher.py +84 -0
  89. package/scripts/automatic_advisory/runner.py +399 -0
  90. package/scripts/automatic_advisory/state.py +116 -0
  91. package/scripts/automatic_advisory/window_flags.py +41 -0
  92. package/scripts/automatic_advisory/windows_job.py +122 -0
  93. package/scripts/codex_compat_materializer.py +99 -13
  94. package/scripts/durable_post_lint.py +30 -2
  95. package/scripts/durable_post_lint_config/config/constants.py +10 -0
  96. package/scripts/durable_post_lint_config/config/test_constants.py +13 -0
  97. package/scripts/local_report_cli.py +184 -0
  98. package/scripts/local_report_core.py +247 -0
  99. package/scripts/local_report_publisher.py +12 -0
  100. package/scripts/local_report_validation.py +357 -0
  101. package/scripts/local_verification/__init__.py +23 -0
  102. package/scripts/local_verification/check_runner.py +364 -0
  103. package/scripts/local_verification/check_support.py +99 -0
  104. package/scripts/local_verification/cli.py +158 -0
  105. package/scripts/local_verification/command_runner.py +84 -0
  106. package/scripts/local_verification/config/__init__.py +1 -0
  107. package/scripts/local_verification/config/constants.py +89 -0
  108. package/scripts/local_verification/config/timing.py +1 -0
  109. package/scripts/local_verification/git_state.py +146 -0
  110. package/scripts/local_verification/manifest.py +224 -0
  111. package/scripts/local_verification/model.py +174 -0
  112. package/scripts/local_verification/runner.py +234 -0
  113. package/scripts/policy_lint/adapter_pairing.py +58 -3
  114. package/scripts/policy_lint/config/approved_test_pairs.py +323 -0
  115. package/scripts/pr_verification/__init__.py +1 -0
  116. package/scripts/pr_verification/branch_refs.py +114 -0
  117. package/scripts/pr_verification/config/__init__.py +1 -0
  118. package/scripts/pr_verification/config/constants.py +84 -0
  119. package/scripts/pr_verification/config/timing.py +5 -0
  120. package/scripts/pr_verification/github.py +137 -0
  121. package/scripts/pr_verification/github_api.py +376 -0
  122. package/scripts/pr_verification/github_labels.py +88 -0
  123. package/scripts/pr_verification/github_parsing.py +182 -0
  124. package/scripts/pr_verification/github_transport.py +91 -0
  125. package/scripts/pr_verification/lock.py +122 -0
  126. package/scripts/pr_verification/model.py +43 -0
  127. package/scripts/pr_verification/requirements.txt +1 -0
  128. package/scripts/test_durable_post_lint.py +97 -0
  129. package/scripts/tests/test-audit-hooks-cli.mjs +150 -0
  130. package/scripts/tests/test-audit-hooks.mjs +94 -2
  131. package/scripts/tests/test_automatic_advisory.py +376 -0
  132. package/scripts/tests/test_automatic_advisory_job_cleanup.py +71 -0
  133. package/scripts/tests/test_automatic_advisory_polling.py +216 -0
  134. package/scripts/tests/test_automatic_advisory_recovery.py +320 -0
  135. package/scripts/tests/test_automatic_advisory_repairs.py +400 -0
  136. package/scripts/tests/test_automatic_advisory_settings.py +48 -0
  137. package/scripts/tests/test_automatic_advisory_window_flags.py +163 -0
  138. package/scripts/tests/test_closed_pr_label.py +157 -0
  139. package/scripts/tests/test_codex_compat_materializer.py +189 -6
  140. package/scripts/tests/test_github_labels.py +101 -0
  141. package/scripts/tests/test_github_transport.py +125 -0
  142. package/scripts/tests/test_local_report_publisher.py +396 -0
  143. package/scripts/tests/test_local_report_publisher_cli.py +85 -0
  144. package/scripts/tests/test_local_verification.py +348 -0
  145. package/scripts/tests/test_local_verification_cli.py +98 -0
  146. package/scripts/tests/test_policy_lint_rules_pairing.py +163 -0
  147. package/scripts/tests/test_pr_verification_github.py +392 -0
  148. package/scripts/tests/test_pr_verification_lock.py +168 -0
  149. package/scripts/tests/test_verification_notice.py +347 -0
  150. package/hooks/blocking/ask_user_question_shape_blocker.py +0 -83
  151. package/hooks/blocking/bot_mention_comment_blocker.py +0 -76
  152. package/hooks/blocking/claude_md_orphan_file_blocker.py +0 -129
  153. package/hooks/blocking/cursor_cli_python_misfire_blocker.py +0 -108
  154. package/hooks/blocking/destructive_command_blocker.py +0 -2073
  155. package/hooks/blocking/fable_spawn_gate.py +0 -187
  156. package/hooks/blocking/luna_fast_mode_gate.py +0 -160
  157. package/hooks/blocking/nas_ssh_binary_enforcer.py +0 -163
  158. package/hooks/blocking/orchestrator_refresh_reschedule_gate.py +0 -260
  159. package/hooks/blocking/package_inventory_stale_blocker.py +0 -107
  160. package/hooks/blocking/pii_prevention_blocker.py +0 -365
  161. package/hooks/blocking/piped_pytest_blocker.py +0 -1223
  162. package/hooks/blocking/question_to_user_enforcer.py +0 -130
  163. package/hooks/blocking/send_user_file_open_locally_blocker.py +0 -71
  164. package/hooks/blocking/sensitive_file_protector.py +0 -167
  165. package/hooks/blocking/session_edit_stage_gate.py +0 -684
  166. package/hooks/blocking/session_handoff_blocker.py +0 -182
  167. package/hooks/blocking/shell_substitution_blocker.py +0 -350
  168. package/hooks/blocking/test_ask_user_question_shape.py +0 -69
  169. package/hooks/blocking/test_bot_mention_comment_blocker.py +0 -131
  170. package/hooks/blocking/test_claude_md_orphan_file_blocker.py +0 -1277
  171. package/hooks/blocking/test_cursor_cli_python_misfire_blocker.py +0 -208
  172. package/hooks/blocking/test_destructive_command_blocker.py +0 -2024
  173. package/hooks/blocking/test_destructive_command_blocker_deny_mode.py +0 -105
  174. package/hooks/blocking/test_destructive_command_blocker_no_verify.py +0 -122
  175. package/hooks/blocking/test_destructive_command_blocker_patterns.py +0 -133
  176. package/hooks/blocking/test_fable_spawn_gate.py +0 -381
  177. package/hooks/blocking/test_inventory_deadlock_resolution.py +0 -154
  178. package/hooks/blocking/test_luna_fast_mode_gate.py +0 -211
  179. package/hooks/blocking/test_nas_ssh_binary_enforcer.py +0 -225
  180. package/hooks/blocking/test_orchestrator_refresh_reschedule_gate.py +0 -231
  181. package/hooks/blocking/test_package_inventory_stale_blocker.py +0 -382
  182. package/hooks/blocking/test_pii_prevention_blocker.py +0 -1222
  183. package/hooks/blocking/test_pii_prevention_windows_git_path.py +0 -41
  184. package/hooks/blocking/test_pii_write_surface_ephemeral.py +0 -221
  185. package/hooks/blocking/test_piped_pytest_blocker.py +0 -587
  186. package/hooks/blocking/test_precommit_code_rules_gate_native_owner.py +0 -233
  187. package/hooks/blocking/test_question_to_user_enforcer.py +0 -169
  188. package/hooks/blocking/test_send_user_file_open_locally_blocker.py +0 -118
  189. package/hooks/blocking/test_sensitive_file_protector.py +0 -365
  190. package/hooks/blocking/test_session_edit_stage_gate.py +0 -537
  191. package/hooks/blocking/test_session_edit_stage_gate_staging.py +0 -64
  192. package/hooks/blocking/test_session_edit_stage_gate_support.py +0 -204
  193. package/hooks/blocking/test_session_edit_stage_gate_tracker.py +0 -116
  194. package/hooks/blocking/test_session_handoff_blocker.py +0 -318
  195. package/hooks/blocking/test_shell_substitution_blocker.py +0 -298
  196. package/hooks/blocking/test_unscoped_search_blocker.py +0 -102
  197. package/hooks/blocking/test_write_existing_file_blocker.py +0 -122
  198. package/hooks/blocking/tests/test_pii_prevention_blocker.py +0 -259
  199. package/hooks/blocking/unscoped_search_blocker.py +0 -391
  200. package/hooks/blocking/write_existing_file_blocker.py +0 -124
  201. package/hooks/hooks_constants/bot_mention_comment_blocker_constants.py +0 -19
  202. package/hooks/hooks_constants/cursor_cli_python_misfire_blocker_constants.py +0 -79
  203. package/hooks/hooks_constants/fable_spawn_gate_constants.py +0 -61
  204. package/hooks/hooks_constants/luna_fast_mode_gate_constants.py +0 -45
  205. package/hooks/hooks_constants/nas_ssh_binary_enforcer_constants.py +0 -20
  206. package/hooks/hooks_constants/orchestrator_refresh_reschedule_gate_constants.py +0 -57
  207. package/hooks/hooks_constants/send_user_file_open_locally_blocker_constants.py +0 -17
  208. package/hooks/hooks_constants/sensitive_file_protector_constants.py +0 -40
  209. package/hooks/hooks_constants/session_handoff_blocker_constants.py +0 -10
  210. package/hooks/hooks_constants/shell_substitution_blocker_constants.py +0 -80
@@ -4,39 +4,24 @@ import importlib
4
4
  import pathlib
5
5
  import sys
6
6
 
7
- try:
8
- _HOOKS_ROOT = pathlib.Path(__file__).resolve().parent.parent
9
- if str(_HOOKS_ROOT) not in sys.path:
10
- sys.path.insert(0, str(_HOOKS_ROOT))
11
-
12
- _BLOCKING_DIR = _HOOKS_ROOT / "blocking"
13
- if str(_BLOCKING_DIR) not in sys.path:
14
- sys.path.insert(0, str(_BLOCKING_DIR))
15
-
16
- from hooks_constants.bash_pre_tool_use_dispatcher_constants import (
17
- ALL_BASH_HOSTED_HOOK_ENTRIES,
18
- )
19
- from hooks_constants.pre_tool_use_dispatcher_constants import (
20
- ALL_HOSTED_HOOK_ENTRIES,
21
- ALL_IMMEDIATE_HARM_SCRIPT_PATHS,
22
- ALL_WRITE_AND_EDIT_TOOL_NAMES,
23
- ALL_WRITE_EDIT_MULTI_EDIT_APPLY_PATCH_TOOL_NAMES,
24
- ALL_WRITE_EDIT_MULTI_EDIT_TOOL_NAMES,
25
- APPLY_PATCH_TOOL_NAME,
26
- BLOCKING_CRASH_DENY_REASON,
27
- EDIT_TOOL_NAME,
28
- MULTI_EDIT_TOOL_NAME,
29
- WRITE_TOOL_NAME,
30
- )
31
- from pre_tool_use_dispatcher import (
32
- HostedHookResult,
33
- aggregate_hosted_hook_results,
34
- )
35
- except ImportError as import_error:
36
- raise ImportError(
37
- "test_pre_tool_use_dispatcher_constants: cannot import its sibling modules; "
38
- "ensure the hooks and blocking directories are importable."
39
- ) from import_error
7
+ _HOOKS_ROOT = pathlib.Path(__file__).resolve().parent.parent
8
+ if str(_HOOKS_ROOT) not in sys.path:
9
+ sys.path.insert(0, str(_HOOKS_ROOT))
10
+
11
+ _BLOCKING_DIR = _HOOKS_ROOT / "blocking"
12
+ if str(_BLOCKING_DIR) not in sys.path:
13
+ sys.path.insert(0, str(_BLOCKING_DIR))
14
+
15
+ from hooks_constants.pre_tool_use_dispatcher_constants import (
16
+ ALL_HOSTED_HOOK_ENTRIES,
17
+ ALL_WRITE_EDIT_MULTI_EDIT_APPLY_PATCH_TOOL_NAMES,
18
+ ALL_WRITE_EDIT_MULTI_EDIT_TOOL_NAMES,
19
+ APPLY_PATCH_TOOL_NAME,
20
+ BLOCKING_CRASH_DENY_REASON,
21
+ EDIT_TOOL_NAME,
22
+ MULTI_EDIT_TOOL_NAME,
23
+ )
24
+ from pre_tool_use_dispatcher import HostedHookResult, aggregate_hosted_hook_results
40
25
 
41
26
 
42
27
  def _entry_for(script_relative_path: str):
@@ -48,80 +33,31 @@ def _entry_for(script_relative_path: str):
48
33
  return matching_entries[0] if matching_entries else None
49
34
 
50
35
 
51
- def test_roster_includes_duplicate_rmtree_helper_blocker_script_path() -> None:
52
- all_registered_script_paths = [
36
+ def test_roster_keeps_only_nonblocking_edit_advisors() -> None:
37
+ all_script_paths = tuple(
53
38
  each_entry.script_relative_path for each_entry in ALL_HOSTED_HOOK_ENTRIES
54
- ]
55
- assert "blocking/duplicate_rmtree_helper_blocker.py" in all_registered_script_paths, (
56
- "duplicate_rmtree_helper_blocker must be hosted by the dispatcher so a local "
57
- "re-definition of the Windows-safe rmtree helper trio is blocked at Write time"
58
39
  )
59
-
60
-
61
- def test_duplicate_rmtree_helper_blocker_applies_to_write_and_edit() -> None:
62
- entry = _entry_for("blocking/duplicate_rmtree_helper_blocker.py")
63
- assert entry is not None
64
- assert WRITE_TOOL_NAME in entry.applicable_tool_names
65
- assert EDIT_TOOL_NAME in entry.applicable_tool_names
66
-
67
-
68
- def test_duplicate_rmtree_helper_blocker_is_blocking() -> None:
69
- entry = _entry_for("blocking/duplicate_rmtree_helper_blocker.py")
70
- assert entry is not None
71
- assert entry.is_blocking is True
72
-
73
-
74
- def test_duplicate_rmtree_helper_blocker_runs_via_runpy() -> None:
75
- entry = _entry_for("blocking/duplicate_rmtree_helper_blocker.py")
76
- assert entry is not None
77
- assert entry.native_module_name is None
78
-
79
-
80
- def test_windows_rmtree_write_check_is_retired_but_bash_check_remains() -> None:
81
- assert _entry_for("blocking/windows_rmtree_blocker.py") is None
82
- all_bash_paths = {
83
- each_entry.script_relative_path for each_entry in ALL_BASH_HOSTED_HOOK_ENTRIES
84
- }
85
- assert "blocking/windows_rmtree_blocker.py" in all_bash_paths
86
-
87
-
88
- def test_blocking_hook_crash_deny_reason_surfaces_the_constant() -> None:
89
- crash_result = HostedHookResult(
90
- exit_code=0,
91
- captured_stdout="",
92
- did_crash=True,
93
- is_blocking=True,
94
- )
95
- decision = aggregate_hosted_hook_results([crash_result])
96
- assert decision.should_deny
97
- assert BLOCKING_CRASH_DENY_REASON in decision.all_deny_reasons
98
-
99
-
100
- def test_refactor_guard_is_hosted_as_an_edit_and_multi_edit_advisory_hook() -> None:
101
- entry = _entry_for("advisory/refactor_guard.py")
102
- assert entry is not None, (
103
- "refactor_guard must be hosted by the dispatcher rather than spawning its own process"
40
+ assert all_script_paths == (
41
+ "advisory/refactor_guard.py",
42
+ "advisory/migration_safety_advisor.py",
104
43
  )
105
- assert entry.applicable_tool_names == frozenset({EDIT_TOOL_NAME, MULTI_EDIT_TOOL_NAME})
106
- assert entry.is_blocking is False
44
+ assert all(not each_entry.is_blocking for each_entry in ALL_HOSTED_HOOK_ENTRIES)
107
45
 
108
46
 
109
- def test_migration_safety_advisor_is_hosted_as_an_edit_and_multi_edit_advisory_hook() -> None:
110
- entry = _entry_for("advisory/migration_safety_advisor.py")
111
- assert entry is not None, (
112
- "migration_safety_advisor must be hosted by the dispatcher rather than "
113
- "spawning its own process"
114
- )
115
- assert entry.applicable_tool_names == frozenset({EDIT_TOOL_NAME, MULTI_EDIT_TOOL_NAME})
116
- assert entry.is_blocking is False
47
+ def test_advisors_apply_to_edit_and_multi_edit() -> None:
48
+ for each_script_path in (
49
+ "advisory/refactor_guard.py",
50
+ "advisory/migration_safety_advisor.py",
51
+ ):
52
+ entry = _entry_for(each_script_path)
53
+ assert entry is not None
54
+ assert entry.applicable_tool_names == frozenset({EDIT_TOOL_NAME, MULTI_EDIT_TOOL_NAME})
117
55
 
118
56
 
119
57
  def test_every_hosted_script_path_exists_under_the_hooks_root() -> None:
120
58
  for each_entry in ALL_HOSTED_HOOK_ENTRIES:
121
59
  hosted_script_path = _HOOKS_ROOT / each_entry.script_relative_path
122
- assert hosted_script_path.is_file(), (
123
- f"{each_entry.script_relative_path} is on the roster but missing from the hooks tree"
124
- )
60
+ assert hosted_script_path.is_file()
125
61
 
126
62
 
127
63
  def test_retired_native_detector_keeps_its_callable_linter_interface() -> None:
@@ -137,92 +73,13 @@ def test_four_way_set_covers_every_mutation_tool_name() -> None:
137
73
  )
138
74
 
139
75
 
140
- def test_immediate_harm_hooks_reach_apply_patch() -> None:
141
- """PII, sensitive-path, and existing-file checks retain apply_patch coverage."""
142
- for each_script_path in ALL_IMMEDIATE_HARM_SCRIPT_PATHS:
143
- entry = _entry_for(each_script_path)
144
- assert entry is not None, f"{each_script_path} must stay on the hosted roster"
145
- assert APPLY_PATCH_TOOL_NAME in entry.applicable_tool_names, (
146
- f"{each_script_path} must reach apply_patch for mutation-tool parity"
147
- )
148
-
149
-
150
- _ALL_DEFERRED_LINT_SCRIPT_PATHS = (
151
- "blocking/claude_md_orphan_file_blocker.py",
152
- "blocking/package_inventory_stale_blocker.py",
153
- "blocking/env_var_table_code_drift_blocker.py",
154
- "blocking/pytest_testpaths_orphan_blocker.py",
155
- "blocking/open_questions_in_plans_blocker.py",
156
- "blocking/docstring_rule_gate_count_blocker.py",
157
- "blocking/duplicate_rmtree_helper_blocker.py",
158
- "validation/hook_format_validator.py",
159
- "advisory/refactor_guard.py",
160
- "advisory/migration_safety_advisor.py",
161
- )
162
-
163
-
164
- def test_lint_and_advisory_hooks_stay_off_the_apply_patch_roster() -> None:
165
- """Every hook reaching MultiEdit but not apply_patch fails the same one rule.
166
-
167
- apply_patch reaches only a gate whose violation is already real and
168
- unrecoverable the moment the call returns: a leaked secret, an exposed
169
- sensitive path, or a blind overwrite. Every
170
- hook named here instead judges the quality of the authored content for a
171
- consequence the write itself does not cause right now: doc-inventory
172
- drift, prose or naming style, a future-runtime correctness pattern (an
173
- unsafe rmtree that only misbehaves on Windows, a subprocess-timeout
174
- budget the harness reads separately), or an advisory suggestion that
175
- never denies at all. Each is fixable in a later Edit-tool pass, so none
176
- needs apply_patch's narrower, deny-only roster.
177
- """
178
- for each_script_path in _ALL_DEFERRED_LINT_SCRIPT_PATHS:
179
- entry = _entry_for(each_script_path)
180
- assert entry is not None, f"{each_script_path} must stay on the hosted roster"
181
- assert MULTI_EDIT_TOOL_NAME in entry.applicable_tool_names, (
182
- f"{each_script_path} must still reach MultiEdit"
183
- )
184
- assert APPLY_PATCH_TOOL_NAME not in entry.applicable_tool_names, (
185
- f"{each_script_path} judges deferred-fixable quality, not immediate harm, "
186
- "so it must stay off the apply_patch roster"
187
- )
188
-
189
-
190
- def test_edit_and_multi_edit_applicable_sets_are_equal() -> None:
191
- """The Edit roster and the MultiEdit roster name the same hooks.
192
-
193
- Every hook applicable to Edit judges Write's content or an Edit's
194
- old/new string pair with no dependency on which tool delivered it, so
195
- the two sets must match exactly. A hook that legitimately stays
196
- Edit-only is an exception written down here with its reason, not a
197
- silent gap this test lets back in.
198
- """
199
- all_edit_script_paths = {
200
- each_entry.script_relative_path
201
- for each_entry in ALL_HOSTED_HOOK_ENTRIES
202
- if EDIT_TOOL_NAME in each_entry.applicable_tool_names
203
- }
204
- all_multi_edit_script_paths = {
205
- each_entry.script_relative_path
206
- for each_entry in ALL_HOSTED_HOOK_ENTRIES
207
- if MULTI_EDIT_TOOL_NAME in each_entry.applicable_tool_names
208
- }
209
- assert all_edit_script_paths == all_multi_edit_script_paths, (
210
- "Edit-only (not MultiEdit): "
211
- f"{sorted(all_edit_script_paths - all_multi_edit_script_paths)}; "
212
- "MultiEdit-only (not Edit): "
213
- f"{sorted(all_multi_edit_script_paths - all_edit_script_paths)}"
76
+ def test_blocking_hook_crash_deny_reason_surfaces_the_constant() -> None:
77
+ crash_result = HostedHookResult(
78
+ exit_code=0,
79
+ captured_stdout="",
80
+ did_crash=True,
81
+ is_blocking=True,
214
82
  )
215
-
216
-
217
- def test_retained_boundary_hooks_reach_multi_edit() -> None:
218
- all_multi_edit_widened_script_paths = (
219
- "blocking/write_existing_file_blocker.py",
220
- "blocking/sensitive_file_protector.py",
221
- "blocking/pii_prevention_blocker.py",
222
- )
223
- for each_script_path in all_multi_edit_widened_script_paths:
224
- entry = _entry_for(each_script_path)
225
- assert entry is not None, f"{each_script_path} must stay on the hosted roster"
226
- assert MULTI_EDIT_TOOL_NAME in entry.applicable_tool_names, (
227
- f"{each_script_path} must reach MultiEdit for mutation-tool parity"
228
- )
83
+ decision = aggregate_hosted_hook_results([crash_result])
84
+ assert decision.should_deny
85
+ assert BLOCKING_CRASH_DENY_REASON in decision.all_deny_reasons
@@ -1,29 +1,7 @@
1
1
  """Tests for the Stop-hook dispatcher hosted-hook roster."""
2
2
 
3
- from __future__ import annotations
3
+ from hooks_constants.stop_dispatcher_constants import ALL_STOP_HOSTED_HOOK_PATHS
4
4
 
5
- import sys
6
- from pathlib import Path
7
5
 
8
- _HOOKS_DIR = str(Path(__file__).resolve().parent.parent)
9
- if _HOOKS_DIR not in sys.path:
10
- sys.path.insert(0, _HOOKS_DIR)
11
-
12
- from hooks_constants.stop_dispatcher_constants import ALL_STOP_HOSTED_HOOK_PATHS # noqa: E402
13
-
14
- _EXPECTED_STOP_ORDER = (
15
- "blocking/question_to_user_enforcer.py",
16
- "blocking/session_handoff_blocker.py",
17
- )
18
-
19
-
20
- def test_roster_lists_all_stop_hooks_in_registration_order() -> None:
21
- """The roster reproduces the standalone Stop chain order exactly."""
22
- assert ALL_STOP_HOSTED_HOOK_PATHS == _EXPECTED_STOP_ORDER
23
-
24
-
25
- def test_every_roster_path_points_at_an_existing_hook() -> None:
26
- """Each roster path names a hook script present on disk, catching a typo."""
27
- hooks_root = Path(__file__).resolve().parent.parent
28
- for each_relative_path in ALL_STOP_HOSTED_HOOK_PATHS:
29
- assert (hooks_root / each_relative_path).is_file()
6
+ def test_roster_has_no_blocking_hooks() -> None:
7
+ assert ALL_STOP_HOSTED_HOOK_PATHS == ()