claude-dev-env 2.2.1 → 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 (170) hide show
  1. package/CLAUDE.md +3 -2
  2. package/_shared/advisor/advisor-protocol.md +2 -2
  3. package/_shared/pr-loop/scripts/CLAUDE.md +1 -0
  4. package/_shared/pr-loop/scripts/grant_project_claude_permissions.py +306 -284
  5. package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +1 -0
  6. package/_shared/pr-loop/scripts/pr_loop_shared_constants/claude_permissions_constants.py +3 -3
  7. package/_shared/pr-loop/scripts/pr_loop_shared_constants/stale_worktree_rule_sweep_constants.py +107 -0
  8. package/_shared/pr-loop/scripts/revoke_project_claude_permissions.py +5 -0
  9. package/_shared/pr-loop/scripts/stale_worktree_rule_sweep.py +107 -0
  10. package/_shared/pr-loop/scripts/tests/CLAUDE.md +2 -0
  11. package/_shared/pr-loop/scripts/tests/test_agent_config_carveout.py +55 -73
  12. package/_shared/pr-loop/scripts/tests/test_claude_permissions_constants.py +9 -15
  13. package/_shared/pr-loop/scripts/tests/test_grant_project_claude_permissions.py +91 -1
  14. package/_shared/pr-loop/scripts/tests/test_revoke_project_claude_permissions.py +88 -1
  15. package/_shared/pr-loop/scripts/tests/test_stale_worktree_rule_sweep.py +301 -0
  16. package/_shared/pr-loop/scripts/tests/test_stale_worktree_rule_sweep_constants.py +85 -0
  17. package/_shared/pr-loop/worker-spawn.md +1 -1
  18. package/agents/CLAUDE.md +1 -0
  19. package/agents/code-verifier.md +4 -4
  20. package/agents/skill-writer-agent.md +84 -0
  21. package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +141 -41
  22. package/audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md +29 -13
  23. package/docs/CLAUDE.md +1 -0
  24. package/docs/references/CLAUDE.md +1 -0
  25. package/docs/references/code-review-enforcement.md +97 -0
  26. package/docs/wsl-docker-cowork-starter-matrix.md +89 -0
  27. package/hooks/blocking/CLAUDE.md +8 -1
  28. package/hooks/blocking/code_review_enforcement_config_bootstrap.py +53 -0
  29. package/hooks/blocking/code_review_gate_deny.py +74 -0
  30. package/hooks/blocking/code_review_pr_create_gate.py +194 -0
  31. package/hooks/blocking/code_review_push_gate.py +140 -0
  32. package/hooks/blocking/code_review_stamp_directory_write_blocker.py +340 -0
  33. package/hooks/blocking/code_review_stamp_store.py +233 -0
  34. package/hooks/blocking/code_review_stamp_write_blocker_parts/__init__.py +7 -0
  35. package/hooks/blocking/code_review_stamp_write_blocker_parts/conftest.py +15 -0
  36. package/hooks/blocking/code_review_stamp_write_blocker_parts/obfuscated_stamp_path_reference.py +212 -0
  37. package/hooks/blocking/code_review_stamp_write_blocker_parts/split_directory_change_into_stamp.py +138 -0
  38. package/hooks/blocking/code_review_stamp_write_blocker_parts/test_obfuscated_stamp_path_reference.py +49 -0
  39. package/hooks/blocking/code_review_stamp_write_blocker_parts/test_split_directory_change_into_stamp.py +38 -0
  40. package/hooks/blocking/code_verifier_spawn_preflight_gate.py +39 -27
  41. package/hooks/blocking/config/code_review_enforcement_constants.py +110 -0
  42. package/hooks/blocking/config/test_code_review_enforcement_constants.py +108 -0
  43. package/hooks/blocking/config/verified_commit_constants.py +24 -9
  44. package/hooks/blocking/conftest.py +2 -0
  45. package/hooks/blocking/convergence_gate_blocker.py +112 -23
  46. package/hooks/blocking/destructive_command_blocker.py +19 -6
  47. package/hooks/blocking/pr_description_proof_of_work.py +52 -34
  48. package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +4 -1
  49. package/hooks/blocking/test_code_review_enforcement_config_bootstrap.py +62 -0
  50. package/hooks/blocking/test_code_review_gate_deny.py +54 -0
  51. package/hooks/blocking/test_code_review_pr_create_gate.py +185 -0
  52. package/hooks/blocking/test_code_review_push_gate.py +189 -0
  53. package/hooks/blocking/test_code_review_stamp_directory_write_blocker.py +180 -0
  54. package/hooks/blocking/test_code_review_stamp_store.py +205 -0
  55. package/hooks/blocking/test_code_verifier_spawn_preflight_gate.py +124 -2
  56. package/hooks/blocking/test_code_verifier_tools_contract.py +28 -0
  57. package/hooks/blocking/test_convergence_gate_blocker.py +153 -5
  58. package/hooks/blocking/test_destructive_command_blocker.py +1 -1
  59. package/hooks/blocking/test_destructive_command_blocker_deny_mode.py +45 -0
  60. package/hooks/blocking/test_pr_description_proof_of_work.py +151 -0
  61. package/hooks/blocking/test_pre_tool_use_dispatcher.py +8 -8
  62. package/hooks/blocking/test_verification_verdict_store.py +920 -810
  63. package/hooks/blocking/test_volatile_path_in_post_blocker.py +114 -2
  64. package/hooks/blocking/verification_verdict_store.py +118 -5
  65. package/hooks/blocking/verified_commit_gate_parts/gated_invocations.py +29 -17
  66. package/hooks/blocking/verified_commit_gate_parts/tests/test_gated_invocations.py +35 -0
  67. package/hooks/blocking/volatile_path_in_post_blocker.py +69 -8
  68. package/hooks/git-hooks/git_hooks_constants/__init__.py +6 -0
  69. package/hooks/git-hooks/pre_push.py +89 -2
  70. package/hooks/git-hooks/test_pre_push.py +103 -0
  71. package/hooks/hooks.json +16 -1
  72. package/hooks/hooks_constants/CLAUDE.md +1 -0
  73. package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +8 -0
  74. package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -0
  75. package/hooks/hooks_constants/code_verifier_spawn_preflight_gate_constants.py +26 -11
  76. package/hooks/hooks_constants/convergence_gate_blocker_constants.py +20 -3
  77. package/hooks/hooks_constants/destructive_command_segment_constants.py +3 -1
  78. package/hooks/hooks_constants/pr_description_proof_of_work_constants.py +0 -4
  79. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +4 -0
  80. package/hooks/hooks_constants/pyproject_config_discovery_constants.py +16 -0
  81. package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +24 -0
  82. package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +6 -0
  83. package/hooks/hooks_constants/volatile_path_in_post_blocker_constants.py +8 -1
  84. package/hooks/validators/CLAUDE.md +1 -0
  85. package/hooks/validators/mypy_integration.py +63 -50
  86. package/hooks/validators/pyproject_config_discovery.py +101 -0
  87. package/hooks/validators/ruff_integration.py +211 -23
  88. package/hooks/validators/run_all_validators.py +21 -14
  89. package/hooks/validators/test_mypy_integration.py +32 -0
  90. package/hooks/validators/test_pyproject_config_discovery.py +94 -0
  91. package/hooks/validators/test_ruff_integration.py +68 -1
  92. package/hooks/validators/test_run_all_validators.py +25 -0
  93. package/hooks/validators/test_run_all_validators_config_discovery.py +123 -0
  94. package/package.json +1 -1
  95. package/rules/docstring-prose-matches-implementation.md +20 -43
  96. package/rules/durable-post-artifacts.md +7 -0
  97. package/scripts/CLAUDE.md +2 -1
  98. package/scripts/claude-chain.example.json +15 -3
  99. package/scripts/claude_chain_runner.py +130 -27
  100. package/scripts/claude_chain_usage.py +346 -0
  101. package/scripts/codec_forwarding_test_support.py +83 -0
  102. package/scripts/conftest.py +8 -0
  103. package/scripts/dev_env_scripts_constants/CLAUDE.md +4 -3
  104. package/scripts/dev_env_scripts_constants/claude_chain_constants.py +57 -2
  105. package/scripts/dev_env_scripts_constants/claude_chain_usage_constants.py +58 -0
  106. package/scripts/dev_env_scripts_constants/code_review_constants.py +129 -12
  107. package/scripts/dev_env_scripts_constants/test_code_review_constants.py +55 -0
  108. package/scripts/dev_env_scripts_constants/timing.py +1 -1
  109. package/scripts/invoke_code_review.py +550 -38
  110. package/scripts/resolve_worker_spawn.py +8 -1
  111. package/scripts/test_claude_chain_runner.py +412 -6
  112. package/scripts/test_claude_chain_usage.py +534 -0
  113. package/scripts/test_invoke_code_review.py +298 -4
  114. package/scripts/test_invoke_code_review_codec.py +77 -0
  115. package/scripts/test_resolve_worker_spawn_codec.py +101 -0
  116. package/skills/autoconverge/SKILL.md +9 -3
  117. package/skills/autoconverge/reference/convergence.md +2 -1
  118. package/skills/autoconverge/reference/multi-pr.md +6 -1
  119. package/skills/autoconverge/reference/stop-conditions.md +16 -10
  120. package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +1 -1
  121. package/skills/autoconverge/workflow/converge.codex-gate.test.mjs +175 -3
  122. package/skills/autoconverge/workflow/converge.contract.test.mjs +19 -0
  123. package/skills/autoconverge/workflow/converge.mjs +24 -8
  124. package/skills/autoconverge/workflow/converge_multi.mjs +7 -3
  125. package/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +5 -0
  126. package/skills/fresh-branch/CLAUDE.md +2 -0
  127. package/skills/fresh-branch/SKILL.md +2 -0
  128. package/skills/fresh-branch/scripts/create_fresh_branch.py +78 -180
  129. package/skills/fresh-branch/scripts/fresh_branch_git_commands.py +285 -0
  130. package/skills/fresh-branch/scripts/fresh_branch_scripts_constants/fresh_branch_cli_constants.py +1 -0
  131. package/skills/fresh-branch/scripts/pytest.ini +4 -0
  132. package/skills/fresh-branch/scripts/test_create_fresh_branch.py +98 -0
  133. package/skills/fresh-branch/scripts/test_fresh_branch_git_commands.py +310 -0
  134. package/skills/orchestrator/SKILL.md +1 -20
  135. package/skills/orchestrator-refresh/SKILL.md +1 -1
  136. package/skills/pr-converge/SKILL.md +3 -3
  137. package/skills/pr-converge/reference/examples.md +3 -3
  138. package/skills/pr-converge/reference/fix-protocol.md +1 -1
  139. package/skills/pr-converge/reference/ground-rules.md +3 -3
  140. package/skills/pr-converge/reference/per-tick.md +5 -5
  141. package/skills/pr-converge/reference/progress-checklist.md +1 -1
  142. package/skills/pr-converge/test_step5_host_branch.py +1 -1
  143. package/skills/prototype/SKILL.md +86 -0
  144. package/skills/prototype/reference/honest-limitations.md +23 -0
  145. package/skills/prototype/reference/promotion-tasks.md +23 -0
  146. package/skills/prototype/scripts/build_sandbox_settings.py +249 -0
  147. package/skills/prototype/scripts/conftest.py +15 -0
  148. package/skills/prototype/scripts/launch_sandbox.py +205 -0
  149. package/skills/prototype/scripts/probe_sandbox_safety.py +311 -0
  150. package/skills/prototype/scripts/prototype_scripts_constants/__init__.py +1 -0
  151. package/skills/prototype/scripts/prototype_scripts_constants/config/__init__.py +0 -0
  152. package/skills/prototype/scripts/prototype_scripts_constants/config/build_sandbox_settings_constants.py +41 -0
  153. package/skills/prototype/scripts/prototype_scripts_constants/config/launch_sandbox_constants.py +23 -0
  154. package/skills/prototype/scripts/prototype_scripts_constants/config/probe_sandbox_safety_constants.py +45 -0
  155. package/skills/prototype/scripts/prototype_scripts_constants/config/prototype_common_constants.py +10 -0
  156. package/skills/prototype/scripts/test_build_sandbox_settings.py +275 -0
  157. package/skills/prototype/scripts/test_launch_sandbox.py +303 -0
  158. package/skills/prototype/scripts/test_probe_sandbox_safety.py +284 -0
  159. package/skills/prototype/workflows/promotion.md +27 -0
  160. package/skills/prototype/workflows/sandbox.md +35 -0
  161. package/skills/skill-builder/CLAUDE.md +3 -3
  162. package/skills/skill-builder/SKILL.md +5 -5
  163. package/skills/skill-builder/references/CLAUDE.md +1 -1
  164. package/skills/skill-builder/references/delegation-map.md +3 -3
  165. package/skills/skill-builder/references/description-field.md +1 -1
  166. package/skills/skill-builder/references/skill-modularity.md +2 -3
  167. package/skills/skill-builder/workflows/CLAUDE.md +1 -1
  168. package/skills/skill-builder/workflows/improve-skill.md +1 -1
  169. package/skills/skill-builder/workflows/new-skill.md +2 -2
  170. package/skills/team-advisor/SKILL.md +5 -4
