claude-dev-env 1.41.0 → 1.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/CLAUDE.md +8 -0
  2. package/_shared/pr-loop/scripts/_claude_permissions_common.py +232 -8
  3. package/_shared/pr-loop/scripts/code_rules_gate.py +293 -8
  4. package/_shared/pr-loop/scripts/fix_hookspath.py +96 -5
  5. package/_shared/pr-loop/scripts/grant_project_claude_permissions.py +124 -20
  6. package/_shared/pr-loop/scripts/post_audit_thread.py +4 -4
  7. package/_shared/pr-loop/scripts/pr_loop_shared_constants/claude_permissions_constants.py +90 -0
  8. package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/claude_settings_keys_constants.py +2 -0
  9. package/_shared/pr-loop/scripts/preflight.py +13 -31
  10. package/_shared/pr-loop/scripts/reviews_disabled.py +2 -16
  11. package/_shared/pr-loop/scripts/revoke_project_claude_permissions.py +76 -33
  12. package/_shared/pr-loop/scripts/tests/conftest.py +1 -51
  13. package/_shared/pr-loop/scripts/tests/test_agent_config_carveout.py +385 -0
  14. package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +4 -2
  15. package/_shared/pr-loop/scripts/tests/test_claude_permissions_constants.py +37 -2
  16. package/_shared/pr-loop/scripts/tests/test_claude_settings_keys_constants.py +4 -2
  17. package/_shared/pr-loop/scripts/tests/test_code_rules_gate_constants.py +4 -2
  18. package/_shared/pr-loop/scripts/tests/test_fix_hookspath_constants.py +6 -2
  19. package/_shared/pr-loop/scripts/tests/test_grant_project_claude_permissions.py +2 -2
  20. package/_shared/pr-loop/scripts/tests/test_post_audit_thread.py +1 -2
  21. package/_shared/pr-loop/scripts/tests/test_post_audit_thread_constants.py +4 -2
  22. package/_shared/pr-loop/scripts/tests/test_preflight.py +17 -52
  23. package/_shared/pr-loop/scripts/tests/test_preflight_constants.py +6 -2
  24. package/_shared/pr-loop/scripts/tests/test_revoke_project_claude_permissions.py +5 -3
  25. package/agents/pr-description-writer.md +50 -140
  26. package/docs/PR_DESCRIPTION_GUIDE.md +101 -102
  27. package/hooks/_gh_pr_author_swap_utils.py +1 -1
  28. package/hooks/blocking/bot_mention_comment_blocker.py +4 -10
  29. package/hooks/blocking/code_rules_enforcer.py +217 -99
  30. package/hooks/blocking/code_rules_path_utils.py +8 -1
  31. package/hooks/blocking/destructive_command_blocker.py +1 -1
  32. package/hooks/blocking/es_exe_path_rewriter.py +7 -13
  33. package/hooks/blocking/gh_body_arg_blocker.py +6 -1
  34. package/hooks/blocking/gh_pr_author_enforcer.py +5 -5
  35. package/hooks/blocking/gh_pr_author_restore.py +5 -5
  36. package/hooks/blocking/hedging_language_blocker.py +4 -10
  37. package/hooks/blocking/md_path_exemptions.py +205 -0
  38. package/hooks/blocking/md_to_html_blocker.py +48 -20
  39. package/hooks/blocking/pr_converge_bugteam_enforcer.py +5 -11
  40. package/hooks/blocking/pr_description_enforcer.py +626 -41
  41. package/hooks/blocking/question_to_user_enforcer.py +4 -10
  42. package/hooks/blocking/state_description_blocker.py +6 -12
  43. package/hooks/blocking/tdd_enforcer.py +1 -1
  44. package/hooks/blocking/test_bot_mention_comment_blocker.py +1 -1
  45. package/hooks/blocking/test_code_rules_enforcer.py +3 -3
  46. package/hooks/blocking/test_code_rules_enforcer_any_exempt_files.py +1 -1
  47. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +0 -2
  48. package/hooks/blocking/test_code_rules_enforcer_comment_string_awareness.py +184 -0
  49. package/hooks/blocking/test_code_rules_enforcer_type_checking_scope.py +82 -0
  50. package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +29 -29
  51. package/hooks/blocking/test_gh_body_arg_blocker.py +7 -8
  52. package/hooks/blocking/test_gh_pr_author_enforcer.py +1 -1
  53. package/hooks/blocking/test_gh_pr_author_restore.py +1 -1
  54. package/hooks/blocking/test_hedging_language_blocker.py +2 -2
  55. package/hooks/blocking/test_md_to_html_blocker.py +463 -8
  56. package/hooks/blocking/test_pr_converge_bugteam_enforcer.py +1 -1
  57. package/hooks/blocking/test_pr_description_enforcer.py +1210 -13
  58. package/hooks/blocking/test_question_to_user_enforcer.py +1 -1
  59. package/hooks/blocking/windows_rmtree_blocker.py +5 -11
  60. package/hooks/diagnostic/hook_log_extractor.py +1 -1
  61. package/hooks/diagnostic/hook_log_init.py +1 -1
  62. package/hooks/diagnostic/hook_log_stop_wrapper.py +1 -1
  63. package/hooks/diagnostic/test_hook_log_extractor.py +1 -1
  64. package/hooks/diagnostic/test_hook_log_init.py +2 -2
  65. package/hooks/diagnostic/test_hook_log_stop_wrapper.py +1 -1
  66. package/hooks/git-hooks/gate_utils.py +1 -1
  67. package/hooks/git-hooks/pre_commit.py +1 -1
  68. package/hooks/git-hooks/pre_push.py +1 -1
  69. package/hooks/git-hooks/test_config.py +5 -5
  70. package/hooks/git-hooks/test_pre_push.py +6 -6
  71. package/hooks/{config → hooks_constants}/code_rules_enforcer_constants.py +37 -0
  72. package/hooks/hooks_constants/code_rules_path_utils_constants.py +28 -0
  73. package/hooks/hooks_constants/md_to_html_blocker_constants.py +82 -0
  74. package/hooks/{config → hooks_constants}/pr_converge_bugteam_enforcer_state.py +1 -1
  75. package/hooks/hooks_constants/pr_description_enforcer_constants.py +154 -0
  76. package/hooks/{config → hooks_constants}/pre_tool_use_stdin.py +1 -1
  77. package/hooks/{config → hooks_constants}/project_paths_reader.py +2 -2
  78. package/hooks/{config → hooks_constants}/test_banned_identifiers_constants.py +1 -1
  79. package/hooks/{config → hooks_constants}/test_dynamic_stderr_handler.py +1 -1
  80. package/hooks/{config → hooks_constants}/test_hardcoded_user_path_constants.py +1 -1
  81. package/hooks/{config → hooks_constants}/test_hook_log_extractor_constants.py +2 -2
  82. package/hooks/hooks_constants/test_md_to_html_blocker_constants.py +110 -0
  83. package/hooks/{config → hooks_constants}/test_messages.py +2 -6
  84. package/hooks/{config → hooks_constants}/test_path_rewriter_constants.py +1 -1
  85. package/hooks/hooks_constants/test_pr_description_enforcer_constants.py +292 -0
  86. package/hooks/{config → hooks_constants}/test_pre_tool_use_stdin.py +2 -2
  87. package/hooks/{config → hooks_constants}/test_project_paths_reader.py +3 -3
  88. package/hooks/{config → hooks_constants}/test_session_env_cleanup_constants.py +1 -1
  89. package/hooks/{config → hooks_constants}/test_setup_project_paths_constants.py +2 -2
  90. package/hooks/{config → hooks_constants}/test_unused_module_import_constants.py +1 -1
  91. package/hooks/lifecycle/pr_converge_bugteam_skill_tracker.py +5 -11
  92. package/hooks/lifecycle/test_pr_converge_bugteam_skill_tracker.py +1 -1
  93. package/hooks/session/gh_pr_author_session_cleanup.py +5 -6
  94. package/hooks/session/session_env_cleanup.py +4 -10
  95. package/hooks/session/test_gh_pr_author_session_cleanup.py +1 -1
  96. package/hooks/session/test_untracked_repo_detector.py +2 -2
  97. package/hooks/session/untracked_repo_detector.py +6 -12
  98. package/hooks/test__gh_pr_author_swap_utils.py +1 -1
  99. package/hooks/validators/run_all_validators.py +16 -5
  100. package/hooks/validators/test_output_formatter.py +46 -0
  101. package/hooks/workflow/doc_gist_auto_publish.py +1 -1
  102. package/hooks/workflow/md_to_html_companion.py +8 -15
  103. package/hooks/workflow/test_md_to_html_companion.py +184 -23
  104. package/package.json +1 -1
  105. package/rules/ask-user-question-required.md +1 -1
  106. package/rules/vault-context.md +1 -1
  107. package/scripts/{config → dev_env_scripts_constants}/timing.py +1 -1
  108. package/scripts/setup_project_paths.py +49 -11
  109. package/scripts/sweep_empty_dirs.py +10 -1
  110. package/scripts/test_setup_project_paths.py +2 -2
  111. package/scripts/test_sweep_empty_dirs.py +2 -6
  112. package/skills/_shared/pr-loop/scripts/_path_resolver.py +1 -1
  113. package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +1 -1
  114. package/skills/_shared/pr-loop/scripts/build_fix_prompt.py +1 -1
  115. package/skills/_shared/pr-loop/scripts/init_loop_state.py +1 -1
  116. package/skills/_shared/pr-loop/scripts/teardown_worktrees.py +1 -1
  117. package/skills/_shared/pr-loop/scripts/write_audit_outcomes.py +2 -2
  118. package/skills/_shared/pr-loop/scripts/write_fix_outcomes.py +2 -2
  119. package/skills/bugteam/PROMPTS.md +1 -1
  120. package/skills/bugteam/SKILL.md +1 -1
  121. package/skills/bugteam/reference/github-pr-reviews.md +1 -1
  122. package/skills/bugteam/scripts/{_claude_permissions_common.py → _bugteam_permissions_common.py} +110 -13
  123. package/skills/bugteam/scripts/bugteam_code_rules_gate.py +1 -13
  124. package/skills/bugteam/scripts/bugteam_fix_hookspath.py +1 -16
  125. package/skills/bugteam/scripts/bugteam_preflight.py +1 -13
  126. package/skills/bugteam/scripts/bugteam_scripts_constants/claude_permissions_common_constants.py +69 -0
  127. package/skills/bugteam/scripts/grant_project_claude_permissions.py +117 -12
  128. package/skills/bugteam/scripts/probe_code_rules_enforcer_check.py +1 -1
  129. package/skills/bugteam/scripts/reflow_skill_md.py +1 -1
  130. package/skills/bugteam/scripts/revoke_project_claude_permissions.py +71 -25
  131. package/skills/bugteam/scripts/{test__claude_permissions_common.py → test__bugteam_permissions_common.py} +4 -4
  132. package/skills/bugteam/scripts/test_agent_config_carveout.py +356 -0
  133. package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +0 -26
  134. package/skills/bugteam/scripts/{test_claude_permissions_common.py → test_bugteam_permissions_common.py} +3 -66
  135. package/skills/bugteam/scripts/test_bugteam_preflight.py +2 -27
  136. package/skills/bugteam/scripts/windows_safe_rmtree.py +1 -1
  137. package/skills/doc-gist/SKILL.md +1 -1
  138. package/skills/doc-gist/scripts/gist_upload.py +1 -1
  139. package/skills/implement/SKILL.md +66 -0
  140. package/skills/implement/scripts/append_note.py +133 -0
  141. package/skills/implement/scripts/implement_scripts_constants/__init__.py +0 -0
  142. package/skills/implement/scripts/implement_scripts_constants/notes_constants.py +12 -0
  143. package/skills/implement/scripts/test_append_note.py +191 -0
  144. package/skills/pr-converge/pr_converge_skill_constants/__init__.py +0 -0
  145. package/skills/pr-converge/{config → pr_converge_skill_constants}/constants.py +6 -1
  146. package/skills/pr-converge/scripts/check_bugbot_ci.py +2 -2
  147. package/skills/pr-converge/scripts/check_convergence.py +175 -29
  148. package/skills/pr-converge/scripts/check_pending_reviews.py +2 -2
  149. package/skills/pr-converge/scripts/fetch_copilot_reviews.py +2 -2
  150. package/skills/pr-converge/scripts/post_fix_reply.py +2 -2
  151. package/skills/pr-converge/scripts/pr_converge_scripts_constants/__init__.py +0 -0
  152. package/skills/pr-converge/scripts/{config → pr_converge_scripts_constants}/pr_converge_constants.py +1 -1
  153. package/skills/pr-converge/scripts/reflow_skill_md.py +90 -16
  154. package/skills/pr-converge/scripts/test_check_bugbot_ci.py +1 -1
  155. package/skills/pr-converge/scripts/test_check_convergence.py +324 -0
  156. package/skills/pr-converge/scripts/test_reflow_skill_md.py +0 -31
  157. package/skills/refine/SKILL.md +257 -0
  158. package/skills/refine/templates/implementation-notes-template.html +56 -0
  159. package/skills/refine/templates/plan-template.md +60 -0
  160. package/skills/session-log/SKILL.md +98 -233
  161. package/_shared/pr-loop/scripts/config/claude_permissions_constants.py +0 -36
  162. package/hooks/config/pr_description_enforcer_constants.py +0 -19
  163. package/hooks/config/test_pr_description_enforcer_constants.py +0 -82
  164. package/skills/bugteam/scripts/config/claude_permissions_common_constants.py +0 -20
  165. package/skills/bugteam/scripts/test_grant_project_claude_permissions.py +0 -55
  166. package/skills/bugteam/scripts/test_revoke_project_claude_permissions.py +0 -55
  167. package/skills/pr-converge/scripts/evict_cached_config_modules.py +0 -20
  168. package/skills/pr-converge/scripts/test_evict_cached_config_modules.py +0 -22
  169. /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/__init__.py +0 -0
  170. /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/code_rules_gate_constants.py +0 -0
  171. /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/fix_hookspath_constants.py +0 -0
  172. /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/post_audit_thread_constants.py +0 -0
  173. /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/preflight_constants.py +0 -0
  174. /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/reviews_disabled_constants.py +0 -0
  175. /package/hooks/git-hooks/{config.py → git_hooks_constants/__init__.py} +0 -0
  176. /package/hooks/{config → hooks_constants}/__init__.py +0 -0
  177. /package/hooks/{config → hooks_constants}/any_type_config.py +0 -0
  178. /package/hooks/{config → hooks_constants}/banned_identifiers_constants.py +0 -0
  179. /package/hooks/{config → hooks_constants}/blocking_check_limits.py +0 -0
  180. /package/hooks/{config → hooks_constants}/bot_mention_comment_blocker_constants.py +0 -0
  181. /package/hooks/{config → hooks_constants}/convergence_branch_constants.py +0 -0
  182. /package/hooks/{config → hooks_constants}/doc_gist_auto_publish_constants.py +0 -0
  183. /package/hooks/{config → hooks_constants}/dynamic_stderr_handler.py +0 -0
  184. /package/hooks/{config → hooks_constants}/gh_pr_author_swap_constants.py +0 -0
  185. /package/hooks/{config → hooks_constants}/hardcoded_user_path_constants.py +0 -0
  186. /package/hooks/{config → hooks_constants}/hook_log_extractor_constants.py +0 -0
  187. /package/hooks/{config → hooks_constants}/html_companion_constants.py +0 -0
  188. /package/hooks/{config → hooks_constants}/inline_tuple_string_magic_constants.py +0 -0
  189. /package/hooks/{config → hooks_constants}/messages.py +0 -0
  190. /package/hooks/{config → hooks_constants}/path_rewriter_constants.py +0 -0
  191. /package/hooks/{config → hooks_constants}/pr_converge_bugteam_enforcer_constants.py +0 -0
  192. /package/hooks/{config → hooks_constants}/session_env_cleanup_constants.py +0 -0
  193. /package/hooks/{config → hooks_constants}/setup_project_paths_constants.py +0 -0
  194. /package/hooks/{config → hooks_constants}/state_description_blocker_constants.py +0 -0
  195. /package/hooks/{config → hooks_constants}/stuttering_check_config.py +0 -0
  196. /package/hooks/{config → hooks_constants}/stuttering_import_binding_constants.py +0 -0
  197. /package/hooks/{config → hooks_constants}/sys_path_insert_constants.py +0 -0
  198. /package/hooks/{config → hooks_constants}/unused_module_import_constants.py +0 -0
  199. /package/hooks/{config → hooks_constants}/windows_rmtree_blocker_constants.py +0 -0
  200. /package/{skills/_shared/pr-loop/scripts/config → hooks/lifecycle}/__init__.py +0 -0
  201. /package/{skills/bugteam/scripts/config → hooks/session}/__init__.py +0 -0
  202. /package/scripts/{config → dev_env_scripts_constants}/__init__.py +0 -0
  203. /package/skills/{doc-gist/scripts/config → _shared/pr-loop/scripts/skills_pr_loop_constants}/__init__.py +0 -0
  204. /package/skills/_shared/pr-loop/scripts/{config → skills_pr_loop_constants}/path_resolver_constants.py +0 -0
  205. /package/skills/{pr-converge/config → bugteam/scripts/bugteam_scripts_constants}/__init__.py +0 -0
  206. /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/bugteam_code_rules_gate_constants.py +0 -0
  207. /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/bugteam_fix_hookspath_constants.py +0 -0
  208. /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/bugteam_preflight_constants.py +0 -0
  209. /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/probe_code_rules_enforcer_check_constants.py +0 -0
  210. /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/reflow_skill_md_constants.py +0 -0
  211. /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/windows_safe_rmtree_constants.py +0 -0
  212. /package/skills/{pr-converge/scripts/config → doc-gist/scripts/doc_gist_scripts_constants}/__init__.py +0 -0
  213. /package/skills/doc-gist/scripts/{config → doc_gist_scripts_constants}/gist_upload_constants.py +0 -0
  214. /package/skills/pr-converge/scripts/{config → pr_converge_scripts_constants}/reflow_skill_md_constants.py +0 -0
