crackerjack 0.33.0__py3-none-any.whl → 0.33.1__py3-none-any.whl

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.

Potentially problematic release.


This version of crackerjack might be problematic. Click here for more details.

Files changed (198) hide show
  1. crackerjack/__main__.py +1350 -34
  2. crackerjack/adapters/__init__.py +17 -0
  3. crackerjack/adapters/lsp_client.py +358 -0
  4. crackerjack/adapters/rust_tool_adapter.py +194 -0
  5. crackerjack/adapters/rust_tool_manager.py +193 -0
  6. crackerjack/adapters/skylos_adapter.py +231 -0
  7. crackerjack/adapters/zuban_adapter.py +560 -0
  8. crackerjack/agents/base.py +7 -3
  9. crackerjack/agents/coordinator.py +271 -33
  10. crackerjack/agents/documentation_agent.py +9 -15
  11. crackerjack/agents/dry_agent.py +3 -15
  12. crackerjack/agents/formatting_agent.py +1 -1
  13. crackerjack/agents/import_optimization_agent.py +36 -180
  14. crackerjack/agents/performance_agent.py +17 -98
  15. crackerjack/agents/performance_helpers.py +7 -31
  16. crackerjack/agents/proactive_agent.py +1 -3
  17. crackerjack/agents/refactoring_agent.py +16 -85
  18. crackerjack/agents/refactoring_helpers.py +7 -42
  19. crackerjack/agents/security_agent.py +9 -48
  20. crackerjack/agents/test_creation_agent.py +356 -513
  21. crackerjack/agents/test_specialist_agent.py +0 -4
  22. crackerjack/api.py +6 -25
  23. crackerjack/cli/cache_handlers.py +204 -0
  24. crackerjack/cli/cache_handlers_enhanced.py +683 -0
  25. crackerjack/cli/facade.py +100 -0
  26. crackerjack/cli/handlers.py +224 -9
  27. crackerjack/cli/interactive.py +6 -4
  28. crackerjack/cli/options.py +642 -55
  29. crackerjack/cli/utils.py +2 -1
  30. crackerjack/code_cleaner.py +58 -117
  31. crackerjack/config/global_lock_config.py +8 -48
  32. crackerjack/config/hooks.py +53 -62
  33. crackerjack/core/async_workflow_orchestrator.py +24 -34
  34. crackerjack/core/autofix_coordinator.py +3 -17
  35. crackerjack/core/enhanced_container.py +4 -13
  36. crackerjack/core/file_lifecycle.py +12 -89
  37. crackerjack/core/performance.py +2 -2
  38. crackerjack/core/performance_monitor.py +15 -55
  39. crackerjack/core/phase_coordinator.py +104 -204
  40. crackerjack/core/resource_manager.py +14 -90
  41. crackerjack/core/service_watchdog.py +62 -95
  42. crackerjack/core/session_coordinator.py +149 -0
  43. crackerjack/core/timeout_manager.py +14 -72
  44. crackerjack/core/websocket_lifecycle.py +13 -78
  45. crackerjack/core/workflow_orchestrator.py +171 -174
  46. crackerjack/docs/INDEX.md +11 -0
  47. crackerjack/docs/generated/api/API_REFERENCE.md +10895 -0
  48. crackerjack/docs/generated/api/CLI_REFERENCE.md +109 -0
  49. crackerjack/docs/generated/api/CROSS_REFERENCES.md +1755 -0
  50. crackerjack/docs/generated/api/PROTOCOLS.md +3 -0
  51. crackerjack/docs/generated/api/SERVICES.md +1252 -0
  52. crackerjack/documentation/__init__.py +31 -0
  53. crackerjack/documentation/ai_templates.py +756 -0
  54. crackerjack/documentation/dual_output_generator.py +765 -0
  55. crackerjack/documentation/mkdocs_integration.py +518 -0
  56. crackerjack/documentation/reference_generator.py +977 -0
  57. crackerjack/dynamic_config.py +55 -50
  58. crackerjack/executors/async_hook_executor.py +10 -15
  59. crackerjack/executors/cached_hook_executor.py +117 -43
  60. crackerjack/executors/hook_executor.py +8 -34
  61. crackerjack/executors/hook_lock_manager.py +26 -183
  62. crackerjack/executors/individual_hook_executor.py +13 -11
  63. crackerjack/executors/lsp_aware_hook_executor.py +270 -0
  64. crackerjack/executors/tool_proxy.py +417 -0
  65. crackerjack/hooks/lsp_hook.py +79 -0
  66. crackerjack/intelligence/adaptive_learning.py +25 -10
  67. crackerjack/intelligence/agent_orchestrator.py +2 -5
  68. crackerjack/intelligence/agent_registry.py +34 -24
  69. crackerjack/intelligence/agent_selector.py +5 -7
  70. crackerjack/interactive.py +17 -6
  71. crackerjack/managers/async_hook_manager.py +0 -1
  72. crackerjack/managers/hook_manager.py +79 -1
  73. crackerjack/managers/publish_manager.py +44 -8
  74. crackerjack/managers/test_command_builder.py +1 -15
  75. crackerjack/managers/test_executor.py +1 -3
  76. crackerjack/managers/test_manager.py +98 -7
  77. crackerjack/managers/test_manager_backup.py +10 -9
  78. crackerjack/mcp/cache.py +2 -2
  79. crackerjack/mcp/client_runner.py +1 -1
  80. crackerjack/mcp/context.py +191 -68
  81. crackerjack/mcp/dashboard.py +7 -5
  82. crackerjack/mcp/enhanced_progress_monitor.py +31 -28
  83. crackerjack/mcp/file_monitor.py +30 -23
  84. crackerjack/mcp/progress_components.py +31 -21
  85. crackerjack/mcp/progress_monitor.py +50 -53
  86. crackerjack/mcp/rate_limiter.py +6 -6
  87. crackerjack/mcp/server_core.py +17 -16
  88. crackerjack/mcp/service_watchdog.py +2 -1
  89. crackerjack/mcp/state.py +4 -7
  90. crackerjack/mcp/task_manager.py +11 -9
  91. crackerjack/mcp/tools/core_tools.py +173 -32
  92. crackerjack/mcp/tools/error_analyzer.py +3 -2
  93. crackerjack/mcp/tools/execution_tools.py +8 -10
  94. crackerjack/mcp/tools/execution_tools_backup.py +42 -30
  95. crackerjack/mcp/tools/intelligence_tool_registry.py +7 -5
  96. crackerjack/mcp/tools/intelligence_tools.py +5 -2
  97. crackerjack/mcp/tools/monitoring_tools.py +33 -70
  98. crackerjack/mcp/tools/proactive_tools.py +24 -11
  99. crackerjack/mcp/tools/progress_tools.py +5 -8
  100. crackerjack/mcp/tools/utility_tools.py +20 -14
  101. crackerjack/mcp/tools/workflow_executor.py +62 -40
  102. crackerjack/mcp/websocket/app.py +8 -0
  103. crackerjack/mcp/websocket/endpoints.py +352 -357
  104. crackerjack/mcp/websocket/jobs.py +40 -57
  105. crackerjack/mcp/websocket/monitoring_endpoints.py +2935 -0
  106. crackerjack/mcp/websocket/server.py +7 -25
  107. crackerjack/mcp/websocket/websocket_handler.py +6 -17
  108. crackerjack/mixins/__init__.py +0 -2
  109. crackerjack/mixins/error_handling.py +1 -70
  110. crackerjack/models/config.py +12 -1
  111. crackerjack/models/config_adapter.py +49 -1
  112. crackerjack/models/protocols.py +122 -122
  113. crackerjack/models/resource_protocols.py +55 -210
  114. crackerjack/monitoring/ai_agent_watchdog.py +13 -13
  115. crackerjack/monitoring/metrics_collector.py +426 -0
  116. crackerjack/monitoring/regression_prevention.py +8 -8
  117. crackerjack/monitoring/websocket_server.py +643 -0
  118. crackerjack/orchestration/advanced_orchestrator.py +11 -6
  119. crackerjack/orchestration/coverage_improvement.py +3 -3
  120. crackerjack/orchestration/execution_strategies.py +26 -6
  121. crackerjack/orchestration/test_progress_streamer.py +8 -5
  122. crackerjack/plugins/base.py +2 -2
  123. crackerjack/plugins/hooks.py +7 -0
  124. crackerjack/plugins/managers.py +11 -8
  125. crackerjack/security/__init__.py +0 -1
  126. crackerjack/security/audit.py +6 -35
  127. crackerjack/services/anomaly_detector.py +392 -0
  128. crackerjack/services/api_extractor.py +615 -0
  129. crackerjack/services/backup_service.py +2 -2
  130. crackerjack/services/bounded_status_operations.py +15 -152
  131. crackerjack/services/cache.py +127 -1
  132. crackerjack/services/changelog_automation.py +395 -0
  133. crackerjack/services/config.py +15 -9
  134. crackerjack/services/config_merge.py +19 -80
  135. crackerjack/services/config_template.py +506 -0
  136. crackerjack/services/contextual_ai_assistant.py +48 -22
  137. crackerjack/services/coverage_badge_service.py +171 -0
  138. crackerjack/services/coverage_ratchet.py +27 -25
  139. crackerjack/services/debug.py +3 -3
  140. crackerjack/services/dependency_analyzer.py +460 -0
  141. crackerjack/services/dependency_monitor.py +14 -11
  142. crackerjack/services/documentation_generator.py +491 -0
  143. crackerjack/services/documentation_service.py +675 -0
  144. crackerjack/services/enhanced_filesystem.py +6 -5
  145. crackerjack/services/enterprise_optimizer.py +865 -0
  146. crackerjack/services/error_pattern_analyzer.py +676 -0
  147. crackerjack/services/file_hasher.py +1 -1
  148. crackerjack/services/git.py +8 -25
  149. crackerjack/services/health_metrics.py +10 -8
  150. crackerjack/services/heatmap_generator.py +735 -0
  151. crackerjack/services/initialization.py +11 -30
  152. crackerjack/services/input_validator.py +5 -97
  153. crackerjack/services/intelligent_commit.py +327 -0
  154. crackerjack/services/log_manager.py +15 -12
  155. crackerjack/services/logging.py +4 -3
  156. crackerjack/services/lsp_client.py +628 -0
  157. crackerjack/services/memory_optimizer.py +19 -87
  158. crackerjack/services/metrics.py +42 -33
  159. crackerjack/services/parallel_executor.py +9 -67
  160. crackerjack/services/pattern_cache.py +1 -1
  161. crackerjack/services/pattern_detector.py +6 -6
  162. crackerjack/services/performance_benchmarks.py +18 -59
  163. crackerjack/services/performance_cache.py +20 -81
  164. crackerjack/services/performance_monitor.py +27 -95
  165. crackerjack/services/predictive_analytics.py +510 -0
  166. crackerjack/services/quality_baseline.py +234 -0
  167. crackerjack/services/quality_baseline_enhanced.py +646 -0
  168. crackerjack/services/quality_intelligence.py +785 -0
  169. crackerjack/services/regex_patterns.py +605 -524
  170. crackerjack/services/regex_utils.py +43 -123
  171. crackerjack/services/secure_path_utils.py +5 -164
  172. crackerjack/services/secure_status_formatter.py +30 -141
  173. crackerjack/services/secure_subprocess.py +11 -92
  174. crackerjack/services/security.py +9 -41
  175. crackerjack/services/security_logger.py +12 -24
  176. crackerjack/services/server_manager.py +124 -16
  177. crackerjack/services/status_authentication.py +16 -159
  178. crackerjack/services/status_security_manager.py +4 -131
  179. crackerjack/services/thread_safe_status_collector.py +19 -125
  180. crackerjack/services/unified_config.py +21 -13
  181. crackerjack/services/validation_rate_limiter.py +5 -54
  182. crackerjack/services/version_analyzer.py +459 -0
  183. crackerjack/services/version_checker.py +1 -1
  184. crackerjack/services/websocket_resource_limiter.py +10 -144
  185. crackerjack/services/zuban_lsp_service.py +390 -0
  186. crackerjack/slash_commands/__init__.py +2 -7
  187. crackerjack/slash_commands/run.md +2 -2
  188. crackerjack/tools/validate_input_validator_patterns.py +14 -40
  189. crackerjack/tools/validate_regex_patterns.py +19 -48
  190. {crackerjack-0.33.0.dist-info → crackerjack-0.33.1.dist-info}/METADATA +196 -25
  191. crackerjack-0.33.1.dist-info/RECORD +229 -0
  192. crackerjack/CLAUDE.md +0 -207
  193. crackerjack/RULES.md +0 -380
  194. crackerjack/py313.py +0 -234
  195. crackerjack-0.33.0.dist-info/RECORD +0 -187
  196. {crackerjack-0.33.0.dist-info → crackerjack-0.33.1.dist-info}/WHEEL +0 -0
  197. {crackerjack-0.33.0.dist-info → crackerjack-0.33.1.dist-info}/entry_points.txt +0 -0
  198. {crackerjack-0.33.0.dist-info → crackerjack-0.33.1.dist-info}/licenses/LICENSE +0 -0
