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,256 @@
|
|
|
1
|
+
"""Dependency manager for cache optional dependencies.
|
|
2
|
+
|
|
3
|
+
Handles auto-detection, user prompts, and installation of sentence-transformers.
|
|
4
|
+
|
|
5
|
+
Copyright 2025 Smart-AI-Memory
|
|
6
|
+
Licensed under Fair Source License 0.9
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
import yaml
|
|
16
|
+
|
|
17
|
+
from attune.config import _validate_file_path
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class DependencyManager:
|
|
23
|
+
"""Manage optional cache dependencies with user prompts.
|
|
24
|
+
|
|
25
|
+
Handles:
|
|
26
|
+
- Auto-detection of installed dependencies
|
|
27
|
+
- One-time user prompt to install cache deps
|
|
28
|
+
- Configuration persistence (user preferences)
|
|
29
|
+
- Pip-based installation
|
|
30
|
+
|
|
31
|
+
Example:
|
|
32
|
+
manager = DependencyManager()
|
|
33
|
+
|
|
34
|
+
if manager.should_prompt_cache_install():
|
|
35
|
+
manager.prompt_cache_install()
|
|
36
|
+
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, config_path: Path | None = None):
|
|
40
|
+
"""Initialize dependency manager.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
config_path: Path to config file (default: ~/.attune/config.yml).
|
|
44
|
+
|
|
45
|
+
"""
|
|
46
|
+
self.config_path = config_path or Path.home() / ".empathy" / "config.yml"
|
|
47
|
+
self.config = self._load_config()
|
|
48
|
+
|
|
49
|
+
def _load_config(self) -> dict:
|
|
50
|
+
"""Load user configuration.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Configuration dictionary.
|
|
54
|
+
|
|
55
|
+
"""
|
|
56
|
+
if not self.config_path.exists():
|
|
57
|
+
return {}
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
with open(self.config_path) as f:
|
|
61
|
+
return yaml.safe_load(f) or {}
|
|
62
|
+
except (yaml.YAMLError, OSError) as e:
|
|
63
|
+
logger.warning(f"Failed to load config: {e}")
|
|
64
|
+
return {}
|
|
65
|
+
|
|
66
|
+
def _save_config(self) -> None:
|
|
67
|
+
"""Save configuration to disk."""
|
|
68
|
+
try:
|
|
69
|
+
self.config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
70
|
+
validated_path = _validate_file_path(str(self.config_path))
|
|
71
|
+
with open(validated_path, "w") as f:
|
|
72
|
+
yaml.safe_dump(self.config, f, default_flow_style=False)
|
|
73
|
+
except (yaml.YAMLError, OSError, ValueError) as e:
|
|
74
|
+
logger.error(f"Failed to save config: {e}")
|
|
75
|
+
|
|
76
|
+
def is_cache_installed(self) -> bool:
|
|
77
|
+
"""Check if cache dependencies are installed.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
True if sentence-transformers is available, False otherwise.
|
|
81
|
+
|
|
82
|
+
"""
|
|
83
|
+
try:
|
|
84
|
+
import sentence_transformers # noqa: F401
|
|
85
|
+
import torch # noqa: F401
|
|
86
|
+
|
|
87
|
+
return True
|
|
88
|
+
except ImportError:
|
|
89
|
+
return False
|
|
90
|
+
|
|
91
|
+
def should_prompt_cache_install(self) -> bool:
|
|
92
|
+
"""Check if we should prompt user to install cache.
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
True if we should prompt, False otherwise.
|
|
96
|
+
|
|
97
|
+
"""
|
|
98
|
+
# Never prompt if already installed
|
|
99
|
+
if self.is_cache_installed():
|
|
100
|
+
return False
|
|
101
|
+
|
|
102
|
+
# Never prompt if user already declined
|
|
103
|
+
cache_config = self.config.get("cache", {})
|
|
104
|
+
if cache_config.get("install_declined", False):
|
|
105
|
+
return False
|
|
106
|
+
|
|
107
|
+
# Never prompt if prompt already shown
|
|
108
|
+
if cache_config.get("prompt_shown", False):
|
|
109
|
+
return False
|
|
110
|
+
|
|
111
|
+
# Never prompt if user disabled prompts
|
|
112
|
+
if not cache_config.get("prompt_enabled", True):
|
|
113
|
+
return False
|
|
114
|
+
|
|
115
|
+
# Prompt on first run
|
|
116
|
+
return True
|
|
117
|
+
|
|
118
|
+
def prompt_cache_install(self) -> bool:
|
|
119
|
+
"""Prompt user to install cache dependencies.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
True if user accepted and install succeeded, False otherwise.
|
|
123
|
+
|
|
124
|
+
"""
|
|
125
|
+
print("\n" + "=" * 60)
|
|
126
|
+
print("⚡ Smart Caching Available")
|
|
127
|
+
print("=" * 60)
|
|
128
|
+
print()
|
|
129
|
+
print(" Empathy Framework can reduce your API costs by 70% with hybrid caching.")
|
|
130
|
+
print(" This requires installing sentence-transformers (~150MB).")
|
|
131
|
+
print()
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
response = (
|
|
135
|
+
input(" Would you like to enable smart caching now? [Y/n]: ").strip().lower()
|
|
136
|
+
)
|
|
137
|
+
except (EOFError, KeyboardInterrupt):
|
|
138
|
+
print("\n Skipping cache installation.")
|
|
139
|
+
response = "n"
|
|
140
|
+
|
|
141
|
+
if response in ["y", "yes", ""]:
|
|
142
|
+
return self.install_cache_dependencies()
|
|
143
|
+
else:
|
|
144
|
+
print()
|
|
145
|
+
print(" ℹ Using hash-only cache (30% savings)")
|
|
146
|
+
print(" ℹ To enable later: empathy install cache")
|
|
147
|
+
print()
|
|
148
|
+
print("=" * 60)
|
|
149
|
+
print()
|
|
150
|
+
|
|
151
|
+
# Save that user declined
|
|
152
|
+
if "cache" not in self.config:
|
|
153
|
+
self.config["cache"] = {}
|
|
154
|
+
self.config["cache"]["install_declined"] = True
|
|
155
|
+
self.config["cache"]["prompt_shown"] = True
|
|
156
|
+
self._save_config()
|
|
157
|
+
|
|
158
|
+
return False
|
|
159
|
+
|
|
160
|
+
def install_cache_dependencies(self) -> bool:
|
|
161
|
+
"""Install cache dependencies using pip.
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
True if installation succeeded, False otherwise.
|
|
165
|
+
|
|
166
|
+
"""
|
|
167
|
+
print()
|
|
168
|
+
print(" ↓ Installing cache dependencies...")
|
|
169
|
+
print()
|
|
170
|
+
|
|
171
|
+
packages = [
|
|
172
|
+
"sentence-transformers>=2.0.0",
|
|
173
|
+
"torch>=2.0.0",
|
|
174
|
+
"numpy>=1.24.0",
|
|
175
|
+
]
|
|
176
|
+
|
|
177
|
+
try:
|
|
178
|
+
# Run pip install
|
|
179
|
+
subprocess.check_call(
|
|
180
|
+
[sys.executable, "-m", "pip", "install", "--quiet"] + packages,
|
|
181
|
+
stdout=subprocess.PIPE,
|
|
182
|
+
stderr=subprocess.PIPE,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
print(" ✓ sentence-transformers installed")
|
|
186
|
+
print(" ✓ torch installed")
|
|
187
|
+
print(" ✓ numpy installed")
|
|
188
|
+
print()
|
|
189
|
+
print(" ✓ Smart caching enabled! Future runs will save 70% on costs.")
|
|
190
|
+
print()
|
|
191
|
+
print("=" * 60)
|
|
192
|
+
print()
|
|
193
|
+
|
|
194
|
+
# Mark as installed in config
|
|
195
|
+
if "cache" not in self.config:
|
|
196
|
+
self.config["cache"] = {}
|
|
197
|
+
self.config["cache"]["enabled"] = True
|
|
198
|
+
self.config["cache"]["install_declined"] = False
|
|
199
|
+
self.config["cache"]["prompt_shown"] = True
|
|
200
|
+
self._save_config()
|
|
201
|
+
|
|
202
|
+
return True
|
|
203
|
+
|
|
204
|
+
except subprocess.CalledProcessError as e:
|
|
205
|
+
print()
|
|
206
|
+
print(f" ✗ Installation failed: {e}")
|
|
207
|
+
print(" ℹ You can try manually: pip install empathy-framework[cache]")
|
|
208
|
+
print()
|
|
209
|
+
print("=" * 60)
|
|
210
|
+
print()
|
|
211
|
+
|
|
212
|
+
logger.error(f"Failed to install cache dependencies: {e}")
|
|
213
|
+
return False
|
|
214
|
+
|
|
215
|
+
def disable_prompts(self) -> None:
|
|
216
|
+
"""Disable cache installation prompts."""
|
|
217
|
+
if "cache" not in self.config:
|
|
218
|
+
self.config["cache"] = {}
|
|
219
|
+
self.config["cache"]["prompt_enabled"] = False
|
|
220
|
+
self._save_config()
|
|
221
|
+
logger.info("Cache installation prompts disabled")
|
|
222
|
+
|
|
223
|
+
def enable_prompts(self) -> None:
|
|
224
|
+
"""Re-enable cache installation prompts."""
|
|
225
|
+
if "cache" not in self.config:
|
|
226
|
+
self.config["cache"] = {}
|
|
227
|
+
self.config["cache"]["prompt_enabled"] = True
|
|
228
|
+
self.config["cache"]["prompt_shown"] = False
|
|
229
|
+
self.config["cache"]["install_declined"] = False
|
|
230
|
+
self._save_config()
|
|
231
|
+
logger.info("Cache installation prompts re-enabled")
|
|
232
|
+
|
|
233
|
+
def get_config(self) -> dict[str, Any]:
|
|
234
|
+
"""Get cache configuration.
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
Cache configuration dictionary.
|
|
238
|
+
|
|
239
|
+
"""
|
|
240
|
+
result = self.config.get("cache", {})
|
|
241
|
+
if not isinstance(result, dict):
|
|
242
|
+
return {}
|
|
243
|
+
return result
|
|
244
|
+
|
|
245
|
+
def set_config(self, key: str, value: Any) -> None:
|
|
246
|
+
"""Set cache configuration value.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
key: Configuration key.
|
|
250
|
+
value: Configuration value.
|
|
251
|
+
|
|
252
|
+
"""
|
|
253
|
+
if "cache" not in self.config:
|
|
254
|
+
self.config["cache"] = {}
|
|
255
|
+
self.config["cache"][key] = value
|
|
256
|
+
self._save_config()
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"""Hash-only cache implementation using SHA256 for exact matching.
|
|
2
|
+
|
|
3
|
+
Provides fast exact-match caching with ~30% hit rate. No dependencies required.
|
|
4
|
+
|
|
5
|
+
Copyright 2025 Smart-AI-Memory
|
|
6
|
+
Licensed under Fair Source License 0.9
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import heapq
|
|
10
|
+
import logging
|
|
11
|
+
import time
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from .base import BaseCache, CacheEntry, CacheStats
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class HashOnlyCache(BaseCache):
|
|
20
|
+
"""Fast hash-based cache for exact prompt matches.
|
|
21
|
+
|
|
22
|
+
Uses SHA256 hashing for O(1) lookup. Provides ~30% cache hit rate
|
|
23
|
+
for workflows with repeated exact prompts (e.g., re-reviewing same code).
|
|
24
|
+
|
|
25
|
+
No external dependencies required - always available as fallback.
|
|
26
|
+
|
|
27
|
+
Example:
|
|
28
|
+
cache = HashOnlyCache()
|
|
29
|
+
|
|
30
|
+
# First call (miss)
|
|
31
|
+
result = cache.get("code-review", "scan", prompt, "claude-3-5-sonnet")
|
|
32
|
+
# → None
|
|
33
|
+
|
|
34
|
+
# Store response
|
|
35
|
+
cache.put("code-review", "scan", prompt, "claude-3-5-sonnet", response)
|
|
36
|
+
|
|
37
|
+
# Second call with exact same prompt (hit)
|
|
38
|
+
result = cache.get("code-review", "scan", prompt, "claude-3-5-sonnet")
|
|
39
|
+
# → response (from cache, <5μs lookup)
|
|
40
|
+
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
max_size_mb: int = 500,
|
|
46
|
+
default_ttl: int = 86400,
|
|
47
|
+
max_memory_mb: int = 100,
|
|
48
|
+
):
|
|
49
|
+
"""Initialize hash-only cache.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
max_size_mb: Maximum disk cache size in MB.
|
|
53
|
+
default_ttl: Default TTL in seconds (24 hours).
|
|
54
|
+
max_memory_mb: Maximum in-memory cache size in MB.
|
|
55
|
+
|
|
56
|
+
"""
|
|
57
|
+
super().__init__(max_size_mb, default_ttl)
|
|
58
|
+
self.max_memory_mb = max_memory_mb
|
|
59
|
+
self._memory_cache: dict[str, CacheEntry] = {}
|
|
60
|
+
self._access_times: dict[str, float] = {} # For LRU eviction
|
|
61
|
+
|
|
62
|
+
logger.debug(
|
|
63
|
+
f"HashOnlyCache initialized (max_memory: {max_memory_mb}MB, "
|
|
64
|
+
f"max_disk: {max_size_mb}MB, ttl: {default_ttl}s)"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def get(
|
|
68
|
+
self,
|
|
69
|
+
workflow: str,
|
|
70
|
+
stage: str,
|
|
71
|
+
prompt: str,
|
|
72
|
+
model: str,
|
|
73
|
+
) -> Any | None:
|
|
74
|
+
"""Get cached response for exact prompt match.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
workflow: Workflow name (e.g., "code-review").
|
|
78
|
+
stage: Stage name (e.g., "scan").
|
|
79
|
+
prompt: Exact prompt text.
|
|
80
|
+
model: Model identifier.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Cached response if exact match found and not expired, None otherwise.
|
|
84
|
+
|
|
85
|
+
"""
|
|
86
|
+
cache_key = self._create_cache_key(workflow, stage, prompt, model)
|
|
87
|
+
|
|
88
|
+
# Check in-memory cache
|
|
89
|
+
if cache_key in self._memory_cache:
|
|
90
|
+
entry = self._memory_cache[cache_key]
|
|
91
|
+
|
|
92
|
+
# Check if expired
|
|
93
|
+
current_time = time.time()
|
|
94
|
+
if entry.is_expired(current_time):
|
|
95
|
+
logger.debug(f"Cache entry expired: {cache_key[:16]}...")
|
|
96
|
+
self._evict_entry(cache_key)
|
|
97
|
+
self.stats.misses += 1
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
# Cache hit!
|
|
101
|
+
self._access_times[cache_key] = current_time
|
|
102
|
+
self.stats.hits += 1
|
|
103
|
+
logger.debug(
|
|
104
|
+
f"Cache HIT (hash): {workflow}/{stage} "
|
|
105
|
+
f"(key: {cache_key[:16]}..., hit_rate: {self.stats.hit_rate:.1f}%)"
|
|
106
|
+
)
|
|
107
|
+
return entry.response
|
|
108
|
+
|
|
109
|
+
# Cache miss
|
|
110
|
+
self.stats.misses += 1
|
|
111
|
+
logger.debug(
|
|
112
|
+
f"Cache MISS (hash): {workflow}/{stage} "
|
|
113
|
+
f"(key: {cache_key[:16]}..., hit_rate: {self.stats.hit_rate:.1f}%)"
|
|
114
|
+
)
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
def put(
|
|
118
|
+
self,
|
|
119
|
+
workflow: str,
|
|
120
|
+
stage: str,
|
|
121
|
+
prompt: str,
|
|
122
|
+
model: str,
|
|
123
|
+
response: Any,
|
|
124
|
+
ttl: int | None = None,
|
|
125
|
+
) -> None:
|
|
126
|
+
"""Store response in cache.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
workflow: Workflow name.
|
|
130
|
+
stage: Stage name.
|
|
131
|
+
prompt: Prompt text.
|
|
132
|
+
model: Model identifier.
|
|
133
|
+
response: LLM response to cache.
|
|
134
|
+
ttl: Optional custom TTL (uses default if None).
|
|
135
|
+
|
|
136
|
+
"""
|
|
137
|
+
cache_key = self._create_cache_key(workflow, stage, prompt, model)
|
|
138
|
+
prompt_hash = self._create_cache_key("", "", prompt, "") # Hash of prompt only
|
|
139
|
+
|
|
140
|
+
# Create cache entry
|
|
141
|
+
entry = CacheEntry(
|
|
142
|
+
key=cache_key,
|
|
143
|
+
response=response,
|
|
144
|
+
workflow=workflow,
|
|
145
|
+
stage=stage,
|
|
146
|
+
model=model,
|
|
147
|
+
prompt_hash=prompt_hash,
|
|
148
|
+
timestamp=time.time(),
|
|
149
|
+
ttl=ttl or self.default_ttl,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# Check if we need to evict entries
|
|
153
|
+
self._maybe_evict_lru()
|
|
154
|
+
|
|
155
|
+
# Store in memory
|
|
156
|
+
self._memory_cache[cache_key] = entry
|
|
157
|
+
self._access_times[cache_key] = entry.timestamp
|
|
158
|
+
|
|
159
|
+
logger.debug(
|
|
160
|
+
f"Cache PUT: {workflow}/{stage} (key: {cache_key[:16]}..., "
|
|
161
|
+
f"entries: {len(self._memory_cache)})"
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
def clear(self) -> None:
|
|
165
|
+
"""Clear all cached entries."""
|
|
166
|
+
count = len(self._memory_cache)
|
|
167
|
+
self._memory_cache.clear()
|
|
168
|
+
self._access_times.clear()
|
|
169
|
+
logger.info(f"Cache cleared ({count} entries removed)")
|
|
170
|
+
|
|
171
|
+
def get_stats(self) -> CacheStats:
|
|
172
|
+
"""Get cache statistics.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
CacheStats with hit/miss/eviction counts.
|
|
176
|
+
|
|
177
|
+
"""
|
|
178
|
+
return self.stats
|
|
179
|
+
|
|
180
|
+
def _evict_entry(self, cache_key: str) -> None:
|
|
181
|
+
"""Remove entry from cache.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
cache_key: Key to evict.
|
|
185
|
+
|
|
186
|
+
"""
|
|
187
|
+
if cache_key in self._memory_cache:
|
|
188
|
+
del self._memory_cache[cache_key]
|
|
189
|
+
if cache_key in self._access_times:
|
|
190
|
+
del self._access_times[cache_key]
|
|
191
|
+
self.stats.evictions += 1
|
|
192
|
+
|
|
193
|
+
def _maybe_evict_lru(self) -> None:
|
|
194
|
+
"""Evict least recently used entries if cache is too large.
|
|
195
|
+
|
|
196
|
+
Uses LRU (Least Recently Used) eviction policy.
|
|
197
|
+
|
|
198
|
+
"""
|
|
199
|
+
# Estimate memory usage (rough)
|
|
200
|
+
estimated_mb = len(self._memory_cache) * 0.01 # Rough estimate: 10KB per entry
|
|
201
|
+
|
|
202
|
+
if estimated_mb > self.max_memory_mb:
|
|
203
|
+
# Evict 10% of entries (LRU)
|
|
204
|
+
num_to_evict = max(1, len(self._memory_cache) // 10)
|
|
205
|
+
|
|
206
|
+
# Get oldest entries by access time (LRU eviction)
|
|
207
|
+
oldest_keys = heapq.nsmallest(
|
|
208
|
+
num_to_evict, self._access_times.items(), key=lambda x: x[1]
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
for cache_key, _ in oldest_keys:
|
|
212
|
+
self._evict_entry(cache_key)
|
|
213
|
+
logger.debug(f"LRU eviction: {cache_key[:16]}...")
|
|
214
|
+
|
|
215
|
+
logger.info(
|
|
216
|
+
f"LRU eviction: removed {num_to_evict} entries "
|
|
217
|
+
f"(cache size: {len(self._memory_cache)} entries)"
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
def evict_expired(self) -> int:
|
|
221
|
+
"""Remove all expired entries.
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
Number of entries evicted.
|
|
225
|
+
|
|
226
|
+
"""
|
|
227
|
+
current_time = time.time()
|
|
228
|
+
expired_keys = [
|
|
229
|
+
key for key, entry in self._memory_cache.items() if entry.is_expired(current_time)
|
|
230
|
+
]
|
|
231
|
+
|
|
232
|
+
for key in expired_keys:
|
|
233
|
+
self._evict_entry(key)
|
|
234
|
+
|
|
235
|
+
if expired_keys:
|
|
236
|
+
logger.info(f"Expired eviction: removed {len(expired_keys)} entries")
|
|
237
|
+
|
|
238
|
+
return len(expired_keys)
|
|
239
|
+
|
|
240
|
+
def size_info(self) -> dict[str, Any]:
|
|
241
|
+
"""Get cache size information.
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
Dictionary with cache size metrics.
|
|
245
|
+
|
|
246
|
+
"""
|
|
247
|
+
return {
|
|
248
|
+
"entries": len(self._memory_cache),
|
|
249
|
+
"estimated_mb": len(self._memory_cache) * 0.01,
|
|
250
|
+
"max_memory_mb": self.max_memory_mb,
|
|
251
|
+
}
|