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,531 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Automatic tier recommendation and progression tracking for workflows.
|
|
3
|
+
|
|
4
|
+
Integrates TierRecommender into workflows to:
|
|
5
|
+
1. Auto-suggest optimal tier at workflow start
|
|
6
|
+
2. Track tier progression during execution
|
|
7
|
+
3. Save tier progression data automatically
|
|
8
|
+
|
|
9
|
+
Copyright 2025 Smart-AI-Memory
|
|
10
|
+
Licensed under Fair Source License 0.9
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import uuid
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from datetime import datetime
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from attune.config import _validate_file_path
|
|
21
|
+
from attune.logging_config import get_logger
|
|
22
|
+
|
|
23
|
+
logger = get_logger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class TierAttempt:
|
|
28
|
+
"""Record of a single tier attempt."""
|
|
29
|
+
|
|
30
|
+
tier: str
|
|
31
|
+
attempt: int
|
|
32
|
+
success: bool
|
|
33
|
+
quality_gate_failed: str | None = None
|
|
34
|
+
quality_gates_passed: list[str] | None = None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class WorkflowTierProgression:
|
|
39
|
+
"""Track tier progression for a workflow run."""
|
|
40
|
+
|
|
41
|
+
workflow_name: str
|
|
42
|
+
workflow_id: str
|
|
43
|
+
bug_description: str
|
|
44
|
+
files_affected: list[str]
|
|
45
|
+
bug_type: str
|
|
46
|
+
|
|
47
|
+
# Tier progression
|
|
48
|
+
recommended_tier: str
|
|
49
|
+
starting_tier: str
|
|
50
|
+
successful_tier: str
|
|
51
|
+
total_attempts: int
|
|
52
|
+
tier_history: list[dict[str, Any]]
|
|
53
|
+
|
|
54
|
+
# Costs
|
|
55
|
+
total_cost: float
|
|
56
|
+
cost_if_always_premium: float
|
|
57
|
+
savings_percent: float
|
|
58
|
+
|
|
59
|
+
# Quality
|
|
60
|
+
tests_passed: bool
|
|
61
|
+
error_occurred: bool
|
|
62
|
+
|
|
63
|
+
# Metadata
|
|
64
|
+
started_at: str
|
|
65
|
+
completed_at: str
|
|
66
|
+
duration_seconds: float
|
|
67
|
+
|
|
68
|
+
# Optional fields must come last
|
|
69
|
+
error_message: str | None = None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class WorkflowTierTracker:
|
|
73
|
+
"""
|
|
74
|
+
Automatically track tier progression for workflow runs.
|
|
75
|
+
|
|
76
|
+
Usage in BaseWorkflow:
|
|
77
|
+
tracker = WorkflowTierTracker(workflow_name, description)
|
|
78
|
+
tracker.show_recommendation(files_affected)
|
|
79
|
+
# ... run workflow ...
|
|
80
|
+
tracker.save_progression(result)
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
TIER_COSTS = {
|
|
84
|
+
"cheap": 0.030,
|
|
85
|
+
"capable": 0.090,
|
|
86
|
+
"premium": 0.450,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# Retention policy: keep only this many workflow files
|
|
90
|
+
MAX_WORKFLOW_FILES = 100
|
|
91
|
+
# Only run cleanup every N saves to avoid overhead
|
|
92
|
+
CLEANUP_FREQUENCY = 10
|
|
93
|
+
|
|
94
|
+
def __init__(
|
|
95
|
+
self,
|
|
96
|
+
workflow_name: str,
|
|
97
|
+
workflow_description: str,
|
|
98
|
+
patterns_dir: Path | None = None,
|
|
99
|
+
):
|
|
100
|
+
"""
|
|
101
|
+
Initialize tier tracker for a workflow.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
workflow_name: Name of the workflow
|
|
105
|
+
workflow_description: Description/purpose of workflow
|
|
106
|
+
patterns_dir: Directory to save tier progression patterns
|
|
107
|
+
"""
|
|
108
|
+
self.workflow_name = workflow_name
|
|
109
|
+
self.workflow_description = workflow_description
|
|
110
|
+
self.workflow_id = str(uuid.uuid4())
|
|
111
|
+
self.started_at = datetime.now()
|
|
112
|
+
|
|
113
|
+
if patterns_dir is None:
|
|
114
|
+
patterns_dir = Path.cwd() / "patterns" / "debugging"
|
|
115
|
+
self.patterns_dir = Path(patterns_dir)
|
|
116
|
+
self.patterns_dir.mkdir(parents=True, exist_ok=True)
|
|
117
|
+
|
|
118
|
+
self.recommended_tier: str | None = None
|
|
119
|
+
self.starting_tier: str | None = None
|
|
120
|
+
self.tier_attempts: list[TierAttempt] = []
|
|
121
|
+
|
|
122
|
+
def show_recommendation(
|
|
123
|
+
self,
|
|
124
|
+
files_affected: list[str] | None = None,
|
|
125
|
+
show_ui: bool = True,
|
|
126
|
+
) -> str:
|
|
127
|
+
"""
|
|
128
|
+
Show tier recommendation at workflow start.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
files_affected: Files involved in this workflow run
|
|
132
|
+
show_ui: Whether to print recommendation to console
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Recommended tier (CHEAP, CAPABLE, or PREMIUM)
|
|
136
|
+
"""
|
|
137
|
+
try:
|
|
138
|
+
from attune.tier_recommender import TierRecommender
|
|
139
|
+
|
|
140
|
+
recommender = TierRecommender()
|
|
141
|
+
result = recommender.recommend(
|
|
142
|
+
bug_description=self.workflow_description,
|
|
143
|
+
files_affected=files_affected or [],
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
self.recommended_tier = result.tier
|
|
147
|
+
|
|
148
|
+
if show_ui:
|
|
149
|
+
self._print_recommendation(result)
|
|
150
|
+
|
|
151
|
+
return result.tier
|
|
152
|
+
|
|
153
|
+
except Exception as e:
|
|
154
|
+
logger.debug(f"Could not get tier recommendation: {e}")
|
|
155
|
+
# Fallback to CHEAP if recommendation fails
|
|
156
|
+
self.recommended_tier = "CHEAP"
|
|
157
|
+
return "CHEAP"
|
|
158
|
+
|
|
159
|
+
def _print_recommendation(self, result):
|
|
160
|
+
"""Print tier recommendation to console."""
|
|
161
|
+
from rich.console import Console
|
|
162
|
+
from rich.panel import Panel
|
|
163
|
+
|
|
164
|
+
console = Console()
|
|
165
|
+
|
|
166
|
+
confidence_color = "green" if result.confidence > 0.7 else "yellow"
|
|
167
|
+
|
|
168
|
+
message = f"""[bold]Workflow:[/bold] {self.workflow_name}
|
|
169
|
+
[bold]Description:[/bold] {self.workflow_description}
|
|
170
|
+
|
|
171
|
+
[bold cyan]💡 Tier Recommendation[/bold cyan]
|
|
172
|
+
📍 Recommended: [bold]{result.tier}[/bold]
|
|
173
|
+
🎯 Confidence: [{confidence_color}]{result.confidence * 100:.0f}%[/{confidence_color}]
|
|
174
|
+
💰 Expected Cost: ${result.expected_cost:.3f}
|
|
175
|
+
🔄 Expected Attempts: {result.expected_attempts:.1f}
|
|
176
|
+
|
|
177
|
+
[dim]Reasoning: {result.reasoning}[/dim]"""
|
|
178
|
+
|
|
179
|
+
if result.fallback_used:
|
|
180
|
+
message += "\n\n[yellow]⚠️ Using default - limited historical data[/yellow]"
|
|
181
|
+
else:
|
|
182
|
+
message += (
|
|
183
|
+
f"\n\n[green]✅ Based on {result.similar_patterns_count} similar patterns[/green]"
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
console.print(Panel(message, title="🎯 Auto Tier Recommendation", border_style="cyan"))
|
|
187
|
+
|
|
188
|
+
def record_tier_attempt(
|
|
189
|
+
self,
|
|
190
|
+
tier: str,
|
|
191
|
+
attempt: int,
|
|
192
|
+
success: bool,
|
|
193
|
+
quality_gate_failed: str | None = None,
|
|
194
|
+
quality_gates_passed: list[str] | None = None,
|
|
195
|
+
):
|
|
196
|
+
"""Record a tier attempt during workflow execution."""
|
|
197
|
+
self.tier_attempts.append(
|
|
198
|
+
TierAttempt(
|
|
199
|
+
tier=tier,
|
|
200
|
+
attempt=attempt,
|
|
201
|
+
success=success,
|
|
202
|
+
quality_gate_failed=quality_gate_failed,
|
|
203
|
+
quality_gates_passed=quality_gates_passed,
|
|
204
|
+
)
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
def save_progression(
|
|
208
|
+
self,
|
|
209
|
+
workflow_result: Any,
|
|
210
|
+
files_affected: list[str] | None = None,
|
|
211
|
+
bug_type: str = "workflow_run",
|
|
212
|
+
tier_progression: list[tuple[str, str, bool]] | None = None,
|
|
213
|
+
) -> Path | None:
|
|
214
|
+
"""
|
|
215
|
+
Save tier progression data after workflow completion.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
workflow_result: WorkflowResult from workflow execution
|
|
219
|
+
files_affected: Files processed by workflow
|
|
220
|
+
bug_type: Type of issue being addressed
|
|
221
|
+
tier_progression: Optional detailed tier progression list
|
|
222
|
+
[(stage, tier, success), ...]
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
Path to saved pattern file, or None if save failed
|
|
226
|
+
"""
|
|
227
|
+
try:
|
|
228
|
+
completed_at = datetime.now()
|
|
229
|
+
duration = (completed_at - self.started_at).total_seconds()
|
|
230
|
+
|
|
231
|
+
# Determine successful tier from workflow result
|
|
232
|
+
successful_tier = self._determine_successful_tier(workflow_result)
|
|
233
|
+
self.starting_tier = self.starting_tier or successful_tier
|
|
234
|
+
|
|
235
|
+
# Build tier history - use detailed progression if available
|
|
236
|
+
if tier_progression:
|
|
237
|
+
tier_history = self._build_tier_history_from_progression(tier_progression)
|
|
238
|
+
else:
|
|
239
|
+
tier_history = self._build_tier_history(workflow_result)
|
|
240
|
+
|
|
241
|
+
# Calculate costs
|
|
242
|
+
total_cost = (
|
|
243
|
+
workflow_result.cost_report.get("total", 0)
|
|
244
|
+
if isinstance(workflow_result.cost_report, dict)
|
|
245
|
+
else sum(stage.cost for stage in workflow_result.stages)
|
|
246
|
+
)
|
|
247
|
+
cost_if_premium = self._estimate_premium_cost(workflow_result)
|
|
248
|
+
savings_percent = (
|
|
249
|
+
((cost_if_premium - total_cost) / cost_if_premium * 100)
|
|
250
|
+
if cost_if_premium > 0
|
|
251
|
+
else 0
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
# Create progression record
|
|
255
|
+
progression = {
|
|
256
|
+
"pattern_id": f"workflow_{datetime.now().strftime('%Y%m%d')}_{self.workflow_id[:8]}",
|
|
257
|
+
"bug_type": bug_type,
|
|
258
|
+
"status": "resolved" if workflow_result.error is None else "failed",
|
|
259
|
+
"root_cause": f"Workflow: {self.workflow_name} - {self.workflow_description}",
|
|
260
|
+
"fix": f"Completed via {self.workflow_name} workflow",
|
|
261
|
+
"resolved_by": "@empathy_framework",
|
|
262
|
+
"resolved_at": completed_at.strftime("%Y-%m-%d"),
|
|
263
|
+
"files_affected": files_affected or [],
|
|
264
|
+
"source": "workflow_tracking",
|
|
265
|
+
"tier_progression": {
|
|
266
|
+
"methodology": "AI-ADDIE",
|
|
267
|
+
"recommended_tier": self.recommended_tier or self.starting_tier,
|
|
268
|
+
"starting_tier": self.starting_tier,
|
|
269
|
+
"successful_tier": successful_tier,
|
|
270
|
+
"total_attempts": len(tier_history),
|
|
271
|
+
"tier_history": tier_history,
|
|
272
|
+
"cost_breakdown": {
|
|
273
|
+
"total_cost": round(total_cost, 3),
|
|
274
|
+
"cost_if_always_premium": round(cost_if_premium, 3),
|
|
275
|
+
"savings_percent": round(savings_percent, 1),
|
|
276
|
+
},
|
|
277
|
+
"quality_metrics": {
|
|
278
|
+
"tests_passed": workflow_result.error is None,
|
|
279
|
+
"health_score_before": 73, # Default
|
|
280
|
+
"health_score_after": 73,
|
|
281
|
+
},
|
|
282
|
+
"xml_protocol_compliance": {
|
|
283
|
+
"prompt_used_xml": True,
|
|
284
|
+
"response_used_xml": True,
|
|
285
|
+
"all_sections_present": True,
|
|
286
|
+
"test_evidence_provided": True,
|
|
287
|
+
"false_complete_avoided": workflow_result.error is None,
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
"workflow_metadata": {
|
|
291
|
+
"workflow_name": self.workflow_name,
|
|
292
|
+
"workflow_id": self.workflow_id,
|
|
293
|
+
"duration_seconds": round(duration, 2),
|
|
294
|
+
"started_at": self.started_at.isoformat(),
|
|
295
|
+
"completed_at": completed_at.isoformat(),
|
|
296
|
+
},
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
# Save to individual pattern file
|
|
300
|
+
pattern_file = self.patterns_dir / f"{progression['pattern_id']}.json"
|
|
301
|
+
validated_pattern_file = _validate_file_path(str(pattern_file))
|
|
302
|
+
with open(validated_pattern_file, "w") as f:
|
|
303
|
+
json.dump(progression, f, indent=2)
|
|
304
|
+
|
|
305
|
+
logger.info(f"💾 Saved tier progression: {validated_pattern_file}")
|
|
306
|
+
|
|
307
|
+
# Also update consolidated patterns file
|
|
308
|
+
self._update_consolidated_patterns(progression)
|
|
309
|
+
|
|
310
|
+
# Periodic cleanup of old workflow files (every CLEANUP_FREQUENCY saves)
|
|
311
|
+
workflow_count = len(list(self.patterns_dir.glob("workflow_*.json")))
|
|
312
|
+
if workflow_count > self.MAX_WORKFLOW_FILES + self.CLEANUP_FREQUENCY:
|
|
313
|
+
self._cleanup_old_workflow_files()
|
|
314
|
+
|
|
315
|
+
return pattern_file
|
|
316
|
+
|
|
317
|
+
except Exception as e:
|
|
318
|
+
logger.warning(f"Failed to save tier progression: {e}")
|
|
319
|
+
return None
|
|
320
|
+
|
|
321
|
+
def _determine_successful_tier(self, workflow_result: Any) -> str:
|
|
322
|
+
"""Determine which tier successfully completed the workflow."""
|
|
323
|
+
if not workflow_result.stages:
|
|
324
|
+
return "CHEAP"
|
|
325
|
+
|
|
326
|
+
# Use the highest tier that was actually used
|
|
327
|
+
tiers_used = [
|
|
328
|
+
stage.tier.value if hasattr(stage.tier, "value") else str(stage.tier).lower()
|
|
329
|
+
for stage in workflow_result.stages
|
|
330
|
+
]
|
|
331
|
+
|
|
332
|
+
if "premium" in tiers_used:
|
|
333
|
+
return "PREMIUM"
|
|
334
|
+
elif "capable" in tiers_used:
|
|
335
|
+
return "CAPABLE"
|
|
336
|
+
else:
|
|
337
|
+
return "CHEAP"
|
|
338
|
+
|
|
339
|
+
def _build_tier_history_from_progression(
|
|
340
|
+
self, tier_progression: list[tuple[str, str, bool]]
|
|
341
|
+
) -> list[dict[str, Any]]:
|
|
342
|
+
"""Build detailed tier history from tier progression tracking.
|
|
343
|
+
|
|
344
|
+
Args:
|
|
345
|
+
tier_progression: List of (stage, tier, success) tuples
|
|
346
|
+
|
|
347
|
+
Returns:
|
|
348
|
+
List of tier history entries with detailed attempt information
|
|
349
|
+
|
|
350
|
+
"""
|
|
351
|
+
# Group attempts by stage
|
|
352
|
+
stage_attempts: dict[str, list[tuple[str, bool]]] = {}
|
|
353
|
+
for stage, tier, success in tier_progression:
|
|
354
|
+
if stage not in stage_attempts:
|
|
355
|
+
stage_attempts[stage] = []
|
|
356
|
+
stage_attempts[stage].append((tier, success))
|
|
357
|
+
|
|
358
|
+
# Build history with fallback information
|
|
359
|
+
history: list[dict[str, Any]] = []
|
|
360
|
+
for stage, attempts in stage_attempts.items():
|
|
361
|
+
stage_entry: dict[str, Any] = {
|
|
362
|
+
"stage": stage,
|
|
363
|
+
"total_attempts": len(attempts),
|
|
364
|
+
"attempts": [],
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
for attempt_num, (tier, success) in enumerate(attempts, 1):
|
|
368
|
+
attempt_entry = {
|
|
369
|
+
"attempt": attempt_num,
|
|
370
|
+
"tier": tier.upper(),
|
|
371
|
+
"success": success,
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if not success:
|
|
375
|
+
attempt_entry["quality_gate_failed"] = "validation_failed"
|
|
376
|
+
|
|
377
|
+
stage_entry["attempts"].append(attempt_entry)
|
|
378
|
+
|
|
379
|
+
# Record successful tier for this stage
|
|
380
|
+
successful_attempts = [a for a in attempts if a[1]]
|
|
381
|
+
if successful_attempts:
|
|
382
|
+
final_tier = successful_attempts[-1][0]
|
|
383
|
+
stage_entry["successful_tier"] = final_tier.upper()
|
|
384
|
+
|
|
385
|
+
# Check if tier fallback occurred
|
|
386
|
+
if len(attempts) > 1:
|
|
387
|
+
first_tier = attempts[0][0]
|
|
388
|
+
stage_entry["tier_fallback_occurred"] = True
|
|
389
|
+
stage_entry["fallback_chain"] = f"{first_tier.upper()} → {final_tier.upper()}"
|
|
390
|
+
|
|
391
|
+
history.append(stage_entry)
|
|
392
|
+
|
|
393
|
+
return history
|
|
394
|
+
|
|
395
|
+
def _build_tier_history(self, workflow_result: Any) -> list[dict[str, Any]]:
|
|
396
|
+
"""Build tier history from workflow stages."""
|
|
397
|
+
tier_groups: dict[str, list[Any]] = {}
|
|
398
|
+
|
|
399
|
+
# Group stages by tier
|
|
400
|
+
for stage in workflow_result.stages:
|
|
401
|
+
tier = stage.tier.value if hasattr(stage.tier, "value") else str(stage.tier).lower()
|
|
402
|
+
tier_upper = tier.upper()
|
|
403
|
+
if tier_upper not in tier_groups:
|
|
404
|
+
tier_groups[tier_upper] = []
|
|
405
|
+
tier_groups[tier_upper].append(stage)
|
|
406
|
+
|
|
407
|
+
# Build history entries
|
|
408
|
+
history = []
|
|
409
|
+
for tier, stages in tier_groups.items():
|
|
410
|
+
# Check if any stage failed
|
|
411
|
+
failures = []
|
|
412
|
+
success_stage = None
|
|
413
|
+
|
|
414
|
+
for i, stage in enumerate(stages, 1):
|
|
415
|
+
if hasattr(stage, "error") and stage.error:
|
|
416
|
+
failures.append({"attempt": i, "quality_gate_failed": "execution"})
|
|
417
|
+
else:
|
|
418
|
+
success_stage = i
|
|
419
|
+
|
|
420
|
+
entry = {
|
|
421
|
+
"tier": tier,
|
|
422
|
+
"attempts": len(stages),
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if failures:
|
|
426
|
+
entry["failures"] = failures
|
|
427
|
+
|
|
428
|
+
if success_stage:
|
|
429
|
+
entry["success"] = {
|
|
430
|
+
"attempt": success_stage,
|
|
431
|
+
"quality_gates_passed": ["execution", "output"],
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
history.append(entry)
|
|
435
|
+
|
|
436
|
+
return history
|
|
437
|
+
|
|
438
|
+
def _estimate_premium_cost(self, workflow_result: Any) -> float:
|
|
439
|
+
"""Estimate what the cost would be if all stages used PREMIUM tier."""
|
|
440
|
+
_total_tokens = sum(
|
|
441
|
+
(stage.input_tokens or 0) + (stage.output_tokens or 0)
|
|
442
|
+
for stage in workflow_result.stages
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
# Calculate actual cost from stages
|
|
446
|
+
actual_cost: float = sum(stage.cost for stage in workflow_result.stages)
|
|
447
|
+
|
|
448
|
+
# Rough estimate: PREMIUM tier is ~15x more expensive than CHEAP
|
|
449
|
+
return actual_cost * 5 # Conservative multiplier
|
|
450
|
+
|
|
451
|
+
def _update_consolidated_patterns(self, progression: dict[str, Any]):
|
|
452
|
+
"""Update the consolidated patterns.json file with retention policy."""
|
|
453
|
+
consolidated_file = self.patterns_dir / "all_patterns.json"
|
|
454
|
+
|
|
455
|
+
try:
|
|
456
|
+
if consolidated_file.exists():
|
|
457
|
+
with open(consolidated_file) as f:
|
|
458
|
+
data = json.load(f)
|
|
459
|
+
if "patterns" not in data:
|
|
460
|
+
data = {"patterns": []}
|
|
461
|
+
else:
|
|
462
|
+
data = {"patterns": []}
|
|
463
|
+
|
|
464
|
+
# Add new progression
|
|
465
|
+
data["patterns"].append(progression)
|
|
466
|
+
|
|
467
|
+
# Apply retention policy: keep only MAX_WORKFLOW_FILES patterns
|
|
468
|
+
if len(data["patterns"]) > self.MAX_WORKFLOW_FILES:
|
|
469
|
+
data["patterns"] = data["patterns"][-self.MAX_WORKFLOW_FILES :]
|
|
470
|
+
|
|
471
|
+
# Save updated file
|
|
472
|
+
validated_consolidated = _validate_file_path(str(consolidated_file))
|
|
473
|
+
with open(validated_consolidated, "w") as f:
|
|
474
|
+
json.dump(data, f, indent=2)
|
|
475
|
+
|
|
476
|
+
except (OSError, ValueError, json.JSONDecodeError) as e:
|
|
477
|
+
logger.warning(f"Could not update consolidated patterns: {e}")
|
|
478
|
+
# If file is corrupted, start fresh
|
|
479
|
+
try:
|
|
480
|
+
data = {"patterns": [progression]}
|
|
481
|
+
validated_consolidated = _validate_file_path(str(consolidated_file))
|
|
482
|
+
with open(validated_consolidated, "w") as f:
|
|
483
|
+
json.dump(data, f, indent=2)
|
|
484
|
+
logger.info("Recreated consolidated patterns file")
|
|
485
|
+
except (OSError, ValueError) as e2:
|
|
486
|
+
logger.warning(f"Could not recreate consolidated patterns: {e2}")
|
|
487
|
+
|
|
488
|
+
def _cleanup_old_workflow_files(self):
|
|
489
|
+
"""Remove old workflow files to prevent unbounded growth.
|
|
490
|
+
|
|
491
|
+
Called periodically during save_progression to keep disk usage bounded.
|
|
492
|
+
Keeps only the most recent MAX_WORKFLOW_FILES workflow files.
|
|
493
|
+
"""
|
|
494
|
+
try:
|
|
495
|
+
workflow_files = sorted(
|
|
496
|
+
self.patterns_dir.glob("workflow_*.json"),
|
|
497
|
+
key=lambda p: p.stat().st_mtime,
|
|
498
|
+
reverse=True,
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
# Delete files beyond retention limit
|
|
502
|
+
files_to_delete = workflow_files[self.MAX_WORKFLOW_FILES :]
|
|
503
|
+
if files_to_delete:
|
|
504
|
+
for f in files_to_delete:
|
|
505
|
+
try:
|
|
506
|
+
f.unlink()
|
|
507
|
+
except OSError:
|
|
508
|
+
pass # Best effort cleanup
|
|
509
|
+
logger.debug(f"Cleaned up {len(files_to_delete)} old workflow files")
|
|
510
|
+
except OSError as e:
|
|
511
|
+
logger.debug(f"Workflow file cleanup skipped: {e}")
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
def auto_recommend_tier(
|
|
515
|
+
workflow_name: str,
|
|
516
|
+
workflow_description: str,
|
|
517
|
+
files_affected: list[str] | None = None,
|
|
518
|
+
) -> str:
|
|
519
|
+
"""
|
|
520
|
+
Quick helper to get tier recommendation without tracker.
|
|
521
|
+
|
|
522
|
+
Args:
|
|
523
|
+
workflow_name: Name of workflow
|
|
524
|
+
workflow_description: What the workflow does
|
|
525
|
+
files_affected: Files involved
|
|
526
|
+
|
|
527
|
+
Returns:
|
|
528
|
+
Recommended tier
|
|
529
|
+
"""
|
|
530
|
+
tracker = WorkflowTierTracker(workflow_name, workflow_description)
|
|
531
|
+
return tracker.show_recommendation(files_affected, show_ui=False)
|