crackerjack 0.18.2__py3-none-any.whl → 0.45.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.
- crackerjack/README.md +19 -0
- crackerjack/__init__.py +96 -2
- crackerjack/__main__.py +637 -138
- crackerjack/adapters/README.md +18 -0
- crackerjack/adapters/__init__.py +39 -0
- crackerjack/adapters/_output_paths.py +167 -0
- crackerjack/adapters/_qa_adapter_base.py +309 -0
- crackerjack/adapters/_tool_adapter_base.py +706 -0
- crackerjack/adapters/ai/README.md +65 -0
- crackerjack/adapters/ai/__init__.py +5 -0
- crackerjack/adapters/ai/claude.py +853 -0
- crackerjack/adapters/complexity/README.md +53 -0
- crackerjack/adapters/complexity/__init__.py +10 -0
- crackerjack/adapters/complexity/complexipy.py +641 -0
- crackerjack/adapters/dependency/__init__.py +22 -0
- crackerjack/adapters/dependency/pip_audit.py +418 -0
- crackerjack/adapters/format/README.md +72 -0
- crackerjack/adapters/format/__init__.py +11 -0
- crackerjack/adapters/format/mdformat.py +313 -0
- crackerjack/adapters/format/ruff.py +516 -0
- crackerjack/adapters/lint/README.md +47 -0
- crackerjack/adapters/lint/__init__.py +11 -0
- crackerjack/adapters/lint/codespell.py +273 -0
- crackerjack/adapters/lsp/README.md +49 -0
- crackerjack/adapters/lsp/__init__.py +27 -0
- crackerjack/adapters/lsp/_base.py +194 -0
- crackerjack/adapters/lsp/_client.py +358 -0
- crackerjack/adapters/lsp/_manager.py +193 -0
- crackerjack/adapters/lsp/skylos.py +283 -0
- crackerjack/adapters/lsp/zuban.py +557 -0
- crackerjack/adapters/refactor/README.md +59 -0
- crackerjack/adapters/refactor/__init__.py +12 -0
- crackerjack/adapters/refactor/creosote.py +318 -0
- crackerjack/adapters/refactor/refurb.py +406 -0
- crackerjack/adapters/refactor/skylos.py +494 -0
- crackerjack/adapters/sast/README.md +132 -0
- crackerjack/adapters/sast/__init__.py +32 -0
- crackerjack/adapters/sast/_base.py +201 -0
- crackerjack/adapters/sast/bandit.py +423 -0
- crackerjack/adapters/sast/pyscn.py +405 -0
- crackerjack/adapters/sast/semgrep.py +241 -0
- crackerjack/adapters/security/README.md +111 -0
- crackerjack/adapters/security/__init__.py +17 -0
- crackerjack/adapters/security/gitleaks.py +339 -0
- crackerjack/adapters/type/README.md +52 -0
- crackerjack/adapters/type/__init__.py +12 -0
- crackerjack/adapters/type/pyrefly.py +402 -0
- crackerjack/adapters/type/ty.py +402 -0
- crackerjack/adapters/type/zuban.py +522 -0
- crackerjack/adapters/utility/README.md +51 -0
- crackerjack/adapters/utility/__init__.py +10 -0
- crackerjack/adapters/utility/checks.py +884 -0
- crackerjack/agents/README.md +264 -0
- crackerjack/agents/__init__.py +66 -0
- crackerjack/agents/architect_agent.py +238 -0
- crackerjack/agents/base.py +167 -0
- crackerjack/agents/claude_code_bridge.py +641 -0
- crackerjack/agents/coordinator.py +600 -0
- crackerjack/agents/documentation_agent.py +520 -0
- crackerjack/agents/dry_agent.py +585 -0
- crackerjack/agents/enhanced_coordinator.py +279 -0
- crackerjack/agents/enhanced_proactive_agent.py +185 -0
- crackerjack/agents/error_middleware.py +53 -0
- crackerjack/agents/formatting_agent.py +230 -0
- crackerjack/agents/helpers/__init__.py +9 -0
- crackerjack/agents/helpers/performance/__init__.py +22 -0
- crackerjack/agents/helpers/performance/performance_ast_analyzer.py +357 -0
- crackerjack/agents/helpers/performance/performance_pattern_detector.py +909 -0
- crackerjack/agents/helpers/performance/performance_recommender.py +572 -0
- crackerjack/agents/helpers/refactoring/__init__.py +22 -0
- crackerjack/agents/helpers/refactoring/code_transformer.py +536 -0
- crackerjack/agents/helpers/refactoring/complexity_analyzer.py +344 -0
- crackerjack/agents/helpers/refactoring/dead_code_detector.py +437 -0
- crackerjack/agents/helpers/test_creation/__init__.py +19 -0
- crackerjack/agents/helpers/test_creation/test_ast_analyzer.py +216 -0
- crackerjack/agents/helpers/test_creation/test_coverage_analyzer.py +643 -0
- crackerjack/agents/helpers/test_creation/test_template_generator.py +1031 -0
- crackerjack/agents/import_optimization_agent.py +1181 -0
- crackerjack/agents/performance_agent.py +325 -0
- crackerjack/agents/performance_helpers.py +205 -0
- crackerjack/agents/proactive_agent.py +55 -0
- crackerjack/agents/refactoring_agent.py +511 -0
- crackerjack/agents/refactoring_helpers.py +247 -0
- crackerjack/agents/security_agent.py +793 -0
- crackerjack/agents/semantic_agent.py +479 -0
- crackerjack/agents/semantic_helpers.py +356 -0
- crackerjack/agents/test_creation_agent.py +570 -0
- crackerjack/agents/test_specialist_agent.py +526 -0
- crackerjack/agents/tracker.py +110 -0
- crackerjack/api.py +647 -0
- crackerjack/cli/README.md +394 -0
- crackerjack/cli/__init__.py +24 -0
- crackerjack/cli/cache_handlers.py +209 -0
- crackerjack/cli/cache_handlers_enhanced.py +680 -0
- crackerjack/cli/facade.py +162 -0
- crackerjack/cli/formatting.py +13 -0
- crackerjack/cli/handlers/__init__.py +85 -0
- crackerjack/cli/handlers/advanced.py +103 -0
- crackerjack/cli/handlers/ai_features.py +62 -0
- crackerjack/cli/handlers/analytics.py +479 -0
- crackerjack/cli/handlers/changelog.py +271 -0
- crackerjack/cli/handlers/config_handlers.py +16 -0
- crackerjack/cli/handlers/coverage.py +84 -0
- crackerjack/cli/handlers/documentation.py +280 -0
- crackerjack/cli/handlers/main_handlers.py +497 -0
- crackerjack/cli/handlers/monitoring.py +371 -0
- crackerjack/cli/handlers.py +700 -0
- crackerjack/cli/interactive.py +488 -0
- crackerjack/cli/options.py +1216 -0
- crackerjack/cli/semantic_handlers.py +292 -0
- crackerjack/cli/utils.py +19 -0
- crackerjack/cli/version.py +19 -0
- crackerjack/code_cleaner.py +1307 -0
- crackerjack/config/README.md +472 -0
- crackerjack/config/__init__.py +275 -0
- crackerjack/config/global_lock_config.py +207 -0
- crackerjack/config/hooks.py +390 -0
- crackerjack/config/loader.py +239 -0
- crackerjack/config/settings.py +141 -0
- crackerjack/config/tool_commands.py +331 -0
- crackerjack/core/README.md +393 -0
- crackerjack/core/__init__.py +0 -0
- crackerjack/core/async_workflow_orchestrator.py +738 -0
- crackerjack/core/autofix_coordinator.py +282 -0
- crackerjack/core/container.py +105 -0
- crackerjack/core/enhanced_container.py +583 -0
- crackerjack/core/file_lifecycle.py +472 -0
- crackerjack/core/performance.py +244 -0
- crackerjack/core/performance_monitor.py +357 -0
- crackerjack/core/phase_coordinator.py +1227 -0
- crackerjack/core/proactive_workflow.py +267 -0
- crackerjack/core/resource_manager.py +425 -0
- crackerjack/core/retry.py +275 -0
- crackerjack/core/service_watchdog.py +601 -0
- crackerjack/core/session_coordinator.py +239 -0
- crackerjack/core/timeout_manager.py +563 -0
- crackerjack/core/websocket_lifecycle.py +410 -0
- crackerjack/core/workflow/__init__.py +21 -0
- crackerjack/core/workflow/workflow_ai_coordinator.py +863 -0
- crackerjack/core/workflow/workflow_event_orchestrator.py +1107 -0
- crackerjack/core/workflow/workflow_issue_parser.py +714 -0
- crackerjack/core/workflow/workflow_phase_executor.py +1158 -0
- crackerjack/core/workflow/workflow_security_gates.py +400 -0
- crackerjack/core/workflow_orchestrator.py +2243 -0
- crackerjack/data/README.md +11 -0
- crackerjack/data/__init__.py +8 -0
- crackerjack/data/models.py +79 -0
- crackerjack/data/repository.py +210 -0
- crackerjack/decorators/README.md +180 -0
- crackerjack/decorators/__init__.py +35 -0
- crackerjack/decorators/error_handling.py +649 -0
- crackerjack/decorators/error_handling_decorators.py +334 -0
- crackerjack/decorators/helpers.py +58 -0
- crackerjack/decorators/patterns.py +281 -0
- crackerjack/decorators/utils.py +58 -0
- crackerjack/docs/INDEX.md +11 -0
- crackerjack/docs/README.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/README.md +11 -0
- crackerjack/documentation/__init__.py +31 -0
- crackerjack/documentation/ai_templates.py +756 -0
- crackerjack/documentation/dual_output_generator.py +767 -0
- crackerjack/documentation/mkdocs_integration.py +518 -0
- crackerjack/documentation/reference_generator.py +1065 -0
- crackerjack/dynamic_config.py +678 -0
- crackerjack/errors.py +378 -0
- crackerjack/events/README.md +11 -0
- crackerjack/events/__init__.py +16 -0
- crackerjack/events/telemetry.py +175 -0
- crackerjack/events/workflow_bus.py +346 -0
- crackerjack/exceptions/README.md +301 -0
- crackerjack/exceptions/__init__.py +5 -0
- crackerjack/exceptions/config.py +4 -0
- crackerjack/exceptions/tool_execution_error.py +245 -0
- crackerjack/executors/README.md +591 -0
- crackerjack/executors/__init__.py +13 -0
- crackerjack/executors/async_hook_executor.py +938 -0
- crackerjack/executors/cached_hook_executor.py +316 -0
- crackerjack/executors/hook_executor.py +1295 -0
- crackerjack/executors/hook_lock_manager.py +708 -0
- crackerjack/executors/individual_hook_executor.py +739 -0
- crackerjack/executors/lsp_aware_hook_executor.py +349 -0
- crackerjack/executors/progress_hook_executor.py +282 -0
- crackerjack/executors/tool_proxy.py +433 -0
- crackerjack/hooks/README.md +485 -0
- crackerjack/hooks/lsp_hook.py +93 -0
- crackerjack/intelligence/README.md +557 -0
- crackerjack/intelligence/__init__.py +37 -0
- crackerjack/intelligence/adaptive_learning.py +693 -0
- crackerjack/intelligence/agent_orchestrator.py +485 -0
- crackerjack/intelligence/agent_registry.py +377 -0
- crackerjack/intelligence/agent_selector.py +439 -0
- crackerjack/intelligence/integration.py +250 -0
- crackerjack/interactive.py +719 -0
- crackerjack/managers/README.md +369 -0
- crackerjack/managers/__init__.py +11 -0
- crackerjack/managers/async_hook_manager.py +135 -0
- crackerjack/managers/hook_manager.py +585 -0
- crackerjack/managers/publish_manager.py +631 -0
- crackerjack/managers/test_command_builder.py +391 -0
- crackerjack/managers/test_executor.py +474 -0
- crackerjack/managers/test_manager.py +1357 -0
- crackerjack/managers/test_progress.py +187 -0
- crackerjack/mcp/README.md +374 -0
- crackerjack/mcp/__init__.py +0 -0
- crackerjack/mcp/cache.py +352 -0
- crackerjack/mcp/client_runner.py +121 -0
- crackerjack/mcp/context.py +802 -0
- crackerjack/mcp/dashboard.py +657 -0
- crackerjack/mcp/enhanced_progress_monitor.py +493 -0
- crackerjack/mcp/file_monitor.py +394 -0
- crackerjack/mcp/progress_components.py +607 -0
- crackerjack/mcp/progress_monitor.py +1016 -0
- crackerjack/mcp/rate_limiter.py +336 -0
- crackerjack/mcp/server.py +24 -0
- crackerjack/mcp/server_core.py +526 -0
- crackerjack/mcp/service_watchdog.py +505 -0
- crackerjack/mcp/state.py +407 -0
- crackerjack/mcp/task_manager.py +259 -0
- crackerjack/mcp/tools/README.md +27 -0
- crackerjack/mcp/tools/__init__.py +19 -0
- crackerjack/mcp/tools/core_tools.py +469 -0
- crackerjack/mcp/tools/error_analyzer.py +283 -0
- crackerjack/mcp/tools/execution_tools.py +384 -0
- crackerjack/mcp/tools/intelligence_tool_registry.py +46 -0
- crackerjack/mcp/tools/intelligence_tools.py +264 -0
- crackerjack/mcp/tools/monitoring_tools.py +628 -0
- crackerjack/mcp/tools/proactive_tools.py +367 -0
- crackerjack/mcp/tools/progress_tools.py +222 -0
- crackerjack/mcp/tools/semantic_tools.py +584 -0
- crackerjack/mcp/tools/utility_tools.py +358 -0
- crackerjack/mcp/tools/workflow_executor.py +699 -0
- crackerjack/mcp/websocket/README.md +31 -0
- crackerjack/mcp/websocket/__init__.py +14 -0
- crackerjack/mcp/websocket/app.py +54 -0
- crackerjack/mcp/websocket/endpoints.py +492 -0
- crackerjack/mcp/websocket/event_bridge.py +188 -0
- crackerjack/mcp/websocket/jobs.py +406 -0
- crackerjack/mcp/websocket/monitoring/__init__.py +25 -0
- crackerjack/mcp/websocket/monitoring/api/__init__.py +19 -0
- crackerjack/mcp/websocket/monitoring/api/dependencies.py +141 -0
- crackerjack/mcp/websocket/monitoring/api/heatmap.py +154 -0
- crackerjack/mcp/websocket/monitoring/api/intelligence.py +199 -0
- crackerjack/mcp/websocket/monitoring/api/metrics.py +203 -0
- crackerjack/mcp/websocket/monitoring/api/telemetry.py +101 -0
- crackerjack/mcp/websocket/monitoring/dashboard.py +18 -0
- crackerjack/mcp/websocket/monitoring/factory.py +109 -0
- crackerjack/mcp/websocket/monitoring/filters.py +10 -0
- crackerjack/mcp/websocket/monitoring/metrics.py +64 -0
- crackerjack/mcp/websocket/monitoring/models.py +90 -0
- crackerjack/mcp/websocket/monitoring/utils.py +171 -0
- crackerjack/mcp/websocket/monitoring/websocket_manager.py +78 -0
- crackerjack/mcp/websocket/monitoring/websockets/__init__.py +17 -0
- crackerjack/mcp/websocket/monitoring/websockets/dependencies.py +126 -0
- crackerjack/mcp/websocket/monitoring/websockets/heatmap.py +176 -0
- crackerjack/mcp/websocket/monitoring/websockets/intelligence.py +291 -0
- crackerjack/mcp/websocket/monitoring/websockets/metrics.py +291 -0
- crackerjack/mcp/websocket/monitoring_endpoints.py +21 -0
- crackerjack/mcp/websocket/server.py +174 -0
- crackerjack/mcp/websocket/websocket_handler.py +276 -0
- crackerjack/mcp/websocket_server.py +10 -0
- crackerjack/models/README.md +308 -0
- crackerjack/models/__init__.py +40 -0
- crackerjack/models/config.py +730 -0
- crackerjack/models/config_adapter.py +265 -0
- crackerjack/models/protocols.py +1535 -0
- crackerjack/models/pydantic_models.py +320 -0
- crackerjack/models/qa_config.py +145 -0
- crackerjack/models/qa_results.py +134 -0
- crackerjack/models/resource_protocols.py +299 -0
- crackerjack/models/results.py +35 -0
- crackerjack/models/semantic_models.py +258 -0
- crackerjack/models/task.py +173 -0
- crackerjack/models/test_models.py +60 -0
- crackerjack/monitoring/README.md +11 -0
- crackerjack/monitoring/__init__.py +0 -0
- crackerjack/monitoring/ai_agent_watchdog.py +405 -0
- crackerjack/monitoring/metrics_collector.py +427 -0
- crackerjack/monitoring/regression_prevention.py +580 -0
- crackerjack/monitoring/websocket_server.py +406 -0
- crackerjack/orchestration/README.md +340 -0
- crackerjack/orchestration/__init__.py +43 -0
- crackerjack/orchestration/advanced_orchestrator.py +894 -0
- crackerjack/orchestration/cache/README.md +312 -0
- crackerjack/orchestration/cache/__init__.py +37 -0
- crackerjack/orchestration/cache/memory_cache.py +338 -0
- crackerjack/orchestration/cache/tool_proxy_cache.py +340 -0
- crackerjack/orchestration/config.py +297 -0
- crackerjack/orchestration/coverage_improvement.py +180 -0
- crackerjack/orchestration/execution_strategies.py +361 -0
- crackerjack/orchestration/hook_orchestrator.py +1398 -0
- crackerjack/orchestration/strategies/README.md +401 -0
- crackerjack/orchestration/strategies/__init__.py +39 -0
- crackerjack/orchestration/strategies/adaptive_strategy.py +630 -0
- crackerjack/orchestration/strategies/parallel_strategy.py +237 -0
- crackerjack/orchestration/strategies/sequential_strategy.py +299 -0
- crackerjack/orchestration/test_progress_streamer.py +647 -0
- crackerjack/plugins/README.md +11 -0
- crackerjack/plugins/__init__.py +15 -0
- crackerjack/plugins/base.py +200 -0
- crackerjack/plugins/hooks.py +254 -0
- crackerjack/plugins/loader.py +335 -0
- crackerjack/plugins/managers.py +264 -0
- crackerjack/py313.py +191 -0
- crackerjack/security/README.md +11 -0
- crackerjack/security/__init__.py +0 -0
- crackerjack/security/audit.py +197 -0
- crackerjack/services/README.md +374 -0
- crackerjack/services/__init__.py +9 -0
- crackerjack/services/ai/README.md +295 -0
- crackerjack/services/ai/__init__.py +7 -0
- crackerjack/services/ai/advanced_optimizer.py +878 -0
- crackerjack/services/ai/contextual_ai_assistant.py +542 -0
- crackerjack/services/ai/embeddings.py +444 -0
- crackerjack/services/ai/intelligent_commit.py +328 -0
- crackerjack/services/ai/predictive_analytics.py +510 -0
- crackerjack/services/anomaly_detector.py +392 -0
- crackerjack/services/api_extractor.py +617 -0
- crackerjack/services/backup_service.py +467 -0
- crackerjack/services/bounded_status_operations.py +530 -0
- crackerjack/services/cache.py +369 -0
- crackerjack/services/changelog_automation.py +399 -0
- crackerjack/services/command_execution_service.py +305 -0
- crackerjack/services/config_integrity.py +132 -0
- crackerjack/services/config_merge.py +546 -0
- crackerjack/services/config_service.py +198 -0
- crackerjack/services/config_template.py +493 -0
- crackerjack/services/coverage_badge_service.py +173 -0
- crackerjack/services/coverage_ratchet.py +381 -0
- crackerjack/services/debug.py +733 -0
- crackerjack/services/dependency_analyzer.py +460 -0
- crackerjack/services/dependency_monitor.py +622 -0
- crackerjack/services/documentation_generator.py +493 -0
- crackerjack/services/documentation_service.py +704 -0
- crackerjack/services/enhanced_filesystem.py +497 -0
- crackerjack/services/enterprise_optimizer.py +865 -0
- crackerjack/services/error_pattern_analyzer.py +676 -0
- crackerjack/services/file_filter.py +221 -0
- crackerjack/services/file_hasher.py +149 -0
- crackerjack/services/file_io_service.py +361 -0
- crackerjack/services/file_modifier.py +615 -0
- crackerjack/services/filesystem.py +381 -0
- crackerjack/services/git.py +422 -0
- crackerjack/services/health_metrics.py +615 -0
- crackerjack/services/heatmap_generator.py +744 -0
- crackerjack/services/incremental_executor.py +380 -0
- crackerjack/services/initialization.py +823 -0
- crackerjack/services/input_validator.py +668 -0
- crackerjack/services/intelligent_commit.py +327 -0
- crackerjack/services/log_manager.py +289 -0
- crackerjack/services/logging.py +228 -0
- crackerjack/services/lsp_client.py +628 -0
- crackerjack/services/memory_optimizer.py +414 -0
- crackerjack/services/metrics.py +587 -0
- crackerjack/services/monitoring/README.md +30 -0
- crackerjack/services/monitoring/__init__.py +9 -0
- crackerjack/services/monitoring/dependency_monitor.py +678 -0
- crackerjack/services/monitoring/error_pattern_analyzer.py +676 -0
- crackerjack/services/monitoring/health_metrics.py +716 -0
- crackerjack/services/monitoring/metrics.py +587 -0
- crackerjack/services/monitoring/performance_benchmarks.py +410 -0
- crackerjack/services/monitoring/performance_cache.py +388 -0
- crackerjack/services/monitoring/performance_monitor.py +569 -0
- crackerjack/services/parallel_executor.py +527 -0
- crackerjack/services/pattern_cache.py +333 -0
- crackerjack/services/pattern_detector.py +478 -0
- crackerjack/services/patterns/__init__.py +142 -0
- crackerjack/services/patterns/agents.py +107 -0
- crackerjack/services/patterns/code/__init__.py +15 -0
- crackerjack/services/patterns/code/detection.py +118 -0
- crackerjack/services/patterns/code/imports.py +107 -0
- crackerjack/services/patterns/code/paths.py +159 -0
- crackerjack/services/patterns/code/performance.py +119 -0
- crackerjack/services/patterns/code/replacement.py +36 -0
- crackerjack/services/patterns/core.py +212 -0
- crackerjack/services/patterns/documentation/__init__.py +14 -0
- crackerjack/services/patterns/documentation/badges_markdown.py +96 -0
- crackerjack/services/patterns/documentation/comments_blocks.py +83 -0
- crackerjack/services/patterns/documentation/docstrings.py +89 -0
- crackerjack/services/patterns/formatting.py +226 -0
- crackerjack/services/patterns/operations.py +339 -0
- crackerjack/services/patterns/security/__init__.py +23 -0
- crackerjack/services/patterns/security/code_injection.py +122 -0
- crackerjack/services/patterns/security/credentials.py +190 -0
- crackerjack/services/patterns/security/path_traversal.py +221 -0
- crackerjack/services/patterns/security/unsafe_operations.py +216 -0
- crackerjack/services/patterns/templates.py +62 -0
- crackerjack/services/patterns/testing/__init__.py +18 -0
- crackerjack/services/patterns/testing/error_patterns.py +107 -0
- crackerjack/services/patterns/testing/pytest_output.py +126 -0
- crackerjack/services/patterns/tool_output/__init__.py +16 -0
- crackerjack/services/patterns/tool_output/bandit.py +72 -0
- crackerjack/services/patterns/tool_output/other.py +97 -0
- crackerjack/services/patterns/tool_output/pyright.py +67 -0
- crackerjack/services/patterns/tool_output/ruff.py +44 -0
- crackerjack/services/patterns/url_sanitization.py +114 -0
- crackerjack/services/patterns/utilities.py +42 -0
- crackerjack/services/patterns/utils.py +339 -0
- crackerjack/services/patterns/validation.py +46 -0
- crackerjack/services/patterns/versioning.py +62 -0
- crackerjack/services/predictive_analytics.py +523 -0
- crackerjack/services/profiler.py +280 -0
- crackerjack/services/quality/README.md +415 -0
- crackerjack/services/quality/__init__.py +11 -0
- crackerjack/services/quality/anomaly_detector.py +392 -0
- crackerjack/services/quality/pattern_cache.py +333 -0
- crackerjack/services/quality/pattern_detector.py +479 -0
- crackerjack/services/quality/qa_orchestrator.py +491 -0
- crackerjack/services/quality/quality_baseline.py +395 -0
- crackerjack/services/quality/quality_baseline_enhanced.py +649 -0
- crackerjack/services/quality/quality_intelligence.py +949 -0
- crackerjack/services/regex_patterns.py +58 -0
- crackerjack/services/regex_utils.py +483 -0
- crackerjack/services/secure_path_utils.py +524 -0
- crackerjack/services/secure_status_formatter.py +450 -0
- crackerjack/services/secure_subprocess.py +635 -0
- crackerjack/services/security.py +239 -0
- crackerjack/services/security_logger.py +495 -0
- crackerjack/services/server_manager.py +411 -0
- crackerjack/services/smart_scheduling.py +167 -0
- crackerjack/services/status_authentication.py +460 -0
- crackerjack/services/status_security_manager.py +315 -0
- crackerjack/services/terminal_utils.py +0 -0
- crackerjack/services/thread_safe_status_collector.py +441 -0
- crackerjack/services/tool_filter.py +368 -0
- crackerjack/services/tool_version_service.py +43 -0
- crackerjack/services/unified_config.py +115 -0
- crackerjack/services/validation_rate_limiter.py +220 -0
- crackerjack/services/vector_store.py +689 -0
- crackerjack/services/version_analyzer.py +461 -0
- crackerjack/services/version_checker.py +223 -0
- crackerjack/services/websocket_resource_limiter.py +438 -0
- crackerjack/services/zuban_lsp_service.py +391 -0
- crackerjack/slash_commands/README.md +11 -0
- crackerjack/slash_commands/__init__.py +59 -0
- crackerjack/slash_commands/init.md +112 -0
- crackerjack/slash_commands/run.md +197 -0
- crackerjack/slash_commands/status.md +127 -0
- crackerjack/tools/README.md +11 -0
- crackerjack/tools/__init__.py +30 -0
- crackerjack/tools/_git_utils.py +105 -0
- crackerjack/tools/check_added_large_files.py +139 -0
- crackerjack/tools/check_ast.py +105 -0
- crackerjack/tools/check_json.py +103 -0
- crackerjack/tools/check_jsonschema.py +297 -0
- crackerjack/tools/check_toml.py +103 -0
- crackerjack/tools/check_yaml.py +110 -0
- crackerjack/tools/codespell_wrapper.py +72 -0
- crackerjack/tools/end_of_file_fixer.py +202 -0
- crackerjack/tools/format_json.py +128 -0
- crackerjack/tools/mdformat_wrapper.py +114 -0
- crackerjack/tools/trailing_whitespace.py +198 -0
- crackerjack/tools/validate_input_validator_patterns.py +236 -0
- crackerjack/tools/validate_regex_patterns.py +188 -0
- crackerjack/ui/README.md +11 -0
- crackerjack/ui/__init__.py +1 -0
- crackerjack/ui/dashboard_renderer.py +28 -0
- crackerjack/ui/templates/README.md +11 -0
- crackerjack/utils/console_utils.py +13 -0
- crackerjack/utils/dependency_guard.py +230 -0
- crackerjack/utils/retry_utils.py +275 -0
- crackerjack/workflows/README.md +590 -0
- crackerjack/workflows/__init__.py +46 -0
- crackerjack/workflows/actions.py +811 -0
- crackerjack/workflows/auto_fix.py +444 -0
- crackerjack/workflows/container_builder.py +499 -0
- crackerjack/workflows/definitions.py +443 -0
- crackerjack/workflows/engine.py +177 -0
- crackerjack/workflows/event_bridge.py +242 -0
- crackerjack-0.45.2.dist-info/METADATA +1678 -0
- crackerjack-0.45.2.dist-info/RECORD +478 -0
- {crackerjack-0.18.2.dist-info → crackerjack-0.45.2.dist-info}/WHEEL +1 -1
- crackerjack-0.45.2.dist-info/entry_points.txt +2 -0
- crackerjack/.gitignore +0 -14
- crackerjack/.libcst.codemod.yaml +0 -18
- crackerjack/.pdm.toml +0 -1
- crackerjack/.pre-commit-config.yaml +0 -91
- crackerjack/.pytest_cache/.gitignore +0 -2
- crackerjack/.pytest_cache/CACHEDIR.TAG +0 -4
- crackerjack/.pytest_cache/README.md +0 -8
- crackerjack/.pytest_cache/v/cache/nodeids +0 -1
- crackerjack/.pytest_cache/v/cache/stepwise +0 -1
- crackerjack/.ruff_cache/.gitignore +0 -1
- crackerjack/.ruff_cache/0.1.11/3256171999636029978 +0 -0
- crackerjack/.ruff_cache/0.1.14/602324811142551221 +0 -0
- crackerjack/.ruff_cache/0.1.4/10355199064880463147 +0 -0
- crackerjack/.ruff_cache/0.1.6/15140459877605758699 +0 -0
- crackerjack/.ruff_cache/0.1.7/1790508110482614856 +0 -0
- crackerjack/.ruff_cache/0.1.9/17041001205004563469 +0 -0
- crackerjack/.ruff_cache/0.11.2/4070660268492669020 +0 -0
- crackerjack/.ruff_cache/0.11.3/9818742842212983150 +0 -0
- crackerjack/.ruff_cache/0.11.4/9818742842212983150 +0 -0
- crackerjack/.ruff_cache/0.11.6/3557596832929915217 +0 -0
- crackerjack/.ruff_cache/0.11.7/10386934055395314831 +0 -0
- crackerjack/.ruff_cache/0.11.7/3557596832929915217 +0 -0
- crackerjack/.ruff_cache/0.11.8/530407680854991027 +0 -0
- crackerjack/.ruff_cache/0.2.0/10047773857155985907 +0 -0
- crackerjack/.ruff_cache/0.2.1/8522267973936635051 +0 -0
- crackerjack/.ruff_cache/0.2.2/18053836298936336950 +0 -0
- crackerjack/.ruff_cache/0.3.0/12548816621480535786 +0 -0
- crackerjack/.ruff_cache/0.3.3/11081883392474770722 +0 -0
- crackerjack/.ruff_cache/0.3.4/676973378459347183 +0 -0
- crackerjack/.ruff_cache/0.3.5/16311176246009842383 +0 -0
- crackerjack/.ruff_cache/0.5.7/1493622539551733492 +0 -0
- crackerjack/.ruff_cache/0.5.7/6231957614044513175 +0 -0
- crackerjack/.ruff_cache/0.5.7/9932762556785938009 +0 -0
- crackerjack/.ruff_cache/0.6.0/11982804814124138945 +0 -0
- crackerjack/.ruff_cache/0.6.0/12055761203849489982 +0 -0
- crackerjack/.ruff_cache/0.6.2/1206147804896221174 +0 -0
- crackerjack/.ruff_cache/0.6.4/1206147804896221174 +0 -0
- crackerjack/.ruff_cache/0.6.5/1206147804896221174 +0 -0
- crackerjack/.ruff_cache/0.6.7/3657366982708166874 +0 -0
- crackerjack/.ruff_cache/0.6.9/285614542852677309 +0 -0
- crackerjack/.ruff_cache/0.7.1/1024065805990144819 +0 -0
- crackerjack/.ruff_cache/0.7.1/285614542852677309 +0 -0
- crackerjack/.ruff_cache/0.7.3/16061516852537040135 +0 -0
- crackerjack/.ruff_cache/0.8.4/16354268377385700367 +0 -0
- crackerjack/.ruff_cache/0.9.10/12813592349865671909 +0 -0
- crackerjack/.ruff_cache/0.9.10/923908772239632759 +0 -0
- crackerjack/.ruff_cache/0.9.3/13948373885254993391 +0 -0
- crackerjack/.ruff_cache/0.9.9/12813592349865671909 +0 -0
- crackerjack/.ruff_cache/0.9.9/8843823720003377982 +0 -0
- crackerjack/.ruff_cache/CACHEDIR.TAG +0 -1
- crackerjack/crackerjack.py +0 -855
- crackerjack/pyproject.toml +0 -214
- crackerjack-0.18.2.dist-info/METADATA +0 -420
- crackerjack-0.18.2.dist-info/RECORD +0 -59
- crackerjack-0.18.2.dist-info/entry_points.txt +0 -4
- {crackerjack-0.18.2.dist-info → crackerjack-0.45.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
"""Service for extracting API documentation from Python source code."""
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
import inspect
|
|
5
|
+
import re
|
|
6
|
+
import typing as t
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from acb.console import Console
|
|
10
|
+
from acb.depends import Inject, depends
|
|
11
|
+
|
|
12
|
+
from ..models.protocols import APIExtractorProtocol
|
|
13
|
+
from .regex_patterns import SAFE_PATTERNS
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PythonDocstringParser:
|
|
17
|
+
"""Parser for extracting structured information from Python docstrings."""
|
|
18
|
+
|
|
19
|
+
def __init__(self) -> None:
|
|
20
|
+
# Regex patterns for different docstring styles using safe patterns
|
|
21
|
+
self.google_param_pattern = SAFE_PATTERNS[
|
|
22
|
+
"extract_google_docstring_params"
|
|
23
|
+
]._get_compiled_pattern()
|
|
24
|
+
self.sphinx_param_pattern = SAFE_PATTERNS[
|
|
25
|
+
"extract_sphinx_docstring_params"
|
|
26
|
+
]._get_compiled_pattern()
|
|
27
|
+
self.returns_pattern = SAFE_PATTERNS[
|
|
28
|
+
"extract_docstring_returns"
|
|
29
|
+
]._get_compiled_pattern()
|
|
30
|
+
|
|
31
|
+
def parse_docstring(self, docstring: str | None) -> dict[str, t.Any]:
|
|
32
|
+
"""Parse a docstring and extract structured information."""
|
|
33
|
+
if not docstring:
|
|
34
|
+
return {"description": "", "parameters": {}, "returns": "", "raises": []}
|
|
35
|
+
|
|
36
|
+
docstring = inspect.cleandoc(docstring)
|
|
37
|
+
|
|
38
|
+
# Extract main description (first paragraph)
|
|
39
|
+
lines = docstring.split("\n")
|
|
40
|
+
description_lines = []
|
|
41
|
+
for line in lines:
|
|
42
|
+
if line.strip() and not self._is_section_header(line):
|
|
43
|
+
description_lines.append(line)
|
|
44
|
+
elif description_lines:
|
|
45
|
+
break
|
|
46
|
+
|
|
47
|
+
description = "\n".join(description_lines).strip()
|
|
48
|
+
|
|
49
|
+
# Extract parameters
|
|
50
|
+
parameters = self._extract_parameters(docstring)
|
|
51
|
+
|
|
52
|
+
# Extract returns
|
|
53
|
+
returns = self._extract_returns(docstring)
|
|
54
|
+
|
|
55
|
+
# Extract raises information
|
|
56
|
+
raises = self._extract_raises(docstring)
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
"description": description,
|
|
60
|
+
"parameters": parameters,
|
|
61
|
+
"returns": returns,
|
|
62
|
+
"raises": raises,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
def _is_section_header(self, line: str) -> bool:
|
|
66
|
+
"""Check if a line is a docstring section header."""
|
|
67
|
+
line = line.strip().lower()
|
|
68
|
+
headers = [
|
|
69
|
+
"args:",
|
|
70
|
+
"arguments:",
|
|
71
|
+
"parameters:",
|
|
72
|
+
"param:",
|
|
73
|
+
"returns:",
|
|
74
|
+
"yields:",
|
|
75
|
+
"raises:",
|
|
76
|
+
"note:",
|
|
77
|
+
"example:",
|
|
78
|
+
]
|
|
79
|
+
return any(line.startswith(header) for header in headers)
|
|
80
|
+
|
|
81
|
+
def _extract_parameters(self, docstring: str) -> dict[str, str]:
|
|
82
|
+
"""Extract parameter documentation from docstring."""
|
|
83
|
+
parameters = {}
|
|
84
|
+
|
|
85
|
+
# Try Google style first
|
|
86
|
+
google_matches = self.google_param_pattern.findall(docstring)
|
|
87
|
+
for param_name, param_desc in google_matches:
|
|
88
|
+
parameters[param_name] = param_desc.strip()
|
|
89
|
+
|
|
90
|
+
# Try Sphinx style if no Google style found
|
|
91
|
+
if not parameters:
|
|
92
|
+
sphinx_matches = self.sphinx_param_pattern.findall(docstring)
|
|
93
|
+
for param_name, param_desc in sphinx_matches:
|
|
94
|
+
parameters[param_name] = param_desc.strip()
|
|
95
|
+
|
|
96
|
+
return parameters
|
|
97
|
+
|
|
98
|
+
def _extract_returns(self, docstring: str) -> str:
|
|
99
|
+
"""Extract return value documentation from docstring."""
|
|
100
|
+
match = self.returns_pattern.search(docstring)
|
|
101
|
+
if match:
|
|
102
|
+
return match.group(1).strip()
|
|
103
|
+
return ""
|
|
104
|
+
|
|
105
|
+
def _extract_raises(self, docstring: str) -> list[str]:
|
|
106
|
+
"""Extract exception documentation from docstring."""
|
|
107
|
+
raises_pattern = re.compile( # REGEX OK: exception extraction
|
|
108
|
+
r"(?:Raises?|Raise):\s*(.+?)(?=\n\n|\n\w+:|\Z)", re.MULTILINE | re.DOTALL
|
|
109
|
+
)
|
|
110
|
+
match = raises_pattern.search(docstring)
|
|
111
|
+
if match:
|
|
112
|
+
raises_text = match.group(1).strip()
|
|
113
|
+
# Split by lines and clean up
|
|
114
|
+
raises_list = [
|
|
115
|
+
line.strip() for line in raises_text.split("\n") if line.strip()
|
|
116
|
+
]
|
|
117
|
+
return raises_list
|
|
118
|
+
return []
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class APIExtractorImpl(APIExtractorProtocol):
|
|
122
|
+
"""Implementation of API documentation extraction from source code."""
|
|
123
|
+
|
|
124
|
+
@depends.inject
|
|
125
|
+
def __init__(self, console: Inject[Console]) -> None:
|
|
126
|
+
self.console = console
|
|
127
|
+
self.docstring_parser = PythonDocstringParser()
|
|
128
|
+
|
|
129
|
+
def extract_from_python_files(self, files: list[Path]) -> dict[str, t.Any]:
|
|
130
|
+
"""Extract API documentation from Python files."""
|
|
131
|
+
api_data: dict[str, t.Any] = {
|
|
132
|
+
"modules": {},
|
|
133
|
+
"classes": {},
|
|
134
|
+
"functions": {},
|
|
135
|
+
"protocols": {},
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
for file_path in files:
|
|
139
|
+
if not file_path.exists() or file_path.suffix != ".py":
|
|
140
|
+
continue
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
source_code = Path(file_path).read_text(encoding="utf-8")
|
|
144
|
+
|
|
145
|
+
tree = ast.parse(source_code)
|
|
146
|
+
module_data = self._extract_module_info(tree, file_path, source_code)
|
|
147
|
+
api_data["modules"][str(file_path)] = module_data
|
|
148
|
+
|
|
149
|
+
except Exception as e:
|
|
150
|
+
self.console.print(
|
|
151
|
+
f"[yellow]Warning: Could not parse {file_path}: {e}[/yellow]"
|
|
152
|
+
)
|
|
153
|
+
continue
|
|
154
|
+
|
|
155
|
+
return api_data
|
|
156
|
+
|
|
157
|
+
def extract_protocol_definitions(self, protocol_file: Path) -> dict[str, t.Any]:
|
|
158
|
+
"""Extract protocol definitions from protocols.py file."""
|
|
159
|
+
if not protocol_file.exists():
|
|
160
|
+
return {}
|
|
161
|
+
|
|
162
|
+
try:
|
|
163
|
+
source_code = Path(protocol_file).read_text(encoding="utf-8")
|
|
164
|
+
|
|
165
|
+
tree = ast.parse(source_code)
|
|
166
|
+
protocols = {}
|
|
167
|
+
|
|
168
|
+
for node in ast.walk(tree):
|
|
169
|
+
if isinstance(node, ast.ClassDef) and self._is_protocol_class(node):
|
|
170
|
+
protocol_info = self._extract_protocol_info(node, source_code)
|
|
171
|
+
protocols[node.name] = protocol_info
|
|
172
|
+
|
|
173
|
+
return {"protocols": protocols}
|
|
174
|
+
|
|
175
|
+
except Exception as e:
|
|
176
|
+
self.console.print(f"[red]Error extracting protocols: {e}[/red]")
|
|
177
|
+
return {}
|
|
178
|
+
|
|
179
|
+
def extract_service_interfaces(self, service_files: list[Path]) -> dict[str, t.Any]:
|
|
180
|
+
"""Extract service interfaces and their methods."""
|
|
181
|
+
services = {}
|
|
182
|
+
|
|
183
|
+
for file_path in service_files:
|
|
184
|
+
if not file_path.exists() or file_path.suffix != ".py":
|
|
185
|
+
continue
|
|
186
|
+
|
|
187
|
+
try:
|
|
188
|
+
source_code = Path(file_path).read_text(encoding="utf-8")
|
|
189
|
+
|
|
190
|
+
tree = ast.parse(source_code)
|
|
191
|
+
service_info = self._extract_service_info(tree, file_path, source_code)
|
|
192
|
+
if service_info:
|
|
193
|
+
services[file_path.stem] = service_info
|
|
194
|
+
|
|
195
|
+
except Exception as e:
|
|
196
|
+
self.console.print(
|
|
197
|
+
f"[yellow]Warning: Could not parse service {file_path}: {e}[/yellow]"
|
|
198
|
+
)
|
|
199
|
+
continue
|
|
200
|
+
|
|
201
|
+
return {"services": services}
|
|
202
|
+
|
|
203
|
+
def extract_cli_commands(self, cli_files: list[Path]) -> dict[str, t.Any]:
|
|
204
|
+
"""Extract CLI command definitions and options."""
|
|
205
|
+
cli_data: dict[str, t.Any] = {"commands": {}, "options": {}}
|
|
206
|
+
|
|
207
|
+
for file_path in cli_files:
|
|
208
|
+
if not file_path.exists() or file_path.suffix != ".py":
|
|
209
|
+
continue
|
|
210
|
+
|
|
211
|
+
try:
|
|
212
|
+
source_code = Path(file_path).read_text(encoding="utf-8")
|
|
213
|
+
|
|
214
|
+
tree = ast.parse(source_code)
|
|
215
|
+
cli_info = self._extract_cli_info(tree, source_code)
|
|
216
|
+
cli_data["commands"][file_path.stem] = cli_info
|
|
217
|
+
|
|
218
|
+
except Exception as e:
|
|
219
|
+
self.console.print(
|
|
220
|
+
f"[yellow]Warning: Could not parse CLI file {file_path}: {e}[/yellow]"
|
|
221
|
+
)
|
|
222
|
+
continue
|
|
223
|
+
|
|
224
|
+
return cli_data
|
|
225
|
+
|
|
226
|
+
def extract_mcp_tools(self, mcp_files: list[Path]) -> dict[str, t.Any]:
|
|
227
|
+
"""Extract MCP tool definitions and their capabilities."""
|
|
228
|
+
mcp_tools = {}
|
|
229
|
+
|
|
230
|
+
for file_path in mcp_files:
|
|
231
|
+
if not file_path.exists():
|
|
232
|
+
continue
|
|
233
|
+
|
|
234
|
+
try:
|
|
235
|
+
if file_path.suffix == ".py":
|
|
236
|
+
source_code = Path(file_path).read_text(encoding="utf-8")
|
|
237
|
+
tree = ast.parse(source_code)
|
|
238
|
+
tool_info = self._extract_mcp_python_tools(tree, source_code)
|
|
239
|
+
elif file_path.suffix == ".md":
|
|
240
|
+
markdown_content = Path(file_path).read_text(encoding="utf-8")
|
|
241
|
+
tool_info = self._extract_mcp_markdown_docs(markdown_content)
|
|
242
|
+
else:
|
|
243
|
+
continue
|
|
244
|
+
|
|
245
|
+
if tool_info:
|
|
246
|
+
mcp_tools[file_path.stem] = tool_info
|
|
247
|
+
|
|
248
|
+
except Exception as e:
|
|
249
|
+
self.console.print(
|
|
250
|
+
f"[yellow]Warning: Could not parse MCP file {file_path}: {e}[/yellow]"
|
|
251
|
+
)
|
|
252
|
+
continue
|
|
253
|
+
|
|
254
|
+
return {"mcp_tools": mcp_tools}
|
|
255
|
+
|
|
256
|
+
def _extract_module_info(
|
|
257
|
+
self, tree: ast.AST, file_path: Path, source_code: str
|
|
258
|
+
) -> dict[str, t.Any]:
|
|
259
|
+
"""Extract information from a Python module."""
|
|
260
|
+
module_info = self._create_base_module_info(tree, file_path)
|
|
261
|
+
self._populate_module_components(module_info, tree, source_code)
|
|
262
|
+
return module_info
|
|
263
|
+
|
|
264
|
+
def _create_base_module_info(
|
|
265
|
+
self, tree: ast.AST, file_path: Path
|
|
266
|
+
) -> dict[str, t.Any]:
|
|
267
|
+
"""Create the base module information structure."""
|
|
268
|
+
# ast.get_docstring requires Module, ClassDef, FunctionDef, or AsyncFunctionDef
|
|
269
|
+
docstring = (
|
|
270
|
+
ast.get_docstring(tree)
|
|
271
|
+
if isinstance(
|
|
272
|
+
tree, ast.Module | ast.ClassDef | ast.FunctionDef | ast.AsyncFunctionDef
|
|
273
|
+
)
|
|
274
|
+
else None
|
|
275
|
+
)
|
|
276
|
+
return {
|
|
277
|
+
"path": str(file_path),
|
|
278
|
+
"docstring": docstring,
|
|
279
|
+
"classes": [],
|
|
280
|
+
"functions": [],
|
|
281
|
+
"imports": [],
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
def _populate_module_components(
|
|
285
|
+
self, module_info: dict[str, t.Any], tree: ast.AST, source_code: str
|
|
286
|
+
) -> None:
|
|
287
|
+
"""Populate module components by walking the AST."""
|
|
288
|
+
for node in ast.walk(tree):
|
|
289
|
+
self._process_ast_node(module_info, node, source_code)
|
|
290
|
+
|
|
291
|
+
def _process_ast_node(
|
|
292
|
+
self, module_info: dict[str, t.Any], node: ast.AST, source_code: str
|
|
293
|
+
) -> None:
|
|
294
|
+
"""Process individual AST nodes and extract relevant information."""
|
|
295
|
+
if isinstance(node, ast.ClassDef):
|
|
296
|
+
class_info = self._extract_class_info(node, source_code)
|
|
297
|
+
module_info["classes"].append(class_info)
|
|
298
|
+
elif isinstance(node, ast.FunctionDef):
|
|
299
|
+
func_info = self._extract_function_info(node, source_code)
|
|
300
|
+
module_info["functions"].append(func_info)
|
|
301
|
+
elif isinstance(node, ast.Import | ast.ImportFrom):
|
|
302
|
+
import_info = self._extract_import_info(node)
|
|
303
|
+
module_info["imports"].append(import_info)
|
|
304
|
+
|
|
305
|
+
def _extract_class_info(
|
|
306
|
+
self, node: ast.ClassDef, source_code: str
|
|
307
|
+
) -> dict[str, t.Any]:
|
|
308
|
+
"""Extract information from a class definition."""
|
|
309
|
+
docstring = ast.get_docstring(node)
|
|
310
|
+
parsed_doc = self.docstring_parser.parse_docstring(docstring)
|
|
311
|
+
|
|
312
|
+
class_info = self._build_base_class_info(node, parsed_doc)
|
|
313
|
+
self._extract_class_methods(class_info, node, source_code)
|
|
314
|
+
|
|
315
|
+
return class_info
|
|
316
|
+
|
|
317
|
+
def _build_base_class_info(
|
|
318
|
+
self, node: ast.ClassDef, parsed_doc: dict[str, t.Any]
|
|
319
|
+
) -> dict[str, t.Any]:
|
|
320
|
+
"""Build the base class information structure."""
|
|
321
|
+
return {
|
|
322
|
+
"name": node.name,
|
|
323
|
+
"docstring": parsed_doc,
|
|
324
|
+
"base_classes": [self._get_node_name(base) for base in node.bases],
|
|
325
|
+
"methods": [],
|
|
326
|
+
"properties": [],
|
|
327
|
+
"is_protocol": self._is_protocol_class(node),
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
def _extract_class_methods(
|
|
331
|
+
self, class_info: dict[str, t.Any], node: ast.ClassDef, source_code: str
|
|
332
|
+
) -> None:
|
|
333
|
+
"""Extract method information from class body."""
|
|
334
|
+
for item in node.body:
|
|
335
|
+
if isinstance(item, ast.FunctionDef):
|
|
336
|
+
method_info = self._extract_function_info(item, source_code)
|
|
337
|
+
method_info["is_method"] = True
|
|
338
|
+
method_info["visibility"] = self._determine_method_visibility(item.name)
|
|
339
|
+
class_info["methods"].append(method_info)
|
|
340
|
+
|
|
341
|
+
def _determine_method_visibility(self, method_name: str) -> str:
|
|
342
|
+
"""Determine method visibility based on naming convention."""
|
|
343
|
+
if method_name.startswith("_") and not method_name.startswith("__"):
|
|
344
|
+
return "protected"
|
|
345
|
+
elif method_name.startswith("__"):
|
|
346
|
+
return "private"
|
|
347
|
+
return "public"
|
|
348
|
+
|
|
349
|
+
def _extract_function_info(
|
|
350
|
+
self, node: ast.FunctionDef, source_code: str
|
|
351
|
+
) -> dict[str, t.Any]:
|
|
352
|
+
"""Extract information from a function definition."""
|
|
353
|
+
docstring = ast.get_docstring(node)
|
|
354
|
+
parsed_doc = self.docstring_parser.parse_docstring(docstring)
|
|
355
|
+
|
|
356
|
+
# Extract parameter information
|
|
357
|
+
parameters = []
|
|
358
|
+
for arg in node.args.args:
|
|
359
|
+
param_info = {
|
|
360
|
+
"name": arg.arg,
|
|
361
|
+
"annotation": self._get_annotation_string(arg.annotation),
|
|
362
|
+
"description": parsed_doc["parameters"].get(arg.arg, ""),
|
|
363
|
+
}
|
|
364
|
+
parameters.append(param_info)
|
|
365
|
+
|
|
366
|
+
# Extract return annotation
|
|
367
|
+
return_annotation = self._get_annotation_string(node.returns)
|
|
368
|
+
|
|
369
|
+
func_info = {
|
|
370
|
+
"name": node.name,
|
|
371
|
+
"docstring": parsed_doc,
|
|
372
|
+
"parameters": parameters,
|
|
373
|
+
"return_annotation": return_annotation,
|
|
374
|
+
"is_async": isinstance(node, ast.AsyncFunctionDef),
|
|
375
|
+
"decorators": [
|
|
376
|
+
self._get_node_name(decorator) for decorator in node.decorator_list
|
|
377
|
+
],
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return func_info
|
|
381
|
+
|
|
382
|
+
def _extract_protocol_info(
|
|
383
|
+
self, node: ast.ClassDef, source_code: str
|
|
384
|
+
) -> dict[str, t.Any]:
|
|
385
|
+
"""Extract detailed information from a protocol definition."""
|
|
386
|
+
docstring = ast.get_docstring(node)
|
|
387
|
+
parsed_doc = self.docstring_parser.parse_docstring(docstring)
|
|
388
|
+
|
|
389
|
+
protocol_info: dict[str, t.Any] = {
|
|
390
|
+
"name": node.name,
|
|
391
|
+
"docstring": parsed_doc,
|
|
392
|
+
"methods": [],
|
|
393
|
+
"runtime_checkable": any(
|
|
394
|
+
self._get_node_name(decorator) == "runtime_checkable"
|
|
395
|
+
for decorator in node.decorator_list
|
|
396
|
+
),
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
for item in node.body:
|
|
400
|
+
if isinstance(item, ast.FunctionDef):
|
|
401
|
+
method_info = self._extract_function_info(item, source_code)
|
|
402
|
+
method_info["is_abstract"] = True # Protocol methods are abstract
|
|
403
|
+
if isinstance(protocol_info["methods"], list):
|
|
404
|
+
protocol_info["methods"].append(method_info)
|
|
405
|
+
|
|
406
|
+
return protocol_info
|
|
407
|
+
|
|
408
|
+
def _extract_service_info(
|
|
409
|
+
self, tree: ast.AST, file_path: Path, source_code: str
|
|
410
|
+
) -> dict[str, t.Any] | None:
|
|
411
|
+
"""Extract service implementation information."""
|
|
412
|
+
service_info = self._create_service_info_structure(file_path)
|
|
413
|
+
self._populate_service_classes(service_info, tree, source_code)
|
|
414
|
+
return service_info if service_info["classes"] else None
|
|
415
|
+
|
|
416
|
+
def _create_service_info_structure(self, file_path: Path) -> dict[str, t.Any]:
|
|
417
|
+
"""Create the base service information structure."""
|
|
418
|
+
return {
|
|
419
|
+
"path": str(file_path),
|
|
420
|
+
"classes": [],
|
|
421
|
+
"functions": [],
|
|
422
|
+
"protocols_implemented": [],
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
def _populate_service_classes(
|
|
426
|
+
self, service_info: dict[str, t.Any], tree: ast.AST, source_code: str
|
|
427
|
+
) -> None:
|
|
428
|
+
"""Populate service classes by walking the AST."""
|
|
429
|
+
for node in ast.walk(tree):
|
|
430
|
+
if isinstance(node, ast.ClassDef):
|
|
431
|
+
self._process_service_class(service_info, node, source_code)
|
|
432
|
+
|
|
433
|
+
def _process_service_class(
|
|
434
|
+
self, service_info: dict[str, t.Any], node: ast.ClassDef, source_code: str
|
|
435
|
+
) -> None:
|
|
436
|
+
"""Process a single service class and extract its information."""
|
|
437
|
+
class_info = self._extract_class_info(node, source_code)
|
|
438
|
+
self._extract_implemented_protocols(service_info, node)
|
|
439
|
+
self._add_class_to_service_info(service_info, class_info)
|
|
440
|
+
|
|
441
|
+
def _extract_implemented_protocols(
|
|
442
|
+
self, service_info: dict[str, t.Any], node: ast.ClassDef
|
|
443
|
+
) -> None:
|
|
444
|
+
"""Extract protocol implementations from class bases."""
|
|
445
|
+
for base in node.bases:
|
|
446
|
+
base_name = self._get_node_name(base)
|
|
447
|
+
if "Protocol" in base_name and isinstance(
|
|
448
|
+
service_info["protocols_implemented"], list
|
|
449
|
+
):
|
|
450
|
+
service_info["protocols_implemented"].append(base_name)
|
|
451
|
+
|
|
452
|
+
def _add_class_to_service_info(
|
|
453
|
+
self, service_info: dict[str, t.Any], class_info: dict[str, t.Any]
|
|
454
|
+
) -> None:
|
|
455
|
+
"""Add class information to the service info structure."""
|
|
456
|
+
if isinstance(service_info["classes"], list):
|
|
457
|
+
service_info["classes"].append(class_info)
|
|
458
|
+
|
|
459
|
+
def _extract_cli_info(self, tree: ast.AST, source_code: str) -> dict[str, t.Any]:
|
|
460
|
+
"""Extract CLI command and option information."""
|
|
461
|
+
cli_info: dict[str, t.Any] = {"options": [], "commands": [], "arguments": []}
|
|
462
|
+
|
|
463
|
+
# Look for Pydantic model fields that represent CLI options
|
|
464
|
+
for node in ast.walk(tree):
|
|
465
|
+
if isinstance(node, ast.ClassDef):
|
|
466
|
+
self._extract_class_cli_options(node, cli_info)
|
|
467
|
+
|
|
468
|
+
return cli_info
|
|
469
|
+
|
|
470
|
+
def _extract_class_cli_options(
|
|
471
|
+
self, class_node: ast.ClassDef, cli_info: dict[str, t.Any]
|
|
472
|
+
) -> None:
|
|
473
|
+
"""Extract CLI options from a class definition."""
|
|
474
|
+
for item in class_node.body:
|
|
475
|
+
if self._is_cli_field(item) and isinstance(item, ast.AnnAssign):
|
|
476
|
+
field_info = self._create_cli_field_info(item)
|
|
477
|
+
if field_info and isinstance(cli_info["options"], list):
|
|
478
|
+
cli_info["options"].append(field_info)
|
|
479
|
+
|
|
480
|
+
def _is_cli_field(self, item: ast.stmt) -> bool:
|
|
481
|
+
"""Check if an AST item represents a CLI field."""
|
|
482
|
+
return isinstance(item, ast.AnnAssign) and item.target is not None
|
|
483
|
+
|
|
484
|
+
def _create_cli_field_info(self, item: ast.AnnAssign) -> dict[str, t.Any] | None:
|
|
485
|
+
"""Create field information from an annotated assignment."""
|
|
486
|
+
field_name = getattr(item.target, "id", None)
|
|
487
|
+
if not field_name:
|
|
488
|
+
return None
|
|
489
|
+
|
|
490
|
+
return {
|
|
491
|
+
"name": field_name,
|
|
492
|
+
"type": self._get_annotation_string(item.annotation),
|
|
493
|
+
"description": "",
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
def _extract_mcp_python_tools(
|
|
497
|
+
self, tree: ast.AST, source_code: str
|
|
498
|
+
) -> dict[str, t.Any] | None:
|
|
499
|
+
"""Extract MCP tool information from Python files."""
|
|
500
|
+
tools = []
|
|
501
|
+
|
|
502
|
+
for node in ast.walk(tree):
|
|
503
|
+
if isinstance(node, ast.FunctionDef):
|
|
504
|
+
# Look for functions that might be MCP tools
|
|
505
|
+
func_info = self._extract_function_info(node, source_code)
|
|
506
|
+
if "mcp" in func_info["name"].lower() or any(
|
|
507
|
+
"tool" in dec.lower() for dec in func_info["decorators"]
|
|
508
|
+
):
|
|
509
|
+
tools.append(func_info)
|
|
510
|
+
|
|
511
|
+
return {"tools": tools} if tools else None
|
|
512
|
+
|
|
513
|
+
def _extract_mcp_markdown_docs(self, content: str) -> dict[str, t.Any]:
|
|
514
|
+
"""Extract MCP tool documentation from markdown files."""
|
|
515
|
+
# Simple extraction of sections and command examples
|
|
516
|
+
sections: list[dict[str, t.Any]] = []
|
|
517
|
+
current_section: dict[str, t.Any] | None = None
|
|
518
|
+
|
|
519
|
+
for line in content.split("\n"):
|
|
520
|
+
if line.startswith("#"):
|
|
521
|
+
if current_section:
|
|
522
|
+
sections.append(current_section)
|
|
523
|
+
current_section = {"title": line.strip("#").strip(), "content": []}
|
|
524
|
+
elif current_section:
|
|
525
|
+
current_section["content"].append(line)
|
|
526
|
+
|
|
527
|
+
if current_section:
|
|
528
|
+
sections.append(current_section)
|
|
529
|
+
|
|
530
|
+
return {"sections": sections}
|
|
531
|
+
|
|
532
|
+
def _is_protocol_class(self, node: ast.ClassDef) -> bool:
|
|
533
|
+
"""Check if a class is a Protocol definition."""
|
|
534
|
+
return "Protocol" in [self._get_node_name(base) for base in node.bases] or any(
|
|
535
|
+
self._get_node_name(decorator) == "runtime_checkable"
|
|
536
|
+
for decorator in node.decorator_list
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
def _get_node_name(self, node: ast.AST) -> str:
|
|
540
|
+
"""Get the name from an AST node."""
|
|
541
|
+
if isinstance(node, ast.Name):
|
|
542
|
+
return node.id
|
|
543
|
+
elif isinstance(node, ast.Attribute):
|
|
544
|
+
return f"{self._get_node_name(node.value)}.{node.attr}"
|
|
545
|
+
elif isinstance(node, ast.Constant):
|
|
546
|
+
return str(node.value)
|
|
547
|
+
return ""
|
|
548
|
+
|
|
549
|
+
def _get_annotation_string(self, annotation: ast.AST | None) -> str:
|
|
550
|
+
"""Convert an annotation AST node to a string representation."""
|
|
551
|
+
if annotation is None:
|
|
552
|
+
return ""
|
|
553
|
+
|
|
554
|
+
try:
|
|
555
|
+
return self._process_annotation_node(annotation)
|
|
556
|
+
except Exception:
|
|
557
|
+
return "Any"
|
|
558
|
+
|
|
559
|
+
def _process_annotation_node(self, annotation: ast.AST) -> str:
|
|
560
|
+
"""Process different types of annotation nodes."""
|
|
561
|
+
if isinstance(annotation, ast.Name):
|
|
562
|
+
return annotation.id
|
|
563
|
+
elif isinstance(annotation, ast.Attribute):
|
|
564
|
+
return self._process_attribute_annotation(annotation)
|
|
565
|
+
elif isinstance(annotation, ast.Subscript):
|
|
566
|
+
return self._process_subscript_annotation(annotation)
|
|
567
|
+
elif isinstance(annotation, ast.BinOp) and isinstance(annotation.op, ast.BitOr):
|
|
568
|
+
return self._process_union_annotation(annotation)
|
|
569
|
+
elif isinstance(annotation, ast.Constant):
|
|
570
|
+
return str(annotation.value)
|
|
571
|
+
elif isinstance(annotation, ast.Tuple):
|
|
572
|
+
return self._process_tuple_annotation(annotation)
|
|
573
|
+
return self._get_fallback_annotation(annotation)
|
|
574
|
+
|
|
575
|
+
def _process_attribute_annotation(self, annotation: ast.Attribute) -> str:
|
|
576
|
+
"""Process attribute-based annotation nodes."""
|
|
577
|
+
return f"{self._get_node_name(annotation.value)}.{annotation.attr}"
|
|
578
|
+
|
|
579
|
+
def _process_subscript_annotation(self, annotation: ast.Subscript) -> str:
|
|
580
|
+
"""Process subscript-based annotation nodes (e.g., List[str])."""
|
|
581
|
+
value = self._get_node_name(annotation.value)
|
|
582
|
+
slice_val = self._get_annotation_string(annotation.slice)
|
|
583
|
+
return f"{value}[{slice_val}]"
|
|
584
|
+
|
|
585
|
+
def _process_union_annotation(self, annotation: ast.BinOp) -> str:
|
|
586
|
+
"""Process Union types with | operator (Python 3.10+)."""
|
|
587
|
+
left = self._get_annotation_string(annotation.left)
|
|
588
|
+
right = self._get_annotation_string(annotation.right)
|
|
589
|
+
return f"{left} | {right}"
|
|
590
|
+
|
|
591
|
+
def _process_tuple_annotation(self, annotation: ast.Tuple) -> str:
|
|
592
|
+
"""Process tuple-based annotation nodes."""
|
|
593
|
+
elements = [self._get_annotation_string(elt) for elt in annotation.elts]
|
|
594
|
+
return f"({', '.join(elements)})"
|
|
595
|
+
|
|
596
|
+
def _get_fallback_annotation(self, annotation: ast.AST) -> str:
|
|
597
|
+
"""Get fallback annotation representation."""
|
|
598
|
+
return ast.unparse(annotation) if hasattr(ast, "unparse") else "Any"
|
|
599
|
+
|
|
600
|
+
def _extract_import_info(
|
|
601
|
+
self, node: ast.Import | ast.ImportFrom
|
|
602
|
+
) -> dict[str, t.Any]:
|
|
603
|
+
"""Extract import statement information."""
|
|
604
|
+
if isinstance(node, ast.Import):
|
|
605
|
+
return {
|
|
606
|
+
"type": "import",
|
|
607
|
+
"names": [alias.name for alias in node.names],
|
|
608
|
+
"from": None,
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
# ast.ImportFrom
|
|
612
|
+
return {
|
|
613
|
+
"type": "from_import",
|
|
614
|
+
"from": node.module,
|
|
615
|
+
"names": [alias.name for alias in node.names] if node.names else ["*"],
|
|
616
|
+
"level": node.level,
|
|
617
|
+
}
|