@@ -17,17 +17,6 @@ from crackerjack.services.logging import get_logger
17
17
 
18
18
 
19
19
  class ConfigMergeService(ConfigMergeServiceProtocol):
20
- """Smart configuration file merging service.
21
-
22
- Extracts and centralizes smart merge logic for:
23
- - pyproject.toml files (preserves project identity, merges tool configs)
24
- - .pre-commit-config.yaml files (adds missing repos, preserves existing hooks)
25
- - .gitignore files (merges patterns while avoiding duplicates)
26
- - Generic file appending with markers
27
-
28
- Follows crackerjack's DRY, YAGNI, KISS principles.
29
- """
30
-
31
20
  def __init__(
32
21
  self,
33
22
  console: Console,
@@ -45,25 +34,23 @@ class ConfigMergeService(ConfigMergeServiceProtocol):
45
34
  target_path: str | t.Any,
46
35
  project_name: str,
47
36
  ) -> dict[str, t.Any]:
48
- """Smart merge pyproject.toml preserving project identity and merging tool configs."""
49
37
  target_path = Path(target_path)
50
38
 
51
39
  if not target_path.exists():
52
- # No existing file, return source with project name replacement
53
- return self._replace_project_name_in_config_value(
54
- source_content, project_name
40
+ return t.cast(
41
+ dict[str, t.Any],
42
+ self._replace_project_name_in_config_value(
43
+ source_content, project_name
44
+ ),
55
45
  )
56
46
 
57
47
  with target_path.open("rb") as f:
58
48
  target_content = tomli.load(f)
59
49
 
60
- # Ensure crackerjack dev dependency
61
50
  self._ensure_crackerjack_dev_dependency(target_content, source_content)
62
51
 
63
- # Merge tool configurations
64
52
  self._merge_tool_configurations(target_content, source_content, project_name)
65
53
 
66
- # Remove fixed coverage requirements (use ratchet system)
67
54
  self._remove_fixed_coverage_requirements(target_content)
68
55
 
69
56
  self.logger.info("Smart merged pyproject.toml", project_name=project_name)
@@ -75,17 +62,17 @@ class ConfigMergeService(ConfigMergeServiceProtocol):
75
62
  target_path: str | t.Any,
76
63
  project_name: str,
77
64
  ) -> dict[str, t.Any]:
