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.
Files changed (214) hide show
  1. package/CLAUDE.md +8 -0
  2. package/_shared/pr-loop/scripts/_claude_permissions_common.py +232 -8
  3. package/_shared/pr-loop/scripts/code_rules_gate.py +293 -8
  4. package/_shared/pr-loop/scripts/fix_hookspath.py +96 -5
  5. package/_shared/pr-loop/scripts/grant_project_claude_permissions.py +124 -20
  6. package/_shared/pr-loop/scripts/post_audit_thread.py +4 -4
  7. package/_shared/pr-loop/scripts/pr_loop_shared_constants/claude_permissions_constants.py +90 -0
  8. package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/claude_settings_keys_constants.py +2 -0
  9. package/_shared/pr-loop/scripts/preflight.py +13 -31
  10. package/_shared/pr-loop/scripts/reviews_disabled.py +2 -16
  11. package/_shared/pr-loop/scripts/revoke_project_claude_permissions.py +76 -33
  12. package/_shared/pr-loop/scripts/tests/conftest.py +1 -51
  13. package/_shared/pr-loop/scripts/tests/test_agent_config_carveout.py +385 -0
  14. package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +4 -2
  15. package/_shared/pr-loop/scripts/tests/test_claude_permissions_constants.py +37 -2
  16. package/_shared/pr-loop/scripts/tests/test_claude_settings_keys_constants.py +4 -2
  17. package/_shared/pr-loop/scripts/tests/test_code_rules_gate_constants.py +4 -2
  18. package/_shared/pr-loop/scripts/tests/test_fix_hookspath_constants.py +6 -2
  19. package/_shared/pr-loop/scripts/tests/test_grant_project_claude_permissions.py +2 -2
  20. package/_shared/pr-loop/scripts/tests/test_post_audit_thread.py +1 -2
  21. package/_shared/pr-loop/scripts/tests/test_post_audit_thread_constants.py +4 -2
  22. package/_shared/pr-loop/scripts/tests/test_preflight.py +17 -52
  23. package/_shared/pr-loop/scripts/tests/test_preflight_constants.py +6 -2
  24. package/_shared/pr-loop/scripts/tests/test_revoke_project_claude_permissions.py +5 -3
  25. package/agents/pr-description-writer.md +50 -140
  26. package/docs/PR_DESCRIPTION_GUIDE.md +101 -102
  27. package/hooks/_gh_pr_author_swap_utils.py +1 -1
  28. package/hooks/blocking/bot_mention_comment_blocker.py +4 -10
  29. package/hooks/blocking/code_rules_enforcer.py +217 -99
  30. package/hooks/blocking/code_rules_path_utils.py +8 -1
  31. package/hooks/blocking/destructive_command_blocker.py +1 -1
  32. package/hooks/blocking/es_exe_path_rewriter.py +7 -13
  33. package/hooks/blocking/gh_body_arg_blocker.py +6 -1
  34. package/hooks/blocking/gh_pr_author_enforcer.py +5 -5
  35. package/hooks/blocking/gh_pr_author_restore.py +5 -5
  36. package/hooks/blocking/hedging_language_blocker.py +4 -10
  37. package/hooks/blocking/md_path_exemptions.py +205 -0
  38. package/hooks/blocking/md_to_html_blocker.py +48 -20
  39. package/hooks/blocking/pr_converge_bugteam_enforcer.py +5 -11
  40. package/hooks/blocking/pr_description_enforcer.py +626 -41
  41. package/hooks/blocking/question_to_user_enforcer.py +4 -10
  42. package/hooks/blocking/state_description_blocker.py +6 -12
  43. package/hooks/blocking/tdd_enforcer.py +1 -1
  44. package/hooks/blocking/test_bot_mention_comment_blocker.py +1 -1
  45. package/hooks/blocking/test_code_rules_enforcer.py +3 -3
  46. package/hooks/blocking/test_code_rules_enforcer_any_exempt_files.py +1 -1
  47. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +0 -2
  48. package/hooks/blocking/test_code_rules_enforcer_comment_string_awareness.py +184 -0
  49. package/hooks/blocking/test_code_rules_enforcer_type_checking_scope.py +82 -0
  50. package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +29 -29
  51. package/hooks/blocking/test_gh_body_arg_blocker.py +7 -8
  52. package/hooks/blocking/test_gh_pr_author_enforcer.py +1 -1
  53. package/hooks/blocking/test_gh_pr_author_restore.py +1 -1
  54. package/hooks/blocking/test_hedging_language_blocker.py +2 -2
  55. package/hooks/blocking/test_md_to_html_blocker.py +463 -8
  56. package/hooks/blocking/test_pr_converge_bugteam_enforcer.py +1 -1
  57. package/hooks/blocking/test_pr_description_enforcer.py +1210 -13
  58. package/hooks/blocking/test_question_to_user_enforcer.py +1 -1
  59. package/hooks/blocking/windows_rmtree_blocker.py +5 -11
  60. package/hooks/diagnostic/hook_log_extractor.py +1 -1
  61. package/hooks/diagnostic/hook_log_init.py +1 -1
  62. package/hooks/diagnostic/hook_log_stop_wrapper.py +1 -1
  63. package/hooks/diagnostic/test_hook_log_extractor.py +1 -1
  64. package/hooks/diagnostic/test_hook_log_init.py +2 -2
  65. package/hooks/diagnostic/test_hook_log_stop_wrapper.py +1 -1
  66. package/hooks/git-hooks/gate_utils.py +1 -1
  67. package/hooks/git-hooks/pre_commit.py +1 -1
  68. package/hooks/git-hooks/pre_push.py +1 -1
  69. package/hooks/git-hooks/test_config.py +5 -5
  70. package/hooks/git-hooks/test_pre_push.py +6 -6
  71. package/hooks/{config → hooks_constants}/code_rules_enforcer_constants.py +37 -0
  72. package/hooks/hooks_constants/code_rules_path_utils_constants.py +28 -0
  73. package/hooks/hooks_constants/md_to_html_blocker_constants.py +82 -0
  74. package/hooks/{config → hooks_constants}/pr_converge_bugteam_enforcer_state.py +1 -1
  75. package/hooks/hooks_constants/pr_description_enforcer_constants.py +154 -0
  76. package/hooks/{config → hooks_constants}/pre_tool_use_stdin.py +1 -1
  77. package/hooks/{config → hooks_constants}/project_paths_reader.py +2 -2
  78. package/hooks/{config → hooks_constants}/test_banned_identifiers_constants.py +1 -1
  79. package/hooks/{config → hooks_constants}/test_dynamic_stderr_handler.py +1 -1
  80. package/hooks/{config → hooks_constants}/test_hardcoded_user_path_constants.py +1 -1
  81. package/hooks/{config → hooks_constants}/test_hook_log_extractor_constants.py +2 -2
  82. package/hooks/hooks_constants/test_md_to_html_blocker_constants.py +110 -0
  83. package/hooks/{config → hooks_constants}/test_messages.py +2 -6
  84. package/hooks/{config → hooks_constants}/test_path_rewriter_constants.py +1 -1
  85. package/hooks/hooks_constants/test_pr_description_enforcer_constants.py +292 -0
  86. package/hooks/{config → hooks_constants}/test_pre_tool_use_stdin.py +2 -2
  87. package/hooks/{config → hooks_constants}/test_project_paths_reader.py +3 -3
  88. package/hooks/{config → hooks_constants}/test_session_env_cleanup_constants.py +1 -1
  89. package/hooks/{config → hooks_constants}/test_setup_project_paths_constants.py +2 -2
  90. package/hooks/{config → hooks_constants}/test_unused_module_import_constants.py +1 -1
  91. package/hooks/lifecycle/pr_converge_bugteam_skill_tracker.py +5 -11
  92. package/hooks/lifecycle/test_pr_converge_bugteam_skill_tracker.py +1 -1
  93. package/hooks/session/gh_pr_author_session_cleanup.py +5 -6
  94. package/hooks/session/session_env_cleanup.py +4 -10
  95. package/hooks/session/test_gh_pr_author_session_cleanup.py +1 -1
  96. package/hooks/session/test_untracked_repo_detector.py +2 -2
  97. package/hooks/session/untracked_repo_detector.py +6 -12
  98. package/hooks/test__gh_pr_author_swap_utils.py +1 -1
  99. package/hooks/validators/run_all_validators.py +16 -5
  100. package/hooks/validators/test_output_formatter.py +46 -0
  101. package/hooks/workflow/doc_gist_auto_publish.py +1 -1
  102. package/hooks/workflow/md_to_html_companion.py +8 -15
  103. package/hooks/workflow/test_md_to_html_companion.py +184 -23
  104. package/package.json +1 -1
  105. package/rules/ask-user-question-required.md +1 -1
  106. package/rules/vault-context.md +1 -1
  107. package/scripts/{config → dev_env_scripts_constants}/timing.py +1 -1
  108. package/scripts/setup_project_paths.py +49 -11
  109. package/scripts/sweep_empty_dirs.py +10 -1
  110. package/scripts/test_setup_project_paths.py +2 -2
  111. package/scripts/test_sweep_empty_dirs.py +2 -6
  112. package/skills/_shared/pr-loop/scripts/_path_resolver.py +1 -1
  113. package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +1 -1
  114. package/skills/_shared/pr-loop/scripts/build_fix_prompt.py +1 -1
  115. package/skills/_shared/pr-loop/scripts/init_loop_state.py +1 -1
  116. package/skills/_shared/pr-loop/scripts/teardown_worktrees.py +1 -1
  117. package/skills/_shared/pr-loop/scripts/write_audit_outcomes.py +2 -2
  118. package/skills/_shared/pr-loop/scripts/write_fix_outcomes.py +2 -2
  119. package/skills/bugteam/PROMPTS.md +1 -1
  120. package/skills/bugteam/SKILL.md +1 -1
  121. package/skills/bugteam/reference/github-pr-reviews.md +1 -1
  122. package/skills/bugteam/scripts/{_claude_permissions_common.py → _bugteam_permissions_common.py} +110 -13
  123. package/skills/bugteam/scripts/bugteam_code_rules_gate.py +1 -13
  124. package/skills/bugteam/scripts/bugteam_fix_hookspath.py +1 -16
  125. package/skills/bugteam/scripts/bugteam_preflight.py +1 -13
  126. package/skills/bugteam/scripts/bugteam_scripts_constants/claude_permissions_common_constants.py +69 -0
  127. package/skills/bugteam/scripts/grant_project_claude_permissions.py +117 -12
  128. package/skills/bugteam/scripts/probe_code_rules_enforcer_check.py +1 -1
  129. package/skills/bugteam/scripts/reflow_skill_md.py +1 -1
  130. package/skills/bugteam/scripts/revoke_project_claude_permissions.py +71 -25
  131. package/skills/bugteam/scripts/{test__claude_permissions_common.py → test__bugteam_permissions_common.py} +4 -4
  132. package/skills/bugteam/scripts/test_agent_config_carveout.py +356 -0
  133. package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +0 -26
  134. package/skills/bugteam/scripts/{test_claude_permissions_common.py → test_bugteam_permissions_common.py} +3 -66
  135. package/skills/bugteam/scripts/test_bugteam_preflight.py +2 -27
  136. package/skills/bugteam/scripts/windows_safe_rmtree.py +1 -1
  137. package/skills/doc-gist/SKILL.md +1 -1
  138. package/skills/doc-gist/scripts/gist_upload.py +1 -1
  139. package/skills/implement/SKILL.md +66 -0
  140. package/skills/implement/scripts/append_note.py +133 -0
  141. package/skills/implement/scripts/implement_scripts_constants/__init__.py +0 -0
  142. package/skills/implement/scripts/implement_scripts_constants/notes_constants.py +12 -0
  143. package/skills/implement/scripts/test_append_note.py +191 -0
  144. package/skills/pr-converge/pr_converge_skill_constants/__init__.py +0 -0
  145. package/skills/pr-converge/{config → pr_converge_skill_constants}/constants.py +6 -1
  146. package/skills/pr-converge/scripts/check_bugbot_ci.py +2 -2
  147. package/skills/pr-converge/scripts/check_convergence.py +175 -29
  148. package/skills/pr-converge/scripts/check_pending_reviews.py +2 -2
  149. package/skills/pr-converge/scripts/fetch_copilot_reviews.py +2 -2
  150. package/skills/pr-converge/scripts/post_fix_reply.py +2 -2
  151. package/skills/pr-converge/scripts/pr_converge_scripts_constants/__init__.py +0 -0
  152. package/skills/pr-converge/scripts/{config → pr_converge_scripts_constants}/pr_converge_constants.py +1 -1
  153. package/skills/pr-converge/scripts/reflow_skill_md.py +90 -16
  154. package/skills/pr-converge/scripts/test_check_bugbot_ci.py +1 -1
  155. package/skills/pr-converge/scripts/test_check_convergence.py +324 -0
  156. package/skills/pr-converge/scripts/test_reflow_skill_md.py +0 -31
  157. package/skills/refine/SKILL.md +257 -0
  158. package/skills/refine/templates/implementation-notes-template.html +56 -0
  159. package/skills/refine/templates/plan-template.md +60 -0
  160. package/skills/session-log/SKILL.md +98 -233
  161. package/_shared/pr-loop/scripts/config/claude_permissions_constants.py +0 -36
  162. package/hooks/config/pr_description_enforcer_constants.py +0 -19
  163. package/hooks/config/test_pr_description_enforcer_constants.py +0 -82
  164. package/skills/bugteam/scripts/config/claude_permissions_common_constants.py +0 -20
  165. package/skills/bugteam/scripts/test_grant_project_claude_permissions.py +0 -55
  166. package/skills/bugteam/scripts/test_revoke_project_claude_permissions.py +0 -55
  167. package/skills/pr-converge/scripts/evict_cached_config_modules.py +0 -20
  168. package/skills/pr-converge/scripts/test_evict_cached_config_modules.py +0 -22
  169. /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/__init__.py +0 -0
  170. /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/code_rules_gate_constants.py +0 -0
  171. /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/fix_hookspath_constants.py +0 -0
  172. /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/post_audit_thread_constants.py +0 -0
  173. /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/preflight_constants.py +0 -0
  174. /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/reviews_disabled_constants.py +0 -0
  175. /package/hooks/git-hooks/{config.py → git_hooks_constants/__init__.py} +0 -0
  176. /package/hooks/{config → hooks_constants}/__init__.py +0 -0
  177. /package/hooks/{config → hooks_constants}/any_type_config.py +0 -0
  178. /package/hooks/{config → hooks_constants}/banned_identifiers_constants.py +0 -0
  179. /package/hooks/{config → hooks_constants}/blocking_check_limits.py +0 -0
  180. /package/hooks/{config → hooks_constants}/bot_mention_comment_blocker_constants.py +0 -0
  181. /package/hooks/{config → hooks_constants}/convergence_branch_constants.py +0 -0
  182. /package/hooks/{config → hooks_constants}/doc_gist_auto_publish_constants.py +0 -0
  183. /package/hooks/{config → hooks_constants}/dynamic_stderr_handler.py +0 -0
  184. /package/hooks/{config → hooks_constants}/gh_pr_author_swap_constants.py +0 -0
  185. /package/hooks/{config → hooks_constants}/hardcoded_user_path_constants.py +0 -0
  186. /package/hooks/{config → hooks_constants}/hook_log_extractor_constants.py +0 -0
  187. /package/hooks/{config → hooks_constants}/html_companion_constants.py +0 -0
  188. /package/hooks/{config → hooks_constants}/inline_tuple_string_magic_constants.py +0 -0
  189. /package/hooks/{config → hooks_constants}/messages.py +0 -0
  190. /package/hooks/{config → hooks_constants}/path_rewriter_constants.py +0 -0
  191. /package/hooks/{config → hooks_constants}/pr_converge_bugteam_enforcer_constants.py +0 -0
  192. /package/hooks/{config → hooks_constants}/session_env_cleanup_constants.py +0 -0
  193. /package/hooks/{config → hooks_constants}/setup_project_paths_constants.py +0 -0
  194. /package/hooks/{config → hooks_constants}/state_description_blocker_constants.py +0 -0
  195. /package/hooks/{config → hooks_constants}/stuttering_check_config.py +0 -0
  196. /package/hooks/{config → hooks_constants}/stuttering_import_binding_constants.py +0 -0
  197. /package/hooks/{config → hooks_constants}/sys_path_insert_constants.py +0 -0
  198. /package/hooks/{config → hooks_constants}/unused_module_import_constants.py +0 -0
  199. /package/hooks/{config → hooks_constants}/windows_rmtree_blocker_constants.py +0 -0
  200. /package/{skills/_shared/pr-loop/scripts/config → hooks/lifecycle}/__init__.py +0 -0
  201. /package/{skills/bugteam/scripts/config → hooks/session}/__init__.py +0 -0
  202. /package/scripts/{config → dev_env_scripts_constants}/__init__.py +0 -0
  203. /package/skills/{doc-gist/scripts/config → _shared/pr-loop/scripts/skills_pr_loop_constants}/__init__.py +0 -0
  204. /package/skills/_shared/pr-loop/scripts/{config → skills_pr_loop_constants}/path_resolver_constants.py +0 -0
  205. /package/skills/{pr-converge/config → bugteam/scripts/bugteam_scripts_constants}/__init__.py +0 -0
  206. /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/bugteam_code_rules_gate_constants.py +0 -0
  207. /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/bugteam_fix_hookspath_constants.py +0 -0
  208. /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/bugteam_preflight_constants.py +0 -0
  209. /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/probe_code_rules_enforcer_check_constants.py +0 -0
  210. /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/reflow_skill_md_constants.py +0 -0
  211. /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/windows_safe_rmtree_constants.py +0 -0
  212. /package/skills/{pr-converge/scripts/config → doc-gist/scripts/doc_gist_scripts_constants}/__init__.py +0 -0
  213. /package/skills/doc-gist/scripts/{config → doc_gist_scripts_constants}/gist_upload_constants.py +0 -0
  214. /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 _claude_permissions_common import ( # noqa: E402
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 config.claude_permissions_common_constants import ( # noqa: E402
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
- rules_added_count = add_rules_to_allow_list(settings, permission_rules)
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
- rules_added_count + directories_added_count + environment_entries_added_count
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: {rules_added_count} of {len(permission_rules)}")
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 config.probe_code_rules_enforcer_check_constants import (
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 config.reflow_skill_md_constants import (
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 _claude_permissions_common import ( # noqa: E402
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 config.claude_permissions_common_constants import ( # noqa: E402
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
- AUTO_MODE_ENVIRONMENT_ENTRY_TEMPLATE,
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 remove_auto_mode_environment_entry(
122
- all_settings: dict[str, object], entry_text: str
142
+ def remove_trust_entries_for_project(
143
+ all_settings: dict[str, object], project_path: str, prefix: str
123
144
  ) -> int:
124
- """Remove an auto-mode environment entry for the project.
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
- entry_text: The environment entry text to remove.
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
- 1 when the entry was removed, 0 when not found.
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 remove_values_from_list(existing_environment, {entry_text})
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 permission rules from config
163
- constants, removes them from ~/.claude/settings.json, and prunes any
164
- newly empty sections.
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
- environment_entry = AUTO_MODE_ENVIRONMENT_ENTRY_TEMPLATE.format(
185
- project_path=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
- rules_removed_count = remove_rules_from_allow_list(settings, permission_rules)
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 = remove_auto_mode_environment_entry(
193
- settings, environment_entry
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
- rules_removed_count
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: {rules_removed_count} of {len(permission_rules)}")
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 _claude_permissions_common module.
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
- ``_claude_permissions_common.py`` carries a leading underscore that the
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
- ``test_claude_permissions_common.py``.
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 _claude_permissions_common as common_module
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