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.
- package/CLAUDE.md +3 -2
- package/_shared/advisor/advisor-protocol.md +2 -2
- package/_shared/pr-loop/scripts/CLAUDE.md +1 -0
- package/_shared/pr-loop/scripts/grant_project_claude_permissions.py +306 -284
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +1 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/claude_permissions_constants.py +3 -3
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/stale_worktree_rule_sweep_constants.py +107 -0
- package/_shared/pr-loop/scripts/revoke_project_claude_permissions.py +5 -0
- package/_shared/pr-loop/scripts/stale_worktree_rule_sweep.py +107 -0
- package/_shared/pr-loop/scripts/tests/CLAUDE.md +2 -0
- package/_shared/pr-loop/scripts/tests/test_agent_config_carveout.py +55 -73
- package/_shared/pr-loop/scripts/tests/test_claude_permissions_constants.py +9 -15
- package/_shared/pr-loop/scripts/tests/test_grant_project_claude_permissions.py +91 -1
- package/_shared/pr-loop/scripts/tests/test_revoke_project_claude_permissions.py +88 -1
- package/_shared/pr-loop/scripts/tests/test_stale_worktree_rule_sweep.py +301 -0
- package/_shared/pr-loop/scripts/tests/test_stale_worktree_rule_sweep_constants.py +85 -0
- package/_shared/pr-loop/worker-spawn.md +1 -1
- package/agents/CLAUDE.md +1 -0
- package/agents/code-verifier.md +4 -4
- package/agents/skill-writer-agent.md +84 -0
- package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +141 -41
- package/audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md +29 -13
- package/docs/CLAUDE.md +1 -0
- package/docs/references/CLAUDE.md +1 -0
- package/docs/references/code-review-enforcement.md +97 -0
- package/docs/wsl-docker-cowork-starter-matrix.md +89 -0
- package/hooks/blocking/CLAUDE.md +8 -1
- package/hooks/blocking/code_review_enforcement_config_bootstrap.py +53 -0
- package/hooks/blocking/code_review_gate_deny.py +74 -0
- package/hooks/blocking/code_review_pr_create_gate.py +194 -0
- package/hooks/blocking/code_review_push_gate.py +140 -0
- package/hooks/blocking/code_review_stamp_directory_write_blocker.py +340 -0
- package/hooks/blocking/code_review_stamp_store.py +233 -0
- package/hooks/blocking/code_review_stamp_write_blocker_parts/__init__.py +7 -0
- package/hooks/blocking/code_review_stamp_write_blocker_parts/conftest.py +15 -0
- package/hooks/blocking/code_review_stamp_write_blocker_parts/obfuscated_stamp_path_reference.py +212 -0
- package/hooks/blocking/code_review_stamp_write_blocker_parts/split_directory_change_into_stamp.py +138 -0
- package/hooks/blocking/code_review_stamp_write_blocker_parts/test_obfuscated_stamp_path_reference.py +49 -0
- package/hooks/blocking/code_review_stamp_write_blocker_parts/test_split_directory_change_into_stamp.py +38 -0
- package/hooks/blocking/code_verifier_spawn_preflight_gate.py +39 -27
- package/hooks/blocking/config/code_review_enforcement_constants.py +110 -0
- package/hooks/blocking/config/test_code_review_enforcement_constants.py +108 -0
- package/hooks/blocking/config/verified_commit_constants.py +24 -9
- package/hooks/blocking/conftest.py +2 -0
- package/hooks/blocking/convergence_gate_blocker.py +112 -23
- package/hooks/blocking/destructive_command_blocker.py +19 -6
- package/hooks/blocking/pr_description_proof_of_work.py +52 -34
- package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +4 -1
- package/hooks/blocking/test_code_review_enforcement_config_bootstrap.py +62 -0
- package/hooks/blocking/test_code_review_gate_deny.py +54 -0
- package/hooks/blocking/test_code_review_pr_create_gate.py +185 -0
- package/hooks/blocking/test_code_review_push_gate.py +189 -0
- package/hooks/blocking/test_code_review_stamp_directory_write_blocker.py +180 -0
- package/hooks/blocking/test_code_review_stamp_store.py +205 -0
- package/hooks/blocking/test_code_verifier_spawn_preflight_gate.py +124 -2
- package/hooks/blocking/test_code_verifier_tools_contract.py +28 -0
- package/hooks/blocking/test_convergence_gate_blocker.py +153 -5
- package/hooks/blocking/test_destructive_command_blocker.py +1 -1
- package/hooks/blocking/test_destructive_command_blocker_deny_mode.py +45 -0
- package/hooks/blocking/test_pr_description_proof_of_work.py +151 -0
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +8 -8
- package/hooks/blocking/test_verification_verdict_store.py +920 -810
- package/hooks/blocking/test_volatile_path_in_post_blocker.py +114 -2
- package/hooks/blocking/verification_verdict_store.py +118 -5
- package/hooks/blocking/verified_commit_gate_parts/gated_invocations.py +29 -17
- package/hooks/blocking/verified_commit_gate_parts/tests/test_gated_invocations.py +35 -0
- package/hooks/blocking/volatile_path_in_post_blocker.py +69 -8
- package/hooks/git-hooks/git_hooks_constants/__init__.py +6 -0
- package/hooks/git-hooks/pre_push.py +89 -2
- package/hooks/git-hooks/test_pre_push.py +103 -0
- package/hooks/hooks.json +16 -1
- package/hooks/hooks_constants/CLAUDE.md +1 -0
- package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +8 -0
- package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -0
- package/hooks/hooks_constants/code_verifier_spawn_preflight_gate_constants.py +26 -11
- package/hooks/hooks_constants/convergence_gate_blocker_constants.py +20 -3
- package/hooks/hooks_constants/destructive_command_segment_constants.py +3 -1
- package/hooks/hooks_constants/pr_description_proof_of_work_constants.py +0 -4
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +4 -0
- package/hooks/hooks_constants/pyproject_config_discovery_constants.py +16 -0
- package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +24 -0
- package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +6 -0
- package/hooks/hooks_constants/volatile_path_in_post_blocker_constants.py +8 -1
- package/hooks/validators/CLAUDE.md +1 -0
- package/hooks/validators/mypy_integration.py +63 -50
- package/hooks/validators/pyproject_config_discovery.py +101 -0
- package/hooks/validators/ruff_integration.py +211 -23
- package/hooks/validators/run_all_validators.py +21 -14
- package/hooks/validators/test_mypy_integration.py +32 -0
- package/hooks/validators/test_pyproject_config_discovery.py +94 -0
- package/hooks/validators/test_ruff_integration.py +68 -1
- package/hooks/validators/test_run_all_validators.py +25 -0
- package/hooks/validators/test_run_all_validators_config_discovery.py +123 -0
- package/package.json +1 -1
- package/rules/docstring-prose-matches-implementation.md +20 -43
- package/rules/durable-post-artifacts.md +7 -0
- package/scripts/CLAUDE.md +2 -1
- package/scripts/claude-chain.example.json +15 -3
- package/scripts/claude_chain_runner.py +130 -27
- package/scripts/claude_chain_usage.py +346 -0
- package/scripts/codec_forwarding_test_support.py +83 -0
- package/scripts/conftest.py +8 -0
- package/scripts/dev_env_scripts_constants/CLAUDE.md +4 -3
- package/scripts/dev_env_scripts_constants/claude_chain_constants.py +57 -2
- package/scripts/dev_env_scripts_constants/claude_chain_usage_constants.py +58 -0
- package/scripts/dev_env_scripts_constants/code_review_constants.py +129 -12
- package/scripts/dev_env_scripts_constants/test_code_review_constants.py +55 -0
- package/scripts/dev_env_scripts_constants/timing.py +1 -1
- package/scripts/invoke_code_review.py +550 -38
- package/scripts/resolve_worker_spawn.py +8 -1
- package/scripts/test_claude_chain_runner.py +412 -6
- package/scripts/test_claude_chain_usage.py +534 -0
- package/scripts/test_invoke_code_review.py +298 -4
- package/scripts/test_invoke_code_review_codec.py +77 -0
- package/scripts/test_resolve_worker_spawn_codec.py +101 -0
- package/skills/autoconverge/SKILL.md +9 -3
- package/skills/autoconverge/reference/convergence.md +2 -1
- package/skills/autoconverge/reference/multi-pr.md +6 -1
- package/skills/autoconverge/reference/stop-conditions.md +16 -10
- package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +1 -1
- package/skills/autoconverge/workflow/converge.codex-gate.test.mjs +175 -3
- package/skills/autoconverge/workflow/converge.contract.test.mjs +19 -0
- package/skills/autoconverge/workflow/converge.mjs +24 -8
- package/skills/autoconverge/workflow/converge_multi.mjs +7 -3
- package/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +5 -0
- package/skills/fresh-branch/CLAUDE.md +2 -0
- package/skills/fresh-branch/SKILL.md +2 -0
- package/skills/fresh-branch/scripts/create_fresh_branch.py +78 -180
- package/skills/fresh-branch/scripts/fresh_branch_git_commands.py +285 -0
- package/skills/fresh-branch/scripts/fresh_branch_scripts_constants/fresh_branch_cli_constants.py +1 -0
- package/skills/fresh-branch/scripts/pytest.ini +4 -0
- package/skills/fresh-branch/scripts/test_create_fresh_branch.py +98 -0
- package/skills/fresh-branch/scripts/test_fresh_branch_git_commands.py +310 -0
- package/skills/orchestrator/SKILL.md +1 -20
- package/skills/orchestrator-refresh/SKILL.md +1 -1
- package/skills/pr-converge/SKILL.md +3 -3
- package/skills/pr-converge/reference/examples.md +3 -3
- package/skills/pr-converge/reference/fix-protocol.md +1 -1
- package/skills/pr-converge/reference/ground-rules.md +3 -3
- package/skills/pr-converge/reference/per-tick.md +5 -5
- package/skills/pr-converge/reference/progress-checklist.md +1 -1
- package/skills/pr-converge/test_step5_host_branch.py +1 -1
- package/skills/prototype/SKILL.md +86 -0
- package/skills/prototype/reference/honest-limitations.md +23 -0
- package/skills/prototype/reference/promotion-tasks.md +23 -0
- package/skills/prototype/scripts/build_sandbox_settings.py +249 -0
- package/skills/prototype/scripts/conftest.py +15 -0
- package/skills/prototype/scripts/launch_sandbox.py +205 -0
- package/skills/prototype/scripts/probe_sandbox_safety.py +311 -0
- package/skills/prototype/scripts/prototype_scripts_constants/__init__.py +1 -0
- package/skills/prototype/scripts/prototype_scripts_constants/config/__init__.py +0 -0
- package/skills/prototype/scripts/prototype_scripts_constants/config/build_sandbox_settings_constants.py +41 -0
- package/skills/prototype/scripts/prototype_scripts_constants/config/launch_sandbox_constants.py +23 -0
- package/skills/prototype/scripts/prototype_scripts_constants/config/probe_sandbox_safety_constants.py +45 -0
- package/skills/prototype/scripts/prototype_scripts_constants/config/prototype_common_constants.py +10 -0
- package/skills/prototype/scripts/test_build_sandbox_settings.py +275 -0
- package/skills/prototype/scripts/test_launch_sandbox.py +303 -0
- package/skills/prototype/scripts/test_probe_sandbox_safety.py +284 -0
- package/skills/prototype/workflows/promotion.md +27 -0
- package/skills/prototype/workflows/sandbox.md +35 -0
- package/skills/skill-builder/CLAUDE.md +3 -3
- package/skills/skill-builder/SKILL.md +5 -5
- package/skills/skill-builder/references/CLAUDE.md +1 -1
- package/skills/skill-builder/references/delegation-map.md +3 -3
- package/skills/skill-builder/references/description-field.md +1 -1
- package/skills/skill-builder/references/skill-modularity.md +2 -3
- package/skills/skill-builder/workflows/CLAUDE.md +1 -1
- package/skills/skill-builder/workflows/improve-skill.md +1 -1
- package/skills/skill-builder/workflows/new-skill.md +2 -2
- package/skills/team-advisor/SKILL.md +5 -4
|
@@ -1,284 +1,306 @@
|
|
|
1
|
-
"""Grant Edit/
|
|
2
|
-
|
|
3
|
-
Run from the project root whose .claude/** you want a Claude Code session
|
|
4
|
-
(including spawned subagents) to edit without prompting. Writes idempotent
|
|
5
|
-
entries into the user-scope settings at ~/.claude/settings.json and prints
|
|
6
|
-
the changes applied. No-op when the entries already exist.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
import sys
|
|
10
|
-
from pathlib import Path
|
|
11
|
-
|
|
12
|
-
parent_directory = str(Path(__file__).resolve().parent)
|
|
13
|
-
if parent_directory not in sys.path:
|
|
14
|
-
sys.path.insert(0, parent_directory)
|
|
15
|
-
|
|
16
|
-
from _claude_permissions_common import ( # noqa: E402
|
|
17
|
-
append_if_missing,
|
|
18
|
-
build_agent_config_deny_rules,
|
|
19
|
-
build_permission_rules,
|
|
20
|
-
ensure_dict_section,
|
|
21
|
-
ensure_list_entry,
|
|
22
|
-
exit_with_error,
|
|
23
|
-
get_current_project_path,
|
|
24
|
-
is_trust_entry_for_project,
|
|
25
|
-
is_valid_project_root,
|
|
26
|
-
load_settings,
|
|
27
|
-
remove_matching_entries_from_list,
|
|
28
|
-
save_settings,
|
|
29
|
-
)
|
|
30
|
-
from pr_loop_shared_constants.claude_permissions_constants import (
|
|
31
|
-
ALL_AGENT_CONFIG_DENY_TOOLS,
|
|
32
|
-
ALL_AGENT_CONFIG_PATH_PATTERNS,
|
|
33
|
-
ALL_PERMISSION_ALLOW_TOOLS,
|
|
34
|
-
AUTO_MODE_ENVIRONMENT_ENTRY_PREFIX,
|
|
35
|
-
AUTO_MODE_ENVIRONMENT_ENTRY_TEMPLATE,
|
|
36
|
-
get_claude_user_settings_path,
|
|
37
|
-
)
|
|
38
|
-
from pr_loop_shared_constants.claude_settings_keys_constants import (
|
|
39
|
-
CLAUDE_SETTINGS_ADDITIONAL_DIRECTORIES_KEY,
|
|
40
|
-
CLAUDE_SETTINGS_ALLOW_KEY,
|
|
41
|
-
CLAUDE_SETTINGS_AUTO_MODE_KEY,
|
|
42
|
-
CLAUDE_SETTINGS_DENY_KEY,
|
|
43
|
-
CLAUDE_SETTINGS_ENVIRONMENT_KEY,
|
|
44
|
-
CLAUDE_SETTINGS_PERMISSIONS_KEY,
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
all_permission_rules
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
environment_entry =
|
|
230
|
-
|
|
231
|
-
)
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
)
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
print(f"
|
|
263
|
-
print(f"
|
|
264
|
-
print(
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
1
|
+
"""Grant Edit/Read permissions on the current directory's .claude tree.
|
|
2
|
+
|
|
3
|
+
Run from the project root whose .claude/** you want a Claude Code session
|
|
4
|
+
(including spawned subagents) to edit without prompting. Writes idempotent
|
|
5
|
+
entries into the user-scope settings at ~/.claude/settings.json and prints
|
|
6
|
+
the changes applied. No-op when the entries already exist.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
parent_directory = str(Path(__file__).resolve().parent)
|
|
13
|
+
if parent_directory not in sys.path:
|
|
14
|
+
sys.path.insert(0, parent_directory)
|
|
15
|
+
|
|
16
|
+
from _claude_permissions_common import ( # noqa: E402
|
|
17
|
+
append_if_missing,
|
|
18
|
+
build_agent_config_deny_rules,
|
|
19
|
+
build_permission_rules,
|
|
20
|
+
ensure_dict_section,
|
|
21
|
+
ensure_list_entry,
|
|
22
|
+
exit_with_error,
|
|
23
|
+
get_current_project_path,
|
|
24
|
+
is_trust_entry_for_project,
|
|
25
|
+
is_valid_project_root,
|
|
26
|
+
load_settings,
|
|
27
|
+
remove_matching_entries_from_list,
|
|
28
|
+
save_settings,
|
|
29
|
+
)
|
|
30
|
+
from pr_loop_shared_constants.claude_permissions_constants import (
|
|
31
|
+
ALL_AGENT_CONFIG_DENY_TOOLS,
|
|
32
|
+
ALL_AGENT_CONFIG_PATH_PATTERNS,
|
|
33
|
+
ALL_PERMISSION_ALLOW_TOOLS,
|
|
34
|
+
AUTO_MODE_ENVIRONMENT_ENTRY_PREFIX,
|
|
35
|
+
AUTO_MODE_ENVIRONMENT_ENTRY_TEMPLATE,
|
|
36
|
+
get_claude_user_settings_path,
|
|
37
|
+
)
|
|
38
|
+
from pr_loop_shared_constants.claude_settings_keys_constants import (
|
|
39
|
+
CLAUDE_SETTINGS_ADDITIONAL_DIRECTORIES_KEY,
|
|
40
|
+
CLAUDE_SETTINGS_ALLOW_KEY,
|
|
41
|
+
CLAUDE_SETTINGS_AUTO_MODE_KEY,
|
|
42
|
+
CLAUDE_SETTINGS_DENY_KEY,
|
|
43
|
+
CLAUDE_SETTINGS_ENVIRONMENT_KEY,
|
|
44
|
+
CLAUDE_SETTINGS_PERMISSIONS_KEY,
|
|
45
|
+
)
|
|
46
|
+
from stale_worktree_rule_sweep import ( # noqa: E402
|
|
47
|
+
sweep_stale_worktree_rules_from_settings,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def add_rules_to_allow_list(
|
|
52
|
+
all_settings: dict[str, object], all_rules_to_add: list[str]
|
|
53
|
+
) -> int:
|
|
54
|
+
"""Add permission rules to the settings allow list.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
all_settings: The parsed settings dictionary.
|
|
58
|
+
all_rules_to_add: Permission rule strings to append.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Number of rules actually added (new entries).
|
|
62
|
+
"""
|
|
63
|
+
permissions_section = ensure_dict_section(
|
|
64
|
+
all_settings, CLAUDE_SETTINGS_PERMISSIONS_KEY
|
|
65
|
+
)
|
|
66
|
+
existing_allow_list = ensure_list_entry(
|
|
67
|
+
permissions_section, CLAUDE_SETTINGS_ALLOW_KEY
|
|
68
|
+
)
|
|
69
|
+
return sum(
|
|
70
|
+
1
|
|
71
|
+
for each_rule in all_rules_to_add
|
|
72
|
+
if append_if_missing(existing_allow_list, each_rule)
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def add_rules_to_deny_list(
|
|
77
|
+
all_settings: dict[str, object], all_rules_to_add: list[str]
|
|
78
|
+
) -> int:
|
|
79
|
+
"""Add permission rules to the settings deny list.
|
|
80
|
+
|
|
81
|
+
Deny rules take precedence over allow rules in Claude Code's permission
|
|
82
|
+
matching, so writing agent-config paths into the deny list forces a
|
|
83
|
+
per-edit user approval even when a broader allow rule would cover them.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
all_settings: The parsed settings dictionary.
|
|
87
|
+
all_rules_to_add: Permission rule strings to append.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Number of rules actually added (new entries).
|
|
91
|
+
"""
|
|
92
|
+
permissions_section = ensure_dict_section(
|
|
93
|
+
all_settings, CLAUDE_SETTINGS_PERMISSIONS_KEY
|
|
94
|
+
)
|
|
95
|
+
existing_deny_list = ensure_list_entry(
|
|
96
|
+
permissions_section, CLAUDE_SETTINGS_DENY_KEY
|
|
97
|
+
)
|
|
98
|
+
return sum(
|
|
99
|
+
1
|
|
100
|
+
for each_rule in all_rules_to_add
|
|
101
|
+
if append_if_missing(existing_deny_list, each_rule)
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def add_directory_to_additional_directories(
|
|
106
|
+
all_settings: dict[str, object], directory_path: str
|
|
107
|
+
) -> int:
|
|
108
|
+
"""Add a project path to the additionalDirectories allow list.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
all_settings: The parsed settings dictionary.
|
|
112
|
+
directory_path: The project directory path to add.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
1 when the entry was added, 0 when it already existed.
|
|
116
|
+
"""
|
|
117
|
+
permissions_section = ensure_dict_section(
|
|
118
|
+
all_settings, CLAUDE_SETTINGS_PERMISSIONS_KEY
|
|
119
|
+
)
|
|
120
|
+
existing_directories = ensure_list_entry(
|
|
121
|
+
permissions_section, CLAUDE_SETTINGS_ADDITIONAL_DIRECTORIES_KEY
|
|
122
|
+
)
|
|
123
|
+
if append_if_missing(existing_directories, directory_path):
|
|
124
|
+
return 1
|
|
125
|
+
return 0
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def add_auto_mode_environment_entry(
|
|
129
|
+
all_settings: dict[str, object], entry_text: str
|
|
130
|
+
) -> int:
|
|
131
|
+
"""Add an auto-mode environment entry for the project.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
all_settings: The parsed settings dictionary.
|
|
135
|
+
entry_text: The environment entry text to add.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
1 when the entry was added, 0 when it already existed.
|
|
139
|
+
"""
|
|
140
|
+
auto_mode_section = ensure_dict_section(
|
|
141
|
+
all_settings, CLAUDE_SETTINGS_AUTO_MODE_KEY
|
|
142
|
+
)
|
|
143
|
+
existing_environment = ensure_list_entry(
|
|
144
|
+
auto_mode_section, CLAUDE_SETTINGS_ENVIRONMENT_KEY
|
|
145
|
+
)
|
|
146
|
+
if append_if_missing(existing_environment, entry_text):
|
|
147
|
+
return 1
|
|
148
|
+
return 0
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _is_stale_trust_entry(
|
|
152
|
+
candidate_entry: object,
|
|
153
|
+
project_path: str,
|
|
154
|
+
prefix: str,
|
|
155
|
+
protected_entry: str | None,
|
|
156
|
+
) -> bool:
|
|
157
|
+
if not is_trust_entry_for_project(candidate_entry, project_path, prefix):
|
|
158
|
+
return False
|
|
159
|
+
if protected_entry is not None and candidate_entry == protected_entry:
|
|
160
|
+
return False
|
|
161
|
+
return True
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def purge_stale_trust_entries(
|
|
165
|
+
all_settings: dict[str, object],
|
|
166
|
+
project_path: str,
|
|
167
|
+
prefix: str,
|
|
168
|
+
protected_entry: str | None = None,
|
|
169
|
+
) -> int:
|
|
170
|
+
"""Remove every prior trust entry for the project from autoMode.environment.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
all_settings: The parsed settings dictionary.
|
|
174
|
+
project_path: The POSIX-style project root path.
|
|
175
|
+
prefix: The literal prefix that marks a trust entry.
|
|
176
|
+
protected_entry: When byte-equal to a candidate, prevents its removal.
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
Number of stale entries removed.
|
|
180
|
+
"""
|
|
181
|
+
auto_mode_section = all_settings.get(CLAUDE_SETTINGS_AUTO_MODE_KEY)
|
|
182
|
+
if not isinstance(auto_mode_section, dict):
|
|
183
|
+
return 0
|
|
184
|
+
existing_environment = auto_mode_section.get(CLAUDE_SETTINGS_ENVIRONMENT_KEY)
|
|
185
|
+
if not isinstance(existing_environment, list):
|
|
186
|
+
return 0
|
|
187
|
+
return remove_matching_entries_from_list(
|
|
188
|
+
existing_environment,
|
|
189
|
+
lambda candidate_entry: _is_stale_trust_entry(
|
|
190
|
+
candidate_entry, project_path, prefix, protected_entry
|
|
191
|
+
),
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _resolve_project_path_or_exit() -> str:
|
|
196
|
+
project_root_path = Path.cwd()
|
|
197
|
+
if not is_valid_project_root(project_root_path):
|
|
198
|
+
print(
|
|
199
|
+
f"ERROR: cwd {project_root_path} is not a project root "
|
|
200
|
+
f"(no .git or .claude). Run from a project root.",
|
|
201
|
+
file=sys.stderr,
|
|
202
|
+
)
|
|
203
|
+
raise SystemExit(1)
|
|
204
|
+
return get_current_project_path()
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _build_grant_rule_sets(
|
|
208
|
+
project_path: str,
|
|
209
|
+
) -> tuple[list[str], list[str], str]:
|
|
210
|
+
all_permission_rules = build_permission_rules(
|
|
211
|
+
project_path, ALL_PERMISSION_ALLOW_TOOLS
|
|
212
|
+
)
|
|
213
|
+
all_agent_config_deny_rules = build_agent_config_deny_rules(
|
|
214
|
+
project_path,
|
|
215
|
+
ALL_AGENT_CONFIG_DENY_TOOLS,
|
|
216
|
+
ALL_AGENT_CONFIG_PATH_PATTERNS,
|
|
217
|
+
)
|
|
218
|
+
environment_entry = AUTO_MODE_ENVIRONMENT_ENTRY_TEMPLATE.format(
|
|
219
|
+
project_path=project_path
|
|
220
|
+
)
|
|
221
|
+
return (all_permission_rules, all_agent_config_deny_rules, environment_entry)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _apply_grant(
|
|
225
|
+
all_settings: dict[str, object],
|
|
226
|
+
project_path: str,
|
|
227
|
+
all_grant_rule_sets: tuple[list[str], list[str], str],
|
|
228
|
+
) -> tuple[int, int, int, int, int]:
|
|
229
|
+
all_permission_rules, all_agent_config_deny_rules, environment_entry = (
|
|
230
|
+
all_grant_rule_sets
|
|
231
|
+
)
|
|
232
|
+
allow_added = add_rules_to_allow_list(all_settings, all_permission_rules)
|
|
233
|
+
deny_added = add_rules_to_deny_list(all_settings, all_agent_config_deny_rules)
|
|
234
|
+
dirs_added = add_directory_to_additional_directories(all_settings, project_path)
|
|
235
|
+
purged = purge_stale_trust_entries(
|
|
236
|
+
all_settings,
|
|
237
|
+
project_path,
|
|
238
|
+
AUTO_MODE_ENVIRONMENT_ENTRY_PREFIX,
|
|
239
|
+
protected_entry=environment_entry,
|
|
240
|
+
)
|
|
241
|
+
env_added = add_auto_mode_environment_entry(all_settings, environment_entry)
|
|
242
|
+
return (allow_added, deny_added, dirs_added, purged, env_added)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _print_no_change_notice(
|
|
246
|
+
project_path: str, claude_user_settings_path: Path
|
|
247
|
+
) -> None:
|
|
248
|
+
print(f"Project path: {project_path}")
|
|
249
|
+
print(f"Settings file: {claude_user_settings_path}")
|
|
250
|
+
print("No changes needed; settings file left untouched.")
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _print_grant_summary(
|
|
254
|
+
project_path: str,
|
|
255
|
+
claude_user_settings_path: Path,
|
|
256
|
+
all_grant_rule_sets: tuple[list[str], list[str], str],
|
|
257
|
+
all_change_counts: tuple[int, int, int, int, int],
|
|
258
|
+
) -> None:
|
|
259
|
+
all_permission_rules, all_agent_config_deny_rules, _ = all_grant_rule_sets
|
|
260
|
+
allow_added, deny_added, dirs_added, purged, env_added = all_change_counts
|
|
261
|
+
print(f"Project path: {project_path}")
|
|
262
|
+
print(f"Settings file: {claude_user_settings_path}")
|
|
263
|
+
print(f"Allow rules added: {allow_added} of {len(all_permission_rules)}")
|
|
264
|
+
print(f"Deny rules added: {deny_added} of {len(all_agent_config_deny_rules)}")
|
|
265
|
+
print(f"Additional directories added: {dirs_added}")
|
|
266
|
+
if purged > 0:
|
|
267
|
+
print(f"Stale auto-mode environment entries purged: {purged}")
|
|
268
|
+
print(f"Auto-mode environment entries added: {env_added}")
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def grant_permissions_for_current_directory() -> None:
|
|
272
|
+
"""Grant Edit/Read permissions for the current project directory.
|
|
273
|
+
|
|
274
|
+
Builds allow and agent-config deny rules from config, writes them to the
|
|
275
|
+
user settings, and prints the counts applied. A fresh grant reports::
|
|
276
|
+
|
|
277
|
+
Allow rules added: 2 of 2
|
|
278
|
+
Deny rules added: 14 of 14
|
|
279
|
+
Additional directories added: 1
|
|
280
|
+
Auto-mode environment entries added: 1
|
|
281
|
+
|
|
282
|
+
Raises:
|
|
283
|
+
SystemExit: When the current directory is not a valid project root.
|
|
284
|
+
ValueError: Propagated from get_current_project_path() when the path
|
|
285
|
+
contains glob metacharacters.
|
|
286
|
+
"""
|
|
287
|
+
claude_user_settings_path: Path = get_claude_user_settings_path()
|
|
288
|
+
project_path = _resolve_project_path_or_exit()
|
|
289
|
+
grant_rule_sets = _build_grant_rule_sets(project_path)
|
|
290
|
+
settings = load_settings(claude_user_settings_path)
|
|
291
|
+
worktree_sweep_removed_count = sweep_stale_worktree_rules_from_settings(settings)
|
|
292
|
+
change_counts = _apply_grant(settings, project_path, grant_rule_sets)
|
|
293
|
+
if sum(change_counts) + worktree_sweep_removed_count == 0:
|
|
294
|
+
_print_no_change_notice(project_path, claude_user_settings_path)
|
|
295
|
+
return
|
|
296
|
+
save_settings(claude_user_settings_path, settings)
|
|
297
|
+
_print_grant_summary(
|
|
298
|
+
project_path, claude_user_settings_path, grant_rule_sets, change_counts
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
if __name__ == "__main__":
|
|
303
|
+
try:
|
|
304
|
+
grant_permissions_for_current_directory()
|
|
305
|
+
except ValueError as path_error:
|
|
306
|
+
exit_with_error(str(path_error))
|
|
@@ -8,6 +8,7 @@ Named constants for every script in `_shared/pr-loop/scripts/`. Each module owns
|
|
|
8
8
|
|---|---|
|
|
9
9
|
| `claude_permissions_constants.py` | `grant_project_claude_permissions.py` and `revoke_project_claude_permissions.py` — permission rule strings and settings.json keys |
|
|
10
10
|
| `claude_settings_keys_constants.py` | Top-level `~/.claude/settings.json` key names used across the permission helpers |
|
|
11
|
+
| `stale_worktree_rule_sweep_constants.py` | `stale_worktree_rule_sweep.py` — the worktrees subdirectory name, the worktree path segment count, the rule-string delimiters, the `.claude` home path marker, and `get_claude_worktrees_root` |
|
|
11
12
|
| `code_rules_gate_constants.py` | `code_rules_gate.py` — file extensions, git diff subcommands, test filename patterns |
|
|
12
13
|
| `inline_duplicate_body_span_constants.py` | `code_rules_gate.py` — regex and capture-group indices for the same-file inline-duplicate message, which carries both the helper and the enclosing span the gate reconstructs the union from |
|
|
13
14
|
| `fix_hookspath_constants.py` | `fix_hookspath.py` — verification suffix and related strings |
|
|
@@ -20,9 +20,9 @@ __all__ = (
|
|
|
20
20
|
)
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
ALL_PERMISSION_ALLOW_TOOLS: tuple[str, ...] = ("Edit", "
|
|
23
|
+
ALL_PERMISSION_ALLOW_TOOLS: tuple[str, ...] = ("Edit", "Read")
|
|
24
24
|
|
|
25
|
-
ALL_AGENT_CONFIG_DENY_TOOLS: tuple[str, ...] = ("Edit", "
|
|
25
|
+
ALL_AGENT_CONFIG_DENY_TOOLS: tuple[str, ...] = ("Edit", "Read")
|
|
26
26
|
|
|
27
27
|
ALL_AGENT_CONFIG_PATH_PATTERNS: tuple[str, ...] = (
|
|
28
28
|
"settings*.json",
|
|
@@ -70,7 +70,7 @@ _AGENT_CONFIG_PATTERN_PHRASE: str = _build_agent_config_pattern_phrase(
|
|
|
70
70
|
|
|
71
71
|
AUTO_MODE_ENVIRONMENT_ENTRY_TEMPLATE: str = (
|
|
72
72
|
f"Trusted local workspace: Files under {{project_path}}/.claude/** inherit "
|
|
73
|
-
f"the workspace's trust for Edit
|
|
73
|
+
f"the workspace's trust for Edit and Read operations EXCEPT "
|
|
74
74
|
f"for agent-config files: {_AGENT_CONFIG_PATTERN_PHRASE}. Edits to those "
|
|
75
75
|
f"agent-config files always require explicit per-edit user approval."
|
|
76
76
|
)
|