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.
- package/_shared/pr-loop/audit-contract.md +24 -12
- package/_shared/pr-loop/scripts/_claude_permissions_common.py +16 -5
- package/_shared/pr-loop/scripts/code_rules_gate.py +2 -3
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +1 -1
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +8 -0
- package/_shared/pr-loop/scripts/reviews_disabled.py +2 -0
- package/_shared/pr-loop/scripts/terminology_sweep.py +85 -39
- package/_shared/pr-loop/scripts/tests/test__claude_permissions_common.py +212 -0
- package/_shared/pr-loop/scripts/tests/test_agent_config_carveout.py +18 -0
- package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +18 -0
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +37 -0
- package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +14 -0
- package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +94 -4
- package/agents/clean-coder.md +3 -0
- package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +1 -1
- package/bin/install.mjs +16 -8
- package/bin/install.test.mjs +17 -6
- package/commands/CLAUDE.md +0 -1
- package/docs/CODE_RULES.md +1 -1
- package/hooks/blocking/CLAUDE.md +0 -4
- package/hooks/blocking/claude_md_orphan_file_blocker.py +1 -1
- package/hooks/blocking/code_rules_constants_config.py +5 -0
- package/hooks/blocking/code_rules_docstrings.py +133 -19
- package/hooks/blocking/code_rules_enforcer.py +4 -0
- package/hooks/blocking/code_rules_imports_logging.py +10 -6
- package/hooks/blocking/code_rules_magic_values.py +5 -0
- package/hooks/blocking/code_rules_naming_collection.py +15 -8
- package/hooks/blocking/code_rules_paired_test.py +3 -2
- package/hooks/blocking/code_rules_shared.py +34 -0
- package/hooks/blocking/code_rules_string_magic.py +1 -1
- package/hooks/blocking/code_rules_unused_imports.py +2 -2
- package/hooks/blocking/duplicate_rmtree_helper_blocker.py +4 -4
- package/hooks/blocking/test_code_rules_enforcer_docstring_args_span_scope.py +29 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_prose_wall_illustration.py +197 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_runon_sentence.py +27 -0
- package/hooks/blocking/test_code_rules_enforcer_naive_datetime.py +24 -0
- package/hooks/blocking/test_code_rules_enforcer_split_constants_config.py +33 -0
- package/hooks/blocking/test_code_rules_enforcer_split_shared.py +23 -0
- package/hooks/blocking/test_code_rules_magic_values.py +54 -0
- package/hooks/blocking/test_duplicate_rmtree_helper_blocker.py +0 -6
- package/hooks/blocking/test_pr_converge_bugteam_enforcer_state_tolerance.py +184 -0
- package/hooks/blocking/test_shared_stdin_adoption.py +5 -30
- package/hooks/hooks_constants/CLAUDE.md +0 -3
- package/hooks/hooks_constants/blocking_check_limits.py +4 -0
- package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -1
- package/hooks/hooks_constants/duplicate_rmtree_helper_blocker_constants.py +0 -1
- package/hooks/hooks_constants/post_tool_use_dispatcher_constants.py +0 -5
- package/hooks/validation/post_tool_use_dispatcher.py +1 -1
- package/hooks/validation/test_post_tool_use_dispatcher.py +51 -35
- package/hooks/workflow/CLAUDE.md +2 -8
- package/package.json +1 -1
- package/rules/CLAUDE.md +1 -1
- package/rules/docstring-prose-matches-implementation.md +1 -1
- package/rules/env-var-table-code-drift.md +1 -1
- package/rules/package-inventory-stale-entry.md +1 -1
- package/rules/paired-test-coverage.md +1 -1
- package/rules/plain-illustrative-docstrings.md +35 -1
- package/skills/CLAUDE.md +6 -1
- package/skills/_shared/pr-loop/CLAUDE.md +1 -0
- package/skills/_shared/pr-loop/scripts/CLAUDE.md +1 -0
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/CLAUDE.md +1 -0
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/handoff_constants.py +45 -0
- package/skills/_shared/pr-loop/scripts/test_write_handoff.py +201 -0
- package/skills/_shared/pr-loop/scripts/write_handoff.py +309 -0
- package/skills/autoconverge/SKILL.md +82 -58
- package/skills/autoconverge/reference/closing-report.md +6 -6
- package/skills/autoconverge/reference/convergence.md +17 -15
- package/skills/autoconverge/reference/gotchas.md +6 -3
- package/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py +2 -9
- package/skills/autoconverge/workflow/converge.contract.test.mjs +87 -13
- package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +1 -1
- package/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +16 -9
- package/skills/autoconverge/workflow/converge.mjs +71 -74
- package/skills/autoconverge/workflow/render_report.py +7 -11
- package/skills/bugteam/CLAUDE.md +1 -1
- package/skills/bugteam/PROMPTS.md +7 -6
- package/skills/bugteam/SKILL.md +25 -72
- package/skills/bugteam/reference/CLAUDE.md +1 -3
- package/skills/bugteam/reference/README.md +1 -1
- package/skills/bugteam/reference/audit-and-teammates.md +1 -1
- package/skills/bugteam/reference/obstacles/fix-publish-summary.md +1 -1
- package/skills/bugteam/reference/team-setup.md +8 -7
- package/skills/bugteam/scripts/CLAUDE.md +0 -6
- package/skills/bugteam/scripts/README.md +0 -4
- package/skills/bugteam/scripts/bugteam_scripts_constants/CLAUDE.md +0 -1
- package/skills/code/SKILL.md +2 -0
- package/skills/copilot-review/CLAUDE.md +1 -1
- package/skills/copilot-review/SKILL.md +25 -23
- package/skills/findbugs/CLAUDE.md +2 -2
- package/skills/findbugs/SKILL.md +22 -83
- package/skills/fixbugs/SKILL.md +2 -4
- package/skills/log-audit/CLAUDE.md +20 -0
- package/skills/log-audit/SKILL.md +68 -0
- package/skills/log-audit/reference/CLAUDE.md +9 -0
- package/skills/log-audit/reference/charter.md +52 -0
- package/skills/log-audit/scripts/CLAUDE.md +27 -0
- package/skills/log-audit/scripts/cluster_recurrences.py +261 -0
- package/skills/log-audit/scripts/collect_log_window.py +199 -0
- package/skills/log-audit/scripts/log_audit_constants/CLAUDE.md +12 -0
- package/skills/log-audit/scripts/log_audit_constants/cluster_recurrences_constants.py +23 -0
- package/skills/log-audit/scripts/log_audit_constants/collect_log_window_constants.py +24 -0
- package/skills/log-audit/scripts/log_audit_constants/mine_copilot_findings_constants.py +49 -0
- package/skills/log-audit/scripts/mine_copilot_findings.py +302 -0
- package/skills/log-audit/scripts/test_cluster_recurrences.py +160 -0
- package/skills/log-audit/scripts/test_collect_log_window.py +111 -0
- package/skills/log-audit/scripts/test_mine_copilot_findings.py +126 -0
- package/skills/monitor-open-prs/SKILL.md +2 -2
- package/skills/post-audit-findings/SKILL.md +84 -0
- package/skills/pr-converge/CLAUDE.md +2 -0
- package/skills/pr-converge/SKILL.md +99 -60
- package/skills/pr-converge/reference/CLAUDE.md +1 -1
- package/skills/pr-converge/reference/convergence-gates.md +16 -19
- package/skills/pr-converge/reference/examples.md +5 -5
- package/skills/pr-converge/reference/fix-protocol.md +16 -43
- package/skills/pr-converge/reference/obstacles/fix-publish-summary.md +1 -1
- package/skills/pr-converge/reference/per-tick.md +24 -45
- package/skills/pr-converge/reference/state-schema.md +27 -0
- package/skills/pr-converge/scripts/README.md +3 -5
- package/skills/pr-fix-protocol/SKILL.md +70 -0
- package/skills/pr-loop-lifecycle/SKILL.md +73 -0
- package/skills/{bugteam → pr-loop-lifecycle}/reference/teardown-publish-permissions.md +22 -24
- package/skills/pr-scope-resolve/SKILL.md +48 -0
- package/skills/qbug/CLAUDE.md +4 -4
- package/skills/qbug/SKILL.md +46 -144
- package/skills/qbug/test_qbug_skill_audit_schema.py +2 -2
- package/skills/qbug/test_qbug_skill_post_fix_audit.py +1 -1
- package/skills/reviewer-gates/SKILL.md +96 -0
- package/skills/session-log/CLAUDE.md +7 -7
- package/skills/session-log/SKILL.md +27 -44
- package/skills/test_markdown_link_integrity.py +103 -0
- package/commands/doc-gist.md +0 -16
- package/hooks/blocking/_md_to_html_blocker_test_support.py +0 -65
- package/hooks/blocking/conftest.py +0 -30
- package/hooks/blocking/md_path_exemptions.py +0 -224
- package/hooks/blocking/md_to_html_blocker.py +0 -155
- package/hooks/blocking/test_md_to_html_blocker_exemptions.py +0 -434
- package/hooks/blocking/test_md_to_html_blocker_extensions.py +0 -157
- package/hooks/blocking/test_md_to_html_blocker_path_resolution.py +0 -336
- package/hooks/hooks_constants/doc_gist_auto_publish_constants.py +0 -18
- package/hooks/hooks_constants/html_companion_constants.py +0 -20
- package/hooks/hooks_constants/md_to_html_blocker_constants.py +0 -76
- package/hooks/hooks_constants/test_md_to_html_blocker_constants.py +0 -125
- package/hooks/workflow/doc_gist_auto_publish.py +0 -144
- package/hooks/workflow/md_to_html_companion.py +0 -358
- package/hooks/workflow/test_doc_gist_auto_publish.py +0 -117
- package/hooks/workflow/test_md_to_html_companion.py +0 -613
- package/skills/bugteam/reference/audit-contract.md +0 -163
- package/skills/bugteam/scripts/_bugteam_permissions_common.py +0 -455
- package/skills/bugteam/scripts/bugteam_scripts_constants/claude_permissions_common_constants.py +0 -69
- package/skills/bugteam/scripts/grant_project_claude_permissions.py +0 -280
- package/skills/bugteam/scripts/revoke_project_claude_permissions.py +0 -266
- package/skills/bugteam/scripts/test__bugteam_permissions_common.py +0 -160
- package/skills/bugteam/scripts/test_agent_config_carveout.py +0 -356
- package/skills/bugteam/scripts/test_bugteam_permissions_common.py +0 -140
- package/skills/doc-gist/CLAUDE.md +0 -25
- package/skills/doc-gist/SKILL.md +0 -97
- package/skills/doc-gist/references/CLAUDE.md +0 -9
- package/skills/doc-gist/references/examples/01-exploration-code-approaches.html +0 -453
- package/skills/doc-gist/references/examples/02-exploration-visual-designs.html +0 -515
- package/skills/doc-gist/references/examples/03-code-review-pr.html +0 -638
- package/skills/doc-gist/references/examples/04-code-understanding.html +0 -491
- package/skills/doc-gist/references/examples/05-design-system.html +0 -629
- package/skills/doc-gist/references/examples/06-component-variants.html +0 -605
- package/skills/doc-gist/references/examples/07-prototype-animation.html +0 -455
- package/skills/doc-gist/references/examples/08-prototype-interaction.html +0 -396
- package/skills/doc-gist/references/examples/09-slide-deck.html +0 -592
- package/skills/doc-gist/references/examples/10-svg-illustrations.html +0 -492
- package/skills/doc-gist/references/examples/11-status-report.html +0 -528
- package/skills/doc-gist/references/examples/12-incident-report.html +0 -596
- package/skills/doc-gist/references/examples/13-flowchart-diagram.html +0 -395
- package/skills/doc-gist/references/examples/14-research-feature-explainer.html +0 -381
- package/skills/doc-gist/references/examples/15-research-concept-explainer.html +0 -368
- package/skills/doc-gist/references/examples/16-implementation-plan.html +0 -702
- package/skills/doc-gist/references/examples/17-pr-writeup.html +0 -595
- package/skills/doc-gist/references/examples/18-editor-triage-board.html +0 -573
- package/skills/doc-gist/references/examples/19-editor-feature-flags.html +0 -663
- package/skills/doc-gist/references/examples/20-editor-prompt-tuner.html +0 -722
- package/skills/doc-gist/references/examples/21-decision-signoff.html +0 -546
- package/skills/doc-gist/references/examples/CLAUDE.md +0 -25
- package/skills/doc-gist/references/examples/README.md +0 -5
- package/skills/doc-gist/scripts/CLAUDE.md +0 -27
- package/skills/doc-gist/scripts/doc_gist_scripts_constants/CLAUDE.md +0 -10
- package/skills/doc-gist/scripts/doc_gist_scripts_constants/gist_upload_constants.py +0 -16
- package/skills/doc-gist/scripts/gist_upload.py +0 -177
- package/skills/doc-gist/scripts/test_gist_upload.py +0 -51
- /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))
|