@@ -16,7 +16,7 @@ import os
16
16
  import sys
17
17
  import time
18
18
 
19
- from config.timing import DEFAULT_AGE_SECONDS, DEFAULT_POLL_INTERVAL
19
+ from dev_env_scripts_constants.timing import DEFAULT_AGE_SECONDS, DEFAULT_POLL_INTERVAL
20
20
 
21
21
 
22
22
  def _positive_int(raw_argument: str) -> int:
@@ -42,6 +42,15 @@ def sweep(root: str, min_age_seconds: int) -> list[str]:
42
42
  Walks bottom-up so nested empty directories are cleaned from the leaves
43
43
  inward. Relies on os.rmdir to fail harmlessly for non-empty directories
44
44
  instead of checking snapshotted subdirectory lists.
45
+
46
+ Args:
47
+ root: Root directory to walk; directories below the root are
48
+ candidates for removal.
49
+ min_age_seconds: Minimum age in seconds for a directory to qualify
50
+ for removal.
51
+
52
+ Returns:
53
+ Sorted list of directory paths that were removed during the sweep.
45
54
  """
46
55
 
47
56
  all_removed: list[str] = []
@@ -22,8 +22,8 @@ for each_sys_path_entry in (
22
22
 
23
23
  import setup_project_paths as setup
24
24
  import untracked_repo_detector as detector_module
25
- from config.project_paths_reader import registry_file_path
26
- from config.setup_project_paths_constants import (
25
+ from hooks_constants.project_paths_reader import registry_file_path
26
+ from hooks_constants.setup_project_paths_constants import (
27
27
  ABORTED_NOTHING_WRITTEN_MESSAGE,
28
28
  CONFIRMATION_PROMPT_TEXT,
29
29
  ES_EXE_FOLDERS_ONLY_QUERY_ARGUMENTS,
@@ -15,10 +15,6 @@ _SCRIPTS_DIR = Path(os.path.abspath(__file__)).parent
15
15
  if str(_SCRIPTS_DIR) not in sys.path:
16
16
  sys.path.insert(0, str(_SCRIPTS_DIR))
17
17
 
18
- for _cached in list(sys.modules):
19
- if _cached == "config" or _cached.startswith("config."):
20
- del sys.modules[_cached]
21
-
22
18
  from sweep_empty_dirs import _build_parser, _positive_int, sweep # noqa: E402
23
19
 
24
20
  _OLD_TIMESTAMP = time.time() - 300
@@ -53,7 +49,7 @@ def test_positive_int_rejects_non_integer() -> None:
53
49
 
54
50
 
55
51
  def test_build_parser_sets_age_default_from_timing_config() -> None:
56
- """_build_parser uses DEFAULT_AGE_SECONDS from config.timing as --age default."""
52
+ """_build_parser uses DEFAULT_AGE_SECONDS from dev_env_scripts_constants.timing as --age default."""
57
53
  parser = _build_parser()
58
54
  default_age = parser.get_default("age")
59
55
  assert isinstance(default_age, int)
@@ -61,7 +57,7 @@ def test_build_parser_sets_age_default_from_timing_config() -> None:
61
57
 
62
58
 
63
59
  def test_build_parser_sets_interval_default_from_timing_config() -> None:
64
- """_build_parser uses DEFAULT_POLL_INTERVAL from config.timing as --interval default."""
60
+ """_build_parser uses DEFAULT_POLL_INTERVAL from dev_env_scripts_constants.timing as --interval default."""
65
61
  parser = _build_parser()
66
62
  default_interval = parser.get_default("interval")
67
63
  assert isinstance(default_interval, int)
@@ -9,7 +9,7 @@ from __future__ import annotations
9
9
  import tempfile
10
10
  from pathlib import Path
11
11
 
12
- from config.path_resolver_constants import (
12
+ from skills_pr_loop_constants.path_resolver_constants import (
13
13
  DIFF_PATCH_TEMPLATE,
14
14
  FIX_OUTCOME_XML_TEMPLATE,
15
15
  MULTI_PR_SLUG_TEMPLATE,
@@ -19,7 +19,7 @@ if str(_self_dir) not in sys.path:
19
19
  sys.path.insert(0, str(_self_dir))
20
20
 
21
21
  from _xml_utils import emit_pretty_xml
22
- from config.path_resolver_constants import (
22
+ from skills_pr_loop_constants.path_resolver_constants import (
23
23
  ALL_AUDIT_CATEGORY_ENTRIES,
24
24
  ALL_AUDIT_CONSTRAINT_TEXTS,
25
25
  )
@@ -21,7 +21,7 @@ if str(_self_dir) not in sys.path:
21
21
 
22
22
  from _cli_utils import require_file
23
23
  from _xml_utils import emit_pretty_xml
24
- from config.path_resolver_constants import (
24
+ from skills_pr_loop_constants.path_resolver_constants import (
25
25
  ALL_FIX_CONSTRAINT_TEXTS,
26
26
  ALL_FIX_EXECUTION_STEPS,
27
27
  )
@@ -20,7 +20,7 @@ from _path_resolver import (
20
20
  per_pr_workspace,
21
21
  resolve_run_temp_dir,
22
22
  )
23
- from config.path_resolver_constants import LOOP_STATE_JSON_INDENT
23
+ from skills_pr_loop_constants.path_resolver_constants import LOOP_STATE_JSON_INDENT
24
24
 
25
25
 
26
26
  def create_loop_state(
@@ -24,7 +24,7 @@ if str(_self_dir) not in sys.path:
24
24
  sys.path.insert(0, str(_self_dir))
25
25
 
26
26
  from _path_resolver import per_pr_workspace
27
- from config.path_resolver_constants import ALL_PYTHON_ONEXC_VERSION
27
+ from skills_pr_loop_constants.path_resolver_constants import ALL_PYTHON_ONEXC_VERSION
28
28
 
29
29
 
30
30
  def _remove_readonly_attribute(
@@ -19,7 +19,7 @@ if str(_self_dir) not in sys.path:
19
19
  from _cli_utils import require_file
20
20
  from _path_resolver import outcome_xml_path
21
21
  from _xml_utils import emit_pretty_xml
22
- from config.path_resolver_constants import ALL_FINDING_BODY_ELEMENT_KEYS
22
+ from skills_pr_loop_constants.path_resolver_constants import ALL_FINDING_BODY_ELEMENT_KEYS
23
23
 
24
24
 
25
25
  def build_audit_xml(
@@ -70,7 +70,7 @@ def _populate_findings(parent: Element, findings_data: list[dict[str, object]])
70
70
 
71
71
  Scalar finding fields become XML attributes on `<finding>`; the
72
72
  body fields named in `ALL_FINDING_BODY_ELEMENT_KEYS` (defined in
73
- `packages/claude-dev-env/skills/_shared/pr-loop/scripts/config/path_resolver_constants.py`
73
+ `packages/claude-dev-env/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py`
74
74
  and currently `("title", "excerpt", "description")`) become child elements.
75
75
  Nested dicts or lists in scalar slots are flattened to string form
76
76
  so attribute serialization stays well-defined.
@@ -1,7 +1,7 @@
1
1
  """Validate status enum values and write <bugteam_fix> XML at the canonical path.
