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,489 @@
|
|
|
1
|
+
"""Telemetry storage implementation.
|
|
2
|
+
|
|
3
|
+
File-based storage for telemetry records.
|
|
4
|
+
|
|
5
|
+
Copyright 2025 Smart-AI-Memory
|
|
6
|
+
Licensed under Fair Source License 0.9
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from .backend import _parse_timestamp
|
|
14
|
+
from .data_models import (
|
|
15
|
+
AgentAssignmentRecord,
|
|
16
|
+
CoverageRecord,
|
|
17
|
+
FileTestRecord,
|
|
18
|
+
LLMCallRecord,
|
|
19
|
+
TaskRoutingRecord,
|
|
20
|
+
TestExecutionRecord,
|
|
21
|
+
WorkflowRunRecord,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TelemetryStore:
|
|
26
|
+
"""JSONL file-based telemetry backend (default implementation).
|
|
27
|
+
|
|
28
|
+
Stores records in JSONL format for easy streaming and analysis.
|
|
29
|
+
Implements the TelemetryBackend protocol.
|
|
30
|
+
|
|
31
|
+
Supports both core telemetry and Tier 1 automation monitoring.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, storage_dir: str = ".empathy"):
|
|
35
|
+
"""Initialize telemetry store.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
storage_dir: Directory for telemetry files
|
|
39
|
+
|
|
40
|
+
"""
|
|
41
|
+
self.storage_dir = Path(storage_dir)
|
|
42
|
+
self.storage_dir.mkdir(parents=True, exist_ok=True)
|
|
43
|
+
|
|
44
|
+
# Core telemetry files
|
|
45
|
+
self.calls_file = self.storage_dir / "llm_calls.jsonl"
|
|
46
|
+
self.workflows_file = self.storage_dir / "workflow_runs.jsonl"
|
|
47
|
+
|
|
48
|
+
# Tier 1 automation monitoring files
|
|
49
|
+
self.task_routing_file = self.storage_dir / "task_routing.jsonl"
|
|
50
|
+
self.test_executions_file = self.storage_dir / "test_executions.jsonl"
|
|
51
|
+
self.coverage_history_file = self.storage_dir / "coverage_history.jsonl"
|
|
52
|
+
self.agent_assignments_file = self.storage_dir / "agent_assignments.jsonl"
|
|
53
|
+
|
|
54
|
+
# Per-file test tracking
|
|
55
|
+
self.file_tests_file = self.storage_dir / "file_tests.jsonl"
|
|
56
|
+
|
|
57
|
+
def log_call(self, record: LLMCallRecord) -> None:
|
|
58
|
+
"""Log an LLM call record."""
|
|
59
|
+
with open(self.calls_file, "a") as f:
|
|
60
|
+
f.write(json.dumps(record.to_dict()) + "\n")
|
|
61
|
+
|
|
62
|
+
def log_workflow(self, record: WorkflowRunRecord) -> None:
|
|
63
|
+
"""Log a workflow run record."""
|
|
64
|
+
with open(self.workflows_file, "a") as f:
|
|
65
|
+
f.write(json.dumps(record.to_dict()) + "\n")
|
|
66
|
+
|
|
67
|
+
def get_calls(
|
|
68
|
+
self,
|
|
69
|
+
since: datetime | None = None,
|
|
70
|
+
workflow_name: str | None = None,
|
|
71
|
+
limit: int = 1000,
|
|
72
|
+
) -> list[LLMCallRecord]:
|
|
73
|
+
"""Get LLM call records.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
since: Only return records after this time
|
|
77
|
+
workflow_name: Filter by workflow name
|
|
78
|
+
limit: Maximum records to return
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
List of LLMCallRecord
|
|
82
|
+
|
|
83
|
+
"""
|
|
84
|
+
records: list[LLMCallRecord] = []
|
|
85
|
+
if not self.calls_file.exists():
|
|
86
|
+
return records
|
|
87
|
+
|
|
88
|
+
with open(self.calls_file) as f:
|
|
89
|
+
for line in f:
|
|
90
|
+
if not line.strip():
|
|
91
|
+
continue
|
|
92
|
+
try:
|
|
93
|
+
data = json.loads(line)
|
|
94
|
+
record = LLMCallRecord.from_dict(data)
|
|
95
|
+
|
|
96
|
+
# Apply filters
|
|
97
|
+
if since:
|
|
98
|
+
record_time = _parse_timestamp(record.timestamp)
|
|
99
|
+
if record_time < since:
|
|
100
|
+
continue
|
|
101
|
+
|
|
102
|
+
if workflow_name and record.workflow_name != workflow_name:
|
|
103
|
+
continue
|
|
104
|
+
|
|
105
|
+
records.append(record)
|
|
106
|
+
|
|
107
|
+
if len(records) >= limit:
|
|
108
|
+
break
|
|
109
|
+
except (json.JSONDecodeError, KeyError):
|
|
110
|
+
continue
|
|
111
|
+
|
|
112
|
+
return records
|
|
113
|
+
|
|
114
|
+
def get_workflows(
|
|
115
|
+
self,
|
|
116
|
+
since: datetime | None = None,
|
|
117
|
+
workflow_name: str | None = None,
|
|
118
|
+
limit: int = 100,
|
|
119
|
+
) -> list[WorkflowRunRecord]:
|
|
120
|
+
"""Get workflow run records.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
since: Only return records after this time
|
|
124
|
+
workflow_name: Filter by workflow name
|
|
125
|
+
limit: Maximum records to return
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
List of WorkflowRunRecord
|
|
129
|
+
|
|
130
|
+
"""
|
|
131
|
+
records: list[WorkflowRunRecord] = []
|
|
132
|
+
if not self.workflows_file.exists():
|
|
133
|
+
return records
|
|
134
|
+
|
|
135
|
+
with open(self.workflows_file) as f:
|
|
136
|
+
for line in f:
|
|
137
|
+
if not line.strip():
|
|
138
|
+
continue
|
|
139
|
+
try:
|
|
140
|
+
data = json.loads(line)
|
|
141
|
+
record = WorkflowRunRecord.from_dict(data)
|
|
142
|
+
|
|
143
|
+
# Apply filters
|
|
144
|
+
if since:
|
|
145
|
+
record_time = _parse_timestamp(record.started_at)
|
|
146
|
+
if record_time < since:
|
|
147
|
+
continue
|
|
148
|
+
|
|
149
|
+
if workflow_name and record.workflow_name != workflow_name:
|
|
150
|
+
continue
|
|
151
|
+
|
|
152
|
+
records.append(record)
|
|
153
|
+
|
|
154
|
+
if len(records) >= limit:
|
|
155
|
+
break
|
|
156
|
+
except (json.JSONDecodeError, KeyError):
|
|
157
|
+
continue
|
|
158
|
+
|
|
159
|
+
return records
|
|
160
|
+
|
|
161
|
+
# Tier 1 automation monitoring methods
|
|
162
|
+
|
|
163
|
+
def log_task_routing(self, record: TaskRoutingRecord) -> None:
|
|
164
|
+
"""Log a task routing decision."""
|
|
165
|
+
with open(self.task_routing_file, "a") as f:
|
|
166
|
+
f.write(json.dumps(record.to_dict()) + "\n")
|
|
167
|
+
|
|
168
|
+
def log_test_execution(self, record: TestExecutionRecord) -> None:
|
|
169
|
+
"""Log a test execution."""
|
|
170
|
+
with open(self.test_executions_file, "a") as f:
|
|
171
|
+
f.write(json.dumps(record.to_dict()) + "\n")
|
|
172
|
+
|
|
173
|
+
def log_coverage(self, record: CoverageRecord) -> None:
|
|
174
|
+
"""Log coverage metrics."""
|
|
175
|
+
with open(self.coverage_history_file, "a") as f:
|
|
176
|
+
f.write(json.dumps(record.to_dict()) + "\n")
|
|
177
|
+
|
|
178
|
+
def log_agent_assignment(self, record: AgentAssignmentRecord) -> None:
|
|
179
|
+
"""Log an agent assignment."""
|
|
180
|
+
with open(self.agent_assignments_file, "a") as f:
|
|
181
|
+
f.write(json.dumps(record.to_dict()) + "\n")
|
|
182
|
+
|
|
183
|
+
def get_task_routings(
|
|
184
|
+
self,
|
|
185
|
+
since: datetime | None = None,
|
|
186
|
+
status: str | None = None,
|
|
187
|
+
limit: int = 1000,
|
|
188
|
+
) -> list[TaskRoutingRecord]:
|
|
189
|
+
"""Get task routing records.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
since: Only return records after this time
|
|
193
|
+
status: Filter by status (pending, running, completed, failed)
|
|
194
|
+
limit: Maximum records to return
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
List of TaskRoutingRecord
|
|
198
|
+
|
|
199
|
+
"""
|
|
200
|
+
records: list[TaskRoutingRecord] = []
|
|
201
|
+
if not self.task_routing_file.exists():
|
|
202
|
+
return records
|
|
203
|
+
|
|
204
|
+
with open(self.task_routing_file) as f:
|
|
205
|
+
for line in f:
|
|
206
|
+
if not line.strip():
|
|
207
|
+
continue
|
|
208
|
+
try:
|
|
209
|
+
data = json.loads(line)
|
|
210
|
+
record = TaskRoutingRecord.from_dict(data)
|
|
211
|
+
|
|
212
|
+
# Apply filters
|
|
213
|
+
if since:
|
|
214
|
+
record_time = _parse_timestamp(record.timestamp)
|
|
215
|
+
if record_time < since:
|
|
216
|
+
continue
|
|
217
|
+
|
|
218
|
+
if status and record.status != status:
|
|
219
|
+
continue
|
|
220
|
+
|
|
221
|
+
records.append(record)
|
|
222
|
+
|
|
223
|
+
if len(records) >= limit:
|
|
224
|
+
break
|
|
225
|
+
except (json.JSONDecodeError, KeyError):
|
|
226
|
+
continue
|
|
227
|
+
|
|
228
|
+
return records
|
|
229
|
+
|
|
230
|
+
def get_test_executions(
|
|
231
|
+
self,
|
|
232
|
+
since: datetime | None = None,
|
|
233
|
+
success_only: bool = False,
|
|
234
|
+
limit: int = 100,
|
|
235
|
+
) -> list[TestExecutionRecord]:
|
|
236
|
+
"""Get test execution records.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
since: Only return records after this time
|
|
240
|
+
success_only: Only return successful test runs
|
|
241
|
+
limit: Maximum records to return
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
List of TestExecutionRecord
|
|
245
|
+
|
|
246
|
+
"""
|
|
247
|
+
records: list[TestExecutionRecord] = []
|
|
248
|
+
if not self.test_executions_file.exists():
|
|
249
|
+
return records
|
|
250
|
+
|
|
251
|
+
with open(self.test_executions_file) as f:
|
|
252
|
+
for line in f:
|
|
253
|
+
if not line.strip():
|
|
254
|
+
continue
|
|
255
|
+
try:
|
|
256
|
+
data = json.loads(line)
|
|
257
|
+
record = TestExecutionRecord.from_dict(data)
|
|
258
|
+
|
|
259
|
+
# Apply filters
|
|
260
|
+
if since:
|
|
261
|
+
record_time = _parse_timestamp(record.timestamp)
|
|
262
|
+
if record_time < since:
|
|
263
|
+
continue
|
|
264
|
+
|
|
265
|
+
if success_only and not record.success:
|
|
266
|
+
continue
|
|
267
|
+
|
|
268
|
+
records.append(record)
|
|
269
|
+
|
|
270
|
+
if len(records) >= limit:
|
|
271
|
+
break
|
|
272
|
+
except (json.JSONDecodeError, KeyError):
|
|
273
|
+
continue
|
|
274
|
+
|
|
275
|
+
return records
|
|
276
|
+
|
|
277
|
+
def get_coverage_history(
|
|
278
|
+
self,
|
|
279
|
+
since: datetime | None = None,
|
|
280
|
+
limit: int = 100,
|
|
281
|
+
) -> list[CoverageRecord]:
|
|
282
|
+
"""Get coverage history records.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
since: Only return records after this time
|
|
286
|
+
limit: Maximum records to return
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
List of CoverageRecord
|
|
290
|
+
|
|
291
|
+
"""
|
|
292
|
+
records: list[CoverageRecord] = []
|
|
293
|
+
if not self.coverage_history_file.exists():
|
|
294
|
+
return records
|
|
295
|
+
|
|
296
|
+
with open(self.coverage_history_file) as f:
|
|
297
|
+
for line in f:
|
|
298
|
+
if not line.strip():
|
|
299
|
+
continue
|
|
300
|
+
try:
|
|
301
|
+
data = json.loads(line)
|
|
302
|
+
record = CoverageRecord.from_dict(data)
|
|
303
|
+
|
|
304
|
+
# Apply filters
|
|
305
|
+
if since:
|
|
306
|
+
record_time = _parse_timestamp(record.timestamp)
|
|
307
|
+
if record_time < since:
|
|
308
|
+
continue
|
|
309
|
+
|
|
310
|
+
records.append(record)
|
|
311
|
+
|
|
312
|
+
if len(records) >= limit:
|
|
313
|
+
break
|
|
314
|
+
except (json.JSONDecodeError, KeyError):
|
|
315
|
+
continue
|
|
316
|
+
|
|
317
|
+
return records
|
|
318
|
+
|
|
319
|
+
def get_agent_assignments(
|
|
320
|
+
self,
|
|
321
|
+
since: datetime | None = None,
|
|
322
|
+
automated_only: bool = True,
|
|
323
|
+
limit: int = 1000,
|
|
324
|
+
) -> list[AgentAssignmentRecord]:
|
|
325
|
+
"""Get agent assignment records.
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
since: Only return records after this time
|
|
329
|
+
automated_only: Only return assignments eligible for Tier 1 automation
|
|
330
|
+
limit: Maximum records to return
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
List of AgentAssignmentRecord
|
|
334
|
+
|
|
335
|
+
"""
|
|
336
|
+
records: list[AgentAssignmentRecord] = []
|
|
337
|
+
if not self.agent_assignments_file.exists():
|
|
338
|
+
return records
|
|
339
|
+
|
|
340
|
+
with open(self.agent_assignments_file) as f:
|
|
341
|
+
for line in f:
|
|
342
|
+
if not line.strip():
|
|
343
|
+
continue
|
|
344
|
+
try:
|
|
345
|
+
data = json.loads(line)
|
|
346
|
+
record = AgentAssignmentRecord.from_dict(data)
|
|
347
|
+
|
|
348
|
+
# Apply filters
|
|
349
|
+
if since:
|
|
350
|
+
record_time = _parse_timestamp(record.timestamp)
|
|
351
|
+
if record_time < since:
|
|
352
|
+
continue
|
|
353
|
+
|
|
354
|
+
if automated_only and not record.automated_eligible:
|
|
355
|
+
continue
|
|
356
|
+
|
|
357
|
+
records.append(record)
|
|
358
|
+
|
|
359
|
+
if len(records) >= limit:
|
|
360
|
+
break
|
|
361
|
+
except (json.JSONDecodeError, KeyError):
|
|
362
|
+
continue
|
|
363
|
+
|
|
364
|
+
return records
|
|
365
|
+
|
|
366
|
+
# Per-file test tracking methods
|
|
367
|
+
|
|
368
|
+
def log_file_test(self, record: "FileTestRecord") -> None:
|
|
369
|
+
"""Log a per-file test execution record.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
record: FileTestRecord to log
|
|
373
|
+
"""
|
|
374
|
+
with open(self.file_tests_file, "a") as f:
|
|
375
|
+
f.write(json.dumps(record.to_dict()) + "\n")
|
|
376
|
+
|
|
377
|
+
def get_file_tests(
|
|
378
|
+
self,
|
|
379
|
+
file_path: str | None = None,
|
|
380
|
+
since: datetime | None = None,
|
|
381
|
+
result_filter: str | None = None,
|
|
382
|
+
limit: int = 1000,
|
|
383
|
+
) -> list["FileTestRecord"]:
|
|
384
|
+
"""Get per-file test records with optional filters.
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
file_path: Filter by specific file path
|
|
388
|
+
since: Only return records after this time
|
|
389
|
+
result_filter: Filter by result (passed, failed, error, skipped, no_tests)
|
|
390
|
+
limit: Maximum records to return
|
|
391
|
+
|
|
392
|
+
Returns:
|
|
393
|
+
List of FileTestRecord
|
|
394
|
+
"""
|
|
395
|
+
records: list[FileTestRecord] = []
|
|
396
|
+
if not self.file_tests_file.exists():
|
|
397
|
+
return records
|
|
398
|
+
|
|
399
|
+
with open(self.file_tests_file) as f:
|
|
400
|
+
for line in f:
|
|
401
|
+
if not line.strip():
|
|
402
|
+
continue
|
|
403
|
+
try:
|
|
404
|
+
data = json.loads(line)
|
|
405
|
+
record = FileTestRecord.from_dict(data)
|
|
406
|
+
|
|
407
|
+
# Apply filters
|
|
408
|
+
if file_path and record.file_path != file_path:
|
|
409
|
+
continue
|
|
410
|
+
|
|
411
|
+
if since:
|
|
412
|
+
record_time = _parse_timestamp(record.timestamp)
|
|
413
|
+
if record_time < since:
|
|
414
|
+
continue
|
|
415
|
+
|
|
416
|
+
if result_filter and record.last_test_result != result_filter:
|
|
417
|
+
continue
|
|
418
|
+
|
|
419
|
+
records.append(record)
|
|
420
|
+
|
|
421
|
+
if len(records) >= limit:
|
|
422
|
+
break
|
|
423
|
+
except (json.JSONDecodeError, KeyError):
|
|
424
|
+
continue
|
|
425
|
+
|
|
426
|
+
return records
|
|
427
|
+
|
|
428
|
+
def get_latest_file_test(self, file_path: str) -> "FileTestRecord | None":
|
|
429
|
+
"""Get the most recent test record for a specific file.
|
|
430
|
+
|
|
431
|
+
Args:
|
|
432
|
+
file_path: Path to the source file
|
|
433
|
+
|
|
434
|
+
Returns:
|
|
435
|
+
Most recent FileTestRecord or None if not found
|
|
436
|
+
"""
|
|
437
|
+
records = self.get_file_tests(file_path=file_path, limit=10000)
|
|
438
|
+
if not records:
|
|
439
|
+
return None
|
|
440
|
+
|
|
441
|
+
# Return the most recent record (last one since we read in chronological order)
|
|
442
|
+
return records[-1]
|
|
443
|
+
|
|
444
|
+
def get_files_needing_tests(
|
|
445
|
+
self,
|
|
446
|
+
stale_only: bool = False,
|
|
447
|
+
failed_only: bool = False,
|
|
448
|
+
) -> list["FileTestRecord"]:
|
|
449
|
+
"""Get files that need test attention.
|
|
450
|
+
|
|
451
|
+
Args:
|
|
452
|
+
stale_only: Only return files with stale tests
|
|
453
|
+
failed_only: Only return files with failed tests
|
|
454
|
+
|
|
455
|
+
Returns:
|
|
456
|
+
List of FileTestRecord for files needing attention
|
|
457
|
+
"""
|
|
458
|
+
all_records = self.get_file_tests(limit=100000)
|
|
459
|
+
|
|
460
|
+
# Get latest record per file
|
|
461
|
+
latest_by_file: dict[str, FileTestRecord] = {}
|
|
462
|
+
for record in all_records:
|
|
463
|
+
existing = latest_by_file.get(record.file_path)
|
|
464
|
+
if existing is None:
|
|
465
|
+
latest_by_file[record.file_path] = record
|
|
466
|
+
else:
|
|
467
|
+
# Keep the more recent one
|
|
468
|
+
if record.timestamp > existing.timestamp:
|
|
469
|
+
latest_by_file[record.file_path] = record
|
|
470
|
+
|
|
471
|
+
# Filter based on criteria
|
|
472
|
+
results = []
|
|
473
|
+
for record in latest_by_file.values():
|
|
474
|
+
if stale_only and not record.is_stale:
|
|
475
|
+
continue
|
|
476
|
+
if failed_only and record.last_test_result not in ("failed", "error"):
|
|
477
|
+
continue
|
|
478
|
+
if not stale_only and not failed_only:
|
|
479
|
+
# Return all files needing attention (stale OR failed OR no_tests)
|
|
480
|
+
if (
|
|
481
|
+
record.last_test_result not in ("failed", "error", "no_tests")
|
|
482
|
+
and not record.is_stale
|
|
483
|
+
):
|
|
484
|
+
continue
|
|
485
|
+
results.append(record)
|
|
486
|
+
|
|
487
|
+
return results
|
|
488
|
+
|
|
489
|
+
|