claude-dev-env 1.38.1 → 1.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +10 -36
- package/_shared/pr-loop/audit-reply-template.md +147 -0
- package/_shared/pr-loop/fix-protocol.md +25 -4
- package/_shared/pr-loop/gh-payloads.md +37 -50
- package/_shared/pr-loop/scripts/code_rules_gate.py +0 -60
- package/_shared/pr-loop/scripts/config/post_audit_thread_constants.py +199 -0
- package/_shared/pr-loop/scripts/config/reviews_disabled_constants.py +8 -0
- package/_shared/pr-loop/scripts/post_audit_thread.py +1242 -0
- package/_shared/pr-loop/scripts/preflight.py +129 -2
- package/_shared/pr-loop/scripts/reviews_disabled.py +59 -0
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +0 -19
- package/_shared/pr-loop/scripts/tests/test_post_audit_thread.py +1116 -0
- package/_shared/pr-loop/scripts/tests/test_post_audit_thread_constants.py +127 -0
- package/_shared/pr-loop/scripts/tests/test_preflight.py +41 -0
- package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +36 -0
- package/_shared/pr-loop/state-schema.md +1 -1
- package/agents/clean-coder.md +2 -2
- package/agents/pr-description-writer.md +150 -52
- package/bin/install.mjs +6 -7
- package/bin/install.test.mjs +8 -0
- package/commands/doc-gist.md +16 -0
- package/commands/plan.md +0 -2
- package/commands/review-plan.md +1 -1
- package/docs/CODE_RULES.md +122 -2
- package/docs/PR_DESCRIPTION_GUIDE.md +127 -64
- package/hooks/blocking/bot_mention_comment_blocker.py +75 -0
- package/hooks/blocking/code_rules_enforcer.py +1143 -129
- package/hooks/blocking/convergence_gate_blocker.py +130 -0
- package/hooks/blocking/destructive_command_blocker.py +74 -0
- package/hooks/blocking/gh_body_arg_blocker.py +30 -0
- package/hooks/blocking/md_to_html_blocker.py +119 -0
- package/hooks/blocking/pr_description_enforcer.py +57 -22
- package/hooks/blocking/test_bot_mention_comment_blocker.py +131 -0
- package/hooks/blocking/test_code_rules_enforcer.py +21 -0
- package/hooks/blocking/test_code_rules_enforcer_any_exempt_files.py +70 -0
- package/hooks/blocking/test_code_rules_enforcer_any_imports_and_cast.py +92 -0
- package/hooks/blocking/test_code_rules_enforcer_banned_import_alias.py +143 -0
- package/hooks/blocking/test_code_rules_enforcer_banned_prefixes.py +152 -0
- package/hooks/blocking/test_code_rules_enforcer_bare_except.py +120 -0
- package/hooks/blocking/test_code_rules_enforcer_boundary_types.py +175 -0
- package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +0 -1
- package/hooks/blocking/test_code_rules_enforcer_collection_prefix.py +50 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_format.py +255 -0
- package/hooks/blocking/test_code_rules_enforcer_inline_tuple_string_magic.py +130 -0
- package/hooks/blocking/test_code_rules_enforcer_stub_implementations.py +141 -0
- package/hooks/blocking/test_code_rules_enforcer_test_branching.py +143 -0
- package/hooks/blocking/test_code_rules_enforcer_thin_wrapper_files.py +169 -0
- package/hooks/blocking/test_code_rules_enforcer_todo_markers.py +99 -0
- package/hooks/blocking/test_code_rules_enforcer_typed_dict_pairs.py +141 -0
- package/hooks/blocking/test_convergence_gate_blocker.py +63 -0
- package/hooks/blocking/test_destructive_command_blocker.py +146 -0
- package/hooks/blocking/test_destructive_command_blocker_no_verify.py +102 -0
- package/hooks/blocking/test_gh_body_arg_blocker.py +45 -0
- package/hooks/blocking/test_md_to_html_blocker.py +317 -0
- package/hooks/blocking/test_pr_description_enforcer.py +69 -8
- package/hooks/config/any_type_config.py +7 -0
- package/hooks/config/banned_identifiers_constants.py +11 -0
- package/hooks/config/blocking_check_limits.py +38 -0
- package/hooks/config/bot_mention_comment_blocker_constants.py +20 -0
- package/hooks/config/code_rules_enforcer_constants.py +53 -0
- package/hooks/config/convergence_branch_constants.py +9 -0
- package/hooks/config/doc_gist_auto_publish_constants.py +18 -0
- package/hooks/config/html_companion_constants.py +20 -0
- package/hooks/config/inline_tuple_string_magic_constants.py +22 -0
- package/hooks/config/pr_description_enforcer_constants.py +14 -0
- package/hooks/config/test_banned_identifiers_constants.py +17 -0
- package/hooks/hooks.json +28 -20
- package/hooks/pyproject.toml +69 -0
- package/hooks/validators/mypy_integration.py +47 -1
- package/hooks/validators/run_all_validators.py +3 -3
- package/hooks/validators/test_mypy_integration.py +50 -1
- package/hooks/workflow/doc_gist_auto_publish.py +144 -0
- package/hooks/workflow/md_to_html_companion.py +365 -0
- package/hooks/workflow/test_doc_gist_auto_publish.py +117 -0
- package/hooks/workflow/test_md_to_html_companion.py +452 -0
- package/package.json +1 -1
- package/rules/gh-body-file.md +2 -0
- package/scripts/Install-SweepEmptyDirs.ps1 +111 -0
- package/scripts/check.ps1 +106 -0
- package/scripts/config/timing.py +11 -0
- package/scripts/sweep_empty_dirs.py +138 -0
- package/scripts/sync_to_cursor/rules.py +1 -1
- package/scripts/test_sweep_empty_dirs.py +183 -0
- package/skills/_shared/pr-loop/prompts/pr-consistency-audit.xml +323 -0
- package/skills/_shared/pr-loop/scripts/_cli_utils.py +22 -0
- package/skills/_shared/pr-loop/scripts/_path_resolver.py +165 -0
- package/skills/_shared/pr-loop/scripts/_xml_utils.py +20 -0
- package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +182 -0
- package/skills/_shared/pr-loop/scripts/build_fix_prompt.py +185 -0
- package/skills/_shared/pr-loop/scripts/config/__init__.py +0 -0
- package/skills/_shared/pr-loop/scripts/config/path_resolver_constants.py +78 -0
- package/skills/_shared/pr-loop/scripts/init_loop_state.py +135 -0
- package/skills/_shared/pr-loop/scripts/teardown_worktrees.py +175 -0
- package/skills/_shared/pr-loop/scripts/write_audit_outcomes.py +182 -0
- package/skills/_shared/pr-loop/scripts/write_fix_outcomes.py +206 -0
- package/skills/bugteam/CONSTRAINTS.md +21 -22
- package/skills/bugteam/EXAMPLES.md +3 -3
- package/skills/bugteam/PROMPTS.md +227 -67
- package/skills/bugteam/SKILL.md +132 -455
- package/skills/bugteam/reference/README.md +1 -1
- package/skills/bugteam/reference/audit-and-teammates.md +112 -39
- package/skills/bugteam/reference/audit-contract.md +4 -22
- package/skills/bugteam/reference/copilot-gap-analysis.md +8 -5
- package/skills/bugteam/reference/design-rationale.md +2 -2
- package/skills/bugteam/reference/github-pr-reviews.md +50 -57
- package/skills/bugteam/reference/obstacles/audit-assign-ids.md +13 -0
- package/skills/bugteam/reference/obstacles/audit-capture-excerpts.md +13 -0
- package/skills/bugteam/reference/obstacles/audit-walk-categories.md +13 -0
- package/skills/bugteam/reference/obstacles/audit-write-xml.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-append-summary.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-apply-fixes.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-git-add-commit.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-git-push.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-post-reply.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-publish-summary.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-py-compile.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-read-files.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-resolve-thread.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-test-suite.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-violation-count.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-write-xml.md +13 -0
- package/skills/bugteam/reference/team-setup.md +111 -9
- package/skills/bugteam/reference/teardown-publish-permissions.md +39 -8
- package/skills/bugteam/scripts/README.md +60 -0
- package/skills/bugteam/scripts/_claude_permissions_common.py +358 -0
- package/skills/bugteam/scripts/bugteam_code_rules_gate.py +976 -0
- package/skills/bugteam/scripts/bugteam_fix_hookspath.py +375 -0
- package/skills/bugteam/scripts/bugteam_preflight.py +328 -0
- package/skills/bugteam/scripts/config/bugteam_code_rules_gate_constants.py +25 -0
- package/skills/bugteam/scripts/config/bugteam_fix_hookspath_constants.py +26 -0
- package/skills/bugteam/scripts/config/bugteam_preflight_constants.py +35 -0
- package/skills/bugteam/scripts/config/claude_permissions_common_constants.py +20 -0
- package/skills/bugteam/scripts/config/probe_code_rules_enforcer_check_constants.py +12 -0
- package/skills/bugteam/scripts/config/windows_safe_rmtree_constants.py +7 -0
- package/skills/bugteam/scripts/grant_project_claude_permissions.py +175 -0
- package/skills/bugteam/scripts/probe_code_rules_enforcer_check.py +107 -0
- package/skills/bugteam/scripts/revoke_project_claude_permissions.py +220 -0
- package/skills/bugteam/scripts/test__claude_permissions_common.py +112 -0
- package/skills/bugteam/scripts/test_bugteam_code_rules_gate.py +400 -0
- package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +384 -0
- package/skills/bugteam/scripts/test_bugteam_preflight.py +309 -0
- package/skills/bugteam/scripts/test_claude_permissions_common.py +195 -0
- package/skills/bugteam/scripts/test_grant_project_claude_permissions.py +55 -0
- package/skills/bugteam/scripts/test_probe_code_rules_enforcer_check.py +76 -0
- package/skills/bugteam/scripts/test_revoke_project_claude_permissions.py +55 -0
- package/skills/bugteam/scripts/test_windows_safe_rmtree.py +108 -0
- package/skills/bugteam/scripts/windows_safe_rmtree.py +100 -0
- package/skills/bugteam/test_skill_additions.py +1 -11
- package/skills/code/SKILL.md +176 -0
- package/skills/copilot-review/SKILL.md +16 -0
- package/skills/doc-gist/SKILL.md +99 -0
- package/skills/doc-gist/references/examples/01-exploration-code-approaches.html +453 -0
- package/skills/doc-gist/references/examples/02-exploration-visual-designs.html +515 -0
- package/skills/doc-gist/references/examples/03-code-review-pr.html +638 -0
- package/skills/doc-gist/references/examples/04-code-understanding.html +491 -0
- package/skills/doc-gist/references/examples/05-design-system.html +629 -0
- package/skills/doc-gist/references/examples/06-component-variants.html +605 -0
- package/skills/doc-gist/references/examples/07-prototype-animation.html +455 -0
- package/skills/doc-gist/references/examples/08-prototype-interaction.html +396 -0
- package/skills/doc-gist/references/examples/09-slide-deck.html +592 -0
- package/skills/doc-gist/references/examples/10-svg-illustrations.html +492 -0
- package/skills/doc-gist/references/examples/11-status-report.html +528 -0
- package/skills/doc-gist/references/examples/12-incident-report.html +596 -0
- package/skills/doc-gist/references/examples/13-flowchart-diagram.html +395 -0
- package/skills/doc-gist/references/examples/14-research-feature-explainer.html +381 -0
- package/skills/doc-gist/references/examples/15-research-concept-explainer.html +368 -0
- package/skills/doc-gist/references/examples/16-implementation-plan.html +702 -0
- package/skills/doc-gist/references/examples/17-pr-writeup.html +595 -0
- package/skills/doc-gist/references/examples/18-editor-triage-board.html +573 -0
- package/skills/doc-gist/references/examples/19-editor-feature-flags.html +663 -0
- package/skills/doc-gist/references/examples/20-editor-prompt-tuner.html +722 -0
- package/skills/doc-gist/references/examples/README.md +5 -0
- package/skills/doc-gist/scripts/config/__init__.py +0 -0
- package/skills/doc-gist/scripts/config/gist_upload_constants.py +16 -0
- package/skills/doc-gist/scripts/gist_upload.py +177 -0
- package/skills/doc-gist/scripts/test_gist_upload.py +51 -0
- package/skills/findbugs/SKILL.md +96 -2
- package/skills/monitor-open-prs/SKILL.md +14 -32
- package/skills/monitor-open-prs/test_skill_contract.py +0 -11
- package/skills/pr-consistency-audit/SKILL.md +112 -0
- package/skills/pr-consistency-audit/reference/detection-rules.md +96 -0
- package/skills/pr-consistency-audit/reference/illustrations.md +78 -0
- package/skills/pr-converge/SKILL.md +229 -23
- package/skills/pr-converge/config/__init__.py +0 -0
- package/skills/pr-converge/config/constants.py +63 -0
- package/skills/pr-converge/reference/convergence-gates.md +138 -44
- package/skills/pr-converge/reference/examples.md +43 -11
- package/skills/pr-converge/reference/fix-protocol.md +6 -5
- package/skills/pr-converge/reference/ground-rules.md +5 -3
- package/skills/pr-converge/reference/multi-pr-orchestration.md +44 -19
- package/skills/pr-converge/reference/obstacles/fix-post-replies.md +13 -0
- package/skills/pr-converge/reference/obstacles/fix-publish-summary.md +13 -0
- package/skills/pr-converge/reference/obstacles/fix-push.md +13 -0
- package/skills/pr-converge/reference/obstacles/fix-read-filelines.md +13 -0
- package/skills/pr-converge/reference/obstacles/fix-reset-state.md +13 -0
- package/skills/pr-converge/reference/obstacles/fix-resolve-threads.md +13 -0
- package/skills/pr-converge/reference/obstacles/fix-spawn-clean-coder.md +13 -0
- package/skills/pr-converge/reference/obstacles/fix-stage-commit.md +13 -0
- package/skills/pr-converge/reference/obstacles/fix-trigger-bugbot.md +13 -0
- package/skills/pr-converge/reference/obstacles/fix-write-test.md +13 -0
- package/skills/pr-converge/reference/per-tick.md +107 -31
- package/skills/pr-converge/reference/state-schema.md +22 -1
- package/skills/pr-converge/reference/stop-conditions.md +9 -7
- package/skills/pr-converge/scripts/README.md +34 -46
- package/skills/pr-converge/scripts/check_bugbot_ci.py +279 -0
- package/skills/pr-converge/scripts/check_convergence.py +497 -0
- package/skills/pr-converge/scripts/check_pending_reviews.py +154 -0
- package/skills/pr-converge/scripts/config/pr_converge_constants.py +118 -0
- package/skills/pr-converge/scripts/fetch_copilot_reviews.py +134 -0
- package/skills/pr-converge/scripts/post_fix_reply.py +168 -0
- package/skills/pr-converge/scripts/test_check_bugbot_ci.py +312 -0
- package/skills/pr-converge/workflows/schedule-wakeup-loop.md +5 -12
- package/skills/qbug/SKILL.md +157 -27
- package/skills/session-log/SKILL.md +216 -114
- package/skills/session-tidy/SKILL.md +1 -1
- package/skills/skill-builder/SKILL.md +138 -56
- package/skills/skill-builder/references/delegation-map.md +72 -113
- package/skills/skill-builder/references/progressive-disclosure.md +122 -0
- package/skills/skill-builder/references/self-audit-checklist.md +92 -0
- package/skills/skill-builder/references/skill-types.md +228 -0
- package/skills/skill-builder/references/thariq-x-post-skills.json +33 -0
- package/skills/skill-builder/templates/gap-analysis.md +15 -8
- package/skills/skill-builder/workflows/improve-skill.md +86 -57
- package/skills/skill-builder/workflows/new-skill.md +80 -168
- package/skills/skill-builder/workflows/polish-skill.md +78 -54
- package/skills/structure-prompt/SKILL.md +50 -0
- package/skills/structure-prompt/reference/adversarial-tuning.md +62 -0
- package/skills/structure-prompt/reference/block-classification.md +27 -0
- package/skills/structure-prompt/reference/canonical-case.md +48 -0
- package/skills/structure-prompt/reference/citation-depth.md +70 -0
- package/skills/structure-prompt/reference/cleanup.md +33 -0
- package/skills/structure-prompt/reference/constraints.md +33 -0
- package/skills/structure-prompt/reference/directives.md +37 -0
- package/skills/structure-prompt/reference/examples.md +72 -0
- package/skills/structure-prompt/reference/instantiation.md +51 -0
- package/skills/structure-prompt/reference/output-contract.md +72 -0
- package/skills/structure-prompt/reference/per-category.md +23 -0
- package/skills/structure-prompt/reference/persona.md +38 -0
- package/skills/structure-prompt/reference/research.md +33 -0
- package/skills/structure-prompt/reference/structure.md +28 -0
- package/agents/code-standards-agent.md +0 -93
- package/agents/groq-coder.md +0 -113
- package/agents/plan-executor.md +0 -226
- package/agents/project-docs-analyzer.md +0 -53
- package/agents/project-structure-organizer-agent.md +0 -72
- package/agents/skill-to-agent-converter.md +0 -370
- package/agents/skill-writer-agent.md +0 -470
- package/agents/user-docs-writer.md +0 -67
- package/agents/workflow-visual-documenter.md +0 -82
- package/commands/readability-review.md +0 -20
- package/hooks/mypy.ini +0 -2
- package/hooks/notification/attention_needed_notify.py +0 -71
- package/hooks/notification/claude_notification_handler.py +0 -67
- package/hooks/notification/notification_utils.py +0 -267
- package/hooks/notification/subagent_complete_notify.py +0 -381
- package/hooks/notification/test_attention_needed_notify.py +0 -47
- package/hooks/notification/test_claude_notification_handler.py +0 -54
- package/hooks/notification/test_notification_utils.py +0 -91
- package/hooks/notification/test_subagent_complete_notify.py +0 -79
- package/scripts/config/groq_bugteam_config.py +0 -230
- package/scripts/config/test_groq_bugteam_config.py +0 -83
- package/scripts/config/test_spec_implementer_prompt.py +0 -32
- package/scripts/groq_bugteam.README.md +0 -131
- package/scripts/groq_bugteam.py +0 -647
- package/scripts/groq_bugteam_dotenv.py +0 -40
- package/scripts/groq_bugteam_spec.py +0 -226
- package/scripts/test_groq_bugteam.py +0 -529
- package/scripts/test_groq_bugteam_apply_fix_from_spec.py +0 -426
- package/scripts/test_groq_bugteam_dotenv.py +0 -66
- package/scripts/test_groq_bugteam_spec.py +0 -338
- package/skills/bugteam/SKILL_EVALS.md +0 -309
- package/skills/dream/SKILL.md +0 -118
- package/skills/ingest/SKILL.md +0 -40
- package/skills/npm-creator/SKILL.md +0 -187
- package/skills/readability-review/SKILL.md +0 -127
- package/skills/resume-review/SKILL.md +0 -261
- package/skills/rule-audit/SKILL.md +0 -307
- package/skills/rule-creator/SKILL.md +0 -150
- package/skills/searching-obsidian-vault/SKILL.md +0 -131
- package/skills/skill-writer/REFERENCE.md +0 -284
- package/skills/skill-writer/SKILL.md +0 -222
- package/skills/tdd-team/SKILL.md +0 -128
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Configuration constants for the bugteam CODE_RULES gate script."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
BUGTEAM_CODE_RULES_GATE_PREFIX: str = "bugteam_code_rules_gate: "
|
|
6
|
+
EXIT_CODE_ENFORCER_MISSING: int = 2
|
|
7
|
+
HUNK_HEADER_RAW_PATTERN: str = r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@"
|
|
8
|
+
MAXIMUM_ISSUES_TO_REPORT: int = 3
|
|
9
|
+
MAXIMUM_COLUMN_TUPLE_ELEMENT_COUNT: int = 2
|
|
10
|
+
VIOLATION_LINE_RAW_PATTERN: str = r"^Line (\d+):"
|
|
11
|
+
|
|
12
|
+
ALL_CODE_FILE_EXTENSIONS: frozenset[str] = frozenset(
|
|
13
|
+
{".py", ".js", ".ts", ".tsx", ".jsx"}
|
|
14
|
+
)
|
|
15
|
+
ALL_JS_FILE_EXTENSIONS: tuple[str, ...] = (".js", ".ts", ".tsx", ".jsx")
|
|
16
|
+
ALL_COLUMN_MAGIC_FALSE_VALUES: frozenset[str] = frozenset(
|
|
17
|
+
{"true", "false", "none", "null"}
|
|
18
|
+
)
|
|
19
|
+
ALL_GIT_DIFF_CACHED_ARGS: tuple[str, ...] = (
|
|
20
|
+
"git",
|
|
21
|
+
"diff",
|
|
22
|
+
"--cached",
|
|
23
|
+
"--name-only",
|
|
24
|
+
"-z",
|
|
25
|
+
)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Configuration constants for bugteam_fix_hookspath auto-remediation script."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
HOOKS_PATH_SUFFIX: str = "hooks/git-hooks"
|
|
6
|
+
|
|
7
|
+
ALL_CANONICAL_HOOKS_DIRECTORY_COMPONENTS: tuple[str, str, str] = (
|
|
8
|
+
".claude",
|
|
9
|
+
"hooks",
|
|
10
|
+
"git-hooks",
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
ALL_HOME_ENV_VAR_NAMES: tuple[str, str] = ("HOME", "USERPROFILE")
|
|
14
|
+
|
|
15
|
+
PREFLIGHT_NO_PYTEST_FLAG: str = "--no-pytest"
|
|
16
|
+
|
|
17
|
+
PREFLIGHT_REPO_ROOT_FLAG: str = "--repo-root"
|
|
18
|
+
|
|
19
|
+
ALL_GLOBAL_HOOKS_PATH_ARGUMENTS: tuple[str, ...] = (
|
|
20
|
+
"git",
|
|
21
|
+
"config",
|
|
22
|
+
"--global",
|
|
23
|
+
"--get",
|
|
24
|
+
"core.hooksPath",
|
|
25
|
+
)
|
|
26
|
+
GIT_DIRECTORY_NAME: str = ".git"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Configuration constants for the bugteam preflight check script."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
BUGTEAM_PREFLIGHT_SKIP_ENV_VAR_NAME: str = "BUGTEAM_PREFLIGHT_SKIP"
|
|
6
|
+
EXPECTED_HOOKS_PATH_SUFFIX: str = "hooks/git-hooks"
|
|
7
|
+
ENFORCEMENT_ABSENT_MESSAGE: str = (
|
|
8
|
+
"Git-side CODE_RULES enforcement is not active on this host.\n"
|
|
9
|
+
"Run: npx claude-dev-env .\n"
|
|
10
|
+
"Or set core.hooksPath at any scope, e.g.:\n"
|
|
11
|
+
" git config --global core.hooksPath ~/.claude/hooks/git-hooks"
|
|
12
|
+
)
|
|
13
|
+
PYTEST_EXIT_CODE_NO_TESTS_COLLECTED: int = 5
|
|
14
|
+
EXIT_CODE_HOOKS_PATH_CHECK_FAILED: int = 1
|
|
15
|
+
ALL_DISCOVERY_IGNORE_DIRECTORIES: frozenset[str] = frozenset(
|
|
16
|
+
{"site-packages", ".venv", "venv", "node_modules"}
|
|
17
|
+
)
|
|
18
|
+
BUGTEAM_PREFLIGHT_PREFIX: str = "bugteam_preflight: "
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
ALL_GIT_CONFIG_HOOKS_PATH_ARGUMENTS: tuple[str, ...] = (
|
|
22
|
+
"config",
|
|
23
|
+
"--get",
|
|
24
|
+
"core.hooksPath",
|
|
25
|
+
)
|
|
26
|
+
ALL_PRE_COMMIT_ARGUMENTS: tuple[str, ...] = (
|
|
27
|
+
"pre-commit",
|
|
28
|
+
"run",
|
|
29
|
+
"--all-files",
|
|
30
|
+
)
|
|
31
|
+
GIT_DIRECTORY_NAME: str = ".git"
|
|
32
|
+
PYTEST_INI_FILENAME: str = "pytest.ini"
|
|
33
|
+
PYPROJECT_FILENAME: str = "pyproject.toml"
|
|
34
|
+
PYPROJECT_PYTEST_SECTION_PREFIX: str = "[tool.pytest"
|
|
35
|
+
PRE_COMMIT_CONFIG_FILENAME: str = ".pre-commit-config.yaml"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Configuration constants for claude_permissions_common shared helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
TEXT_FILE_ENCODING: str = "utf-8"
|
|
6
|
+
ALL_PERMISSION_ALLOW_TOOLS: tuple[str, ...] = ("Edit", "Write", "Read")
|
|
7
|
+
AUTO_MODE_ENVIRONMENT_ENTRY_TEMPLATE: str = (
|
|
8
|
+
"Trusted local workspace: {project_path}/.claude/** is the user's "
|
|
9
|
+
"project Claude Code config tree; edits inside are routine"
|
|
10
|
+
)
|
|
11
|
+
ATOMIC_WRITE_TEMPORARY_SUFFIX: str = ".tmp"
|
|
12
|
+
GIT_DIRECTORY_MARKER: str = ".git"
|
|
13
|
+
CLAUDE_DIRECTORY_MARKER: str = ".claude"
|
|
14
|
+
CLAUDE_USER_SETTINGS_FILENAME: str = "settings.json"
|
|
15
|
+
DEFAULT_SETTINGS_FILE_MODE: int = 0o600
|
|
16
|
+
SETTINGS_PERMISSIONS_KEY: str = "permissions"
|
|
17
|
+
SETTINGS_ALLOW_KEY: str = "allow"
|
|
18
|
+
SETTINGS_ADDITIONAL_DIRECTORIES_KEY: str = "additionalDirectories"
|
|
19
|
+
SETTINGS_AUTO_MODE_KEY: str = "autoMode"
|
|
20
|
+
SETTINGS_ENVIRONMENT_KEY: str = "environment"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Configuration constants for the probe_code_rules_enforcer_check script."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
DEFAULT_REPORTED_PATH: str = "fixture.py"
|
|
6
|
+
EXIT_CODE_USAGE_ERROR: int = 2
|
|
7
|
+
MINIMUM_ARGUMENT_COUNT: int = 3
|
|
8
|
+
MAXIMUM_ARGUMENT_COUNT: int = 4
|
|
9
|
+
ENFORCER_RELATIVE_PATH: Path = (
|
|
10
|
+
Path(".claude") / "hooks" / "blocking" / "code_rules_enforcer.py"
|
|
11
|
+
)
|
|
12
|
+
ENFORCER_MODULE_NAME: str = "code_rules_enforcer"
|
|
@@ -0,0 +1,175 @@
|
|
|
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
|
+
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
|
+
parent_directory = str(Path(__file__).resolve().parent)
|
|
19
|
+
if parent_directory not in sys.path:
|
|
20
|
+
sys.path.insert(0, parent_directory)
|
|
21
|
+
|
|
22
|
+
from _claude_permissions_common import ( # noqa: E402
|
|
23
|
+
append_if_missing,
|
|
24
|
+
build_permission_rules,
|
|
25
|
+
ensure_dict_section,
|
|
26
|
+
ensure_list_entry,
|
|
27
|
+
exit_with_error,
|
|
28
|
+
get_current_project_path,
|
|
29
|
+
load_settings,
|
|
30
|
+
save_settings,
|
|
31
|
+
)
|
|
32
|
+
from config.claude_permissions_common_constants import ( # noqa: E402
|
|
33
|
+
ALL_PERMISSION_ALLOW_TOOLS,
|
|
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_ENVIRONMENT_KEY,
|
|
42
|
+
SETTINGS_PERMISSIONS_KEY,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def is_valid_project_root(candidate_path: Path) -> bool:
|
|
47
|
+
"""Check whether a candidate path has expected project-root markers.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
candidate_path: Path to check for project-root markers.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
True when the path contains .git or .claude directory.
|
|
54
|
+
"""
|
|
55
|
+
return (
|
|
56
|
+
(candidate_path / GIT_DIRECTORY_MARKER).exists()
|
|
57
|
+
or (candidate_path / CLAUDE_DIRECTORY_MARKER).exists()
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def add_rules_to_allow_list(all_settings: dict[str, object], all_rules_to_add: list[str]) -> int:
|
|
62
|
+
"""Add permission rules to the settings allow list.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
all_settings: The parsed settings dictionary.
|
|
66
|
+
all_rules_to_add: Permission rule strings to append.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Number of rules actually added (new entries).
|
|
70
|
+
"""
|
|
71
|
+
permissions_section = ensure_dict_section(all_settings, SETTINGS_PERMISSIONS_KEY)
|
|
72
|
+
existing_allow_list = ensure_list_entry(permissions_section, SETTINGS_ALLOW_KEY)
|
|
73
|
+
return sum(
|
|
74
|
+
1
|
|
75
|
+
for each_rule in all_rules_to_add
|
|
76
|
+
if append_if_missing(existing_allow_list, each_rule)
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def add_directory_to_additional_directories(
|
|
81
|
+
all_settings: dict[str, object], directory_path: str
|
|
82
|
+
) -> int:
|
|
83
|
+
"""Add a project path to the additionalDirectories allow list.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
all_settings: The parsed settings dictionary.
|
|
87
|
+
directory_path: The project directory path to add.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
1 when the entry was added, 0 when it already existed.
|
|
91
|
+
"""
|
|
92
|
+
permissions_section = ensure_dict_section(all_settings, SETTINGS_PERMISSIONS_KEY)
|
|
93
|
+
existing_directories = ensure_list_entry(
|
|
94
|
+
permissions_section, SETTINGS_ADDITIONAL_DIRECTORIES_KEY
|
|
95
|
+
)
|
|
96
|
+
if append_if_missing(existing_directories, directory_path):
|
|
97
|
+
return 1
|
|
98
|
+
return 0
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def add_auto_mode_environment_entry(
|
|
102
|
+
all_settings: dict[str, object], entry_text: str
|
|
103
|
+
) -> int:
|
|
104
|
+
"""Add an auto-mode environment entry for the project.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
all_settings: The parsed settings dictionary.
|
|
108
|
+
entry_text: The environment entry text to add.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
1 when the entry was added, 0 when it already existed.
|
|
112
|
+
"""
|
|
113
|
+
auto_mode_section = ensure_dict_section(all_settings, SETTINGS_AUTO_MODE_KEY)
|
|
114
|
+
existing_environment = ensure_list_entry(auto_mode_section, SETTINGS_ENVIRONMENT_KEY)
|
|
115
|
+
if append_if_missing(existing_environment, entry_text):
|
|
116
|
+
return 1
|
|
117
|
+
return 0
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def grant_permissions_for_current_directory() -> None:
|
|
121
|
+
"""Grant Edit/Write/Read permissions for the current project directory.
|
|
122
|
+
|
|
123
|
+
Reads the current project path, constructs permission rules from config
|
|
124
|
+
constants, and writes them to ~/.claude/settings.json atomically.
|
|
125
|
+
|
|
126
|
+
Raises:
|
|
127
|
+
SystemExit(1): When the current directory is not a valid project root.
|
|
128
|
+
ValueError: Propagated from get_current_project_path() when the path
|
|
129
|
+
contains glob metacharacters.
|
|
130
|
+
"""
|
|
131
|
+
claude_user_settings_path: Path = (
|
|
132
|
+
Path.home() / CLAUDE_DIRECTORY_MARKER / CLAUDE_USER_SETTINGS_FILENAME
|
|
133
|
+
)
|
|
134
|
+
project_root_path = Path.cwd()
|
|
135
|
+
if not is_valid_project_root(project_root_path):
|
|
136
|
+
print(
|
|
137
|
+
f"ERROR: cwd {project_root_path} is not a project root "
|
|
138
|
+
f"(no {GIT_DIRECTORY_MARKER} or {CLAUDE_DIRECTORY_MARKER}). Run from a project root.",
|
|
139
|
+
file=sys.stderr,
|
|
140
|
+
)
|
|
141
|
+
raise SystemExit(1)
|
|
142
|
+
project_path = get_current_project_path()
|
|
143
|
+
permission_rules = build_permission_rules(project_path, ALL_PERMISSION_ALLOW_TOOLS)
|
|
144
|
+
environment_entry = AUTO_MODE_ENVIRONMENT_ENTRY_TEMPLATE.format(
|
|
145
|
+
project_path=project_path
|
|
146
|
+
)
|
|
147
|
+
settings = load_settings(claude_user_settings_path)
|
|
148
|
+
rules_added_count = add_rules_to_allow_list(settings, permission_rules)
|
|
149
|
+
directories_added_count = add_directory_to_additional_directories(
|
|
150
|
+
settings, project_path
|
|
151
|
+
)
|
|
152
|
+
environment_entries_added_count = add_auto_mode_environment_entry(
|
|
153
|
+
settings, environment_entry
|
|
154
|
+
)
|
|
155
|
+
total_changes_count = (
|
|
156
|
+
rules_added_count + directories_added_count + environment_entries_added_count
|
|
157
|
+
)
|
|
158
|
+
if total_changes_count == 0:
|
|
159
|
+
print(f"Project path: {project_path}")
|
|
160
|
+
print(f"Settings file: {claude_user_settings_path}")
|
|
161
|
+
print("No changes needed; settings file left untouched.")
|
|
162
|
+
return
|
|
163
|
+
save_settings(claude_user_settings_path, settings)
|
|
164
|
+
print(f"Project path: {project_path}")
|
|
165
|
+
print(f"Settings file: {claude_user_settings_path}")
|
|
166
|
+
print(f"Allow rules added: {rules_added_count} of {len(permission_rules)}")
|
|
167
|
+
print(f"Additional directories added: {directories_added_count}")
|
|
168
|
+
print(f"Auto-mode environment entries added: {environment_entries_added_count}")
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
if __name__ == "__main__":
|
|
172
|
+
try:
|
|
173
|
+
grant_permissions_for_current_directory()
|
|
174
|
+
except ValueError as path_error:
|
|
175
|
+
exit_with_error(str(path_error))
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Probe one detector inside code_rules_enforcer.py against a fixture file.
|
|
2
|
+
|
|
3
|
+
Loads ~/.claude/hooks/blocking/code_rules_enforcer.py dynamically and invokes
|
|
4
|
+
the requested check function (e.g. check_collection_prefix, check_library_print)
|
|
5
|
+
against the contents of a target fixture file. Prints the returned issue list.
|
|
6
|
+
|
|
7
|
+
Used as a verification shape during the historical Copilot gap-analysis
|
|
8
|
+
investigation (see reference/copilot-gap-analysis.md). This script replaces the
|
|
9
|
+
inline python -c probe that the doc used to embed.
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
python probe_code_rules_enforcer_check.py <check_function> <fixture_path> [reported_path]
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import importlib.util
|
|
18
|
+
import sys
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from types import ModuleType
|
|
21
|
+
|
|
22
|
+
from config.probe_code_rules_enforcer_check_constants import (
|
|
23
|
+
DEFAULT_REPORTED_PATH,
|
|
24
|
+
ENFORCER_MODULE_NAME,
|
|
25
|
+
ENFORCER_RELATIVE_PATH,
|
|
26
|
+
EXIT_CODE_USAGE_ERROR,
|
|
27
|
+
MAXIMUM_ARGUMENT_COUNT,
|
|
28
|
+
MINIMUM_ARGUMENT_COUNT,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _load_enforcer_module() -> ModuleType:
|
|
33
|
+
enforcer_path = Path.home() / ENFORCER_RELATIVE_PATH
|
|
34
|
+
spec = importlib.util.spec_from_file_location(
|
|
35
|
+
ENFORCER_MODULE_NAME, str(enforcer_path)
|
|
36
|
+
)
|
|
37
|
+
if spec is None or spec.loader is None:
|
|
38
|
+
raise RuntimeError(f"could not load enforcer at {enforcer_path}")
|
|
39
|
+
module = importlib.util.module_from_spec(spec)
|
|
40
|
+
spec.loader.exec_module(module)
|
|
41
|
+
return module
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def run_probe(
|
|
45
|
+
check_function_name: str, fixture_path: str, reported_path: str
|
|
46
|
+
) -> list[str]:
|
|
47
|
+
"""Load an enforcer check function and run it against a fixture file.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
check_function_name: Name of the check function in code_rules_enforcer.
|
|
51
|
+
fixture_path: Absolute path to the fixture file to check.
|
|
52
|
+
reported_path: Path string to pass as the file_path parameter.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
List of issue strings returned by the check function.
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
AttributeError: When the named check function is not found in the enforcer.
|
|
59
|
+
RuntimeError: When the enforcer module cannot be loaded.
|
|
60
|
+
"""
|
|
61
|
+
enforcer_module = _load_enforcer_module()
|
|
62
|
+
check_function = getattr(enforcer_module, check_function_name, None)
|
|
63
|
+
if check_function is None:
|
|
64
|
+
raise AttributeError(f"{check_function_name} not found in code_rules_enforcer")
|
|
65
|
+
fixture_content = Path(fixture_path).read_text(encoding="utf-8")
|
|
66
|
+
return check_function(fixture_content, reported_path)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _print_usage_to_stderr() -> None:
|
|
70
|
+
sys.stderr.write(
|
|
71
|
+
"usage: python probe_code_rules_enforcer_check.py "
|
|
72
|
+
"<check_function> <fixture_path> [reported_path]\n"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def main(all_arguments: list[str]) -> int:
|
|
77
|
+
"""Invoke the probe with command-line arguments and print results.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
all_arguments: Command-line arguments including script name,
|
|
81
|
+
check function name, fixture path, and optional reported path.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
Exit code 0 on success, EXIT_CODE_USAGE_ERROR on invalid arguments.
|
|
85
|
+
"""
|
|
86
|
+
argument_count = len(all_arguments)
|
|
87
|
+
if (
|
|
88
|
+
argument_count < MINIMUM_ARGUMENT_COUNT
|
|
89
|
+
or argument_count > MAXIMUM_ARGUMENT_COUNT
|
|
90
|
+
):
|
|
91
|
+
_print_usage_to_stderr()
|
|
92
|
+
return EXIT_CODE_USAGE_ERROR
|
|
93
|
+
check_function_name = all_arguments[1]
|
|
94
|
+
fixture_path = all_arguments[2]
|
|
95
|
+
reported_path = (
|
|
96
|
+
all_arguments[3]
|
|
97
|
+
if argument_count == MAXIMUM_ARGUMENT_COUNT
|
|
98
|
+
else DEFAULT_REPORTED_PATH
|
|
99
|
+
)
|
|
100
|
+
issues = run_probe(check_function_name, fixture_path, reported_path)
|
|
101
|
+
for each_issue in issues:
|
|
102
|
+
print(each_issue)
|
|
103
|
+
return 0
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
if __name__ == "__main__":
|
|
107
|
+
sys.exit(main(sys.argv))
|
|
@@ -0,0 +1,220 @@
|
|
|
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
|
+
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
|
+
parent_directory = str(Path(__file__).resolve().parent)
|
|
20
|
+
if parent_directory not in sys.path:
|
|
21
|
+
sys.path.insert(0, parent_directory)
|
|
22
|
+
|
|
23
|
+
from _claude_permissions_common import ( # noqa: E402
|
|
24
|
+
build_permission_rules,
|
|
25
|
+
exit_with_error,
|
|
26
|
+
get_current_project_path,
|
|
27
|
+
load_settings,
|
|
28
|
+
prune_empty_list_then_empty_section,
|
|
29
|
+
save_settings,
|
|
30
|
+
)
|
|
31
|
+
from config.claude_permissions_common_constants import ( # noqa: E402
|
|
32
|
+
ALL_PERMISSION_ALLOW_TOOLS,
|
|
33
|
+
AUTO_MODE_ENVIRONMENT_ENTRY_TEMPLATE,
|
|
34
|
+
CLAUDE_DIRECTORY_MARKER,
|
|
35
|
+
CLAUDE_USER_SETTINGS_FILENAME,
|
|
36
|
+
GIT_DIRECTORY_MARKER,
|
|
37
|
+
SETTINGS_ADDITIONAL_DIRECTORIES_KEY,
|
|
38
|
+
SETTINGS_ALLOW_KEY,
|
|
39
|
+
SETTINGS_AUTO_MODE_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_directory_from_additional_directories(
|
|
101
|
+
all_settings: dict[str, object], directory_path: str
|
|
102
|
+
) -> int:
|
|
103
|
+
"""Remove a project path from the additionalDirectories list.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
all_settings: The parsed settings dictionary.
|
|
107
|
+
directory_path: The project directory path to remove.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
1 when the entry was removed, 0 when not found.
|
|
111
|
+
"""
|
|
112
|
+
permissions_section = all_settings.get(SETTINGS_PERMISSIONS_KEY)
|
|
113
|
+
if not isinstance(permissions_section, dict):
|
|
114
|
+
return 0
|
|
115
|
+
existing_directories = permissions_section.get(SETTINGS_ADDITIONAL_DIRECTORIES_KEY)
|
|
116
|
+
if not isinstance(existing_directories, list):
|
|
117
|
+
return 0
|
|
118
|
+
return remove_values_from_list(existing_directories, {directory_path})
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def remove_auto_mode_environment_entry(
|
|
122
|
+
all_settings: dict[str, object], entry_text: str
|
|
123
|
+
) -> int:
|
|
124
|
+
"""Remove an auto-mode environment entry for the project.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
all_settings: The parsed settings dictionary.
|
|
128
|
+
entry_text: The environment entry text to remove.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
1 when the entry was removed, 0 when not found.
|
|
132
|
+
"""
|
|
133
|
+
auto_mode_section = all_settings.get(SETTINGS_AUTO_MODE_KEY)
|
|
134
|
+
if not isinstance(auto_mode_section, dict):
|
|
135
|
+
return 0
|
|
136
|
+
existing_environment = auto_mode_section.get(SETTINGS_ENVIRONMENT_KEY)
|
|
137
|
+
if not isinstance(existing_environment, list):
|
|
138
|
+
return 0
|
|
139
|
+
return remove_values_from_list(existing_environment, {entry_text})
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def prune_settings_after_revoke(all_settings: dict[str, object]) -> None:
|
|
143
|
+
"""Remove empty lists and their parent sections after revoking entries.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
all_settings: The parsed settings dictionary to prune in place.
|
|
147
|
+
"""
|
|
148
|
+
prune_empty_list_then_empty_section(
|
|
149
|
+
all_settings, SETTINGS_PERMISSIONS_KEY, SETTINGS_ALLOW_KEY
|
|
150
|
+
)
|
|
151
|
+
prune_empty_list_then_empty_section(
|
|
152
|
+
all_settings, SETTINGS_PERMISSIONS_KEY, SETTINGS_ADDITIONAL_DIRECTORIES_KEY
|
|
153
|
+
)
|
|
154
|
+
prune_empty_list_then_empty_section(
|
|
155
|
+
all_settings, SETTINGS_AUTO_MODE_KEY, SETTINGS_ENVIRONMENT_KEY
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def revoke_permissions_for_current_directory() -> None:
|
|
160
|
+
"""Revoke Edit/Write/Read permissions for the current project directory.
|
|
161
|
+
|
|
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.
|
|
165
|
+
|
|
166
|
+
Raises:
|
|
167
|
+
SystemExit(1): When the current directory is not a valid project root.
|
|
168
|
+
ValueError: Propagated from get_current_project_path() when the path
|
|
169
|
+
contains glob metacharacters.
|
|
170
|
+
"""
|
|
171
|
+
claude_user_settings_path: Path = (
|
|
172
|
+
Path.home() / CLAUDE_DIRECTORY_MARKER / CLAUDE_USER_SETTINGS_FILENAME
|
|
173
|
+
)
|
|
174
|
+
project_root_path = Path.cwd()
|
|
175
|
+
if not is_valid_project_root(project_root_path):
|
|
176
|
+
print(
|
|
177
|
+
f"ERROR: cwd {project_root_path} is not a project root "
|
|
178
|
+
f"(no {GIT_DIRECTORY_MARKER} or {CLAUDE_DIRECTORY_MARKER}). Run from a project root.",
|
|
179
|
+
file=sys.stderr,
|
|
180
|
+
)
|
|
181
|
+
raise SystemExit(1)
|
|
182
|
+
project_path = get_current_project_path()
|
|
183
|
+
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
|
|
186
|
+
)
|
|
187
|
+
settings = load_settings(claude_user_settings_path)
|
|
188
|
+
rules_removed_count = remove_rules_from_allow_list(settings, permission_rules)
|
|
189
|
+
directories_removed_count = remove_directory_from_additional_directories(
|
|
190
|
+
settings, project_path
|
|
191
|
+
)
|
|
192
|
+
environment_entries_removed_count = remove_auto_mode_environment_entry(
|
|
193
|
+
settings, environment_entry
|
|
194
|
+
)
|
|
195
|
+
total_changes_count = (
|
|
196
|
+
rules_removed_count
|
|
197
|
+
+ directories_removed_count
|
|
198
|
+
+ environment_entries_removed_count
|
|
199
|
+
)
|
|
200
|
+
if total_changes_count == 0:
|
|
201
|
+
print(f"Project path: {project_path}")
|
|
202
|
+
print(f"Settings file: {claude_user_settings_path}")
|
|
203
|
+
print("No changes to revoke; settings file left untouched.")
|
|
204
|
+
return
|
|
205
|
+
prune_settings_after_revoke(settings)
|
|
206
|
+
save_settings(claude_user_settings_path, settings)
|
|
207
|
+
print(f"Project path: {project_path}")
|
|
208
|
+
print(f"Settings file: {claude_user_settings_path}")
|
|
209
|
+
print(f"Allow rules removed: {rules_removed_count} of {len(permission_rules)}")
|
|
210
|
+
print(f"Additional directories removed: {directories_removed_count}")
|
|
211
|
+
print(
|
|
212
|
+
f"Auto-mode environment entries removed: {environment_entries_removed_count}"
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
if __name__ == "__main__":
|
|
217
|
+
try:
|
|
218
|
+
revoke_permissions_for_current_directory()
|
|
219
|
+
except ValueError as path_error:
|
|
220
|
+
exit_with_error(str(path_error))
|