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.

Files changed (200) hide show
  1. crackerjack/__main__.py +1350 -34
  2. crackerjack/adapters/__init__.py +17 -0
  3. crackerjack/adapters/lsp_client.py +358 -0
  4. crackerjack/adapters/rust_tool_adapter.py +194 -0
  5. crackerjack/adapters/rust_tool_manager.py +193 -0
  6. crackerjack/adapters/skylos_adapter.py +231 -0
  7. crackerjack/adapters/zuban_adapter.py +560 -0
  8. crackerjack/agents/base.py +7 -3
  9. crackerjack/agents/coordinator.py +271 -33
  10. crackerjack/agents/documentation_agent.py +9 -15
  11. crackerjack/agents/dry_agent.py +3 -15
  12. crackerjack/agents/formatting_agent.py +1 -1
  13. crackerjack/agents/import_optimization_agent.py +36 -180
  14. crackerjack/agents/performance_agent.py +17 -98
  15. crackerjack/agents/performance_helpers.py +7 -31
  16. crackerjack/agents/proactive_agent.py +1 -3
  17. crackerjack/agents/refactoring_agent.py +16 -85
  18. crackerjack/agents/refactoring_helpers.py +7 -42
  19. crackerjack/agents/security_agent.py +9 -48
  20. crackerjack/agents/test_creation_agent.py +356 -513
  21. crackerjack/agents/test_specialist_agent.py +0 -4
  22. crackerjack/api.py +6 -25
  23. crackerjack/cli/cache_handlers.py +204 -0
  24. crackerjack/cli/cache_handlers_enhanced.py +683 -0
  25. crackerjack/cli/facade.py +100 -0
  26. crackerjack/cli/handlers.py +224 -9
  27. crackerjack/cli/interactive.py +6 -4
  28. crackerjack/cli/options.py +642 -55
  29. crackerjack/cli/utils.py +2 -1
  30. crackerjack/code_cleaner.py +58 -117
  31. crackerjack/config/global_lock_config.py +8 -48
  32. crackerjack/config/hooks.py +53 -62
  33. crackerjack/core/async_workflow_orchestrator.py +24 -34
  34. crackerjack/core/autofix_coordinator.py +3 -17
  35. crackerjack/core/enhanced_container.py +64 -6
  36. crackerjack/core/file_lifecycle.py +12 -89
  37. crackerjack/core/performance.py +2 -2
  38. crackerjack/core/performance_monitor.py +15 -55
  39. crackerjack/core/phase_coordinator.py +257 -218
  40. crackerjack/core/resource_manager.py +14 -90
  41. crackerjack/core/service_watchdog.py +62 -95
  42. crackerjack/core/session_coordinator.py +149 -0
  43. crackerjack/core/timeout_manager.py +14 -72
  44. crackerjack/core/websocket_lifecycle.py +13 -78
  45. crackerjack/core/workflow_orchestrator.py +558 -240
  46. crackerjack/docs/INDEX.md +11 -0
  47. crackerjack/docs/generated/api/API_REFERENCE.md +10895 -0
  48. crackerjack/docs/generated/api/CLI_REFERENCE.md +109 -0
  49. crackerjack/docs/generated/api/CROSS_REFERENCES.md +1755 -0
  50. crackerjack/docs/generated/api/PROTOCOLS.md +3 -0
  51. crackerjack/docs/generated/api/SERVICES.md +1252 -0
  52. crackerjack/documentation/__init__.py +31 -0
  53. crackerjack/documentation/ai_templates.py +756 -0
  54. crackerjack/documentation/dual_output_generator.py +765 -0
  55. crackerjack/documentation/mkdocs_integration.py +518 -0
  56. crackerjack/documentation/reference_generator.py +977 -0
  57. crackerjack/dynamic_config.py +55 -50
  58. crackerjack/executors/async_hook_executor.py +10 -15
  59. crackerjack/executors/cached_hook_executor.py +117 -43
  60. crackerjack/executors/hook_executor.py +8 -34
  61. crackerjack/executors/hook_lock_manager.py +26 -183
  62. crackerjack/executors/individual_hook_executor.py +13 -11
  63. crackerjack/executors/lsp_aware_hook_executor.py +270 -0
  64. crackerjack/executors/tool_proxy.py +417 -0
  65. crackerjack/hooks/lsp_hook.py +79 -0
  66. crackerjack/intelligence/adaptive_learning.py +25 -10
  67. crackerjack/intelligence/agent_orchestrator.py +2 -5
  68. crackerjack/intelligence/agent_registry.py +34 -24
  69. crackerjack/intelligence/agent_selector.py +5 -7
  70. crackerjack/interactive.py +17 -6
  71. crackerjack/managers/async_hook_manager.py +0 -1
  72. crackerjack/managers/hook_manager.py +79 -1
  73. crackerjack/managers/publish_manager.py +66 -13
  74. crackerjack/managers/test_command_builder.py +5 -17
  75. crackerjack/managers/test_executor.py +1 -3
  76. crackerjack/managers/test_manager.py +109 -7
  77. crackerjack/managers/test_manager_backup.py +10 -9
  78. crackerjack/mcp/cache.py +2 -2
  79. crackerjack/mcp/client_runner.py +1 -1
  80. crackerjack/mcp/context.py +191 -68
  81. crackerjack/mcp/dashboard.py +7 -5
  82. crackerjack/mcp/enhanced_progress_monitor.py +31 -28
  83. crackerjack/mcp/file_monitor.py +30 -23
  84. crackerjack/mcp/progress_components.py +31 -21
  85. crackerjack/mcp/progress_monitor.py +50 -53
  86. crackerjack/mcp/rate_limiter.py +6 -6
  87. crackerjack/mcp/server_core.py +161 -32
  88. crackerjack/mcp/service_watchdog.py +2 -1
  89. crackerjack/mcp/state.py +4 -7
  90. crackerjack/mcp/task_manager.py +11 -9
  91. crackerjack/mcp/tools/core_tools.py +174 -33
  92. crackerjack/mcp/tools/error_analyzer.py +3 -2
  93. crackerjack/mcp/tools/execution_tools.py +15 -12
  94. crackerjack/mcp/tools/execution_tools_backup.py +42 -30
  95. crackerjack/mcp/tools/intelligence_tool_registry.py +7 -5
  96. crackerjack/mcp/tools/intelligence_tools.py +5 -2
  97. crackerjack/mcp/tools/monitoring_tools.py +33 -70
  98. crackerjack/mcp/tools/proactive_tools.py +24 -11
  99. crackerjack/mcp/tools/progress_tools.py +5 -8
  100. crackerjack/mcp/tools/utility_tools.py +20 -14
  101. crackerjack/mcp/tools/workflow_executor.py +62 -40
  102. crackerjack/mcp/websocket/app.py +8 -0
  103. crackerjack/mcp/websocket/endpoints.py +352 -357
  104. crackerjack/mcp/websocket/jobs.py +40 -57
  105. crackerjack/mcp/websocket/monitoring_endpoints.py +2935 -0
  106. crackerjack/mcp/websocket/server.py +7 -25
  107. crackerjack/mcp/websocket/websocket_handler.py +6 -17
  108. crackerjack/mixins/__init__.py +3 -0
  109. crackerjack/mixins/error_handling.py +145 -0
  110. crackerjack/models/config.py +21 -1
  111. crackerjack/models/config_adapter.py +49 -1
  112. crackerjack/models/protocols.py +176 -107
  113. crackerjack/models/resource_protocols.py +55 -210
  114. crackerjack/models/task.py +3 -0
  115. crackerjack/monitoring/ai_agent_watchdog.py +13 -13
  116. crackerjack/monitoring/metrics_collector.py +426 -0
  117. crackerjack/monitoring/regression_prevention.py +8 -8
  118. crackerjack/monitoring/websocket_server.py +643 -0
  119. crackerjack/orchestration/advanced_orchestrator.py +11 -6
  120. crackerjack/orchestration/coverage_improvement.py +3 -3
  121. crackerjack/orchestration/execution_strategies.py +26 -6
  122. crackerjack/orchestration/test_progress_streamer.py +8 -5
  123. crackerjack/plugins/base.py +2 -2
  124. crackerjack/plugins/hooks.py +7 -0
  125. crackerjack/plugins/managers.py +11 -8
  126. crackerjack/security/__init__.py +0 -1
  127. crackerjack/security/audit.py +90 -105
  128. crackerjack/services/anomaly_detector.py +392 -0
  129. crackerjack/services/api_extractor.py +615 -0
  130. crackerjack/services/backup_service.py +2 -2
  131. crackerjack/services/bounded_status_operations.py +15 -152
  132. crackerjack/services/cache.py +127 -1
  133. crackerjack/services/changelog_automation.py +395 -0
  134. crackerjack/services/config.py +18 -11
  135. crackerjack/services/config_merge.py +30 -85
  136. crackerjack/services/config_template.py +506 -0
  137. crackerjack/services/contextual_ai_assistant.py +48 -22
  138. crackerjack/services/coverage_badge_service.py +171 -0
  139. crackerjack/services/coverage_ratchet.py +41 -17
  140. crackerjack/services/debug.py +3 -3
  141. crackerjack/services/dependency_analyzer.py +460 -0
  142. crackerjack/services/dependency_monitor.py +14 -11
  143. crackerjack/services/documentation_generator.py +491 -0
  144. crackerjack/services/documentation_service.py +675 -0
  145. crackerjack/services/enhanced_filesystem.py +6 -5
  146. crackerjack/services/enterprise_optimizer.py +865 -0
  147. crackerjack/services/error_pattern_analyzer.py +676 -0
  148. crackerjack/services/file_hasher.py +1 -1
  149. crackerjack/services/git.py +41 -45
  150. crackerjack/services/health_metrics.py +10 -8
  151. crackerjack/services/heatmap_generator.py +735 -0
  152. crackerjack/services/initialization.py +30 -33
  153. crackerjack/services/input_validator.py +5 -97
  154. crackerjack/services/intelligent_commit.py +327 -0
  155. crackerjack/services/log_manager.py +15 -12
  156. crackerjack/services/logging.py +4 -3
  157. crackerjack/services/lsp_client.py +628 -0
  158. crackerjack/services/memory_optimizer.py +409 -0
  159. crackerjack/services/metrics.py +42 -33
  160. crackerjack/services/parallel_executor.py +416 -0
  161. crackerjack/services/pattern_cache.py +1 -1
  162. crackerjack/services/pattern_detector.py +6 -6
  163. crackerjack/services/performance_benchmarks.py +250 -576
  164. crackerjack/services/performance_cache.py +382 -0
  165. crackerjack/services/performance_monitor.py +565 -0
  166. crackerjack/services/predictive_analytics.py +510 -0
  167. crackerjack/services/quality_baseline.py +234 -0
  168. crackerjack/services/quality_baseline_enhanced.py +646 -0
  169. crackerjack/services/quality_intelligence.py +785 -0
  170. crackerjack/services/regex_patterns.py +605 -524
  171. crackerjack/services/regex_utils.py +43 -123
  172. crackerjack/services/secure_path_utils.py +5 -164
  173. crackerjack/services/secure_status_formatter.py +30 -141
  174. crackerjack/services/secure_subprocess.py +11 -92
  175. crackerjack/services/security.py +61 -30
  176. crackerjack/services/security_logger.py +18 -22
  177. crackerjack/services/server_manager.py +124 -16
  178. crackerjack/services/status_authentication.py +16 -159
  179. crackerjack/services/status_security_manager.py +4 -131
  180. crackerjack/services/terminal_utils.py +0 -0
  181. crackerjack/services/thread_safe_status_collector.py +19 -125
  182. crackerjack/services/unified_config.py +21 -13
  183. crackerjack/services/validation_rate_limiter.py +5 -54
  184. crackerjack/services/version_analyzer.py +459 -0
  185. crackerjack/services/version_checker.py +1 -1
  186. crackerjack/services/websocket_resource_limiter.py +10 -144
  187. crackerjack/services/zuban_lsp_service.py +390 -0
  188. crackerjack/slash_commands/__init__.py +2 -7
  189. crackerjack/slash_commands/run.md +2 -2
  190. crackerjack/tools/validate_input_validator_patterns.py +14 -40
  191. crackerjack/tools/validate_regex_patterns.py +19 -48
  192. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/METADATA +197 -26
  193. crackerjack-0.33.1.dist-info/RECORD +229 -0
  194. crackerjack/CLAUDE.md +0 -207
  195. crackerjack/RULES.md +0 -380
  196. crackerjack/py313.py +0 -234
  197. crackerjack-0.32.0.dist-info/RECORD +0 -180
  198. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/WHEEL +0 -0
  199. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/entry_points.txt +0 -0
  200. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,977 @@
