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.
Files changed (282) hide show
  1. package/CLAUDE.md +10 -36
  2. package/_shared/pr-loop/audit-reply-template.md +147 -0
  3. package/_shared/pr-loop/fix-protocol.md +25 -4
  4. package/_shared/pr-loop/gh-payloads.md +37 -50
  5. package/_shared/pr-loop/scripts/code_rules_gate.py +0 -60
  6. package/_shared/pr-loop/scripts/config/post_audit_thread_constants.py +199 -0
  7. package/_shared/pr-loop/scripts/config/reviews_disabled_constants.py +8 -0
  8. package/_shared/pr-loop/scripts/post_audit_thread.py +1242 -0
  9. package/_shared/pr-loop/scripts/preflight.py +129 -2
  10. package/_shared/pr-loop/scripts/reviews_disabled.py +59 -0
  11. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +0 -19
  12. package/_shared/pr-loop/scripts/tests/test_post_audit_thread.py +1116 -0
  13. package/_shared/pr-loop/scripts/tests/test_post_audit_thread_constants.py +127 -0
  14. package/_shared/pr-loop/scripts/tests/test_preflight.py +41 -0
  15. package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +36 -0
  16. package/_shared/pr-loop/state-schema.md +1 -1
  17. package/agents/clean-coder.md +2 -2
  18. package/agents/pr-description-writer.md +150 -52
  19. package/bin/install.mjs +6 -7
  20. package/bin/install.test.mjs +8 -0
  21. package/commands/doc-gist.md +16 -0
  22. package/commands/plan.md +0 -2
  23. package/commands/review-plan.md +1 -1
  24. package/docs/CODE_RULES.md +122 -2
  25. package/docs/PR_DESCRIPTION_GUIDE.md +127 -64
  26. package/hooks/blocking/bot_mention_comment_blocker.py +75 -0
  27. package/hooks/blocking/code_rules_enforcer.py +1143 -129
  28. package/hooks/blocking/convergence_gate_blocker.py +130 -0
  29. package/hooks/blocking/destructive_command_blocker.py +74 -0
  30. package/hooks/blocking/gh_body_arg_blocker.py +30 -0
  31. package/hooks/blocking/md_to_html_blocker.py +119 -0
  32. package/hooks/blocking/pr_description_enforcer.py +57 -22
  33. package/hooks/blocking/test_bot_mention_comment_blocker.py +131 -0
  34. package/hooks/blocking/test_code_rules_enforcer.py +21 -0
  35. package/hooks/blocking/test_code_rules_enforcer_any_exempt_files.py +70 -0
  36. package/hooks/blocking/test_code_rules_enforcer_any_imports_and_cast.py +92 -0
  37. package/hooks/blocking/test_code_rules_enforcer_banned_import_alias.py +143 -0
  38. package/hooks/blocking/test_code_rules_enforcer_banned_prefixes.py +152 -0
  39. package/hooks/blocking/test_code_rules_enforcer_bare_except.py +120 -0
  40. package/hooks/blocking/test_code_rules_enforcer_boundary_types.py +175 -0
  41. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +0 -1
  42. package/hooks/blocking/test_code_rules_enforcer_collection_prefix.py +50 -0
  43. package/hooks/blocking/test_code_rules_enforcer_docstring_format.py +255 -0
  44. package/hooks/blocking/test_code_rules_enforcer_inline_tuple_string_magic.py +130 -0
  45. package/hooks/blocking/test_code_rules_enforcer_stub_implementations.py +141 -0
  46. package/hooks/blocking/test_code_rules_enforcer_test_branching.py +143 -0
  47. package/hooks/blocking/test_code_rules_enforcer_thin_wrapper_files.py +169 -0
  48. package/hooks/blocking/test_code_rules_enforcer_todo_markers.py +99 -0
  49. package/hooks/blocking/test_code_rules_enforcer_typed_dict_pairs.py +141 -0
  50. package/hooks/blocking/test_convergence_gate_blocker.py +63 -0
  51. package/hooks/blocking/test_destructive_command_blocker.py +146 -0
  52. package/hooks/blocking/test_destructive_command_blocker_no_verify.py +102 -0
  53. package/hooks/blocking/test_gh_body_arg_blocker.py +45 -0
  54. package/hooks/blocking/test_md_to_html_blocker.py +317 -0
  55. package/hooks/blocking/test_pr_description_enforcer.py +69 -8
  56. package/hooks/config/any_type_config.py +7 -0
  57. package/hooks/config/banned_identifiers_constants.py +11 -0
  58. package/hooks/config/blocking_check_limits.py +38 -0
  59. package/hooks/config/bot_mention_comment_blocker_constants.py +20 -0
  60. package/hooks/config/code_rules_enforcer_constants.py +53 -0
  61. package/hooks/config/convergence_branch_constants.py +9 -0
  62. package/hooks/config/doc_gist_auto_publish_constants.py +18 -0
  63. package/hooks/config/html_companion_constants.py +20 -0
  64. package/hooks/config/inline_tuple_string_magic_constants.py +22 -0
  65. package/hooks/config/pr_description_enforcer_constants.py +14 -0
  66. package/hooks/config/test_banned_identifiers_constants.py +17 -0
  67. package/hooks/hooks.json +28 -20
  68. package/hooks/pyproject.toml +69 -0
  69. package/hooks/validators/mypy_integration.py +47 -1
  70. package/hooks/validators/run_all_validators.py +3 -3
  71. package/hooks/validators/test_mypy_integration.py +50 -1
  72. package/hooks/workflow/doc_gist_auto_publish.py +144 -0
  73. package/hooks/workflow/md_to_html_companion.py +365 -0
  74. package/hooks/workflow/test_doc_gist_auto_publish.py +117 -0
  75. package/hooks/workflow/test_md_to_html_companion.py +452 -0
  76. package/package.json +1 -1
  77. package/rules/gh-body-file.md +2 -0
  78. package/scripts/Install-SweepEmptyDirs.ps1 +111 -0
  79. package/scripts/check.ps1 +106 -0
  80. package/scripts/config/timing.py +11 -0
  81. package/scripts/sweep_empty_dirs.py +138 -0
  82. package/scripts/sync_to_cursor/rules.py +1 -1
  83. package/scripts/test_sweep_empty_dirs.py +183 -0
  84. package/skills/_shared/pr-loop/prompts/pr-consistency-audit.xml +323 -0
  85. package/skills/_shared/pr-loop/scripts/_cli_utils.py +22 -0
  86. package/skills/_shared/pr-loop/scripts/_path_resolver.py +165 -0
  87. package/skills/_shared/pr-loop/scripts/_xml_utils.py +20 -0
  88. package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +182 -0
  89. package/skills/_shared/pr-loop/scripts/build_fix_prompt.py +185 -0
  90. package/skills/_shared/pr-loop/scripts/config/__init__.py +0 -0
  91. package/skills/_shared/pr-loop/scripts/config/path_resolver_constants.py +78 -0
  92. package/skills/_shared/pr-loop/scripts/init_loop_state.py +135 -0
  93. package/skills/_shared/pr-loop/scripts/teardown_worktrees.py +175 -0
  94. package/skills/_shared/pr-loop/scripts/write_audit_outcomes.py +182 -0
  95. package/skills/_shared/pr-loop/scripts/write_fix_outcomes.py +206 -0
  96. package/skills/bugteam/CONSTRAINTS.md +21 -22
  97. package/skills/bugteam/EXAMPLES.md +3 -3
  98. package/skills/bugteam/PROMPTS.md +227 -67
  99. package/skills/bugteam/SKILL.md +132 -455
  100. package/skills/bugteam/reference/README.md +1 -1
  101. package/skills/bugteam/reference/audit-and-teammates.md +112 -39
  102. package/skills/bugteam/reference/audit-contract.md +4 -22
  103. package/skills/bugteam/reference/copilot-gap-analysis.md +8 -5
  104. package/skills/bugteam/reference/design-rationale.md +2 -2
  105. package/skills/bugteam/reference/github-pr-reviews.md +50 -57
  106. package/skills/bugteam/reference/obstacles/audit-assign-ids.md +13 -0
  107. package/skills/bugteam/reference/obstacles/audit-capture-excerpts.md +13 -0
  108. package/skills/bugteam/reference/obstacles/audit-walk-categories.md +13 -0
  109. package/skills/bugteam/reference/obstacles/audit-write-xml.md +13 -0
  110. package/skills/bugteam/reference/obstacles/fix-append-summary.md +13 -0
  111. package/skills/bugteam/reference/obstacles/fix-apply-fixes.md +13 -0
  112. package/skills/bugteam/reference/obstacles/fix-git-add-commit.md +13 -0
  113. package/skills/bugteam/reference/obstacles/fix-git-push.md +13 -0
  114. package/skills/bugteam/reference/obstacles/fix-post-reply.md +13 -0
  115. package/skills/bugteam/reference/obstacles/fix-publish-summary.md +13 -0
  116. package/skills/bugteam/reference/obstacles/fix-py-compile.md +13 -0
  117. package/skills/bugteam/reference/obstacles/fix-read-files.md +13 -0
  118. package/skills/bugteam/reference/obstacles/fix-resolve-thread.md +13 -0
  119. package/skills/bugteam/reference/obstacles/fix-test-suite.md +13 -0
  120. package/skills/bugteam/reference/obstacles/fix-violation-count.md +13 -0
  121. package/skills/bugteam/reference/obstacles/fix-write-xml.md +13 -0
  122. package/skills/bugteam/reference/team-setup.md +111 -9
  123. package/skills/bugteam/reference/teardown-publish-permissions.md +39 -8
  124. package/skills/bugteam/scripts/README.md +60 -0
  125. package/skills/bugteam/scripts/_claude_permissions_common.py +358 -0
  126. package/skills/bugteam/scripts/bugteam_code_rules_gate.py +976 -0
  127. package/skills/bugteam/scripts/bugteam_fix_hookspath.py +375 -0
  128. package/skills/bugteam/scripts/bugteam_preflight.py +328 -0
  129. package/skills/bugteam/scripts/config/bugteam_code_rules_gate_constants.py +25 -0
  130. package/skills/bugteam/scripts/config/bugteam_fix_hookspath_constants.py +26 -0
  131. package/skills/bugteam/scripts/config/bugteam_preflight_constants.py +35 -0
  132. package/skills/bugteam/scripts/config/claude_permissions_common_constants.py +20 -0
  133. package/skills/bugteam/scripts/config/probe_code_rules_enforcer_check_constants.py +12 -0
  134. package/skills/bugteam/scripts/config/windows_safe_rmtree_constants.py +7 -0
  135. package/skills/bugteam/scripts/grant_project_claude_permissions.py +175 -0
  136. package/skills/bugteam/scripts/probe_code_rules_enforcer_check.py +107 -0
  137. package/skills/bugteam/scripts/revoke_project_claude_permissions.py +220 -0
  138. package/skills/bugteam/scripts/test__claude_permissions_common.py +112 -0
  139. package/skills/bugteam/scripts/test_bugteam_code_rules_gate.py +400 -0
  140. package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +384 -0
  141. package/skills/bugteam/scripts/test_bugteam_preflight.py +309 -0
  142. package/skills/bugteam/scripts/test_claude_permissions_common.py +195 -0
  143. package/skills/bugteam/scripts/test_grant_project_claude_permissions.py +55 -0
  144. package/skills/bugteam/scripts/test_probe_code_rules_enforcer_check.py +76 -0
  145. package/skills/bugteam/scripts/test_revoke_project_claude_permissions.py +55 -0
  146. package/skills/bugteam/scripts/test_windows_safe_rmtree.py +108 -0
  147. package/skills/bugteam/scripts/windows_safe_rmtree.py +100 -0
  148. package/skills/bugteam/test_skill_additions.py +1 -11
  149. package/skills/code/SKILL.md +176 -0
  150. package/skills/copilot-review/SKILL.md +16 -0
  151. package/skills/doc-gist/SKILL.md +99 -0
  152. package/skills/doc-gist/references/examples/01-exploration-code-approaches.html +453 -0
  153. package/skills/doc-gist/references/examples/02-exploration-visual-designs.html +515 -0
  154. package/skills/doc-gist/references/examples/03-code-review-pr.html +638 -0
  155. package/skills/doc-gist/references/examples/04-code-understanding.html +491 -0
  156. package/skills/doc-gist/references/examples/05-design-system.html +629 -0
  157. package/skills/doc-gist/references/examples/06-component-variants.html +605 -0
  158. package/skills/doc-gist/references/examples/07-prototype-animation.html +455 -0
  159. package/skills/doc-gist/references/examples/08-prototype-interaction.html +396 -0
  160. package/skills/doc-gist/references/examples/09-slide-deck.html +592 -0
  161. package/skills/doc-gist/references/examples/10-svg-illustrations.html +492 -0
  162. package/skills/doc-gist/references/examples/11-status-report.html +528 -0
  163. package/skills/doc-gist/references/examples/12-incident-report.html +596 -0
  164. package/skills/doc-gist/references/examples/13-flowchart-diagram.html +395 -0
  165. package/skills/doc-gist/references/examples/14-research-feature-explainer.html +381 -0
  166. package/skills/doc-gist/references/examples/15-research-concept-explainer.html +368 -0
  167. package/skills/doc-gist/references/examples/16-implementation-plan.html +702 -0
  168. package/skills/doc-gist/references/examples/17-pr-writeup.html +595 -0
  169. package/skills/doc-gist/references/examples/18-editor-triage-board.html +573 -0
  170. package/skills/doc-gist/references/examples/19-editor-feature-flags.html +663 -0
  171. package/skills/doc-gist/references/examples/20-editor-prompt-tuner.html +722 -0
  172. package/skills/doc-gist/references/examples/README.md +5 -0
  173. package/skills/doc-gist/scripts/config/__init__.py +0 -0
  174. package/skills/doc-gist/scripts/config/gist_upload_constants.py +16 -0
  175. package/skills/doc-gist/scripts/gist_upload.py +177 -0
  176. package/skills/doc-gist/scripts/test_gist_upload.py +51 -0
  177. package/skills/findbugs/SKILL.md +96 -2
  178. package/skills/monitor-open-prs/SKILL.md +14 -32
  179. package/skills/monitor-open-prs/test_skill_contract.py +0 -11
  180. package/skills/pr-consistency-audit/SKILL.md +112 -0
  181. package/skills/pr-consistency-audit/reference/detection-rules.md +96 -0
  182. package/skills/pr-consistency-audit/reference/illustrations.md +78 -0
  183. package/skills/pr-converge/SKILL.md +229 -23
  184. package/skills/pr-converge/config/__init__.py +0 -0
  185. package/skills/pr-converge/config/constants.py +63 -0
  186. package/skills/pr-converge/reference/convergence-gates.md +138 -44
  187. package/skills/pr-converge/reference/examples.md +43 -11
  188. package/skills/pr-converge/reference/fix-protocol.md +6 -5
  189. package/skills/pr-converge/reference/ground-rules.md +5 -3
  190. package/skills/pr-converge/reference/multi-pr-orchestration.md +44 -19
  191. package/skills/pr-converge/reference/obstacles/fix-post-replies.md +13 -0
  192. package/skills/pr-converge/reference/obstacles/fix-publish-summary.md +13 -0
  193. package/skills/pr-converge/reference/obstacles/fix-push.md +13 -0
  194. package/skills/pr-converge/reference/obstacles/fix-read-filelines.md +13 -0
  195. package/skills/pr-converge/reference/obstacles/fix-reset-state.md +13 -0
  196. package/skills/pr-converge/reference/obstacles/fix-resolve-threads.md +13 -0
  197. package/skills/pr-converge/reference/obstacles/fix-spawn-clean-coder.md +13 -0
  198. package/skills/pr-converge/reference/obstacles/fix-stage-commit.md +13 -0
  199. package/skills/pr-converge/reference/obstacles/fix-trigger-bugbot.md +13 -0
  200. package/skills/pr-converge/reference/obstacles/fix-write-test.md +13 -0
  201. package/skills/pr-converge/reference/per-tick.md +107 -31
  202. package/skills/pr-converge/reference/state-schema.md +22 -1
  203. package/skills/pr-converge/reference/stop-conditions.md +9 -7
  204. package/skills/pr-converge/scripts/README.md +34 -46
  205. package/skills/pr-converge/scripts/check_bugbot_ci.py +279 -0
  206. package/skills/pr-converge/scripts/check_convergence.py +497 -0
  207. package/skills/pr-converge/scripts/check_pending_reviews.py +154 -0
  208. package/skills/pr-converge/scripts/config/pr_converge_constants.py +118 -0
  209. package/skills/pr-converge/scripts/fetch_copilot_reviews.py +134 -0
  210. package/skills/pr-converge/scripts/post_fix_reply.py +168 -0
  211. package/skills/pr-converge/scripts/test_check_bugbot_ci.py +312 -0
  212. package/skills/pr-converge/workflows/schedule-wakeup-loop.md +5 -12
  213. package/skills/qbug/SKILL.md +157 -27
  214. package/skills/session-log/SKILL.md +216 -114
  215. package/skills/session-tidy/SKILL.md +1 -1
  216. package/skills/skill-builder/SKILL.md +138 -56
  217. package/skills/skill-builder/references/delegation-map.md +72 -113
  218. package/skills/skill-builder/references/progressive-disclosure.md +122 -0
  219. package/skills/skill-builder/references/self-audit-checklist.md +92 -0
  220. package/skills/skill-builder/references/skill-types.md +228 -0
  221. package/skills/skill-builder/references/thariq-x-post-skills.json +33 -0
  222. package/skills/skill-builder/templates/gap-analysis.md +15 -8
  223. package/skills/skill-builder/workflows/improve-skill.md +86 -57
  224. package/skills/skill-builder/workflows/new-skill.md +80 -168
  225. package/skills/skill-builder/workflows/polish-skill.md +78 -54
  226. package/skills/structure-prompt/SKILL.md +50 -0
  227. package/skills/structure-prompt/reference/adversarial-tuning.md +62 -0
  228. package/skills/structure-prompt/reference/block-classification.md +27 -0
  229. package/skills/structure-prompt/reference/canonical-case.md +48 -0
  230. package/skills/structure-prompt/reference/citation-depth.md +70 -0
  231. package/skills/structure-prompt/reference/cleanup.md +33 -0
  232. package/skills/structure-prompt/reference/constraints.md +33 -0
  233. package/skills/structure-prompt/reference/directives.md +37 -0
  234. package/skills/structure-prompt/reference/examples.md +72 -0
  235. package/skills/structure-prompt/reference/instantiation.md +51 -0
  236. package/skills/structure-prompt/reference/output-contract.md +72 -0
  237. package/skills/structure-prompt/reference/per-category.md +23 -0
  238. package/skills/structure-prompt/reference/persona.md +38 -0
  239. package/skills/structure-prompt/reference/research.md +33 -0
  240. package/skills/structure-prompt/reference/structure.md +28 -0
  241. package/agents/code-standards-agent.md +0 -93
  242. package/agents/groq-coder.md +0 -113
  243. package/agents/plan-executor.md +0 -226
  244. package/agents/project-docs-analyzer.md +0 -53
  245. package/agents/project-structure-organizer-agent.md +0 -72
  246. package/agents/skill-to-agent-converter.md +0 -370
  247. package/agents/skill-writer-agent.md +0 -470
  248. package/agents/user-docs-writer.md +0 -67
  249. package/agents/workflow-visual-documenter.md +0 -82
  250. package/commands/readability-review.md +0 -20
  251. package/hooks/mypy.ini +0 -2
  252. package/hooks/notification/attention_needed_notify.py +0 -71
  253. package/hooks/notification/claude_notification_handler.py +0 -67
  254. package/hooks/notification/notification_utils.py +0 -267
  255. package/hooks/notification/subagent_complete_notify.py +0 -381
  256. package/hooks/notification/test_attention_needed_notify.py +0 -47
  257. package/hooks/notification/test_claude_notification_handler.py +0 -54
  258. package/hooks/notification/test_notification_utils.py +0 -91
  259. package/hooks/notification/test_subagent_complete_notify.py +0 -79
  260. package/scripts/config/groq_bugteam_config.py +0 -230
  261. package/scripts/config/test_groq_bugteam_config.py +0 -83
  262. package/scripts/config/test_spec_implementer_prompt.py +0 -32
  263. package/scripts/groq_bugteam.README.md +0 -131
  264. package/scripts/groq_bugteam.py +0 -647
  265. package/scripts/groq_bugteam_dotenv.py +0 -40
  266. package/scripts/groq_bugteam_spec.py +0 -226
  267. package/scripts/test_groq_bugteam.py +0 -529
  268. package/scripts/test_groq_bugteam_apply_fix_from_spec.py +0 -426
  269. package/scripts/test_groq_bugteam_dotenv.py +0 -66
  270. package/scripts/test_groq_bugteam_spec.py +0 -338
  271. package/skills/bugteam/SKILL_EVALS.md +0 -309
  272. package/skills/dream/SKILL.md +0 -118
  273. package/skills/ingest/SKILL.md +0 -40
  274. package/skills/npm-creator/SKILL.md +0 -187
  275. package/skills/readability-review/SKILL.md +0 -127
  276. package/skills/resume-review/SKILL.md +0 -261
  277. package/skills/rule-audit/SKILL.md +0 -307
  278. package/skills/rule-creator/SKILL.md +0 -150
  279. package/skills/searching-obsidian-vault/SKILL.md +0 -131
  280. package/skills/skill-writer/REFERENCE.md +0 -284
  281. package/skills/skill-writer/SKILL.md +0 -222
  282. 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,7 @@
1
+ """Configuration constants for the bugteam windows_safe_rmtree script."""
2
+
3
+ EXIT_CODE_USAGE_ERROR: int = 2
4
+ EXIT_CODE_REMOVE_TREE_FAILURE: int = 3
5
+ EXPECTED_ARGUMENT_COUNT: int = 2
6
+ ONEXC_PYTHON_MAJOR_VERSION: int = 3
7
+ ONEXC_PYTHON_MINOR_VERSION: int = 12
@@ -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))