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