claude-dev-env 1.41.0 → 1.43.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 +8 -0
- package/_shared/pr-loop/scripts/_claude_permissions_common.py +232 -8
- package/_shared/pr-loop/scripts/code_rules_gate.py +293 -8
- package/_shared/pr-loop/scripts/fix_hookspath.py +96 -5
- package/_shared/pr-loop/scripts/grant_project_claude_permissions.py +124 -20
- package/_shared/pr-loop/scripts/post_audit_thread.py +4 -4
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/claude_permissions_constants.py +90 -0
- package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/claude_settings_keys_constants.py +2 -0
- package/_shared/pr-loop/scripts/preflight.py +13 -31
- package/_shared/pr-loop/scripts/reviews_disabled.py +2 -16
- package/_shared/pr-loop/scripts/revoke_project_claude_permissions.py +76 -33
- package/_shared/pr-loop/scripts/tests/conftest.py +1 -51
- package/_shared/pr-loop/scripts/tests/test_agent_config_carveout.py +385 -0
- package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +4 -2
- package/_shared/pr-loop/scripts/tests/test_claude_permissions_constants.py +37 -2
- package/_shared/pr-loop/scripts/tests/test_claude_settings_keys_constants.py +4 -2
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate_constants.py +4 -2
- package/_shared/pr-loop/scripts/tests/test_fix_hookspath_constants.py +6 -2
- package/_shared/pr-loop/scripts/tests/test_grant_project_claude_permissions.py +2 -2
- package/_shared/pr-loop/scripts/tests/test_post_audit_thread.py +1 -2
- package/_shared/pr-loop/scripts/tests/test_post_audit_thread_constants.py +4 -2
- package/_shared/pr-loop/scripts/tests/test_preflight.py +17 -52
- package/_shared/pr-loop/scripts/tests/test_preflight_constants.py +6 -2
- package/_shared/pr-loop/scripts/tests/test_revoke_project_claude_permissions.py +5 -3
- package/agents/pr-description-writer.md +50 -140
- package/docs/PR_DESCRIPTION_GUIDE.md +101 -102
- package/hooks/_gh_pr_author_swap_utils.py +1 -1
- package/hooks/blocking/bot_mention_comment_blocker.py +4 -10
- package/hooks/blocking/code_rules_enforcer.py +217 -99
- package/hooks/blocking/code_rules_path_utils.py +8 -1
- package/hooks/blocking/destructive_command_blocker.py +1 -1
- package/hooks/blocking/es_exe_path_rewriter.py +7 -13
- package/hooks/blocking/gh_body_arg_blocker.py +6 -1
- package/hooks/blocking/gh_pr_author_enforcer.py +5 -5
- package/hooks/blocking/gh_pr_author_restore.py +5 -5
- package/hooks/blocking/hedging_language_blocker.py +4 -10
- package/hooks/blocking/md_path_exemptions.py +205 -0
- package/hooks/blocking/md_to_html_blocker.py +48 -20
- package/hooks/blocking/pr_converge_bugteam_enforcer.py +5 -11
- package/hooks/blocking/pr_description_enforcer.py +626 -41
- package/hooks/blocking/question_to_user_enforcer.py +4 -10
- package/hooks/blocking/state_description_blocker.py +6 -12
- package/hooks/blocking/tdd_enforcer.py +1 -1
- package/hooks/blocking/test_bot_mention_comment_blocker.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer.py +3 -3
- package/hooks/blocking/test_code_rules_enforcer_any_exempt_files.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +0 -2
- package/hooks/blocking/test_code_rules_enforcer_comment_string_awareness.py +184 -0
- package/hooks/blocking/test_code_rules_enforcer_type_checking_scope.py +82 -0
- package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +29 -29
- package/hooks/blocking/test_gh_body_arg_blocker.py +7 -8
- package/hooks/blocking/test_gh_pr_author_enforcer.py +1 -1
- package/hooks/blocking/test_gh_pr_author_restore.py +1 -1
- package/hooks/blocking/test_hedging_language_blocker.py +2 -2
- package/hooks/blocking/test_md_to_html_blocker.py +463 -8
- package/hooks/blocking/test_pr_converge_bugteam_enforcer.py +1 -1
- package/hooks/blocking/test_pr_description_enforcer.py +1210 -13
- package/hooks/blocking/test_question_to_user_enforcer.py +1 -1
- package/hooks/blocking/windows_rmtree_blocker.py +5 -11
- package/hooks/diagnostic/hook_log_extractor.py +1 -1
- package/hooks/diagnostic/hook_log_init.py +1 -1
- package/hooks/diagnostic/hook_log_stop_wrapper.py +1 -1
- package/hooks/diagnostic/test_hook_log_extractor.py +1 -1
- package/hooks/diagnostic/test_hook_log_init.py +2 -2
- package/hooks/diagnostic/test_hook_log_stop_wrapper.py +1 -1
- package/hooks/git-hooks/gate_utils.py +1 -1
- package/hooks/git-hooks/pre_commit.py +1 -1
- package/hooks/git-hooks/pre_push.py +1 -1
- package/hooks/git-hooks/test_config.py +5 -5
- package/hooks/git-hooks/test_pre_push.py +6 -6
- package/hooks/{config → hooks_constants}/code_rules_enforcer_constants.py +37 -0
- package/hooks/hooks_constants/code_rules_path_utils_constants.py +28 -0
- package/hooks/hooks_constants/md_to_html_blocker_constants.py +82 -0
- package/hooks/{config → hooks_constants}/pr_converge_bugteam_enforcer_state.py +1 -1
- package/hooks/hooks_constants/pr_description_enforcer_constants.py +154 -0
- package/hooks/{config → hooks_constants}/pre_tool_use_stdin.py +1 -1
- package/hooks/{config → hooks_constants}/project_paths_reader.py +2 -2
- package/hooks/{config → hooks_constants}/test_banned_identifiers_constants.py +1 -1
- package/hooks/{config → hooks_constants}/test_dynamic_stderr_handler.py +1 -1
- package/hooks/{config → hooks_constants}/test_hardcoded_user_path_constants.py +1 -1
- package/hooks/{config → hooks_constants}/test_hook_log_extractor_constants.py +2 -2
- package/hooks/hooks_constants/test_md_to_html_blocker_constants.py +110 -0
- package/hooks/{config → hooks_constants}/test_messages.py +2 -6
- package/hooks/{config → hooks_constants}/test_path_rewriter_constants.py +1 -1
- package/hooks/hooks_constants/test_pr_description_enforcer_constants.py +292 -0
- package/hooks/{config → hooks_constants}/test_pre_tool_use_stdin.py +2 -2
- package/hooks/{config → hooks_constants}/test_project_paths_reader.py +3 -3
- package/hooks/{config → hooks_constants}/test_session_env_cleanup_constants.py +1 -1
- package/hooks/{config → hooks_constants}/test_setup_project_paths_constants.py +2 -2
- package/hooks/{config → hooks_constants}/test_unused_module_import_constants.py +1 -1
- package/hooks/lifecycle/pr_converge_bugteam_skill_tracker.py +5 -11
- package/hooks/lifecycle/test_pr_converge_bugteam_skill_tracker.py +1 -1
- package/hooks/session/gh_pr_author_session_cleanup.py +5 -6
- package/hooks/session/session_env_cleanup.py +4 -10
- package/hooks/session/test_gh_pr_author_session_cleanup.py +1 -1
- package/hooks/session/test_untracked_repo_detector.py +2 -2
- package/hooks/session/untracked_repo_detector.py +6 -12
- package/hooks/test__gh_pr_author_swap_utils.py +1 -1
- package/hooks/validators/run_all_validators.py +16 -5
- package/hooks/validators/test_output_formatter.py +46 -0
- package/hooks/workflow/doc_gist_auto_publish.py +1 -1
- package/hooks/workflow/md_to_html_companion.py +8 -15
- package/hooks/workflow/test_md_to_html_companion.py +184 -23
- package/package.json +1 -1
- package/rules/ask-user-question-required.md +1 -1
- package/rules/vault-context.md +1 -1
- package/scripts/{config → dev_env_scripts_constants}/timing.py +1 -1
- package/scripts/setup_project_paths.py +49 -11
- package/scripts/sweep_empty_dirs.py +10 -1
- package/scripts/test_setup_project_paths.py +2 -2
- package/scripts/test_sweep_empty_dirs.py +2 -6
- package/skills/_shared/pr-loop/scripts/_path_resolver.py +1 -1
- package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +1 -1
- package/skills/_shared/pr-loop/scripts/build_fix_prompt.py +1 -1
- package/skills/_shared/pr-loop/scripts/init_loop_state.py +1 -1
- package/skills/_shared/pr-loop/scripts/teardown_worktrees.py +1 -1
- package/skills/_shared/pr-loop/scripts/write_audit_outcomes.py +2 -2
- package/skills/_shared/pr-loop/scripts/write_fix_outcomes.py +2 -2
- package/skills/bugteam/PROMPTS.md +1 -1
- package/skills/bugteam/SKILL.md +1 -1
- package/skills/bugteam/reference/github-pr-reviews.md +1 -1
- package/skills/bugteam/scripts/{_claude_permissions_common.py → _bugteam_permissions_common.py} +110 -13
- package/skills/bugteam/scripts/bugteam_code_rules_gate.py +1 -13
- package/skills/bugteam/scripts/bugteam_fix_hookspath.py +1 -16
- package/skills/bugteam/scripts/bugteam_preflight.py +1 -13
- package/skills/bugteam/scripts/bugteam_scripts_constants/claude_permissions_common_constants.py +69 -0
- package/skills/bugteam/scripts/grant_project_claude_permissions.py +117 -12
- package/skills/bugteam/scripts/probe_code_rules_enforcer_check.py +1 -1
- package/skills/bugteam/scripts/reflow_skill_md.py +1 -1
- package/skills/bugteam/scripts/revoke_project_claude_permissions.py +71 -25
- package/skills/bugteam/scripts/{test__claude_permissions_common.py → test__bugteam_permissions_common.py} +4 -4
- package/skills/bugteam/scripts/test_agent_config_carveout.py +356 -0
- package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +0 -26
- package/skills/bugteam/scripts/{test_claude_permissions_common.py → test_bugteam_permissions_common.py} +3 -66
- package/skills/bugteam/scripts/test_bugteam_preflight.py +2 -27
- package/skills/bugteam/scripts/windows_safe_rmtree.py +1 -1
- package/skills/doc-gist/SKILL.md +1 -1
- package/skills/doc-gist/scripts/gist_upload.py +1 -1
- package/skills/implement/SKILL.md +66 -0
- package/skills/implement/scripts/append_note.py +133 -0
- package/skills/implement/scripts/implement_scripts_constants/__init__.py +0 -0
- package/skills/implement/scripts/implement_scripts_constants/notes_constants.py +12 -0
- package/skills/implement/scripts/test_append_note.py +191 -0
- package/skills/pr-converge/pr_converge_skill_constants/__init__.py +0 -0
- package/skills/pr-converge/{config → pr_converge_skill_constants}/constants.py +6 -1
- package/skills/pr-converge/scripts/check_bugbot_ci.py +2 -2
- package/skills/pr-converge/scripts/check_convergence.py +175 -29
- package/skills/pr-converge/scripts/check_pending_reviews.py +2 -2
- package/skills/pr-converge/scripts/fetch_copilot_reviews.py +2 -2
- package/skills/pr-converge/scripts/post_fix_reply.py +2 -2
- package/skills/pr-converge/scripts/pr_converge_scripts_constants/__init__.py +0 -0
- package/skills/pr-converge/scripts/{config → pr_converge_scripts_constants}/pr_converge_constants.py +1 -1
- package/skills/pr-converge/scripts/reflow_skill_md.py +90 -16
- package/skills/pr-converge/scripts/test_check_bugbot_ci.py +1 -1
- package/skills/pr-converge/scripts/test_check_convergence.py +324 -0
- package/skills/pr-converge/scripts/test_reflow_skill_md.py +0 -31
- package/skills/refine/SKILL.md +257 -0
- package/skills/refine/templates/implementation-notes-template.html +56 -0
- package/skills/refine/templates/plan-template.md +60 -0
- package/skills/session-log/SKILL.md +98 -233
- package/_shared/pr-loop/scripts/config/claude_permissions_constants.py +0 -36
- package/hooks/config/pr_description_enforcer_constants.py +0 -19
- package/hooks/config/test_pr_description_enforcer_constants.py +0 -82
- package/skills/bugteam/scripts/config/claude_permissions_common_constants.py +0 -20
- package/skills/bugteam/scripts/test_grant_project_claude_permissions.py +0 -55
- package/skills/bugteam/scripts/test_revoke_project_claude_permissions.py +0 -55
- package/skills/pr-converge/scripts/evict_cached_config_modules.py +0 -20
- package/skills/pr-converge/scripts/test_evict_cached_config_modules.py +0 -22
- /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/__init__.py +0 -0
- /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/code_rules_gate_constants.py +0 -0
- /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/fix_hookspath_constants.py +0 -0
- /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/post_audit_thread_constants.py +0 -0
- /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/preflight_constants.py +0 -0
- /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/reviews_disabled_constants.py +0 -0
- /package/hooks/git-hooks/{config.py → git_hooks_constants/__init__.py} +0 -0
- /package/hooks/{config → hooks_constants}/__init__.py +0 -0
- /package/hooks/{config → hooks_constants}/any_type_config.py +0 -0
- /package/hooks/{config → hooks_constants}/banned_identifiers_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/blocking_check_limits.py +0 -0
- /package/hooks/{config → hooks_constants}/bot_mention_comment_blocker_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/convergence_branch_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/doc_gist_auto_publish_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/dynamic_stderr_handler.py +0 -0
- /package/hooks/{config → hooks_constants}/gh_pr_author_swap_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/hardcoded_user_path_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/hook_log_extractor_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/html_companion_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/inline_tuple_string_magic_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/messages.py +0 -0
- /package/hooks/{config → hooks_constants}/path_rewriter_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/pr_converge_bugteam_enforcer_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/session_env_cleanup_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/setup_project_paths_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/state_description_blocker_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/stuttering_check_config.py +0 -0
- /package/hooks/{config → hooks_constants}/stuttering_import_binding_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/sys_path_insert_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/unused_module_import_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/windows_rmtree_blocker_constants.py +0 -0
- /package/{skills/_shared/pr-loop/scripts/config → hooks/lifecycle}/__init__.py +0 -0
- /package/{skills/bugteam/scripts/config → hooks/session}/__init__.py +0 -0
- /package/scripts/{config → dev_env_scripts_constants}/__init__.py +0 -0
- /package/skills/{doc-gist/scripts/config → _shared/pr-loop/scripts/skills_pr_loop_constants}/__init__.py +0 -0
- /package/skills/_shared/pr-loop/scripts/{config → skills_pr_loop_constants}/path_resolver_constants.py +0 -0
- /package/skills/{pr-converge/config → bugteam/scripts/bugteam_scripts_constants}/__init__.py +0 -0
- /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/bugteam_code_rules_gate_constants.py +0 -0
- /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/bugteam_fix_hookspath_constants.py +0 -0
- /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/bugteam_preflight_constants.py +0 -0
- /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/probe_code_rules_enforcer_check_constants.py +0 -0
- /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/reflow_skill_md_constants.py +0 -0
- /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/windows_safe_rmtree_constants.py +0 -0
- /package/skills/{pr-converge/scripts/config → doc-gist/scripts/doc_gist_scripts_constants}/__init__.py +0 -0
- /package/skills/doc-gist/scripts/{config → doc_gist_scripts_constants}/gist_upload_constants.py +0 -0
- /package/skills/pr-converge/scripts/{config → pr_converge_scripts_constants}/reflow_skill_md_constants.py +0 -0
|
@@ -9,28 +9,28 @@ the changes applied. No-op when the entries already exist.
|
|
|
9
9
|
import sys
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
|
|
12
|
-
for each_cached_module_name in [
|
|
13
|
-
each_module_key
|
|
14
|
-
for each_module_key in list(sys.modules)
|
|
15
|
-
if each_module_key == "config" or each_module_key.startswith("config.")
|
|
16
|
-
]:
|
|
17
|
-
sys.modules.pop(each_cached_module_name, None)
|
|
18
12
|
parent_directory = str(Path(__file__).resolve().parent)
|
|
19
13
|
if parent_directory not in sys.path:
|
|
20
14
|
sys.path.insert(0, parent_directory)
|
|
21
15
|
|
|
22
|
-
from
|
|
16
|
+
from _bugteam_permissions_common import ( # noqa: E402
|
|
23
17
|
append_if_missing,
|
|
18
|
+
build_agent_config_deny_rules,
|
|
24
19
|
build_permission_rules,
|
|
25
20
|
ensure_dict_section,
|
|
26
21
|
ensure_list_entry,
|
|
27
22
|
exit_with_error,
|
|
28
23
|
get_current_project_path,
|
|
24
|
+
is_trust_entry_for_project,
|
|
29
25
|
load_settings,
|
|
26
|
+
remove_matching_entries_from_list,
|
|
30
27
|
save_settings,
|
|
31
28
|
)
|
|
32
|
-
from
|
|
29
|
+
from bugteam_scripts_constants.claude_permissions_common_constants import ( # noqa: E402
|
|
30
|
+
ALL_AGENT_CONFIG_DENY_TOOLS,
|
|
31
|
+
ALL_AGENT_CONFIG_PATH_PATTERNS,
|
|
33
32
|
ALL_PERMISSION_ALLOW_TOOLS,
|
|
33
|
+
AUTO_MODE_ENVIRONMENT_ENTRY_PREFIX,
|
|
34
34
|
AUTO_MODE_ENVIRONMENT_ENTRY_TEMPLATE,
|
|
35
35
|
CLAUDE_DIRECTORY_MARKER,
|
|
36
36
|
CLAUDE_USER_SETTINGS_FILENAME,
|
|
@@ -38,6 +38,7 @@ from config.claude_permissions_common_constants import ( # noqa: E402
|
|
|
38
38
|
SETTINGS_ADDITIONAL_DIRECTORIES_KEY,
|
|
39
39
|
SETTINGS_ALLOW_KEY,
|
|
40
40
|
SETTINGS_AUTO_MODE_KEY,
|
|
41
|
+
SETTINGS_DENY_KEY,
|
|
41
42
|
SETTINGS_ENVIRONMENT_KEY,
|
|
42
43
|
SETTINGS_PERMISSIONS_KEY,
|
|
43
44
|
)
|
|
@@ -77,6 +78,31 @@ def add_rules_to_allow_list(all_settings: dict[str, object], all_rules_to_add: l
|
|
|
77
78
|
)
|
|
78
79
|
|
|
79
80
|
|
|
81
|
+
def add_rules_to_deny_list(
|
|
82
|
+
all_settings: dict[str, object], all_rules_to_add: list[str]
|
|
83
|
+
) -> int:
|
|
84
|
+
"""Add permission rules to the settings deny list.
|
|
85
|
+
|
|
86
|
+
Deny rules take precedence over allow rules in Claude Code's permission
|
|
87
|
+
matching, so writing agent-config paths into the deny list forces a
|
|
88
|
+
per-edit user approval even when a broader allow rule would cover them.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
all_settings: The parsed settings dictionary.
|
|
92
|
+
all_rules_to_add: Permission rule strings to append.
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
Number of rules actually added (new entries).
|
|
96
|
+
"""
|
|
97
|
+
permissions_section = ensure_dict_section(all_settings, SETTINGS_PERMISSIONS_KEY)
|
|
98
|
+
existing_deny_list = ensure_list_entry(permissions_section, SETTINGS_DENY_KEY)
|
|
99
|
+
return sum(
|
|
100
|
+
1
|
|
101
|
+
for each_rule in all_rules_to_add
|
|
102
|
+
if append_if_missing(existing_deny_list, each_rule)
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
80
106
|
def add_directory_to_additional_directories(
|
|
81
107
|
all_settings: dict[str, object], directory_path: str
|
|
82
108
|
) -> int:
|
|
@@ -117,11 +143,63 @@ def add_auto_mode_environment_entry(
|
|
|
117
143
|
return 0
|
|
118
144
|
|
|
119
145
|
|
|
146
|
+
def purge_stale_trust_entries(
|
|
147
|
+
all_settings: dict[str, object],
|
|
148
|
+
project_path: str,
|
|
149
|
+
prefix: str,
|
|
150
|
+
protected_entry: str | None = None,
|
|
151
|
+
) -> int:
|
|
152
|
+
"""Remove every prior trust entry for the project from autoMode.environment.
|
|
153
|
+
|
|
154
|
+
A trust entry is any string in autoMode.environment whose prefix matches
|
|
155
|
+
the trust-entry marker and that contains the project's .claude/** path.
|
|
156
|
+
Purging stale entries before adding the current template prevents
|
|
157
|
+
accumulation across template revisions. The optional protected_entry
|
|
158
|
+
survives the purge so an entry byte-identical to the one about to be
|
|
159
|
+
re-added is not removed and re-added on every invocation, preserving the
|
|
160
|
+
idempotency contract documented on grant_permissions_for_current_directory.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
all_settings: The parsed settings dictionary.
|
|
164
|
+
project_path: The POSIX-style project root path.
|
|
165
|
+
prefix: The literal prefix that marks a trust entry.
|
|
166
|
+
protected_entry: Optional entry text that, when byte-equal to a
|
|
167
|
+
candidate, prevents removal. Pass the freshly-formatted current
|
|
168
|
+
template entry from grant to preserve idempotency. Revoke passes
|
|
169
|
+
None so every matching entry is removed.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
Number of stale entries removed.
|
|
173
|
+
"""
|
|
174
|
+
auto_mode_section = all_settings.get(SETTINGS_AUTO_MODE_KEY)
|
|
175
|
+
if not isinstance(auto_mode_section, dict):
|
|
176
|
+
return 0
|
|
177
|
+
existing_environment = auto_mode_section.get(SETTINGS_ENVIRONMENT_KEY)
|
|
178
|
+
if not isinstance(existing_environment, list):
|
|
179
|
+
return 0
|
|
180
|
+
|
|
181
|
+
def _should_purge_candidate(candidate_entry: object) -> bool:
|
|
182
|
+
if not is_trust_entry_for_project(candidate_entry, project_path, prefix):
|
|
183
|
+
return False
|
|
184
|
+
if protected_entry is not None and candidate_entry == protected_entry:
|
|
185
|
+
return False
|
|
186
|
+
return True
|
|
187
|
+
|
|
188
|
+
return remove_matching_entries_from_list(
|
|
189
|
+
existing_environment,
|
|
190
|
+
_should_purge_candidate,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
120
194
|
def grant_permissions_for_current_directory() -> None:
|
|
121
195
|
"""Grant Edit/Write/Read permissions for the current project directory.
|
|
122
196
|
|
|
123
197
|
Reads the current project path, constructs permission rules from config
|
|
124
|
-
constants, and writes them to ~/.claude/settings.json atomically.
|
|
198
|
+
constants, and writes them to ~/.claude/settings.json atomically. Adds
|
|
199
|
+
deny rules for agent-config paths so edits to settings, hooks, commands,
|
|
200
|
+
agents, skills, mcp.json, and CLAUDE.md still require per-edit user
|
|
201
|
+
approval. Purges any prior trust entries for this project before writing
|
|
202
|
+
the current template to prevent accumulation across template revisions.
|
|
125
203
|
|
|
126
204
|
Raises:
|
|
127
205
|
SystemExit(1): When the current directory is not a valid project root.
|
|
@@ -141,19 +219,37 @@ def grant_permissions_for_current_directory() -> None:
|
|
|
141
219
|
raise SystemExit(1)
|
|
142
220
|
project_path = get_current_project_path()
|
|
143
221
|
permission_rules = build_permission_rules(project_path, ALL_PERMISSION_ALLOW_TOOLS)
|
|
222
|
+
all_agent_config_deny_rules = build_agent_config_deny_rules(
|
|
223
|
+
project_path,
|
|
224
|
+
ALL_AGENT_CONFIG_DENY_TOOLS,
|
|
225
|
+
ALL_AGENT_CONFIG_PATH_PATTERNS,
|
|
226
|
+
)
|
|
144
227
|
environment_entry = AUTO_MODE_ENVIRONMENT_ENTRY_TEMPLATE.format(
|
|
145
228
|
project_path=project_path
|
|
146
229
|
)
|
|
147
230
|
settings = load_settings(claude_user_settings_path)
|
|
148
|
-
|
|
231
|
+
allow_rules_added_count = add_rules_to_allow_list(settings, permission_rules)
|
|
232
|
+
deny_rules_added_count = add_rules_to_deny_list(
|
|
233
|
+
settings, all_agent_config_deny_rules
|
|
234
|
+
)
|
|
149
235
|
directories_added_count = add_directory_to_additional_directories(
|
|
150
236
|
settings, project_path
|
|
151
237
|
)
|
|
238
|
+
stale_trust_entries_purged_count = purge_stale_trust_entries(
|
|
239
|
+
settings,
|
|
240
|
+
project_path,
|
|
241
|
+
AUTO_MODE_ENVIRONMENT_ENTRY_PREFIX,
|
|
242
|
+
protected_entry=environment_entry,
|
|
243
|
+
)
|
|
152
244
|
environment_entries_added_count = add_auto_mode_environment_entry(
|
|
153
245
|
settings, environment_entry
|
|
154
246
|
)
|
|
155
247
|
total_changes_count = (
|
|
156
|
-
|
|
248
|
+
allow_rules_added_count
|
|
249
|
+
+ deny_rules_added_count
|
|
250
|
+
+ directories_added_count
|
|
251
|
+
+ stale_trust_entries_purged_count
|
|
252
|
+
+ environment_entries_added_count
|
|
157
253
|
)
|
|
158
254
|
if total_changes_count == 0:
|
|
159
255
|
print(f"Project path: {project_path}")
|
|
@@ -163,8 +259,17 @@ def grant_permissions_for_current_directory() -> None:
|
|
|
163
259
|
save_settings(claude_user_settings_path, settings)
|
|
164
260
|
print(f"Project path: {project_path}")
|
|
165
261
|
print(f"Settings file: {claude_user_settings_path}")
|
|
166
|
-
print(f"Allow rules added: {
|
|
262
|
+
print(f"Allow rules added: {allow_rules_added_count} of {len(permission_rules)}")
|
|
263
|
+
print(
|
|
264
|
+
f"Deny rules added: {deny_rules_added_count} of "
|
|
265
|
+
f"{len(all_agent_config_deny_rules)}"
|
|
266
|
+
)
|
|
167
267
|
print(f"Additional directories added: {directories_added_count}")
|
|
268
|
+
if stale_trust_entries_purged_count > 0:
|
|
269
|
+
print(
|
|
270
|
+
f"Stale auto-mode environment entries purged: "
|
|
271
|
+
f"{stale_trust_entries_purged_count}"
|
|
272
|
+
)
|
|
168
273
|
print(f"Auto-mode environment entries added: {environment_entries_added_count}")
|
|
169
274
|
|
|
170
275
|
|
|
@@ -19,7 +19,7 @@ import sys
|
|
|
19
19
|
from pathlib import Path
|
|
20
20
|
from types import ModuleType
|
|
21
21
|
|
|
22
|
-
from
|
|
22
|
+
from bugteam_scripts_constants.probe_code_rules_enforcer_check_constants import (
|
|
23
23
|
DEFAULT_REPORTED_PATH,
|
|
24
24
|
ENFORCER_MODULE_NAME,
|
|
25
25
|
ENFORCER_RELATIVE_PATH,
|
|
@@ -18,7 +18,7 @@ from __future__ import annotations
|
|
|
18
18
|
import re
|
|
19
19
|
import textwrap
|
|
20
20
|
|
|
21
|
-
from
|
|
21
|
+
from bugteam_scripts_constants.reflow_skill_md_constants import (
|
|
22
22
|
BASH_CONTINUATION_MARKER_WIDTH,
|
|
23
23
|
BULLET_LIST_ITEM_PATTERN as BULLET_RE,
|
|
24
24
|
MAXIMUM_LINE_WIDTH as MAX_WIDTH,
|
|
@@ -10,33 +10,33 @@ autoMode sections so repeated grant/revoke cycles leave no dead structure.
|
|
|
10
10
|
import sys
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
|
|
13
|
-
for each_cached_module_name in [
|
|
14
|
-
each_module_key
|
|
15
|
-
for each_module_key in list(sys.modules)
|
|
16
|
-
if each_module_key == "config" or each_module_key.startswith("config.")
|
|
17
|
-
]:
|
|
18
|
-
sys.modules.pop(each_cached_module_name, None)
|
|
19
13
|
parent_directory = str(Path(__file__).resolve().parent)
|
|
20
14
|
if parent_directory not in sys.path:
|
|
21
15
|
sys.path.insert(0, parent_directory)
|
|
22
16
|
|
|
23
|
-
from
|
|
17
|
+
from _bugteam_permissions_common import ( # noqa: E402
|
|
18
|
+
build_agent_config_deny_rules,
|
|
24
19
|
build_permission_rules,
|
|
25
20
|
exit_with_error,
|
|
26
21
|
get_current_project_path,
|
|
22
|
+
is_trust_entry_for_project,
|
|
27
23
|
load_settings,
|
|
28
24
|
prune_empty_list_then_empty_section,
|
|
25
|
+
remove_matching_entries_from_list,
|
|
29
26
|
save_settings,
|
|
30
27
|
)
|
|
31
|
-
from
|
|
28
|
+
from bugteam_scripts_constants.claude_permissions_common_constants import ( # noqa: E402
|
|
29
|
+
ALL_AGENT_CONFIG_DENY_TOOLS,
|
|
30
|
+
ALL_AGENT_CONFIG_PATH_PATTERNS,
|
|
32
31
|
ALL_PERMISSION_ALLOW_TOOLS,
|
|
33
|
-
|
|
32
|
+
AUTO_MODE_ENVIRONMENT_ENTRY_PREFIX,
|
|
34
33
|
CLAUDE_DIRECTORY_MARKER,
|
|
35
34
|
CLAUDE_USER_SETTINGS_FILENAME,
|
|
36
35
|
GIT_DIRECTORY_MARKER,
|
|
37
36
|
SETTINGS_ADDITIONAL_DIRECTORIES_KEY,
|
|
38
37
|
SETTINGS_ALLOW_KEY,
|
|
39
38
|
SETTINGS_AUTO_MODE_KEY,
|
|
39
|
+
SETTINGS_DENY_KEY,
|
|
40
40
|
SETTINGS_ENVIRONMENT_KEY,
|
|
41
41
|
SETTINGS_PERMISSIONS_KEY,
|
|
42
42
|
)
|
|
@@ -97,6 +97,27 @@ def remove_rules_from_allow_list(
|
|
|
97
97
|
return remove_values_from_list(existing_allow_list, set(all_rules_to_remove))
|
|
98
98
|
|
|
99
99
|
|
|
100
|
+
def remove_rules_from_deny_list(
|
|
101
|
+
all_settings: dict[str, object], all_rules_to_remove: list[str]
|
|
102
|
+
) -> int:
|
|
103
|
+
"""Remove matching permission rules from the settings deny list.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
all_settings: The parsed settings dictionary.
|
|
107
|
+
all_rules_to_remove: Permission rule strings to remove.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Number of rules removed.
|
|
111
|
+
"""
|
|
112
|
+
permissions_section = all_settings.get(SETTINGS_PERMISSIONS_KEY)
|
|
113
|
+
if not isinstance(permissions_section, dict):
|
|
114
|
+
return 0
|
|
115
|
+
existing_deny_list = permissions_section.get(SETTINGS_DENY_KEY)
|
|
116
|
+
if not isinstance(existing_deny_list, list):
|
|
117
|
+
return 0
|
|
118
|
+
return remove_values_from_list(existing_deny_list, set(all_rules_to_remove))
|
|
119
|
+
|
|
120
|
+
|
|
100
121
|
def remove_directory_from_additional_directories(
|
|
101
122
|
all_settings: dict[str, object], directory_path: str
|
|
102
123
|
) -> int:
|
|
@@ -118,17 +139,23 @@ def remove_directory_from_additional_directories(
|
|
|
118
139
|
return remove_values_from_list(existing_directories, {directory_path})
|
|
119
140
|
|
|
120
141
|
|
|
121
|
-
def
|
|
122
|
-
all_settings: dict[str, object],
|
|
142
|
+
def remove_trust_entries_for_project(
|
|
143
|
+
all_settings: dict[str, object], project_path: str, prefix: str
|
|
123
144
|
) -> int:
|
|
124
|
-
"""Remove
|
|
145
|
+
"""Remove every trust entry for the project from autoMode.environment.
|
|
146
|
+
|
|
147
|
+
Matches any string in autoMode.environment whose prefix matches the
|
|
148
|
+
trust-entry marker and that contains the project's .claude/** path.
|
|
149
|
+
The match is wording-agnostic so prior template revisions are removed
|
|
150
|
+
cleanly even when the current template differs.
|
|
125
151
|
|
|
126
152
|
Args:
|
|
127
153
|
all_settings: The parsed settings dictionary.
|
|
128
|
-
|
|
154
|
+
project_path: The POSIX-style project root path.
|
|
155
|
+
prefix: The literal prefix that marks a trust entry.
|
|
129
156
|
|
|
130
157
|
Returns:
|
|
131
|
-
|
|
158
|
+
Number of entries removed.
|
|
132
159
|
"""
|
|
133
160
|
auto_mode_section = all_settings.get(SETTINGS_AUTO_MODE_KEY)
|
|
134
161
|
if not isinstance(auto_mode_section, dict):
|
|
@@ -136,7 +163,12 @@ def remove_auto_mode_environment_entry(
|
|
|
136
163
|
existing_environment = auto_mode_section.get(SETTINGS_ENVIRONMENT_KEY)
|
|
137
164
|
if not isinstance(existing_environment, list):
|
|
138
165
|
return 0
|
|
139
|
-
return
|
|
166
|
+
return remove_matching_entries_from_list(
|
|
167
|
+
existing_environment,
|
|
168
|
+
lambda candidate_entry: is_trust_entry_for_project(
|
|
169
|
+
candidate_entry, project_path, prefix
|
|
170
|
+
),
|
|
171
|
+
)
|
|
140
172
|
|
|
141
173
|
|
|
142
174
|
def prune_settings_after_revoke(all_settings: dict[str, object]) -> None:
|
|
@@ -148,6 +180,9 @@ def prune_settings_after_revoke(all_settings: dict[str, object]) -> None:
|
|
|
148
180
|
prune_empty_list_then_empty_section(
|
|
149
181
|
all_settings, SETTINGS_PERMISSIONS_KEY, SETTINGS_ALLOW_KEY
|
|
150
182
|
)
|
|
183
|
+
prune_empty_list_then_empty_section(
|
|
184
|
+
all_settings, SETTINGS_PERMISSIONS_KEY, SETTINGS_DENY_KEY
|
|
185
|
+
)
|
|
151
186
|
prune_empty_list_then_empty_section(
|
|
152
187
|
all_settings, SETTINGS_PERMISSIONS_KEY, SETTINGS_ADDITIONAL_DIRECTORIES_KEY
|
|
153
188
|
)
|
|
@@ -159,9 +194,10 @@ def prune_settings_after_revoke(all_settings: dict[str, object]) -> None:
|
|
|
159
194
|
def revoke_permissions_for_current_directory() -> None:
|
|
160
195
|
"""Revoke Edit/Write/Read permissions for the current project directory.
|
|
161
196
|
|
|
162
|
-
Reads the current project path, constructs
|
|
163
|
-
|
|
164
|
-
|
|
197
|
+
Reads the current project path, constructs the matching allow and deny
|
|
198
|
+
permission rules, removes them from ~/.claude/settings.json, removes
|
|
199
|
+
every trust entry for the project from autoMode.environment, and prunes
|
|
200
|
+
any newly empty sections.
|
|
165
201
|
|
|
166
202
|
Raises:
|
|
167
203
|
SystemExit(1): When the current directory is not a valid project root.
|
|
@@ -181,19 +217,25 @@ def revoke_permissions_for_current_directory() -> None:
|
|
|
181
217
|
raise SystemExit(1)
|
|
182
218
|
project_path = get_current_project_path()
|
|
183
219
|
permission_rules = build_permission_rules(project_path, ALL_PERMISSION_ALLOW_TOOLS)
|
|
184
|
-
|
|
185
|
-
project_path
|
|
220
|
+
all_agent_config_deny_rules = build_agent_config_deny_rules(
|
|
221
|
+
project_path,
|
|
222
|
+
ALL_AGENT_CONFIG_DENY_TOOLS,
|
|
223
|
+
ALL_AGENT_CONFIG_PATH_PATTERNS,
|
|
186
224
|
)
|
|
187
225
|
settings = load_settings(claude_user_settings_path)
|
|
188
|
-
|
|
226
|
+
allow_rules_removed_count = remove_rules_from_allow_list(settings, permission_rules)
|
|
227
|
+
deny_rules_removed_count = remove_rules_from_deny_list(
|
|
228
|
+
settings, all_agent_config_deny_rules
|
|
229
|
+
)
|
|
189
230
|
directories_removed_count = remove_directory_from_additional_directories(
|
|
190
231
|
settings, project_path
|
|
191
232
|
)
|
|
192
|
-
environment_entries_removed_count =
|
|
193
|
-
settings,
|
|
233
|
+
environment_entries_removed_count = remove_trust_entries_for_project(
|
|
234
|
+
settings, project_path, AUTO_MODE_ENVIRONMENT_ENTRY_PREFIX
|
|
194
235
|
)
|
|
195
236
|
total_changes_count = (
|
|
196
|
-
|
|
237
|
+
allow_rules_removed_count
|
|
238
|
+
+ deny_rules_removed_count
|
|
197
239
|
+ directories_removed_count
|
|
198
240
|
+ environment_entries_removed_count
|
|
199
241
|
)
|
|
@@ -206,7 +248,11 @@ def revoke_permissions_for_current_directory() -> None:
|
|
|
206
248
|
save_settings(claude_user_settings_path, settings)
|
|
207
249
|
print(f"Project path: {project_path}")
|
|
208
250
|
print(f"Settings file: {claude_user_settings_path}")
|
|
209
|
-
print(f"Allow rules removed: {
|
|
251
|
+
print(f"Allow rules removed: {allow_rules_removed_count} of {len(permission_rules)}")
|
|
252
|
+
print(
|
|
253
|
+
f"Deny rules removed: {deny_rules_removed_count} of "
|
|
254
|
+
f"{len(all_agent_config_deny_rules)}"
|
|
255
|
+
)
|
|
210
256
|
print(f"Additional directories removed: {directories_removed_count}")
|
|
211
257
|
print(
|
|
212
258
|
f"Auto-mode environment entries removed: {environment_entries_removed_count}"
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
"""TDD-pair tests for the underscore-prefixed
|
|
1
|
+
"""TDD-pair tests for the underscore-prefixed _bugteam_permissions_common module.
|
|
2
2
|
|
|
3
3
|
The TDD enforcer matches a production filename ``X.py`` to ``test_X.py``;
|
|
4
|
-
``
|
|
4
|
+
``_bugteam_permissions_common.py`` carries a leading underscore that the
|
|
5
5
|
enforcer treats as part of the name. This file's tests are the canonical
|
|
6
6
|
match. The broader behavioral suite continues to live alongside, in
|
|
7
|
-
``
|
|
7
|
+
``test_bugteam_permissions_common.py``.
|
|
8
8
|
"""
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
@@ -18,7 +18,7 @@ _script_directory = str(Path(__file__).resolve().parent)
|
|
|
18
18
|
if _script_directory not in sys.path:
|
|
19
19
|
sys.path.insert(0, _script_directory)
|
|
20
20
|
|
|
21
|
-
import
|
|
21
|
+
import _bugteam_permissions_common as common_module
|
|
22
22
|
import grant_project_claude_permissions as grant_module
|
|
23
23
|
import revoke_project_claude_permissions as revoke_module
|
|
24
24
|
|