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
@@ -286,7 +286,7 @@ test('installer fault after_file_staging restores prior managed installation', (
286
286
  'utf8',
287
287
  );
288
288
 
289
- const failedRun = runInstaller(homeDirectory, ['--only', 'journal'], {
289
+ const failedRun = runInstaller(homeDirectory, ['--only', 'core'], {
290
290
  faultPhase: FAULT_PHASES.AFTER_FILE_STAGING,
291
291
  });
292
292
  assert.notEqual(failedRun.status, 0, failedRun.stdout + failedRun.stderr);
@@ -333,7 +333,7 @@ test('installer fault after_manifest_write restores prior manifest', () => {
333
333
  'utf8',
334
334
  );
335
335
 
336
- const failedRun = runInstaller(homeDirectory, ['--only', 'journal'], {
336
+ const failedRun = runInstaller(homeDirectory, ['--only', 'core'], {
337
337
  faultPhase: FAULT_PHASES.AFTER_MANIFEST_WRITE,
338
338
  });
339
339
  assert.notEqual(failedRun.status, 0, failedRun.stdout + failedRun.stderr);
@@ -387,7 +387,7 @@ test('successful install after prior install leaves one manifest and no fault',
387
387
  const homeDirectory = mkdtempSync(join(tmpdir(), 'cdev-txn-e2e-ok-'));
388
388
  try {
389
389
  seedPriorInstall(homeDirectory);
390
- const okRun = runInstaller(homeDirectory, ['--only', 'journal']);
390
+ const okRun = runInstaller(homeDirectory, ['--only', 'core']);
391
391
  assert.equal(okRun.status, 0, okRun.stdout + okRun.stderr);
392
392
  const manifestPath = join(homeDirectory, '.claude', '.claude-dev-env-manifest.json');
393
393
  assert.equal(existsSync(manifestPath), true);
@@ -43,7 +43,7 @@ Before writing ANY constant: search `config/` for the exact value → semantic m
43
43
 
44
44
  ## 5. NO ABBREVIATIONS
45
45
 
46
- Full words only (`context`, not `ctx`). Exceptions: `i`/`j`/`k` in loops, `e` for exception. Naming patterns: loop vars `each_*`; booleans `is_/has_/should_/can_/was_/did_`; collections `all_*`; maps `X_by_Y`; preposition params (`from_path=`, `to=`, `into=`). Banned names: `result`, `data`, `output`, `response`, `value`, `item`, `temp`. Banned prefixes: `handle`, `process`, `manage`, `do`. Name a component for what it IS — `Overlay`, `Validator`, `InvoicePreview`.
46
+ Full words only (`context`, not `ctx`). Exceptions: `i`/`j`/`k` in loops, `e` for exception. Naming patterns: loop vars `each_*`; booleans `is_/has_/should_/can_/was_/did_`; collections `all_*`; maps `X_by_Y`; preposition params (`from_path=`, `to=`, `into=`). Banned names: `result`, `data`, `output`, `response`, `value`, `item`, `temp`. Banned prefixes: `handle`, `process`, `manage`, `do`. Name components for what they are: `Overlay`, `Validator`, `InvoicePreview`. The failure-blast-radius rule requires `ItemBlocked` or `RunFatal`. Names ending with either bypass the banned-noun word check.
47
47
 
48
48
  ### Public compatibility definitions
49
49
 
@@ -30,6 +30,7 @@ from code_rules_shared import ( # noqa: E402
30
30
  from hooks_constants.banned_identifiers_constants import ( # noqa: E402
31
31
  ALL_BANNED_IDENTIFIERS,
32
32
  ALL_BANNED_NOUN_WORDS,
33
+ ALL_BLAST_RADIUS_EXCEPTION_SUFFIXES,
33
34
  BANNED_IDENTIFIER_MESSAGE_SUFFIX,
34
35
  BANNED_IDENTIFIER_SKIP_ADVISORY,
35
36
  BANNED_NOUN_COMPATIBILITY_COMMENT,
@@ -228,6 +229,8 @@ def _find_banned_noun_word(identifier: str) -> str | None:
228
229
  identifier (e.g., ``'result'`` for ``'HolidayPeakResult'``). Returns
229
230
  ``None`` when no banned noun word is present.
230
231
  """
232
+ if identifier.endswith(ALL_BLAST_RADIUS_EXCEPTION_SUFFIXES):
233
+ return None
231
234
  for each_word in _identifier_word_parts(identifier):
232
235
  if each_word in ALL_BANNED_NOUN_WORDS:
233
236
  return each_word
@@ -1,11 +1,8 @@
1
- """Library helper: resolve a directory's Git repository root.
2
-
3
- ``pii_prevention_blocker.py`` and ``pii_payload_scan.py`` import
4
- ``resolve_repository_root`` to find the repository a staged-commit scan
5
- targets, and ``session_edit_stage_gate.py`` imports it to find the
6
- repository a commit's unstaged-edit check targets. This module carries no
7
- hook entry point of its own; the native ``git-hooks/pre_commit.py`` runs
8
- the real commit-time CODE_RULES enforcement.
1
+ """Library helper that resolves a directory to its Git repository root.
2
+
3
+ ``pii_payload_scan.py`` imports ``resolve_repository_root`` for staged scans.
4
+ This module has no hook entry point. Native ``git-hooks/pre_commit.py`` shows
5
+ the local verification advisory and does not enforce CODE_RULES.
9
6
  """
10
7
 
11
8
  import subprocess
@@ -1,61 +1,16 @@
1
- """Benchmark: consolidated Bash dispatcher starts one interpreter, not N.
1
+ """Tests for the retired Bash and PowerShell blocking roster."""
2
2
 
3
- Standalone registration starts one Python process per hosted hook. The
4
- dispatcher replaces that chain with a single process that hosts every hook
5
- in-process. This test pins the before/after interpreter-start counts so a
6
- regression that re-splits the chain fails CI.
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- import json
12
- import subprocess
13
3
  import sys
14
4
  from pathlib import Path
15
5
 
16
- _HOOKS_DIR = str(Path(__file__).resolve().parent.parent)
17
- if _HOOKS_DIR not in sys.path:
18
- sys.path.insert(0, _HOOKS_DIR)
6
+ _HOOKS_ROOT = Path(__file__).resolve().parent.parent
7
+ if str(_HOOKS_ROOT) not in sys.path:
8
+ sys.path.insert(0, str(_HOOKS_ROOT))
19
9
 
20
- from hooks_constants.bash_pre_tool_use_dispatcher_constants import ( # noqa: E402
10
+ from hooks_constants.bash_pre_tool_use_dispatcher_constants import (
21
11
  ALL_BASH_HOSTED_HOOK_ENTRIES,
22
- BASH_TOOL_NAME,
23
12
  )
24
13
 
25
- _BLOCKING_DIR = Path(__file__).resolve().parent
26
- _HOOKS_ROOT = _BLOCKING_DIR.parent
27
- _DISPATCHER_SCRIPT = str(_BLOCKING_DIR / "bash_pre_tool_use_dispatcher.py")
28
-
29
- STANDALONE_INTERPRETER_STARTS_PER_BASH_CALL = len(ALL_BASH_HOSTED_HOOK_ENTRIES)
30
- DISPATCHER_INTERPRETER_STARTS_PER_BASH_CALL = 1
31
-
32
-
33
- def test_roster_is_large_enough_that_consolidation_saves_starts() -> None:
34
- """Consolidation only pays off when the chain hosts more than one hook."""
35
- assert STANDALONE_INTERPRETER_STARTS_PER_BASH_CALL >= 10
36
- assert DISPATCHER_INTERPRETER_STARTS_PER_BASH_CALL == 1
37
- assert STANDALONE_INTERPRETER_STARTS_PER_BASH_CALL > DISPATCHER_INTERPRETER_STARTS_PER_BASH_CALL
38
-
39
-
40
- def test_dispatcher_handles_a_bash_call_in_one_process() -> None:
41
- """One subprocess run of the dispatcher covers the full Bash hosted chain."""
42
- payload_text = json.dumps(
43
- {"tool_name": BASH_TOOL_NAME, "tool_input": {"command": "echo interpreter-start-probe"}}
44
- )
45
- completed = subprocess.run(
46
- [sys.executable, _DISPATCHER_SCRIPT],
47
- check=False,
48
- input=payload_text,
49
- capture_output=True,
50
- text=True,
51
- encoding="utf-8",
52
- )
53
- assert completed.returncode == 0
54
- assert DISPATCHER_INTERPRETER_STARTS_PER_BASH_CALL == 1
55
-
56
14
 
57
- def test_standalone_chain_would_start_one_process_per_roster_entry() -> None:
58
- """Each roster path is a real script that would own its own interpreter start."""
59
- for each_entry in ALL_BASH_HOSTED_HOOK_ENTRIES:
60
- assert (_HOOKS_ROOT / each_entry.script_relative_path).is_file()
61
- assert len(ALL_BASH_HOSTED_HOOK_ENTRIES) == STANDALONE_INTERPRETER_STARTS_PER_BASH_CALL
15
+ def test_retired_bash_roster_has_no_effective_blocking_hooks() -> None:
16
+ assert ALL_BASH_HOSTED_HOOK_ENTRIES == ()
@@ -100,17 +100,6 @@ def test_clean_command_yields_no_decision_matching_the_standalone_chain() -> Non
100
100
  assert dispatcher_decision == expected_decision
101
101
 
102
102
 
103
- def test_destructive_command_asks_matching_the_standalone_chain() -> None:
104
- """A destructive command the chain asks on makes the dispatcher ask too."""
105
- payload_text = _bash_payload("rm -rf /")
106
- expected_decision = _expected_precedence(payload_text, BASH_TOOL_NAME)
107
- dispatcher_decision, _reason = _decision_from_stdout(
108
- _run_process(_DISPATCHER_SCRIPT, payload_text).stdout
109
- )
110
- assert dispatcher_decision == expected_decision
111
- assert expected_decision == "ask"
112
-
113
-
114
103
  def test_dispatcher_exits_zero_on_a_clean_command() -> None:
115
104
  """The dispatcher exits zero for a command no hook decides on."""
116
105
  completed = _run_process(_DISPATCHER_SCRIPT, _bash_payload("echo hi"))
@@ -212,19 +201,6 @@ def test_additional_context_is_collected_from_deciding_hooks() -> None:
212
201
  assert aggregated.all_additional_context == ["see docs/runbook.md"]
213
202
 
214
203
 
215
- def test_powershell_selects_the_shared_hooks_in_registration_order() -> None:
216
- """Selecting for PowerShell yields the shared hooks in registration order."""
217
- powershell_paths = [
218
- each_entry.script_relative_path
219
- for each_entry in select_applicable_entries(POWERSHELL_TOOL_NAME)
220
- ]
221
- assert powershell_paths == [
222
- "blocking/cursor_cli_python_misfire_blocker.py",
223
- "blocking/unscoped_search_blocker.py",
224
- "blocking/pii_prevention_blocker.py",
225
- ]
226
-
227
-
228
204
  def test_dispatcher_imports_standalone_with_only_blocking_on_the_path() -> None:
229
205
  """The dispatcher's bootstrap resolves hooks_constants without hooks/ on PYTHONPATH."""
230
206
  subprocess_environment = {**os.environ, "PYTHONPATH": str(_BLOCKING_DIR)}
@@ -259,48 +235,3 @@ def test_emit_decision_re_emits_silent_deny_fields(
259
235
  assert hook_specific["additionalContext"] == "see docs/runbook.md"
260
236
  assert emitted_payload["systemMessage"] == "[gh-gate] blocked redirected gh pr create"
261
237
  assert emitted_payload["suppressOutput"] is True
262
-
263
-
264
- def test_dispatcher_denies_subshell_disguised_as_arithmetic_expansion() -> None:
265
- """The dispatcher denies a `$((cd X) && Y)` subshell, not only the bare hook.
266
-
267
- `echo $((cd /tmp) && pwd)` opens with a `$((` two-character lookahead
268
- match, so a function-level test alone does not prove the block reaches
269
- the live Bash tool path.
270
- """
271
- payload_text = _bash_payload("echo $((cd /tmp) && pwd)")
272
- decision, _reason = _decision_from_stdout(_run_process(_DISPATCHER_SCRIPT, payload_text).stdout)
273
- assert decision == "deny"
274
-
275
-
276
- def test_dispatch_emits_deny_immediately_and_skips_later_hooks(
277
- monkeypatch: pytest.MonkeyPatch,
278
- capsys: pytest.CaptureFixture[str],
279
- ) -> None:
280
- """A mid-chain deny emits at once and does not invoke later hosted hooks."""
281
- all_call_paths: list[str] = []
282
-
283
- def _fake_run_hook(script_path: str, payload_text: str) -> HostedHookRun:
284
- del payload_text
285
- all_call_paths.append(script_path)
286
- script_name = Path(script_path).name
287
- if script_name == "destructive_command_blocker.py":
288
- return HostedHookRun(
289
- captured_stdout=_decision_json("deny", "blocked early"),
290
- did_crash=False,
291
- )
292
- if script_name == "precommit_code_rules_gate.py":
293
- raise AssertionError("later hooks must not run after an early deny")
294
- return HostedHookRun(captured_stdout="", did_crash=False)
295
-
296
- monkeypatch.setattr(
297
- "bash_pre_tool_use_dispatcher.run_hook_capturing_output",
298
- _fake_run_hook,
299
- )
300
- dispatch(_bash_payload("rm -rf /"), BASH_TOOL_NAME)
301
- captured = capsys.readouterr()
302
- decision, reason = _decision_from_stdout(captured.out)
303
- assert decision == "deny"
304
- assert "blocked early" in reason
305
- assert any(path.endswith("destructive_command_blocker.py") for path in all_call_paths)
306
- assert not any(path.endswith("precommit_code_rules_gate.py") for path in all_call_paths)
@@ -0,0 +1,64 @@
1
+ from __future__ import annotations
2
+
3
+ from code_rules_banned_identifiers import (
4
+ _find_banned_noun_word,
5
+ check_banned_noun_word_boundary,
6
+ )
7
+
8
+ PRODUCTION_FILE_PATH = "packages/app/services/customer_pipeline.py"
9
+
10
+
11
+ def test_should_return_no_banned_word_for_item_blocked_exception_suffix() -> None:
12
+ assert _find_banned_noun_word("AssetSizingItemBlocked") is None
13
+ assert _find_banned_noun_word("NinePatchGuideFrameItemBlocked") is None
14
+
15
+
16
+ def test_should_return_no_banned_word_for_run_fatal_exception_suffix() -> None:
17
+ assert _find_banned_noun_word("AssetVariantRunFatal") is None
18
+
19
+
20
+ def test_should_return_banned_word_for_item_without_blast_radius_suffix() -> None:
21
+ assert _find_banned_noun_word("IconGridItem") == "item"
22
+ assert _find_banned_noun_word("item_key") == "item"
23
+
24
+
25
+ def test_should_allow_class_ending_with_item_blocked_suffix() -> None:
26
+ source = "class AssetSizingItemBlocked(Exception):\n pass\n"
27
+ issues = check_banned_noun_word_boundary(source, PRODUCTION_FILE_PATH)
28
+ assert issues == []
29
+
30
+
31
+ def test_should_allow_long_class_ending_with_item_blocked_suffix() -> None:
32
+ source = "class NinePatchGuideFrameItemBlocked(Exception):\n pass\n"
33
+ issues = check_banned_noun_word_boundary(source, PRODUCTION_FILE_PATH)
34
+ assert issues == []
35
+
36
+
37
+ def test_should_allow_class_ending_with_run_fatal_suffix() -> None:
38
+ source = "class AssetVariantRunFatal(Exception):\n pass\n"
39
+ issues = check_banned_noun_word_boundary(source, PRODUCTION_FILE_PATH)
40
+ assert issues == []
41
+
42
+
43
+ def test_should_flag_class_ending_with_item_without_blast_radius_suffix() -> None:
44
+ source = "class IconGridItem:\n pass\n"
45
+ issues = check_banned_noun_word_boundary(source, PRODUCTION_FILE_PATH)
46
+ assert any("IconGridItem" in each_issue for each_issue in issues)
47
+
48
+
49
+ def test_should_flag_item_key_assignment() -> None:
50
+ source = "item_key = 'slot'\n"
51
+ issues = check_banned_noun_word_boundary(source, PRODUCTION_FILE_PATH)
52
+ assert any("item_key" in each_issue for each_issue in issues)
53
+
54
+
55
+ def test_should_flag_output_path_assignment() -> None:
56
+ source = "output_path = 'slot'\n"
57
+ issues = check_banned_noun_word_boundary(source, PRODUCTION_FILE_PATH)
58
+ assert any("output_path" in each_issue for each_issue in issues)
59
+
60
+
61
+ def test_should_flag_each_value_assignment() -> None:
62
+ source = "each_value = 1\n"
63
+ issues = check_banned_noun_word_boundary(source, PRODUCTION_FILE_PATH)
64
+ assert any("each_value" in each_issue for each_issue in issues)
@@ -1,14 +1,8 @@
1
1
  """Golden differential and failure-mode tests for the PreToolUse dispatcher.
2
2
 
3
- Each golden differential test runs a payload through every applicable hosted
4
- hook as its own subprocess (the production path), records each hook's
5
- allow-or-deny and messages, computes the expected aggregate, then runs the
6
- dispatcher on the same payload and asserts equal decision and equal message
7
- union.
8
-
9
- The failure-mode tests cover one row each from spec/failure-modes.md:
10
- early-exit-then-later-deny, multi-deny, context-survival, blocking-hook crash,
11
- fail-open malformed input.
3
+ The live roster carries only nonblocking edit advisors. These tests keep the
4
+ aggregator contract and prove retired policy registrations fail open while
5
+ explicit standalone checks remain callable.
12
6
  """
13
7
 
14
8
  from __future__ import annotations
@@ -31,7 +25,6 @@ if str(_HOOKS_ROOT) not in sys.path:
31
25
 
32
26
  from hooks_constants.pre_tool_use_dispatcher_constants import ( # noqa: E402, I001
33
27
  ALL_HOSTED_HOOK_ENTRIES,
34
- ALL_IMMEDIATE_HARM_SCRIPT_PATHS,
35
28
  APPLY_PATCH_TOOL_NAME,
36
29
  BLOCKING_CRASH_DENY_REASON,
37
30
  BLOCKING_CRASH_EXIT_CODE,
@@ -407,53 +400,6 @@ def test_write_existing_file_blocker_allows_multi_edit_to_an_existing_path(
407
400
  _assert_dispatcher_matches_individual_hooks(payload_text, MULTI_EDIT_TOOL_NAME)
408
401
 
409
402
 
410
- def test_context_survives_alongside_deny_reason(
411
- monkeypatch: pytest.MonkeyPatch, tmp_path: Path
412
- ) -> None:
413
- """A non-denying hook's additional context survives in the dispatcher output.
414
-
415
- This tests that hooks whose output is additional-context (not a deny) still
416
- have their output preserved when another hook denies.
417
- """
418
- monkeypatch.setenv("HOME", str(tmp_path))
419
- monkeypatch.setenv("USERPROFILE", str(tmp_path))
420
- sensitive_path = str(Path.home() / ".env")
421
- payload_text = _write_payload(sensitive_path, "SECRET=abc")
422
- dispatcher_result = _run_dispatcher(payload_text)
423
- is_deny, _reason = _parse_hook_decision(dispatcher_result)
424
- assert is_deny, (
425
- "sensitive_file_protector should deny a write to .env — "
426
- "if it did not, check whether the path is on the sensitive list"
427
- )
428
- assert dispatcher_result.stdout.strip(), "Dispatcher must emit output when denying"
429
-
430
-
431
- def test_all_deny_reasons_present_when_multiple_hooks_deny(
432
- monkeypatch: pytest.MonkeyPatch, tmp_path: Path,
433
- ) -> None:
434
- """Preserve every deny reason from the retained file-boundary checks."""
435
- monkeypatch.setenv("HOME", str(tmp_path))
436
- monkeypatch.setenv("USERPROFILE", str(tmp_path))
437
- existing_sensitive_path = tmp_path / ".env"
438
- existing_sensitive_path.write_text("VALUE=fixture\n", encoding="utf-8")
439
- payload_text = _write_payload(str(existing_sensitive_path), "VALUE=changed\n")
440
- _assert_dispatcher_matches_individual_hooks(payload_text, WRITE_TOOL_NAME)
441
- dispatcher_result = _run_dispatcher(payload_text)
442
- dispatcher_is_deny, dispatcher_reason = _parse_hook_decision(dispatcher_result)
443
- assert dispatcher_is_deny
444
- all_expected_deny_reasons: list[str] = []
445
- for each_entry in _applicable_entries_for_tool(WRITE_TOOL_NAME):
446
- completed_process = _run_hook_subprocess(each_entry.script_relative_path, payload_text)
447
- is_deny, reason_text = _parse_hook_decision(completed_process)
448
- if is_deny and reason_text:
449
- all_expected_deny_reasons.append(reason_text)
450
- assert len(all_expected_deny_reasons) >= 2, (
451
- "Existing-file and sensitive-file checks must both deny the fixture"
452
- )
453
- for each_reason in all_expected_deny_reasons:
454
- assert each_reason in dispatcher_reason
455
-
456
-
457
403
  def test_aggregate_exit_code_two_signals_deny() -> None:
458
404
  """A HostedHookResult with exit_code 2 and did_crash False signals deny.
459
405
 
@@ -672,81 +618,6 @@ def test_emit_allow_preserves_system_message_and_additional_context(
672
618
  assert parsed["systemMessage"] == "system-a\nsystem-b"
673
619
 
674
620
 
675
- def test_later_hook_deny_survives_early_hook_exit(tmp_path: Path) -> None:
676
- """A retained document-reference denial survives earlier successful checks."""
677
- payload_text = _orphan_claude_md_payload(tmp_path)
678
- _assert_dispatcher_matches_individual_hooks(payload_text, WRITE_TOOL_NAME)
679
- is_deny, reason = _parse_hook_decision(_run_dispatcher(payload_text))
680
- assert is_deny, reason
681
-
682
-
683
- def test_dispatcher_write_applies_both_groups() -> None:
684
- """Write tool triggers both Group A and Group B hooks through the dispatcher.
685
-
686
- Verifies that the set of applicable entries for Write includes entries from
687
- both ALL_WRITE_AND_EDIT_TOOL_NAMES (Group A) and ALL_WRITE_EDIT_MULTI_EDIT_TOOL_NAMES
688
- (Group B) in the constants.
689
- """
690
- all_write_entries = _applicable_entries_for_tool(WRITE_TOOL_NAME)
691
- all_write_script_paths = {each_entry.script_relative_path for each_entry in all_write_entries}
692
- assert "blocking/write_existing_file_blocker.py" in all_write_script_paths, (
693
- "write_existing_file_blocker (Group A) must be in Write applicable set"
694
- )
695
- assert len(all_write_entries) == 11, (
696
- f"Write tool must apply to the 11 retained write hooks, got {len(all_write_entries)}"
697
- )
698
-
699
-
700
- def test_dispatcher_edit_applies_both_groups() -> None:
701
- """Edit triggers Group A, Group B, and the Edit-scoped entry through the
702
- dispatcher.
703
- """
704
- all_edit_entries = _applicable_entries_for_tool(EDIT_TOOL_NAME)
705
- all_edit_script_paths = {each_entry.script_relative_path for each_entry in all_edit_entries}
706
- assert "blocking/stale_comment_reference_blocker.py" not in all_edit_script_paths
707
- assert "advisory/refactor_guard.py" in all_edit_script_paths, (
708
- "refactor_guard is Edit-scoped and hosted, so it belongs in the Edit applicable set"
709
- )
710
- assert len(all_edit_entries) == 13, (
711
- f"expected 13 retained Edit entries, got {len(all_edit_entries)}"
712
- )
713
-
714
-
715
- def test_dispatcher_multi_edit_reaches_the_sensitive_protector() -> None:
716
- """A MultiEdit onto a sensitive path meets the same gate a Write does.
717
-
718
- test_edit_and_multi_edit_applicable_sets_are_equal in the constants suite
719
- holds the Edit and MultiEdit rosters equal, so this pins the one hook whose
720
- absence would let a secret through a MultiEdit.
721
- """
722
- all_multi_edit_entries = _applicable_entries_for_tool(MULTI_EDIT_TOOL_NAME)
723
- all_multi_edit_script_paths = {
724
- each_entry.script_relative_path for each_entry in all_multi_edit_entries
725
- }
726
- assert "blocking/sensitive_file_protector.py" in all_multi_edit_script_paths, (
727
- "sensitive_file_protector belongs in the MultiEdit applicable set"
728
- )
729
-
730
-
731
- def test_dispatcher_apply_patch_applies_immediate_harm_hooks() -> None:
732
- """apply_patch applies to exactly the immediate-harm roster.
733
-
734
- apply_patch reaches only the retained immediate-harm roster the constants
735
- module names, not the full Write/Edit/MultiEdit lint surface.
736
- """
737
- all_apply_patch_entries = _applicable_entries_for_tool(APPLY_PATCH_TOOL_NAME)
738
- all_apply_patch_script_paths = {
739
- each_entry.script_relative_path for each_entry in all_apply_patch_entries
740
- }
741
- assert "blocking/sensitive_file_protector.py" in all_apply_patch_script_paths, (
742
- "sensitive_file_protector belongs in the apply_patch applicable set"
743
- )
744
- assert all_apply_patch_script_paths == set(ALL_IMMEDIATE_HARM_SCRIPT_PATHS), (
745
- "apply_patch must apply to exactly the immediate-harm roster, got: "
746
- f"{sorted(all_apply_patch_script_paths)}"
747
- )
748
-
749
-
750
621
  def _assert_roster_names_no_run_all_validators_entry() -> None:
751
622
  """Assert no hosted-roster ``script_relative_path`` names run_all_validators."""
752
623
  all_roster_script_paths = {
@@ -809,40 +680,6 @@ def _orphan_claude_md_payload(tmp_path: Path) -> str:
809
680
  return _write_payload(claude_md_path, orphan_table)
810
681
 
811
682
 
812
- def test_runpy_deny_preserves_additional_context_and_suppress_output(tmp_path: Path) -> None:
813
- """A runpy-hosted deny carries its additionalContext and suppressOutput through the dispatcher.
814
-
815
- claude_md_orphan_file_blocker emits hookSpecificOutput.additionalContext and a
816
- top-level suppressOutput flag on a deny. The dispatcher must preserve both so
817
- the dispatched denial matches the standalone hook's deny shape.
818
-
819
- Args:
820
- tmp_path: Pytest temp directory hosting the throwaway CLAUDE.md.
821
- """
822
- payload_text = _orphan_claude_md_payload(tmp_path)
823
-
824
- standalone_result = _run_hook_subprocess(
825
- "blocking/claude_md_orphan_file_blocker.py", payload_text
826
- )
827
- standalone_payload = json.loads(standalone_result.stdout.strip())
828
- standalone_hook_specific = standalone_payload["hookSpecificOutput"]
829
- expected_additional_context = standalone_hook_specific["additionalContext"]
830
-
831
- dispatcher_result = _run_dispatcher(payload_text)
832
- dispatcher_payload = json.loads(dispatcher_result.stdout.strip())
833
- dispatcher_hook_specific = dispatcher_payload.get("hookSpecificOutput", {})
834
- assert isinstance(dispatcher_hook_specific, dict)
835
- assert dispatcher_hook_specific.get("additionalContext") == expected_additional_context, (
836
- "Dispatcher must preserve the runpy hook's additionalContext.\n"
837
- f"Expected: {expected_additional_context!r}\n"
838
- f"Got: {dispatcher_hook_specific.get('additionalContext')!r}"
839
- )
840
- assert dispatcher_payload.get("suppressOutput") is True, (
841
- "Dispatcher must preserve the runpy hook's suppressOutput flag.\n"
842
- f"Got: {dispatcher_payload.get('suppressOutput')!r}"
843
- )
844
-
845
-
846
683
  def _parse_hook_allow(completed_process: subprocess.CompletedProcess[str]) -> bool:
847
684
  """Parse one hook's subprocess result for an explicit permissionDecision allow.
848
685
 
@@ -996,72 +833,6 @@ def _codex_add_patch(relative_file_path: str, file_body: str) -> str:
996
833
  )
997
834
 
998
835
 
999
- def test_dispatcher_denies_apply_patch_add_with_hardcoded_secret(
1000
- tmp_path: Path,
1001
- init_bare_git_repo: Callable[[Path], None],
1002
- synthetic_github_token: str,
1003
- ) -> None:
1004
- """The dispatcher denies an apply_patch "add" that writes a hardcoded token.
1005
-
1006
- Proves Done-when scenario 1 (hardcoded secret/PII denied) reaches apply_patch
1007
- through the real dispatcher subprocess, not only the standalone
1008
- pii_prevention_blocker test.
1009
- """
1010
- repository_root = tmp_path / "repo"
1011
- init_bare_git_repo(repository_root)
1012
- patch_command = _codex_add_patch("leaked.py", f"token is {synthetic_github_token}\n")
1013
- payload_text = _apply_patch_payload(str(repository_root), patch_command)
1014
-
1015
- dispatcher_result = _run_dispatcher(payload_text)
1016
- is_deny, reason_text = _parse_hook_decision(dispatcher_result)
1017
- assert is_deny, (
1018
- "dispatcher must deny an apply_patch add carrying a hardcoded secret, "
1019
- f"got stdout {dispatcher_result.stdout.strip()!r}"
1020
- )
1021
- assert reason_text, "a deny must carry a non-empty reason"
1022
-
1023
-
1024
- def test_dispatcher_denies_apply_patch_targeting_a_sensitive_path(tmp_path: Path) -> None:
1025
- """The dispatcher denies an apply_patch "add" that targets a sensitive file.
1026
-
1027
- Proves Done-when scenario 2 (sensitive path denied) reaches apply_patch
1028
- through the real dispatcher subprocess.
1029
- """
1030
- patch_command = _codex_add_patch(".env", "SECRET_KEY=irrelevant\n")
1031
- payload_text = _apply_patch_payload(str(tmp_path), patch_command)
1032
-
1033
- dispatcher_result = _run_dispatcher(payload_text)
1034
- is_deny, reason_text = _parse_hook_decision(dispatcher_result)
1035
- assert is_deny, (
1036
- "dispatcher must deny an apply_patch add targeting a sensitive path, "
1037
- f"got stdout {dispatcher_result.stdout.strip()!r}"
1038
- )
1039
- assert reason_text, "a deny must carry a non-empty reason"
1040
-
1041
-
1042
- def test_dispatcher_denies_apply_patch_add_onto_an_existing_path(tmp_path: Path) -> None:
1043
- """The dispatcher denies an apply_patch "add" that targets a path already on disk.
1044
-
1045
- Proves Done-when scenario 3 (overwrite-without-reading denied) reaches
1046
- apply_patch: a Codex "add" operation onto an existing path is caught by
1047
- code_rules_enforcer's codex-patch reading, blocking/codex_apply_patch.py's
1048
- _codex_read_patch_file, which raises CodexPatchError for an add already on
1049
- disk, reported through the dispatcher's aggregate.
1050
- """
1051
- existing_target = tmp_path / "already_here.py"
1052
- existing_target.write_text("value = 1\n", encoding="utf-8")
1053
- patch_command = _codex_add_patch("already_here.py", "value = 2\n")
1054
- payload_text = _apply_patch_payload(str(tmp_path), patch_command)
1055
-
1056
- dispatcher_result = _run_dispatcher(payload_text)
1057
- is_deny, reason_text = _parse_hook_decision(dispatcher_result)
1058
- assert is_deny, (
1059
- "dispatcher must deny an apply_patch add onto an already-existing path, "
1060
- f"got stdout {dispatcher_result.stdout.strip()!r}"
1061
- )
1062
- assert reason_text, "a deny must carry a non-empty reason"
1063
-
1064
-
1065
836
  def test_dispatcher_allows_clean_apply_patch_add(tmp_path: Path) -> None:
1066
837
  """A clean patch can proceed before the staged linter evaluates test pairing."""
1067
838
  patch_command = _codex_add_patch(
@@ -1074,21 +845,11 @@ def test_dispatcher_allows_clean_apply_patch_add(tmp_path: Path) -> None:
1074
845
 
1075
846
 
1076
847
  ALL_RETAINED_PATHS = {
1077
- "blocking/write_existing_file_blocker.py",
1078
- "blocking/sensitive_file_protector.py",
1079
- "blocking/pii_prevention_blocker.py",
1080
- "validation/hook_format_validator.py",
1081
- "blocking/duplicate_rmtree_helper_blocker.py",
1082
- "blocking/claude_md_orphan_file_blocker.py",
1083
- "blocking/package_inventory_stale_blocker.py",
1084
- "blocking/env_var_table_code_drift_blocker.py",
1085
- "blocking/pytest_testpaths_orphan_blocker.py",
1086
- "blocking/open_questions_in_plans_blocker.py",
1087
- "blocking/docstring_rule_gate_count_blocker.py",
1088
848
  "advisory/refactor_guard.py",
1089
849
  "advisory/migration_safety_advisor.py",
1090
850
  }
1091
851
  ALL_RETIRED_PATHS = {
852
+ "validation/hook_format_validator.py",
1092
853
  "blocking/code_rules_enforcer.py",
1093
854
  "blocking/tdd_enforcer.py",
1094
855
  "blocking/windows_rmtree_blocker.py",
@@ -1096,10 +857,15 @@ ALL_RETIRED_PATHS = {
1096
857
  "blocking/subprocess_budget_completeness.py",
1097
858
  "blocking/hook_prose_detector_consistency.py",
1098
859
  "blocking/workflow_substitution_slot_blocker.py",
860
+ "blocking/open_questions_in_plans_blocker.py",
861
+ "blocking/docstring_rule_gate_count_blocker.py",
862
+ "blocking/duplicate_rmtree_helper_blocker.py",
863
+ "blocking/env_var_table_code_drift_blocker.py",
864
+ "blocking/pytest_testpaths_orphan_blocker.py",
1099
865
  }
1100
866
 
1101
867
  def test_hosted_hook_set_retains_boundaries_and_excludes_replaced_file_checks() -> None:
1102
- """Keep unresolved controls and exclude the seven staged-linter replacements."""
868
+ """Keep boundary controls and exclude every registry-backed policy hook."""
1103
869
  all_hosted_script_paths = {
1104
870
  each_entry.script_relative_path for each_entry in ALL_HOSTED_HOOK_ENTRIES
1105
871
  }