78
- """Smart merge .pre-commit-config.yaml adding missing repos."""
79
65
  target_path = Path(target_path)
80
66
 
81
67
  if not target_path.exists():
82
- # No existing file, return source
83
68
  return source_content
84
69
 
85
70
  with target_path.open() as f:
86
- target_content = yaml.safe_load(f) or {}
71
+ loaded_config = yaml.safe_load(f)
72
+ target_content: dict[str, t.Any] = (
73
+ loaded_config if isinstance(loaded_config, dict) else {}
74
+ )
87
75
 
88
- # Ensure target_content is a dict
89
76
  if not isinstance(target_content, dict):
90
77
  self.logger.warning(
91
78
  f"Target config is not a dictionary, using source: {type(target_content)}"
@@ -95,16 +82,13 @@ class ConfigMergeService(ConfigMergeServiceProtocol):
95
82
  source_repos = source_content.get("repos", [])
96
83
  target_repos = target_content.get("repos", [])
97
84
 
98
- # Ensure target_repos is a list of dicts
99
85
  if not isinstance(target_repos, list):
100
86
  target_repos = []
101
87
 
102
- # Get existing repo URLs to avoid duplicates
103
88
  existing_repo_urls = {
104
89
  repo.get("repo", "") for repo in target_repos if isinstance(repo, dict)
105
90
  }
106
91
 
107
- # Find new repos to add
108
92
  new_repos = [
109
93
  repo
110
94
  for repo in source_repos
@@ -130,19 +114,15 @@ class ConfigMergeService(ConfigMergeServiceProtocol):
130
114
  end_marker: str,
131
115
  force: bool = False,
132
116
  ) -> str:
133
- """Smart append content to file with markers (for CLAUDE.md, etc)."""
134
117
  target_path = Path(target_path)
135
118
 
136
119
  if not target_path.exists():
137
- # No existing file, return source content wrapped in markers
138
120
  return f"{start_marker}\n{source_content.strip()}\n{end_marker}\n"
139
121
 
140
122
  existing_content = target_path.read_text()
141
123
 
142
- # Check if markers already exist
143
124
  if start_marker in existing_content:
144
125
  if force:
145
- # Replace existing section
146
126
  start_idx = existing_content.find(start_marker)
147
127
  end_idx = existing_content.find(end_marker)
148
128
  if end_idx != -1:
@@ -151,10 +131,8 @@ class ConfigMergeService(ConfigMergeServiceProtocol):
151
131
  existing_content[:start_idx] + existing_content[end_idx:]
152
132
  ).strip()
