crackerjack 0.30.3__py3-none-any.whl → 0.31.7__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 (156) hide show
  1. crackerjack/CLAUDE.md +1005 -0
  2. crackerjack/RULES.md +380 -0
  3. crackerjack/__init__.py +42 -13
  4. crackerjack/__main__.py +227 -299
  5. crackerjack/agents/__init__.py +41 -0
  6. crackerjack/agents/architect_agent.py +281 -0
  7. crackerjack/agents/base.py +170 -0
  8. crackerjack/agents/coordinator.py +512 -0
  9. crackerjack/agents/documentation_agent.py +498 -0
  10. crackerjack/agents/dry_agent.py +388 -0
  11. crackerjack/agents/formatting_agent.py +245 -0
  12. crackerjack/agents/import_optimization_agent.py +281 -0
  13. crackerjack/agents/performance_agent.py +669 -0
  14. crackerjack/agents/proactive_agent.py +104 -0
  15. crackerjack/agents/refactoring_agent.py +788 -0
  16. crackerjack/agents/security_agent.py +529 -0
  17. crackerjack/agents/test_creation_agent.py +657 -0
  18. crackerjack/agents/test_specialist_agent.py +486 -0
  19. crackerjack/agents/tracker.py +212 -0
  20. crackerjack/api.py +560 -0
  21. crackerjack/cli/__init__.py +24 -0
  22. crackerjack/cli/facade.py +104 -0
  23. crackerjack/cli/handlers.py +267 -0
  24. crackerjack/cli/interactive.py +471 -0
  25. crackerjack/cli/options.py +409 -0
  26. crackerjack/cli/utils.py +18 -0
  27. crackerjack/code_cleaner.py +618 -928
  28. crackerjack/config/__init__.py +19 -0
  29. crackerjack/config/hooks.py +218 -0
  30. crackerjack/core/__init__.py +0 -0
  31. crackerjack/core/async_workflow_orchestrator.py +406 -0
  32. crackerjack/core/autofix_coordinator.py +200 -0
  33. crackerjack/core/container.py +104 -0
  34. crackerjack/core/enhanced_container.py +542 -0
  35. crackerjack/core/performance.py +243 -0
  36. crackerjack/core/phase_coordinator.py +585 -0
  37. crackerjack/core/proactive_workflow.py +316 -0
  38. crackerjack/core/session_coordinator.py +289 -0
  39. crackerjack/core/workflow_orchestrator.py +826 -0
  40. crackerjack/dynamic_config.py +94 -103
  41. crackerjack/errors.py +263 -41
  42. crackerjack/executors/__init__.py +11 -0
  43. crackerjack/executors/async_hook_executor.py +431 -0
  44. crackerjack/executors/cached_hook_executor.py +242 -0
  45. crackerjack/executors/hook_executor.py +345 -0
  46. crackerjack/executors/individual_hook_executor.py +669 -0
  47. crackerjack/intelligence/__init__.py +44 -0
  48. crackerjack/intelligence/adaptive_learning.py +751 -0
  49. crackerjack/intelligence/agent_orchestrator.py +551 -0
  50. crackerjack/intelligence/agent_registry.py +414 -0
  51. crackerjack/intelligence/agent_selector.py +502 -0
  52. crackerjack/intelligence/integration.py +290 -0
  53. crackerjack/interactive.py +576 -315
  54. crackerjack/managers/__init__.py +11 -0
  55. crackerjack/managers/async_hook_manager.py +135 -0
  56. crackerjack/managers/hook_manager.py +137 -0
  57. crackerjack/managers/publish_manager.py +433 -0
  58. crackerjack/managers/test_command_builder.py +151 -0
  59. crackerjack/managers/test_executor.py +443 -0
  60. crackerjack/managers/test_manager.py +258 -0
  61. crackerjack/managers/test_manager_backup.py +1124 -0
  62. crackerjack/managers/test_progress.py +114 -0
  63. crackerjack/mcp/__init__.py +0 -0
  64. crackerjack/mcp/cache.py +336 -0
  65. crackerjack/mcp/client_runner.py +104 -0
  66. crackerjack/mcp/context.py +621 -0
  67. crackerjack/mcp/dashboard.py +636 -0
  68. crackerjack/mcp/enhanced_progress_monitor.py +479 -0
  69. crackerjack/mcp/file_monitor.py +336 -0
  70. crackerjack/mcp/progress_components.py +569 -0
  71. crackerjack/mcp/progress_monitor.py +949 -0
  72. crackerjack/mcp/rate_limiter.py +332 -0
  73. crackerjack/mcp/server.py +22 -0
  74. crackerjack/mcp/server_core.py +244 -0
  75. crackerjack/mcp/service_watchdog.py +501 -0
  76. crackerjack/mcp/state.py +395 -0
  77. crackerjack/mcp/task_manager.py +257 -0
  78. crackerjack/mcp/tools/__init__.py +17 -0
  79. crackerjack/mcp/tools/core_tools.py +249 -0
  80. crackerjack/mcp/tools/error_analyzer.py +308 -0
  81. crackerjack/mcp/tools/execution_tools.py +372 -0
  82. crackerjack/mcp/tools/execution_tools_backup.py +1097 -0
  83. crackerjack/mcp/tools/intelligence_tool_registry.py +80 -0
  84. crackerjack/mcp/tools/intelligence_tools.py +314 -0
  85. crackerjack/mcp/tools/monitoring_tools.py +502 -0
  86. crackerjack/mcp/tools/proactive_tools.py +384 -0
  87. crackerjack/mcp/tools/progress_tools.py +217 -0
  88. crackerjack/mcp/tools/utility_tools.py +341 -0
  89. crackerjack/mcp/tools/workflow_executor.py +565 -0
  90. crackerjack/mcp/websocket/__init__.py +14 -0
  91. crackerjack/mcp/websocket/app.py +39 -0
  92. crackerjack/mcp/websocket/endpoints.py +559 -0
  93. crackerjack/mcp/websocket/jobs.py +253 -0
  94. crackerjack/mcp/websocket/server.py +116 -0
  95. crackerjack/mcp/websocket/websocket_handler.py +78 -0
  96. crackerjack/mcp/websocket_server.py +10 -0
  97. crackerjack/models/__init__.py +31 -0
  98. crackerjack/models/config.py +93 -0
  99. crackerjack/models/config_adapter.py +230 -0
  100. crackerjack/models/protocols.py +118 -0
  101. crackerjack/models/task.py +154 -0
  102. crackerjack/monitoring/ai_agent_watchdog.py +450 -0
  103. crackerjack/monitoring/regression_prevention.py +638 -0
  104. crackerjack/orchestration/__init__.py +0 -0
  105. crackerjack/orchestration/advanced_orchestrator.py +970 -0
  106. crackerjack/orchestration/coverage_improvement.py +223 -0
  107. crackerjack/orchestration/execution_strategies.py +341 -0
  108. crackerjack/orchestration/test_progress_streamer.py +636 -0
  109. crackerjack/plugins/__init__.py +15 -0
  110. crackerjack/plugins/base.py +200 -0
  111. crackerjack/plugins/hooks.py +246 -0
  112. crackerjack/plugins/loader.py +335 -0
  113. crackerjack/plugins/managers.py +259 -0
  114. crackerjack/py313.py +8 -3
  115. crackerjack/services/__init__.py +22 -0
  116. crackerjack/services/cache.py +314 -0
  117. crackerjack/services/config.py +358 -0
  118. crackerjack/services/config_integrity.py +99 -0
  119. crackerjack/services/contextual_ai_assistant.py +516 -0
  120. crackerjack/services/coverage_ratchet.py +356 -0
  121. crackerjack/services/debug.py +736 -0
  122. crackerjack/services/dependency_monitor.py +617 -0
  123. crackerjack/services/enhanced_filesystem.py +439 -0
  124. crackerjack/services/file_hasher.py +151 -0
  125. crackerjack/services/filesystem.py +421 -0
  126. crackerjack/services/git.py +176 -0
  127. crackerjack/services/health_metrics.py +611 -0
  128. crackerjack/services/initialization.py +873 -0
  129. crackerjack/services/log_manager.py +286 -0
  130. crackerjack/services/logging.py +174 -0
  131. crackerjack/services/metrics.py +578 -0
  132. crackerjack/services/pattern_cache.py +362 -0
  133. crackerjack/services/pattern_detector.py +515 -0
  134. crackerjack/services/performance_benchmarks.py +653 -0
  135. crackerjack/services/security.py +163 -0
  136. crackerjack/services/server_manager.py +234 -0
  137. crackerjack/services/smart_scheduling.py +144 -0
  138. crackerjack/services/tool_version_service.py +61 -0
  139. crackerjack/services/unified_config.py +437 -0
  140. crackerjack/services/version_checker.py +248 -0
  141. crackerjack/slash_commands/__init__.py +14 -0
  142. crackerjack/slash_commands/init.md +122 -0
  143. crackerjack/slash_commands/run.md +163 -0
  144. crackerjack/slash_commands/status.md +127 -0
  145. crackerjack-0.31.7.dist-info/METADATA +742 -0
  146. crackerjack-0.31.7.dist-info/RECORD +149 -0
  147. crackerjack-0.31.7.dist-info/entry_points.txt +2 -0
  148. crackerjack/.gitignore +0 -34
  149. crackerjack/.libcst.codemod.yaml +0 -18
  150. crackerjack/.pdm.toml +0 -1
  151. crackerjack/crackerjack.py +0 -3805
  152. crackerjack/pyproject.toml +0 -286
  153. crackerjack-0.30.3.dist-info/METADATA +0 -1290
  154. crackerjack-0.30.3.dist-info/RECORD +0 -16
  155. {crackerjack-0.30.3.dist-info → crackerjack-0.31.7.dist-info}/WHEEL +0 -0
  156. {crackerjack-0.30.3.dist-info → crackerjack-0.31.7.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,873 @@
1
+ import json
2
+ import subprocess
3
+ import typing as t
4
+ from pathlib import Path
5
+
6
+ import tomli
7
+ import tomli_w
8
+ import yaml
9
+ from rich.console import Console
10
+
11
+ from .filesystem import FileSystemService
12
+ from .git import GitService
13
+
14
+
15
+ class InitializationService:
16
+ def __init__(
17
+ self,
18
+ console: Console,
19
+ filesystem: FileSystemService,
20
+ git_service: GitService,
21
+ pkg_path: Path,
22
+ ) -> None:
23
+ self.console = console
24
+ self.filesystem = filesystem
25
+ self.git_service = git_service
26
+ self.pkg_path = pkg_path
27
+
28
+ def initialize_project(
29
+ self,
30
+ target_path: Path | None = None,
31
+ force: bool = False,
32
+ ) -> dict[str, t.Any]:
33
+ if target_path is None:
34
+ target_path = Path.cwd()
35
+
36
+ results = self._create_results_dict(target_path)
37
+
38
+ try:
39
+ config_files = self._get_config_files()
40
+ project_name = target_path.name
41
+
42
+ for file_name, merge_strategy in config_files.items():
43
+ self._process_config_file(
44
+ file_name,
45
+ merge_strategy,
46
+ project_name,
47
+ target_path,
48
+ force,
49
+ results,
50
+ )
51
+
52
+ self._print_summary(results)
53
+
54
+ except Exception as e:
55
+ self._handle_initialization_error(results, e)
56
+
57
+ return results
58
+
59
+ def _create_results_dict(self, target_path: Path) -> dict[str, t.Any]:
60
+ return {
61
+ "target_path": str(target_path),
62
+ "files_copied": [],
63
+ "files_skipped": [],
64
+ "errors": [],
65
+ "success": True,
66
+ }
67
+
68
+ def _get_config_files(self) -> dict[str, str]:
69
+ """Get config files with their merge strategies."""
70
+ return {
71
+ ".pre-commit-config.yaml": "smart_merge",
72
+ "pyproject.toml": "smart_merge",
73
+ "CLAUDE.md": "smart_append",
74
+ "RULES.md": "replace_if_missing",
75
+ "mcp.json": "special", # Special handling: mcp.json -> .mcp.json with merging
76
+ }
77
+
78
+ def _process_config_file(
79
+ self,
80
+ file_name: str,
81
+ merge_strategy: str,
82
+ project_name: str,
83
+ target_path: Path,
84
+ force: bool,
85
+ results: dict[str, t.Any],
86
+ ) -> None:
87
+ # Special handling for mcp.json -> .mcp.json
88
+ if file_name == "mcp.json":
89
+ self._process_mcp_config(target_path, force, results)
90
+ return
91
+
92
+ source_file = self.pkg_path.parent / file_name
93
+ target_file = target_path / file_name
94
+
95
+ if not source_file.exists():
96
+ self._handle_missing_source_file(file_name, results)
97
+ return
98
+
99
+ try:
100
+ # Handle different merge strategies
101
+ if merge_strategy == "smart_merge":
102
+ self._smart_merge_config(
103
+ source_file,
104
+ target_file,
105
+ file_name,
106
+ project_name,
107
+ force,
108
+ results,
109
+ )
110
+ elif merge_strategy == "smart_append":
111
+ self._smart_append_config(
112
+ source_file,
113
+ target_file,
114
+ file_name,
115
+ project_name,
116
+ force,
117
+ results,
118
+ )
119
+ elif merge_strategy == "replace_if_missing":
120
+ if not target_file.exists() or force:
121
+ content = self._read_and_process_content(
122
+ source_file,
123
+ True,
124
+ project_name,
125
+ )
126
+ self._write_file_and_track(target_file, content, file_name, results)
127
+ else:
128
+ self._skip_existing_file(file_name, results)
129
+ else:
130
+ # Fallback to old behavior
131
+ if not self._should_copy_file(target_file, force, file_name, results):
132
+ return
133
+ content = self._read_and_process_content(
134
+ source_file,
135
+ True,
136
+ project_name,
137
+ )
138
+ self._write_file_and_track(target_file, content, file_name, results)
139
+
140
+ except Exception as e:
141
+ self._handle_file_processing_error(file_name, e, results)
142
+
143
+ def _should_copy_file(
144
+ self,
145
+ target_file: Path,
146
+ force: bool,
147
+ file_name: str,
148
+ results: dict[str, t.Any],
149
+ ) -> bool:
150
+ if target_file.exists() and not force:
151
+ t.cast("list[str]", results["files_skipped"]).append(file_name)
152
+ self.console.print(
153
+ f"[yellow]⚠️[/yellow] Skipped {file_name} (already exists)",
154
+ )
155
+ return False
156
+ return True
157
+
158
+ def _read_and_process_content(
159
+ self,
160
+ source_file: Path,
161
+ should_replace: bool,
162
+ project_name: str,
163
+ ) -> str:
164
+ content = source_file.read_text()
165
+
166
+ if should_replace and project_name != "crackerjack":
167
+ content = content.replace("crackerjack", project_name)
168
+
169
+ return content
170
+
171
+ def _write_file_and_track(
172
+ self,
173
+ target_file: Path,
174
+ content: str,
175
+ file_name: str,
176
+ results: dict[str, t.Any],
177
+ ) -> None:
178
+ target_file.write_text(content)
179
+ t.cast("list[str]", results["files_copied"]).append(file_name)
180
+
181
+ try:
182
+ self.git_service.add_files([str(target_file)])
183
+ except Exception as e:
184
+ self.console.print(f"[yellow]⚠️[/yellow] Could not git add {file_name}: {e}")
185
+
186
+ self.console.print(f"[green]✅[/green] Copied {file_name}")
187
+
188
+ def _skip_existing_file(self, file_name: str, results: dict[str, t.Any]) -> None:
189
+ t.cast("list[str]", results["files_skipped"]).append(file_name)
190
+ self.console.print(f"[yellow]⚠️[/yellow] Skipped {file_name} (already exists)")
191
+
192
+ def _handle_missing_source_file(
193
+ self,
194
+ file_name: str,
195
+ results: dict[str, t.Any],
196
+ ) -> None:
197
+ error_msg = f"Source file not found: {file_name}"
198
+ t.cast("list[str]", results["errors"]).append(error_msg)
199
+ self.console.print(f"[yellow]⚠️[/yellow] {error_msg}")
200
+
201
+ def _handle_file_processing_error(
202
+ self,
203
+ file_name: str,
204
+ error: Exception,
205
+ results: dict[str, t.Any],
206
+ ) -> None:
207
+ error_msg = f"Failed to copy {file_name}: {error}"
208
+ t.cast("list[str]", results["errors"]).append(error_msg)
209
+ results["success"] = False
210
+ self.console.print(f"[red]❌[/red] {error_msg}")
211
+
212
+ def _print_summary(self, results: dict[str, t.Any]) -> None:
213
+ if results["success"]:
214
+ self.console.print(
215
+ f"[green]🎉 Project initialized successfully ! [/green] "
216
+ f"Copied {len(t.cast('list[str]', results['files_copied']))} files",
217
+ )
218
+ else:
219
+ self.console.print(
220
+ "[red]❌ Project initialization completed with errors[/red]",
221
+ )
222
+
223
+ def _handle_initialization_error(
224
+ self,
225
+ results: dict[str, t.Any],
226
+ error: Exception,
227
+ ) -> None:
228
+ results["success"] = False
229
+ t.cast("list[str]", results["errors"]).append(f"Initialization failed: {error}")
230
+ self.console.print(f"[red]❌[/red] Initialization failed: {error}")
231
+
232
+ def check_uv_installed(self) -> bool:
233
+ try:
234
+ result = subprocess.run(
235
+ ["uv", "--version"],
236
+ capture_output=True,
237
+ text=True,
238
+ timeout=10,
239
+ check=False,
240
+ )
241
+ return result.returncode == 0
242
+ except (FileNotFoundError, subprocess.TimeoutExpired):
243
+ return False
244
+
245
+ def _process_mcp_config(
246
+ self,
247
+ target_path: Path,
248
+ force: bool,
249
+ results: dict[str, t.Any],
250
+ ) -> None:
251
+ """Handle special processing for mcp.json -> .mcp.json with merging."""
252
+ # Source: mcp.json in crackerjack package (contains servers to add to projects)
253
+ source_file = self.pkg_path / "mcp.json"
254
+ # Target: .mcp.json in target project
255
+ target_file = target_path / ".mcp.json"
256
+
257
+ if not source_file.exists():
258
+ self._handle_missing_source_file("mcp.json", results)
259
+ return
260
+
261
+ try:
262
+ # Load the crackerjack MCP servers to add
263
+ with source_file.open() as f:
264
+ source_config = json.load(f)
265
+
266
+ if not isinstance(source_config.get("mcpServers"), dict):
267
+ self._handle_file_processing_error(
268
+ "mcp.json",
269
+ ValueError("Invalid mcp.json format: missing mcpServers"),
270
+ results,
271
+ )
272
+ return
273
+
274
+ crackerjack_servers = source_config["mcpServers"]
275
+
276
+ # If target .mcp.json doesn't exist, create it with crackerjack servers
277
+ if not target_file.exists():
278
+ target_config = {"mcpServers": crackerjack_servers}
279
+ self._write_mcp_config_and_track(target_file, target_config, results)
280
+ self.console.print(
281
+ "[green]✅[/green] Created .mcp.json with crackerjack MCP servers",
282
+ )
283
+ return
284
+
285
+ # If target exists and force=False, skip unless we're merging
286
+ if target_file.exists() and not force:
287
+ # Always merge crackerjack servers into existing config
288
+ self._merge_mcp_config(target_file, crackerjack_servers, results)
289
+ return
290
+
291
+ # If force=True, replace entirely with crackerjack servers
292
+ target_config = {"mcpServers": crackerjack_servers}
293
+ self._write_mcp_config_and_track(target_file, target_config, results)
294
+ self.console.print(
295
+ "[green]✅[/green] Updated .mcp.json with crackerjack MCP servers",
296
+ )
297
+
298
+ except Exception as e:
299
+ self._handle_file_processing_error(".mcp.json", e, results)
300
+
301
+ def _merge_mcp_config(
302
+ self,
303
+ target_file: Path,
304
+ crackerjack_servers: dict[str, t.Any],
305
+ results: dict[str, t.Any],
306
+ ) -> None:
307
+ """Merge crackerjack servers into existing .mcp.json."""
308
+ try:
309
+ # Load existing config
310
+ with target_file.open() as f:
311
+ existing_config = json.load(f)
312
+
313
+ if not isinstance(existing_config.get("mcpServers"), dict):
314
+ existing_config["mcpServers"] = {}
315
+
316
+ # Merge crackerjack servers (they override existing ones with same name)
317
+ existing_servers = existing_config["mcpServers"]
318
+ updated_servers = {}
319
+
320
+ for name, config in crackerjack_servers.items():
321
+ if name in existing_servers:
322
+ self.console.print(
323
+ f"[yellow]🔄[/yellow] Updating existing MCP server: {name}",
324
+ )
325
+ else:
326
+ self.console.print(
327
+ f"[green]➕[/green] Adding new MCP server: {name}",
328
+ )
329
+ updated_servers[name] = config
330
+
331
+ # Merge into existing config
332
+ existing_servers.update(updated_servers)
333
+
334
+ # Write the merged config
335
+ self._write_mcp_config_and_track(target_file, existing_config, results)
336
+
337
+ t.cast("list[str]", results["files_copied"]).append(".mcp.json (merged)")
338
+
339
+ except Exception as e:
340
+ self._handle_file_processing_error(".mcp.json (merge)", e, results)
341
+
342
+ def _write_mcp_config_and_track(
343
+ self,
344
+ target_file: Path,
345
+ config: dict[str, t.Any],
346
+ results: dict[str, t.Any],
347
+ ) -> None:
348
+ """Write MCP config file and track in results."""
349
+ with target_file.open("w") as f:
350
+ json.dump(config, f, indent=2)
351
+
352
+ t.cast("list[str]", results["files_copied"]).append(".mcp.json")
353
+
354
+ # Try to git add the file
355
+ try:
356
+ self.git_service.add_files([str(target_file)])
357
+ except Exception as e:
358
+ self.console.print(f"[yellow]⚠️[/yellow] Could not git add .mcp.json: {e}")
359
+
360
+ def validate_project_structure(self) -> bool:
361
+ required_indicators = [
362
+ self.pkg_path / "pyproject.toml",
363
+ self.pkg_path / "setup.py",
364
+ ]
365
+
366
+ return any(path.exists() for path in required_indicators)
367
+
368
+ def _generate_project_claude_content(self, project_name: str) -> str:
369
+ """Generate customized CLAUDE.md content for external projects."""
370
+ return f"""
371
+ # Crackerjack Integration for {project_name}
372
+
373
+ This project uses crackerjack for Python project management and quality assurance.
374
+
375
+ ## Recommended Claude Code Agents
376
+
377
+ For optimal development experience with this crackerjack-enabled project, use these specialized agents:
378
+
379
+ ### **Primary Agents (Use for all Python development)**
380
+
381
+ - **🏗️ crackerjack-architect**: Expert in crackerjack's modular architecture and Python project management patterns. **Use PROACTIVELY** for all feature development, architectural decisions, and ensuring code follows crackerjack standards from the start.
382
+
383
+ - **🐍 python-pro**: Modern Python development with type hints, async/await patterns, and clean architecture
384
+
385
+ - **🧪 pytest-hypothesis-specialist**: Advanced testing patterns, property-based testing, and test optimization
386
+
387
+ ### **Task-Specific Agents**
388
+
389
+ - **🧪 crackerjack-test-specialist**: Advanced testing specialist for complex testing scenarios and coverage optimization
390
+ - **🏗️ backend-architect**: System design, API architecture, and service integration patterns
391
+ - **🔒 security-auditor**: Security analysis, vulnerability detection, and secure coding practices
392
+
393
+ ### **Agent Usage Patterns**
394
+
395
+ ```bash
396
+ # Start development with crackerjack-compliant architecture
397
+ Task tool with subagent_type="crackerjack-architect" for feature planning
398
+
399
+ # Implement with modern Python best practices
400
+ Task tool with subagent_type="python-pro" for code implementation
401
+
402
+ # Add comprehensive testing
403
+ Task tool with subagent_type="pytest-hypothesis-specialist" for test development
404
+
405
+ # Security review before completion
406
+ Task tool with subagent_type="security-auditor" for security analysis
407
+ ```
408
+
409
+ **💡 Pro Tip**: The crackerjack-architect agent automatically ensures code follows crackerjack patterns from the start, eliminating the need for retrofitting and quality fixes.
410
+
411
+ ## Crackerjack Quality Standards
412
+
413
+ This project follows crackerjack's clean code philosophy:
414
+
415
+ ### **Core Principles**
416
+ - **EVERY LINE OF CODE IS A LIABILITY**: The best code is no code
417
+ - **DRY (Don't Repeat Yourself)**: If you write it twice, you're doing it wrong
418
+ - **YAGNI (You Ain't Gonna Need It)**: Build only what's needed NOW
419
+ - **KISS (Keep It Simple, Stupid)**: Complexity is the enemy of maintainability
420
+
421
+ ### **Quality Rules**
422
+ - **Cognitive complexity ≤15** per function (automatically enforced)
423
+ - **Coverage ratchet system**: Never decrease coverage, always improve toward 100%
424
+ - **Type annotations required**: All functions must have return type hints
425
+ - **Security patterns**: No hardcoded paths, proper temp file handling
426
+ - **Python 3.13+ modern patterns**: Use `|` unions, pathlib over os.path
427
+
428
+ ## Development Workflow
429
+
430
+ ### **Quality Commands**
431
+ ```bash
432
+ # Quality checks (fast feedback during development)
433
+ python -m crackerjack
434
+
435
+ # With comprehensive testing
436
+ python -m crackerjack -t
437
+
438
+ # AI agent mode with autonomous fixing
439
+ python -m crackerjack --ai-agent -t
440
+
441
+ # Full release workflow
442
+ python -m crackerjack -a patch
443
+ ```
444
+
445
+ ### **Recommended Workflow**
446
+ 1. **Plan with crackerjack-architect**: Ensure proper architecture from the start
447
+ 2. **Implement with python-pro**: Follow modern Python patterns
448
+ 3. **Test comprehensively**: Use pytest-hypothesis-specialist for robust testing
449
+ 4. **Run quality checks**: `python -m crackerjack -t` before committing
450
+ 5. **Security review**: Use security-auditor for final validation
451
+
452
+ ## Important Instructions
453
+
454
+ - **Use crackerjack-architect agent proactively** for all significant code changes
455
+ - **Never reduce test coverage** - the ratchet system only allows improvements
456
+ - **Follow crackerjack patterns** - the tools will enforce quality automatically
457
+ - **Leverage AI agent auto-fixing** - `python -m crackerjack --ai-agent -t` for autonomous quality fixes
458
+
459
+ ---
460
+ *This project is enhanced by crackerjack's intelligent Python project management.*
461
+ """.strip()
462
+
463
+ def _smart_append_config(
464
+ self,
465
+ source_file: Path,
466
+ target_file: Path,
467
+ file_name: str,
468
+ project_name: str,
469
+ force: bool,
470
+ results: dict[str, t.Any],
471
+ ) -> None:
472
+ """Smart append for CLAUDE.md - append crackerjack content without overwriting."""
473
+ if file_name == "CLAUDE.md" and project_name != "crackerjack":
474
+ # For external projects, generate customized crackerjack guidance
475
+ source_content = self._generate_project_claude_content(project_name)
476
+ else:
477
+ source_content = self._read_and_process_content(
478
+ source_file, True, project_name
479
+ )
480
+
481
+ if not target_file.exists():
482
+ # No existing file, just copy
483
+ self._write_file_and_track(target_file, source_content, file_name, results)
484
+ return
485
+
486
+ existing_content = target_file.read_text()
487
+
488
+ # Check if crackerjack content already exists
489
+ crackerjack_start_marker = "<!-- CRACKERJACK INTEGRATION START -->"
490
+ crackerjack_end_marker = "<!-- CRACKERJACK INTEGRATION END -->"
491
+
492
+ if crackerjack_start_marker in existing_content:
493
+ if force:
494
+ # Replace existing crackerjack section
495
+ start_idx = existing_content.find(crackerjack_start_marker)
496
+ end_idx = existing_content.find(crackerjack_end_marker)
497
+ if end_idx != -1:
498
+ end_idx += len(crackerjack_end_marker)
499
+ # Remove old crackerjack section
500
+ existing_content = (
501
+ existing_content[:start_idx] + existing_content[end_idx:]
502
+ ).strip()
503
+ else:
504
+ self._skip_existing_file(f"{file_name} (crackerjack section)", results)
505
+ return
506
+
507
+ # Append crackerjack content with clear markers
508
+ merged_content = (
509
+ existing_content.strip() + "\n\n" + crackerjack_start_marker + "\n"
510
+ )
511
+ merged_content += source_content.strip() + "\n"
512
+ merged_content += crackerjack_end_marker + "\n"
513
+
514
+ target_file.write_text(merged_content)
515
+ t.cast("list[str]", results["files_copied"]).append(f"{file_name} (appended)")
516
+
517
+ try:
518
+ self.git_service.add_files([str(target_file)])
519
+ except Exception as e:
520
+ self.console.print(f"[yellow]⚠️[/yellow] Could not git add {file_name}: {e}")
521
+
522
+ self.console.print(f"[green]✅[/green] Appended to {file_name}")
523
+
524
+ def _smart_merge_config(
525
+ self,
526
+ source_file: Path,
527
+ target_file: Path,
528
+ file_name: str,
529
+ project_name: str,
530
+ force: bool,
531
+ results: dict[str, t.Any],
532
+ ) -> None:
533
+ """Smart merge for configuration files."""
534
+ if file_name == "pyproject.toml":
535
+ self._smart_merge_pyproject(
536
+ source_file,
537
+ target_file,
538
+ project_name,
539
+ force,
540
+ results,
541
+ )
542
+ elif file_name == ".pre-commit-config.yaml":
543
+ self._smart_merge_pre_commit_config(
544
+ source_file,
545
+ target_file,
546
+ force,
547
+ results,
548
+ )
549
+ # Fallback to regular copy
550
+ elif not target_file.exists() or force:
551
+ content = self._read_and_process_content(
552
+ source_file,
553
+ True,
554
+ project_name,
555
+ )
556
+ self._write_file_and_track(target_file, content, file_name, results)
557
+ else:
558
+ self._skip_existing_file(file_name, results)
559
+
560
+ def _smart_merge_pyproject(
561
+ self,
562
+ source_file: Path,
563
+ target_file: Path,
564
+ project_name: str,
565
+ force: bool,
566
+ results: dict[str, t.Any],
567
+ ) -> None:
568
+ """Intelligently merge pyproject.toml configurations."""
569
+ # Load source (crackerjack) config
570
+ with source_file.open("rb") as f:
571
+ source_config = tomli.load(f)
572
+
573
+ if not target_file.exists():
574
+ # No existing file, just copy and replace project name
575
+ content = self._read_and_process_content(source_file, True, project_name)
576
+ self._write_file_and_track(target_file, content, "pyproject.toml", results)
577
+ return
578
+
579
+ # Load existing config
580
+ with target_file.open("rb") as f:
581
+ target_config = tomli.load(f)
582
+
583
+ # 1. Ensure crackerjack is in dev dependencies
584
+ self._ensure_crackerjack_dev_dependency(target_config, source_config)
585
+
586
+ # 2. Merge tool configurations
587
+ self._merge_tool_configurations(target_config, source_config)
588
+
589
+ # 3. Remove any fixed coverage requirements (use ratchet system instead)
590
+ self._remove_fixed_coverage_requirements(target_config)
591
+
592
+ # Write merged config with proper formatting
593
+ import io
594
+
595
+ # Use in-memory buffer to clean content before writing
596
+ buffer = io.BytesIO()
597
+ tomli_w.dump(target_config, buffer)
598
+ content = buffer.getvalue().decode("utf-8")
599
+
600
+ # Clean trailing whitespace and ensure single trailing newline
601
+ from crackerjack.services.filesystem import FileSystemService
602
+
603
+ content = FileSystemService.clean_trailing_whitespace_and_newlines(content)
604
+
605
+ with target_file.open("w", encoding="utf-8") as f:
606
+ f.write(content)
607
+
608
+ t.cast("list[str]", results["files_copied"]).append("pyproject.toml (merged)")
609
+
610
+ try:
611
+ self.git_service.add_files([str(target_file)])
612
+ except Exception as e:
613
+ self.console.print(
614
+ f"[yellow]⚠️[/yellow] Could not git add pyproject.toml: {e}",
615
+ )
616
+
617
+ self.console.print("[green]✅[/green] Smart merged pyproject.toml")
618
+
619
+ def _ensure_crackerjack_dev_dependency(
620
+ self,
621
+ target_config: dict[str, t.Any],
622
+ source_config: dict[str, t.Any],
623
+ ) -> None:
624
+ """Ensure crackerjack is in dev dependencies."""
625
+ # Check different dependency group structures
626
+ if "dependency-groups" not in target_config:
627
+ target_config["dependency-groups"] = {}
628
+
629
+ if "dev" not in target_config["dependency-groups"]:
630
+ target_config["dependency-groups"]["dev"] = []
631
+
632
+ dev_deps = target_config["dependency-groups"]["dev"]
633
+ if "crackerjack" not in str(dev_deps):
634
+ # Add crackerjack to dev dependencies
635
+ dev_deps.append("crackerjack")
636
+
637
+ def _merge_tool_configurations(
638
+ self,
639
+ target_config: dict[str, t.Any],
640
+ source_config: dict[str, t.Any],
641
+ ) -> None:
642
+ """Merge tool configurations, preserving existing settings."""
643
+ source_tools = source_config.get("tool", {})
644
+
645
+ if "tool" not in target_config:
646
+ target_config["tool"] = {}
647
+
648
+ target_tools = target_config["tool"]
649
+
650
+ # Tools to merge (add if missing, preserve if existing)
651
+ tools_to_merge = [
652
+ "ruff",
653
+ "pyright",
654
+ "bandit",
655
+ "vulture",
656
+ "refurb",
657
+ "complexipy",
658
+ "codespell",
659
+ "creosote",
660
+ ]
661
+
662
+ for tool_name in tools_to_merge:
663
+ if tool_name in source_tools:
664
+ if tool_name not in target_tools:
665
+ # Tool missing, add it
666
+ target_tools[tool_name] = source_tools[tool_name]
667
+ self.console.print(
668
+ f"[green]➕[/green] Added [tool.{tool_name}] configuration",
669
+ )
670
+ else:
671
+ # Tool exists, merge settings
672
+ self._merge_tool_settings(
673
+ target_tools[tool_name],
674
+ source_tools[tool_name],
675
+ tool_name,
676
+ )
677
+
678
+ # Special handling for pytest.ini_options markers
679
+ self._merge_pytest_markers(target_tools, source_tools)
680
+
681
+ def _merge_tool_settings(
682
+ self,
683
+ target_tool: dict[str, t.Any],
684
+ source_tool: dict[str, t.Any],
685
+ tool_name: str,
686
+ ) -> None:
687
+ """Merge individual tool settings."""
688
+ updated_keys = []
689
+
690
+ for key, value in source_tool.items():
691
+ if key not in target_tool:
692
+ target_tool[key] = value
693
+ updated_keys.append(key)
694
+
695
+ if updated_keys:
696
+ self.console.print(
697
+ f"[yellow]🔄[/yellow] Updated [tool.{tool_name}] with: {', '.join(updated_keys)}",
698
+ )
699
+
700
+ def _merge_pytest_markers(
701
+ self,
702
+ target_tools: dict[str, t.Any],
703
+ source_tools: dict[str, t.Any],
704
+ ) -> None:
705
+ """Merge pytest markers without duplication."""
706
+ if "pytest" not in source_tools or "pytest" not in target_tools:
707
+ return
708
+
709
+ source_pytest = source_tools["pytest"]
710
+ target_pytest = target_tools["pytest"]
711
+
712
+ if "ini_options" not in source_pytest or "ini_options" not in target_pytest:
713
+ return
714
+
715
+ source_markers = source_pytest["ini_options"].get("markers", [])
716
+ target_markers = target_pytest["ini_options"].get("markers", [])
717
+
718
+ # Extract marker names to avoid duplication
719
+ existing_marker_names = {marker.split(":")[0] for marker in target_markers}
720
+ new_markers = [
721
+ marker
722
+ for marker in source_markers
723
+ if marker.split(":")[0] not in existing_marker_names
724
+ ]
725
+
726
+ if new_markers:
727
+ target_markers.extend(new_markers)
728
+ self.console.print(
729
+ f"[green]➕[/green] Added pytest markers: {len(new_markers)}",
730
+ )
731
+
732
+ def _remove_fixed_coverage_requirements(
733
+ self,
734
+ target_config: dict[str, t.Any],
735
+ ) -> None:
736
+ """Remove fixed coverage requirements in favor of ratchet system."""
737
+ import re
738
+
739
+ target_coverage = (
740
+ target_config.get("tool", {}).get("pytest", {}).get("ini_options", {})
741
+ )
742
+
743
+ # Remove --cov-fail-under from pytest addopts
744
+ addopts = target_coverage.get("addopts", "")
745
+ if isinstance(addopts, str):
746
+ original_addopts = addopts
747
+ # Remove --cov-fail-under=N pattern
748
+ addopts = re.sub(r"--cov-fail-under=\d+\.?\d*\s*", "", addopts).strip()
749
+ # Clean up extra spaces
750
+ addopts = " ".join(addopts.split())
751
+
752
+ if original_addopts != addopts:
753
+ target_coverage["addopts"] = addopts
754
+ self.console.print(
755
+ "[green]🔄[/green] Removed fixed coverage requirement (using ratchet system)",
756
+ )
757
+
758
+ # Remove fail_under from coverage.report section
759
+ coverage_report = (
760
+ target_config.get("tool", {}).get("coverage", {}).get("report", {})
761
+ )
762
+ if "fail_under" in coverage_report:
763
+ original_fail_under = coverage_report["fail_under"]
764
+ coverage_report["fail_under"] = 0
765
+ self.console.print(
766
+ f"[green]🔄[/green] Reset coverage.report.fail_under from {original_fail_under} to 0 (ratchet system)",
767
+ )
768
+
769
+ def _extract_coverage_requirement(self, addopts: str | list[str]) -> int | None:
770
+ """Extract coverage requirement from pytest addopts."""
771
+ import re
772
+
773
+ # Handle both string and list formats
774
+ addopts_str = " ".join(addopts) if isinstance(addopts, list) else addopts
775
+ match = re.search(r"--cov-fail-under=(\d+)", addopts_str)
776
+ return int(match.group(1)) if match else None
777
+
778
+ def _smart_merge_pre_commit_config(
779
+ self,
780
+ source_file: Path,
781
+ target_file: Path,
782
+ force: bool,
783
+ results: dict[str, t.Any],
784
+ ) -> None:
785
+ """Smart merge for .pre-commit-config.yaml."""
786
+ # Load source config
787
+ with source_file.open() as f:
788
+ source_config = yaml.safe_load(f)
789
+
790
+ if not target_file.exists():
791
+ # No existing file, just copy with proper formatting
792
+ content = source_file.read_text()
793
+ # Clean trailing whitespace and ensure single trailing newline
794
+ from crackerjack.services.filesystem import FileSystemService
795
+
796
+ content = FileSystemService.clean_trailing_whitespace_and_newlines(content)
797
+ self._write_file_and_track(
798
+ target_file,
799
+ content,
800
+ ".pre-commit-config.yaml",
801
+ results,
802
+ )
803
+ return
804
+
805
+ # Load existing config
806
+ with target_file.open() as f:
807
+ target_config = yaml.safe_load(f)
808
+
809
+ # Ensure configs are dictionaries
810
+ if not isinstance(source_config, dict):
811
+ source_config = {}
812
+ if not isinstance(target_config, dict):
813
+ target_config = {}
814
+
815
+ # Merge hooks without duplication
816
+ source_repos = source_config.get("repos", [])
817
+ target_repos = target_config.get("repos", [])
818
+
819
+ # Track existing repo URLs
820
+ existing_repo_urls = {repo.get("repo", "") for repo in target_repos}
821
+
822
+ # Add new repos that don't already exist
823
+ new_repos = [
824
+ repo
825
+ for repo in source_repos
826
+ if repo.get("repo", "") not in existing_repo_urls
827
+ ]
828
+
829
+ if new_repos:
830
+ target_repos.extend(new_repos)
831
+ target_config["repos"] = target_repos
832
+
833
+ # Write merged config with proper formatting
834
+ yaml_content = yaml.dump(
835
+ target_config,
836
+ default_flow_style=False,
837
+ sort_keys=False,
838
+ width=float("inf"),
839
+ )
840
+ content = (
841
+ yaml_content.decode()
842
+ if isinstance(yaml_content, bytes)
843
+ else yaml_content
844
+ )
845
+
846
+ # Ensure content is not None before cleaning
847
+ if content is None:
848
+ content = ""
849
+
850
+ # Clean trailing whitespace and ensure single trailing newline
851
+ from crackerjack.services.filesystem import FileSystemService
852
+
853
+ content = FileSystemService.clean_trailing_whitespace_and_newlines(content)
854
+
855
+ with target_file.open("w") as f:
856
+ f.write(content)
857
+
858
+ t.cast("list[str]", results["files_copied"]).append(
859
+ ".pre-commit-config.yaml (merged)",
860
+ )
861
+
862
+ try:
863
+ self.git_service.add_files([str(target_file)])
864
+ except Exception as e:
865
+ self.console.print(
866
+ f"[yellow]⚠️[/yellow] Could not git add .pre-commit-config.yaml: {e}",
867
+ )
868
+
869
+ self.console.print(
870
+ f"[green]✅[/green] Merged .pre-commit-config.yaml ({len(new_repos)} new repos)",
871
+ )
872
+ else:
873
+ self._skip_existing_file(".pre-commit-config.yaml (no new repos)", results)