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,441 @@
|
|
|
1
|
+
"""Leverage Point Analysis for System Interventions
|
|
2
|
+
|
|
3
|
+
Identifies high-leverage intervention points based on Donella Meadows's
|
|
4
|
+
seminal work "Leverage Points: Places to Intervene in a System".
|
|
5
|
+
|
|
6
|
+
Leverage points are places within a complex system where a small shift
|
|
7
|
+
can produce big changes in system behavior.
|
|
8
|
+
|
|
9
|
+
Copyright 2025 Smart AI Memory, LLC
|
|
10
|
+
Licensed under Fair Source 0.9
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import heapq
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from enum import IntEnum
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class LeverageLevel(IntEnum):
|
|
20
|
+
"""Donella Meadows's 12 Leverage Points (ordered by effectiveness)
|
|
21
|
+
|
|
22
|
+
Higher numbers = More effective leverage points
|
|
23
|
+
Lower numbers = Less effective (but often easier to implement)
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
# Low leverage (easy to change, small impact)
|
|
27
|
+
PARAMETERS = 1 # Constants, numbers (least effective)
|
|
28
|
+
BUFFERS = 2 # Stabilizing stocks relative to flows
|
|
29
|
+
STOCK_FLOW = 3 # Physical structure of system
|
|
30
|
+
DELAYS = 4 # Length of delays relative to change rate
|
|
31
|
+
BALANCING_LOOPS = 5 # Strength of negative feedback loops
|
|
32
|
+
REINFORCING_LOOPS = 6 # Strength of positive feedback loops
|
|
33
|
+
|
|
34
|
+
# Medium leverage
|
|
35
|
+
INFORMATION_FLOWS = 7 # Structure of information flows
|
|
36
|
+
RULES = 8 # Rules of the system (incentives, constraints)
|
|
37
|
+
SELF_ORGANIZATION = 9 # Power to add/change system structure
|
|
38
|
+
|
|
39
|
+
# High leverage (hard to change, huge impact)
|
|
40
|
+
GOALS = 10 # Goals of the system
|
|
41
|
+
PARADIGM = 11 # Mindset or paradigm out of which system arises
|
|
42
|
+
TRANSCEND_PARADIGM = 12 # Power to transcend paradigms (most effective)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class LeveragePoint:
|
|
47
|
+
"""A specific leverage point identified in the system
|
|
48
|
+
|
|
49
|
+
Represents a place where intervention can create significant change
|
|
50
|
+
in system behavior.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
level: LeverageLevel
|
|
54
|
+
description: str
|
|
55
|
+
problem_domain: str
|
|
56
|
+
impact_potential: float = 0.5 # 0.0-1.0
|
|
57
|
+
implementation_difficulty: float = 0.5 # 0.0-1.0
|
|
58
|
+
current_state: str | None = None
|
|
59
|
+
proposed_intervention: str | None = None
|
|
60
|
+
expected_outcomes: list[str] = field(default_factory=list)
|
|
61
|
+
risks: list[str] = field(default_factory=list)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class LeveragePointAnalyzer:
|
|
65
|
+
"""Identifies high-leverage intervention points in AI-human systems
|
|
66
|
+
|
|
67
|
+
Based on Donella Meadows's 12 leverage points. Helps identify where
|
|
68
|
+
to intervene in a system for maximum effectiveness.
|
|
69
|
+
|
|
70
|
+
**The 12 Leverage Points (in increasing order of effectiveness):**
|
|
71
|
+
|
|
72
|
+
1. **Parameters**: Constants, numbers (e.g., change tax rate)
|
|
73
|
+
- Least effective but easiest to change
|
|
74
|
+
|
|
75
|
+
2. **Buffers**: Size of stabilizing stocks
|
|
76
|
+
- Example: inventory sizes, account balances
|
|
77
|
+
|
|
78
|
+
3. **Stock-Flow Structure**: Physical system structure
|
|
79
|
+
- Example: road networks, factories
|
|
80
|
+
|
|
81
|
+
4. **Delays**: Length of delays relative to rate of change
|
|
82
|
+
- Example: feedback delay in learning systems
|
|
83
|
+
|
|
84
|
+
5. **Balancing Feedback Loops**: Strength of stabilizing loops
|
|
85
|
+
- Example: quality control mechanisms
|
|
86
|
+
|
|
87
|
+
6. **Reinforcing Feedback Loops**: Strength of amplifying loops
|
|
88
|
+
- Example: compound interest, viral growth
|
|
89
|
+
|
|
90
|
+
7. **Information Flows**: Structure of information flows
|
|
91
|
+
- Example: transparency, data access
|
|
92
|
+
|
|
93
|
+
8. **Rules**: Incentives, punishments, constraints
|
|
94
|
+
- Example: laws, policies, protocols
|
|
95
|
+
|
|
96
|
+
9. **Self-Organization**: Power to evolve system structure
|
|
97
|
+
- Example: ability to create new rules
|
|
98
|
+
|
|
99
|
+
10. **Goals**: Purpose or function of the system
|
|
100
|
+
- Example: shift from growth to sustainability
|
|
101
|
+
|
|
102
|
+
11. **Paradigm**: Mindset underlying the system
|
|
103
|
+
- Example: worldview, shared assumptions
|
|
104
|
+
|
|
105
|
+
12. **Transcending Paradigms**: Keep perspective on all paradigms
|
|
106
|
+
- Most effective but hardest to achieve
|
|
107
|
+
|
|
108
|
+
Example:
|
|
109
|
+
>>> analyzer = LeveragePointAnalyzer()
|
|
110
|
+
>>> problem = {
|
|
111
|
+
... "class": "documentation_burden",
|
|
112
|
+
... "description": "Developers spend 40% time on repetitive docs",
|
|
113
|
+
... "instances": 18
|
|
114
|
+
... }
|
|
115
|
+
>>> points = analyzer.find_leverage_points(problem)
|
|
116
|
+
>>> for point in points:
|
|
117
|
+
... print(f"{point.level.name}: {point.description}")
|
|
118
|
+
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
def __init__(self):
|
|
122
|
+
"""Initialize LeveragePointAnalyzer"""
|
|
123
|
+
self.identified_points: list[LeveragePoint] = []
|
|
124
|
+
|
|
125
|
+
def find_leverage_points(self, problem_class: dict[str, Any]) -> list[LeveragePoint]:
|
|
126
|
+
"""Find high-leverage intervention points for a problem class
|
|
127
|
+
|
|
128
|
+
Analyzes the problem and identifies leverage points at different
|
|
129
|
+
levels of the Meadows hierarchy.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
problem_class: Dict with keys:
|
|
133
|
+
- class: Problem category (e.g., "documentation_burden")
|
|
134
|
+
- description: Problem description
|
|
135
|
+
- instances: Number of occurrences (optional)
|
|
136
|
+
- context: Additional context (optional)
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
List of leverage points, ranked by effectiveness
|
|
140
|
+
|
|
141
|
+
Example:
|
|
142
|
+
>>> problem = {
|
|
143
|
+
... "class": "trust_deficit",
|
|
144
|
+
... "description": "Users don't trust AI recommendations",
|
|
145
|
+
... "instances": 50
|
|
146
|
+
... }
|
|
147
|
+
>>> points = analyzer.find_leverage_points(problem)
|
|
148
|
+
|
|
149
|
+
"""
|
|
150
|
+
points: list[LeveragePoint] = []
|
|
151
|
+
problem_type = problem_class.get("class", "unknown")
|
|
152
|
+
description = problem_class.get("description", "")
|
|
153
|
+
|
|
154
|
+
# Analyze based on problem type
|
|
155
|
+
if "documentation" in problem_type.lower() or "documentation" in description.lower():
|
|
156
|
+
points.extend(self._analyze_documentation_problem(problem_class))
|
|
157
|
+
|
|
158
|
+
elif "trust" in problem_type.lower() or "trust" in description.lower():
|
|
159
|
+
points.extend(self._analyze_trust_problem(problem_class))
|
|
160
|
+
|
|
161
|
+
elif "efficiency" in problem_type.lower() or "speed" in description.lower():
|
|
162
|
+
points.extend(self._analyze_efficiency_problem(problem_class))
|
|
163
|
+
|
|
164
|
+
else:
|
|
165
|
+
# Generic analysis for unknown problem types
|
|
166
|
+
points.extend(self._generic_leverage_analysis(problem_class))
|
|
167
|
+
|
|
168
|
+
# Rank by effectiveness (leverage level)
|
|
169
|
+
points_ranked = self.rank_by_effectiveness(points)
|
|
170
|
+
|
|
171
|
+
self.identified_points.extend(points_ranked)
|
|
172
|
+
return points_ranked
|
|
173
|
+
|
|
174
|
+
def rank_by_effectiveness(self, points: list[LeveragePoint]) -> list[LeveragePoint]:
|
|
175
|
+
"""Rank leverage points by Meadows's hierarchy
|
|
176
|
+
|
|
177
|
+
Higher leverage levels (paradigms, goals) ranked before lower
|
|
178
|
+
levels (parameters, buffers).
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
points: List of leverage points to rank
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
Sorted list with most effective points first
|
|
185
|
+
|
|
186
|
+
"""
|
|
187
|
+
return sorted(points, key=lambda p: p.level, reverse=True)
|
|
188
|
+
|
|
189
|
+
def _analyze_documentation_problem(self, problem: dict[str, Any]) -> list[LeveragePoint]:
|
|
190
|
+
"""Analyze leverage points for documentation problems"""
|
|
191
|
+
points = []
|
|
192
|
+
|
|
193
|
+
# High leverage: Change paradigm (how we think about docs)
|
|
194
|
+
points.append(
|
|
195
|
+
LeveragePoint(
|
|
196
|
+
level=LeverageLevel.PARADIGM,
|
|
197
|
+
description="Paradigm shift: Docs as learning artifact, not compliance burden",
|
|
198
|
+
problem_domain="documentation",
|
|
199
|
+
impact_potential=0.9,
|
|
200
|
+
implementation_difficulty=0.8,
|
|
201
|
+
proposed_intervention="Reframe docs as 'capturing team learning' not 'creating artifacts'",
|
|
202
|
+
expected_outcomes=[
|
|
203
|
+
"Developers see value in documentation",
|
|
204
|
+
"Documentation becomes natural part of workflow",
|
|
205
|
+
"Quality improves as purpose clarifies",
|
|
206
|
+
],
|
|
207
|
+
),
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
# High leverage: Change goal
|
|
211
|
+
points.append(
|
|
212
|
+
LeveragePoint(
|
|
213
|
+
level=LeverageLevel.GOALS,
|
|
214
|
+
description="Change goal: From 'comprehensive docs' to 'shared understanding'",
|
|
215
|
+
problem_domain="documentation",
|
|
216
|
+
impact_potential=0.85,
|
|
217
|
+
implementation_difficulty=0.6,
|
|
218
|
+
proposed_intervention="Optimize for team understanding not document completeness",
|
|
219
|
+
expected_outcomes=[
|
|
220
|
+
"Focus on what matters",
|
|
221
|
+
"Less redundant documentation",
|
|
222
|
+
"More collaboration",
|
|
223
|
+
],
|
|
224
|
+
),
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
# Medium leverage: Self-organization (Level 5 systems thinking)
|
|
228
|
+
points.append(
|
|
229
|
+
LeveragePoint(
|
|
230
|
+
level=LeverageLevel.SELF_ORGANIZATION,
|
|
231
|
+
description="Enable self-organization: AI agents auto-generate docs from patterns",
|
|
232
|
+
problem_domain="documentation",
|
|
233
|
+
impact_potential=0.8,
|
|
234
|
+
implementation_difficulty=0.5,
|
|
235
|
+
proposed_intervention="Deploy Level 5 system that detects patterns and auto-documents",
|
|
236
|
+
expected_outcomes=[
|
|
237
|
+
"Reduce manual documentation by 70%",
|
|
238
|
+
"Free developers for creative work",
|
|
239
|
+
"Maintain quality through pattern detection",
|
|
240
|
+
],
|
|
241
|
+
),
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
# Low leverage: Parameters (quickest but least impactful)
|
|
245
|
+
points.append(
|
|
246
|
+
LeveragePoint(
|
|
247
|
+
level=LeverageLevel.PARAMETERS,
|
|
248
|
+
description="Adjust parameters: Reduce required documentation fields",
|
|
249
|
+
problem_domain="documentation",
|
|
250
|
+
impact_potential=0.3,
|
|
251
|
+
implementation_difficulty=0.1,
|
|
252
|
+
proposed_intervention="Cut required fields from 20 to 8",
|
|
253
|
+
expected_outcomes=["Faster documentation", "May not address root cause"],
|
|
254
|
+
),
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
return points
|
|
258
|
+
|
|
259
|
+
def _analyze_trust_problem(self, problem: dict[str, Any]) -> list[LeveragePoint]:
|
|
260
|
+
"""Analyze leverage points for trust problems"""
|
|
261
|
+
points = []
|
|
262
|
+
|
|
263
|
+
# High leverage: Paradigm shift
|
|
264
|
+
points.append(
|
|
265
|
+
LeveragePoint(
|
|
266
|
+
level=LeverageLevel.PARADIGM,
|
|
267
|
+
description="Shift paradigm: AI as collaborator, not tool",
|
|
268
|
+
problem_domain="trust",
|
|
269
|
+
impact_potential=0.9,
|
|
270
|
+
implementation_difficulty=0.8,
|
|
271
|
+
proposed_intervention="Reframe AI relationship from automation to collaboration",
|
|
272
|
+
expected_outcomes=[
|
|
273
|
+
"Users engage differently with AI",
|
|
274
|
+
"Set appropriate expectations",
|
|
275
|
+
"Build genuine partnership",
|
|
276
|
+
],
|
|
277
|
+
),
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
# High leverage: Information flows
|
|
281
|
+
points.append(
|
|
282
|
+
LeveragePoint(
|
|
283
|
+
level=LeverageLevel.INFORMATION_FLOWS,
|
|
284
|
+
description="Increase transparency: Show AI reasoning process",
|
|
285
|
+
problem_domain="trust",
|
|
286
|
+
impact_potential=0.75,
|
|
287
|
+
implementation_difficulty=0.4,
|
|
288
|
+
proposed_intervention="Implement explainable AI with visible reasoning chains",
|
|
289
|
+
expected_outcomes=[
|
|
290
|
+
"Users understand AI decisions",
|
|
291
|
+
"Can verify AI logic",
|
|
292
|
+
"Trust through transparency",
|
|
293
|
+
],
|
|
294
|
+
),
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
# Medium leverage: Reinforcing feedback loops
|
|
298
|
+
points.append(
|
|
299
|
+
LeveragePoint(
|
|
300
|
+
level=LeverageLevel.REINFORCING_LOOPS,
|
|
301
|
+
description="Activate virtuous cycle: Success → Trust → Delegation → More Success",
|
|
302
|
+
problem_domain="trust",
|
|
303
|
+
impact_potential=0.7,
|
|
304
|
+
implementation_difficulty=0.5,
|
|
305
|
+
proposed_intervention="Start with high-confidence, low-risk tasks for momentum",
|
|
306
|
+
expected_outcomes=[
|
|
307
|
+
"Quick wins build trust",
|
|
308
|
+
"Positive feedback loop activated",
|
|
309
|
+
"Natural progression to harder tasks",
|
|
310
|
+
],
|
|
311
|
+
),
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
return points
|
|
315
|
+
|
|
316
|
+
def _analyze_efficiency_problem(self, problem: dict[str, Any]) -> list[LeveragePoint]:
|
|
317
|
+
"""Analyze leverage points for efficiency problems"""
|
|
318
|
+
points = []
|
|
319
|
+
|
|
320
|
+
# High leverage: Goals
|
|
321
|
+
points.append(
|
|
322
|
+
LeveragePoint(
|
|
323
|
+
level=LeverageLevel.GOALS,
|
|
324
|
+
description="Redefine goal: From 'fast completion' to 'sustainable pace'",
|
|
325
|
+
problem_domain="efficiency",
|
|
326
|
+
impact_potential=0.8,
|
|
327
|
+
implementation_difficulty=0.6,
|
|
328
|
+
proposed_intervention="Optimize for long-term throughput not short-term speed",
|
|
329
|
+
expected_outcomes=["Prevent burnout", "Sustainable productivity", "Higher quality"],
|
|
330
|
+
),
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
# Medium leverage: Delays
|
|
334
|
+
points.append(
|
|
335
|
+
LeveragePoint(
|
|
336
|
+
level=LeverageLevel.DELAYS,
|
|
337
|
+
description="Reduce feedback delays: Real-time testing and validation",
|
|
338
|
+
problem_domain="efficiency",
|
|
339
|
+
impact_potential=0.65,
|
|
340
|
+
implementation_difficulty=0.4,
|
|
341
|
+
proposed_intervention="Implement continuous testing with instant feedback",
|
|
342
|
+
expected_outcomes=["Faster iteration", "Catch errors early", "Better learning"],
|
|
343
|
+
),
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
return points
|
|
347
|
+
|
|
348
|
+
def _generic_leverage_analysis(self, problem: dict[str, Any]) -> list[LeveragePoint]:
|
|
349
|
+
"""Generic leverage point analysis for unknown problem types"""
|
|
350
|
+
points = []
|
|
351
|
+
|
|
352
|
+
# Always consider paradigm shift (highest leverage)
|
|
353
|
+
points.append(
|
|
354
|
+
LeveragePoint(
|
|
355
|
+
level=LeverageLevel.PARADIGM,
|
|
356
|
+
description="Question underlying assumptions about this problem",
|
|
357
|
+
problem_domain=problem.get("class", "unknown"),
|
|
358
|
+
impact_potential=0.8,
|
|
359
|
+
implementation_difficulty=0.8,
|
|
360
|
+
proposed_intervention="Examine and challenge fundamental beliefs about problem",
|
|
361
|
+
),
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
# Consider information flows
|
|
365
|
+
points.append(
|
|
366
|
+
LeveragePoint(
|
|
367
|
+
level=LeverageLevel.INFORMATION_FLOWS,
|
|
368
|
+
description="Improve information flow and transparency",
|
|
369
|
+
problem_domain=problem.get("class", "unknown"),
|
|
370
|
+
impact_potential=0.6,
|
|
371
|
+
implementation_difficulty=0.4,
|
|
372
|
+
proposed_intervention="Make relevant information more accessible to stakeholders",
|
|
373
|
+
),
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
return points
|
|
377
|
+
|
|
378
|
+
def get_top_leverage_points(
|
|
379
|
+
self,
|
|
380
|
+
n: int = 3,
|
|
381
|
+
min_level: LeverageLevel | None = None,
|
|
382
|
+
) -> list[LeveragePoint]:
|
|
383
|
+
"""Get top N leverage points, optionally filtered by minimum level
|
|
384
|
+
|
|
385
|
+
Args:
|
|
386
|
+
n: Number of top points to return
|
|
387
|
+
min_level: Optional minimum leverage level to consider
|
|
388
|
+
|
|
389
|
+
Returns:
|
|
390
|
+
Top N leverage points
|
|
391
|
+
|
|
392
|
+
"""
|
|
393
|
+
points = self.identified_points
|
|
394
|
+
|
|
395
|
+
if min_level:
|
|
396
|
+
points = [p for p in points if p.level >= min_level]
|
|
397
|
+
|
|
398
|
+
return heapq.nlargest(n, points, key=lambda p: p.level)
|
|
399
|
+
|
|
400
|
+
def analyze_intervention_feasibility(self, point: LeveragePoint) -> dict[str, Any]:
|
|
401
|
+
"""Analyze feasibility of intervening at a leverage point
|
|
402
|
+
|
|
403
|
+
Considers:
|
|
404
|
+
- Impact potential
|
|
405
|
+
- Implementation difficulty
|
|
406
|
+
- Risk level
|
|
407
|
+
- Time to results
|
|
408
|
+
|
|
409
|
+
Args:
|
|
410
|
+
point: Leverage point to analyze
|
|
411
|
+
|
|
412
|
+
Returns:
|
|
413
|
+
Feasibility analysis with recommendation
|
|
414
|
+
|
|
415
|
+
"""
|
|
416
|
+
# Calculate feasibility score (impact vs difficulty)
|
|
417
|
+
feasibility_score = point.impact_potential / max(point.implementation_difficulty, 0.1)
|
|
418
|
+
|
|
419
|
+
# Determine recommendation
|
|
420
|
+
if feasibility_score > 1.5:
|
|
421
|
+
recommendation = "HIGHLY RECOMMENDED: High impact, manageable difficulty"
|
|
422
|
+
elif feasibility_score > 1.0:
|
|
423
|
+
recommendation = "RECOMMENDED: Good balance of impact and feasibility"
|
|
424
|
+
elif feasibility_score > 0.7:
|
|
425
|
+
recommendation = "CONSIDER: Significant effort but worthwhile impact"
|
|
426
|
+
else:
|
|
427
|
+
recommendation = "CAUTION: High difficulty relative to impact"
|
|
428
|
+
|
|
429
|
+
return {
|
|
430
|
+
"leverage_level": point.level.name,
|
|
431
|
+
"impact_potential": point.impact_potential,
|
|
432
|
+
"implementation_difficulty": point.implementation_difficulty,
|
|
433
|
+
"feasibility_score": feasibility_score,
|
|
434
|
+
"recommendation": recommendation,
|
|
435
|
+
"risks": point.risks,
|
|
436
|
+
"expected_outcomes": point.expected_outcomes,
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
def reset(self):
|
|
440
|
+
"""Reset analyzer state"""
|
|
441
|
+
self.identified_points = []
|
attune/logging_config.py
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"""Centralized Logging Configuration for Empathy Framework
|
|
2
|
+
|
|
3
|
+
Provides professional logging setup with:
|
|
4
|
+
- Console and file logging
|
|
5
|
+
- Structured logging with context
|
|
6
|
+
- Log rotation for production use
|
|
7
|
+
- Configurable log levels
|
|
8
|
+
|
|
9
|
+
Copyright 2025 Smart AI Memory, LLC
|
|
10
|
+
Licensed under Fair Source 0.9
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
import logging.handlers
|
|
15
|
+
import os
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class StructuredFormatter(logging.Formatter):
|
|
21
|
+
"""Custom formatter for structured logging with context."""
|
|
22
|
+
|
|
23
|
+
COLORS = {
|
|
24
|
+
"DEBUG": "\033[36m", # Cyan
|
|
25
|
+
"INFO": "\033[32m", # Green
|
|
26
|
+
"WARNING": "\033[33m", # Yellow
|
|
27
|
+
"ERROR": "\033[31m", # Red
|
|
28
|
+
"CRITICAL": "\033[35m", # Magenta
|
|
29
|
+
}
|
|
30
|
+
RESET = "\033[0m"
|
|
31
|
+
BOLD = "\033[1m"
|
|
32
|
+
|
|
33
|
+
def __init__(self, use_color: bool = True, include_context: bool = False):
|
|
34
|
+
"""Initialize formatter.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
use_color: Whether to use colored output
|
|
38
|
+
include_context: Whether to include contextual information
|
|
39
|
+
|
|
40
|
+
"""
|
|
41
|
+
self.use_color = use_color and sys.stderr.isatty()
|
|
42
|
+
self.include_context = include_context
|
|
43
|
+
super().__init__()
|
|
44
|
+
|
|
45
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
46
|
+
"""Format log record with optional colors and context."""
|
|
47
|
+
# Get color for this level
|
|
48
|
+
color = self.COLORS.get(record.levelname, self.RESET) if self.use_color else ""
|
|
49
|
+
reset = self.RESET if self.use_color else ""
|
|
50
|
+
|
|
51
|
+
# Build the main log message
|
|
52
|
+
timestamp = self.formatTime(record, "%Y-%m-%d %H:%M:%S")
|
|
53
|
+
level_name = f"{color}{record.levelname}{reset}" if self.use_color else record.levelname
|
|
54
|
+
|
|
55
|
+
# Format: [TIMESTAMP] [LEVEL] module.function: message
|
|
56
|
+
log_msg = (
|
|
57
|
+
f"[{timestamp}] [{level_name}] {record.name}:{record.funcName}: {record.getMessage()}"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Add context if available
|
|
61
|
+
if self.include_context and hasattr(record, "context"):
|
|
62
|
+
context_str = " ".join(f"{k}={v}" for k, v in record.context.items())
|
|
63
|
+
log_msg += f" [{context_str}]"
|
|
64
|
+
|
|
65
|
+
# Add exception info if present
|
|
66
|
+
if record.exc_info:
|
|
67
|
+
log_msg += "\n" + self.formatException(record.exc_info)
|
|
68
|
+
|
|
69
|
+
return log_msg
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def create_logger(
|
|
73
|
+
name: str,
|
|
74
|
+
level: int = logging.INFO,
|
|
75
|
+
log_file: str | None = None,
|
|
76
|
+
log_dir: str | None = None,
|
|
77
|
+
use_color: bool = True,
|
|
78
|
+
include_context: bool = False,
|
|
79
|
+
max_bytes: int = 10 * 1024 * 1024, # 10MB
|
|
80
|
+
backup_count: int = 5,
|
|
81
|
+
) -> logging.Logger:
|
|
82
|
+
"""Create a configured logger instance.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
name: Logger name (typically __name__)
|
|
86
|
+
level: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
|
87
|
+
log_file: Optional log file path
|
|
88
|
+
log_dir: Optional log directory for file-based logging
|
|
89
|
+
use_color: Whether to use colored console output
|
|
90
|
+
include_context: Whether to include contextual information
|
|
91
|
+
max_bytes: Max size of log file before rotation
|
|
92
|
+
backup_count: Number of backup files to keep
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
Configured logger instance
|
|
96
|
+
|
|
97
|
+
Example:
|
|
98
|
+
>>> logger = create_logger(__name__, level=logging.DEBUG)
|
|
99
|
+
>>> logger.info("Application started")
|
|
100
|
+
>>> logger.debug("Detailed debug information")
|
|
101
|
+
|
|
102
|
+
"""
|
|
103
|
+
logger = logging.getLogger(name)
|
|
104
|
+
|
|
105
|
+
# Avoid adding handlers multiple times
|
|
106
|
+
if logger.handlers:
|
|
107
|
+
return logger
|
|
108
|
+
|
|
109
|
+
logger.setLevel(level)
|
|
110
|
+
|
|
111
|
+
# Console handler
|
|
112
|
+
console_handler = logging.StreamHandler(sys.stderr)
|
|
113
|
+
console_handler.setLevel(level)
|
|
114
|
+
console_formatter = StructuredFormatter(use_color=use_color, include_context=include_context)
|
|
115
|
+
console_handler.setFormatter(console_formatter)
|
|
116
|
+
logger.addHandler(console_handler)
|
|
117
|
+
|
|
118
|
+
# File handler with rotation (if log_file or log_dir specified)
|
|
119
|
+
if log_file or log_dir:
|
|
120
|
+
if log_dir:
|
|
121
|
+
log_dir_path = Path(log_dir)
|
|
122
|
+
log_dir_path.mkdir(parents=True, exist_ok=True)
|
|
123
|
+
log_file_path = log_dir_path / f"{name.replace('.', '_')}.log"
|
|
124
|
+
else:
|
|
125
|
+
log_file_path = Path(log_file) # type: ignore[arg-type]
|
|
126
|
+
log_file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
127
|
+
|
|
128
|
+
file_handler = logging.handlers.RotatingFileHandler(
|
|
129
|
+
str(log_file_path),
|
|
130
|
+
maxBytes=max_bytes,
|
|
131
|
+
backupCount=backup_count,
|
|
132
|
+
)
|
|
133
|
+
file_handler.setLevel(level)
|
|
134
|
+
file_formatter = StructuredFormatter(use_color=False, include_context=include_context)
|
|
135
|
+
file_handler.setFormatter(file_formatter)
|
|
136
|
+
logger.addHandler(file_handler)
|
|
137
|
+
|
|
138
|
+
return logger
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class LoggingConfig:
|
|
142
|
+
"""Centralized logging configuration manager."""
|
|
143
|
+
|
|
144
|
+
_configured = False
|
|
145
|
+
_loggers: dict[str, logging.Logger] = {}
|
|
146
|
+
_level: int = logging.INFO
|
|
147
|
+
_log_dir: str | None = None
|
|
148
|
+
_use_color: bool = True
|
|
149
|
+
_include_context: bool = False
|
|
150
|
+
|
|
151
|
+
@classmethod
|
|
152
|
+
def configure(
|
|
153
|
+
cls,
|
|
154
|
+
level: int = logging.INFO,
|
|
155
|
+
log_dir: str | None = None,
|
|
156
|
+
use_color: bool = True,
|
|
157
|
+
include_context: bool = False,
|
|
158
|
+
) -> None:
|
|
159
|
+
"""Configure global logging settings.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
level: Default logging level
|
|
163
|
+
log_dir: Directory for log files
|
|
164
|
+
use_color: Whether to use colored output
|
|
165
|
+
include_context: Whether to include contextual information
|
|
166
|
+
|
|
167
|
+
Example:
|
|
168
|
+
>>> LoggingConfig.configure(
|
|
169
|
+
... level=logging.DEBUG,
|
|
170
|
+
... log_dir="./logs",
|
|
171
|
+
... use_color=True
|
|
172
|
+
... )
|
|
173
|
+
|
|
174
|
+
"""
|
|
175
|
+
cls._level = level
|
|
176
|
+
cls._log_dir = log_dir
|
|
177
|
+
cls._use_color = use_color
|
|
178
|
+
cls._include_context = include_context
|
|
179
|
+
cls._configured = True
|
|
180
|
+
|
|
181
|
+
@classmethod
|
|
182
|
+
def get_logger(
|
|
183
|
+
cls,
|
|
184
|
+
name: str,
|
|
185
|
+
level: int | None = None,
|
|
186
|
+
) -> logging.Logger:
|
|
187
|
+
"""Get or create a logger instance.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
name: Logger name (typically __name__)
|
|
191
|
+
level: Optional override for logging level
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Configured logger instance
|
|
195
|
+
|
|
196
|
+
Example:
|
|
197
|
+
>>> logger = LoggingConfig.get_logger(__name__)
|
|
198
|
+
>>> logger.info("Processing data")
|
|
199
|
+
|
|
200
|
+
"""
|
|
201
|
+
if name not in cls._loggers:
|
|
202
|
+
if not cls._configured:
|
|
203
|
+
cls.configure()
|
|
204
|
+
|
|
205
|
+
logger = create_logger(
|
|
206
|
+
name,
|
|
207
|
+
level=level or cls._level,
|
|
208
|
+
log_dir=cls._log_dir,
|
|
209
|
+
use_color=cls._use_color,
|
|
210
|
+
include_context=cls._include_context,
|
|
211
|
+
)
|
|
212
|
+
cls._loggers[name] = logger
|
|
213
|
+
|
|
214
|
+
return cls._loggers[name]
|
|
215
|
+
|
|
216
|
+
@classmethod
|
|
217
|
+
def set_level(cls, level: int) -> None:
|
|
218
|
+
"""Set logging level for all loggers."""
|
|
219
|
+
for logger in cls._loggers.values():
|
|
220
|
+
logger.setLevel(level)
|
|
221
|
+
for handler in logger.handlers:
|
|
222
|
+
handler.setLevel(level)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def get_logger(name: str) -> logging.Logger:
|
|
226
|
+
"""Convenience function to get a logger.
|
|
227
|
+
|
|
228
|
+
This is the primary function to use throughout the codebase.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
name: Logger name (typically __name__)
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
Configured logger instance
|
|
235
|
+
|
|
236
|
+
Example:
|
|
237
|
+
>>> from attune.logging_config import get_logger
|
|
238
|
+
>>> logger = get_logger(__name__)
|
|
239
|
+
>>> logger.info("Starting process")
|
|
240
|
+
>>> logger.debug("Processing item", extra={"context": {"item_id": 123}})
|
|
241
|
+
|
|
242
|
+
"""
|
|
243
|
+
return LoggingConfig.get_logger(name)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
# Initialize with environment variable support
|
|
247
|
+
def init_logging_from_env() -> None:
|
|
248
|
+
"""Initialize logging configuration from environment variables."""
|
|
249
|
+
log_level_str = os.getenv("EMPATHY_LOG_LEVEL", "INFO").upper()
|
|
250
|
+
log_level = getattr(logging, log_level_str, logging.INFO)
|
|
251
|
+
|
|
252
|
+
log_dir = os.getenv("EMPATHY_LOG_DIR")
|
|
253
|
+
use_color = os.getenv("EMPATHY_LOG_COLOR", "true").lower() == "true"
|
|
254
|
+
include_context = os.getenv("EMPATHY_LOG_CONTEXT", "false").lower() == "true"
|
|
255
|
+
|
|
256
|
+
LoggingConfig.configure(
|
|
257
|
+
level=log_level,
|
|
258
|
+
log_dir=log_dir,
|
|
259
|
+
use_color=use_color,
|
|
260
|
+
include_context=include_context,
|
|
261
|
+
)
|