claude-dev-env 2.3.0 → 2.4.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 (136) hide show
  1. package/CLAUDE.md +3 -2
  2. package/agents/CLAUDE.md +1 -0
  3. package/agents/code-verifier.md +3 -3
  4. package/agents/skill-writer-agent.md +84 -0
  5. package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +141 -41
  6. package/audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md +29 -13
  7. package/docs/CLAUDE.md +1 -0
  8. package/docs/references/CLAUDE.md +1 -0
  9. package/docs/references/code-review-enforcement.md +97 -0
  10. package/docs/wsl-docker-cowork-starter-matrix.md +89 -0
  11. package/hooks/blocking/CLAUDE.md +8 -1
  12. package/hooks/blocking/code_review_enforcement_config_bootstrap.py +53 -0
  13. package/hooks/blocking/code_review_gate_deny.py +74 -0
  14. package/hooks/blocking/code_review_pr_create_gate.py +194 -0
  15. package/hooks/blocking/code_review_push_gate.py +140 -0
  16. package/hooks/blocking/code_review_stamp_directory_write_blocker.py +340 -0
  17. package/hooks/blocking/code_review_stamp_store.py +233 -0
  18. package/hooks/blocking/code_review_stamp_write_blocker_parts/__init__.py +7 -0
  19. package/hooks/blocking/code_review_stamp_write_blocker_parts/conftest.py +15 -0
  20. package/hooks/blocking/code_review_stamp_write_blocker_parts/obfuscated_stamp_path_reference.py +212 -0
  21. package/hooks/blocking/code_review_stamp_write_blocker_parts/split_directory_change_into_stamp.py +138 -0
  22. package/hooks/blocking/code_review_stamp_write_blocker_parts/test_obfuscated_stamp_path_reference.py +49 -0
  23. package/hooks/blocking/code_review_stamp_write_blocker_parts/test_split_directory_change_into_stamp.py +38 -0
  24. package/hooks/blocking/code_verifier_spawn_preflight_gate.py +39 -27
  25. package/hooks/blocking/config/code_review_enforcement_constants.py +110 -0
  26. package/hooks/blocking/config/test_code_review_enforcement_constants.py +108 -0
  27. package/hooks/blocking/config/verified_commit_constants.py +13 -9
  28. package/hooks/blocking/conftest.py +2 -0
  29. package/hooks/blocking/convergence_gate_blocker.py +112 -23
  30. package/hooks/blocking/destructive_command_blocker.py +19 -6
  31. package/hooks/blocking/pr_description_proof_of_work.py +52 -34
  32. package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +4 -1
  33. package/hooks/blocking/test_code_review_enforcement_config_bootstrap.py +62 -0
  34. package/hooks/blocking/test_code_review_gate_deny.py +54 -0
  35. package/hooks/blocking/test_code_review_pr_create_gate.py +185 -0
  36. package/hooks/blocking/test_code_review_push_gate.py +189 -0
  37. package/hooks/blocking/test_code_review_stamp_directory_write_blocker.py +180 -0
  38. package/hooks/blocking/test_code_review_stamp_store.py +205 -0
  39. package/hooks/blocking/test_code_verifier_spawn_preflight_gate.py +124 -2
  40. package/hooks/blocking/test_convergence_gate_blocker.py +153 -5
  41. package/hooks/blocking/test_destructive_command_blocker.py +1 -1
  42. package/hooks/blocking/test_destructive_command_blocker_deny_mode.py +45 -0
  43. package/hooks/blocking/test_pr_description_proof_of_work.py +151 -0
  44. package/hooks/blocking/test_pre_tool_use_dispatcher.py +8 -8
  45. package/hooks/blocking/test_verification_verdict_store.py +920 -903
  46. package/hooks/blocking/test_volatile_path_in_post_blocker.py +114 -2
  47. package/hooks/blocking/verification_verdict_store.py +27 -5
  48. package/hooks/blocking/verified_commit_gate_parts/gated_invocations.py +29 -17
  49. package/hooks/blocking/verified_commit_gate_parts/tests/test_gated_invocations.py +35 -0
  50. package/hooks/blocking/volatile_path_in_post_blocker.py +69 -8
  51. package/hooks/git-hooks/git_hooks_constants/__init__.py +6 -0
  52. package/hooks/git-hooks/pre_push.py +89 -2
  53. package/hooks/git-hooks/test_pre_push.py +103 -0
  54. package/hooks/hooks.json +16 -1
  55. package/hooks/hooks_constants/CLAUDE.md +1 -0
  56. package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +8 -0
  57. package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -0
  58. package/hooks/hooks_constants/code_verifier_spawn_preflight_gate_constants.py +26 -11
  59. package/hooks/hooks_constants/convergence_gate_blocker_constants.py +20 -3
  60. package/hooks/hooks_constants/destructive_command_segment_constants.py +3 -1
  61. package/hooks/hooks_constants/pr_description_proof_of_work_constants.py +0 -4
  62. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +4 -0
  63. package/hooks/hooks_constants/pyproject_config_discovery_constants.py +16 -0
  64. package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +24 -0
  65. package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +6 -0
  66. package/hooks/hooks_constants/volatile_path_in_post_blocker_constants.py +8 -1
  67. package/hooks/validators/CLAUDE.md +1 -0
  68. package/hooks/validators/mypy_integration.py +63 -50
  69. package/hooks/validators/pyproject_config_discovery.py +101 -0
  70. package/hooks/validators/ruff_integration.py +211 -23
  71. package/hooks/validators/run_all_validators.py +21 -14
  72. package/hooks/validators/test_mypy_integration.py +32 -0
  73. package/hooks/validators/test_pyproject_config_discovery.py +94 -0
  74. package/hooks/validators/test_ruff_integration.py +68 -1
  75. package/hooks/validators/test_run_all_validators.py +25 -0
  76. package/hooks/validators/test_run_all_validators_config_discovery.py +123 -0
  77. package/package.json +1 -1
  78. package/rules/docstring-prose-matches-implementation.md +20 -43
  79. package/rules/durable-post-artifacts.md +7 -0
  80. package/scripts/codec_forwarding_test_support.py +83 -0
  81. package/scripts/conftest.py +8 -0
  82. package/scripts/dev_env_scripts_constants/CLAUDE.md +1 -1
  83. package/scripts/dev_env_scripts_constants/claude_chain_constants.py +44 -1
  84. package/scripts/dev_env_scripts_constants/code_review_constants.py +129 -12
  85. package/scripts/dev_env_scripts_constants/test_code_review_constants.py +55 -0
  86. package/scripts/invoke_code_review.py +550 -38
  87. package/scripts/resolve_worker_spawn.py +8 -1
  88. package/scripts/test_invoke_code_review.py +298 -4
  89. package/scripts/test_invoke_code_review_codec.py +77 -0
  90. package/scripts/test_resolve_worker_spawn_codec.py +101 -0
  91. package/skills/autoconverge/SKILL.md +9 -3
  92. package/skills/autoconverge/reference/convergence.md +2 -1
  93. package/skills/autoconverge/reference/multi-pr.md +6 -1
  94. package/skills/autoconverge/reference/stop-conditions.md +16 -10
  95. package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +1 -1
  96. package/skills/autoconverge/workflow/converge.codex-gate.test.mjs +175 -3
  97. package/skills/autoconverge/workflow/converge.contract.test.mjs +19 -0
  98. package/skills/autoconverge/workflow/converge.mjs +24 -8
  99. package/skills/autoconverge/workflow/converge_multi.mjs +7 -3
  100. package/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +5 -0
  101. package/skills/fresh-branch/CLAUDE.md +2 -0
  102. package/skills/fresh-branch/SKILL.md +2 -0
  103. package/skills/fresh-branch/scripts/create_fresh_branch.py +78 -180
  104. package/skills/fresh-branch/scripts/fresh_branch_git_commands.py +285 -0
  105. package/skills/fresh-branch/scripts/fresh_branch_scripts_constants/fresh_branch_cli_constants.py +1 -0
  106. package/skills/fresh-branch/scripts/pytest.ini +4 -0
  107. package/skills/fresh-branch/scripts/test_create_fresh_branch.py +98 -0
  108. package/skills/fresh-branch/scripts/test_fresh_branch_git_commands.py +310 -0
  109. package/skills/prototype/SKILL.md +86 -0
  110. package/skills/prototype/reference/honest-limitations.md +23 -0
  111. package/skills/prototype/reference/promotion-tasks.md +23 -0
  112. package/skills/prototype/scripts/build_sandbox_settings.py +249 -0
  113. package/skills/prototype/scripts/conftest.py +15 -0
  114. package/skills/prototype/scripts/launch_sandbox.py +205 -0
  115. package/skills/prototype/scripts/probe_sandbox_safety.py +311 -0
  116. package/skills/prototype/scripts/prototype_scripts_constants/__init__.py +1 -0
  117. package/skills/prototype/scripts/prototype_scripts_constants/config/__init__.py +0 -0
  118. package/skills/prototype/scripts/prototype_scripts_constants/config/build_sandbox_settings_constants.py +41 -0
  119. package/skills/prototype/scripts/prototype_scripts_constants/config/launch_sandbox_constants.py +23 -0
  120. package/skills/prototype/scripts/prototype_scripts_constants/config/probe_sandbox_safety_constants.py +45 -0
  121. package/skills/prototype/scripts/prototype_scripts_constants/config/prototype_common_constants.py +10 -0
  122. package/skills/prototype/scripts/test_build_sandbox_settings.py +275 -0
  123. package/skills/prototype/scripts/test_launch_sandbox.py +303 -0
  124. package/skills/prototype/scripts/test_probe_sandbox_safety.py +284 -0
  125. package/skills/prototype/workflows/promotion.md +27 -0
  126. package/skills/prototype/workflows/sandbox.md +35 -0
  127. package/skills/skill-builder/CLAUDE.md +3 -3
  128. package/skills/skill-builder/SKILL.md +5 -5
  129. package/skills/skill-builder/references/CLAUDE.md +1 -1
  130. package/skills/skill-builder/references/delegation-map.md +3 -3
  131. package/skills/skill-builder/references/description-field.md +1 -1
  132. package/skills/skill-builder/references/skill-modularity.md +2 -3
  133. package/skills/skill-builder/workflows/CLAUDE.md +1 -1
  134. package/skills/skill-builder/workflows/improve-skill.md +1 -1
  135. package/skills/skill-builder/workflows/new-skill.md +2 -2
  136. package/skills/team-advisor/SKILL.md +2 -2