153
133
  else:
154
- # Already exists and not forced, return existing
155
134
  return existing_content
156
135
 
157
- # Append new section with markers
158
136
  merged_content = existing_content.strip() + "\n\n" + start_marker + "\n"
159
137
  merged_content += source_content.strip() + "\n"
160
138
  merged_content += end_marker + "\n"
@@ -167,7 +145,6 @@ class ConfigMergeService(ConfigMergeServiceProtocol):
167
145
  patterns: list[str],
168
146
  target_path: str | t.Any,
169
147
  ) -> str:
170
- """Smart merge .gitignore patterns avoiding and cleaning out duplicates."""
171
148
  target_path = Path(target_path)
172
149
 
173
150
  if not target_path.exists():
@@ -175,13 +152,10 @@ class ConfigMergeService(ConfigMergeServiceProtocol):
175
152
 
176
153
  lines = target_path.read_text().splitlines()
177
154
 
178
- # Parse existing content and extract patterns
179
155
  parsed_content = self._parse_existing_gitignore_content(lines)
180
156
 
181
- # Build merged content
182
157
  merged_content = self._build_merged_gitignore_content(parsed_content, patterns)
183
158
 
184
- # Write and log results
185
159
  target_path.write_text(merged_content)
186
160
  new_patterns_count = len(
187
161
  [p for p in patterns if p not in parsed_content.existing_patterns]
@@ -196,7 +170,6 @@ class ConfigMergeService(ConfigMergeServiceProtocol):
196
170
  return merged_content
197
171
 
198
172
  def _create_new_gitignore(self, target_path: Path, patterns: list[str]) -> str:
199
- """Create a new .gitignore file with patterns."""
200
173
  merged_content = "# Crackerjack patterns\n"
201
174
  for pattern in patterns:
202
175
  merged_content += f"{pattern}\n"
@@ -205,13 +178,10 @@ class ConfigMergeService(ConfigMergeServiceProtocol):
205
178
  return merged_content
206
179
 
207
180
  def _parse_existing_gitignore_content(self, lines: list[str]) -> t.Any:
208
- """Parse existing .gitignore content, extracting patterns and non-Crackerjack lines."""
209
-
210
- # Using a simple namespace class to group related data
211
181
  class ParsedContent:
212
- def __init__(self):
213
- self.cleaned_lines = []
214
- self.existing_patterns = set()
182
+ def __init__(self) -> None:
183
+ self.cleaned_lines: list[str] = []
184
+ self.existing_patterns: set[str] = set()
215
185
 
216
186
  parsed = ParsedContent()
217
187
  parser_state = self._init_parser_state()
@@ -222,7 +192,6 @@ class ConfigMergeService(ConfigMergeServiceProtocol):
222
192
  return parsed
223
193
 
224
194
  def _init_parser_state(self) -> dict[str, bool]:
225
- """Initialize parser state for gitignore parsing."""
226
195
  return {
227
196
  "inside_crackerjack_section": False,
228
197
  "skip_empty_after_crackerjack": False,
@@ -231,60 +200,49 @@ class ConfigMergeService(ConfigMergeServiceProtocol):
231
200
  def _process_gitignore_line(
232
201
  self, line: str, parsed: t.Any, state: dict[str, bool]
233
202
  ) -> dict[str, bool]:
234
- """Process a single line during gitignore parsing."""
235
203
  stripped = line.strip()
236
204
 
237
- # Handle Crackerjack section headers
238
205
  if self._is_crackerjack_header(stripped):
239
206
  return self._handle_crackerjack_header(state)
240
207
 
241
- # Handle empty lines after headers
242
208
  if self._should_skip_empty_line(stripped, state):
243
209
  state["skip_empty_after_crackerjack"] = False
244
210
  return state
245
211
 
246
212
  state["skip_empty_after_crackerjack"] = False
247
213
 
248
- # Process patterns and lines
249
214
  self._collect_pattern_if_present(stripped, parsed, state)
250
215
  self._add_line_if_non_crackerjack(line, parsed, state)
251
216
 
252
217
  return state
253
218
 
254
219
  def _handle_crackerjack_header(self, state: dict[str, bool]) -> dict[str, bool]:
255
- """Handle Crackerjack section header detection."""
256
220
  if not state["inside_crackerjack_section"]:
257
221
  state["inside_crackerjack_section"] = True
258
222
  state["skip_empty_after_crackerjack"] = True
259
223
  return state
260
224
 
261
225
  def _should_skip_empty_line(self, stripped: str, state: dict[str, bool]) -> bool:
262
- """Check if empty line should be skipped after Crackerjack header."""
263
226
  return state["skip_empty_after_crackerjack"] and not stripped
264
227
 
265
228
  def _collect_pattern_if_present(
266
229
  self, stripped: str, parsed: t.Any, state: dict[str, bool]
267
230
  ) -> None:
268
- """Collect gitignore pattern if present on this line."""
269
231
  if stripped and not stripped.startswith("#"):
270
232
  parsed.existing_patterns.add(stripped)
271
233
 
272
234
  def _add_line_if_non_crackerjack(
273
235
  self, line: str, parsed: t.Any, state: dict[str, bool]
274
236
  ) -> None:
275
- """Add line to cleaned output if not in Crackerjack section."""
276
237
  if not state["inside_crackerjack_section"]:
277
238
  parsed.cleaned_lines.append(line)
278
239
 
279
240
  def _is_crackerjack_header(self, line: str) -> bool:
280
- """Check if a line is a Crackerjack section header."""
281
241
  return line in ("# Crackerjack patterns", "# Crackerjack generated files")
282
242
 
283
243
  def _build_merged_gitignore_content(
284
244
  self, parsed_content: t.Any, new_patterns: list[str]
285
245
  ) -> str:
286
- """Build the final merged .gitignore content."""
287
- # Remove trailing empty line if exists
288
246
  if parsed_content.cleaned_lines and not parsed_content.cleaned_lines[-1]:
289
247
  parsed_content.cleaned_lines.pop()
290
248
 
@@ -292,7 +250,6 @@ class ConfigMergeService(ConfigMergeServiceProtocol):
292
250
  if merged_content:
293
251
  merged_content += "\n"
294
252
 
295
- # Add consolidated Crackerjack section
296
253
  all_crackerjack_patterns = self._get_consolidated_patterns(
297
254
  parsed_content.existing_patterns, new_patterns
298
255
  )
@@ -307,24 +264,20 @@ class ConfigMergeService(ConfigMergeServiceProtocol):
307
264
  def _get_consolidated_patterns(
308
265
  self, existing_patterns: set[str], new_patterns: list[str]
309
266
  ) -> list[str]:
310
- """Get consolidated list of all Crackerjack patterns."""
311
267
  new_patterns_to_add = [p for p in new_patterns if p not in existing_patterns]
312
- return list(existing_patterns) + new_patterns_to_add
268
+ return list[t.Any](existing_patterns) + new_patterns_to_add
313
269
 
314
270
  def write_pyproject_config(
315
271
  self,
316
272
  config: dict[str, t.Any],
317
273
  target_path: str | t.Any,
318
274
  ) -> None:
319
- """Write pyproject.toml config with proper formatting."""
320
275
  target_path = Path(target_path)
321
276
 
322
- # Use BytesIO for proper TOML encoding
323
277
  buffer = io.BytesIO()
324
278
  tomli_w.dump(config, buffer)
325
279
  content = buffer.getvalue().decode("utf-8")
326
280
 
327
- # Clean trailing whitespace
328
281
  from crackerjack.services.filesystem import FileSystemService
329
282
 
330
283
  content = FileSystemService.clean_trailing_whitespace_and_newlines(content)
@@ -339,7 +292,6 @@ class ConfigMergeService(ConfigMergeServiceProtocol):
339
292
  config: dict[str, t.Any],
340
293
  target_path: str | t.Any,
341
294
  ) -> None:
342
- """Write .pre-commit-config.yaml with proper formatting."""
343
295
  target_path = Path(target_path)
344
296
 
345
297
  yaml_content = yaml.dump(
@@ -348,12 +300,8 @@ class ConfigMergeService(ConfigMergeServiceProtocol):
348
300
  sort_keys=False,
349
301
  width=float("inf"),
350
302
  )
351
- content = (
352
- yaml_content.decode() if isinstance(yaml_content, bytes) else yaml_content
353
- )
354
- content = content or ""
303
+ content = yaml_content or ""
355
304
 
356
- # Clean trailing whitespace
357
305
  from crackerjack.services.filesystem import FileSystemService
358
306
 
359
307
  content = FileSystemService.clean_trailing_whitespace_and_newlines(content)
@@ -368,7 +316,6 @@ class ConfigMergeService(ConfigMergeServiceProtocol):
368
316
  target_config: dict[str, t.Any],
369
317
  source_config: dict[str, t.Any],
370
318
  ) -> None:
