claude-dev-env 1.35.0 → 1.36.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 (113) hide show
  1. package/agents/clean-coder.md +109 -1
  2. package/bin/install.mjs +28 -8
  3. package/bin/install.test.mjs +9 -1
  4. package/docs/CODE_RULES.md +3 -0
  5. package/docs/agents-md-alignment-plan.md +123 -0
  6. package/hooks/blocking/code_rules_enforcer.py +451 -39
  7. package/hooks/blocking/es_exe_path_rewriter.py +10 -4
  8. package/hooks/blocking/test_code_rules_enforcer.py +182 -0
  9. package/hooks/blocking/test_code_rules_enforcer_banned_identifier.py +106 -0
  10. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +173 -0
  11. package/hooks/blocking/test_code_rules_enforcer_collection_prefix.py +191 -0
  12. package/hooks/blocking/test_code_rules_enforcer_constant_equality.py +40 -0
  13. package/hooks/blocking/test_code_rules_enforcer_hardcoded_user_path.py +291 -0
  14. package/hooks/blocking/test_code_rules_enforcer_loop_variable_naming.py +87 -3
  15. package/hooks/blocking/test_code_rules_enforcer_naming_pattern.py +49 -0
  16. package/hooks/blocking/test_code_rules_enforcer_sys_path_insert.py +157 -0
  17. package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +244 -0
  18. package/hooks/blocking/test_es_exe_path_rewriter.py +81 -3
  19. package/hooks/blocking/test_windows_rmtree_blocker.py +120 -8
  20. package/hooks/blocking/windows_rmtree_blocker.py +23 -6
  21. package/hooks/config/banned_identifiers_constants.py +24 -0
  22. package/hooks/config/hardcoded_user_path_constants.py +12 -0
  23. package/hooks/config/hook_log_extractor_constants.py +1 -1
  24. package/hooks/config/pre_tool_use_stdin.py +48 -0
  25. package/hooks/config/setup_project_paths_constants.py +4 -0
  26. package/hooks/config/stuttering_check_config.py +14 -0
  27. package/hooks/config/stuttering_import_binding_constants.py +11 -0
  28. package/hooks/config/sys_path_insert_constants.py +4 -0
  29. package/hooks/config/test_banned_identifiers_constants.py +48 -0
  30. package/hooks/config/test_hardcoded_user_path_constants.py +78 -0
  31. package/hooks/config/test_hook_log_extractor_constants.py +3 -3
  32. package/hooks/config/test_pre_tool_use_stdin.py +80 -0
  33. package/hooks/config/unused_module_import_constants.py +7 -0
  34. package/hooks/config/windows_rmtree_blocker_constants.py +3 -0
  35. package/hooks/diagnostic/hook_log_stop_wrapper.py +7 -4
  36. package/hooks/git-hooks/config.py +3 -3
  37. package/hooks/git-hooks/test_gate_utils.py +10 -10
  38. package/hooks/mypy.ini +2 -0
  39. package/package.json +1 -1
  40. package/rules/gh-paginate.md +125 -0
  41. package/skills/bugteam/CONSTRAINTS.md +12 -6
  42. package/skills/bugteam/SKILL.md +77 -91
  43. package/skills/bugteam/SKILL_EVALS.md +25 -23
  44. package/skills/bugteam/reference/README.md +2 -0
  45. package/skills/bugteam/reference/audit-and-teammates.md +2 -2
  46. package/skills/bugteam/reference/teardown-publish-permissions.md +1 -1
  47. package/skills/bugteam/reference/workflow-path-a-orchestrated-teams.md +113 -0
  48. package/skills/bugteam/reference/workflow-path-b-task-harness.md +48 -0
  49. package/skills/bugteam/test_skill_additions.py +13 -4
  50. package/skills/bugteam/test_team_lifecycle.py +94 -0
  51. package/skills/findbugs/SKILL.md +3 -3
  52. package/skills/fixbugs/SKILL.md +4 -4
  53. package/skills/monitor-open-prs/SKILL.md +32 -2
  54. package/skills/monitor-open-prs/test_team_lifecycle.py +46 -0
  55. package/skills/pr-converge/SKILL.md +562 -97
  56. package/skills/pr-converge/scripts/README.md +145 -0
  57. package/skills/pr-converge/scripts/caller-window-pid.ps1 +86 -0
  58. package/skills/pr-converge/scripts/check_pr_mergeability.py +79 -0
  59. package/skills/pr-converge/scripts/config/pr_converge_constants.py +65 -0
  60. package/skills/pr-converge/scripts/config/test_pr_converge_constants.py +176 -0
  61. package/skills/pr-converge/scripts/cursor-agents-continue-caller.cmd +9 -0
  62. package/skills/pr-converge/scripts/cursor-agents-continue-stop-others.ps1 +16 -0
  63. package/skills/pr-converge/scripts/cursor-agents-continue.ahk +172 -0
  64. package/skills/pr-converge/scripts/cursor-agents-continue.cmd +2 -0
  65. package/skills/pr-converge/scripts/evict_cached_config_modules.py +20 -0
  66. package/skills/pr-converge/scripts/fetch_bugbot_inline_comments.py +110 -0
  67. package/skills/pr-converge/scripts/fetch_bugbot_reviews.py +103 -0
  68. package/skills/pr-converge/scripts/fetch_copilot_inline_comments.py +112 -0
  69. package/skills/pr-converge/scripts/fetch_copilot_reviews.py +121 -0
  70. package/skills/pr-converge/scripts/mark_pr_ready.py +54 -0
  71. package/skills/pr-converge/scripts/open_followup_copilot_pr.py +136 -0
  72. package/skills/pr-converge/scripts/post-bugbot-run.helpers.ps1 +49 -0
  73. package/skills/pr-converge/scripts/post-bugbot-run.ps1 +33 -0
  74. package/skills/pr-converge/scripts/reply_to_inline_comment.py +84 -0
  75. package/skills/pr-converge/scripts/request_copilot_review.py +71 -0
  76. package/skills/pr-converge/scripts/resolve_pr_head.py +58 -0
  77. package/skills/pr-converge/scripts/review_field_helpers.py +43 -0
  78. package/skills/pr-converge/scripts/test_check_pr_mergeability.py +126 -0
  79. package/skills/pr-converge/scripts/test_evict_cached_config_modules.py +22 -0
  80. package/skills/pr-converge/scripts/test_fetch_bugbot_inline_comments.py +342 -0
  81. package/skills/pr-converge/scripts/test_fetch_bugbot_reviews.py +220 -0
  82. package/skills/pr-converge/scripts/test_fetch_copilot_inline_comments.py +372 -0
  83. package/skills/pr-converge/scripts/test_fetch_copilot_reviews.py +280 -0
  84. package/skills/pr-converge/scripts/test_mark_pr_ready.py +69 -0
  85. package/skills/pr-converge/scripts/test_open_followup_copilot_pr.py +236 -0
  86. package/skills/pr-converge/scripts/test_post_bugbot_run.py +195 -0
  87. package/skills/pr-converge/scripts/test_reply_to_inline_comment.py +159 -0
  88. package/skills/pr-converge/scripts/test_request_copilot_review.py +101 -0
  89. package/skills/pr-converge/scripts/test_resolve_pr_head.py +79 -0
  90. package/skills/pr-converge/scripts/test_review_field_helpers.py +80 -0
  91. package/skills/pr-converge/scripts/test_trigger_bugbot.py +139 -0
  92. package/skills/pr-converge/scripts/test_view_pr_context.py +111 -0
  93. package/skills/pr-converge/scripts/trigger_bugbot.py +77 -0
  94. package/skills/pr-converge/scripts/view_pr_context.py +47 -0
  95. package/skills/pr-converge/test_team_lifecycle.py +47 -0
  96. package/skills/pr-converge/workflows/ahk-auto-continue-loop.md +108 -0
  97. package/skills/pr-converge/workflows/schedule-wakeup-loop.md +37 -0
  98. package/skills/qbug/SKILL.md +4 -4
  99. package/skills/qbug/test_qbug_skill_post_fix_audit.py +2 -2
  100. package/skills/resume-review/SKILL.md +261 -0
  101. package/skills/bugteam/scripts/README.md +0 -58
  102. package/skills/bugteam/scripts/_claude_permissions_common.py +0 -219
  103. package/skills/bugteam/scripts/bugteam_code_rules_gate.py +0 -633
  104. package/skills/bugteam/scripts/bugteam_fix_hookspath.py +0 -260
  105. package/skills/bugteam/scripts/bugteam_preflight.py +0 -201
  106. package/skills/bugteam/scripts/config/bugteam_fix_hookspath_constants.py +0 -17
  107. package/skills/bugteam/scripts/grant_project_claude_permissions.py +0 -109
  108. package/skills/bugteam/scripts/revoke_project_claude_permissions.py +0 -135
  109. package/skills/bugteam/scripts/test_bugteam_code_rules_gate.py +0 -271
  110. package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +0 -267
  111. package/skills/bugteam/scripts/test_bugteam_preflight.py +0 -189
  112. package/skills/bugteam/scripts/test_claude_permissions_common.py +0 -44
  113. /package/skills/{bugteam → pr-converge}/scripts/config/__init__.py +0 -0
