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
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
"""Intelligent commit message generation service."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import typing as t
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
|
|
9
|
+
from crackerjack.models.protocols import GitInterface
|
|
10
|
+
|
|
11
|
+
from .regex_patterns import CompiledPatternCache
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CommitMessageGenerator:
|
|
15
|
+
"""Generate intelligent commit messages based on changes and context."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, console: Console, git_service: GitInterface) -> None:
|
|
18
|
+
"""Initialize commit message generator."""
|
|
19
|
+
self.console = console
|
|
20
|
+
self.git = git_service
|
|
21
|
+
|
|
22
|
+
# Common change type patterns
|
|
23
|
+
self.patterns = {
|
|
24
|
+
"feat": [
|
|
25
|
+
r"add|create|implement|introduce",
|
|
26
|
+
r"new.*feature",
|
|
27
|
+
r"support.*for",
|
|
28
|
+
],
|
|
29
|
+
"fix": [
|
|
30
|
+
r"fix|resolve|correct|repair",
|
|
31
|
+
r"bug|issue|problem",
|
|
32
|
+
r"error|exception|failure",
|
|
33
|
+
],
|
|
34
|
+
"refactor": [
|
|
35
|
+
r"refactor|restructure|reorganize",
|
|
36
|
+
r"extract|move|rename",
|
|
37
|
+
r"improve.*structure",
|
|
38
|
+
],
|
|
39
|
+
"test": [
|
|
40
|
+
r"test|spec|fixture",
|
|
41
|
+
r"coverage|assertion",
|
|
42
|
+
r"mock|stub",
|
|
43
|
+
],
|
|
44
|
+
"docs": [
|
|
45
|
+
r"readme|documentation|doc",
|
|
46
|
+
r"comment|docstring",
|
|
47
|
+
r"\.md$|\.rst$|\.txt$",
|
|
48
|
+
],
|
|
49
|
+
"style": [
|
|
50
|
+
r"format|style|lint",
|
|
51
|
+
r"whitespace|indentation",
|
|
52
|
+
r"prettier|black|ruff",
|
|
53
|
+
],
|
|
54
|
+
"chore": [
|
|
55
|
+
r"config|setup|build",
|
|
56
|
+
r"dependency|requirement",
|
|
57
|
+
r"version|release",
|
|
58
|
+
],
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
def generate_commit_message(
|
|
62
|
+
self,
|
|
63
|
+
include_body: bool = True,
|
|
64
|
+
conventional_commits: bool = True,
|
|
65
|
+
) -> str:
|
|
66
|
+
"""Generate an intelligent commit message based on staged changes."""
|
|
67
|
+
try:
|
|
68
|
+
# Get changed files and their changes
|
|
69
|
+
staged_files = self.git.get_staged_files()
|
|
70
|
+
if not staged_files:
|
|
71
|
+
return "chore: no changes to commit"
|
|
72
|
+
|
|
73
|
+
# Analyze file changes
|
|
74
|
+
change_analysis = self._analyze_changes(staged_files)
|
|
75
|
+
|
|
76
|
+
# Generate message components
|
|
77
|
+
commit_type = self._determine_commit_type(change_analysis)
|
|
78
|
+
scope = self._determine_scope(change_analysis)
|
|
79
|
+
subject = self._generate_subject(change_analysis)
|
|
80
|
+
|
|
81
|
+
# Build commit message
|
|
82
|
+
if conventional_commits:
|
|
83
|
+
header = self._build_conventional_header(commit_type, scope, subject)
|
|
84
|
+
else:
|
|
85
|
+
header = subject
|
|
86
|
+
|
|
87
|
+
if not include_body:
|
|
88
|
+
return header
|
|
89
|
+
|
|
90
|
+
# Add body with details
|
|
91
|
+
body = self._generate_body(change_analysis)
|
|
92
|
+
|
|
93
|
+
if body:
|
|
94
|
+
return f"{header}\n\n{body}"
|
|
95
|
+
|
|
96
|
+
return header
|
|
97
|
+
|
|
98
|
+
except Exception as e:
|
|
99
|
+
self.console.print(
|
|
100
|
+
f"[yellow]⚠️[/yellow] Error generating commit message: {e}"
|
|
101
|
+
)
|
|
102
|
+
return "chore: update files"
|
|
103
|
+
|
|
104
|
+
def _analyze_changes(self, staged_files: list[str]) -> dict[str, t.Any]:
|
|
105
|
+
"""Analyze staged files to understand the nature of changes."""
|
|
106
|
+
analysis: dict[str, t.Any] = {
|
|
107
|
+
"files": staged_files,
|
|
108
|
+
"file_types": set(),
|
|
109
|
+
"directories": set(),
|
|
110
|
+
"total_files": len(staged_files),
|
|
111
|
+
"patterns_found": set(),
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
for file_path in staged_files:
|
|
115
|
+
path = Path(file_path)
|
|
116
|
+
|
|
117
|
+
# Track file types
|
|
118
|
+
if path.suffix:
|
|
119
|
+
analysis["file_types"].add(path.suffix)
|
|
120
|
+
|
|
121
|
+
# Track directories
|
|
122
|
+
if path.parent != Path():
|
|
123
|
+
analysis["directories"].add(str(path.parent))
|
|
124
|
+
|
|
125
|
+
# Check for patterns in file names
|
|
126
|
+
file_str = str(path).lower()
|
|
127
|
+
for commit_type, patterns in self.patterns.items():
|
|
128
|
+
for pattern in patterns:
|
|
129
|
+
# Use safe compiled pattern cache instead of raw re.search
|
|
130
|
+
compiled_pattern = (
|
|
131
|
+
CompiledPatternCache.get_compiled_pattern_with_flags(
|
|
132
|
+
f"commit_{commit_type}_{pattern}", pattern, re.IGNORECASE
|
|
133
|
+
)
|
|
134
|
+
)
|
|
135
|
+
if compiled_pattern.search(file_str):
|
|
136
|
+
analysis["patterns_found"].add(commit_type)
|
|
137
|
+
|
|
138
|
+
return analysis
|
|
139
|
+
|
|
140
|
+
def _determine_commit_type(self, analysis: dict[str, t.Any]) -> str:
|
|
141
|
+
"""Determine the most appropriate commit type."""
|
|
142
|
+
patterns_found = analysis["patterns_found"]
|
|
143
|
+
files = analysis["files"]
|
|
144
|
+
file_types = analysis["file_types"]
|
|
145
|
+
|
|
146
|
+
# Check commit types in priority order
|
|
147
|
+
commit_type_checks = self._get_commit_type_checks()
|
|
148
|
+
|
|
149
|
+
for commit_type, check_func in commit_type_checks:
|
|
150
|
+
if check_func(patterns_found, files, file_types):
|
|
151
|
+
return commit_type
|
|
152
|
+
|
|
153
|
+
# Default to chore for misc changes
|
|
154
|
+
return "chore"
|
|
155
|
+
|
|
156
|
+
def _get_commit_type_checks(self) -> list[tuple[str, t.Callable[..., t.Any]]]:
|
|
157
|
+
"""Get ordered list[t.Any] of commit type checks."""
|
|
158
|
+
return [
|
|
159
|
+
("fix", self._is_fix_commit),
|
|
160
|
+
("feat", self._is_feat_commit),
|
|
161
|
+
("test", self._is_test_commit),
|
|
162
|
+
("docs", self._is_docs_commit),
|
|
163
|
+
("style", self._is_style_commit),
|
|
164
|
+
("refactor", self._is_refactor_commit),
|
|
165
|
+
]
|
|
166
|
+
|
|
167
|
+
def _is_fix_commit(
|
|
168
|
+
self, patterns: set[t.Any], files: list[t.Any], file_types: set[t.Any]
|
|
169
|
+
) -> bool:
|
|
170
|
+
"""Check if this is a fix commit."""
|
|
171
|
+
return "fix" in patterns
|
|
172
|
+
|
|
173
|
+
def _is_feat_commit(
|
|
174
|
+
self, patterns: set[t.Any], files: list[t.Any], file_types: set[t.Any]
|
|
175
|
+
) -> bool:
|
|
176
|
+
"""Check if this is a feature commit."""
|
|
177
|
+
return "feat" in patterns
|
|
178
|
+
|
|
179
|
+
def _is_test_commit(
|
|
180
|
+
self, patterns: set[t.Any], files: list[t.Any], file_types: set[t.Any]
|
|
181
|
+
) -> bool:
|
|
182
|
+
"""Check if this is a test commit."""
|
|
183
|
+
return "test" in patterns or any(
|
|
184
|
+
".py" in f and "test" in f.lower() for f in files
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
def _is_docs_commit(
|
|
188
|
+
self, patterns: set[t.Any], files: list[t.Any], file_types: set[t.Any]
|
|
189
|
+
) -> bool:
|
|
190
|
+
"""Check if this is a documentation commit."""
|
|
191
|
+
return "docs" in patterns or any(
|
|
192
|
+
ext in file_types for ext in (".md", ".rst", ".txt")
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
def _is_style_commit(
|
|
196
|
+
self, patterns: set[t.Any], files: list[t.Any], file_types: set[t.Any]
|
|
197
|
+
) -> bool:
|
|
198
|
+
"""Check if this is a style commit."""
|
|
199
|
+
return (
|
|
200
|
+
"style" in patterns or len(files) > 5
|
|
201
|
+
) # Multiple files suggest style changes
|
|
202
|
+
|
|
203
|
+
def _is_refactor_commit(
|
|
204
|
+
self, patterns: set[t.Any], files: list[t.Any], file_types: set[t.Any]
|
|
205
|
+
) -> bool:
|
|
206
|
+
"""Check if this is a refactor commit."""
|
|
207
|
+
return "refactor" in patterns
|
|
208
|
+
|
|
209
|
+
def _determine_scope(self, analysis: dict[str, t.Any]) -> str | None:
|
|
210
|
+
"""Determine an appropriate scope for the commit."""
|
|
211
|
+
directories = analysis["directories"]
|
|
212
|
+
files = analysis["files"]
|
|
213
|
+
|
|
214
|
+
if len(directories) == 1:
|
|
215
|
+
# Single directory - use as scope
|
|
216
|
+
directory = list[str](directories)[0]
|
|
217
|
+
# Simplify common directory patterns
|
|
218
|
+
if "/" in directory:
|
|
219
|
+
return directory.split("/")[0] # Use top-level directory
|
|
220
|
+
return directory
|
|
221
|
+
|
|
222
|
+
# Check for common patterns
|
|
223
|
+
if any("test" in f.lower() for f in files):
|
|
224
|
+
return "test"
|
|
225
|
+
if any("doc" in f.lower() for f in files):
|
|
226
|
+
return "docs"
|
|
227
|
+
if any(f.endswith(".py") for f in files):
|
|
228
|
+
return "core"
|
|
229
|
+
if any(f.endswith((".yml", ".yaml", ".toml", ".json")) for f in files):
|
|
230
|
+
return "config"
|
|
231
|
+
|
|
232
|
+
# No clear scope
|
|
233
|
+
return None
|
|
234
|
+
|
|
235
|
+
def _generate_subject(self, analysis: dict[str, t.Any]) -> str:
|
|
236
|
+
"""Generate a descriptive subject line."""
|
|
237
|
+
files = analysis["files"]
|
|
238
|
+
file_types = analysis["file_types"]
|
|
239
|
+
total_files = analysis["total_files"]
|
|
240
|
+
|
|
241
|
+
# Handle single file changes
|
|
242
|
+
if total_files == 1:
|
|
243
|
+
file_path = Path(files[0])
|
|
244
|
+
file_name = file_path.stem
|
|
245
|
+
|
|
246
|
+
# Generate descriptive action based on file name
|
|
247
|
+
if "test" in file_name.lower():
|
|
248
|
+
return f"update {file_name} test"
|
|
249
|
+
elif file_path.suffix in (".md", ".rst", ".txt"):
|
|
250
|
+
return f"update {file_name} documentation"
|
|
251
|
+
elif "config" in file_name.lower():
|
|
252
|
+
return f"update {file_name} configuration"
|
|
253
|
+
else:
|
|
254
|
+
return f"update {file_name}"
|
|
255
|
+
|
|
256
|
+
# Handle multiple files
|
|
257
|
+
if total_files <= 3:
|
|
258
|
+
# List specific files
|
|
259
|
+
file_names = [Path(f).stem for f in files]
|
|
260
|
+
return f"update {', '.join(file_names)}"
|
|
261
|
+
|
|
262
|
+
# Handle bulk changes
|
|
263
|
+
if len(file_types) == 1:
|
|
264
|
+
file_type = list[t.Any](file_types)[0]
|
|
265
|
+
return f"update {total_files} {file_type} files"
|
|
266
|
+
|
|
267
|
+
return f"update {total_files} files"
|
|
268
|
+
|
|
269
|
+
def _build_conventional_header(
|
|
270
|
+
self, commit_type: str, scope: str | None, subject: str
|
|
271
|
+
) -> str:
|
|
272
|
+
"""Build conventional commit header format."""
|
|
273
|
+
if scope:
|
|
274
|
+
return f"{commit_type}({scope}): {subject}"
|
|
275
|
+
return f"{commit_type}: {subject}"
|
|
276
|
+
|
|
277
|
+
def _generate_body(self, analysis: dict[str, t.Any]) -> str:
|
|
278
|
+
"""Generate detailed commit body."""
|
|
279
|
+
files = analysis["files"]
|
|
280
|
+
total_files = analysis["total_files"]
|
|
281
|
+
|
|
282
|
+
if total_files <= 3:
|
|
283
|
+
# List specific files for small changes
|
|
284
|
+
body_lines = ["Modified files:"]
|
|
285
|
+
for file_path in sorted(files):
|
|
286
|
+
body_lines.append(f"- {file_path}")
|
|
287
|
+
return "\n".join(body_lines)
|
|
288
|
+
|
|
289
|
+
# Summarize for larger changes
|
|
290
|
+
directories = analysis["directories"]
|
|
291
|
+
file_types = analysis["file_types"]
|
|
292
|
+
|
|
293
|
+
body_lines = [f"Updated {total_files} files across:"]
|
|
294
|
+
|
|
295
|
+
if directories:
|
|
296
|
+
body_lines.append("Directories:")
|
|
297
|
+
for directory in sorted(directories):
|
|
298
|
+
body_lines.append(f"- {directory}")
|
|
299
|
+
|
|
300
|
+
if file_types:
|
|
301
|
+
body_lines.append("File types:")
|
|
302
|
+
for file_type in sorted(file_types):
|
|
303
|
+
body_lines.append(f"- {file_type}")
|
|
304
|
+
|
|
305
|
+
return "\n".join(body_lines)
|
|
306
|
+
|
|
307
|
+
def commit_with_generated_message(
|
|
308
|
+
self,
|
|
309
|
+
include_body: bool = True,
|
|
310
|
+
conventional_commits: bool = True,
|
|
311
|
+
dry_run: bool = False,
|
|
312
|
+
) -> bool:
|
|
313
|
+
"""Generate commit message and create commit."""
|
|
314
|
+
message = self.generate_commit_message(
|
|
315
|
+
include_body=include_body,
|
|
316
|
+
conventional_commits=conventional_commits,
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
if dry_run:
|
|
320
|
+
self.console.print("[cyan]📝[/cyan] Generated commit message:")
|
|
321
|
+
self.console.print(f"[dim]{message}[/dim]")
|
|
322
|
+
return True
|
|
323
|
+
|
|
324
|
+
self.console.print(
|
|
325
|
+
f"[cyan]📝[/cyan] Committing with message: {message.split(chr(10))[0]}"
|
|
326
|
+
)
|
|
327
|
+
return self.git.commit(message)
|
|
@@ -2,6 +2,7 @@ import logging
|
|
|
2
2
|
import os
|
|
3
3
|
import shutil
|
|
4
4
|
import time
|
|
5
|
+
import typing as t
|
|
5
6
|
from contextlib import suppress
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
|
|
@@ -141,7 +142,7 @@ class LogManager:
|
|
|
141
142
|
return {"moved": 0, "failed": 0, "found": 0}
|
|
142
143
|
|
|
143
144
|
debug_pattern = "crackerjack-debug-*.log"
|
|
144
|
-
legacy_files = list(source_dir.glob(debug_pattern))
|
|
145
|
+
legacy_files = list[t.Any](source_dir.glob(debug_pattern))
|
|
145
146
|
|
|
146
147
|
results = {"found": len(legacy_files), "moved": 0, "failed": 0}
|
|
147
148
|
|
|
@@ -184,8 +185,8 @@ class LogManager:
|
|
|
184
185
|
|
|
185
186
|
return results
|
|
186
187
|
|
|
187
|
-
def get_log_stats(self) -> dict[str, dict[str, int | str]]:
|
|
188
|
-
stats = {}
|
|
188
|
+
def get_log_stats(self) -> dict[str, dict[str, int | str | float]]:
|
|
189
|
+
stats: dict[str, dict[str, int | str | float]] = {}
|
|
189
190
|
|
|
190
191
|
for log_type, log_dir in (
|
|
191
192
|
("debug", self.debug_dir),
|
|
@@ -193,7 +194,7 @@ class LogManager:
|
|
|
193
194
|
("audit", self.audit_dir),
|
|
194
195
|
):
|
|
195
196
|
if log_dir.exists():
|
|
196
|
-
files = list(log_dir.glob("*.log"))
|
|
197
|
+
files = list[t.Any](log_dir.glob("*.log"))
|
|
197
198
|
total_size = sum(f.stat().st_size for f in files if f.exists())
|
|
198
199
|
|
|
199
200
|
stats[log_type] = {
|
|
@@ -250,17 +251,19 @@ class LogManager:
|
|
|
250
251
|
console.print("\n[bold]📊 Log File Summary[/ bold]")
|
|
251
252
|
console.print(f"[dim]Location: {self.log_dir}[/ dim]")
|
|
252
253
|
|
|
253
|
-
total_files = 0
|
|
254
|
-
total_size = 0.0
|
|
254
|
+
total_files: int = 0
|
|
255
|
+
total_size: float = 0.0
|
|
255
256
|
|
|
256
257
|
for log_type, data in stats.items():
|
|
257
|
-
|
|
258
|
-
|
|
258
|
+
count_raw = data["count"]
|
|
259
|
+
count: int = (
|
|
260
|
+
int(count_raw) if isinstance(count_raw, str) else t.cast(int, count_raw)
|
|
259
261
|
)
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
262
|
+
size_raw = data["size_mb"]
|
|
263
|
+
size: float = (
|
|
264
|
+
float(size_raw)
|
|
265
|
+
if isinstance(size_raw, str)
|
|
266
|
+
else t.cast(float, size_raw)
|
|
264
267
|
)
|
|
265
268
|
|
|
266
269
|
total_files += count
|
crackerjack/services/logging.py
CHANGED
|
@@ -32,7 +32,7 @@ def add_correlation_id(_: Any, __: Any, event_dict: EventDict) -> EventDict:
|
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
def add_timestamp(_: Any, __: Any, event_dict: EventDict) -> EventDict:
|
|
35
|
-
event_dict["timestamp"] = time.strftime("%Y-%m-%dT%H
|
|
35
|
+
event_dict["timestamp"] = time.strftime("%Y-%m-%dT%H: %M: %S.%f")[:-3] + "Z"
|
|
36
36
|
return event_dict
|
|
37
37
|
|
|
38
38
|
|
|
@@ -69,7 +69,7 @@ def setup_structured_logging(
|
|
|
69
69
|
console_handler = logging.StreamHandler(sys.stdout)
|
|
70
70
|
console_handler.setLevel(log_level)
|
|
71
71
|
|
|
72
|
-
handlers = [console_handler]
|
|
72
|
+
handlers: list[logging.Handler] = [console_handler]
|
|
73
73
|
|
|
74
74
|
if log_file:
|
|
75
75
|
log_file.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -85,7 +85,8 @@ def setup_structured_logging(
|
|
|
85
85
|
|
|
86
86
|
|
|
87
87
|
def get_logger(name: str) -> structlog.BoundLogger:
|
|
88
|
-
|
|
88
|
+
logger: structlog.BoundLogger = structlog.get_logger(name)
|
|
89
|
+
return logger
|
|
89
90
|
|
|
90
91
|
|
|
91
92
|
class LoggingContext:
|