@@ -0,0 +1,311 @@
1
+ #!/usr/bin/env python3
2
+ """Prove both sandbox safety hooks block their probe payloads before trusting them.
3
+
4
+ ::
5
+
6
+ python probe_sandbox_safety.py --settings sandbox-settings.json
7
+ exit 0 -> pii_prevention_blocker and destructive_command_blocker both blocked
8
+ exit 3 -> one hook allowed its probe payload or errored
9
+
10
+ The probe reads each safety hook command from the settings, feeds it a
11
+ PreToolUse payload the hook's own tests prove it blocks, and confirms the
12
+ hook returns a hard-deny decision. It runs each hook under the settings'
13
+ ``env`` block, so the destructive gate runs in deny mode and its block holds
14
+ the way it does for the launched session. It runs the hook scripts directly,
15
+ so it confirms each script is present, imports its constants package, and
16
+ blocks its probe payload — it does not exercise Claude Code's matcher dispatch.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import argparse
22
+ import json
23
+ import logging
24
+ import os
25
+ import shlex
26
+ import subprocess
27
+ import sys
28
+ from pathlib import Path
29
+
30
+ from build_sandbox_settings import read_settings_document
31
+ from prototype_scripts_constants.config.build_sandbox_settings_constants import (
32
+ ALL_SAFETY_HOOK_SCRIPT_BASENAMES,
33
+ COMMAND_KEY,
34
+ ENV_KEY,
35
+ HOOKS_KEY,
36
+ MATCHER_KEY,
37
+ PRE_TOOL_USE_KEY,
38
+ )
39
+ from prototype_scripts_constants.config.probe_sandbox_safety_constants import (
40
+ ALL_DESTRUCTIVE_PROBE_COMMAND_TOKENS,
41
+ COMMAND_TOKEN_JOIN_SEPARATOR,
42
+ DESTRUCTIVE_PROBE_TOOL_NAME,
43
+ ENVELOPE_HOOK_EVENT_NAME_KEY,
44
+ ENVELOPE_PRE_TOOL_USE_EVENT_NAME,
45
+ ENVELOPE_PROBE_SESSION_ID,
46
+ ENVELOPE_SESSION_ID_KEY,
47
+ ENVELOPE_TOOL_INPUT_KEY,
48
+ ENVELOPE_TOOL_NAME_KEY,
49
+ HARD_DENY_DECISION,
50
+ HOOK_SPECIFIC_REPLY_KEY,
51
+ MATCHER_JOIN_SEPARATOR,
52
+ PERMISSION_DECISION_KEY,
53
+ PII_PROBE_CONTENT_TEMPLATE,
54
+ PII_PROBE_FILE_PATH,
55
+ PII_PROBE_TOKEN_BODY_CHARACTER,
56
+ PII_PROBE_TOKEN_BODY_LENGTH,
57
+ PII_PROBE_TOKEN_PREFIX,
58
+ PII_PROBE_TOOL_NAME,
59
+ PROBE_FAILURE_EXIT_CODE,
60
+ PROBE_HOOK_TIMEOUT_SECONDS,
61
+ PROBE_SUCCESS_EXIT_CODE,
62
+ TOOL_INPUT_COMMAND_KEY,
63
+ TOOL_INPUT_CONTENT_KEY,
64
+ TOOL_INPUT_FILE_PATH_KEY,
65
+ )
66
+ from prototype_scripts_constants.config.prototype_common_constants import LOGGING_FORMAT
67
+
68
+ logger = logging.getLogger("probe_sandbox_safety")
69
+
70
+
71
+ def _build_pii_probe_payload() -> dict:
72
+ synthetic_secret = (
73
+ PII_PROBE_TOKEN_PREFIX
74
+ + PII_PROBE_TOKEN_BODY_CHARACTER * PII_PROBE_TOKEN_BODY_LENGTH
75
+ )
76
+ probe_content = PII_PROBE_CONTENT_TEMPLATE.format(secret=synthetic_secret)
77
+ return {
78
+ ENVELOPE_SESSION_ID_KEY: ENVELOPE_PROBE_SESSION_ID,
79
+ ENVELOPE_HOOK_EVENT_NAME_KEY: ENVELOPE_PRE_TOOL_USE_EVENT_NAME,
80
+ ENVELOPE_TOOL_NAME_KEY: PII_PROBE_TOOL_NAME,
81
+ ENVELOPE_TOOL_INPUT_KEY: {
82
+ TOOL_INPUT_FILE_PATH_KEY: PII_PROBE_FILE_PATH,
83
+ TOOL_INPUT_CONTENT_KEY: probe_content,
84
+ },
85
+ }
86
+
87
+
88
+ def _build_destructive_probe_payload() -> dict:
89
+ destructive_command = COMMAND_TOKEN_JOIN_SEPARATOR.join(
90
+ ALL_DESTRUCTIVE_PROBE_COMMAND_TOKENS
91
+ )
92
+ return {
93
+ ENVELOPE_SESSION_ID_KEY: ENVELOPE_PROBE_SESSION_ID,
94
+ ENVELOPE_HOOK_EVENT_NAME_KEY: ENVELOPE_PRE_TOOL_USE_EVENT_NAME,
95
+ ENVELOPE_TOOL_NAME_KEY: DESTRUCTIVE_PROBE_TOOL_NAME,
96
+ ENVELOPE_TOOL_INPUT_KEY: {TOOL_INPUT_COMMAND_KEY: destructive_command},
97
+ }
98
+
99
+
100
+ def build_probe_payload_for_basename(basename: str) -> dict:
101
+ """Build the PreToolUse probe payload a safety hook is proven to block.
102
+
103
+ ::
104
+
105
+ "pii_prevention_blocker.py" -> Write payload carrying a secret
106
+ "destructive_command_blocker.py" -> Bash payload carrying rm -rf
107
+
108
+ Args:
109
+ basename: the safety hook script basename to build a payload for.
110
+
111
+ Returns:
112
+ The PreToolUse payload the named hook blocks.
113
+
114
+ Raises:
115
+ ValueError: when the basename names no known safety hook.
116
+ """
117
+ if basename == ALL_SAFETY_HOOK_SCRIPT_BASENAMES[0]:
118
+ return _build_pii_probe_payload()
119
+ if basename == ALL_SAFETY_HOOK_SCRIPT_BASENAMES[1]:
120
+ return _build_destructive_probe_payload()
121
+ raise ValueError(f"no probe payload for hook basename: {basename}")
122
+
123
+
124
+ def _command_in_block_for_basename(hook_block: dict, basename: str) -> str | None:
125
+ for each_subhook in hook_block.get(HOOKS_KEY, []):
126
+ command = each_subhook.get(COMMAND_KEY, "")
127
+ if basename in command:
128
+ return command
129
+ return None
130
+
131
+
132
+ def find_hook_command_for_basename(
133
+ settings_document: dict, basename: str
134
+ ) -> str | None:
135
+ """Find the first hook command in the settings that runs the named script.
136
+
137
+ Args:
138
+ settings_document: the minimal sandbox settings document.
139
+ basename: the safety hook script basename to look for.
140
+
141
+ Returns:
142
+ The command string that runs the named script, or None when no
143
+ hook command names it.
144
+ """
145
+ pre_tool_use_blocks = settings_document.get(HOOKS_KEY, {}).get(PRE_TOOL_USE_KEY, [])
146
+ for each_block in pre_tool_use_blocks:
147
+ command = _command_in_block_for_basename(each_block, basename)
148
+ if command is not None:
149
+ return command
150
+ return None
151
+
152
+
153
+ def settings_environment_overrides(settings_document: dict) -> dict[str, str]:
154
+ """Read the env-block overrides the settings apply to each hook subprocess.
155
+
156
+ ::
157
+
158
+ {"env": {deny-mode override}} -> that mapping
159
+ settings with no env block -> {}
160
+
161
+ Args:
162
+ settings_document: the minimal sandbox settings document.
163
+
164
+ Returns:
165
+ The env-block mapping keyed by variable name, or an empty mapping when
166
+ the settings carry no env block.
167
+ """
168
+ return settings_document.get(ENV_KEY, {})
169
+
170
+
171
+ def _find_matchers_for_basename(settings_document: dict, basename: str) -> list[str]:
172
+ pre_tool_use_blocks = settings_document.get(HOOKS_KEY, {}).get(PRE_TOOL_USE_KEY, [])
173
+ all_matchers = []
174
+ for each_block in pre_tool_use_blocks:
175
+ names_basename = any(
176
+ basename in each_subhook.get(COMMAND_KEY, "")
177
+ for each_subhook in each_block.get(HOOKS_KEY, [])
178
+ )
179
+ if names_basename:
180
+ all_matchers.append(str(each_block.get(MATCHER_KEY)))
181
+ return all_matchers
182
+
183
+
184
+ def parse_command_argv(command: str) -> list[str]:
185
+ """Split a hook command string into an argument vector, quotes removed.
186
+
187
+ ::
188
+
189
+ '"py" "a b.py"' -> ["py", "a b.py"]
190
+
191
+ Args:
192
+ command: the hook command string from the settings.
193
+
194
+ Returns:
195
+ The argument vector with any surrounding quotes stripped.
196
+ """
197
+ all_tokens = shlex.split(command, posix=False)
198
+ return [each_token.strip('"').strip("'") for each_token in all_tokens]
199
+
200
+
201
+ def _run_hook_and_parse_reply(
202
+ all_command_tokens: list[str],
203
+ probe_payload: dict,
204
+ override_by_name: dict[str, str],
205
+ ) -> dict | None:
206
+ child_environment = {**os.environ, **override_by_name}
207
+ try:
208
+ completed_process = subprocess.run(
209
+ all_command_tokens,
210
+ input=json.dumps(probe_payload),
211
+ text=True,
212
+ capture_output=True,
213
+ timeout=PROBE_HOOK_TIMEOUT_SECONDS,
214
+ check=False,
215
+ env=child_environment,
216
+ )
217
+ except (OSError, subprocess.SubprocessError):
218
+ return None
219
+ if not completed_process.stdout.strip():
220
+ return None
221
+ try:
222
+ return json.loads(completed_process.stdout)
223
+ except json.JSONDecodeError:
224
+ return None
225
+
226
+
227
+ def hook_blocks_probe(
228
+ all_command_tokens: list[str],
229
+ probe_payload: dict,
230
+ override_by_name: dict[str, str],
231
+ ) -> bool:
232
+ """Run a hook against its probe payload and report whether it blocks.
233
+
234
+ Args:
235
+ all_command_tokens: the hook argument vector to run.
236
+ probe_payload: the PreToolUse payload fed to the hook on stdin.
237
+ override_by_name: env values keyed by variable name that the settings
238
+ apply to the hook, overlaid on the current environment first.
239
+
240
+ Returns:
241
+ True when the hook emits a hard-deny decision, False when it asks,
242
+ allows, emits no decision, or errors.
243
+ """
244
+ parsed_hook_reply = _run_hook_and_parse_reply(
245
+ all_command_tokens, probe_payload, override_by_name
246
+ )
247
+ if parsed_hook_reply is None:
248
+ return False
249
+ decision = parsed_hook_reply.get(HOOK_SPECIFIC_REPLY_KEY, {}).get(
250
+ PERMISSION_DECISION_KEY
251
+ )
252
+ return decision == HARD_DENY_DECISION
253
+
254
+
255
+ def probe_safety_hook(settings_document: dict, basename: str) -> bool:
256
+ """Probe one safety hook from the settings and report whether it blocks.
257
+
258
+ Args:
259
+ settings_document: the minimal sandbox settings document.
260
+ basename: the safety hook script basename to probe.
261
+
262
+ Returns:
263
+ True when the hook blocks its probe payload, False otherwise.
264
+ """
265
+ command = find_hook_command_for_basename(settings_document, basename)
266
+ if command is None:
267
+ logger.error("no hook command in settings runs %s", basename)
268
+ return False
269
+ all_matchers = _find_matchers_for_basename(settings_document, basename)
270
+ logger.info("%s matchers: %s", basename, MATCHER_JOIN_SEPARATOR.join(all_matchers))
271
+ all_command_tokens = parse_command_argv(command)
272
+ probe_payload = build_probe_payload_for_basename(basename)
273
+ override_by_name = settings_environment_overrides(settings_document)
274
+ return hook_blocks_probe(all_command_tokens, probe_payload, override_by_name)
275
+
276
+
277
+ def _parse_arguments(all_arguments: list[str] | None) -> argparse.Namespace:
278
+ parser = argparse.ArgumentParser(description=__doc__)
279
+ parser.add_argument("--settings", required=True, help="the sandbox settings file")
280
+ return parser.parse_args(all_arguments)
281
+
282
+
283
+ def main(all_arguments: list[str] | None = None) -> int:
284
+ """Probe both safety hooks and report whether the sandbox is contained.
285
+
286
+ Args:
287
+ all_arguments: the command-line arguments, or None to read sys.argv.
288
+
289
+ Returns:
290
+ 0 when both safety hooks block their probe payloads, 3 when either
291
+ allows, errors, or the settings cannot be read.
292
+ """
293
+ logging.basicConfig(level=logging.INFO, format=LOGGING_FORMAT)
294
+ arguments = _parse_arguments(all_arguments)
295
+ settings_path = Path(arguments.settings).expanduser()
296
+ try:
297
+ settings_document = read_settings_document(settings_path)
298
+ except (OSError, json.JSONDecodeError) as read_error:
299
+ logger.error("cannot read settings %s: %s", settings_path, read_error)
300
+ return PROBE_FAILURE_EXIT_CODE
301
+ all_hook_block_outcomes = [
302
+ probe_safety_hook(settings_document, each_basename)
303
+ for each_basename in ALL_SAFETY_HOOK_SCRIPT_BASENAMES
304
+ ]
305
+ if all(all_hook_block_outcomes):
306
+ return PROBE_SUCCESS_EXIT_CODE
307
+ return PROBE_FAILURE_EXIT_CODE
308
+
309
+
310
+ if __name__ == "__main__":
311
+ raise SystemExit(main(sys.argv[1:]))
@@ -0,0 +1 @@
1
+ """Named constants for the prototype skill scripts."""
@@ -0,0 +1,41 @@
1
+ """Constants for the sandbox safety-settings builder.
2
+
3
+ Groups: the two safety hook script basenames the sandbox keeps, the matchers
4
+ each safety hook must cover, the mapping from basename to those matchers, the
5
+ settings JSON key names, the env-block key with the deny-mode env variable and
6
+ value, the default live settings source, the pretty-print indent, the join
7
+ separator for the missing-basename message, and the exit codes.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ ALL_SAFETY_HOOK_SCRIPT_BASENAMES = (
13
+ "pii_prevention_blocker.py",
14
+ "destructive_command_blocker.py",
15
+ )
16
+
17
+ ALL_PII_REQUIRED_MATCHERS = ("Write", "Edit", "MultiEdit", "Bash")
18
+ ALL_DESTRUCTIVE_REQUIRED_MATCHERS = ("Bash",)
19
+
20
+ ALL_REQUIRED_MATCHERS_BY_SAFETY_BASENAME = {
21
+ ALL_SAFETY_HOOK_SCRIPT_BASENAMES[0]: ALL_PII_REQUIRED_MATCHERS,
22
+ ALL_SAFETY_HOOK_SCRIPT_BASENAMES[1]: ALL_DESTRUCTIVE_REQUIRED_MATCHERS,
23
+ }
24
+
25
+ HOOKS_KEY = "hooks"
26
+ PRE_TOOL_USE_KEY = "PreToolUse"
27
+ MATCHER_KEY = "matcher"
28
+ COMMAND_KEY = "command"
29
+ ENV_KEY = "env"
30
+
31
+ DESTRUCTIVE_DENY_MODE_ENV_VAR = "CLAUDE_DESTRUCTIVE_DENY_MODE"
32
+ DESTRUCTIVE_DENY_MODE_ENV_VALUE = "1"
33
+
34
+ DEFAULT_SETTINGS_SOURCE = "~/.claude/settings.json"
35
+ JSON_INDENT_SPACES = 2
36
+
37
+ MISSING_BASENAMES_JOIN_SEPARATOR = ", "
38
+
39
+ BUILD_SUCCESS_EXIT_CODE = 0
40
+ SETTINGS_MISSING_SAFETY_HOOK_EXIT_CODE = 2
41
+ SETTINGS_SOURCE_UNREADABLE_EXIT_CODE = 2
@@ -0,0 +1,23 @@
1
+ """Constants for the hookless sandbox launcher.
2
+
3
+ Groups: the headless claude executable name and its flag tokens, the default
4
+ session timeout, the JSON summary key names, the missing-path exit code, and
5
+ the wall-clock timeout exit code.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ CLAUDE_EXECUTABLE_NAME = "claude"
11
+ PROMPT_FLAG = "-p"
12
+ BARE_FLAG = "--bare"
13
+ SKIP_PERMISSIONS_FLAG = "--dangerously-skip-permissions"
14
+ SETTINGS_FLAG = "--settings"
15
+
16
+ DEFAULT_TIMEOUT_SECONDS = 3600
17
+
18
+ SUMMARY_KEY_WORKTREE = "worktree"
19
+ SUMMARY_KEY_SETTINGS = "settings"
20
+ SUMMARY_KEY_EXIT_CODE = "exit_code"
21
+
22
+ LAUNCH_MISSING_PATH_EXIT_CODE = 2
23
+ LAUNCH_TIMEOUT_EXIT_CODE = 124
@@ -0,0 +1,45 @@
1
+ """Constants for the sandbox safety-hook probe.
2
+
3
+ Groups: the PreToolUse envelope keys and their probe payloads, the tool-input
4
+ keys, the hook decision keys and the block decisions, the join separators, the
5
+ destructive and personal-data probe payloads, the probe hook timeout, and the
6
+ exit codes.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ ENVELOPE_SESSION_ID_KEY = "session_id"
12
+ ENVELOPE_PROBE_SESSION_ID = "probe"
13
+ ENVELOPE_HOOK_EVENT_NAME_KEY = "hook_event_name"
14
+ ENVELOPE_PRE_TOOL_USE_EVENT_NAME = "PreToolUse"
15
+ ENVELOPE_TOOL_NAME_KEY = "tool_name"
16
+ ENVELOPE_TOOL_INPUT_KEY = "tool_input"
17
+
18
+ TOOL_INPUT_COMMAND_KEY = "command"
19
+ TOOL_INPUT_FILE_PATH_KEY = "file_path"
20
+ TOOL_INPUT_CONTENT_KEY = "content"
21
+
22
+ HOOK_SPECIFIC_REPLY_KEY = "hookSpecificOutput"
23
+ PERMISSION_DECISION_KEY = "permissionDecision"
24
+ HARD_DENY_DECISION = "deny"
25
+
26
+ MATCHER_JOIN_SEPARATOR = ", "
27
+ COMMAND_TOKEN_JOIN_SEPARATOR = " "
28
+
29
+ DESTRUCTIVE_PROBE_TOOL_NAME = "Bash"
30
+ ALL_DESTRUCTIVE_PROBE_COMMAND_TOKENS = ("rm", "-rf", "/var/log/myapp")
31
+ DESTRUCTIVE_PROBE_COMMAND = COMMAND_TOKEN_JOIN_SEPARATOR.join(
32
+ ALL_DESTRUCTIVE_PROBE_COMMAND_TOKENS
33
+ )
34
+
35
+ PII_PROBE_TOOL_NAME = "Write"
36
+ PII_PROBE_FILE_PATH = "src/config.env.example.md"
37
+ PII_PROBE_TOKEN_PREFIX = "ghp" + "_"
38
+ PII_PROBE_TOKEN_BODY_CHARACTER = "C"
39
+ PII_PROBE_TOKEN_BODY_LENGTH = 36
40
+ PII_PROBE_CONTENT_TEMPLATE = "TOKEN={secret}\n"
41
+
42
+ PROBE_HOOK_TIMEOUT_SECONDS = 30
43
+
44
+ PROBE_SUCCESS_EXIT_CODE = 0
45
+ PROBE_FAILURE_EXIT_CODE = 3
@@ -0,0 +1,10 @@
1
+ """Constants shared by every prototype skill script.
2
+
3
+ Groups: the log line format the scripts configure, and the text encoding
4
+ for reading the settings source and the sandbox task file.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ LOGGING_FORMAT = "%(levelname)s %(name)s: %(message)s"
10
+ TEXT_ENCODING_UTF8 = "utf-8"