claude-dev-env 1.83.0 → 1.85.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 (186) hide show
  1. package/_shared/pr-loop/audit-contract.md +24 -12
  2. package/_shared/pr-loop/scripts/_claude_permissions_common.py +16 -5
  3. package/_shared/pr-loop/scripts/code_rules_gate.py +2 -3
  4. package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +1 -1
  5. package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +8 -0
  6. package/_shared/pr-loop/scripts/reviews_disabled.py +2 -0
  7. package/_shared/pr-loop/scripts/terminology_sweep.py +85 -39
  8. package/_shared/pr-loop/scripts/tests/test__claude_permissions_common.py +212 -0
  9. package/_shared/pr-loop/scripts/tests/test_agent_config_carveout.py +18 -0
  10. package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +18 -0
  11. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +37 -0
  12. package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +14 -0
  13. package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +94 -4
  14. package/agents/clean-coder.md +3 -0
  15. package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +1 -1
  16. package/bin/install.mjs +16 -8
  17. package/bin/install.test.mjs +17 -6
  18. package/commands/CLAUDE.md +0 -1
  19. package/docs/CODE_RULES.md +1 -1
  20. package/hooks/blocking/CLAUDE.md +0 -4
  21. package/hooks/blocking/claude_md_orphan_file_blocker.py +1 -1
  22. package/hooks/blocking/code_rules_constants_config.py +5 -0
  23. package/hooks/blocking/code_rules_docstrings.py +133 -19
  24. package/hooks/blocking/code_rules_enforcer.py +4 -0
  25. package/hooks/blocking/code_rules_imports_logging.py +10 -6
  26. package/hooks/blocking/code_rules_magic_values.py +5 -0
  27. package/hooks/blocking/code_rules_naming_collection.py +15 -8
  28. package/hooks/blocking/code_rules_paired_test.py +3 -2
  29. package/hooks/blocking/code_rules_shared.py +34 -0
  30. package/hooks/blocking/code_rules_string_magic.py +1 -1
  31. package/hooks/blocking/code_rules_unused_imports.py +2 -2
  32. package/hooks/blocking/duplicate_rmtree_helper_blocker.py +4 -4
  33. package/hooks/blocking/test_code_rules_enforcer_docstring_args_span_scope.py +29 -0
  34. package/hooks/blocking/test_code_rules_enforcer_docstring_prose_wall_illustration.py +197 -0
  35. package/hooks/blocking/test_code_rules_enforcer_docstring_runon_sentence.py +27 -0
  36. package/hooks/blocking/test_code_rules_enforcer_naive_datetime.py +24 -0
  37. package/hooks/blocking/test_code_rules_enforcer_split_constants_config.py +33 -0
  38. package/hooks/blocking/test_code_rules_enforcer_split_shared.py +23 -0
  39. package/hooks/blocking/test_code_rules_magic_values.py +54 -0
  40. package/hooks/blocking/test_duplicate_rmtree_helper_blocker.py +0 -6
  41. package/hooks/blocking/test_pr_converge_bugteam_enforcer_state_tolerance.py +184 -0
  42. package/hooks/blocking/test_shared_stdin_adoption.py +5 -30
  43. package/hooks/hooks_constants/CLAUDE.md +0 -3
  44. package/hooks/hooks_constants/blocking_check_limits.py +4 -0
  45. package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -1
  46. package/hooks/hooks_constants/duplicate_rmtree_helper_blocker_constants.py +0 -1
  47. package/hooks/hooks_constants/post_tool_use_dispatcher_constants.py +0 -5
  48. package/hooks/validation/post_tool_use_dispatcher.py +1 -1
  49. package/hooks/validation/test_post_tool_use_dispatcher.py +51 -35
  50. package/hooks/workflow/CLAUDE.md +2 -8
  51. package/package.json +1 -1
  52. package/rules/CLAUDE.md +1 -1
  53. package/rules/docstring-prose-matches-implementation.md +1 -1
  54. package/rules/env-var-table-code-drift.md +1 -1
  55. package/rules/package-inventory-stale-entry.md +1 -1
  56. package/rules/paired-test-coverage.md +1 -1
  57. package/rules/plain-illustrative-docstrings.md +35 -1
  58. package/skills/CLAUDE.md +6 -1
  59. package/skills/_shared/pr-loop/CLAUDE.md +1 -0
  60. package/skills/_shared/pr-loop/scripts/CLAUDE.md +1 -0
  61. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/CLAUDE.md +1 -0
  62. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/handoff_constants.py +45 -0
  63. package/skills/_shared/pr-loop/scripts/test_write_handoff.py +201 -0
  64. package/skills/_shared/pr-loop/scripts/write_handoff.py +309 -0
  65. package/skills/autoconverge/SKILL.md +82 -58
  66. package/skills/autoconverge/reference/closing-report.md +6 -6
  67. package/skills/autoconverge/reference/convergence.md +17 -15
  68. package/skills/autoconverge/reference/gotchas.md +6 -3
  69. package/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py +2 -9
  70. package/skills/autoconverge/workflow/converge.contract.test.mjs +87 -13
  71. package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +1 -1
  72. package/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +16 -9
  73. package/skills/autoconverge/workflow/converge.mjs +71 -74
  74. package/skills/autoconverge/workflow/render_report.py +7 -11
  75. package/skills/bugteam/CLAUDE.md +1 -1
  76. package/skills/bugteam/PROMPTS.md +7 -6
  77. package/skills/bugteam/SKILL.md +25 -72
  78. package/skills/bugteam/reference/CLAUDE.md +1 -3
  79. package/skills/bugteam/reference/README.md +1 -1
  80. package/skills/bugteam/reference/audit-and-teammates.md +1 -1
  81. package/skills/bugteam/reference/obstacles/fix-publish-summary.md +1 -1
  82. package/skills/bugteam/reference/team-setup.md +8 -7
  83. package/skills/bugteam/scripts/CLAUDE.md +0 -6
  84. package/skills/bugteam/scripts/README.md +0 -4
  85. package/skills/bugteam/scripts/bugteam_scripts_constants/CLAUDE.md +0 -1
  86. package/skills/code/SKILL.md +2 -0
  87. package/skills/copilot-review/CLAUDE.md +1 -1
  88. package/skills/copilot-review/SKILL.md +25 -23
  89. package/skills/findbugs/CLAUDE.md +2 -2
  90. package/skills/findbugs/SKILL.md +22 -83
  91. package/skills/fixbugs/SKILL.md +2 -4
  92. package/skills/log-audit/CLAUDE.md +20 -0
  93. package/skills/log-audit/SKILL.md +68 -0
  94. package/skills/log-audit/reference/CLAUDE.md +9 -0
  95. package/skills/log-audit/reference/charter.md +52 -0
  96. package/skills/log-audit/scripts/CLAUDE.md +27 -0
  97. package/skills/log-audit/scripts/cluster_recurrences.py +261 -0
  98. package/skills/log-audit/scripts/collect_log_window.py +199 -0
  99. package/skills/log-audit/scripts/log_audit_constants/CLAUDE.md +12 -0
  100. package/skills/log-audit/scripts/log_audit_constants/cluster_recurrences_constants.py +23 -0
  101. package/skills/log-audit/scripts/log_audit_constants/collect_log_window_constants.py +24 -0
  102. package/skills/log-audit/scripts/log_audit_constants/mine_copilot_findings_constants.py +49 -0
  103. package/skills/log-audit/scripts/mine_copilot_findings.py +302 -0
  104. package/skills/log-audit/scripts/test_cluster_recurrences.py +160 -0
  105. package/skills/log-audit/scripts/test_collect_log_window.py +111 -0
  106. package/skills/log-audit/scripts/test_mine_copilot_findings.py +126 -0
  107. package/skills/monitor-open-prs/SKILL.md +2 -2
  108. package/skills/post-audit-findings/SKILL.md +84 -0
  109. package/skills/pr-converge/CLAUDE.md +2 -0
  110. package/skills/pr-converge/SKILL.md +99 -60
  111. package/skills/pr-converge/reference/CLAUDE.md +1 -1
  112. package/skills/pr-converge/reference/convergence-gates.md +16 -19
  113. package/skills/pr-converge/reference/examples.md +5 -5
  114. package/skills/pr-converge/reference/fix-protocol.md +16 -43
  115. package/skills/pr-converge/reference/obstacles/fix-publish-summary.md +1 -1
  116. package/skills/pr-converge/reference/per-tick.md +24 -45
  117. package/skills/pr-converge/reference/state-schema.md +27 -0
  118. package/skills/pr-converge/scripts/README.md +3 -5
  119. package/skills/pr-fix-protocol/SKILL.md +70 -0
  120. package/skills/pr-loop-lifecycle/SKILL.md +73 -0
  121. package/skills/{bugteam → pr-loop-lifecycle}/reference/teardown-publish-permissions.md +22 -24
  122. package/skills/pr-scope-resolve/SKILL.md +48 -0
  123. package/skills/qbug/CLAUDE.md +4 -4
  124. package/skills/qbug/SKILL.md +46 -144
  125. package/skills/qbug/test_qbug_skill_audit_schema.py +2 -2
  126. package/skills/qbug/test_qbug_skill_post_fix_audit.py +1 -1
  127. package/skills/reviewer-gates/SKILL.md +96 -0
  128. package/skills/session-log/CLAUDE.md +7 -7
  129. package/skills/session-log/SKILL.md +27 -44
  130. package/skills/test_markdown_link_integrity.py +103 -0
  131. package/commands/doc-gist.md +0 -16
  132. package/hooks/blocking/_md_to_html_blocker_test_support.py +0 -65
  133. package/hooks/blocking/conftest.py +0 -30
  134. package/hooks/blocking/md_path_exemptions.py +0 -224
  135. package/hooks/blocking/md_to_html_blocker.py +0 -155
  136. package/hooks/blocking/test_md_to_html_blocker_exemptions.py +0 -434
  137. package/hooks/blocking/test_md_to_html_blocker_extensions.py +0 -157
  138. package/hooks/blocking/test_md_to_html_blocker_path_resolution.py +0 -336
  139. package/hooks/hooks_constants/doc_gist_auto_publish_constants.py +0 -18
  140. package/hooks/hooks_constants/html_companion_constants.py +0 -20
  141. package/hooks/hooks_constants/md_to_html_blocker_constants.py +0 -76
  142. package/hooks/hooks_constants/test_md_to_html_blocker_constants.py +0 -125
  143. package/hooks/workflow/doc_gist_auto_publish.py +0 -144
  144. package/hooks/workflow/md_to_html_companion.py +0 -358
  145. package/hooks/workflow/test_doc_gist_auto_publish.py +0 -117
  146. package/hooks/workflow/test_md_to_html_companion.py +0 -613
  147. package/skills/bugteam/reference/audit-contract.md +0 -163
  148. package/skills/bugteam/scripts/_bugteam_permissions_common.py +0 -455
  149. package/skills/bugteam/scripts/bugteam_scripts_constants/claude_permissions_common_constants.py +0 -69
  150. package/skills/bugteam/scripts/grant_project_claude_permissions.py +0 -280
  151. package/skills/bugteam/scripts/revoke_project_claude_permissions.py +0 -266
  152. package/skills/bugteam/scripts/test__bugteam_permissions_common.py +0 -160
  153. package/skills/bugteam/scripts/test_agent_config_carveout.py +0 -356
  154. package/skills/bugteam/scripts/test_bugteam_permissions_common.py +0 -140
  155. package/skills/doc-gist/CLAUDE.md +0 -25
  156. package/skills/doc-gist/SKILL.md +0 -97
  157. package/skills/doc-gist/references/CLAUDE.md +0 -9
  158. package/skills/doc-gist/references/examples/01-exploration-code-approaches.html +0 -453
  159. package/skills/doc-gist/references/examples/02-exploration-visual-designs.html +0 -515
  160. package/skills/doc-gist/references/examples/03-code-review-pr.html +0 -638
  161. package/skills/doc-gist/references/examples/04-code-understanding.html +0 -491
  162. package/skills/doc-gist/references/examples/05-design-system.html +0 -629
  163. package/skills/doc-gist/references/examples/06-component-variants.html +0 -605
  164. package/skills/doc-gist/references/examples/07-prototype-animation.html +0 -455
  165. package/skills/doc-gist/references/examples/08-prototype-interaction.html +0 -396
  166. package/skills/doc-gist/references/examples/09-slide-deck.html +0 -592
  167. package/skills/doc-gist/references/examples/10-svg-illustrations.html +0 -492
  168. package/skills/doc-gist/references/examples/11-status-report.html +0 -528
  169. package/skills/doc-gist/references/examples/12-incident-report.html +0 -596
  170. package/skills/doc-gist/references/examples/13-flowchart-diagram.html +0 -395
  171. package/skills/doc-gist/references/examples/14-research-feature-explainer.html +0 -381
  172. package/skills/doc-gist/references/examples/15-research-concept-explainer.html +0 -368
  173. package/skills/doc-gist/references/examples/16-implementation-plan.html +0 -702
  174. package/skills/doc-gist/references/examples/17-pr-writeup.html +0 -595
  175. package/skills/doc-gist/references/examples/18-editor-triage-board.html +0 -573
  176. package/skills/doc-gist/references/examples/19-editor-feature-flags.html +0 -663
  177. package/skills/doc-gist/references/examples/20-editor-prompt-tuner.html +0 -722
  178. package/skills/doc-gist/references/examples/21-decision-signoff.html +0 -546
  179. package/skills/doc-gist/references/examples/CLAUDE.md +0 -25
  180. package/skills/doc-gist/references/examples/README.md +0 -5
  181. package/skills/doc-gist/scripts/CLAUDE.md +0 -27
  182. package/skills/doc-gist/scripts/doc_gist_scripts_constants/CLAUDE.md +0 -10
  183. package/skills/doc-gist/scripts/doc_gist_scripts_constants/gist_upload_constants.py +0 -16
  184. package/skills/doc-gist/scripts/gist_upload.py +0 -177
  185. package/skills/doc-gist/scripts/test_gist_upload.py +0 -51
  186. /package/skills/{doc-gist/scripts/doc_gist_scripts_constants → log-audit/scripts/log_audit_constants}/__init__.py +0 -0