1
+ """Reference generator for comprehensive command documentation.
2
+
3
+ This module provides automatic generation of command reference documentation
4
+ from CLI definitions, including usage examples, parameter descriptions, and
5
+ workflow integration guides.
6
+ """
7
+
8
+ import ast
9
+ import typing as t
10
+ from dataclasses import dataclass, field
11
+ from datetime import datetime
12
+ from enum import Enum
13
+ from pathlib import Path
14
+
15
+ from ..models.protocols import (
16
+ ConfigManagerProtocol,
17
+ LoggerProtocol,
18
+ )
19
+
20
+
21
+ class ReferenceFormat(Enum):
22
+ """Formats for reference documentation."""
23
+
24
+ MARKDOWN = "markdown"
25
+ HTML = "html"
26
+ JSON = "json"
27
+ YAML = "yaml"
28
+ RST = "rst"
29
+
30
+
31
+ @dataclass
32
+ class ParameterInfo:
33
+ """Information about a command parameter."""
34
+
35
+ name: str
36
+ type_hint: str
37
+ default_value: t.Any
38
+ description: str
39
+ required: bool = False
40
+ choices: list[str] | None = None
41
+ example: str | None = None
42
+ deprecated: bool = False
43
+ added_in_version: str | None = None
44
+
45
+
46
+ @dataclass
47
+ class CommandInfo:
48
+ """Information about a CLI command."""
49
+
50
+ name: str
51
+ description: str
52
+ category: str
53
+ parameters: list[ParameterInfo] = field(default_factory=list)
54
+ examples: list[dict[str, str]] = field(default_factory=list)
55
+ related_commands: list[str] = field(default_factory=list)
56
+ aliases: list[str] = field(default_factory=list)
57
+ deprecated: bool = False
58
+ added_in_version: str | None = None
59
+
60
+ # Workflow integration
61
+ common_workflows: list[str] = field(default_factory=list)
62
+ prerequisites: list[str] = field(default_factory=list)
63
+ side_effects: list[str] = field(default_factory=list)
64
+
65
+ # AI optimization
66
+ ai_context: dict[str, t.Any] = field(default_factory=dict[str, t.Any])
67
+ success_patterns: list[str] = field(default_factory=list)
68
+ failure_patterns: list[str] = field(default_factory=list)
69
+
70
+
71
+ @dataclass
72
+ class CommandReference:
73
+ """Complete command reference documentation."""
74
+
75
+ commands: dict[str, CommandInfo]
76
+ categories: dict[str, list[str]]
77
+ workflows: dict[str, list[str]]
78
+ generated_at: datetime = field(default_factory=datetime.now)
79
+ version: str = "unknown"
80
+
81
+ def get_commands_by_category(self, category: str) -> list[CommandInfo]:
82
+ """Get all commands in a specific category.
83
+
84
+ Args:
85
+ category: Category name
86
+
87
+ Returns:
88
+ List of commands in category
89
+ """
90
+ return [cmd for cmd in self.commands.values() if cmd.category == category]
91
+
92
+ def get_command_by_name(self, name: str) -> CommandInfo | None:
93
+ """Get command by name or alias.
94
+
95
+ Args:
96
+ name: Command name or alias
97
+
98
+ Returns:
99
+ Command info if found, None otherwise
100
+ """
101
+ # Direct name match
102
+ if name in self.commands:
103
+ return self.commands[name]
104
+
105
+ # Alias match
106
+ for cmd in self.commands.values():
107
+ if name in cmd.aliases:
108
+ return cmd
109
+
110
+ return None
111
+
112
+
113
+ class ReferenceGenerator:
114
+ """Generator for comprehensive command reference documentation."""
115
+
116
+ def __init__(
117
+ self,
118
+ config_manager: ConfigManagerProtocol,
119
+ logger: LoggerProtocol,
120
+ ):
121
+ self.config_manager = config_manager
122
+ self.logger = logger
123
+
124
+ async def generate_reference(
125
+ self,
126
+ cli_module_path: str,
127
+ output_format: ReferenceFormat = ReferenceFormat.MARKDOWN,
128
+ include_examples: bool = True,
129
+ include_workflows: bool = True,
130
+ ) -> CommandReference:
131
+ """Generate command reference from CLI module.
132
+
133
+ Args:
134
+ cli_module_path: Path to CLI module to analyze
135
+ output_format: Output format for documentation
136
+ include_examples: Whether to include usage examples
137
+ include_workflows: Whether to include workflow information
138
+
139
+ Returns:
140
+ Generated command reference
141
+ """
142
+ self.logger.info(f"Generating command reference from: {cli_module_path}")
143
+
144
+ # Analyze CLI module
145
+ commands = await self._analyze_cli_module(cli_module_path)
146
+
147
+ # Enhance with examples if requested
148
+ if include_examples:
149
+ commands = await self._enhance_with_examples(commands)
150
+
151
+ # Enhance with workflows if requested
152
+ if include_workflows:
153
+ commands = await self._enhance_with_workflows(commands)
154
+
155
+ # Categorize commands
156
+ categories = self._categorize_commands(commands)
157
+
158
+ # Generate workflows
159
+ workflows = self._generate_workflows(commands) if include_workflows else {}
160
+
161
+ reference = CommandReference(
162
+ commands=commands,
163
+ categories=categories,
164
+ workflows=workflows,
165
+ )
166
+
167
+ self.logger.info(f"Generated reference for {len(commands)} commands")
168
+ return reference
169
+
170
+ async def render_reference(
171
+ self,
172
+ reference: CommandReference,
173
+ output_format: ReferenceFormat,
174
+ template_name: str | None = None,
175
+ ) -> str:
176
+ """Render command reference to specified format.
177
+
178
+ Args:
179
+ reference: Command reference to render
180
+ output_format: Output format
181
+ template_name: Optional template name to use
182
+
183
+ Returns:
184
+ Rendered reference documentation
185
+ """
186
+ if output_format == ReferenceFormat.MARKDOWN:
187
+ return self._render_markdown(reference)
188
+ elif output_format == ReferenceFormat.HTML:
189
+ return self._render_html(reference)
190
+ elif output_format == ReferenceFormat.JSON:
191
+ return self._render_json(reference)
192
+ elif output_format == ReferenceFormat.YAML:
193
+ return self._render_yaml(reference)
194
+ elif output_format == ReferenceFormat.RST:
195
+ return self._render_rst(reference)
196
+ else:
197
+ raise ValueError(f"Unsupported format: {output_format}")
198
+
199
+ async def _analyze_cli_module(self, module_path: str) -> dict[str, CommandInfo]:
200
+ """Analyze CLI module to extract command information.
201
+
202
+ Args:
203
+ module_path: Path to CLI module
204
+
205
+ Returns:
206
+ Dictionary of command name to CommandInfo
207
+ """
208
+ commands = {}
209
+
210
+ try:
211
+ # Read and parse the module
212
+ module_file = Path(module_path)
213
+ if not module_file.exists():
214
+ raise FileNotFoundError(f"CLI module not found: {module_path}")
215
+
216
+ source_code = module_file.read_text()
217
+
218
+ # Parse AST
219
+ tree = ast.parse(source_code)
220
+
221
+ # Extract command information
222
+ commands = self._extract_commands_from_ast(tree)
223
+
224
+ except Exception as e:
225
+ self.logger.error(f"Failed to analyze CLI module: {e}")
226
+
227
+ return commands
228
+
229
+ def _extract_commands_from_ast(self, tree: ast.AST) -> dict[str, CommandInfo]:
230
+ """Extract command information from AST.
231
+
232
+ Args:
233
+ tree: Parsed AST
234
+
235
+ Returns:
236
+ Dictionary of commands
237
+ """
238
+ commands: dict[str, CommandInfo] = {}
239
+ visitor = self._create_command_visitor(commands)
240
+ visitor.visit(tree)
241
+ return commands
242
+
243
+ def _create_command_visitor(
244
+ self, commands: dict[str, CommandInfo]
245
+ ) -> ast.NodeVisitor:
246
+ """Create AST visitor for command extraction.
247
+
248
+ Args:
249
+ commands: Dictionary to populate with commands
250
+
251
+ Returns:
252
+ Configured AST visitor
253
+ """
254
+
255
+ class CommandVisitor(ast.NodeVisitor):
256
+ def __init__(self, generator: t.Any) -> None:
257
+ self.generator = generator
258
+
259
+ def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
260
+ self.generator._process_function_node(node, commands)
261
+ self.generic_visit(node)
262
+
263
+ return CommandVisitor(self)
264
+
265
+ def _process_function_node(
266
+ self, node: ast.FunctionDef, commands: dict[str, CommandInfo]
267
+ ) -> None:
268
+ """Process function node for command extraction.
269
+
270
+ Args:
271
+ node: Function definition node
272
+ commands: Commands dictionary to update
273
+ """
274
+ for decorator in node.decorator_list:
275
+ if self._is_command_decorator(decorator):
276
+ command_info = self._extract_command_from_function(node)
277
+ if command_info:
278
+ commands[command_info.name] = command_info
279
+
280
+ def _is_command_decorator(self, decorator: ast.AST) -> bool:
281
+ """Check if decorator indicates a CLI command.
282
+
283
+ Args:
284
+ decorator: AST decorator node
285
+
286
+ Returns:
287
+ True if command decorator
288
+ """
289
+ if isinstance(decorator, ast.Name):
290
+ return decorator.id in ("command", "click_command")
291
+ elif isinstance(decorator, ast.Attribute):
292
+ return decorator.attr in ("command", "callback")
293
+ return False
294
+
295
+ def _extract_command_from_function(
296
+ self, node: ast.FunctionDef
297
+ ) -> CommandInfo | None:
298
+ """Extract command info from function definition.
299
+
300
+ Args:
301
+ node: Function definition node
302
+
303
+ Returns:
304
+ Command info or None if extraction fails
305
+ """
306
+ try:
307
+ command_name = node.name.replace("_", "-")
308
+ description = self._extract_docstring(node)
309
+ parameters = self._extract_function_parameters(node)
310
+
311
+ return CommandInfo(
312
+ name=command_name,
313
+ description=description or f"Execute {command_name}",
314
+ category="general",
315
+ parameters=parameters,
316
+ )
317
+
318
+ except Exception as e:
319
+ self.logger.warning(f"Failed to extract command {node.name}: {e}")
320
+ return None
321
+
322
+ def _extract_function_parameters(
323
+ self, node: ast.FunctionDef
324
+ ) -> list[ParameterInfo]:
325
+ """Extract parameter information from function.
326
+
327
+ Args:
328
+ node: Function definition node
329
+
330
+ Returns:
331
+ List of parameter information
332
+ """
333
+ parameters = []
334
+ for arg in node.args.args:
335
+ if arg.arg != "self":
336
+ param_info = self._extract_parameter_info(arg, node)
337
+ parameters.append(param_info)
338
+ return parameters
339
+
340
+ def _extract_docstring(self, node: ast.FunctionDef) -> str | None:
341
+ """Extract docstring from function.
342
+
343
+ Args:
344
+ node: Function definition node
345
+
346
+ Returns:
347
+ Docstring or None
348
+ """
349
+ if (
350
+ node.body
351
+ and isinstance(node.body[0], ast.Expr)
352
+ and isinstance(node.body[0].value, ast.Constant)
353
+ and isinstance(node.body[0].value.value, str)
354
+ ):
355
+ return node.body[0].value.value.strip()
356
+ return None
357
+
358
+ def _extract_parameter_info(
359
+ self, arg: ast.arg, func_node: ast.FunctionDef
360
+ ) -> ParameterInfo:
361
+ """Extract parameter information.
362
+
363
+ Args:
364
+ arg: Function argument node
365
+ func_node: Parent function node
366
+
367
+ Returns:
368
+ Parameter information
369
+ """
370
+ param_name = arg.arg.replace("_", "-")
371
+ type_hint = ast.unparse(arg.annotation) if arg.annotation else "Any"
372
+ default_value, required = self._extract_default_value(arg, func_node)
373
+
374
+ return ParameterInfo(
375
+ name=param_name,
376
+ type_hint=type_hint,
377
+ default_value=default_value,
378
+ description=f"Parameter: {param_name}",
379
+ required=required,
380
+ )
381
+
382
+ def _extract_default_value(
383
+ self, arg: ast.arg, func_node: ast.FunctionDef
384
+ ) -> tuple[t.Any, bool]:
385
+ """Extract default value and required status for parameter.
386
+
387
+ Args:
388
+ arg: Function argument node
389
+ func_node: Parent function node
390
+
391
+ Returns:
392
+ Tuple of (default_value, required)
393
+ """
394
+ defaults_count = len(func_node.args.defaults)
395
+ args_count = len(func_node.args.args)
396
+ defaults_start = args_count - defaults_count
397
+
398
+ arg_index = func_node.args.args.index(arg)
399
+ if arg_index >= defaults_start:
400
+ default_index = arg_index - defaults_start
401
+ default_node = func_node.args.defaults[default_index]
402
+ if isinstance(default_node, ast.Constant):
403
+ return default_node.value, False
404
+
405
+ return None, True
406
+
407
+ async def _enhance_with_examples(
408
+ self, commands: dict[str, CommandInfo]
409
+ ) -> dict[str, CommandInfo]:
410
+ """Enhance commands with usage examples.
411
+
412
+ Args:
413
+ commands: Commands to enhance
414
+
415
+ Returns:
416
+ Enhanced commands with examples
417
+ """
418
+ for command in commands.values():
419
+ # Generate basic examples
420
+ basic_example = f"python -m crackerjack --{command.name}"
421
+
422
+ # Add parameter examples
423
+ param_examples = []
424
+ for param in command.parameters:
425
+ if not param.required and param.default_value is not None:
426
+ if isinstance(param.default_value, bool):
427
+ param_examples.append(f"--{param.name}")
428
+ else:
429
+ param_examples.append(f"--{param.name} {param.default_value}")
430
+
431
+ if param_examples:
432
+ enhanced_example = f"{basic_example} {' '.join(param_examples)}"
433
+ command.examples.append(
434
+ {
435
+ "description": f"Using {command.name} with parameters",
436
+ "command": enhanced_example,
437
+ }
438
+ )
439
+
440
+ command.examples.append(
441
+ {
442
+ "description": f"Basic {command.name} usage",
443
+ "command": basic_example,
444
+ }
445
+ )
446
+
447
+ return commands
448
+
449
+ async def _enhance_with_workflows(
450
+ self, commands: dict[str, CommandInfo]
451
+ ) -> dict[str, CommandInfo]:
452
+ """Enhance commands with workflow information.
453
+
454
+ Args:
455
+ commands: Commands to enhance
456
+
457
+ Returns:
458
+ Enhanced commands with workflow info
459
+ """
460
+ # Define common workflow patterns
461
+ workflow_patterns = {
462
+ "development": ["test", "format", "lint", "type-check"],
463
+ "release": ["version", "build", "publish", "tag"],
464
+ "maintenance": ["clean", "update", "optimize", "backup"],
465
+ "monitoring": ["status", "health", "metrics", "logs"],
466
+ }
467
+
468
+ for command in commands.values():
469
+ # Assign workflows based on command name patterns
470
+ for workflow, patterns in workflow_patterns.items():
471
+ if any(pattern in command.name for pattern in patterns):
472
+ command.common_workflows.append(workflow)
473
+
474
+ # Add AI context based on command purpose
475
+ if "test" in command.name:
476
+ command.ai_context.update(
477
+ {
478
+ "purpose": "quality_assurance",
479
+ "automation_level": "high",
480
+ "ai_agent_compatible": True,
481
+ }
482
+ )
483
+ command.success_patterns.append("All tests passed")
484
+ command.failure_patterns.append("Test failures detected")
485
+
486
+ elif "format" in command.name or "lint" in command.name:
487
+ command.ai_context.update(
488
+ {
489
+ "purpose": "code_quality",
490
+ "automation_level": "high",
491
+ "ai_agent_compatible": True,
492
+ }
493
+ )
494
+ command.success_patterns.append("No formatting issues")
495
+ command.failure_patterns.append("Style violations found")
496
+
497
+ return commands
498
+
499
+ def _categorize_commands(
500
+ self, commands: dict[str, CommandInfo]
501
+ ) -> dict[str, list[str]]:
502
+ """Categorize commands by purpose.
503
+
504
+ Args:
505
+ commands: Commands to categorize
506
+
507
+ Returns:
508
+ Dictionary of category to command names
509
+ """
510
+ categories: dict[str, list[str]] = {}
511
+
512
+ category_patterns = {
513
+ "development": ["test", "format", "lint", "check", "run"],
514
+ "server": ["server", "start", "stop", "restart", "monitor"],
515
+ "release": ["version", "bump", "publish", "build", "tag"],
516
+ "configuration": ["config", "init", "setup", "install"],
517
+ "utilities": ["clean", "help", "info", "status"],
518
+ }
519
+
520
+ for command in commands.values():
521
+ assigned = False
522
+
523
+ # Assign based on patterns
524
+ for category, patterns in category_patterns.items():
525
+ if any(pattern in command.name for pattern in patterns):
526
+ command.category = category
527
+ if category not in categories:
528
+ categories[category] = []
529
+ categories[category].append(command.name)
530
+ assigned = True
531
+ break
532
+
533
+ # Default category
534
+ if not assigned:
535
+ command.category = "general"
536
+ if "general" not in categories:
537
+ categories["general"] = []
538
+ categories["general"].append(command.name)
539
+
540
+ return categories
541
+
542
+ def _generate_workflows(
543
+ self, commands: dict[str, CommandInfo]
544
+ ) -> dict[str, list[str]]:
545
+ """Generate workflow sequences from commands.
546
+
547
+ Args:
548
+ commands: Available commands
549
+
550
+ Returns:
551
+ Dictionary of workflow name to command sequence
552
+ """
553
+ workflows = {
554
+ "development_cycle": [
555
+ "format",
556
+ "lint",
557
+ "test",
558
+ "type-check",
559
+ ],
560
+ "release_cycle": [
561
+ "test",
562
+ "lint",
563
+ "version-bump",
564
+ "build",
565
+ "publish",
566
+ ],
567
+ "maintenance_cycle": [
568
+ "clean",
569
+ "update-dependencies",
570
+ "test",
571
+ "optimize",
572
+ ],
573
+ }
574
+
575
+ # Filter workflows to only include available commands
576
+ available_workflows = {}
577
+ for workflow_name, command_sequence in workflows.items():
578
+ available_sequence = [
579
+ cmd
580
+ for cmd in command_sequence
581
+ if any(cmd in available_cmd.name for available_cmd in commands.values())
582
+ ]
583
+ if available_sequence:
584
+ available_workflows[workflow_name] = available_sequence
585
+
586
+ return available_workflows
587
+
588
+ def _render_markdown(self, reference: CommandReference) -> str:
589
+ """Render reference as Markdown.
590
+
591
+ Args:
592
+ reference: Command reference
593
+
594
+ Returns:
595
+ Markdown formatted reference
596
+ """
597
+ lines = [
598
+ "# Command Reference",
599
+ "",
600
+ f"Generated: {reference.generated_at.strftime('%Y-%m-%d %H:%M:%S')}",
601
+ f"Version: {reference.version}",
602
+ "",
603
+ "## Categories",
604
+ "",
605
+ ]
606
+
607
+ # Add table of contents
608
+ lines.extend(self._render_markdown_toc(reference.categories))
609
+ lines.append("")
610
+
611
+ # Add commands by category
612
+ lines.extend(self._render_markdown_categories(reference))
613
+
614
+ # Add workflows if present
615
+ if reference.workflows:
616
+ lines.extend(self._render_markdown_workflows(reference.workflows))
617
+
618
+ return "\n".join(lines)
619
+
620
+ def _render_markdown_toc(self, categories: dict[str, list[str]]) -> list[str]:
621
+ """Render table of contents for markdown.
622
+
623
+ Args:
624
+ categories: Command categories
625
+
626
+ Returns:
627
+ List of TOC lines
628
+ """
629
+ return [
630
+ f"- [{category.title()}](#{category.replace('_', '-')})"
631
+ for category in categories
632
+ ]
633
+
634
+ def _render_markdown_categories(self, reference: CommandReference) -> list[str]:
635
+ """Render command categories for markdown.
636
+
637
+ Args:
638
+ reference: Command reference
639
+
640
+ Returns:
641
+ List of category section lines
642
+ """
643
+ category_lines = []
644
+ for category, command_names in reference.categories.items():
645
+ category_lines.extend(
646
+ [
647
+ f"## {category.title()}",
648
+ "",
649
+ ]
650
+ )
651
+
652
+ for command_name in command_names:
653
+ command = reference.commands[command_name]
654
+ category_lines.extend(self._render_command_markdown(command))
655
+
656
+ return category_lines
657
+
658
+ def _render_markdown_workflows(self, workflows: dict[str, list[str]]) -> list[str]:
659
+ """Render workflows section for markdown.
660
+
661
+ Args:
662
+ workflows: Workflow definitions
663
+
664
+ Returns:
665
+ List of workflow section lines
666
+ """
667
+ workflow_lines = [
668
+ "## Workflows",
669
+ "",
670
+ ]
671
+
672
+ for workflow_name, command_sequence in workflows.items():
673
+ workflow_lines.extend(
674
+ [
675
+ f"### {workflow_name.replace('_', ' ').title()}",
676
+ "",
677
+ ]
678
+ )
679
+
680
+ for i, cmd in enumerate(command_sequence, 1):
681
+ workflow_lines.append(f"{i}. `{cmd}`")
682
+
683
+ workflow_lines.append("")
684
+
685
+ return workflow_lines
686
+
687
+ def _render_command_markdown(self, command: CommandInfo) -> list[str]:
688
+ """Render single command as Markdown.
689
+
690
+ Args:
691
+ command: Command to render
692
+
693
+ Returns:
694
+ List of markdown lines
695
+ """
696
+ lines = [
697
+ f"### `{command.name}`",
698
+ "",
699
+ command.description,
700
+ "",
701
+ ]
702
+
703
+ # Add parameters section
704
+ if command.parameters:
705
+ lines.extend(self._render_command_parameters_markdown(command.parameters))
706
+
707
+ # Add examples section
708
+ if command.examples:
709
+ lines.extend(self._render_command_examples_markdown(command.examples))
710
+
711
+ # Add related commands section
712
+ if command.related_commands:
713
+ lines.extend(
714
+ self._render_command_related_markdown(command.related_commands)
715
+ )
716
+
717
+ return lines
718
+
719
+ def _render_command_parameters_markdown(
720
+ self, parameters: list[ParameterInfo]
721
+ ) -> list[str]:
722
+ """Render command parameters for markdown.
723
+
724
+ Args:
725
+ parameters: List of parameters to render
726
+
727
+ Returns:
728
+ List of parameter section lines
729
+ """
730
+ param_lines = [
731
+ "**Parameters:**",
732
+ "",
733
+ ]
734
+
735
+ for param in parameters:
736
+ required_str = " (required)" if param.required else ""
737
+ default_str = (
738
+ f" (default: {param.default_value})"
739
+ if param.default_value is not None
740
+ else ""
741
+ )
742
+ param_lines.append(
743
+ f"- `--{param.name}` ({param.type_hint}){required_str}{default_str}: {param.description}"
744
+ )
745
+
746
+ param_lines.append("")
747
+ return param_lines
748
+
749
+ def _render_command_examples_markdown(
750
+ self, examples: list[dict[str, str]]
751
+ ) -> list[str]:
752
+ """Render command examples for markdown.
753
+
754
+ Args:
755
+ examples: List of examples to render
756
+
757
+ Returns:
758
+ List of examples section lines
759
+ """
760
+ example_lines = [
761
+ "**Examples:**",
762
+ "",
763
+ ]
764
+
765
+ for example in examples:
766
+ example_lines.extend(
767
+ [
768
+ f"*{example['description']}:*",
769
+ "```bash",
770
+ example["command"],
771
+ "```",
772
+ "",
773
+ ]
774
+ )
775
+
776
+ return example_lines
777
+
778
+ def _render_command_related_markdown(
779
+ self, related_commands: list[str]
780
+ ) -> list[str]:
781
+ """Render related commands for markdown.
782
+
783
+ Args:
784
+ related_commands: List of related command names
785
+
786
+ Returns:
787
+ List of related commands section lines
788
+ """
789
+ return [
790
+ "**Related commands:** "
791
+ + ", ".join(f"`{cmd}`" for cmd in related_commands),
792
+ "",
793
+ ]
794
+
795
+ def _render_html(self, reference: CommandReference) -> str:
796
+ """Render reference as HTML."""
797
+ html = self._render_html_header(
798
+ reference.generated_at.strftime("%Y-%m-%d %H:%M:%S")
799
+ )
800
+ html += self._render_html_commands(reference)
801
+ html += "</body></html>"
802
+ return html
803
+
804
+ def _render_html_header(self, generated_at: str) -> str:
805
+ """Render HTML header with styles and metadata."""
806
+ return f"""<!DOCTYPE html>
807
+ <html>
808
+ <head>
809
+ <title>Command Reference</title>
810
+ <style>
811
+ body {{ font-family: Arial, sans-serif; margin: 40px; }}
812
+ .command {{ margin-bottom: 2em; }}
813
+ .parameter {{ margin-left: 1em; }}
814
+ code {{ background-color: #f5f5f5; padding: 2px 4px; }}
815
+ pre {{ background-color: #f5f5f5; padding: 10px; }}
816
+ </style>
817
+ </head>
818
+ <body>
819
+ <h1>Command Reference</h1>
820
+ <p>Generated: {generated_at}</p>
821
+ """
822
+
823
+ def _render_html_commands(self, reference: CommandReference) -> str:
824
+ """Render HTML commands by category."""
825
+ html = ""
826
+ for category, command_names in reference.categories.items():
827
+ html += f"<h2>{category.title()}</h2>"
828
+ html += self._render_html_category_commands(
829
+ reference.commands, command_names
830
+ )
831
+ return html
832
+
833
+ def _render_html_category_commands(
834
+ self, commands: dict[str, CommandInfo], command_names: list[str]
835
+ ) -> str:
836
+ """Render HTML for commands in a category."""
837
+ html = ""
838
+ for command_name in command_names:
839
+ command = commands[command_name]
840
+ html += '<div class="command">'
841
+ html += f"<h3><code>{command.name}</code></h3>"
842
+ html += f"<p>{command.description}</p>"
843
+ html += self._render_html_command_parameters(command.parameters)
844
+ html += "</div>"
845
+ return html
846
+
847
+ def _render_html_command_parameters(self, parameters: list[ParameterInfo]) -> str:
848
+ """Render HTML for command parameters."""
849
+ if not parameters:
850
+ return ""
851
+
852
+ html = "<h4>Parameters:</h4><ul>"
853
+ for param in parameters:
854
+ html += f'<li class="parameter"><code>--{param.name}</code> ({param.type_hint}): {param.description}</li>'
855
+ html += "</ul>"
856
+ return html
857
+
858
+ def _render_json(self, reference: CommandReference) -> str:
859
+ """Render reference as JSON."""
860
+ import json
861
+
862
+ data: dict[str, t.Any] = {
863
+ "generated_at": reference.generated_at.isoformat(),
864
+ "version": reference.version,
865
+ "categories": reference.categories,
866
+ "workflows": reference.workflows,
867
+ "commands": {},
868
+ }
869
+
870
+ for name, command in reference.commands.items():
871
+ data["commands"][name] = {
872
+ "name": command.name,
873
+ "description": command.description,
874
+ "category": command.category,
875
+ "parameters": [
876
+ {
877
+ "name": param.name,
878
+ "type": param.type_hint,
879
+ "default": param.default_value,
880
+ "description": param.description,
881
+ "required": param.required,
882
+ }
883
+ for param in command.parameters
884
+ ],
885
+ "examples": command.examples,
886
+ "related_commands": command.related_commands,
887
+ "aliases": command.aliases,
888
+ }
889
+
890
+ return json.dumps(data, indent=2, default=str)
891
+
892
+ def _render_yaml(self, reference: CommandReference) -> str:
893
+ """Render reference as YAML."""
894
+ import yaml
895
+
896
+ # Convert to JSON-serializable format first
897
+ json_data = self._render_json(reference)
898
+ import json
899
+
900
+ data = json.loads(json_data)
901
+
902
+ return yaml.dump(data, default_flow_style=False, sort_keys=False)
903
+
904
+ def _render_rst(self, reference: CommandReference) -> str:
905
+ """Render reference as reStructuredText."""
906
+ lines = [
907
+ "Command Reference",
908
+ "=================",
909
+ "",
910
+ f"Generated: {reference.generated_at.strftime('%Y-%m-%d %H:%M:%S')}",
911
+ f"Version: {reference.version}",
912
+ "",
913
+ ]
914
+
915
+ lines.extend(self._render_rst_categories(reference))
916
+ return "\n".join(lines)
917
+
918
+ def _render_rst_categories(self, reference: CommandReference) -> list[str]:
919
+ """Render RST categories and commands."""
920
+ rst_lines = []
921
+
922
+ for category, command_names in reference.categories.items():
923
+ rst_lines.extend(
924
+ [
925
+ category.title(),
926
+ "-" * len(category),
927
+ "",
928
+ ]
929
+ )
930
+
931
+ rst_lines.extend(
932
+ self._render_rst_category_commands(reference.commands, command_names)
933
+ )
934
+
935
+ return rst_lines
936
+
937
+ def _render_rst_category_commands(
938
+ self, commands: dict[str, CommandInfo], command_names: list[str]
939
+ ) -> list[str]:
940
+ """Render RST commands for a category."""
941
+ command_lines = []
942
+
943
+ for command_name in command_names:
944
+ command = commands[command_name]
945
+ command_lines.extend(
946
+ [
947
+ f"``{command.name}``",
948
+ "^" * (len(command.name) + 4),
949
+ "",
950
+ command.description,
951
+ "",
952
+ ]
953
+ )
954
+
955
+ if command.parameters:
956
+ command_lines.extend(
957
+ self._render_rst_command_parameters(command.parameters)
958
+ )
959
+
960
+ return command_lines
961
+
962
+ def _render_rst_command_parameters(
963
+ self, parameters: list[ParameterInfo]
964
+ ) -> list[str]:
965
+ """Render RST command parameters."""
966
+ param_lines = [
967
+ "Parameters:",
968
+ "",
969
+ ]
970
+
971
+ for param in parameters:
972
+ param_lines.append(
973
+ f"* ``--{param.name}`` ({param.type_hint}): {param.description}"
974
+ )
975
+
976
+ param_lines.append("")
977
+ return param_lines