crackerjack 0.29.0__py3-none-any.whl → 0.31.4__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 (158) hide show
  1. crackerjack/CLAUDE.md +1005 -0
  2. crackerjack/RULES.md +380 -0
  3. crackerjack/__init__.py +42 -13
  4. crackerjack/__main__.py +225 -253
  5. crackerjack/agents/__init__.py +41 -0
  6. crackerjack/agents/architect_agent.py +281 -0
  7. crackerjack/agents/base.py +169 -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 +652 -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 +401 -0
  26. crackerjack/cli/utils.py +18 -0
  27. crackerjack/code_cleaner.py +670 -0
  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 +561 -0
  37. crackerjack/core/proactive_workflow.py +316 -0
  38. crackerjack/core/session_coordinator.py +289 -0
  39. crackerjack/core/workflow_orchestrator.py +640 -0
  40. crackerjack/dynamic_config.py +577 -0
  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 +411 -0
  58. crackerjack/managers/test_command_builder.py +151 -0
  59. crackerjack/managers/test_executor.py +435 -0
  60. crackerjack/managers/test_manager.py +258 -0
  61. crackerjack/managers/test_manager_backup.py +1124 -0
  62. crackerjack/managers/test_progress.py +144 -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 +615 -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 +370 -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 +141 -0
  88. crackerjack/mcp/tools/utility_tools.py +341 -0
  89. crackerjack/mcp/tools/workflow_executor.py +360 -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/execution_strategies.py +341 -0
  107. crackerjack/orchestration/test_progress_streamer.py +636 -0
  108. crackerjack/plugins/__init__.py +15 -0
  109. crackerjack/plugins/base.py +200 -0
  110. crackerjack/plugins/hooks.py +246 -0
  111. crackerjack/plugins/loader.py +335 -0
  112. crackerjack/plugins/managers.py +259 -0
  113. crackerjack/py313.py +8 -3
  114. crackerjack/services/__init__.py +22 -0
  115. crackerjack/services/cache.py +314 -0
  116. crackerjack/services/config.py +347 -0
  117. crackerjack/services/config_integrity.py +99 -0
  118. crackerjack/services/contextual_ai_assistant.py +516 -0
  119. crackerjack/services/coverage_ratchet.py +347 -0
  120. crackerjack/services/debug.py +736 -0
  121. crackerjack/services/dependency_monitor.py +617 -0
  122. crackerjack/services/enhanced_filesystem.py +439 -0
  123. crackerjack/services/file_hasher.py +151 -0
  124. crackerjack/services/filesystem.py +395 -0
  125. crackerjack/services/git.py +165 -0
  126. crackerjack/services/health_metrics.py +611 -0
  127. crackerjack/services/initialization.py +847 -0
  128. crackerjack/services/log_manager.py +286 -0
  129. crackerjack/services/logging.py +174 -0
  130. crackerjack/services/metrics.py +578 -0
  131. crackerjack/services/pattern_cache.py +362 -0
  132. crackerjack/services/pattern_detector.py +515 -0
  133. crackerjack/services/performance_benchmarks.py +653 -0
  134. crackerjack/services/security.py +163 -0
  135. crackerjack/services/server_manager.py +234 -0
  136. crackerjack/services/smart_scheduling.py +144 -0
  137. crackerjack/services/tool_version_service.py +61 -0
  138. crackerjack/services/unified_config.py +437 -0
  139. crackerjack/services/version_checker.py +248 -0
  140. crackerjack/slash_commands/__init__.py +14 -0
  141. crackerjack/slash_commands/init.md +122 -0
  142. crackerjack/slash_commands/run.md +163 -0
  143. crackerjack/slash_commands/status.md +127 -0
  144. crackerjack-0.31.4.dist-info/METADATA +742 -0
  145. crackerjack-0.31.4.dist-info/RECORD +148 -0
  146. crackerjack-0.31.4.dist-info/entry_points.txt +2 -0
  147. crackerjack/.gitignore +0 -34
  148. crackerjack/.libcst.codemod.yaml +0 -18
  149. crackerjack/.pdm.toml +0 -1
  150. crackerjack/.pre-commit-config-ai.yaml +0 -149
  151. crackerjack/.pre-commit-config-fast.yaml +0 -69
  152. crackerjack/.pre-commit-config.yaml +0 -114
  153. crackerjack/crackerjack.py +0 -4140
  154. crackerjack/pyproject.toml +0 -285
  155. crackerjack-0.29.0.dist-info/METADATA +0 -1289
  156. crackerjack-0.29.0.dist-info/RECORD +0 -17
  157. {crackerjack-0.29.0.dist-info → crackerjack-0.31.4.dist-info}/WHEEL +0 -0
  158. {crackerjack-0.29.0.dist-info → crackerjack-0.31.4.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,286 @@
1
+ import logging
2
+ import os
3
+ import shutil
4
+ import time
5
+ from contextlib import suppress
6
+ from pathlib import Path
7
+
8
+ from rich.console import Console
9
+
10
+ console = Console()
11
+
12
+
13
+ class LogManager:
14
+ def __init__(self, app_name: str = "crackerjack") -> None:
15
+ self.app_name = app_name
16
+ self._log_dir = self._get_log_directory()
17
+ self._setup_directories()
18
+
19
+ def _get_log_directory(self) -> Path:
20
+ cache_home = os.environ.get("XDG_CACHE_HOME")
21
+ base_dir = Path(cache_home) if cache_home else Path.home() / ".cache"
22
+
23
+ return base_dir / self.app_name / "logs"
24
+
25
+ def _setup_directories(self) -> None:
26
+ self._log_dir.mkdir(parents=True, exist_ok=True)
27
+
28
+ (self._log_dir / "debug").mkdir(exist_ok=True)
29
+ (self._log_dir / "error").mkdir(exist_ok=True)
30
+ (self._log_dir / "audit").mkdir(exist_ok=True)
31
+
32
+ @property
33
+ def log_dir(self) -> Path:
34
+ return self._log_dir
35
+
36
+ @property
37
+ def debug_dir(self) -> Path:
38
+ return self._log_dir / "debug"
39
+
40
+ @property
41
+ def error_dir(self) -> Path:
42
+ return self._log_dir / "error"
43
+
44
+ @property
45
+ def audit_dir(self) -> Path:
46
+ return self._log_dir / "audit"
47
+
48
+ def create_debug_log_file(self, session_id: str | None = None) -> Path:
49
+ timestamp = int(time.time())
50
+ if session_id:
51
+ filename = f"debug-{timestamp}-{session_id}.log"
52
+ else:
53
+ filename = f"debug-{timestamp}.log"
54
+
55
+ return self.debug_dir / filename
56
+
57
+ def create_error_log_file(self, error_type: str = "general") -> Path:
58
+ timestamp = int(time.time())
59
+ filename = f"error-{error_type}-{timestamp}.log"
60
+ return self.error_dir / filename
61
+
62
+ def rotate_logs(
63
+ self,
64
+ log_dir: Path,
65
+ pattern: str,
66
+ max_files: int = 10,
67
+ max_age_days: int = 30,
68
+ ) -> int:
69
+ if not log_dir.exists():
70
+ return 0
71
+
72
+ removed_count = 0
73
+ cutoff_time = time.time() - (max_age_days * 24 * 60 * 60)
74
+
75
+ log_files = sorted(
76
+ log_dir.glob(pattern),
77
+ key=lambda p: p.stat().st_mtime,
78
+ reverse=True,
79
+ )
80
+
81
+ for i, log_file in enumerate(log_files):
82
+ should_remove = False
83
+ reason = ""
84
+
85
+ if i >= max_files:
86
+ should_remove = True
87
+ reason = f"exceeds max files ({max_files})"
88
+
89
+ elif log_file.stat().st_mtime < cutoff_time:
90
+ should_remove = True
91
+ reason = f"older than {max_age_days} days"
92
+
93
+ if should_remove:
94
+ with suppress(OSError, PermissionError):
95
+ log_file.unlink()
96
+ removed_count += 1
97
+ console.print(
98
+ f"[dim]🗑️ Removed old log: {log_file.name} ({reason})[/dim]",
99
+ )
100
+
101
+ return removed_count
102
+
103
+ def cleanup_all_logs(
104
+ self,
105
+ debug_max_files: int = 10,
106
+ error_max_files: int = 20,
107
+ audit_max_files: int = 50,
108
+ max_age_days: int = 30,
109
+ ) -> dict[str, int]:
110
+ results = {}
111
+
112
+ results["debug"] = self.rotate_logs(
113
+ self.debug_dir,
114
+ "debug-*.log",
115
+ debug_max_files,
116
+ max_age_days,
117
+ )
118
+
119
+ results["error"] = self.rotate_logs(
120
+ self.error_dir,
121
+ "error-*.log",
122
+ error_max_files,
123
+ max_age_days,
124
+ )
125
+
126
+ results["audit"] = self.rotate_logs(
127
+ self.audit_dir,
128
+ "audit-*.log",
129
+ audit_max_files,
130
+ max_age_days,
131
+ )
132
+
133
+ return results
134
+
135
+ def migrate_legacy_logs(
136
+ self,
137
+ source_dir: Path,
138
+ dry_run: bool = False,
139
+ ) -> dict[str, int]:
140
+ if not source_dir.exists():
141
+ return {"moved": 0, "failed": 0, "found": 0}
142
+
143
+ debug_pattern = "crackerjack-debug-*.log"
144
+ legacy_files = list(source_dir.glob(debug_pattern))
145
+
146
+ results = {"found": len(legacy_files), "moved": 0, "failed": 0}
147
+
148
+ if not legacy_files:
149
+ console.print("[green]✅ No legacy log files found to migrate[/green]")
150
+ return results
151
+
152
+ console.print(
153
+ f"[yellow]📦 Found {len(legacy_files)} legacy debug log files to migrate[/yellow]",
154
+ )
155
+
156
+ for legacy_file in legacy_files:
157
+ try:
158
+ parts = legacy_file.stem.split(" - ")
159
+ if len(parts) >= 3 and parts[-1].isdigit():
160
+ timestamp = parts[-1]
161
+ else:
162
+ timestamp = str(int(legacy_file.stat().st_mtime))
163
+
164
+ new_filename = f"debug-{timestamp}-migrated.log"
165
+ new_path = self.debug_dir / new_filename
166
+
167
+ if dry_run:
168
+ console.print(
169
+ f"[cyan]📋 Would move: {legacy_file.name} → {new_filename}[/cyan]",
170
+ )
171
+ results["moved"] += 1
172
+ else:
173
+ shutil.move(str(legacy_file), str(new_path))
174
+ results["moved"] += 1
175
+ console.print(
176
+ f"[green]✅ Moved: {legacy_file.name} → {new_filename}[/green]",
177
+ )
178
+
179
+ except (OSError, ValueError) as e:
180
+ results["failed"] += 1
181
+ console.print(
182
+ f"[red]❌ Failed to migrate {legacy_file.name}: {e}[/red]",
183
+ )
184
+
185
+ return results
186
+
187
+ def get_log_stats(self) -> dict[str, dict[str, int | str]]:
188
+ stats = {}
189
+
190
+ for log_type, log_dir in (
191
+ ("debug", self.debug_dir),
192
+ ("error", self.error_dir),
193
+ ("audit", self.audit_dir),
194
+ ):
195
+ if log_dir.exists():
196
+ files = list(log_dir.glob("*.log"))
197
+ total_size = sum(f.stat().st_size for f in files if f.exists())
198
+
199
+ stats[log_type] = {
200
+ "count": len(files),
201
+ "size_mb": round(total_size / (1024 * 1024), 2),
202
+ "location": str(log_dir),
203
+ }
204
+ else:
205
+ stats[log_type] = {
206
+ "count": 0,
207
+ "size_mb": 0.0,
208
+ "location": str(log_dir),
209
+ }
210
+
211
+ return stats
212
+
213
+ def setup_rotating_file_handler(
214
+ self,
215
+ logger: logging.Logger,
216
+ log_type: str = "debug",
217
+ max_bytes: int = 10 * 1024 * 1024,
218
+ backup_count: int = 5,
219
+ ) -> logging.FileHandler:
220
+ from logging.handlers import RotatingFileHandler
221
+
222
+ if log_type == "error":
223
+ log_dir = self.error_dir
224
+ elif log_type == "audit":
225
+ log_dir = self.audit_dir
226
+ else:
227
+ log_dir = self.debug_dir
228
+
229
+ log_file = log_dir / f"{log_type}.log"
230
+
231
+ handler = RotatingFileHandler(
232
+ log_file,
233
+ maxBytes=max_bytes,
234
+ backupCount=backup_count,
235
+ encoding="utf-8",
236
+ )
237
+
238
+ formatter = logging.Formatter(
239
+ "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
240
+ )
241
+ handler.setFormatter(formatter)
242
+
243
+ logger.addHandler(handler)
244
+
245
+ return handler
246
+
247
+ def print_log_summary(self) -> None:
248
+ stats = self.get_log_stats()
249
+
250
+ console.print("\n[bold]📊 Log File Summary[/bold]")
251
+ console.print(f"[dim]Location: {self.log_dir}[/dim]")
252
+
253
+ total_files = 0
254
+ total_size = 0.0
255
+
256
+ for log_type, data in stats.items():
257
+ count = (
258
+ int(data["count"]) if isinstance(data["count"], str) else data["count"]
259
+ )
260
+ size = (
261
+ float(data["size_mb"])
262
+ if isinstance(data["size_mb"], str)
263
+ else data["size_mb"]
264
+ )
265
+
266
+ total_files += count
267
+ total_size += size
268
+
269
+ if count > 0:
270
+ console.print(f" {log_type.capitalize()}: {count} files, {size}MB")
271
+ else:
272
+ console.print(f" {log_type.capitalize()}: No files")
273
+
274
+ if total_files > 0:
275
+ console.print(
276
+ f"\n[bold]Total: {total_files} files, {total_size:.2f}MB[/bold]",
277
+ )
278
+ else:
279
+ console.print("\n[dim]No log files found[/dim]")
280
+
281
+
282
+ log_manager = LogManager()
283
+
284
+
285
+ def get_log_manager() -> LogManager:
286
+ return log_manager
@@ -0,0 +1,174 @@
1
+ import logging
2
+ import sys
3
+ import time
4
+ import typing as t
5
+ import uuid
6
+ from contextvars import ContextVar
7
+ from pathlib import Path
8
+ from types import TracebackType
9
+ from typing import Any
10
+
11
+ import structlog
12
+
13
+ correlation_id: ContextVar[str | None] = ContextVar("correlation_id", default=None)
14
+
15
+
16
+ def set_correlation_id(cid: str) -> None:
17
+ correlation_id.set(cid)
18
+
19
+
20
+ def get_correlation_id() -> str:
21
+ cid = correlation_id.get()
22
+ if cid is None:
23
+ cid = str(uuid.uuid4())[:8]
24
+ correlation_id.set(cid)
25
+ return cid
26
+
27
+
28
+ def add_correlation_id(_: Any, __: Any, event_dict: dict[str, Any]) -> dict[str, Any]:
29
+ event_dict["correlation_id"] = get_correlation_id()
30
+ return event_dict
31
+
32
+
33
+ def add_timestamp(_: Any, __: Any, event_dict: dict[str, Any]) -> dict[str, Any]:
34
+ event_dict["timestamp"] = time.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
35
+ return event_dict
36
+
37
+
38
+ def setup_structured_logging(
39
+ level: str = "INFO",
40
+ json_output: bool = True,
41
+ log_file: Path | None = None,
42
+ ) -> None:
43
+ processors = [
44
+ structlog.stdlib.filter_by_level,
45
+ add_timestamp,
46
+ add_correlation_id,
47
+ structlog.stdlib.add_logger_name,
48
+ structlog.stdlib.add_log_level,
49
+ structlog.stdlib.PositionalArgumentsFormatter(),
50
+ structlog.processors.StackInfoRenderer(),
51
+ structlog.processors.format_exc_info,
52
+ ]
53
+
54
+ if json_output:
55
+ processors.append(structlog.processors.JSONRenderer()) # type: ignore[arg-type]
56
+ else:
57
+ processors.append(structlog.dev.ConsoleRenderer(colors=True)) # type: ignore[arg-type]
58
+
59
+ structlog.configure(
60
+ processors=processors, # type: ignore[arg-type]
61
+ wrapper_class=structlog.stdlib.BoundLogger,
62
+ logger_factory=structlog.stdlib.LoggerFactory(),
63
+ cache_logger_on_first_use=True,
64
+ )
65
+
66
+ log_level = getattr(logging, level.upper())
67
+
68
+ console_handler = logging.StreamHandler(sys.stdout)
69
+ console_handler.setLevel(log_level)
70
+
71
+ handlers = [console_handler]
72
+
73
+ if log_file:
74
+ log_file.parent.mkdir(parents=True, exist_ok=True)
75
+ file_handler = logging.FileHandler(log_file)
76
+ file_handler.setLevel(log_level)
77
+ handlers.append(file_handler)
78
+
79
+ logging.basicConfig(
80
+ level=log_level,
81
+ handlers=handlers,
82
+ format="%(message)s",
83
+ )
84
+
85
+
86
+ def get_logger(name: str) -> structlog.BoundLogger:
87
+ return structlog.get_logger(name)
88
+
89
+
90
+ class LoggingContext:
91
+ def __init__(self, operation: str, **kwargs: Any) -> None:
92
+ self.operation = operation
93
+ self.kwargs = kwargs
94
+ self.correlation_id = str(uuid.uuid4())[:8]
95
+ self.logger = get_logger("crackerjack.context")
96
+ self.start_time = time.time()
97
+
98
+ def __enter__(self) -> str:
99
+ set_correlation_id(self.correlation_id)
100
+ self.logger.info("Operation started", operation=self.operation, **self.kwargs)
101
+ return self.correlation_id
102
+
103
+ def __exit__(
104
+ self,
105
+ exc_type: type[BaseException] | None,
106
+ exc_val: BaseException | None,
107
+ _: TracebackType | None,
108
+ ) -> None:
109
+ duration = time.time() - self.start_time
110
+
111
+ if exc_type is None:
112
+ self.logger.info(
113
+ "Operation completed",
114
+ operation=self.operation,
115
+ duration_seconds=round(duration, 3),
116
+ **self.kwargs,
117
+ )
118
+ else:
119
+ self.logger.error(
120
+ "Operation failed",
121
+ operation=self.operation,
122
+ duration_seconds=round(duration, 3),
123
+ error=str(exc_val),
124
+ error_type=exc_type.__name__ if exc_type else None,
125
+ **self.kwargs,
126
+ )
127
+
128
+
129
+ def log_performance(
130
+ operation: str,
131
+ **kwargs: Any,
132
+ ) -> t.Callable[[t.Callable[..., t.Any]], t.Callable[..., t.Any]]:
133
+ def decorator(func: t.Callable[..., t.Any]) -> t.Callable[..., t.Any]:
134
+ def wrapper(*args: t.Any, **func_kwargs: t.Any) -> t.Any:
135
+ logger = get_logger(f"crackerjack.perf.{func.__name__}")
136
+ start_time = time.time()
137
+
138
+ try:
139
+ result = func(*args, **func_kwargs)
140
+ duration = time.time() - start_time
141
+ logger.info(
142
+ "Function completed",
143
+ operation=operation,
144
+ function=func.__name__,
145
+ duration_seconds=round(duration, 3),
146
+ success=True,
147
+ **kwargs,
148
+ )
149
+ return result
150
+ except Exception as e:
151
+ duration = time.time() - start_time
152
+ logger.exception(
153
+ "Function failed",
154
+ operation=operation,
155
+ function=func.__name__,
156
+ duration_seconds=round(duration, 3),
157
+ success=False,
158
+ error=str(e),
159
+ error_type=type(e).__name__,
160
+ **kwargs,
161
+ )
162
+ raise
163
+
164
+ return wrapper
165
+
166
+ return decorator
167
+
168
+
169
+ hook_logger = get_logger("crackerjack.hooks")
170
+ test_logger = get_logger("crackerjack.tests")
171
+ config_logger = get_logger("crackerjack.config")
172
+ cache_logger = get_logger("crackerjack.cache")
173
+ security_logger = get_logger("crackerjack.security")
174
+ performance_logger = get_logger("crackerjack.performance")