@@ -1,219 +0,0 @@
1
- """Shared helpers for grant_project_claude_permissions and revoke_project_claude_permissions.
2
-
3
- Writes to ~/.claude/settings.json are atomic and permission-preserving: the
4
- target file's existing POSIX mode is captured, a sibling temp file is
5
- created via os.open with O_CREAT | O_EXCL and the preserved mode, content
6
- is written, then os.replace swaps it into place. Output is serialized with
7
- sort_keys=True for a stable on-disk layout; the first run on a hand-ordered
8
- settings file produces a one-time re-sort diff, subsequent writes are stable.
9
- """
10
-
11
- import json
12
- import os
13
- import stat
14
- import sys
15
- from pathlib import Path
16
- from typing import NoReturn
17
-
18
-
19
- TEXT_FILE_ENCODING: str = "utf-8"
20
- PERMISSION_ALLOW_TOOLS: tuple[str, ...] = ("Edit", "Write", "Read")
21
-
22
- AUTO_MODE_ENVIRONMENT_ENTRY_TEMPLATE: str = (
23
- "Trusted local workspace: {project_path}/.claude/** is the user's "
24
- "project Claude Code config tree; edits inside are routine"
25
- )
26
-
27
-
28
- def exit_with_error(message: str) -> NoReturn:
29
- print(f"Error: {message}", file=sys.stderr)
30
- raise SystemExit(1)
31
-
32
-
33
- def path_contains_glob_metacharacters(candidate_path: str) -> bool:
34
- glob_metacharacters_in_path: tuple[str, ...] = (
35
- "*",
36
- "?",
37
- "[",
38
- "]",
39
- "(",
40
- ")",
41
- "{",
42
- "}",
43
- ",",
44
- )
45
- return any(
46
- each_character in candidate_path
47
- for each_character in glob_metacharacters_in_path
48
- )
49
-
50
-
51
- def get_current_project_path() -> str:
52
- normalized_project_path = str(Path.cwd()).replace("\\", "/")
53
- if path_contains_glob_metacharacters(normalized_project_path):
54
- raise ValueError(
55
- f"Current directory path contains glob metacharacters and cannot "
56
- f"be used to build permission rules safely: {normalized_project_path}"
57
- )
58
- return normalized_project_path
59
-
60
-
61
- def build_permission_rule(tool_name: str, project_path: str) -> str:
62
- return f"{tool_name}({project_path}/.claude/**)"
63
-
64
-
65
- def build_permission_rules(
66
- project_path: str, permission_allow_tools: tuple[str, ...]
67
- ) -> list[str]:
68
- return [
69
- build_permission_rule(each_tool, project_path)
70
- for each_tool in permission_allow_tools
71
- ]
72
-
73
-
74
- def load_settings(settings_path: Path) -> dict[str, object]:
75
- if not settings_path.exists():
76
- return {}
77
- parsed_settings: dict[str, object] = {}
78
- try:
79
- raw_text = settings_path.read_text(encoding=TEXT_FILE_ENCODING)
80
- except OSError as read_error:
81
- exit_with_error(f"Failed to read {settings_path}: {read_error}")
82
- try:
83
- parsed_settings = json.loads(raw_text)
84
- except json.JSONDecodeError as decode_error:
85
- exit_with_error(
86
- f"Refusing to modify {settings_path}: existing file is not valid JSON "
87
- f"({decode_error}). Fix or back up the file manually, then re-run."
88
- )
89
- if not isinstance(parsed_settings, dict):
90
- exit_with_error(
91
- f"Refusing to modify {settings_path}: existing file's root is "
92
- f"{type(parsed_settings).__name__}, not a JSON object. Fix or back up "
93
- f"the file manually, then re-run."
94
- )
95
- return parsed_settings
96
-
97
-
98
- def serialize_settings_to_json_text(settings: dict[str, object]) -> str:
99
- json_indent_width_columns: int = len(" ")
100
- return json.dumps(
101
- settings,
102
- indent=json_indent_width_columns,
103
- sort_keys=True,
104
- )
105
-
106
-
107
- def get_mode_to_preserve(settings_path: Path) -> int:
108
- default_settings_file_mode: int = 0o600
109
- try:
110
- stat_result = os.stat(settings_path)
111
- except FileNotFoundError:
112
- return default_settings_file_mode
113
- except OSError as stat_error:
114
- exit_with_error(f"Failed to stat {settings_path}: {stat_error}")
115
- return stat.S_IMODE(stat_result.st_mode)
116
-
117
-
118
- def write_atomically_with_mode(
119
- temporary_path: Path, serialized_content: str, file_mode: int
120
- ) -> None:
121
- file_descriptor = os.open(
122
- str(temporary_path),
123
- os.O_WRONLY | os.O_CREAT | os.O_EXCL,
124
- file_mode,
125
- )
126
- with os.fdopen(file_descriptor, "w", encoding=TEXT_FILE_ENCODING) as writer:
127
- writer.write(serialized_content)
128
-
129
-
130
- def save_settings(settings_path: Path, settings: dict[str, object]) -> None:
131
- atomic_write_temporary_suffix: str = ".tmp"
132
- settings_path.parent.mkdir(parents=True, exist_ok=True)
133
- serialized_settings = serialize_settings_to_json_text(settings)
134
- temporary_path = settings_path.with_suffix(
135
- settings_path.suffix + atomic_write_temporary_suffix
136
- )
137
- mode_to_preserve = get_mode_to_preserve(settings_path)
138
- try:
139
- try:
140
- write_atomically_with_mode(
141
- temporary_path, serialized_settings, mode_to_preserve
142
- )
143
- os.replace(str(temporary_path), str(settings_path))
144
- except OSError as os_error:
145
- exit_with_error(
146
- f"Failed to write settings atomically to {settings_path}: {os_error}"
147
- )
148
- finally:
149
- if temporary_path.exists():
150
- try:
151
- temporary_path.unlink()
152
- except OSError:
153
- pass
154
-
155
-
156
- def append_if_missing(target_list: list[object], new_value: str) -> bool:
157
- if new_value in target_list:
158
- return False
159
- target_list.append(new_value)
160
- return True
161
-
162
-
163
- def ensure_dict_section(
164
- settings: dict[str, object], section_name: str
165
- ) -> dict[str, object]:
166
- """Return an existing dict section or create an empty one if absent.
167
-
168
- A missing key and an explicit JSON null are treated identically: both
169
- produce a fresh empty dict stored back into settings. Any other non-dict
170
- value (string, list, number, bool) calls exit_with_error to avoid
171
- overwriting user data.
172
- """
173
- existing_section = settings.get(section_name)
174
- if existing_section is None:
175
- replacement_section: dict[str, object] = {}
176
- settings[section_name] = replacement_section
177
- return replacement_section
178
- if not isinstance(existing_section, dict):
179
- exit_with_error(
180
- f"Refusing to modify settings key {section_name!r}: existing value "
181
- f"is {type(existing_section).__name__}, not a JSON object. Fix or "
182
- f"remove the key manually, then re-run."
183
- )
184
- return existing_section
185
-
186
-
187
- def ensure_list_entry(section: dict[str, object], entry_name: str) -> list[object]:
188
- """Return an existing list entry or create an empty one if absent.
189
-
190
- A missing key and an explicit JSON null are treated identically: both
191
- produce a fresh empty list stored back into the section. Any other
192
- non-list value (string, dict, number, bool) calls exit_with_error to
193
- avoid overwriting user data.
194
- """
195
- existing_entry = section.get(entry_name)
196
- if existing_entry is None:
197
- replacement_entry: list[object] = []
198
- section[entry_name] = replacement_entry
199
- return replacement_entry
200
- if not isinstance(existing_entry, list):
201
- exit_with_error(
202
- f"Refusing to modify settings entry {entry_name!r}: existing value "
203
- f"is {type(existing_entry).__name__}, not a JSON array. Fix or "
204
- f"remove the entry manually, then re-run."
205
- )
206
- return existing_entry
207
-
208
-
209
- def prune_empty_list_then_empty_section(
210
- settings: dict[str, object], section_key: str, list_key: str
211
- ) -> None:
212
- section = settings.get(section_key)
213
- if not isinstance(section, dict):
214
- return
215
- list_entry = section.get(list_key)
216
- if isinstance(list_entry, list) and len(list_entry) == 0:
217
- del section[list_key]
218
- if len(section) == 0:
219
- del settings[section_key]