2
2
 
3
3
  Status enum (canonical source: `ALL_VALID_FIX_STATUSES` in
4
- `packages/claude-dev-env/skills/_shared/pr-loop/scripts/config/path_resolver_constants.py`):
4
+ `packages/claude-dev-env/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py`):
5
5
  fixed | could_not_address | hook_blocked | unverified_fixed.
6
6
 
7
7
  Each outcome's scalar fields become XML attributes on `<outcome>`; the
@@ -27,7 +27,7 @@ if str(_self_dir) not in sys.path:
27
27
  from _cli_utils import require_file
28
28
  from _path_resolver import fix_outcome_xml_path
29
29
  from _xml_utils import emit_pretty_xml
30
- from config.path_resolver_constants import (
30
+ from skills_pr_loop_constants.path_resolver_constants import (
31
31
  ALL_FIX_OUTCOME_BODY_ELEMENT_KEYS,
32
32
  ALL_VALID_FIX_STATUSES,
33
33
  )
@@ -108,7 +108,7 @@ cd into `<worktree_path>` before any git or file operation.
108
108
  teammate does NOT author the inline-comment body directly:
109
109
  `post_audit_thread.py` renders every body from
110
110
  `INLINE_COMMENT_BODY_TEMPLATE` (defined in
111
- [`_shared/pr-loop/scripts/config/post_audit_thread_constants.py`](../../_shared/pr-loop/scripts/config/post_audit_thread_constants.py))
111
+ [`_shared/pr-loop/scripts/pr_loop_shared_constants/post_audit_thread_constants.py`](../../_shared/pr-loop/scripts/pr_loop_shared_constants/post_audit_thread_constants.py))
112
112
  — the template prepends `**[<severity>] <Skill> audit finding**`
