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
attune/config.py
ADDED
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
"""Configuration Management for Empathy Framework
|
|
2
|
+
|
|
3
|
+
Supports:
|
|
4
|
+
- YAML configuration files
|
|
5
|
+
- JSON configuration files
|
|
6
|
+
- Environment variables
|
|
7
|
+
- Default configuration
|
|
8
|
+
|
|
9
|
+
Copyright 2025 Smart AI Memory, LLC
|
|
10
|
+
Licensed under Fair Source 0.9
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
from dataclasses import asdict, dataclass, field
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import TYPE_CHECKING, Any
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from attune.workflows.config import ModelConfig
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
import yaml
|
|
24
|
+
|
|
25
|
+
YAML_AVAILABLE = True
|
|
26
|
+
except ImportError:
|
|
27
|
+
YAML_AVAILABLE = False
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _validate_file_path(path: str, allowed_dir: str | None = None) -> Path:
|
|
31
|
+
"""Validate file path to prevent path traversal and arbitrary writes.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
path: File path to validate
|
|
35
|
+
allowed_dir: Optional directory to restrict writes to
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Validated Path object
|
|
39
|
+
|
|
40
|
+
Raises:
|
|
41
|
+
ValueError: If path is invalid or unsafe
|
|
42
|
+
"""
|
|
43
|
+
if not path or not isinstance(path, str):
|
|
44
|
+
raise ValueError("path must be a non-empty string")
|
|
45
|
+
|
|
46
|
+
# Check for null bytes
|
|
47
|
+
if "\x00" in path:
|
|
48
|
+
raise ValueError("path contains null bytes")
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
resolved = Path(path).resolve()
|
|
52
|
+
except (OSError, RuntimeError) as e:
|
|
53
|
+
raise ValueError(f"Invalid path: {e}")
|
|
54
|
+
|
|
55
|
+
# Check if within allowed directory
|
|
56
|
+
if allowed_dir:
|
|
57
|
+
try:
|
|
58
|
+
allowed = Path(allowed_dir).resolve()
|
|
59
|
+
resolved.relative_to(allowed)
|
|
60
|
+
except ValueError:
|
|
61
|
+
raise ValueError(f"path must be within {allowed_dir}")
|
|
62
|
+
|
|
63
|
+
# Check for dangerous system paths
|
|
64
|
+
# Note: On macOS, /etc is a symlink to /private/etc, so we check both
|
|
65
|
+
dangerous_paths = [
|
|
66
|
+
"/etc",
|
|
67
|
+
"/sys",
|
|
68
|
+
"/proc",
|
|
69
|
+
"/dev",
|
|
70
|
+
"/private/etc", # macOS: /etc -> /private/etc
|
|
71
|
+
"/private/var/root", # macOS: root's home directory
|
|
72
|
+
"/usr/bin", # System binaries
|
|
73
|
+
"/usr/sbin", # System admin binaries
|
|
74
|
+
"/bin", # Essential binaries
|
|
75
|
+
"/sbin", # System binaries
|
|
76
|
+
]
|
|
77
|
+
resolved_str = str(resolved)
|
|
78
|
+
for dangerous in dangerous_paths:
|
|
79
|
+
if resolved_str.startswith(dangerous + "/") or resolved_str == dangerous:
|
|
80
|
+
raise ValueError(f"Cannot write to system directory: {dangerous}")
|
|
81
|
+
|
|
82
|
+
return resolved
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass
|
|
86
|
+
class EmpathyConfig:
|
|
87
|
+
"""Configuration for EmpathyOS instance
|
|
88
|
+
|
|
89
|
+
Can be loaded from:
|
|
90
|
+
- YAML file (.empathy.yml, attune.config.yml)
|
|
91
|
+
- JSON file (.empathy.json, attune.config.json)
|
|
92
|
+
- Environment variables (EMPATHY_*)
|
|
93
|
+
- Direct instantiation
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
# Core settings
|
|
97
|
+
user_id: str = "default_user"
|
|
98
|
+
target_level: int = 3
|
|
99
|
+
confidence_threshold: float = 0.75
|
|
100
|
+
|
|
101
|
+
# Trust settings
|
|
102
|
+
trust_building_rate: float = 0.05
|
|
103
|
+
trust_erosion_rate: float = 0.10
|
|
104
|
+
|
|
105
|
+
# Persistence settings
|
|
106
|
+
persistence_enabled: bool = True
|
|
107
|
+
persistence_backend: str = "sqlite" # "sqlite", "json", "none"
|
|
108
|
+
persistence_path: str = "./empathy_data"
|
|
109
|
+
|
|
110
|
+
# State management
|
|
111
|
+
state_persistence: bool = True
|
|
112
|
+
state_path: str = "./empathy_state"
|
|
113
|
+
|
|
114
|
+
# Metrics settings
|
|
115
|
+
metrics_enabled: bool = True
|
|
116
|
+
metrics_path: str = "./metrics.db"
|
|
117
|
+
|
|
118
|
+
# Logging settings
|
|
119
|
+
log_level: str = "INFO"
|
|
120
|
+
log_file: str | None = None
|
|
121
|
+
structured_logging: bool = True
|
|
122
|
+
|
|
123
|
+
# Pattern library settings
|
|
124
|
+
pattern_library_enabled: bool = True
|
|
125
|
+
pattern_sharing: bool = True
|
|
126
|
+
pattern_confidence_threshold: float = 0.3
|
|
127
|
+
|
|
128
|
+
# Advanced settings
|
|
129
|
+
async_enabled: bool = True
|
|
130
|
+
feedback_loop_monitoring: bool = True
|
|
131
|
+
leverage_point_analysis: bool = True
|
|
132
|
+
|
|
133
|
+
# Custom metadata
|
|
134
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
135
|
+
|
|
136
|
+
# Model settings
|
|
137
|
+
models: list["ModelConfig"] = field(default_factory=list)
|
|
138
|
+
default_model: str | None = None
|
|
139
|
+
log_path: str | None = None
|
|
140
|
+
max_threads: int = 4
|
|
141
|
+
model_router: dict[str, Any] | None = None
|
|
142
|
+
|
|
143
|
+
def __post_init__(self):
|
|
144
|
+
"""Post-initialization validation."""
|
|
145
|
+
if self.default_model and not any(m.name == self.default_model for m in self.models):
|
|
146
|
+
raise ValueError(f"Default model '{self.default_model}' not in models.")
|
|
147
|
+
|
|
148
|
+
@classmethod
|
|
149
|
+
def from_yaml(cls, filepath: str) -> "EmpathyConfig":
|
|
150
|
+
"""Load configuration from YAML file
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
filepath: Path to YAML configuration file
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
EmpathyConfig instance
|
|
157
|
+
|
|
158
|
+
Raises:
|
|
159
|
+
ImportError: If PyYAML is not installed
|
|
160
|
+
FileNotFoundError: If file doesn't exist
|
|
161
|
+
|
|
162
|
+
Example:
|
|
163
|
+
>>> config = EmpathyConfig.from_yaml("attune.config.yml")
|
|
164
|
+
>>> empathy = EmpathyOS(config=config)
|
|
165
|
+
|
|
166
|
+
Note:
|
|
167
|
+
Unknown fields in the YAML file are silently ignored.
|
|
168
|
+
This allows config files to contain settings for other
|
|
169
|
+
components (e.g., model_preferences, workflows) without
|
|
170
|
+
breaking EmpathyConfig loading.
|
|
171
|
+
|
|
172
|
+
"""
|
|
173
|
+
if not YAML_AVAILABLE:
|
|
174
|
+
raise ImportError(
|
|
175
|
+
"PyYAML is required for YAML configuration. Install with: pip install pyyaml",
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
with open(filepath) as f:
|
|
179
|
+
data = yaml.safe_load(f)
|
|
180
|
+
|
|
181
|
+
# Filter to only known fields (gracefully ignore unknown fields like
|
|
182
|
+
# 'provider', 'model_preferences', 'workflows', etc.)
|
|
183
|
+
from dataclasses import fields as dataclass_fields
|
|
184
|
+
|
|
185
|
+
valid_fields = {f.name for f in dataclass_fields(cls)}
|
|
186
|
+
filtered_data = {k: v for k, v in data.items() if k in valid_fields}
|
|
187
|
+
|
|
188
|
+
return cls.from_dict(filtered_data)
|
|
189
|
+
|
|
190
|
+
@classmethod
|
|
191
|
+
def from_dict(cls, data: dict[str, Any]) -> "EmpathyConfig":
|
|
192
|
+
"""Create an EmpathyConfig from a dictionary, ignoring unknown fields."""
|
|
193
|
+
known_fields = {f.name for f in cls.__dataclass_fields__.values()}
|
|
194
|
+
filtered_data = {k: v for k, v in data.items() if k in known_fields}
|
|
195
|
+
|
|
196
|
+
# Handle nested ModelConfig objects
|
|
197
|
+
if filtered_data.get("models"):
|
|
198
|
+
from attune.workflows.config import ModelConfig
|
|
199
|
+
|
|
200
|
+
filtered_data["models"] = [ModelConfig(**m) for m in filtered_data["models"]]
|
|
201
|
+
|
|
202
|
+
return cls(**filtered_data)
|
|
203
|
+
|
|
204
|
+
@classmethod
|
|
205
|
+
def from_json(cls, filepath: str) -> "EmpathyConfig":
|
|
206
|
+
"""Load configuration from JSON file
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
filepath: Path to JSON configuration file
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
EmpathyConfig instance
|
|
213
|
+
|
|
214
|
+
Example:
|
|
215
|
+
>>> config = EmpathyConfig.from_json("attune.config.json")
|
|
216
|
+
>>> empathy = EmpathyOS(config=config)
|
|
217
|
+
|
|
218
|
+
Note:
|
|
219
|
+
Unknown fields in the JSON file are silently ignored.
|
|
220
|
+
|
|
221
|
+
"""
|
|
222
|
+
with open(filepath) as f:
|
|
223
|
+
data = json.load(f)
|
|
224
|
+
|
|
225
|
+
# Filter to only known fields (gracefully ignore unknown fields)
|
|
226
|
+
from dataclasses import fields as dataclass_fields
|
|
227
|
+
|
|
228
|
+
valid_fields = {f.name for f in dataclass_fields(cls)}
|
|
229
|
+
filtered_data = {k: v for k, v in data.items() if k in valid_fields}
|
|
230
|
+
|
|
231
|
+
return cls(**filtered_data)
|
|
232
|
+
|
|
233
|
+
@classmethod
|
|
234
|
+
def from_env(cls, prefix: str = "EMPATHY_") -> "EmpathyConfig":
|
|
235
|
+
"""Load configuration from environment variables
|
|
236
|
+
|
|
237
|
+
Environment variables should be prefixed with EMPATHY_
|
|
238
|
+
and match config field names in uppercase.
|
|
239
|
+
|
|
240
|
+
Example:
|
|
241
|
+
EMPATHY_USER_ID=alice
|
|
242
|
+
EMPATHY_TARGET_LEVEL=4
|
|
243
|
+
EMPATHY_CONFIDENCE_THRESHOLD=0.8
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
prefix: Environment variable prefix (default: "EMPATHY_")
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
EmpathyConfig instance
|
|
250
|
+
|
|
251
|
+
Example:
|
|
252
|
+
>>> os.environ["EMPATHY_USER_ID"] = "alice"
|
|
253
|
+
>>> config = EmpathyConfig.from_env()
|
|
254
|
+
>>> print(config.user_id) # "alice"
|
|
255
|
+
|
|
256
|
+
"""
|
|
257
|
+
from dataclasses import fields as dataclass_fields
|
|
258
|
+
|
|
259
|
+
# Get valid field names from the dataclass
|
|
260
|
+
valid_fields = {f.name for f in dataclass_fields(cls)}
|
|
261
|
+
|
|
262
|
+
data: dict[str, Any] = {}
|
|
263
|
+
|
|
264
|
+
# Get all environment variables with prefix
|
|
265
|
+
for key, value in os.environ.items():
|
|
266
|
+
if key.startswith(prefix):
|
|
267
|
+
# Convert EMPATHY_USER_ID -> user_id
|
|
268
|
+
field_name = key[len(prefix) :].lower()
|
|
269
|
+
|
|
270
|
+
# Skip unknown fields (e.g., EMPATHY_MASTER_KEY for encryption)
|
|
271
|
+
if field_name not in valid_fields:
|
|
272
|
+
continue
|
|
273
|
+
|
|
274
|
+
# Type conversion based on field name
|
|
275
|
+
if field_name in ("target_level",):
|
|
276
|
+
data[field_name] = int(value)
|
|
277
|
+
elif field_name in (
|
|
278
|
+
"confidence_threshold",
|
|
279
|
+
"trust_building_rate",
|
|
280
|
+
"trust_erosion_rate",
|
|
281
|
+
"pattern_confidence_threshold",
|
|
282
|
+
):
|
|
283
|
+
data[field_name] = float(value)
|
|
284
|
+
elif field_name in (
|
|
285
|
+
"persistence_enabled",
|
|
286
|
+
"state_persistence",
|
|
287
|
+
"metrics_enabled",
|
|
288
|
+
"structured_logging",
|
|
289
|
+
"pattern_library_enabled",
|
|
290
|
+
"pattern_sharing",
|
|
291
|
+
"async_enabled",
|
|
292
|
+
"feedback_loop_monitoring",
|
|
293
|
+
"leverage_point_analysis",
|
|
294
|
+
):
|
|
295
|
+
data[field_name] = value.lower() in ("true", "1", "yes")
|
|
296
|
+
else:
|
|
297
|
+
data[field_name] = value
|
|
298
|
+
|
|
299
|
+
return cls(**data)
|
|
300
|
+
|
|
301
|
+
@classmethod
|
|
302
|
+
def from_file(cls, filepath: str | None = None) -> "EmpathyConfig":
|
|
303
|
+
"""Automatically detect and load configuration from file
|
|
304
|
+
|
|
305
|
+
Looks for configuration files in this order:
|
|
306
|
+
1. Provided filepath
|
|
307
|
+
2. .empathy.yml
|
|
308
|
+
3. .empathy.yaml
|
|
309
|
+
4. attune.config.yml
|
|
310
|
+
5. attune.config.yaml
|
|
311
|
+
6. .empathy.json
|
|
312
|
+
7. attune.config.json
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
filepath: Optional explicit path to config file
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
EmpathyConfig instance, or default if no file found
|
|
319
|
+
|
|
320
|
+
Example:
|
|
321
|
+
>>> config = EmpathyConfig.from_file() # Auto-detect
|
|
322
|
+
>>> config = EmpathyConfig.from_file("my-config.yml")
|
|
323
|
+
|
|
324
|
+
"""
|
|
325
|
+
search_paths = [
|
|
326
|
+
filepath,
|
|
327
|
+
".empathy.yml",
|
|
328
|
+
".empathy.yaml",
|
|
329
|
+
"attune.config.yml",
|
|
330
|
+
"attune.config.yaml",
|
|
331
|
+
".empathy.json",
|
|
332
|
+
"attune.config.json",
|
|
333
|
+
]
|
|
334
|
+
|
|
335
|
+
for path in search_paths:
|
|
336
|
+
if path and Path(path).exists():
|
|
337
|
+
if path.endswith((".yml", ".yaml")):
|
|
338
|
+
return cls.from_yaml(path)
|
|
339
|
+
if path.endswith(".json"):
|
|
340
|
+
return cls.from_json(path)
|
|
341
|
+
|
|
342
|
+
# No config file found - return default
|
|
343
|
+
return cls()
|
|
344
|
+
|
|
345
|
+
def to_yaml(self, filepath: str):
|
|
346
|
+
"""Save configuration to YAML file
|
|
347
|
+
|
|
348
|
+
Args:
|
|
349
|
+
filepath: Path to save YAML file
|
|
350
|
+
|
|
351
|
+
Example:
|
|
352
|
+
>>> config = EmpathyConfig(user_id="alice", target_level=4)
|
|
353
|
+
>>> config.to_yaml("my-config.yml")
|
|
354
|
+
|
|
355
|
+
"""
|
|
356
|
+
if not YAML_AVAILABLE:
|
|
357
|
+
raise ImportError(
|
|
358
|
+
"PyYAML is required for YAML export. Install with: pip install pyyaml",
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
validated_path = _validate_file_path(filepath)
|
|
362
|
+
data = asdict(self)
|
|
363
|
+
|
|
364
|
+
with open(validated_path, "w") as f:
|
|
365
|
+
yaml.dump(data, f, default_flow_style=False, sort_keys=False)
|
|
366
|
+
|
|
367
|
+
def to_json(self, filepath: str, indent: int = 2):
|
|
368
|
+
"""Save configuration to JSON file
|
|
369
|
+
|
|
370
|
+
Args:
|
|
371
|
+
filepath: Path to save JSON file
|
|
372
|
+
indent: JSON indentation (default: 2)
|
|
373
|
+
|
|
374
|
+
Example:
|
|
375
|
+
>>> config = EmpathyConfig(user_id="alice", target_level=4)
|
|
376
|
+
>>> config.to_json("my-config.json")
|
|
377
|
+
|
|
378
|
+
"""
|
|
379
|
+
validated_path = _validate_file_path(filepath)
|
|
380
|
+
data = asdict(self)
|
|
381
|
+
|
|
382
|
+
with open(validated_path, "w") as f:
|
|
383
|
+
json.dump(data, f, indent=indent)
|
|
384
|
+
|
|
385
|
+
def to_dict(self) -> dict[str, Any]:
|
|
386
|
+
"""Convert configuration to dictionary"""
|
|
387
|
+
return asdict(self)
|
|
388
|
+
|
|
389
|
+
def update(self, **kwargs):
|
|
390
|
+
"""Update configuration fields
|
|
391
|
+
|
|
392
|
+
Args:
|
|
393
|
+
**kwargs: Fields to update
|
|
394
|
+
|
|
395
|
+
Example:
|
|
396
|
+
>>> config = EmpathyConfig()
|
|
397
|
+
>>> config.update(user_id="bob", target_level=5)
|
|
398
|
+
|
|
399
|
+
"""
|
|
400
|
+
for key, value in kwargs.items():
|
|
401
|
+
if hasattr(self, key):
|
|
402
|
+
setattr(self, key, value)
|
|
403
|
+
|
|
404
|
+
def merge(self, other: "EmpathyConfig") -> "EmpathyConfig":
|
|
405
|
+
"""Merge with another configuration (other takes precedence)
|
|
406
|
+
|
|
407
|
+
Args:
|
|
408
|
+
other: Configuration to merge
|
|
409
|
+
|
|
410
|
+
Returns:
|
|
411
|
+
New merged configuration
|
|
412
|
+
|
|
413
|
+
Example:
|
|
414
|
+
>>> base = EmpathyConfig(user_id="alice")
|
|
415
|
+
>>> override = EmpathyConfig(target_level=5)
|
|
416
|
+
>>> merged = base.merge(override)
|
|
417
|
+
|
|
418
|
+
"""
|
|
419
|
+
# Start with base values
|
|
420
|
+
base_dict = self.to_dict()
|
|
421
|
+
other_dict = other.to_dict()
|
|
422
|
+
|
|
423
|
+
# Get default values for comparison
|
|
424
|
+
defaults = EmpathyConfig().to_dict()
|
|
425
|
+
|
|
426
|
+
# Only update fields from 'other' that differ from defaults
|
|
427
|
+
for key, value in other_dict.items():
|
|
428
|
+
if value != defaults.get(key):
|
|
429
|
+
base_dict[key] = value
|
|
430
|
+
|
|
431
|
+
return EmpathyConfig(**base_dict)
|
|
432
|
+
|
|
433
|
+
def validate(self) -> bool:
|
|
434
|
+
"""Validate configuration values
|
|
435
|
+
|
|
436
|
+
Returns:
|
|
437
|
+
True if valid, raises ValueError if invalid
|
|
438
|
+
|
|
439
|
+
Raises:
|
|
440
|
+
ValueError: If configuration is invalid
|
|
441
|
+
|
|
442
|
+
"""
|
|
443
|
+
if self.target_level not in range(1, 6):
|
|
444
|
+
raise ValueError(f"target_level must be 1-5, got {self.target_level}")
|
|
445
|
+
|
|
446
|
+
if not 0.0 <= self.confidence_threshold <= 1.0:
|
|
447
|
+
raise ValueError(
|
|
448
|
+
f"confidence_threshold must be 0.0-1.0, got {self.confidence_threshold}",
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
if not 0.0 <= self.pattern_confidence_threshold <= 1.0:
|
|
452
|
+
threshold_val = self.pattern_confidence_threshold
|
|
453
|
+
raise ValueError(f"pattern_confidence_threshold must be 0.0-1.0, got {threshold_val}")
|
|
454
|
+
|
|
455
|
+
if self.persistence_backend not in ("sqlite", "json", "none"):
|
|
456
|
+
backend_val = self.persistence_backend
|
|
457
|
+
raise ValueError(
|
|
458
|
+
f"persistence_backend must be 'sqlite', 'json', or 'none', got {backend_val}",
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
return True
|
|
462
|
+
|
|
463
|
+
def __repr__(self) -> str:
|
|
464
|
+
"""String representation"""
|
|
465
|
+
return (
|
|
466
|
+
f"EmpathyConfig(user_id={self.user_id!r}, target_level={self.target_level}, "
|
|
467
|
+
f"confidence_threshold={self.confidence_threshold})"
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def load_config(
|
|
472
|
+
filepath: str | None = None,
|
|
473
|
+
use_env: bool = True,
|
|
474
|
+
defaults: dict[str, Any] | None = None,
|
|
475
|
+
) -> EmpathyConfig:
|
|
476
|
+
"""Load configuration with flexible precedence
|
|
477
|
+
|
|
478
|
+
Precedence (highest to lowest):
|
|
479
|
+
1. Environment variables (if use_env=True)
|
|
480
|
+
2. Configuration file (if provided/found)
|
|
481
|
+
3. Defaults (if provided)
|
|
482
|
+
4. Built-in defaults
|
|
483
|
+
|
|
484
|
+
Args:
|
|
485
|
+
filepath: Optional path to config file
|
|
486
|
+
use_env: Whether to check environment variables (default: True)
|
|
487
|
+
defaults: Optional default values
|
|
488
|
+
|
|
489
|
+
Returns:
|
|
490
|
+
EmpathyConfig instance
|
|
491
|
+
|
|
492
|
+
Example:
|
|
493
|
+
>>> # Load from file, override with env vars
|
|
494
|
+
>>> config = load_config("empathy.yml", use_env=True)
|
|
495
|
+
|
|
496
|
+
>>> # Load with custom defaults
|
|
497
|
+
>>> config = load_config(defaults={"target_level": 4})
|
|
498
|
+
|
|
499
|
+
"""
|
|
500
|
+
# Start with built-in defaults
|
|
501
|
+
config = EmpathyConfig()
|
|
502
|
+
|
|
503
|
+
# Apply custom defaults
|
|
504
|
+
if defaults:
|
|
505
|
+
config.update(**defaults)
|
|
506
|
+
|
|
507
|
+
# Load from file if provided/found
|
|
508
|
+
# First check if a file actually exists
|
|
509
|
+
file_found = False
|
|
510
|
+
if filepath and Path(filepath).exists():
|
|
511
|
+
file_found = True
|
|
512
|
+
else:
|
|
513
|
+
# Check default config file locations
|
|
514
|
+
for default_path in [
|
|
515
|
+
".empathy.yml",
|
|
516
|
+
".empathy.yaml",
|
|
517
|
+
"attune.config.yml",
|
|
518
|
+
"attune.config.yaml",
|
|
519
|
+
".empathy.json",
|
|
520
|
+
"attune.config.json",
|
|
521
|
+
]:
|
|
522
|
+
if Path(default_path).exists():
|
|
523
|
+
file_found = True
|
|
524
|
+
break
|
|
525
|
+
|
|
526
|
+
if file_found:
|
|
527
|
+
try:
|
|
528
|
+
file_config = EmpathyConfig.from_file(filepath)
|
|
529
|
+
config = config.merge(file_config)
|
|
530
|
+
except (FileNotFoundError, json.JSONDecodeError):
|
|
531
|
+
pass # Use defaults
|
|
532
|
+
|
|
533
|
+
# Override with environment variables
|
|
534
|
+
if use_env:
|
|
535
|
+
try:
|
|
536
|
+
env_config = EmpathyConfig.from_env()
|
|
537
|
+
config = config.merge(env_config)
|
|
538
|
+
except (ValueError, TypeError):
|
|
539
|
+
# Graceful fallback: invalid env var type conversion
|
|
540
|
+
pass # Use current config if environment parsing fails
|
|
541
|
+
|
|
542
|
+
# Validate final configuration
|
|
543
|
+
config.validate()
|
|
544
|
+
|
|
545
|
+
return config
|