crackerjack 0.33.0__py3-none-any.whl → 0.33.2__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 +4 -13
- 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 +104 -204
- 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 +171 -174
- 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 +44 -8
- crackerjack/managers/test_command_builder.py +1 -15
- crackerjack/managers/test_executor.py +1 -3
- crackerjack/managers/test_manager.py +98 -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 +17 -16
- 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 +173 -32
- crackerjack/mcp/tools/error_analyzer.py +3 -2
- crackerjack/mcp/tools/execution_tools.py +8 -10
- 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 +0 -2
- crackerjack/mixins/error_handling.py +1 -70
- crackerjack/models/config.py +12 -1
- crackerjack/models/config_adapter.py +49 -1
- crackerjack/models/protocols.py +122 -122
- crackerjack/models/resource_protocols.py +55 -210
- 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 +6 -35
- 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 +15 -9
- crackerjack/services/config_merge.py +19 -80
- 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 +27 -25
- 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 +8 -25
- crackerjack/services/health_metrics.py +10 -8
- crackerjack/services/heatmap_generator.py +735 -0
- crackerjack/services/initialization.py +11 -30
- 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 +19 -87
- crackerjack/services/metrics.py +42 -33
- crackerjack/services/parallel_executor.py +9 -67
- crackerjack/services/pattern_cache.py +1 -1
- crackerjack/services/pattern_detector.py +6 -6
- crackerjack/services/performance_benchmarks.py +18 -59
- crackerjack/services/performance_cache.py +20 -81
- crackerjack/services/performance_monitor.py +27 -95
- 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 +618 -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 +9 -41
- crackerjack/services/security_logger.py +12 -24
- crackerjack/services/server_manager.py +124 -16
- crackerjack/services/status_authentication.py +16 -159
- crackerjack/services/status_security_manager.py +4 -131
- 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.33.0.dist-info → crackerjack-0.33.2.dist-info}/METADATA +196 -25
- crackerjack-0.33.2.dist-info/RECORD +229 -0
- crackerjack/CLAUDE.md +0 -207
- crackerjack/RULES.md +0 -380
- crackerjack/py313.py +0 -234
- crackerjack-0.33.0.dist-info/RECORD +0 -187
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/WHEEL +0 -0
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
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())
|
|
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
|
|
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
|
|