113
113
  and renders the suggested-fix block, so a teammate who hand-formats
114
114
  a title or footer wastes the work.
@@ -103,7 +103,7 @@ narrative) becomes `description`, and the suffix starting at `Fix:`
103
103
  When the agent omits the `Fix:` heading on a given finding, write the
104
104
  full `failure_mode` text to BOTH `description` and `fix_summary` so the
105
105
  script's body template (`INLINE_COMMENT_BODY_TEMPLATE` in
106
- [`_shared/pr-loop/scripts/config/post_audit_thread_constants.py`](../../_shared/pr-loop/scripts/config/post_audit_thread_constants.py))
106
+ [`_shared/pr-loop/scripts/pr_loop_shared_constants/post_audit_thread_constants.py`](../../_shared/pr-loop/scripts/pr_loop_shared_constants/post_audit_thread_constants.py))
107
107
  still renders coherently. Set `side="RIGHT"` for every entry. On CLEAN,
108
108
  pass an empty array (`[]`) so the script posts an APPROVE review
109
109
  (GitHub stores it as `state=APPROVED`) with a "no findings" summary and
@@ -22,7 +22,7 @@ python "${CLAUDE_SKILL_DIR}/../../_shared/pr-loop/scripts/post_audit_thread.py"
22
22
 