371
- """Ensure crackerjack is in dev dependencies."""
372
319
  if "dependency-groups" not in target_config:
373
320
  target_config["dependency-groups"] = {}
374
321
 
@@ -386,7 +333,6 @@ class ConfigMergeService(ConfigMergeServiceProtocol):
386
333
  source_config: dict[str, t.Any],
387
334
  project_name: str,
388
335
  ) -> None:
389
- """Merge tool configurations from source to target."""
390
336
  source_tools = source_config.get("tool", {})
391
337
 
392
338
  if "tool" not in target_config:
@@ -422,7 +368,6 @@ class ConfigMergeService(ConfigMergeServiceProtocol):
422
368
  project_name,
423
369
  )
424
370
 
425
- # Merge pytest markers
426
371
  self._merge_pytest_markers(target_tools, source_tools)
427
372
 
428
373
  def _merge_tool_settings(
@@ -432,7 +377,6 @@ class ConfigMergeService(ConfigMergeServiceProtocol):
432
377
  tool_name: str,
433
378
  project_name: str,
434
379
  ) -> None:
435
- """Merge individual tool settings."""
436
380
  updated_keys = []
437
381
 
438
382
  for key, value in source_tool.items():
@@ -452,7 +396,6 @@ class ConfigMergeService(ConfigMergeServiceProtocol):
452
396
  target_tools: dict[str, t.Any],
