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,416 @@
|
|
|
1
|
+
"""Feature discovery parsers for keyboard shortcut generation.
|
|
2
|
+
|
|
3
|
+
Supports multiple input sources:
|
|
4
|
+
- VSCode extension package.json
|
|
5
|
+
- Python pyproject.toml entry points
|
|
6
|
+
- Custom features.yaml manifest
|
|
7
|
+
- LLM-based code analysis
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
from abc import ABC, abstractmethod
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
import yaml
|
|
15
|
+
|
|
16
|
+
from .schema import Category, Feature, FeatureManifest, FrequencyTier
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class FeatureParser(ABC):
|
|
20
|
+
"""Base class for feature parsers."""
|
|
21
|
+
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def parse(self, path: Path) -> list[Feature]:
|
|
24
|
+
"""Extract features from source file."""
|
|
25
|
+
|
|
26
|
+
@abstractmethod
|
|
27
|
+
def can_parse(self, path: Path) -> bool:
|
|
28
|
+
"""Check if this parser can handle the given path."""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class VSCodeCommandParser(FeatureParser):
|
|
32
|
+
"""Parse VSCode extension package.json for commands."""
|
|
33
|
+
|
|
34
|
+
def can_parse(self, path: Path) -> bool:
|
|
35
|
+
return path.name == "package.json" and path.exists()
|
|
36
|
+
|
|
37
|
+
def parse(self, path: Path) -> list[Feature]:
|
|
38
|
+
"""Extract commands from VSCode package.json."""
|
|
39
|
+
try:
|
|
40
|
+
pkg = json.loads(path.read_text())
|
|
41
|
+
except (json.JSONDecodeError, OSError):
|
|
42
|
+
return []
|
|
43
|
+
|
|
44
|
+
commands = pkg.get("contributes", {}).get("commands", [])
|
|
45
|
+
features = []
|
|
46
|
+
|
|
47
|
+
for cmd in commands:
|
|
48
|
+
command_id = cmd.get("command", "")
|
|
49
|
+
title = cmd.get("title", "")
|
|
50
|
+
|
|
51
|
+
# Skip internal/hidden commands
|
|
52
|
+
if not title or command_id.startswith("_"):
|
|
53
|
+
continue
|
|
54
|
+
|
|
55
|
+
# Extract category from title (e.g., "Empathy: Quick → Morning")
|
|
56
|
+
category = self._extract_category(title)
|
|
57
|
+
name = self._extract_name(title)
|
|
58
|
+
|
|
59
|
+
# Determine frequency tier based on category
|
|
60
|
+
tier = self._infer_tier(category)
|
|
61
|
+
|
|
62
|
+
features.append(
|
|
63
|
+
Feature(
|
|
64
|
+
id=command_id.split(".")[-1],
|
|
65
|
+
name=name,
|
|
66
|
+
description=f"VSCode command: {command_id}",
|
|
67
|
+
command=command_id,
|
|
68
|
+
icon=cmd.get("icon", "$(symbol-misc)"),
|
|
69
|
+
frequency=tier,
|
|
70
|
+
),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
return features
|
|
74
|
+
|
|
75
|
+
def _extract_category(self, title: str) -> str:
|
|
76
|
+
"""Extract category from title like 'Empathy: Quick → Morning'."""
|
|
77
|
+
if "→" in title:
|
|
78
|
+
parts = title.split("→")
|
|
79
|
+
if len(parts) >= 2:
|
|
80
|
+
# Get the part before the arrow
|
|
81
|
+
prefix = parts[0].strip()
|
|
82
|
+
if ":" in prefix:
|
|
83
|
+
return prefix.split(":")[-1].strip()
|
|
84
|
+
return prefix
|
|
85
|
+
if ":" in title:
|
|
86
|
+
return title.split(":")[0].strip()
|
|
87
|
+
return "General"
|
|
88
|
+
|
|
89
|
+
def _extract_name(self, title: str) -> str:
|
|
90
|
+
"""Extract feature name from title like 'Empathy: Quick → Morning'."""
|
|
91
|
+
if "→" in title:
|
|
92
|
+
return title.split("→")[-1].strip()
|
|
93
|
+
if ":" in title:
|
|
94
|
+
return title.split(":")[-1].strip()
|
|
95
|
+
return title.strip()
|
|
96
|
+
|
|
97
|
+
def _infer_tier(self, category: str) -> FrequencyTier:
|
|
98
|
+
"""Infer frequency tier from category name."""
|
|
99
|
+
category_lower = category.lower()
|
|
100
|
+
if "quick" in category_lower or "daily" in category_lower:
|
|
101
|
+
return FrequencyTier.DAILY
|
|
102
|
+
if "view" in category_lower or "workflow" in category_lower:
|
|
103
|
+
return FrequencyTier.FREQUENT
|
|
104
|
+
return FrequencyTier.ADVANCED
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class PyProjectParser(FeatureParser):
|
|
108
|
+
"""Parse Python pyproject.toml for CLI entry points."""
|
|
109
|
+
|
|
110
|
+
def can_parse(self, path: Path) -> bool:
|
|
111
|
+
return path.name == "pyproject.toml" and path.exists()
|
|
112
|
+
|
|
113
|
+
def parse(self, path: Path) -> list[Feature]:
|
|
114
|
+
"""Extract CLI scripts from pyproject.toml."""
|
|
115
|
+
try:
|
|
116
|
+
# Python 3.11+ has tomllib built-in
|
|
117
|
+
import tomllib
|
|
118
|
+
|
|
119
|
+
data = tomllib.loads(path.read_text())
|
|
120
|
+
except ImportError:
|
|
121
|
+
# Fallback for older Python
|
|
122
|
+
try:
|
|
123
|
+
import tomli as tomllib
|
|
124
|
+
|
|
125
|
+
data = tomllib.loads(path.read_text())
|
|
126
|
+
except ImportError:
|
|
127
|
+
return []
|
|
128
|
+
except (OSError, Exception):
|
|
129
|
+
return []
|
|
130
|
+
except (OSError, Exception):
|
|
131
|
+
return []
|
|
132
|
+
|
|
133
|
+
features = []
|
|
134
|
+
|
|
135
|
+
# Parse [project.scripts] section
|
|
136
|
+
scripts = data.get("project", {}).get("scripts", {})
|
|
137
|
+
for name, _entry_point in scripts.items():
|
|
138
|
+
features.append(
|
|
139
|
+
Feature(
|
|
140
|
+
id=name.replace("-", "_"),
|
|
141
|
+
name=name.replace("-", " ").title(),
|
|
142
|
+
description=f"CLI command: {name}",
|
|
143
|
+
command=f"cli.{name}",
|
|
144
|
+
cli_alias=name,
|
|
145
|
+
frequency=FrequencyTier.FREQUENT,
|
|
146
|
+
),
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# Parse [project.entry-points] section
|
|
150
|
+
entry_points = data.get("project", {}).get("entry-points", {})
|
|
151
|
+
for group, entries in entry_points.items():
|
|
152
|
+
if isinstance(entries, dict):
|
|
153
|
+
for name, entry_point in entries.items():
|
|
154
|
+
features.append(
|
|
155
|
+
Feature(
|
|
156
|
+
id=f"{group}_{name}".replace("-", "_"),
|
|
157
|
+
name=name.replace("-", " ").title(),
|
|
158
|
+
description=f"Entry point: {group}.{name}",
|
|
159
|
+
command=entry_point,
|
|
160
|
+
frequency=FrequencyTier.ADVANCED,
|
|
161
|
+
),
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
return features
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class YAMLManifestParser(FeatureParser):
|
|
168
|
+
"""Parse custom features.yaml manifest."""
|
|
169
|
+
|
|
170
|
+
def can_parse(self, path: Path) -> bool:
|
|
171
|
+
return path.name in ("features.yaml", "features.yml") and path.exists()
|
|
172
|
+
|
|
173
|
+
def parse(self, path: Path) -> list[Feature]:
|
|
174
|
+
"""Extract features from custom YAML manifest."""
|
|
175
|
+
try:
|
|
176
|
+
manifest = yaml.safe_load(path.read_text())
|
|
177
|
+
except (yaml.YAMLError, OSError):
|
|
178
|
+
return []
|
|
179
|
+
|
|
180
|
+
if not manifest or not isinstance(manifest, dict):
|
|
181
|
+
return []
|
|
182
|
+
|
|
183
|
+
features = []
|
|
184
|
+
categories = manifest.get("categories", [])
|
|
185
|
+
|
|
186
|
+
for category in categories:
|
|
187
|
+
if not isinstance(category, dict):
|
|
188
|
+
continue
|
|
189
|
+
|
|
190
|
+
category.get("name", "General")
|
|
191
|
+
tier_str = category.get("tier", "frequent")
|
|
192
|
+
tier = (
|
|
193
|
+
FrequencyTier(tier_str)
|
|
194
|
+
if tier_str in FrequencyTier._value2member_map_
|
|
195
|
+
else FrequencyTier.FREQUENT
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
for feat in category.get("features", []):
|
|
199
|
+
if not isinstance(feat, dict):
|
|
200
|
+
continue
|
|
201
|
+
|
|
202
|
+
freq_str = feat.get("frequency", tier_str)
|
|
203
|
+
freq = (
|
|
204
|
+
FrequencyTier(freq_str)
|
|
205
|
+
if freq_str in FrequencyTier._value2member_map_
|
|
206
|
+
else tier
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
features.append(
|
|
210
|
+
Feature(
|
|
211
|
+
id=feat.get("id", feat.get("name", "").lower().replace(" ", "_")),
|
|
212
|
+
name=feat.get("name", ""),
|
|
213
|
+
description=feat.get("description", ""),
|
|
214
|
+
command=feat.get("command", ""),
|
|
215
|
+
cli_alias=feat.get("cli_alias", ""),
|
|
216
|
+
frequency=freq,
|
|
217
|
+
context=feat.get("context", "global"),
|
|
218
|
+
icon=feat.get("icon", "$(symbol-misc)"),
|
|
219
|
+
),
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
return features
|
|
223
|
+
|
|
224
|
+
def parse_full_manifest(self, path: Path) -> FeatureManifest | None:
|
|
225
|
+
"""Parse complete manifest including project metadata."""
|
|
226
|
+
try:
|
|
227
|
+
data = yaml.safe_load(path.read_text())
|
|
228
|
+
except (yaml.YAMLError, OSError):
|
|
229
|
+
return None
|
|
230
|
+
|
|
231
|
+
if not data:
|
|
232
|
+
return None
|
|
233
|
+
|
|
234
|
+
project = data.get("project", {})
|
|
235
|
+
categories = []
|
|
236
|
+
|
|
237
|
+
for cat_data in data.get("categories", []):
|
|
238
|
+
features = []
|
|
239
|
+
tier_str = cat_data.get("tier", "frequent")
|
|
240
|
+
tier = (
|
|
241
|
+
FrequencyTier(tier_str)
|
|
242
|
+
if tier_str in FrequencyTier._value2member_map_
|
|
243
|
+
else FrequencyTier.FREQUENT
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
for feat_data in cat_data.get("features", []):
|
|
247
|
+
freq_str = feat_data.get("frequency", tier_str)
|
|
248
|
+
freq = (
|
|
249
|
+
FrequencyTier(freq_str)
|
|
250
|
+
if freq_str in FrequencyTier._value2member_map_
|
|
251
|
+
else tier
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
features.append(
|
|
255
|
+
Feature(
|
|
256
|
+
id=feat_data.get("id", ""),
|
|
257
|
+
name=feat_data.get("name", ""),
|
|
258
|
+
description=feat_data.get("description", ""),
|
|
259
|
+
command=feat_data.get("command", ""),
|
|
260
|
+
cli_alias=feat_data.get("cli_alias", ""),
|
|
261
|
+
frequency=freq,
|
|
262
|
+
context=feat_data.get("context", "global"),
|
|
263
|
+
icon=feat_data.get("icon", "$(symbol-misc)"),
|
|
264
|
+
),
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
categories.append(
|
|
268
|
+
Category(
|
|
269
|
+
name=cat_data.get("name", "General"),
|
|
270
|
+
icon=cat_data.get("icon", "$(folder)"),
|
|
271
|
+
tier=tier,
|
|
272
|
+
features=features,
|
|
273
|
+
),
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
return FeatureManifest(
|
|
277
|
+
project_name=project.get("name", "project"),
|
|
278
|
+
project_type=project.get("type", "custom"),
|
|
279
|
+
prefix=project.get("prefix", "ctrl+shift+e"),
|
|
280
|
+
categories=categories,
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
class LLMFeatureAnalyzer:
|
|
285
|
+
"""Use LLM to analyze codebase and discover features."""
|
|
286
|
+
|
|
287
|
+
def __init__(self, llm_client=None):
|
|
288
|
+
"""Initialize with optional LLM client."""
|
|
289
|
+
self.llm_client = llm_client
|
|
290
|
+
|
|
291
|
+
async def analyze_codebase(self, project_path: Path) -> list[Feature]:
|
|
292
|
+
"""Analyze codebase using LLM to discover features.
|
|
293
|
+
|
|
294
|
+
This is a placeholder for LLM-based feature discovery.
|
|
295
|
+
The actual implementation would:
|
|
296
|
+
1. Scan for function signatures, CLI decorators, etc.
|
|
297
|
+
2. Send code snippets to LLM for analysis
|
|
298
|
+
3. Extract feature descriptions and usage patterns
|
|
299
|
+
"""
|
|
300
|
+
# TODO: Implement LLM-based feature discovery
|
|
301
|
+
# For now, return empty list
|
|
302
|
+
return []
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
class CompositeParser:
|
|
306
|
+
"""Combines multiple parsers for comprehensive feature discovery."""
|
|
307
|
+
|
|
308
|
+
def __init__(self):
|
|
309
|
+
self.parsers: list[FeatureParser] = [
|
|
310
|
+
VSCodeCommandParser(),
|
|
311
|
+
PyProjectParser(),
|
|
312
|
+
YAMLManifestParser(),
|
|
313
|
+
]
|
|
314
|
+
self.llm_analyzer = LLMFeatureAnalyzer()
|
|
315
|
+
|
|
316
|
+
def discover_features(self, project_path: Path) -> FeatureManifest:
|
|
317
|
+
"""Discover all features from a project.
|
|
318
|
+
|
|
319
|
+
Scans for:
|
|
320
|
+
- VSCode package.json commands
|
|
321
|
+
- Python pyproject.toml entry points
|
|
322
|
+
- Custom features.yaml manifest
|
|
323
|
+
"""
|
|
324
|
+
all_features: list[Feature] = []
|
|
325
|
+
|
|
326
|
+
# Try each parser
|
|
327
|
+
for parser in self.parsers:
|
|
328
|
+
for file_pattern in self._get_patterns(parser):
|
|
329
|
+
for found_path in project_path.rglob(file_pattern):
|
|
330
|
+
if parser.can_parse(found_path):
|
|
331
|
+
features = parser.parse(found_path)
|
|
332
|
+
all_features.extend(features)
|
|
333
|
+
|
|
334
|
+
# Check for custom manifest with full metadata
|
|
335
|
+
manifest_path = project_path / "features.yaml"
|
|
336
|
+
if manifest_path.exists():
|
|
337
|
+
yaml_parser = YAMLManifestParser()
|
|
338
|
+
full_manifest = yaml_parser.parse_full_manifest(manifest_path)
|
|
339
|
+
if full_manifest:
|
|
340
|
+
return full_manifest
|
|
341
|
+
|
|
342
|
+
# Create manifest from discovered features
|
|
343
|
+
return self._create_manifest_from_features(project_path, all_features)
|
|
344
|
+
|
|
345
|
+
def _get_patterns(self, parser: FeatureParser) -> list[str]:
|
|
346
|
+
"""Get file patterns for a parser."""
|
|
347
|
+
if isinstance(parser, VSCodeCommandParser):
|
|
348
|
+
return ["package.json"]
|
|
349
|
+
if isinstance(parser, PyProjectParser):
|
|
350
|
+
return ["pyproject.toml"]
|
|
351
|
+
if isinstance(parser, YAMLManifestParser):
|
|
352
|
+
return ["features.yaml", "features.yml"]
|
|
353
|
+
return []
|
|
354
|
+
|
|
355
|
+
def _create_manifest_from_features(
|
|
356
|
+
self,
|
|
357
|
+
project_path: Path,
|
|
358
|
+
features: list[Feature],
|
|
359
|
+
) -> FeatureManifest:
|
|
360
|
+
"""Create a manifest from discovered features."""
|
|
361
|
+
# Group features by inferred category
|
|
362
|
+
categories_dict: dict[str, list[Feature]] = {}
|
|
363
|
+
for feature in features:
|
|
364
|
+
# Try to infer category from feature properties
|
|
365
|
+
category = "General"
|
|
366
|
+
if "quick" in feature.id.lower() or feature.frequency == FrequencyTier.DAILY:
|
|
367
|
+
category = "Quick Actions"
|
|
368
|
+
elif "view" in feature.id.lower() or "dashboard" in feature.id.lower():
|
|
369
|
+
category = "Views"
|
|
370
|
+
elif "workflow" in feature.id.lower() or "run" in feature.id.lower():
|
|
371
|
+
category = "Workflows"
|
|
372
|
+
elif "security" in feature.id.lower() or "audit" in feature.id.lower():
|
|
373
|
+
category = "Security"
|
|
374
|
+
|
|
375
|
+
if category not in categories_dict:
|
|
376
|
+
categories_dict[category] = []
|
|
377
|
+
categories_dict[category].append(feature)
|
|
378
|
+
|
|
379
|
+
# Convert to Category objects
|
|
380
|
+
categories = []
|
|
381
|
+
for name, feats in categories_dict.items():
|
|
382
|
+
# Determine tier based on most common tier in category
|
|
383
|
+
tier = FrequencyTier.FREQUENT
|
|
384
|
+
if feats:
|
|
385
|
+
tier_counts: dict[FrequencyTier, int] = {}
|
|
386
|
+
for f in feats:
|
|
387
|
+
tier_counts[f.frequency] = tier_counts.get(f.frequency, 0) + 1
|
|
388
|
+
tier = max(tier_counts.keys(), key=lambda t: tier_counts[t])
|
|
389
|
+
|
|
390
|
+
categories.append(
|
|
391
|
+
Category(
|
|
392
|
+
name=name,
|
|
393
|
+
tier=tier,
|
|
394
|
+
features=feats,
|
|
395
|
+
),
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
# Infer project name
|
|
399
|
+
project_name = project_path.name
|
|
400
|
+
|
|
401
|
+
# Infer project type - use cast for Literal typing
|
|
402
|
+
from typing import Literal, cast
|
|
403
|
+
|
|
404
|
+
project_type_str = "custom"
|
|
405
|
+
if (project_path / "package.json").exists():
|
|
406
|
+
project_type_str = "vscode-extension"
|
|
407
|
+
elif (project_path / "pyproject.toml").exists():
|
|
408
|
+
project_type_str = "python-cli"
|
|
409
|
+
|
|
410
|
+
project_type = cast("Literal['vscode-extension', 'python-cli', 'custom']", project_type_str)
|
|
411
|
+
|
|
412
|
+
return FeatureManifest(
|
|
413
|
+
project_name=project_name,
|
|
414
|
+
project_type=project_type,
|
|
415
|
+
categories=categories,
|
|
416
|
+
)
|