23
23
  Capture `<head_sha>` via `git rev-parse HEAD` in the subagent cwd immediately before this call so the review attaches to the commit the audit actually scoped.
24
24
 
25
- `--findings-json` points to a JSON file whose root is a list of objects shaped `{path, line, side, severity, description, fix_summary}`. Build it from the merged Shape A findings: finding `file` → `path`. Each finding's `failure_mode` carries the full audit-to-fix handoff text per [`agents/code-quality-agent.md`](../../../agents/code-quality-agent.md); split `failure_mode` at the literal `Fix:` heading so the failure narrative becomes `description` and the suffix beginning at `Fix:` (including the trailing `Validation:` clause) becomes `fix_summary`. When a finding's `failure_mode` omits the `Fix:` heading, write the full text to BOTH `description` and `fix_summary` so the script's body template (`INLINE_COMMENT_BODY_TEMPLATE` in [`packages/claude-dev-env/_shared/pr-loop/scripts/config/post_audit_thread_constants.py`](../../../_shared/pr-loop/scripts/config/post_audit_thread_constants.py)) renders coherently. Set `side="RIGHT"` for every entry. On CLEAN the list is empty (`[]`); on DIRTY the list carries one entry per finding.
25
+ `--findings-json` points to a JSON file whose root is a list of objects shaped `{path, line, side, severity, description, fix_summary}`. Build it from the merged Shape A findings: finding `file` → `path`. Each finding's `failure_mode` carries the full audit-to-fix handoff text per [`agents/code-quality-agent.md`](../../../agents/code-quality-agent.md); split `failure_mode` at the literal `Fix:` heading so the failure narrative becomes `description` and the suffix beginning at `Fix:` (including the trailing `Validation:` clause) becomes `fix_summary`. When a finding's `failure_mode` omits the `Fix:` heading, write the full text to BOTH `description` and `fix_summary` so the script's body template (`INLINE_COMMENT_BODY_TEMPLATE` in [`packages/claude-dev-env/_shared/pr-loop/scripts/pr_loop_shared_constants/post_audit_thread_constants.py`](../../../_shared/pr-loop/scripts/pr_loop_shared_constants/post_audit_thread_constants.py)) renders coherently. Set `side="RIGHT"` for every entry. On CLEAN the list is empty (`[]`); on DIRTY the list carries one entry per finding.
26
26
 
