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
@@ -1,41 +0,0 @@
1
- """Windows path detection tests for the PII prevention commit gate.
2
-
3
- Guards the unquoted absolute Windows git path shape on the PowerShell surface::
4
-
5
- is_git_commit_shell_command(r"C:\\tools\\git.exe commit")
6
- ok: True -- the git.exe basename survives tokenization
7
- flag: False -- backslash escaping mangles the basename token
8
-
9
- An unquoted absolute path to the git binary must still be recognized as a
10
- commit so a staged blob carrying PII cannot slip past the gate.
11
- """
12
-
13
- from __future__ import annotations
14
-
15
- import importlib
16
- import sys
17
- from pathlib import Path
18
-
19
- _BLOCKING_DIRECTORY = Path(__file__).resolve().parent
20
- if str(_BLOCKING_DIRECTORY) not in sys.path:
21
- sys.path.insert(0, str(_BLOCKING_DIRECTORY))
22
-
23
- _blocker_module = importlib.import_module("pii_prevention_blocker")
24
-
25
-
26
- def test_unquoted_windows_git_exe_path_is_detected_as_commit() -> None:
27
- assert _blocker_module.is_git_commit_shell_command(r"C:\tools\git.exe commit -m x")
28
-
29
-
30
- def test_unquoted_windows_git_path_without_extension_is_detected() -> None:
31
- assert _blocker_module.is_git_commit_shell_command(r"D:\bin\git commit -m note")
32
-
33
-
34
- def test_quoted_windows_git_path_stays_detected_as_commit() -> None:
35
- assert _blocker_module.is_git_commit_shell_command(
36
- r'& "C:\Program Files\Git\cmd\git.exe" commit -m x'
37
- )
38
-
39
-
40
- def test_unquoted_windows_git_status_is_not_a_commit() -> None:
41
- assert not _blocker_module.is_git_commit_shell_command(r"C:\tools\git.exe status")
@@ -1,221 +0,0 @@
1
- """Behavior tests for the ephemeral-path skip on the Write/Edit PII surface.
2
-
3
- Sub-defect (ii): the Write/Edit surface scanned files under an ephemeral scratch
4
- root that sit outside every git repository. Such a draft only becomes durable
5
- through ``gh --body-file`` or a staged commit, and each of those surfaces keeps
6
- its own PII scan, so scanning the draft at write time is a pure false positive.
7
-
8
- The write surface consults the shared ``is_ephemeral_path`` predicate directly.
9
- These tests drive that real predicate on genuine ephemeral shapes: a harness
10
- session scratchpad under the user temp directory, and a ``CLAUDE_JOB_DIR``
11
- scratch tree. A write inside a git repository keeps full scanning, and the
12
- durable-post and staged-commit surfaces stay gated even from ephemeral sources.
13
- """
14
-
15
- from __future__ import annotations
16
-
17
- import shutil
18
- import subprocess
19
- import sys
20
- import uuid
21
- from collections.abc import Iterator
22
- from pathlib import Path
23
-
24
- import pytest
25
-
26
- _HOOK_DIR = Path(__file__).parent
27
- _HOOKS_DIR = _HOOK_DIR.parent
28
- if str(_HOOK_DIR) not in sys.path:
29
- sys.path.insert(0, str(_HOOK_DIR))
30
- if str(_HOOKS_DIR) not in sys.path:
31
- sys.path.insert(0, str(_HOOKS_DIR))
32
-
33
- import tempfile # noqa: E402
34
-
35
- from pii_payload_scan import evaluate_write_edit_payload # noqa: E402
36
- from pii_prevention_blocker import evaluate, evaluate_bash_command # noqa: E402
37
-
38
- from hooks_constants.code_rules_enforcer_constants import ( # noqa: E402
39
- CLAUDE_JOB_DIR_ENVIRONMENT_VARIABLE_NAME,
40
- CLAUDE_JOB_DIR_SCRATCH_SUBDIRECTORY,
41
- EPHEMERAL_EXEMPT_DISABLE_ENVIRONMENT_VARIABLE_NAME,
42
- )
43
- from hooks_constants.harness_scratchpad_constants import ( # noqa: E402
44
- HARNESS_SCRATCHPAD_LEAF_DIRECTORY_NAME,
45
- HARNESS_SCRATCHPAD_USER_DIRECTORY_NAME,
46
- HOOK_PAYLOAD_SESSION_ID_KEY,
47
- )
48
-
49
-
50
- def _real_pii_email() -> str:
51
- return "owner.fixture" + "@" + "acme-corp" + ".example" + ".io"
52
-
53
-
54
- def _bot_trailer_address() -> str:
55
- return "noreply" + "@" + "anthropic" + ".com"
56
-
57
-
58
- def _write_deny_reason(
59
- target_path: Path,
60
- sensitive_text: str,
61
- hook_payload: dict[str, object] | None = None,
62
- ) -> str | None:
63
- content = "owner contact " + sensitive_text + "\n"
64
- return evaluate_write_edit_payload(
65
- "Write",
66
- {"file_path": str(target_path), "content": content},
67
- hook_payload=hook_payload,
68
- )
69
-
70
-
71
- def _enable_job_dir_ephemeral_root(
72
- monkeypatch: pytest.MonkeyPatch, job_directory: Path
73
- ) -> Path:
74
- monkeypatch.delenv(EPHEMERAL_EXEMPT_DISABLE_ENVIRONMENT_VARIABLE_NAME, raising=False)
75
- monkeypatch.setenv(CLAUDE_JOB_DIR_ENVIRONMENT_VARIABLE_NAME, str(job_directory))
76
- ephemeral_root = job_directory / CLAUDE_JOB_DIR_SCRATCH_SUBDIRECTORY
77
- ephemeral_root.mkdir(parents=True, exist_ok=True)
78
- return ephemeral_root
79
-
80
-
81
- @pytest.fixture
82
- def harness_scratchpad() -> Iterator[tuple[Path, str]]:
83
- session_id = "session-" + uuid.uuid4().hex
84
- mangled_working_directory = "cwd-" + uuid.uuid4().hex
85
- temp_root = Path(tempfile.gettempdir())
86
- user_root = temp_root / HARNESS_SCRATCHPAD_USER_DIRECTORY_NAME
87
- scratchpad_root = (
88
- user_root
89
- / mangled_working_directory
90
- / session_id
91
- / HARNESS_SCRATCHPAD_LEAF_DIRECTORY_NAME
92
- )
93
- scratchpad_root.mkdir(parents=True, exist_ok=True)
94
- try:
95
- yield scratchpad_root, session_id
96
- finally:
97
- try:
98
- shutil.rmtree(user_root / mangled_working_directory)
99
- except OSError:
100
- pass
101
-
102
-
103
- def _init_repo_with_github_origin(repository_root: Path, origin_slug: str) -> None:
104
- repository_root.mkdir(parents=True, exist_ok=True)
105
- subprocess.run(["git", "init", "-q"], cwd=repository_root, check=True)
106
- origin_url = "https://github.com/" + origin_slug + ".git"
107
- subprocess.run(
108
- ["git", "remote", "add", "origin", origin_url],
109
- cwd=repository_root,
110
- check=True,
111
- )
112
-
113
-
114
- def _init_repo_with_staged_email(repository_root: Path, staged_email: str) -> None:
115
- repository_root.mkdir(parents=True, exist_ok=True)
116
- subprocess.run(["git", "init", "-q"], cwd=repository_root, check=True)
117
- subprocess.run(
118
- ["git", "config", "user.email", "dev@example.com"],
119
- cwd=repository_root,
120
- check=True,
121
- )
122
- subprocess.run(
123
- ["git", "config", "user.name", "Fixture Dev"],
124
- cwd=repository_root,
125
- check=True,
126
- )
127
- tracked_file = repository_root / "notes.md"
128
- tracked_file.write_text("owner email " + staged_email + "\n", encoding="utf-8")
129
- subprocess.run(["git", "add", "notes.md"], cwd=repository_root, check=True)
130
-
131
-
132
- def test_windows_shaped_scratchpad_write_is_skipped(
133
- harness_scratchpad: tuple[Path, str],
134
- ) -> None:
135
- scratchpad_root, session_id = harness_scratchpad
136
- scratch_target = scratchpad_root / "blocker-matrix.txt"
137
- payload = {HOOK_PAYLOAD_SESSION_ID_KEY: session_id}
138
- assert _write_deny_reason(scratch_target, _bot_trailer_address(), payload) is None
139
-
140
-
141
- def test_write_with_no_scannable_texts_returns_none(tmp_path: Path) -> None:
142
- target_path = tmp_path / "empty-write.md"
143
- assert (
144
- evaluate_write_edit_payload(
145
- "Write",
146
- {"file_path": str(target_path), "content": ""},
147
- )
148
- is None
149
- )
150
-
151
-
152
- def test_entry_point_threads_payload_session_id_to_scratchpad_skip(
153
- monkeypatch: pytest.MonkeyPatch,
154
- harness_scratchpad: tuple[Path, str],
155
- ) -> None:
156
- scratchpad_root, session_id = harness_scratchpad
157
- monkeypatch.delenv("CLAUDE_CODE_SESSION_ID", raising=False)
158
- scratch_target = scratchpad_root / "blocker-matrix.txt"
159
- content = "owner contact " + _real_pii_email() + "\n"
160
- payload = {
161
- "tool_name": "Write",
162
- "tool_input": {"file_path": str(scratch_target), "content": content},
163
- HOOK_PAYLOAD_SESSION_ID_KEY: session_id,
164
- }
165
- assert evaluate(payload) is None
166
-
167
-
168
- def test_job_dir_ephemeral_write_outside_repo_is_skipped(
169
- monkeypatch: pytest.MonkeyPatch, tmp_path: Path
170
- ) -> None:
171
- ephemeral_root = _enable_job_dir_ephemeral_root(monkeypatch, tmp_path)
172
- scratch_target = ephemeral_root / "drafts" / "draft.md"
173
- scratch_target.parent.mkdir(parents=True)
174
- assert _write_deny_reason(scratch_target, _real_pii_email()) is None
175
-
176
-
177
- def test_write_inside_ordinary_repository_is_still_denied(
178
- tmp_path: Path,
179
- ) -> None:
180
- repository_root = tmp_path / "repo"
181
- _init_repo_with_github_origin(repository_root, "SomeOwner/some-repo")
182
- deny_reason = _write_deny_reason(repository_root / "notes.md", _real_pii_email())
183
- assert deny_reason is not None
184
- assert "email" in deny_reason
185
-
186
-
187
- def test_ephemeral_path_inside_a_repository_is_still_denied(
188
- monkeypatch: pytest.MonkeyPatch, tmp_path: Path
189
- ) -> None:
190
- ephemeral_root = _enable_job_dir_ephemeral_root(monkeypatch, tmp_path)
191
- repository_root = ephemeral_root / "repo"
192
- _init_repo_with_github_origin(repository_root, "SomeOwner/some-repo")
193
- deny_reason = _write_deny_reason(repository_root / "notes.md", _real_pii_email())
194
- assert deny_reason is not None
195
- assert "email" in deny_reason
196
-
197
-
198
- def test_gh_body_file_from_ephemeral_path_is_still_scanned(
199
- monkeypatch: pytest.MonkeyPatch, tmp_path: Path
200
- ) -> None:
201
- ephemeral_root = _enable_job_dir_ephemeral_root(monkeypatch, tmp_path)
202
- body_path = ephemeral_root / "pr_body.md"
203
- body_path.write_text("contact " + _real_pii_email() + "\n", encoding="utf-8")
204
- command = 'gh pr comment 12 --body-file "' + str(body_path) + '"'
205
- deny_reason = evaluate_bash_command(command, working_directory=None)
206
- assert deny_reason is not None
207
- assert "email" in deny_reason
208
-
209
-
210
- def test_staged_commit_from_ephemeral_tree_is_still_scanned(
211
- monkeypatch: pytest.MonkeyPatch, tmp_path: Path
212
- ) -> None:
213
- ephemeral_root = _enable_job_dir_ephemeral_root(monkeypatch, tmp_path)
214
- repository_root = ephemeral_root / "repo"
215
- _init_repo_with_staged_email(repository_root, _real_pii_email())
216
- deny_reason = evaluate_bash_command(
217
- "git commit -m test", working_directory=str(repository_root)
218
- )
219
- assert deny_reason is not None
220
- assert "email" in deny_reason
221
- assert "staged commit" in deny_reason