453
397
  source_tools: dict[str, t.Any],
454
398
  ) -> None:
455
- """Merge pytest markers avoiding duplicates."""
456
399
  if "pytest" not in source_tools or "pytest" not in target_tools:
457
400
  return
458
401
 
@@ -465,7 +408,6 @@ class ConfigMergeService(ConfigMergeServiceProtocol):
465
408
  source_markers = source_pytest["ini_options"].get("markers", [])
466
409
  target_markers = target_pytest["ini_options"].get("markers", [])
467
410
 
468
- # Extract existing marker names
469
411
  existing_marker_names = {marker.split(": ")[0] for marker in target_markers}
470
412
  new_markers = [
471
413
  marker
@@ -483,21 +425,18 @@ class ConfigMergeService(ConfigMergeServiceProtocol):
483
425
  self,
484
426
  target_config: dict[str, t.Any],
485
427
  ) -> None:
486
- """Remove fixed coverage requirements to use ratchet system."""
487
428
  target_coverage = (
488
429
  target_config.get("tool", {}).get("pytest", {}).get("ini_options", {})
489
430
  )
490
431
 
491
- # Remove --cov-fail-under from addopts
492
432
  addopts = target_coverage.get("addopts", "")
493
433
  if isinstance(addopts, str):
494
434
  original_addopts = addopts