27
27
  The script handles retries internally — 1s / 4s / 16s backoff across four attempts (one initial plus three retries). Exit codes:
28
28
 
@@ -12,27 +12,17 @@ import json
12
12
  import os
13
13
  import stat
14
14
  import sys
15
+ from collections.abc import Callable
15
16
  from pathlib import Path
16
17
  from typing import NoReturn
17
18
 
18
- _previously_cached_config = {}
19
- for each_cached_module_name in [
20
- each_module_key
21
- for each_module_key in list(sys.modules)
22
- if each_module_key == "config" or each_module_key.startswith("config.")
23
- ]:
24
- _previously_cached_config[each_cached_module_name] = sys.modules.pop(
25
- each_cached_module_name
26
- )
27
-
28
- from config.claude_permissions_common_constants import (
19
+ from bugteam_scripts_constants.claude_permissions_common_constants import (
20
+ ALL_TRUST_ENTRY_PROJECT_PATH_BOUNDARY_QUOTE_CHARACTERS,
29
21
  ATOMIC_WRITE_TEMPORARY_SUFFIX,
30
22
  DEFAULT_SETTINGS_FILE_MODE,
31
23
  TEXT_FILE_ENCODING,
32
24
  )
33
25
 
34
- sys.modules.update(_previously_cached_config)
35
-
36
26
 
37
27
  def exit_with_error(message: str) -> NoReturn:
38
28
  print(f"Error: {message}", file=sys.stderr)
@@ -105,6 +95,113 @@ def build_permission_rules(
105
95
  ]
106
96
 
107
97
 
98
+ def build_agent_config_deny_rule(
99
+ tool_name: str, project_path: str, agent_config_path_pattern: str
100
+ ) -> str:
101
+ """Construct a deny rule for a single agent-config path pattern.
102
+
103
+ Args:
104
+ tool_name: The permission tool name (e.g., "Edit", "Write", "Read").
105
+ project_path: The POSIX-style project root path.
106
+ agent_config_path_pattern: The agent-config path pattern under .claude/.
107
+
108
+ Returns:
109
+ The deny rule string Claude Code matches against tool invocations.
110
+ """
111
+ return f"{tool_name}({project_path}/.claude/{agent_config_path_pattern})"
112
+
113
+
114
+ def build_agent_config_deny_rules(
115
+ project_path: str,
116
+ all_permission_allow_tools: tuple[str, ...],
117
+ all_agent_config_path_patterns: tuple[str, ...],
118
+ ) -> list[str]:
119
+ """Construct deny rules covering every tool and pattern pair.
120
+
121
+ Args:
122
+ project_path: The POSIX-style project root path.
123
+ all_permission_allow_tools: Tool names to build deny rules for.
124
+ all_agent_config_path_patterns: Agent-config path patterns to deny under .claude/.
125
+
126
+ Returns:
127
+ List of deny rule strings, one per tool/pattern combination.
128
+ """
129
+ return [
130
+ build_agent_config_deny_rule(each_tool, project_path, each_pattern)
131
+ for each_tool in all_permission_allow_tools
132
+ for each_pattern in all_agent_config_path_patterns
133
+ ]
134
+
135
+
136
+ def _is_project_path_token_at_word_boundary(
137
+ body_after_prefix: str, token_position: int
138
+ ) -> bool:
139
+ if token_position == 0:
140
+ return True
141
+ preceding_character = body_after_prefix[token_position - 1]
142
+ if preceding_character.isspace():
143
+ return True
144
+ return preceding_character in ALL_TRUST_ENTRY_PROJECT_PATH_BOUNDARY_QUOTE_CHARACTERS
145
+
146
+
147
+ def is_trust_entry_for_project(
148
+ candidate_entry: object, project_path: str, prefix: str
149
+ ) -> bool:
150
+ """Detect whether an autoMode.environment entry is a trust entry for the project.
151
+
152
+ The predicate matches any string entry whose prefix matches the trust-entry
153
+ marker and that contains the project's .claude/** path token anchored on a
154
+ non-path boundary (the start of the body after the prefix, a whitespace
155
+ character, or a quote character). The boundary anchor prevents
156
+ cross-project false positives where the current project's path is a path
157
+ suffix of an unrelated entry's path. The exact wording after the prefix is
158
+ allowed to vary between template revisions.
159
+
160
+ Args:
161
+ candidate_entry: The autoMode.environment list value to inspect.
162
+ project_path: The POSIX-style project root path.
163
+ prefix: The literal prefix that marks a trust entry.
164
+
165
+ Returns:
166
+ True when the entry is a prior trust entry for this project.
167
+ """
168
+ if not isinstance(candidate_entry, str):
169
+ return False
170
+ if not candidate_entry.startswith(prefix):
171
+ return False
172
+ project_path_token = f"{project_path}/.claude/**"
173
+ body_after_prefix = candidate_entry[len(prefix):]
174
+ token_position = body_after_prefix.find(project_path_token)
175
+ while token_position != -1:
176
+ if _is_project_path_token_at_word_boundary(body_after_prefix, token_position):
177
+ return True
178
+ next_search_start = token_position + 1
179
+ token_position = body_after_prefix.find(project_path_token, next_search_start)
180
+ return False
181
+
182
+
183
+ def remove_matching_entries_from_list(
184
+ all_target_list: list[object],
185
+ match_predicate: Callable[[object], bool],
186
+ ) -> int:
187
+ """Remove every entry from a list that satisfies the predicate.
188
+
189
+ Args:
190
+ all_target_list: The list to filter in place.
191
+ match_predicate: Function returning True for entries to remove.
192
+
193
+ Returns:
194
+ Number of entries removed.
195
+ """
196
+ original_length = len(all_target_list)
197
+ all_target_list[:] = [
198
+ each_value
199
+ for each_value in all_target_list
200
+ if not match_predicate(each_value)
201
+ ]
202
+ return original_length - len(all_target_list)
203
+
204
+
108
205
  def load_settings(settings_path: Path) -> dict[str, object]:
109
206
  """Read and parse a JSON settings file from disk.
110
207
 
@@ -11,17 +11,7 @@ from pathlib import Path
11
11
 
12
12
  ValidateContentCallable = Callable[..., list[str]]
13
13
 
14
- _previously_cached_config = {}
15
- for each_cached_module_name in [
16
- each_module_key
17
- for each_module_key in list(sys.modules)
18
- if each_module_key == "config" or each_module_key.startswith("config.")
19
- ]:
20
- _previously_cached_config[each_cached_module_name] = sys.modules.pop(
21
- each_cached_module_name
22
- )
23
-
24
- from config.bugteam_code_rules_gate_constants import (
14
+ from bugteam_scripts_constants.bugteam_code_rules_gate_constants import (
25
15
  ALL_CODE_FILE_EXTENSIONS,
26
16
  ALL_COLUMN_MAGIC_FALSE_VALUES,
27
17
  ALL_GIT_DIFF_CACHED_ARGS,
@@ -34,8 +24,6 @@ from config.bugteam_code_rules_gate_constants import (
34
24
  VIOLATION_LINE_RAW_PATTERN,
35
25
  )
36
26
 
37
- sys.modules.update(_previously_cached_config)
38
-
39
27
 
40
28
  def hunk_header_pattern() -> re.Pattern[str]:
41
29
  return re.compile(HUNK_HEADER_RAW_PATTERN)
@@ -5,22 +5,7 @@ import subprocess
5
5
  import sys
6
6
  from pathlib import Path
7
7
 
8
- parent_directory = str(Path(__file__).resolve().parent)
9
- try:
10
- sys.path.remove(parent_directory)
11
- except ValueError:
12
- pass
13
- if parent_directory not in sys.path:
14
- sys.path.insert(0, parent_directory)
15
-
16
- for each_cached_module_name in [
17
- each_module_key
18
- for each_module_key in list(sys.modules)
19
- if each_module_key == "config" or each_module_key.startswith("config.")
20
- ]:
21
- sys.modules.pop(each_cached_module_name, None)
22
-
23
- from config.bugteam_fix_hookspath_constants import (
8
+ from bugteam_scripts_constants.bugteam_fix_hookspath_constants import (
24
9
  ALL_CANONICAL_HOOKS_DIRECTORY_COMPONENTS,
25
10
  ALL_GLOBAL_HOOKS_PATH_ARGUMENTS,
26
11
  ALL_HOME_ENV_VAR_NAMES,
@@ -6,19 +6,13 @@ import subprocess
6
6
  import sys
7
7
  from pathlib import Path
8
8
 
9
- for each_cached_module_name in [
10
- each_module_key
11
- for each_module_key in list(sys.modules)
12
- if each_module_key == "config" or each_module_key.startswith("config.")
13
- ]:
14
- sys.modules.pop(each_cached_module_name, None)
15
9
  _bugteam_scripts_directory = str(Path(__file__).absolute().parent)
16
10
  while _bugteam_scripts_directory in sys.path:
17
11
  sys.path.remove(_bugteam_scripts_directory)
18
12
  if _bugteam_scripts_directory not in sys.path:
19
13
  sys.path.insert(0, _bugteam_scripts_directory)
20
14
 
21
- from config.bugteam_preflight_constants import (
15
+ from bugteam_scripts_constants.bugteam_preflight_constants import (
22
16
  ALL_DISCOVERY_IGNORE_DIRECTORIES,
23
17
  ALL_GIT_CONFIG_HOOKS_PATH_ARGUMENTS,
24
18
  ALL_PRE_COMMIT_ARGUMENTS,
@@ -35,12 +29,6 @@ from config.bugteam_preflight_constants import (
35
29
  PYTEST_INI_FILENAME,
36
30
  )
37
31
 
38
- for each_cached_module_name in [
39
- each_module_key
40
- for each_module_key in list(sys.modules)
41
- if each_module_key == "config" or each_module_key.startswith("config.")
42
- ]:
43
- sys.modules.pop(each_cached_module_name, None)
44
32
  _shared_pr_loop_scripts_directory = (
45
33
  Path(__file__).absolute().parent
46
34
  / ".." / ".." / ".." / "_shared" / "pr-loop" / "scripts"
@@ -0,0 +1,69 @@
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
+ ALL_AGENT_CONFIG_DENY_TOOLS: tuple[str, ...] = ("Edit", "Write", "Read", "Glob")
8
+ ALL_AGENT_CONFIG_PATH_PATTERNS: tuple[str, ...] = (
9
+ "settings*.json",
10
+ "hooks/**",
11
+ "commands/**",
12
+ "agents/**",
13
+ "skills/**",
14
+ "mcp.json",
15
+ "CLAUDE.md",
16
+ )
17
+ AUTO_MODE_ENVIRONMENT_ENTRY_PREFIX: str = "Trusted local workspace:"
18
+
19
+
20
+ def _describe_agent_config_pattern_for_humans(agent_config_path_pattern: str) -> str:
21
+ glob_suffix_under_directory = "/**"
22
+ file_name_for_special_phrasing = "mcp.json"
23
+ if agent_config_path_pattern.endswith(glob_suffix_under_directory):
24
+ directory_name = agent_config_path_pattern[
25
+ : -len(glob_suffix_under_directory)
26
+ ]
27
+ return f"anything under {directory_name}/"
28
+ if agent_config_path_pattern == file_name_for_special_phrasing:
29
+ return f"the {file_name_for_special_phrasing} file"
30
+ return agent_config_path_pattern
31
+
32
+
33
+ def _build_agent_config_pattern_phrase(
34
+ all_agent_config_path_patterns: tuple[str, ...],
35
+ ) -> str:
36
+ all_described_patterns: list[str] = [
37
+ _describe_agent_config_pattern_for_humans(each_pattern)
38
+ for each_pattern in all_agent_config_path_patterns
39
+ ]
40
+ if len(all_described_patterns) <= 1:
41
+ return ", ".join(all_described_patterns)
42
+ leading_phrase_parts = ", ".join(all_described_patterns[:-1])
43
+ final_phrase_part = all_described_patterns[-1]
44
+ return f"{leading_phrase_parts}, and {final_phrase_part}"
45
+
46
+
47
+ _AGENT_CONFIG_PATTERN_PHRASE: str = _build_agent_config_pattern_phrase(
48
+ ALL_AGENT_CONFIG_PATH_PATTERNS
49
+ )
50
+
51
+ AUTO_MODE_ENVIRONMENT_ENTRY_TEMPLATE: str = (
52
+ f"Trusted local workspace: Files under {{project_path}}/.claude/** inherit "
53
+ f"the workspace's trust for Edit, Write, Read, and Glob operations EXCEPT "
54
+ f"for agent-config files: {_AGENT_CONFIG_PATTERN_PHRASE}. Edits to those "
55
+ f"agent-config files always require explicit per-edit user approval."
56
+ )
57
+
58
+ ALL_TRUST_ENTRY_PROJECT_PATH_BOUNDARY_QUOTE_CHARACTERS: tuple[str, ...] = ('"', "'")
59
+ ATOMIC_WRITE_TEMPORARY_SUFFIX: str = ".tmp"
60
+ GIT_DIRECTORY_MARKER: str = ".git"
61
+ CLAUDE_DIRECTORY_MARKER: str = ".claude"
62
+ CLAUDE_USER_SETTINGS_FILENAME: str = "settings.json"
63
+ DEFAULT_SETTINGS_FILE_MODE: int = 0o600
64
+ SETTINGS_PERMISSIONS_KEY: str = "permissions"
65
+ SETTINGS_ALLOW_KEY: str = "allow"
66
+ SETTINGS_DENY_KEY: str = "deny"
67
+ SETTINGS_ADDITIONAL_DIRECTORIES_KEY: str = "additionalDirectories"
68
+ SETTINGS_AUTO_MODE_KEY: str = "autoMode"
69
+ SETTINGS_ENVIRONMENT_KEY: str = "environment"