attune-ai 2.0.0__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.
- attune/__init__.py +358 -0
- attune/adaptive/__init__.py +13 -0
- attune/adaptive/task_complexity.py +127 -0
- attune/agent_monitoring.py +414 -0
- attune/cache/__init__.py +117 -0
- attune/cache/base.py +166 -0
- attune/cache/dependency_manager.py +256 -0
- attune/cache/hash_only.py +251 -0
- attune/cache/hybrid.py +457 -0
- attune/cache/storage.py +285 -0
- attune/cache_monitor.py +356 -0
- attune/cache_stats.py +298 -0
- attune/cli/__init__.py +152 -0
- attune/cli/__main__.py +12 -0
- attune/cli/commands/__init__.py +1 -0
- attune/cli/commands/batch.py +264 -0
- attune/cli/commands/cache.py +248 -0
- attune/cli/commands/help.py +331 -0
- attune/cli/commands/info.py +140 -0
- attune/cli/commands/inspect.py +436 -0
- attune/cli/commands/inspection.py +57 -0
- attune/cli/commands/memory.py +48 -0
- attune/cli/commands/metrics.py +92 -0
- attune/cli/commands/orchestrate.py +184 -0
- attune/cli/commands/patterns.py +207 -0
- attune/cli/commands/profiling.py +202 -0
- attune/cli/commands/provider.py +98 -0
- attune/cli/commands/routing.py +285 -0
- attune/cli/commands/setup.py +96 -0
- attune/cli/commands/status.py +235 -0
- attune/cli/commands/sync.py +166 -0
- attune/cli/commands/tier.py +121 -0
- attune/cli/commands/utilities.py +114 -0
- attune/cli/commands/workflow.py +579 -0
- attune/cli/core.py +32 -0
- attune/cli/parsers/__init__.py +68 -0
- attune/cli/parsers/batch.py +118 -0
- attune/cli/parsers/cache.py +65 -0
- attune/cli/parsers/help.py +41 -0
- attune/cli/parsers/info.py +26 -0
- attune/cli/parsers/inspect.py +66 -0
- attune/cli/parsers/metrics.py +42 -0
- attune/cli/parsers/orchestrate.py +61 -0
- attune/cli/parsers/patterns.py +54 -0
- attune/cli/parsers/provider.py +40 -0
- attune/cli/parsers/routing.py +110 -0
- attune/cli/parsers/setup.py +42 -0
- attune/cli/parsers/status.py +47 -0
- attune/cli/parsers/sync.py +31 -0
- attune/cli/parsers/tier.py +33 -0
- attune/cli/parsers/workflow.py +77 -0
- attune/cli/utils/__init__.py +1 -0
- attune/cli/utils/data.py +242 -0
- attune/cli/utils/helpers.py +68 -0
- attune/cli_legacy.py +3957 -0
- attune/cli_minimal.py +1159 -0
- attune/cli_router.py +437 -0
- attune/cli_unified.py +814 -0
- attune/config/__init__.py +66 -0
- attune/config/xml_config.py +286 -0
- attune/config.py +545 -0
- attune/coordination.py +870 -0
- attune/core.py +1511 -0
- attune/core_modules/__init__.py +15 -0
- attune/cost_tracker.py +626 -0
- attune/dashboard/__init__.py +41 -0
- attune/dashboard/app.py +512 -0
- attune/dashboard/simple_server.py +435 -0
- attune/dashboard/standalone_server.py +547 -0
- attune/discovery.py +306 -0
- attune/emergence.py +306 -0
- attune/exceptions.py +123 -0
- attune/feedback_loops.py +373 -0
- attune/hot_reload/README.md +473 -0
- attune/hot_reload/__init__.py +62 -0
- attune/hot_reload/config.py +83 -0
- attune/hot_reload/integration.py +229 -0
- attune/hot_reload/reloader.py +298 -0
- attune/hot_reload/watcher.py +183 -0
- attune/hot_reload/websocket.py +177 -0
- attune/levels.py +577 -0
- attune/leverage_points.py +441 -0
- attune/logging_config.py +261 -0
- attune/mcp/__init__.py +10 -0
- attune/mcp/server.py +506 -0
- attune/memory/__init__.py +237 -0
- attune/memory/claude_memory.py +469 -0
- attune/memory/config.py +224 -0
- attune/memory/control_panel.py +1290 -0
- attune/memory/control_panel_support.py +145 -0
- attune/memory/cross_session.py +845 -0
- attune/memory/edges.py +179 -0
- attune/memory/encryption.py +159 -0
- attune/memory/file_session.py +770 -0
- attune/memory/graph.py +570 -0
- attune/memory/long_term.py +913 -0
- attune/memory/long_term_types.py +99 -0
- attune/memory/mixins/__init__.py +25 -0
- attune/memory/mixins/backend_init_mixin.py +249 -0
- attune/memory/mixins/capabilities_mixin.py +208 -0
- attune/memory/mixins/handoff_mixin.py +208 -0
- attune/memory/mixins/lifecycle_mixin.py +49 -0
- attune/memory/mixins/long_term_mixin.py +352 -0
- attune/memory/mixins/promotion_mixin.py +109 -0
- attune/memory/mixins/short_term_mixin.py +182 -0
- attune/memory/nodes.py +179 -0
- attune/memory/redis_bootstrap.py +540 -0
- attune/memory/security/__init__.py +31 -0
- attune/memory/security/audit_logger.py +932 -0
- attune/memory/security/pii_scrubber.py +640 -0
- attune/memory/security/secrets_detector.py +678 -0
- attune/memory/short_term.py +2192 -0
- attune/memory/simple_storage.py +302 -0
- attune/memory/storage/__init__.py +15 -0
- attune/memory/storage_backend.py +167 -0
- attune/memory/summary_index.py +583 -0
- attune/memory/types.py +446 -0
- attune/memory/unified.py +182 -0
- attune/meta_workflows/__init__.py +74 -0
- attune/meta_workflows/agent_creator.py +248 -0
- attune/meta_workflows/builtin_templates.py +567 -0
- attune/meta_workflows/cli_commands/__init__.py +56 -0
- attune/meta_workflows/cli_commands/agent_commands.py +321 -0
- attune/meta_workflows/cli_commands/analytics_commands.py +442 -0
- attune/meta_workflows/cli_commands/config_commands.py +232 -0
- attune/meta_workflows/cli_commands/memory_commands.py +182 -0
- attune/meta_workflows/cli_commands/template_commands.py +354 -0
- attune/meta_workflows/cli_commands/workflow_commands.py +382 -0
- attune/meta_workflows/cli_meta_workflows.py +59 -0
- attune/meta_workflows/form_engine.py +292 -0
- attune/meta_workflows/intent_detector.py +409 -0
- attune/meta_workflows/models.py +569 -0
- attune/meta_workflows/pattern_learner.py +738 -0
- attune/meta_workflows/plan_generator.py +384 -0
- attune/meta_workflows/session_context.py +397 -0
- attune/meta_workflows/template_registry.py +229 -0
- attune/meta_workflows/workflow.py +984 -0
- attune/metrics/__init__.py +12 -0
- attune/metrics/collector.py +31 -0
- attune/metrics/prompt_metrics.py +194 -0
- attune/models/__init__.py +172 -0
- attune/models/__main__.py +13 -0
- attune/models/adaptive_routing.py +437 -0
- attune/models/auth_cli.py +444 -0
- attune/models/auth_strategy.py +450 -0
- attune/models/cli.py +655 -0
- attune/models/empathy_executor.py +354 -0
- attune/models/executor.py +257 -0
- attune/models/fallback.py +762 -0
- attune/models/provider_config.py +282 -0
- attune/models/registry.py +472 -0
- attune/models/tasks.py +359 -0
- attune/models/telemetry/__init__.py +71 -0
- attune/models/telemetry/analytics.py +594 -0
- attune/models/telemetry/backend.py +196 -0
- attune/models/telemetry/data_models.py +431 -0
- attune/models/telemetry/storage.py +489 -0
- attune/models/token_estimator.py +420 -0
- attune/models/validation.py +280 -0
- attune/monitoring/__init__.py +52 -0
- attune/monitoring/alerts.py +946 -0
- attune/monitoring/alerts_cli.py +448 -0
- attune/monitoring/multi_backend.py +271 -0
- attune/monitoring/otel_backend.py +362 -0
- attune/optimization/__init__.py +19 -0
- attune/optimization/context_optimizer.py +272 -0
- attune/orchestration/__init__.py +67 -0
- attune/orchestration/agent_templates.py +707 -0
- attune/orchestration/config_store.py +499 -0
- attune/orchestration/execution_strategies.py +2111 -0
- attune/orchestration/meta_orchestrator.py +1168 -0
- attune/orchestration/pattern_learner.py +696 -0
- attune/orchestration/real_tools.py +931 -0
- attune/pattern_cache.py +187 -0
- attune/pattern_library.py +542 -0
- attune/patterns/debugging/all_patterns.json +81 -0
- attune/patterns/debugging/workflow_20260107_1770825e.json +77 -0
- attune/patterns/refactoring_memory.json +89 -0
- attune/persistence.py +564 -0
- attune/platform_utils.py +265 -0
- attune/plugins/__init__.py +28 -0
- attune/plugins/base.py +361 -0
- attune/plugins/registry.py +268 -0
- attune/project_index/__init__.py +32 -0
- attune/project_index/cli.py +335 -0
- attune/project_index/index.py +667 -0
- attune/project_index/models.py +504 -0
- attune/project_index/reports.py +474 -0
- attune/project_index/scanner.py +777 -0
- attune/project_index/scanner_parallel.py +291 -0
- attune/prompts/__init__.py +61 -0
- attune/prompts/config.py +77 -0
- attune/prompts/context.py +177 -0
- attune/prompts/parser.py +285 -0
- attune/prompts/registry.py +313 -0
- attune/prompts/templates.py +208 -0
- attune/redis_config.py +302 -0
- attune/redis_memory.py +799 -0
- attune/resilience/__init__.py +56 -0
- attune/resilience/circuit_breaker.py +256 -0
- attune/resilience/fallback.py +179 -0
- attune/resilience/health.py +300 -0
- attune/resilience/retry.py +209 -0
- attune/resilience/timeout.py +135 -0
- attune/routing/__init__.py +43 -0
- attune/routing/chain_executor.py +433 -0
- attune/routing/classifier.py +217 -0
- attune/routing/smart_router.py +234 -0
- attune/routing/workflow_registry.py +343 -0
- attune/scaffolding/README.md +589 -0
- attune/scaffolding/__init__.py +35 -0
- attune/scaffolding/__main__.py +14 -0
- attune/scaffolding/cli.py +240 -0
- attune/scaffolding/templates/base_wizard.py.jinja2 +121 -0
- attune/scaffolding/templates/coach_wizard.py.jinja2 +321 -0
- attune/scaffolding/templates/domain_wizard.py.jinja2 +408 -0
- attune/scaffolding/templates/linear_flow_wizard.py.jinja2 +203 -0
- attune/socratic/__init__.py +256 -0
- attune/socratic/ab_testing.py +958 -0
- attune/socratic/blueprint.py +533 -0
- attune/socratic/cli.py +703 -0
- attune/socratic/collaboration.py +1114 -0
- attune/socratic/domain_templates.py +924 -0
- attune/socratic/embeddings.py +738 -0
- attune/socratic/engine.py +794 -0
- attune/socratic/explainer.py +682 -0
- attune/socratic/feedback.py +772 -0
- attune/socratic/forms.py +629 -0
- attune/socratic/generator.py +732 -0
- attune/socratic/llm_analyzer.py +637 -0
- attune/socratic/mcp_server.py +702 -0
- attune/socratic/session.py +312 -0
- attune/socratic/storage.py +667 -0
- attune/socratic/success.py +730 -0
- attune/socratic/visual_editor.py +860 -0
- attune/socratic/web_ui.py +958 -0
- attune/telemetry/__init__.py +39 -0
- attune/telemetry/agent_coordination.py +475 -0
- attune/telemetry/agent_tracking.py +367 -0
- attune/telemetry/approval_gates.py +545 -0
- attune/telemetry/cli.py +1231 -0
- attune/telemetry/commands/__init__.py +14 -0
- attune/telemetry/commands/dashboard_commands.py +696 -0
- attune/telemetry/event_streaming.py +409 -0
- attune/telemetry/feedback_loop.py +567 -0
- attune/telemetry/usage_tracker.py +591 -0
- attune/templates.py +754 -0
- attune/test_generator/__init__.py +38 -0
- attune/test_generator/__main__.py +14 -0
- attune/test_generator/cli.py +234 -0
- attune/test_generator/generator.py +355 -0
- attune/test_generator/risk_analyzer.py +216 -0
- attune/test_generator/templates/unit_test.py.jinja2 +272 -0
- attune/tier_recommender.py +384 -0
- attune/tools.py +183 -0
- attune/trust/__init__.py +28 -0
- attune/trust/circuit_breaker.py +579 -0
- attune/trust_building.py +527 -0
- attune/validation/__init__.py +19 -0
- attune/validation/xml_validator.py +281 -0
- attune/vscode_bridge.py +173 -0
- attune/workflow_commands.py +780 -0
- attune/workflow_patterns/__init__.py +33 -0
- attune/workflow_patterns/behavior.py +249 -0
- attune/workflow_patterns/core.py +76 -0
- attune/workflow_patterns/output.py +99 -0
- attune/workflow_patterns/registry.py +255 -0
- attune/workflow_patterns/structural.py +288 -0
- attune/workflows/__init__.py +539 -0
- attune/workflows/autonomous_test_gen.py +1268 -0
- attune/workflows/base.py +2667 -0
- attune/workflows/batch_processing.py +342 -0
- attune/workflows/bug_predict.py +1084 -0
- attune/workflows/builder.py +273 -0
- attune/workflows/caching.py +253 -0
- attune/workflows/code_review.py +1048 -0
- attune/workflows/code_review_adapters.py +312 -0
- attune/workflows/code_review_pipeline.py +722 -0
- attune/workflows/config.py +645 -0
- attune/workflows/dependency_check.py +644 -0
- attune/workflows/document_gen/__init__.py +25 -0
- attune/workflows/document_gen/config.py +30 -0
- attune/workflows/document_gen/report_formatter.py +162 -0
- attune/workflows/document_gen/workflow.py +1426 -0
- attune/workflows/document_manager.py +216 -0
- attune/workflows/document_manager_README.md +134 -0
- attune/workflows/documentation_orchestrator.py +1205 -0
- attune/workflows/history.py +510 -0
- attune/workflows/keyboard_shortcuts/__init__.py +39 -0
- attune/workflows/keyboard_shortcuts/generators.py +391 -0
- attune/workflows/keyboard_shortcuts/parsers.py +416 -0
- attune/workflows/keyboard_shortcuts/prompts.py +295 -0
- attune/workflows/keyboard_shortcuts/schema.py +193 -0
- attune/workflows/keyboard_shortcuts/workflow.py +509 -0
- attune/workflows/llm_base.py +363 -0
- attune/workflows/manage_docs.py +87 -0
- attune/workflows/manage_docs_README.md +134 -0
- attune/workflows/manage_documentation.py +821 -0
- attune/workflows/new_sample_workflow1.py +149 -0
- attune/workflows/new_sample_workflow1_README.md +150 -0
- attune/workflows/orchestrated_health_check.py +849 -0
- attune/workflows/orchestrated_release_prep.py +600 -0
- attune/workflows/output.py +413 -0
- attune/workflows/perf_audit.py +863 -0
- attune/workflows/pr_review.py +762 -0
- attune/workflows/progress.py +785 -0
- attune/workflows/progress_server.py +322 -0
- attune/workflows/progressive/README 2.md +454 -0
- attune/workflows/progressive/README.md +454 -0
- attune/workflows/progressive/__init__.py +82 -0
- attune/workflows/progressive/cli.py +219 -0
- attune/workflows/progressive/core.py +488 -0
- attune/workflows/progressive/orchestrator.py +723 -0
- attune/workflows/progressive/reports.py +520 -0
- attune/workflows/progressive/telemetry.py +274 -0
- attune/workflows/progressive/test_gen.py +495 -0
- attune/workflows/progressive/workflow.py +589 -0
- attune/workflows/refactor_plan.py +694 -0
- attune/workflows/release_prep.py +895 -0
- attune/workflows/release_prep_crew.py +969 -0
- attune/workflows/research_synthesis.py +404 -0
- attune/workflows/routing.py +168 -0
- attune/workflows/secure_release.py +593 -0
- attune/workflows/security_adapters.py +297 -0
- attune/workflows/security_audit.py +1329 -0
- attune/workflows/security_audit_phase3.py +355 -0
- attune/workflows/seo_optimization.py +633 -0
- attune/workflows/step_config.py +234 -0
- attune/workflows/telemetry_mixin.py +269 -0
- attune/workflows/test5.py +125 -0
- attune/workflows/test5_README.md +158 -0
- attune/workflows/test_coverage_boost_crew.py +849 -0
- attune/workflows/test_gen/__init__.py +52 -0
- attune/workflows/test_gen/ast_analyzer.py +249 -0
- attune/workflows/test_gen/config.py +88 -0
- attune/workflows/test_gen/data_models.py +38 -0
- attune/workflows/test_gen/report_formatter.py +289 -0
- attune/workflows/test_gen/test_templates.py +381 -0
- attune/workflows/test_gen/workflow.py +655 -0
- attune/workflows/test_gen.py +54 -0
- attune/workflows/test_gen_behavioral.py +477 -0
- attune/workflows/test_gen_parallel.py +341 -0
- attune/workflows/test_lifecycle.py +526 -0
- attune/workflows/test_maintenance.py +627 -0
- attune/workflows/test_maintenance_cli.py +590 -0
- attune/workflows/test_maintenance_crew.py +840 -0
- attune/workflows/test_runner.py +622 -0
- attune/workflows/tier_tracking.py +531 -0
- attune/workflows/xml_enhanced_crew.py +285 -0
- attune_ai-2.0.0.dist-info/METADATA +1026 -0
- attune_ai-2.0.0.dist-info/RECORD +457 -0
- attune_ai-2.0.0.dist-info/WHEEL +5 -0
- attune_ai-2.0.0.dist-info/entry_points.txt +26 -0
- attune_ai-2.0.0.dist-info/licenses/LICENSE +201 -0
- attune_ai-2.0.0.dist-info/licenses/LICENSE_CHANGE_ANNOUNCEMENT.md +101 -0
- attune_ai-2.0.0.dist-info/top_level.txt +5 -0
- attune_healthcare/__init__.py +13 -0
- attune_healthcare/monitors/__init__.py +9 -0
- attune_healthcare/monitors/clinical_protocol_monitor.py +315 -0
- attune_healthcare/monitors/monitoring/__init__.py +44 -0
- attune_healthcare/monitors/monitoring/protocol_checker.py +300 -0
- attune_healthcare/monitors/monitoring/protocol_loader.py +214 -0
- attune_healthcare/monitors/monitoring/sensor_parsers.py +306 -0
- attune_healthcare/monitors/monitoring/trajectory_analyzer.py +389 -0
- attune_llm/README.md +553 -0
- attune_llm/__init__.py +28 -0
- attune_llm/agent_factory/__init__.py +53 -0
- attune_llm/agent_factory/adapters/__init__.py +85 -0
- attune_llm/agent_factory/adapters/autogen_adapter.py +312 -0
- attune_llm/agent_factory/adapters/crewai_adapter.py +483 -0
- attune_llm/agent_factory/adapters/haystack_adapter.py +298 -0
- attune_llm/agent_factory/adapters/langchain_adapter.py +362 -0
- attune_llm/agent_factory/adapters/langgraph_adapter.py +333 -0
- attune_llm/agent_factory/adapters/native.py +228 -0
- attune_llm/agent_factory/adapters/wizard_adapter.py +423 -0
- attune_llm/agent_factory/base.py +305 -0
- attune_llm/agent_factory/crews/__init__.py +67 -0
- attune_llm/agent_factory/crews/code_review.py +1113 -0
- attune_llm/agent_factory/crews/health_check.py +1262 -0
- attune_llm/agent_factory/crews/refactoring.py +1128 -0
- attune_llm/agent_factory/crews/security_audit.py +1018 -0
- attune_llm/agent_factory/decorators.py +287 -0
- attune_llm/agent_factory/factory.py +558 -0
- attune_llm/agent_factory/framework.py +193 -0
- attune_llm/agent_factory/memory_integration.py +328 -0
- attune_llm/agent_factory/resilient.py +320 -0
- attune_llm/agents_md/__init__.py +22 -0
- attune_llm/agents_md/loader.py +218 -0
- attune_llm/agents_md/parser.py +271 -0
- attune_llm/agents_md/registry.py +307 -0
- attune_llm/claude_memory.py +466 -0
- attune_llm/cli/__init__.py +8 -0
- attune_llm/cli/sync_claude.py +487 -0
- attune_llm/code_health.py +1313 -0
- attune_llm/commands/__init__.py +51 -0
- attune_llm/commands/context.py +375 -0
- attune_llm/commands/loader.py +301 -0
- attune_llm/commands/models.py +231 -0
- attune_llm/commands/parser.py +371 -0
- attune_llm/commands/registry.py +429 -0
- attune_llm/config/__init__.py +29 -0
- attune_llm/config/unified.py +291 -0
- attune_llm/context/__init__.py +22 -0
- attune_llm/context/compaction.py +455 -0
- attune_llm/context/manager.py +434 -0
- attune_llm/contextual_patterns.py +361 -0
- attune_llm/core.py +907 -0
- attune_llm/git_pattern_extractor.py +435 -0
- attune_llm/hooks/__init__.py +24 -0
- attune_llm/hooks/config.py +306 -0
- attune_llm/hooks/executor.py +289 -0
- attune_llm/hooks/registry.py +302 -0
- attune_llm/hooks/scripts/__init__.py +39 -0
- attune_llm/hooks/scripts/evaluate_session.py +201 -0
- attune_llm/hooks/scripts/first_time_init.py +285 -0
- attune_llm/hooks/scripts/pre_compact.py +207 -0
- attune_llm/hooks/scripts/session_end.py +183 -0
- attune_llm/hooks/scripts/session_start.py +163 -0
- attune_llm/hooks/scripts/suggest_compact.py +225 -0
- attune_llm/learning/__init__.py +30 -0
- attune_llm/learning/evaluator.py +438 -0
- attune_llm/learning/extractor.py +514 -0
- attune_llm/learning/storage.py +560 -0
- attune_llm/levels.py +227 -0
- attune_llm/pattern_confidence.py +414 -0
- attune_llm/pattern_resolver.py +272 -0
- attune_llm/pattern_summary.py +350 -0
- attune_llm/providers.py +967 -0
- attune_llm/routing/__init__.py +32 -0
- attune_llm/routing/model_router.py +362 -0
- attune_llm/security/IMPLEMENTATION_SUMMARY.md +413 -0
- attune_llm/security/PHASE2_COMPLETE.md +384 -0
- attune_llm/security/PHASE2_SECRETS_DETECTOR_COMPLETE.md +271 -0
- attune_llm/security/QUICK_REFERENCE.md +316 -0
- attune_llm/security/README.md +262 -0
- attune_llm/security/__init__.py +62 -0
- attune_llm/security/audit_logger.py +929 -0
- attune_llm/security/audit_logger_example.py +152 -0
- attune_llm/security/pii_scrubber.py +640 -0
- attune_llm/security/secrets_detector.py +678 -0
- attune_llm/security/secrets_detector_example.py +304 -0
- attune_llm/security/secure_memdocs.py +1192 -0
- attune_llm/security/secure_memdocs_example.py +278 -0
- attune_llm/session_status.py +745 -0
- attune_llm/state.py +246 -0
- attune_llm/utils/__init__.py +5 -0
- attune_llm/utils/tokens.py +349 -0
- attune_software/SOFTWARE_PLUGIN_README.md +57 -0
- attune_software/__init__.py +13 -0
- attune_software/cli/__init__.py +120 -0
- attune_software/cli/inspect.py +362 -0
- attune_software/cli.py +574 -0
- attune_software/plugin.py +188 -0
- workflow_scaffolding/__init__.py +11 -0
- workflow_scaffolding/__main__.py +12 -0
- workflow_scaffolding/cli.py +206 -0
- workflow_scaffolding/generator.py +265 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
"""Empathy Framework - Plugin Registry
|
|
2
|
+
|
|
3
|
+
Auto-discovery and management of domain plugins.
|
|
4
|
+
|
|
5
|
+
Copyright 2025 Smart AI Memory, LLC
|
|
6
|
+
Licensed under Fair Source 0.9
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from importlib.metadata import entry_points
|
|
11
|
+
|
|
12
|
+
from .base import BasePlugin, BaseWorkflow, PluginValidationError
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PluginRegistry:
|
|
18
|
+
"""Central registry for managing domain plugins.
|
|
19
|
+
|
|
20
|
+
Features:
|
|
21
|
+
- Auto-discovery via entry points
|
|
22
|
+
- Manual registration
|
|
23
|
+
- Lazy initialization
|
|
24
|
+
- Graceful degradation (missing plugins don't crash)
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self):
|
|
28
|
+
self._plugins: dict[str, BasePlugin] = {}
|
|
29
|
+
self._auto_discovered = False
|
|
30
|
+
self.logger = logging.getLogger("empathy.plugins.registry")
|
|
31
|
+
|
|
32
|
+
def auto_discover(self) -> None:
|
|
33
|
+
"""Automatically discover plugins via entry points.
|
|
34
|
+
|
|
35
|
+
Plugins register themselves in setup.py/pyproject.toml:
|
|
36
|
+
|
|
37
|
+
[project.entry-points."empathy_framework.plugins"]
|
|
38
|
+
software = "empathy_software.plugin:SoftwarePlugin"
|
|
39
|
+
healthcare = "empathy_healthcare.plugin:HealthcarePlugin"
|
|
40
|
+
"""
|
|
41
|
+
if self._auto_discovered:
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
self.logger.info("Auto-discovering plugins...")
|
|
45
|
+
|
|
46
|
+
# Python 3.10+ has modern entry_points API with group parameter
|
|
47
|
+
discovered = entry_points(group="empathy_framework.plugins")
|
|
48
|
+
|
|
49
|
+
for ep in discovered:
|
|
50
|
+
try:
|
|
51
|
+
self.logger.info(f"Loading plugin '{ep.name}' from entry point")
|
|
52
|
+
plugin_class = ep.load()
|
|
53
|
+
plugin_instance = plugin_class()
|
|
54
|
+
self.register_plugin(ep.name, plugin_instance)
|
|
55
|
+
self.logger.info(f"Successfully loaded plugin: {ep.name}")
|
|
56
|
+
except Exception as e:
|
|
57
|
+
# Graceful degradation: log but don't crash
|
|
58
|
+
self.logger.warning(f"Failed to load plugin '{ep.name}': {e}", exc_info=True)
|
|
59
|
+
|
|
60
|
+
self._auto_discovered = True
|
|
61
|
+
self.logger.info(f"Auto-discovery complete. {len(self._plugins)} plugins loaded.")
|
|
62
|
+
|
|
63
|
+
def register_plugin(self, name: str, plugin: BasePlugin) -> None:
|
|
64
|
+
"""Manually register a plugin.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
name: Plugin identifier (e.g., 'software', 'healthcare')
|
|
68
|
+
plugin: Plugin instance
|
|
69
|
+
|
|
70
|
+
Raises:
|
|
71
|
+
PluginValidationError: If plugin is invalid
|
|
72
|
+
|
|
73
|
+
"""
|
|
74
|
+
# Validate plugin
|
|
75
|
+
try:
|
|
76
|
+
metadata = plugin.get_metadata()
|
|
77
|
+
if not metadata.name:
|
|
78
|
+
raise PluginValidationError("Plugin metadata missing 'name'")
|
|
79
|
+
if not metadata.domain:
|
|
80
|
+
raise PluginValidationError("Plugin metadata missing 'domain'")
|
|
81
|
+
except Exception as e:
|
|
82
|
+
raise PluginValidationError(f"Invalid plugin metadata: {e}") from e
|
|
83
|
+
|
|
84
|
+
# Register
|
|
85
|
+
self._plugins[name] = plugin
|
|
86
|
+
self.logger.info(
|
|
87
|
+
f"Registered plugin '{name}' (domain: {metadata.domain}, version: {metadata.version})",
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def get_plugin(self, name: str) -> BasePlugin | None:
|
|
91
|
+
"""Get a plugin by name.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
name: Plugin identifier
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Plugin instance or None if not found
|
|
98
|
+
|
|
99
|
+
"""
|
|
100
|
+
if not self._auto_discovered:
|
|
101
|
+
self.auto_discover()
|
|
102
|
+
|
|
103
|
+
plugin = self._plugins.get(name)
|
|
104
|
+
if plugin and not plugin._initialized:
|
|
105
|
+
plugin.initialize()
|
|
106
|
+
|
|
107
|
+
return plugin
|
|
108
|
+
|
|
109
|
+
def list_plugins(self) -> list[str]:
|
|
110
|
+
"""List all registered plugin names.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
List of plugin identifiers
|
|
114
|
+
|
|
115
|
+
"""
|
|
116
|
+
if not self._auto_discovered:
|
|
117
|
+
self.auto_discover()
|
|
118
|
+
|
|
119
|
+
return list(self._plugins.keys())
|
|
120
|
+
|
|
121
|
+
def list_all_workflows(self) -> dict[str, list[str]]:
|
|
122
|
+
"""List all workflows from all plugins.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
Dictionary mapping plugin_name -> list of workflow_ids
|
|
126
|
+
|
|
127
|
+
"""
|
|
128
|
+
if not self._auto_discovered:
|
|
129
|
+
self.auto_discover()
|
|
130
|
+
|
|
131
|
+
result = {}
|
|
132
|
+
for plugin_name, plugin in self._plugins.items():
|
|
133
|
+
result[plugin_name] = plugin.list_workflows()
|
|
134
|
+
|
|
135
|
+
return result
|
|
136
|
+
|
|
137
|
+
def get_workflow(self, plugin_name: str, workflow_id: str) -> type[BaseWorkflow] | None:
|
|
138
|
+
"""Get a workflow from a specific plugin.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
plugin_name: Plugin identifier
|
|
142
|
+
workflow_id: Workflow identifier within plugin
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Workflow class or None if not found
|
|
146
|
+
|
|
147
|
+
"""
|
|
148
|
+
plugin = self.get_plugin(plugin_name)
|
|
149
|
+
if not plugin:
|
|
150
|
+
self.logger.warning(f"Plugin '{plugin_name}' not found")
|
|
151
|
+
return None
|
|
152
|
+
|
|
153
|
+
return plugin.get_workflow(workflow_id)
|
|
154
|
+
|
|
155
|
+
def get_workflow_info(self, plugin_name: str, workflow_id: str) -> dict | None:
|
|
156
|
+
"""Get information about a workflow.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
plugin_name: Plugin identifier
|
|
160
|
+
workflow_id: Workflow identifier
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Dictionary with workflow metadata or None
|
|
164
|
+
|
|
165
|
+
"""
|
|
166
|
+
plugin = self.get_plugin(plugin_name)
|
|
167
|
+
if not plugin:
|
|
168
|
+
return None
|
|
169
|
+
|
|
170
|
+
return plugin.get_workflow_info(workflow_id)
|
|
171
|
+
|
|
172
|
+
def find_workflows_by_level(self, empathy_level: int) -> list[dict]:
|
|
173
|
+
"""Find all workflows operating at a specific empathy level.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
empathy_level: Target empathy level (1-5)
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
List of workflow info dictionaries
|
|
180
|
+
|
|
181
|
+
"""
|
|
182
|
+
if not self._auto_discovered:
|
|
183
|
+
self.auto_discover()
|
|
184
|
+
|
|
185
|
+
results = []
|
|
186
|
+
for plugin_name, plugin in self._plugins.items():
|
|
187
|
+
for workflow_id in plugin.list_workflows():
|
|
188
|
+
info = plugin.get_workflow_info(workflow_id)
|
|
189
|
+
if info and info.get("empathy_level") == empathy_level:
|
|
190
|
+
info["plugin"] = plugin_name
|
|
191
|
+
results.append(info)
|
|
192
|
+
|
|
193
|
+
return results
|
|
194
|
+
|
|
195
|
+
def find_workflows_by_domain(self, domain: str) -> list[dict]:
|
|
196
|
+
"""Find all workflows for a specific domain.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
domain: Domain identifier (e.g., 'software', 'healthcare')
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
List of workflow info dictionaries
|
|
203
|
+
|
|
204
|
+
"""
|
|
205
|
+
if not self._auto_discovered:
|
|
206
|
+
self.auto_discover()
|
|
207
|
+
|
|
208
|
+
results = []
|
|
209
|
+
for plugin_name, plugin in self._plugins.items():
|
|
210
|
+
metadata = plugin.get_metadata()
|
|
211
|
+
if metadata.domain == domain:
|
|
212
|
+
for workflow_id in plugin.list_workflows():
|
|
213
|
+
info = plugin.get_workflow_info(workflow_id)
|
|
214
|
+
if info:
|
|
215
|
+
info["plugin"] = plugin_name
|
|
216
|
+
results.append(info)
|
|
217
|
+
|
|
218
|
+
return results
|
|
219
|
+
|
|
220
|
+
def get_statistics(self) -> dict:
|
|
221
|
+
"""Get registry statistics.
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
Dictionary with counts and metadata
|
|
225
|
+
|
|
226
|
+
"""
|
|
227
|
+
if not self._auto_discovered:
|
|
228
|
+
self.auto_discover()
|
|
229
|
+
|
|
230
|
+
total_workflows = sum(len(plugin.list_workflows()) for plugin in self._plugins.values())
|
|
231
|
+
|
|
232
|
+
# Count workflows by level
|
|
233
|
+
workflows_by_level = {}
|
|
234
|
+
for level in range(1, 6):
|
|
235
|
+
workflows_by_level[f"level_{level}"] = len(self.find_workflows_by_level(level))
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
"total_plugins": len(self._plugins),
|
|
239
|
+
"total_workflows": total_workflows,
|
|
240
|
+
"plugins": [
|
|
241
|
+
{
|
|
242
|
+
"name": name,
|
|
243
|
+
"domain": plugin.get_metadata().domain,
|
|
244
|
+
"version": plugin.get_metadata().version,
|
|
245
|
+
"workflow_count": len(plugin.list_workflows()),
|
|
246
|
+
}
|
|
247
|
+
for name, plugin in self._plugins.items()
|
|
248
|
+
],
|
|
249
|
+
"workflows_by_level": workflows_by_level,
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
# Global registry instance
|
|
254
|
+
_global_registry: PluginRegistry | None = None
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def get_global_registry() -> PluginRegistry:
|
|
258
|
+
"""Get the global plugin registry instance (singleton).
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
Global PluginRegistry instance
|
|
262
|
+
|
|
263
|
+
"""
|
|
264
|
+
global _global_registry
|
|
265
|
+
if _global_registry is None:
|
|
266
|
+
_global_registry = PluginRegistry()
|
|
267
|
+
_global_registry.auto_discover()
|
|
268
|
+
return _global_registry
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Project Index - Codebase Intelligence Layer
|
|
2
|
+
|
|
3
|
+
Tracks metadata about all files in a project to enable:
|
|
4
|
+
- Test coverage gap analysis
|
|
5
|
+
- Staleness detection (code changed, tests didn't)
|
|
6
|
+
- Dependency mapping
|
|
7
|
+
- Project health reports
|
|
8
|
+
- Workflow intelligence
|
|
9
|
+
|
|
10
|
+
Storage:
|
|
11
|
+
- Primary: .attune/project_index.json
|
|
12
|
+
- Real-time: Redis (when short-term memory enabled)
|
|
13
|
+
|
|
14
|
+
Copyright 2025 Smart AI Memory, LLC
|
|
15
|
+
Licensed under Fair Source 0.9
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from .index import ProjectIndex
|
|
19
|
+
from .models import FileRecord, IndexConfig, ProjectSummary
|
|
20
|
+
from .reports import ReportGenerator
|
|
21
|
+
from .scanner import ProjectScanner
|
|
22
|
+
from .scanner_parallel import ParallelProjectScanner
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"FileRecord",
|
|
26
|
+
"IndexConfig",
|
|
27
|
+
"ParallelProjectScanner",
|
|
28
|
+
"ProjectIndex",
|
|
29
|
+
"ProjectScanner",
|
|
30
|
+
"ProjectSummary",
|
|
31
|
+
"ReportGenerator",
|
|
32
|
+
]
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
"""CLI for Project Index
|
|
2
|
+
|
|
3
|
+
Commands for generating, querying, and reporting on the project index.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
python -m attune.project_index.cli refresh
|
|
7
|
+
python -m attune.project_index.cli report health
|
|
8
|
+
python -m attune.project_index.cli query needing_tests
|
|
9
|
+
python -m attune.project_index.cli summary
|
|
10
|
+
|
|
11
|
+
Copyright 2025 Smart AI Memory, LLC
|
|
12
|
+
Licensed under Fair Source 0.9
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import json
|
|
17
|
+
import sys
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
from .index import ProjectIndex
|
|
21
|
+
from .reports import ReportGenerator
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def main() -> int:
|
|
25
|
+
"""Main CLI entry point."""
|
|
26
|
+
parser = argparse.ArgumentParser(
|
|
27
|
+
description="Project Index - Codebase Intelligence",
|
|
28
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
29
|
+
epilog="""
|
|
30
|
+
Examples:
|
|
31
|
+
# Generate/refresh the index
|
|
32
|
+
python -m attune.project_index.cli refresh
|
|
33
|
+
|
|
34
|
+
# View project summary
|
|
35
|
+
python -m attune.project_index.cli summary
|
|
36
|
+
|
|
37
|
+
# Generate health report
|
|
38
|
+
python -m attune.project_index.cli report health
|
|
39
|
+
|
|
40
|
+
# Find files needing tests
|
|
41
|
+
python -m attune.project_index.cli query needing_tests
|
|
42
|
+
|
|
43
|
+
# Find stale test files
|
|
44
|
+
python -m attune.project_index.cli query stale
|
|
45
|
+
""",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
parser.add_argument(
|
|
49
|
+
"--project",
|
|
50
|
+
"-p",
|
|
51
|
+
default=".",
|
|
52
|
+
help="Project root directory (default: current directory)",
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
parser.add_argument(
|
|
56
|
+
"--json",
|
|
57
|
+
"-j",
|
|
58
|
+
action="store_true",
|
|
59
|
+
help="Output in JSON format",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
subparsers = parser.add_subparsers(dest="command", help="Command to run")
|
|
63
|
+
|
|
64
|
+
# refresh command
|
|
65
|
+
refresh_parser = subparsers.add_parser("refresh", help="Refresh the project index")
|
|
66
|
+
refresh_parser.add_argument(
|
|
67
|
+
"--force",
|
|
68
|
+
"-f",
|
|
69
|
+
action="store_true",
|
|
70
|
+
help="Force full re-scan even if index exists",
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# summary command
|
|
74
|
+
subparsers.add_parser("summary", help="Show project summary")
|
|
75
|
+
|
|
76
|
+
# report command
|
|
77
|
+
report_parser = subparsers.add_parser("report", help="Generate a report")
|
|
78
|
+
report_parser.add_argument(
|
|
79
|
+
"report_type",
|
|
80
|
+
choices=["health", "test_gap", "staleness", "coverage", "sprint"],
|
|
81
|
+
help="Type of report to generate",
|
|
82
|
+
)
|
|
83
|
+
report_parser.add_argument(
|
|
84
|
+
"--markdown",
|
|
85
|
+
"-m",
|
|
86
|
+
action="store_true",
|
|
87
|
+
help="Output in markdown format",
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# query command
|
|
91
|
+
query_parser = subparsers.add_parser("query", help="Query the index")
|
|
92
|
+
query_parser.add_argument(
|
|
93
|
+
"query_type",
|
|
94
|
+
choices=["needing_tests", "stale", "high_impact", "attention", "all"],
|
|
95
|
+
help="Type of query",
|
|
96
|
+
)
|
|
97
|
+
query_parser.add_argument(
|
|
98
|
+
"--limit",
|
|
99
|
+
"-l",
|
|
100
|
+
type=int,
|
|
101
|
+
default=20,
|
|
102
|
+
help="Maximum number of results",
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# file command
|
|
106
|
+
file_parser = subparsers.add_parser("file", help="Get info about a specific file")
|
|
107
|
+
file_parser.add_argument("path", help="Path to the file")
|
|
108
|
+
|
|
109
|
+
args = parser.parse_args()
|
|
110
|
+
|
|
111
|
+
if not args.command:
|
|
112
|
+
parser.print_help()
|
|
113
|
+
return 1
|
|
114
|
+
|
|
115
|
+
# Initialize index
|
|
116
|
+
project_root = Path(args.project).resolve()
|
|
117
|
+
index = ProjectIndex(str(project_root))
|
|
118
|
+
|
|
119
|
+
# Execute command
|
|
120
|
+
if args.command == "refresh":
|
|
121
|
+
return cmd_refresh(index, args)
|
|
122
|
+
if args.command == "summary":
|
|
123
|
+
return cmd_summary(index, args)
|
|
124
|
+
if args.command == "report":
|
|
125
|
+
return cmd_report(index, args)
|
|
126
|
+
if args.command == "query":
|
|
127
|
+
return cmd_query(index, args)
|
|
128
|
+
if args.command == "file":
|
|
129
|
+
return cmd_file(index, args)
|
|
130
|
+
|
|
131
|
+
return 0
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def cmd_refresh(index: ProjectIndex, args: argparse.Namespace) -> int:
|
|
135
|
+
"""Refresh the index."""
|
|
136
|
+
print(f"Refreshing index for: {index.project_root}")
|
|
137
|
+
print()
|
|
138
|
+
|
|
139
|
+
index.refresh()
|
|
140
|
+
|
|
141
|
+
summary = index.get_summary()
|
|
142
|
+
|
|
143
|
+
print("Index refreshed successfully!")
|
|
144
|
+
print(f" Files indexed: {summary.total_files}")
|
|
145
|
+
print(f" Source files: {summary.source_files}")
|
|
146
|
+
print(f" Test files: {summary.test_files}")
|
|
147
|
+
print(f" Files needing tests: {summary.files_without_tests}")
|
|
148
|
+
print(f" Files needing attention: {summary.files_needing_attention}")
|
|
149
|
+
print()
|
|
150
|
+
print(f"Index saved to: {index._index_path}")
|
|
151
|
+
|
|
152
|
+
return 0
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def cmd_summary(index: ProjectIndex, args: argparse.Namespace) -> int:
|
|
156
|
+
"""Show project summary."""
|
|
157
|
+
if not index.load():
|
|
158
|
+
print("No index found. Run 'refresh' first.")
|
|
159
|
+
return 1
|
|
160
|
+
|
|
161
|
+
summary = index.get_summary()
|
|
162
|
+
|
|
163
|
+
if args.json:
|
|
164
|
+
print(json.dumps(summary.to_dict(), indent=2))
|
|
165
|
+
return 0
|
|
166
|
+
|
|
167
|
+
print("=" * 60)
|
|
168
|
+
print("PROJECT INDEX SUMMARY")
|
|
169
|
+
print("=" * 60)
|
|
170
|
+
print()
|
|
171
|
+
print("FILE COUNTS")
|
|
172
|
+
print(f" Total files: {summary.total_files}")
|
|
173
|
+
print(f" Source files: {summary.source_files}")
|
|
174
|
+
print(f" Test files: {summary.test_files}")
|
|
175
|
+
print(f" Config files: {summary.config_files}")
|
|
176
|
+
print(f" Doc files: {summary.doc_files}")
|
|
177
|
+
print()
|
|
178
|
+
print("TEST HEALTH")
|
|
179
|
+
print(f" Files requiring tests: {summary.files_requiring_tests}")
|
|
180
|
+
print(f" Files with tests: {summary.files_with_tests}")
|
|
181
|
+
print(f" Files WITHOUT tests: {summary.files_without_tests}")
|
|
182
|
+
print(f" Average coverage: {summary.test_coverage_avg:.1f}%")
|
|
183
|
+
print(f" Test-to-code ratio: {summary.test_to_code_ratio:.2f}")
|
|
184
|
+
print()
|
|
185
|
+
print("ATTENTION NEEDED")
|
|
186
|
+
print(f" Stale test count: {summary.stale_file_count}")
|
|
187
|
+
print(f" Files need attention: {summary.files_needing_attention}")
|
|
188
|
+
print()
|
|
189
|
+
|
|
190
|
+
if summary.critical_untested_files:
|
|
191
|
+
print("CRITICAL UNTESTED FILES (high impact)")
|
|
192
|
+
for f in summary.critical_untested_files[:5]:
|
|
193
|
+
print(f" - {f}")
|
|
194
|
+
print()
|
|
195
|
+
|
|
196
|
+
return 0
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def cmd_report(index: ProjectIndex, args: argparse.Namespace) -> int:
|
|
200
|
+
"""Generate a report."""
|
|
201
|
+
if not index.load():
|
|
202
|
+
print("No index found. Run 'refresh' first.")
|
|
203
|
+
return 1
|
|
204
|
+
|
|
205
|
+
generator = ReportGenerator(index.get_summary(), index.get_all_files())
|
|
206
|
+
|
|
207
|
+
report_type = args.report_type
|
|
208
|
+
if report_type == "sprint":
|
|
209
|
+
report_type = "sprint_planning"
|
|
210
|
+
|
|
211
|
+
# Generate report
|
|
212
|
+
if args.markdown:
|
|
213
|
+
print(generator.to_markdown(report_type))
|
|
214
|
+
elif args.json:
|
|
215
|
+
if report_type == "health":
|
|
216
|
+
print(json.dumps(generator.health_report(), indent=2))
|
|
217
|
+
elif report_type == "test_gap":
|
|
218
|
+
print(json.dumps(generator.test_gap_report(), indent=2))
|
|
219
|
+
elif report_type == "staleness":
|
|
220
|
+
print(json.dumps(generator.staleness_report(), indent=2))
|
|
221
|
+
elif report_type == "coverage":
|
|
222
|
+
print(json.dumps(generator.coverage_report(), indent=2))
|
|
223
|
+
elif report_type == "sprint_planning":
|
|
224
|
+
print(json.dumps(generator.sprint_planning_report(), indent=2))
|
|
225
|
+
else:
|
|
226
|
+
# Human-readable format
|
|
227
|
+
print(generator.to_markdown(report_type))
|
|
228
|
+
|
|
229
|
+
return 0
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def cmd_query(index: ProjectIndex, args: argparse.Namespace) -> int:
|
|
233
|
+
"""Query the index."""
|
|
234
|
+
if not index.load():
|
|
235
|
+
print("No index found. Run 'refresh' first.")
|
|
236
|
+
return 1
|
|
237
|
+
|
|
238
|
+
query_type = args.query_type
|
|
239
|
+
limit = args.limit
|
|
240
|
+
|
|
241
|
+
# Execute query
|
|
242
|
+
if query_type == "needing_tests":
|
|
243
|
+
files = index.get_files_needing_tests()[:limit]
|
|
244
|
+
title = "FILES NEEDING TESTS"
|
|
245
|
+
elif query_type == "stale":
|
|
246
|
+
files = index.get_stale_files()[:limit]
|
|
247
|
+
title = "STALE TEST FILES"
|
|
248
|
+
elif query_type == "high_impact":
|
|
249
|
+
files = index.get_high_impact_files()[:limit]
|
|
250
|
+
title = "HIGH IMPACT FILES"
|
|
251
|
+
elif query_type == "attention":
|
|
252
|
+
files = index.get_files_needing_attention()[:limit]
|
|
253
|
+
title = "FILES NEEDING ATTENTION"
|
|
254
|
+
elif query_type == "all":
|
|
255
|
+
files = index.get_all_files()[:limit]
|
|
256
|
+
title = "ALL FILES"
|
|
257
|
+
else:
|
|
258
|
+
files = []
|
|
259
|
+
title = "RESULTS"
|
|
260
|
+
|
|
261
|
+
if args.json:
|
|
262
|
+
print(json.dumps([f.to_dict() for f in files], indent=2))
|
|
263
|
+
return 0
|
|
264
|
+
|
|
265
|
+
print(f"\n{title} ({len(files)} results)")
|
|
266
|
+
print("=" * 60)
|
|
267
|
+
|
|
268
|
+
for f in files:
|
|
269
|
+
print(f"\n{f.path}")
|
|
270
|
+
print(f" Category: {f.category.value}")
|
|
271
|
+
print(f" Impact Score: {f.impact_score:.1f}")
|
|
272
|
+
print(f" Has Tests: {f.tests_exist}")
|
|
273
|
+
print(f" Coverage: {f.coverage_percent:.1f}%")
|
|
274
|
+
if f.is_stale:
|
|
275
|
+
print(f" STALE: {f.staleness_days} days")
|
|
276
|
+
if f.attention_reasons:
|
|
277
|
+
print(f" Attention: {', '.join(f.attention_reasons)}")
|
|
278
|
+
|
|
279
|
+
print()
|
|
280
|
+
return 0
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def cmd_file(index: ProjectIndex, args: argparse.Namespace) -> int:
|
|
284
|
+
"""Get info about a specific file."""
|
|
285
|
+
if not index.load():
|
|
286
|
+
print("No index found. Run 'refresh' first.")
|
|
287
|
+
return 1
|
|
288
|
+
|
|
289
|
+
record = index.get_file(args.path)
|
|
290
|
+
|
|
291
|
+
if not record:
|
|
292
|
+
print(f"File not found in index: {args.path}")
|
|
293
|
+
return 1
|
|
294
|
+
|
|
295
|
+
if args.json:
|
|
296
|
+
print(json.dumps(record.to_dict(), indent=2))
|
|
297
|
+
return 0
|
|
298
|
+
|
|
299
|
+
print(f"\nFILE: {record.path}")
|
|
300
|
+
print("=" * 60)
|
|
301
|
+
print(f" Name: {record.name}")
|
|
302
|
+
print(f" Category: {record.category.value}")
|
|
303
|
+
print(f" Language: {record.language}")
|
|
304
|
+
print()
|
|
305
|
+
print("TESTING")
|
|
306
|
+
print(f" Requires Tests: {record.test_requirement.value}")
|
|
307
|
+
print(f" Has Tests: {record.tests_exist}")
|
|
308
|
+
print(f" Test File: {record.test_file_path or 'None'}")
|
|
309
|
+
print(f" Coverage: {record.coverage_percent:.1f}%")
|
|
310
|
+
print(f" Test Count: {record.test_count}")
|
|
311
|
+
print()
|
|
312
|
+
print("METRICS")
|
|
313
|
+
print(f" Lines of Code: {record.lines_of_code}")
|
|
314
|
+
print(f" Complexity: {record.complexity_score:.1f}")
|
|
315
|
+
print(f" Has Docstrings: {record.has_docstrings}")
|
|
316
|
+
print(f" Has Type Hints: {record.has_type_hints}")
|
|
317
|
+
print()
|
|
318
|
+
print("DEPENDENCIES")
|
|
319
|
+
print(f" Imports: {record.import_count} modules")
|
|
320
|
+
print(f" Imported By: {record.imported_by_count} files")
|
|
321
|
+
print(f" Impact Score: {record.impact_score:.1f}")
|
|
322
|
+
print()
|
|
323
|
+
print("STATUS")
|
|
324
|
+
print(f" Needs Attention: {record.needs_attention}")
|
|
325
|
+
if record.attention_reasons:
|
|
326
|
+
print(f" Reasons: {', '.join(record.attention_reasons)}")
|
|
327
|
+
if record.is_stale:
|
|
328
|
+
print(f" STALE: {record.staleness_days} days")
|
|
329
|
+
print()
|
|
330
|
+
|
|
331
|
+
return 0
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
if __name__ == "__main__":
|
|
335
|
+
sys.exit(main())
|