@@ -0,0 +1,284 @@
1
+ """Tests for the sandbox safety-hook probe.
2
+
3
+ The block-detection unit tests drive fixture hook scripts, and the
4
+ integration tests point a settings file at the real installed safety hooks
5
+ to prove they block their probe payloads end to end.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import importlib.util
11
+ import json
12
+ import sys
13
+ from pathlib import Path
14
+ from types import ModuleType
15
+
16
+ from prototype_scripts_constants.config.build_sandbox_settings_constants import (
17
+ ALL_SAFETY_HOOK_SCRIPT_BASENAMES,
18
+ COMMAND_KEY,
19
+ HOOKS_KEY,
20
+ MATCHER_KEY,
21
+ PRE_TOOL_USE_KEY,
22
+ )
23
+ from prototype_scripts_constants.config.probe_sandbox_safety_constants import (
24
+ DESTRUCTIVE_PROBE_COMMAND,
25
+ ENVELOPE_TOOL_INPUT_KEY,
26
+ ENVELOPE_TOOL_NAME_KEY,
27
+ PII_PROBE_TOKEN_PREFIX,
28
+ PII_PROBE_TOOL_NAME,
29
+ PROBE_FAILURE_EXIT_CODE,
30
+ PROBE_SUCCESS_EXIT_CODE,
31
+ TOOL_INPUT_COMMAND_KEY,
32
+ TOOL_INPUT_CONTENT_KEY,
33
+ )
34
+
35
+ SCRIPTS_DIRECTORY = Path(__file__).resolve().parent
36
+ PROBE_PATH = SCRIPTS_DIRECTORY / "probe_sandbox_safety.py"
37
+ HOOKS_BLOCKING_DIRECTORY = SCRIPTS_DIRECTORY.parents[2] / "hooks" / "blocking"
38
+
39
+ PII_HOOK_BASENAME = ALL_SAFETY_HOOK_SCRIPT_BASENAMES[0]
40
+ DESTRUCTIVE_HOOK_BASENAME = ALL_SAFETY_HOOK_SCRIPT_BASENAMES[1]
41
+
42
+ DENY_HOOK_SOURCE = (
43
+ "import json, sys\n"
44
+ "sys.stdout.write(json.dumps("
45
+ '{"hookSpecificOutput": {"permissionDecision": "deny"}}))\n'
46
+ )
47
+ ASK_HOOK_SOURCE = (
48
+ "import json, sys\n"
49
+ "sys.stdout.write(json.dumps("
50
+ '{"hookSpecificOutput": {"permissionDecision": "ask"}}))\n'
51
+ )
52
+ ALLOW_HOOK_SOURCE = "import sys\nsys.exit(0)\n"
53
+
54
+
55
+ def load_probe_module() -> ModuleType:
56
+ spec = importlib.util.spec_from_file_location("probe_sandbox_safety", PROBE_PATH)
57
+ assert spec is not None
58
+ assert spec.loader is not None
59
+ probe_module = importlib.util.module_from_spec(spec)
60
+ spec.loader.exec_module(probe_module)
61
+ return probe_module
62
+
63
+
64
+ def quoted_command(script_path: Path) -> str:
65
+ return f'"{sys.executable}" "{script_path}"'
66
+
67
+
68
+ def real_hook_settings_document() -> dict:
69
+ pii_hook_path = HOOKS_BLOCKING_DIRECTORY / PII_HOOK_BASENAME
70
+ destructive_hook_path = HOOKS_BLOCKING_DIRECTORY / DESTRUCTIVE_HOOK_BASENAME
71
+ return {
72
+ HOOKS_KEY: {
73
+ PRE_TOOL_USE_KEY: [
74
+ {
75
+ MATCHER_KEY: "Write",
76
+ HOOKS_KEY: [{COMMAND_KEY: quoted_command(pii_hook_path)}],
77
+ },
78
+ {
79
+ MATCHER_KEY: "Bash",
80
+ HOOKS_KEY: [{COMMAND_KEY: quoted_command(destructive_hook_path)}],
81
+ },
82
+ ]
83
+ }
84
+ }
85
+
86
+
87
+ def real_hook_deny_mode_settings_document() -> dict:
88
+ settings_document = real_hook_settings_document()
89
+ settings_document["env"] = {"CLAUDE_DESTRUCTIVE_DENY_MODE": "1"}
90
+ return settings_document
91
+
92
+
93
+ def write_settings_file(tmp_path: Path, settings_document: dict) -> Path:
94
+ settings_path = tmp_path / "sandbox-settings.json"
95
+ settings_path.write_text(json.dumps(settings_document), encoding="utf-8")
96
+ return settings_path
97
+
98
+
99
+ def test_settings_environment_overrides_reads_the_env_block() -> None:
100
+ probe = load_probe_module()
101
+ environment_overrides = probe.settings_environment_overrides(
102
+ real_hook_deny_mode_settings_document()
103
+ )
104
+ assert environment_overrides == {"CLAUDE_DESTRUCTIVE_DENY_MODE": "1"}
105
+
106
+
107
+ def test_settings_environment_overrides_empty_without_an_env_block() -> None:
108
+ probe = load_probe_module()
109
+ environment_overrides = probe.settings_environment_overrides(
110
+ real_hook_settings_document()
111
+ )
112
+ assert environment_overrides == {}
113
+
114
+
115
+ def test_main_exits_zero_when_the_real_destructive_hook_denies_in_deny_mode(
116
+ tmp_path: Path,
117
+ ) -> None:
118
+ probe = load_probe_module()
119
+ settings_path = write_settings_file(
120
+ tmp_path, real_hook_deny_mode_settings_document()
121
+ )
122
+ exit_code = probe.main(["--settings", str(settings_path)])
123
+ assert exit_code == PROBE_SUCCESS_EXIT_CODE
124
+
125
+
126
+ def test_pii_basename_probe_payload_carries_the_secret() -> None:
127
+ probe = load_probe_module()
128
+ probe_payload = probe.build_probe_payload_for_basename(PII_HOOK_BASENAME)
129
+ assert probe_payload[ENVELOPE_TOOL_NAME_KEY] == PII_PROBE_TOOL_NAME
130
+ content = probe_payload[ENVELOPE_TOOL_INPUT_KEY][TOOL_INPUT_CONTENT_KEY]
131
+ assert PII_PROBE_TOKEN_PREFIX in content
132
+
133
+
134
+ def test_destructive_basename_probe_payload_carries_the_command() -> None:
135
+ probe = load_probe_module()
136
+ probe_payload = probe.build_probe_payload_for_basename(DESTRUCTIVE_HOOK_BASENAME)
137
+ command = probe_payload[ENVELOPE_TOOL_INPUT_KEY][TOOL_INPUT_COMMAND_KEY]
138
+ assert command == DESTRUCTIVE_PROBE_COMMAND
139
+
140
+
141
+ def test_find_hook_command_for_basename_returns_the_matching_command() -> None:
142
+ probe = load_probe_module()
143
+ settings_document = real_hook_settings_document()
144
+ command = probe.find_hook_command_for_basename(settings_document, PII_HOOK_BASENAME)
145
+ assert command is not None
146
+ assert PII_HOOK_BASENAME in command
147
+ assert probe.find_hook_command_for_basename(settings_document, "absent.py") is None
148
+
149
+
150
+ def test_parse_command_argv_splits_and_unquotes_the_command() -> None:
151
+ probe = load_probe_module()
152
+ command_argv = probe.parse_command_argv('"/usr/bin/python" "/a/b hook.py"')
153
+ assert command_argv == ["/usr/bin/python", "/a/b hook.py"]
154
+
155
+
156
+ def test_hook_blocks_probe_reports_true_for_a_deny_hook(tmp_path: Path) -> None:
157
+ probe = load_probe_module()
158
+ deny_hook_path = tmp_path / "deny_hook.py"
159
+ deny_hook_path.write_text(DENY_HOOK_SOURCE, encoding="utf-8")
160
+ command_argv = [sys.executable, str(deny_hook_path)]
161
+ probe_payload = probe.build_probe_payload_for_basename(DESTRUCTIVE_HOOK_BASENAME)
162
+ assert probe.hook_blocks_probe(command_argv, probe_payload, {}) is True
163
+
164
+
165
+ def test_hook_blocks_probe_reports_false_for_an_ask_hook(tmp_path: Path) -> None:
166
+ probe = load_probe_module()
167
+ ask_hook_path = tmp_path / "ask_hook.py"
168
+ ask_hook_path.write_text(ASK_HOOK_SOURCE, encoding="utf-8")
169
+ command_argv = [sys.executable, str(ask_hook_path)]
170
+ probe_payload = probe.build_probe_payload_for_basename(DESTRUCTIVE_HOOK_BASENAME)
171
+ assert probe.hook_blocks_probe(command_argv, probe_payload, {}) is False
172
+
173
+
174
+ def test_hook_blocks_probe_reports_false_for_an_allow_hook(tmp_path: Path) -> None:
175
+ probe = load_probe_module()
176
+ allow_hook_path = tmp_path / "allow_hook.py"
177
+ allow_hook_path.write_text(ALLOW_HOOK_SOURCE, encoding="utf-8")
178
+ command_argv = [sys.executable, str(allow_hook_path)]
179
+ probe_payload = probe.build_probe_payload_for_basename(DESTRUCTIVE_HOOK_BASENAME)
180
+ assert probe.hook_blocks_probe(command_argv, probe_payload, {}) is False
181
+
182
+
183
+ def test_probe_safety_hook_blocks_against_the_real_pii_hook() -> None:
184
+ probe = load_probe_module()
185
+ settings_document = real_hook_settings_document()
186
+ assert probe.probe_safety_hook(settings_document, PII_HOOK_BASENAME) is True
187
+
188
+
189
+ def test_main_exits_three_when_the_real_destructive_hook_only_asks(
190
+ tmp_path: Path,
191
+ ) -> None:
192
+ probe = load_probe_module()
193
+ settings_path = write_settings_file(tmp_path, real_hook_settings_document())
194
+ exit_code = probe.main(["--settings", str(settings_path)])
195
+ assert exit_code == PROBE_FAILURE_EXIT_CODE
196
+
197
+
198
+ def test_main_exits_zero_when_both_real_hooks_hard_deny(tmp_path: Path) -> None:
199
+ probe = load_probe_module()
200
+ deny_pii_hook_path = tmp_path / PII_HOOK_BASENAME
201
+ deny_pii_hook_path.write_text(DENY_HOOK_SOURCE, encoding="utf-8")
202
+ deny_destructive_hook_path = tmp_path / DESTRUCTIVE_HOOK_BASENAME
203
+ deny_destructive_hook_path.write_text(DENY_HOOK_SOURCE, encoding="utf-8")
204
+ settings_document = {
205
+ HOOKS_KEY: {
206
+ PRE_TOOL_USE_KEY: [
207
+ {
208
+ MATCHER_KEY: "Write",
209
+ HOOKS_KEY: [{COMMAND_KEY: quoted_command(deny_pii_hook_path)}],
210
+ },
211
+ {
212
+ MATCHER_KEY: "Bash",
213
+ HOOKS_KEY: [
214
+ {COMMAND_KEY: quoted_command(deny_destructive_hook_path)}
215
+ ],
216
+ },
217
+ ]
218
+ }
219
+ }
220
+ settings_path = write_settings_file(tmp_path, settings_document)
221
+ exit_code = probe.main(["--settings", str(settings_path)])
222
+ assert exit_code == PROBE_SUCCESS_EXIT_CODE
223
+
224
+
225
+ def test_main_exits_three_when_a_hook_allows(tmp_path: Path) -> None:
226
+ probe = load_probe_module()
227
+ allow_pii_hook_path = tmp_path / PII_HOOK_BASENAME
228
+ allow_pii_hook_path.write_text(ALLOW_HOOK_SOURCE, encoding="utf-8")
229
+ destructive_hook_path = HOOKS_BLOCKING_DIRECTORY / DESTRUCTIVE_HOOK_BASENAME
230
+ settings_document = {
231
+ HOOKS_KEY: {
232
+ PRE_TOOL_USE_KEY: [
233
+ {
234
+ MATCHER_KEY: "Write",
235
+ HOOKS_KEY: [{COMMAND_KEY: quoted_command(allow_pii_hook_path)}],
236
+ },
237
+ {
238
+ MATCHER_KEY: "Bash",
239
+ HOOKS_KEY: [{COMMAND_KEY: quoted_command(destructive_hook_path)}],
240
+ },
241
+ ]
242
+ }
243
+ }
244
+ settings_path = write_settings_file(tmp_path, settings_document)
245
+ exit_code = probe.main(["--settings", str(settings_path)])
246
+ assert exit_code == PROBE_FAILURE_EXIT_CODE
247
+
248
+
249
+ def test_main_exits_three_when_the_settings_file_is_absent(tmp_path: Path) -> None:
250
+ probe = load_probe_module()
251
+ absent_settings_path = tmp_path / "does-not-exist.json"
252
+ exit_code = probe.main(["--settings", str(absent_settings_path)])
253
+ assert exit_code == PROBE_FAILURE_EXIT_CODE
254
+
255
+
256
+ def test_main_exits_three_when_the_settings_file_is_invalid_json(tmp_path: Path) -> None:
257
+ probe = load_probe_module()
258
+ settings_path = tmp_path / "sandbox-settings.json"
259
+ settings_path.write_text("{not valid json", encoding="utf-8")
260
+ exit_code = probe.main(["--settings", str(settings_path)])
261
+ assert exit_code == PROBE_FAILURE_EXIT_CODE
262
+
263
+
264
+ def test_main_exits_three_when_a_hook_script_is_bogus(tmp_path: Path) -> None:
265
+ probe = load_probe_module()
266
+ bogus_pii_hook_path = tmp_path / PII_HOOK_BASENAME
267
+ destructive_hook_path = HOOKS_BLOCKING_DIRECTORY / DESTRUCTIVE_HOOK_BASENAME
268
+ settings_document = {
269
+ HOOKS_KEY: {
270
+ PRE_TOOL_USE_KEY: [
271
+ {
272
+ MATCHER_KEY: "Write",
273
+ HOOKS_KEY: [{COMMAND_KEY: quoted_command(bogus_pii_hook_path)}],
274
+ },
275
+ {
276
+ MATCHER_KEY: "Bash",
277
+ HOOKS_KEY: [{COMMAND_KEY: quoted_command(destructive_hook_path)}],
278
+ },
279
+ ]
280
+ }
281
+ }
282
+ settings_path = write_settings_file(tmp_path, settings_document)
283
+ exit_code = probe.main(["--settings", str(settings_path)])
284
+ assert exit_code == PROBE_FAILURE_EXIT_CODE
@@ -0,0 +1,27 @@
1
+ # Promotion workflow (Phase 2)
2
+
3
+ Turn a successful proof-of-concept into a real, verified change. Run every step in the normal, fully-hooked session — never inside the `--bare` sandbox. The sandbox produced a reference build; promotion re-verifies it to standard and often rewrites parts of it.
4
+
5
+ ## Seed the task list first
6
+
7
+ Register every item in `reference/promotion-tasks.md` as a session task (`TaskCreate`, or `TodoWrite` if that is the host tool). Work only from the task list. Mark each complete with evidence — a command result, a path, a verdict, or a skill's return.
8
+
9
+ ## The clean-room protocol
10
+
11
+ The task seeds carry the full ordered detail. The shape:
12
+
13
+ 1. **Confirm** the POC is worth promoting and the user wants it shipped.
14
+ 2. **Fresh branch** off freshly-fetched `origin/main` via `fresh-branch` — clean history, based on live upstream.
15
+ 3. **Bring content as an uncommitted diff.** Copy the POC's file changes into the new branch's working tree. Do not cherry-pick or merge the sandbox commits; the sandbox history stays behind.
16
+ 4. **Cleanup.** Remove scratch files, debug dumps, and temp helpers the POC created (`cleanup-temp-files` rule).
17
+ 5. **Privacy sweep** via `privacy-hygiene` over the diff.
18
+ 6. **Verify** with the `code-verifier` agent in a fresh context. This is where standards re-engage. Expect findings and a repair loop — the POC was un-TDD'd.
19
+ 7. **Commit and PR.** Only on a clean verdict, run `/commit`, then open a draft PR per the `git-workflow` rule.
20
+ 8. **State the honest limitations** from `reference/honest-limitations.md` in the PR body or to the user.
21
+ 9. **Converge** by handing the PR to `autoconverge` by default; use `pr-converge` for paced ticks or `bugteam` for an open-loop audit.
22
+
23
+ ## Why the clean room, not a push
24
+
25
+ `code_rules_enforcer` is a write-time Write/Edit gate. Content that lands through `git apply`, `git checkout`, or cherry-pick never passes through it, so pushing the sandbox branch would ship code the rule engine never saw, carrying sandbox scratch along with it. Standards re-engage through the `code-verifier` agent, the `privacy-hygiene` sweep, and the PR review — not through a write-time hook. Steps 4-6 are hard gates: the honest claim on promoted POC code is code-verifier-passed, privacy-swept, review-passed, with TDD ordering waived.
26
+
27
+ The `verified_commit_gate` hook is the backstop under all of this — it refuses a commit or push with no minted verdict — but the clean-room steps are the mechanism, not the gate firing.
@@ -0,0 +1,35 @@
1
+ # Sandbox workflow (Phase 1)
2
+
3
+ Stand up an isolated worktree where a hookless agent builds a proof-of-concept with zero standards friction. Only two safety gates stay live: personal-data blocking and destructive-command blocking.
4
+
5
+ ## Step 1 — Isolated worktree
6
+
7
+ Invoke the `fresh-branch` skill to create a worktree off `origin/main`. Keep the returned `worktree_path` and `base_commit` for the promotion phase. If `fresh-branch` is not installed, stop with the refusal line from `SKILL.md`.
8
+
9
+ ## Step 2 — Minimal safety settings
10
+
11
+ Run `scripts/build_sandbox_settings.py --out <worktree>/.prototype-sandbox-settings.json`.
12
+
13
+ It reads the live `~/.claude/settings.json`, resolves each safety hook's command (the correct interpreter and absolute path for this machine), and registers it on the matchers the sandbox needs — personal-data on Write, Edit, MultiEdit, and Bash; destructive-command on Bash. The write file carries only those two gates, plus an `env` block that sets `CLAUDE_DESTRUCTIVE_DENY_MODE`, so the destructive gate hard-denies a match. A hard deny holds under `--dangerously-skip-permissions`; an `ask` there is auto-resolved.
14
+
15
+ - Exit 0 → continue with the printed settings path.
16
+ - Exit 2 → one of the two safety hooks could not be resolved from the live settings. Stop; the sandbox cannot be contained without both. Report which one is missing.
17
+
18
+ ## Step 3 — Prove containment
19
+
20
+ Run `scripts/probe_sandbox_safety.py --settings <settings path>`.
21
+
22
+ It feeds each safety hook a payload the hook is proven to block and confirms a hard-deny decision — the block that holds even under `--dangerously-skip-permissions`, unlike an `ask` prompt the flag auto-resolves. It runs each hook under the settings' `env` block, so the destructive gate runs in deny mode, the same way the launched session runs it.
23
+
24
+ - Exit 0 → both gates hard-deny their probe; the sandbox is contained.
25
+ - Exit 3 → a gate allowed its probe, returned an `ask`, or errored. Stop; do not launch an uncontained sandbox. This is a runtime check, not a code read (verify-runtime-state) — do not skip it.
26
+
27
+ ## Step 4 — Launch the hookless session
28
+
29
+ Run `scripts/launch_sandbox.py --worktree <worktree_path> --settings <settings path> --task-file <task file>`.
30
+
31
+ It starts a headless `claude -p --bare --dangerously-skip-permissions --settings <settings path>` session in the worktree, so every standards gate is skipped and the two safety gates from the settings file are the only containment. Write the proof-of-concept task to the task file first; the sandbox agent cannot invoke other skills, so the task must be self-contained.
32
+
33
+ ## Step 5 — Judge the result
34
+
35
+ Read what the sandbox built and decide whether the proof-of-concept proves the idea. On success, move to `promotion.md`. To discard, remove the worktree with `git worktree remove --force <worktree_path>`.
@@ -1,12 +1,12 @@
1
1
  # skill-builder
2
2
 
3
- Orchestrates the skill-building lifecycle: classify type, scaffold folders, write via `skill-writer`, enforce modularity (sub-skills / composition), write description as a trigger catalog, require deterministic steps as code, self-audit, and refine from real usage.
3
+ Orchestrates the skill-building lifecycle: classify type, scaffold folders, write via the `skill-writer-agent` agent, enforce modularity (sub-skills / composition), write description as a trigger catalog, require deterministic steps as code, self-audit, and refine from real usage.
4
4
 
5
5
  **Trigger:** build a skill, new skill workflow, improve this skill, optimize skill description, skill development lifecycle, skill modularity, description trigger catalog, deterministic skill scripts.
6
6
 
7
7
  ## Purpose
8
8
 
9
- Enforces craft standards for new and existing skills. For quick one-off SKILL.md edits, use `/skill-writer` directly. This skill classifies, scaffolds, gathers context (composition plan, description triggers, deterministic inventory), delegates writing, and self-audits.
9
+ Enforces craft standards for new and existing skills. For quick one-off SKILL.md edits, spawn the `skill-writer-agent` agent directly. This skill classifies, scaffolds, gathers context (composition plan, description triggers, deterministic inventory), delegates writing, and self-audits.
10
10
 
11
11
  ## Key files
12
12
 
@@ -45,5 +45,5 @@ Enforces craft standards for new and existing skills. For quick one-off SKILL.md
45
45
 
46
46
  - Every build ends with the self-audit at `references/self-audit-checklist.md`; fix failures before delivery.
47
47
  - Modularity items, description trigger-catalog items, and deterministic-element classification are mandatory on every delivery.
48
- - `skill-builder` orchestrates; `skill-writer` authors. Handoff packet must include type, gap analysis, composition plan, description trigger catalog, deterministic inventory, degree-of-freedom assessment, and constraints.
48
+ - `skill-builder` orchestrates; the `skill-writer-agent` agent authors. Handoff packet must include type, gap analysis, composition plan, description trigger catalog, deterministic inventory, degree-of-freedom assessment, and constraints.
49
49
  - Claude A / Claude B: Claude A (this session) designs; Claude B (subagents) tests the built skill on real tasks.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: skill-builder
3
3
  description: >-
4
- Skill lifecycle: classify, scaffold, write via skill-writer, self-audit, compose
4
+ Skill lifecycle: classify, scaffold, write via the skill-writer-agent, self-audit, compose
5
5
  sub-skills, polish description triggers, enforce deterministic scripts. Triggers:
6
6
  build a skill, new skill workflow, improve this skill, optimize skill description,
7
7
  skill development lifecycle, skill modularity, skill composition, description
@@ -28,9 +28,9 @@ Highest-signal content. Append a bullet each time a skill build fails in a new w
28
28
 
29
29
  ## When this skill applies
30
30
 
31
- Trigger for requests to **build**, **improve**, or **polish** a skill. This skill orchestrates the process — it classifies, scaffolds, gathers context, delegates writing to `/skill-writer`, and self-audits the result.
31
+ Trigger for requests to **build**, **improve**, or **polish** a skill. This skill orchestrates the process — it classifies, scaffolds, gathers context, delegates writing to the `skill-writer-agent` agent, and self-audits the result.
32
32
 
33
- For quick skill syntax questions or one-off SKILL.md edits, use `/skill-writer` directly instead.
33
+ For quick skill syntax questions or one-off SKILL.md edits, spawn the `skill-writer-agent` agent directly.
34
34
 
35
35
  **Refusal cases — first match wins:**
36
36
 
@@ -161,9 +161,9 @@ Also: story-form descriptions; monolith multi-capability skills; silent reimplem
161
161
 
162
162
  After every build, improvement, or polish pass, load `${CLAUDE_SKILL_DIR}/references/self-audit-checklist.md`, **register every bullet as a session task**, and complete each with evidence. Every item must pass before delivery. Fix failures, then re-audit.
163
163
 
164
- ## Delegation to skill-writer
164
+ ## Delegation to skill-writer-agent
165
165
 
166
- skill-builder orchestrates; skill-writer authors. The handoff packet from Step 4 must include:
166
+ skill-builder orchestrates; the `skill-writer-agent` agent authors. The caller spawns it with `Agent(subagent_type="skill-writer-agent", ...)`. The handoff packet from Step 4 must include:
167
167
 
168
168
  - Skill type and folder structure
169
169
  - Gap analysis or observation findings (composition plan + description triggers + deterministic inventory)
@@ -12,7 +12,7 @@ Best-practice specifications and the mandatory self-audit checklist for the `/sk
12
12
  | `skill-modularity.md` | Cross-skill modularity: one capability, sub-skills by name, composition plan, anti-monolith. |
13
13
  | `description-field.md` | Frontmatter description as trigger catalog (capability stem + Triggers). No story prose. |
14
14
  | `deterministic-elements.md` | Classify process steps; deterministic ones ship as code, task seeds, or fixed artifacts under CODE_RULES. |
15
- | `delegation-map.md` | Subagent handoff patterns and transcript guidance for skill-builder → skill-writer. |
15
+ | `delegation-map.md` | Subagent handoff patterns and transcript guidance for skill-builder → skill-writer-agent. |
16
16
  | `thariq-x-post-skills.json` | Source reference data from Anthropic lessons on building Claude Code skills. |
17
17
 
18
18
  ## Conventions
@@ -1,10 +1,10 @@
1
1
  # Delegation Map
2
2
 
3
- How skill-builder delegates work to subagents and `/skill-writer`.
3
+ How skill-builder delegates authoring to the `skill-writer-agent` agent.
4
4
 
5
- ## Delegating to skill-writer (Step 4)
5
+ ## Delegating to skill-writer-agent (Step 4)
6
6
 
7
- skill-builder orchestrates; skill-writer authors the SKILL.md and companion files. The handoff must be structured so skill-writer has everything it needs.
7
+ skill-builder orchestrates; the `skill-writer-agent` agent authors the SKILL.md and companion files. The caller spawns it with `Agent(subagent_type="skill-writer-agent", ...)`. The handoff must be structured so the agent has everything it needs.
8
8
 
9
9
  ### New skill handoff
10
10
 
@@ -44,7 +44,7 @@ description: >-
44
44
 
45
45
  ```yaml
46
46
  description: >-
47
- Skill lifecycle: classify, scaffold, write via skill-writer, self-audit, compose sub-skills, polish description triggers. Triggers: build a skill, new skill workflow, improve this skill, optimize skill description, skill development lifecycle, skill modularity.
47
+ Skill lifecycle: classify, scaffold, write via the skill-writer-agent, self-audit, compose sub-skills, polish description triggers. Triggers: build a skill, new skill workflow, improve this skill, optimize skill description, skill development lifecycle, skill modularity.
48
48
  ```
49
49
 
50
50
  ```yaml
@@ -54,7 +54,7 @@ sibling-skill-a/ # one capability
54
54
  sibling-skill-b/ # one capability
55
55
  ```
56
56
 
57
- The orchestrator owns sequence and gates. Sub-skills own domain detail. skill-builder itself follows this shape: it orchestrates and delegates writing to `/skill-writer`.
57
+ The orchestrator owns sequence and gates. Sub-skills own domain detail. skill-builder itself follows this shape: it orchestrates and delegates writing to the `skill-writer-agent` agent (spawned via the Agent tool).
58
58
 
59
59
  ### Within-skill vs split
60
60
 
@@ -85,12 +85,11 @@ In Process or a dedicated **Sub-skills** section:
85
85
  | Skill | When | Produces |
86
86
  |---|---|---|
87
87
  | `/reviewer-gates` | Step 1 — gate external reviewers | opt-out, Copilot quota, Bugbot trigger decisions |
88
- | `/skill-writer` | Step 4 — author SKILL.md | skill package files |
89
88
 
90
89
  If a listed skill is not installed, respond: `[exact refusal or degrade line]`.
91
90
  ```
92
91
 
93
- Self-audit requires this table (or equivalent) whenever the skill composes others.
92
+ Self-audit requires this table (or equivalent) whenever the skill composes others. skill-builder itself delegates SKILL.md authoring to the `skill-writer-agent` agent, spawned via the Agent tool.
94
93
 
95
94
  ## Anti-patterns
96
95
 
@@ -6,7 +6,7 @@ Step-by-step workflow files for each skill lifecycle phase, loaded on demand by
6
6
 
7
7
  | File | Purpose |
8
8
  |---|---|
9
- | `new-skill.md` | Full lifecycle: classify, scaffold, gather (composition + description triggers + deterministic inventory), write via skill-writer, self-audit, deliver. |
9
+ | `new-skill.md` | Full lifecycle: classify, scaffold, gather (composition + description triggers + deterministic inventory), write via the skill-writer-agent agent, self-audit, deliver. |
10
10
  | `improve-skill.md` | Observation-first improve: diagnose activation/modularity/quality/deterministic prose, targeted fix, re-audit. |
11
11
  | `polish-skill.md` | Description trigger-catalog audit; progressive disclosure + modularity + deterministic audit; task-seeded self-audit. |
12
12
 
@@ -83,7 +83,7 @@ For each diagnosis from Step 2:
83
83
  6. For checkbox/tracker failures: replace markdown `- [ ]` boards with a plain task-seed list and a seed instruction (`TaskCreate` / `TodoWrite`).
84
84
  7. Verify the fix doesn’t break anything that was working.
85
85
 
86
- Delegate larger rewrites to `/skill-writer` using the refine-skill handoff from delegation-map.md.
86
+ Spawn the `skill-writer-agent` agent for larger rewrites, using the refine-skill handoff from delegation-map.md.
87
87
 
88
88
  **Output:** Modified skill files with targeted fixes.
89
89
 
@@ -125,11 +125,11 @@ Register each task seed under **Required task seeds** in `deterministic-elements
125
125
 
126
126
  **Goal:** Produce the skill package — SKILL.md and companion files.
127
127
 
128
- Delegate to `/skill-writer` using the structured handoff from `${CLAUDE_SKILL_DIR}/references/delegation-map.md`.
128
+ Spawn the `skill-writer-agent` agent (`Agent(subagent_type="skill-writer-agent", ...)`) with the structured handoff from `${CLAUDE_SKILL_DIR}/references/delegation-map.md`.
129
129
 
130
130
  The handoff must include: skill type, folder structure, gap analysis (composition plan + description triggers + deterministic inventory), initial gotchas, degree of freedom, constraints, sub-skills to name in SKILL.md, exact description string to put in frontmatter, script/test paths for every deterministic step.
131
131
 
132
- After skill-writer produces the draft:
132
+ After the `skill-writer-agent` agent produces the draft:
133
133
 
134
134
  1. Verify it follows the hub layout (principle → gotchas → when-applies → process → file index → folder map).
135
135
  2. Verify SKILL.md body is under 500 lines.
@@ -17,8 +17,8 @@ One warm, addressable advisor available at the strongest model tier the session
17
17
 
18
18
  **Detect the host profile first** (Host profiles in
19
19
  [`_shared/advisor/advisor-protocol.md`](../../_shared/advisor/advisor-protocol.md)
20
- — e.g. `ADVISOR_HOST_PROFILE` or `THIRD_PARTY`). Do not start a model-floor
21
- walk until the host is known.
20
+ — e.g. %USERPROFILE%\.claude\_shared\advisor\advisor-protocol.md). Do not start a model-floor
21
+ walk until the host is known.
22
22
 
23
23
  This session is the shared advisor's sole consumer, so its model floor is
24
24
  simply this session's own tier — no routing table to take a max against.
@@ -30,8 +30,9 @@ the only consumer (skip the "who you are and your assignment" opener in each
30
30
  consult; a single-consumer session doesn't need it).
31
31
 
32
32
  **Third-party host:** bind a max-tier Claude advisor through the shared CLI Claude-chain
33
- in the protocol (Fable max, then Opus max; `claude_chain_runner.py` walks
34
- `~/.claude/claude-chain.json` for account usage failover). Consult via
33
+ in the protocol (Fable max, then Opus max; `claude_chain_runner.py` ranks
34
+ `~/.claude/claude-chain.json` accounts by weekly remaining via `claude_chain_usage`
35
+ and fails over on usage limits). Consult via
35
36
  `--resume <session_id>` on that bind. This session is the sole consumer of that
36
37
  CLI advisor; skip the multi-consumer opener. When the chain cannot bind or
37
38
  reply, fail closed and report to the user — do **not** answer ENDORSE /