495
435
 
496
- # Remove coverage fail-under flags
497
436
  from crackerjack.services.regex_patterns import remove_coverage_fail_under
498
437
 
499
438
  addopts = remove_coverage_fail_under(addopts).strip()
500
- addopts = " ".join(addopts.split()) # Normalize whitespace
439
+ addopts = " ".join(addopts.split())
501
440
 
502
441
  if original_addopts != addopts:
503
442
  target_coverage["addopts"] = addopts
@@ -505,7 +444,6 @@ class ConfigMergeService(ConfigMergeServiceProtocol):
505
444
  "[green]🔄[/green] Removed fixed coverage requirement (using ratchet system)"
506
445
  )
507
446
 
508
- # Reset coverage.report.fail_under to 0
509
447
  coverage_report = (
510
448
  target_config.get("tool", {}).get("coverage", {}).get("report", {})
511
449
  )
@@ -519,17 +457,18 @@ class ConfigMergeService(ConfigMergeServiceProtocol):
519
457
  def _replace_project_name_in_tool_config(
520
458
  self, tool_config: dict[str, t.Any], project_name: str
521
459
  ) -> dict[str, t.Any]:
522
- """Replace project name in tool configuration."""
523
460
  if project_name == "crackerjack":
524
461
  return tool_config
525
462
 
526
463
  result = copy.deepcopy(tool_config)
527
- return self._replace_project_name_in_config_value(result, project_name)
464
+ return t.cast(
465
+ dict[str, t.Any],
466
+ self._replace_project_name_in_config_value(result, project_name),
467
+ )
528
468
 
529
469
  def _replace_project_name_in_config_value(
530
470
  self, value: t.Any, project_name: str
531
471
  ) -> t.Any:
532
- """Recursively replace project name in configuration values."""
533
472
  if project_name == "crackerjack":
534
473
  return value
535
474