@@ -1,280 +0,0 @@
1
- """Grant Edit/Write/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 _bugteam_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
- load_settings,
26
- remove_matching_entries_from_list,
27
- save_settings,
28
- )
29
- from bugteam_scripts_constants.claude_permissions_common_constants import ( # noqa: E402
30
- ALL_AGENT_CONFIG_DENY_TOOLS,
31
- ALL_AGENT_CONFIG_PATH_PATTERNS,
32
- ALL_PERMISSION_ALLOW_TOOLS,
33
- AUTO_MODE_ENVIRONMENT_ENTRY_PREFIX,
34
- AUTO_MODE_ENVIRONMENT_ENTRY_TEMPLATE,
35
- CLAUDE_DIRECTORY_MARKER,
36
- CLAUDE_USER_SETTINGS_FILENAME,
37
- GIT_DIRECTORY_MARKER,
38
- SETTINGS_ADDITIONAL_DIRECTORIES_KEY,
39
- SETTINGS_ALLOW_KEY,
40
- SETTINGS_AUTO_MODE_KEY,
41
- SETTINGS_DENY_KEY,
42
- SETTINGS_ENVIRONMENT_KEY,
43
- SETTINGS_PERMISSIONS_KEY,
44
- )
45
-
46
-
47
- def is_valid_project_root(candidate_path: Path) -> bool:
48
- """Check whether a candidate path has expected project-root markers.
49
-
50
- Args:
51
- candidate_path: Path to check for project-root markers.
52
-
53
- Returns:
54
- True when the path contains .git or .claude directory.
55
- """
56
- return (
57
- (candidate_path / GIT_DIRECTORY_MARKER).exists()
58
- or (candidate_path / CLAUDE_DIRECTORY_MARKER).exists()
59
- )
60
-
61
-
62
- def add_rules_to_allow_list(all_settings: dict[str, object], all_rules_to_add: list[str]) -> int:
63
- """Add permission rules to the settings allow list.
64
-
65
- Args:
66
- all_settings: The parsed settings dictionary.
67
- all_rules_to_add: Permission rule strings to append.
68
-
69
- Returns:
70
- Number of rules actually added (new entries).
71
- """
72
- permissions_section = ensure_dict_section(all_settings, SETTINGS_PERMISSIONS_KEY)
73
- existing_allow_list = ensure_list_entry(permissions_section, SETTINGS_ALLOW_KEY)
74
- return sum(
75
- 1
76
- for each_rule in all_rules_to_add
77
- if append_if_missing(existing_allow_list, each_rule)
78
- )
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
-
106
- def add_directory_to_additional_directories(
107
- all_settings: dict[str, object], directory_path: str
108
- ) -> int:
109
- """Add a project path to the additionalDirectories allow list.
110
-
111
- Args:
112
- all_settings: The parsed settings dictionary.
113
- directory_path: The project directory path to add.
114
-
115
- Returns:
116
- 1 when the entry was added, 0 when it already existed.
117
- """
118
- permissions_section = ensure_dict_section(all_settings, SETTINGS_PERMISSIONS_KEY)
119
- existing_directories = ensure_list_entry(
120
- permissions_section, SETTINGS_ADDITIONAL_DIRECTORIES_KEY
121
- )
122
- if append_if_missing(existing_directories, directory_path):
123
- return 1
124
- return 0
125
-
126
-
127
- def add_auto_mode_environment_entry(
128
- all_settings: dict[str, object], entry_text: str
129
- ) -> int:
130
- """Add an auto-mode environment entry for the project.
131
-
132
- Args:
133
- all_settings: The parsed settings dictionary.
134
- entry_text: The environment entry text to add.
135
-
136
- Returns:
137
- 1 when the entry was added, 0 when it already existed.
138
- """
139
- auto_mode_section = ensure_dict_section(all_settings, SETTINGS_AUTO_MODE_KEY)
140
- existing_environment = ensure_list_entry(auto_mode_section, SETTINGS_ENVIRONMENT_KEY)
141
- if append_if_missing(existing_environment, entry_text):
142
- return 1
143
- return 0
144
-
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
-
194
- def grant_permissions_for_current_directory() -> None:
195
- """Grant Edit/Write/Read permissions for the current project directory.
196
-
197
- Reads the current project path, constructs permission rules from config
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.
203
-
204
- Raises:
205
- SystemExit(1): When the current directory is not a valid project root.
206
- ValueError: Propagated from get_current_project_path() when the path
207
- contains glob metacharacters.
208
- """
209
- claude_user_settings_path: Path = (
210
- Path.home() / CLAUDE_DIRECTORY_MARKER / CLAUDE_USER_SETTINGS_FILENAME
211
- )
212
- project_root_path = Path.cwd()
213
- if not is_valid_project_root(project_root_path):
214
- print(
215
- f"ERROR: cwd {project_root_path} is not a project root "
216
- f"(no {GIT_DIRECTORY_MARKER} or {CLAUDE_DIRECTORY_MARKER}). Run from a project root.",
217
- file=sys.stderr,
218
- )
219
- raise SystemExit(1)
220
- project_path = get_current_project_path()
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
- )
227
- environment_entry = AUTO_MODE_ENVIRONMENT_ENTRY_TEMPLATE.format(
228
- project_path=project_path
229
- )
230
- settings = load_settings(claude_user_settings_path)
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
- )
235
- directories_added_count = add_directory_to_additional_directories(
236
- settings, project_path
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
- )
244
- environment_entries_added_count = add_auto_mode_environment_entry(
245
- settings, environment_entry
246
- )
247
- total_changes_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
253
- )
254
- if total_changes_count == 0:
255
- print(f"Project path: {project_path}")
256
- print(f"Settings file: {claude_user_settings_path}")
257
- print("No changes needed; settings file left untouched.")
258
- return
259
- save_settings(claude_user_settings_path, settings)
260
- print(f"Project path: {project_path}")
261
- print(f"Settings file: {claude_user_settings_path}")
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
- )
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
- )
273
- print(f"Auto-mode environment entries added: {environment_entries_added_count}")
274
-
275
-
276
- if __name__ == "__main__":
277
- try:
278
- grant_permissions_for_current_directory()
279
- except ValueError as path_error:
280
- exit_with_error(str(path_error))
@@ -1,266 +0,0 @@
1
- """Revoke the permissions previously granted by grant_project_claude_permissions.
2
-
3
- Run from the same project root you previously granted. Removes the matching
4
- allow rules, the additionalDirectories entry, and the autoMode environment
5
- entry from ~/.claude/settings.json. Safe to run when no prior grant exists.
6
- After removals, prunes any newly empty lists and their parent permissions or
7
- autoMode sections so repeated grant/revoke cycles leave no dead structure.
8
- """
9
-
10
- import sys
11
- from pathlib import Path
12
-
13
- parent_directory = str(Path(__file__).resolve().parent)
14
- if parent_directory not in sys.path:
15
- sys.path.insert(0, parent_directory)
16
-
17
- from _bugteam_permissions_common import ( # noqa: E402
18
- build_agent_config_deny_rules,
19
- build_permission_rules,
20
- exit_with_error,
21
- get_current_project_path,
22
- is_trust_entry_for_project,
23
- load_settings,
24
- prune_empty_list_then_empty_section,
25
- remove_matching_entries_from_list,
26
- save_settings,
27
- )
28
- from bugteam_scripts_constants.claude_permissions_common_constants import ( # noqa: E402
29
- ALL_AGENT_CONFIG_DENY_TOOLS,
30
- ALL_AGENT_CONFIG_PATH_PATTERNS,
31
- ALL_PERMISSION_ALLOW_TOOLS,
32
- AUTO_MODE_ENVIRONMENT_ENTRY_PREFIX,
33
- CLAUDE_DIRECTORY_MARKER,
34
- CLAUDE_USER_SETTINGS_FILENAME,
35
- GIT_DIRECTORY_MARKER,
36
- SETTINGS_ADDITIONAL_DIRECTORIES_KEY,
37
- SETTINGS_ALLOW_KEY,
38
- SETTINGS_AUTO_MODE_KEY,
39
- SETTINGS_DENY_KEY,
40
- SETTINGS_ENVIRONMENT_KEY,
41
- SETTINGS_PERMISSIONS_KEY,
42
- )
43
-
44
-
45
- def is_valid_project_root(candidate_path: Path) -> bool:
46
- """Check whether a candidate path has expected project-root markers.
47
-
48
- Args:
49
- candidate_path: Path to check for project-root markers.
50
-
51
- Returns:
52
- True when the path contains .git or .claude directory.
53
- """
54
- return (
55
- (candidate_path / GIT_DIRECTORY_MARKER).exists()
56
- or (candidate_path / CLAUDE_DIRECTORY_MARKER).exists()
57
- )
58
-
59
-
60
- def remove_values_from_list(all_target_list: list[object], all_values_to_remove: set[str]) -> int:
61
- """Remove matching values from a list in place.
62
-
63
- Args:
64
- all_target_list: The list to remove values from.
65
- all_values_to_remove: Set of string values to remove.
66
-
67
- Returns:
68
- Number of values removed.
69
- """
70
- original_length = len(all_target_list)
71
- all_target_list[:] = [
72
- each_value
73
- for each_value in all_target_list
74
- if not (isinstance(each_value, str) and each_value in all_values_to_remove)
75
- ]
76
- return original_length - len(all_target_list)
77
-
78
-
79
- def remove_rules_from_allow_list(
80
- all_settings: dict[str, object], all_rules_to_remove: list[str]
81
- ) -> int:
82
- """Remove matching permission rules from the settings allow list.
83
-
84
- Args:
85
- all_settings: The parsed settings dictionary.
86
- all_rules_to_remove: Permission rule strings to remove.
87
-
88
- Returns:
89
- Number of rules removed.
90
- """
91
- permissions_section = all_settings.get(SETTINGS_PERMISSIONS_KEY)
92
- if not isinstance(permissions_section, dict):
93
- return 0
94
- existing_allow_list = permissions_section.get(SETTINGS_ALLOW_KEY)
95
- if not isinstance(existing_allow_list, list):
96
- return 0
97
- return remove_values_from_list(existing_allow_list, set(all_rules_to_remove))
98
-
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
-
121
- def remove_directory_from_additional_directories(
122
- all_settings: dict[str, object], directory_path: str
123
- ) -> int:
124
- """Remove a project path from the additionalDirectories list.
125
-
126
- Args:
127
- all_settings: The parsed settings dictionary.
128
- directory_path: The project directory path to remove.
129
-
130
- Returns:
131
- 1 when the entry was removed, 0 when not found.
132
- """
133
- permissions_section = all_settings.get(SETTINGS_PERMISSIONS_KEY)
134
- if not isinstance(permissions_section, dict):
135
- return 0
136
- existing_directories = permissions_section.get(SETTINGS_ADDITIONAL_DIRECTORIES_KEY)
137
- if not isinstance(existing_directories, list):
138
- return 0
139
- return remove_values_from_list(existing_directories, {directory_path})
140
-
141
-
142
- def remove_trust_entries_for_project(
143
- all_settings: dict[str, object], project_path: str, prefix: str
144
- ) -> int:
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.
151
-
152
- Args:
153
- all_settings: The parsed settings dictionary.
154
- project_path: The POSIX-style project root path.
155
- prefix: The literal prefix that marks a trust entry.
156
-
157
- Returns:
158
- Number of entries removed.
159
- """
160
- auto_mode_section = all_settings.get(SETTINGS_AUTO_MODE_KEY)
161
- if not isinstance(auto_mode_section, dict):
162
- return 0
163
- existing_environment = auto_mode_section.get(SETTINGS_ENVIRONMENT_KEY)
164
- if not isinstance(existing_environment, list):
165
- return 0
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
- )
172
-
173
-
174
- def prune_settings_after_revoke(all_settings: dict[str, object]) -> None:
175
- """Remove empty lists and their parent sections after revoking entries.
176
-
177
- Args:
178
- all_settings: The parsed settings dictionary to prune in place.
179
- """
180
- prune_empty_list_then_empty_section(
181
- all_settings, SETTINGS_PERMISSIONS_KEY, SETTINGS_ALLOW_KEY
182
- )
183
- prune_empty_list_then_empty_section(
184
- all_settings, SETTINGS_PERMISSIONS_KEY, SETTINGS_DENY_KEY
185
- )
186
- prune_empty_list_then_empty_section(
187
- all_settings, SETTINGS_PERMISSIONS_KEY, SETTINGS_ADDITIONAL_DIRECTORIES_KEY
188
- )
189
- prune_empty_list_then_empty_section(
190
- all_settings, SETTINGS_AUTO_MODE_KEY, SETTINGS_ENVIRONMENT_KEY
191
- )
192
-
193
-
194
- def revoke_permissions_for_current_directory() -> None:
195
- """Revoke Edit/Write/Read permissions for the current project directory.
196
-
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.
201
-
202
- Raises:
203
- SystemExit(1): When the current directory is not a valid project root.
204
- ValueError: Propagated from get_current_project_path() when the path
205
- contains glob metacharacters.
206
- """
207
- claude_user_settings_path: Path = (
208
- Path.home() / CLAUDE_DIRECTORY_MARKER / CLAUDE_USER_SETTINGS_FILENAME
209
- )
210
- project_root_path = Path.cwd()
211
- if not is_valid_project_root(project_root_path):
212
- print(
213
- f"ERROR: cwd {project_root_path} is not a project root "
214
- f"(no {GIT_DIRECTORY_MARKER} or {CLAUDE_DIRECTORY_MARKER}). Run from a project root.",
215
- file=sys.stderr,
216
- )
217
- raise SystemExit(1)
218
- project_path = get_current_project_path()
219
- permission_rules = build_permission_rules(project_path, ALL_PERMISSION_ALLOW_TOOLS)
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,
224
- )
225
- settings = load_settings(claude_user_settings_path)
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
- )
230
- directories_removed_count = remove_directory_from_additional_directories(
231
- settings, project_path
232
- )
233
- environment_entries_removed_count = remove_trust_entries_for_project(
234
- settings, project_path, AUTO_MODE_ENVIRONMENT_ENTRY_PREFIX
235
- )
236
- total_changes_count = (
237
- allow_rules_removed_count
238
- + deny_rules_removed_count
239
- + directories_removed_count
240
- + environment_entries_removed_count
241
- )
242
- if total_changes_count == 0:
243
- print(f"Project path: {project_path}")
244
- print(f"Settings file: {claude_user_settings_path}")
245
- print("No changes to revoke; settings file left untouched.")
246
- return
247
- prune_settings_after_revoke(settings)
248
- save_settings(claude_user_settings_path, settings)
249
- print(f"Project path: {project_path}")
250
- print(f"Settings file: {claude_user_settings_path}")
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
- )
256
- print(f"Additional directories removed: {directories_removed_count}")
257
- print(
258
- f"Auto-mode environment entries removed: {environment_entries_removed_count}"
259
- )
260
-
261
-
262
- if __name__ == "__main__":
263
- try:
264
- revoke_permissions_for_current_directory()
265
- except ValueError as path_error:
266
- exit_with_error(str(path_error))