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,384 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Real-time tier recommendation system for cascading workflows.
|
|
3
|
+
|
|
4
|
+
This module provides intelligent tier selection based on historical patterns,
|
|
5
|
+
bug types, and file analysis. It can be used programmatically or via CLI.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
from attune import TierRecommender
|
|
9
|
+
|
|
10
|
+
recommender = TierRecommender()
|
|
11
|
+
tier = recommender.recommend(
|
|
12
|
+
bug_description="integration test failure with import error",
|
|
13
|
+
files_affected=["tests/integration/test_foo.py"]
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
print(f"Recommended tier: {tier.tier}")
|
|
17
|
+
print(f"Confidence: {tier.confidence}")
|
|
18
|
+
print(f"Expected cost: ${tier.expected_cost}")
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
from collections import defaultdict
|
|
23
|
+
from dataclasses import dataclass
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
from attune.config import _validate_file_path
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class TierRecommendationResult:
|
|
31
|
+
"""Result of tier recommendation."""
|
|
32
|
+
|
|
33
|
+
tier: str # CHEAP, CAPABLE, or PREMIUM
|
|
34
|
+
confidence: float # 0.0-1.0
|
|
35
|
+
reasoning: str
|
|
36
|
+
expected_cost: float
|
|
37
|
+
expected_attempts: float
|
|
38
|
+
similar_patterns_count: int
|
|
39
|
+
fallback_used: bool = False
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class TierRecommender:
|
|
43
|
+
"""
|
|
44
|
+
Intelligent tier recommendation system.
|
|
45
|
+
|
|
46
|
+
Learns from historical patterns to recommend optimal starting tier
|
|
47
|
+
for new bugs based on:
|
|
48
|
+
- Bug type/description
|
|
49
|
+
- Files affected
|
|
50
|
+
- Historical success rates
|
|
51
|
+
- Cost optimization
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(self, patterns_dir: Path | None = None, confidence_threshold: float = 0.7):
|
|
55
|
+
"""
|
|
56
|
+
Initialize tier recommender.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
patterns_dir: Directory containing pattern JSON files.
|
|
60
|
+
Defaults to patterns/debugging/
|
|
61
|
+
confidence_threshold: Minimum confidence for non-default recommendations
|
|
62
|
+
|
|
63
|
+
Raises:
|
|
64
|
+
ValueError: If confidence_threshold is out of valid range
|
|
65
|
+
"""
|
|
66
|
+
# Pattern 4: Range validation
|
|
67
|
+
if not 0.0 <= confidence_threshold <= 1.0:
|
|
68
|
+
raise ValueError(
|
|
69
|
+
f"confidence_threshold must be between 0.0 and 1.0, got {confidence_threshold}"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
if patterns_dir is None:
|
|
73
|
+
patterns_dir = Path(__file__).parent.parent.parent / "patterns" / "debugging"
|
|
74
|
+
|
|
75
|
+
self.patterns_dir = Path(patterns_dir)
|
|
76
|
+
self.confidence_threshold = confidence_threshold
|
|
77
|
+
self.patterns = self._load_patterns()
|
|
78
|
+
|
|
79
|
+
# Build indexes for fast lookup
|
|
80
|
+
self._build_indexes()
|
|
81
|
+
|
|
82
|
+
def _load_patterns(self) -> list[dict]:
|
|
83
|
+
"""Load all enhanced patterns with tier_progression data."""
|
|
84
|
+
patterns: list[dict] = []
|
|
85
|
+
|
|
86
|
+
if not self.patterns_dir.exists():
|
|
87
|
+
return patterns
|
|
88
|
+
|
|
89
|
+
for file_path in self.patterns_dir.glob("*.json"):
|
|
90
|
+
try:
|
|
91
|
+
validated_path = _validate_file_path(str(file_path))
|
|
92
|
+
with open(validated_path) as f:
|
|
93
|
+
data = json.load(f)
|
|
94
|
+
|
|
95
|
+
# Check if this is an enhanced pattern
|
|
96
|
+
if isinstance(data, dict) and "tier_progression" in data:
|
|
97
|
+
patterns.append(data)
|
|
98
|
+
# Or if it's a patterns array
|
|
99
|
+
elif isinstance(data, dict) and "patterns" in data:
|
|
100
|
+
for pattern in data["patterns"]:
|
|
101
|
+
if "tier_progression" in pattern:
|
|
102
|
+
patterns.append(pattern)
|
|
103
|
+
except (json.JSONDecodeError, KeyError, ValueError):
|
|
104
|
+
continue
|
|
105
|
+
|
|
106
|
+
return patterns
|
|
107
|
+
|
|
108
|
+
def _build_indexes(self):
|
|
109
|
+
"""Build indexes for fast pattern lookup."""
|
|
110
|
+
self.bug_type_index: dict[str, list[dict]] = defaultdict(list)
|
|
111
|
+
self.file_pattern_index: dict[str, list[dict]] = defaultdict(list)
|
|
112
|
+
|
|
113
|
+
for pattern in self.patterns:
|
|
114
|
+
# Index by bug type
|
|
115
|
+
bug_type = pattern.get("bug_type", "unknown")
|
|
116
|
+
self.bug_type_index[bug_type].append(pattern)
|
|
117
|
+
|
|
118
|
+
# Index by file patterns
|
|
119
|
+
files = pattern.get("files_affected", [])
|
|
120
|
+
for file in files:
|
|
121
|
+
# Extract file pattern (e.g., "tests/" from "tests/test_foo.py")
|
|
122
|
+
parts = Path(file).parts
|
|
123
|
+
if parts:
|
|
124
|
+
self.file_pattern_index[parts[0]].append(pattern)
|
|
125
|
+
|
|
126
|
+
def recommend(
|
|
127
|
+
self,
|
|
128
|
+
bug_description: str,
|
|
129
|
+
files_affected: list[str] | None = None,
|
|
130
|
+
complexity_hint: int | None = None,
|
|
131
|
+
) -> TierRecommendationResult:
|
|
132
|
+
"""
|
|
133
|
+
Recommend optimal starting tier for a new bug.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
bug_description: Description of the bug/task
|
|
137
|
+
files_affected: List of files involved (optional)
|
|
138
|
+
complexity_hint: Manual complexity score 1-10 (optional)
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
TierRecommendationResult with tier, confidence, and reasoning
|
|
142
|
+
|
|
143
|
+
Raises:
|
|
144
|
+
ValueError: If bug_description is empty or complexity_hint out of range
|
|
145
|
+
TypeError: If files_affected is not a list
|
|
146
|
+
"""
|
|
147
|
+
# Pattern 1: String ID validation
|
|
148
|
+
if not bug_description or not bug_description.strip():
|
|
149
|
+
raise ValueError("bug_description cannot be empty")
|
|
150
|
+
|
|
151
|
+
# Pattern 5: Type validation
|
|
152
|
+
if files_affected is not None and not isinstance(files_affected, list):
|
|
153
|
+
raise TypeError(f"files_affected must be list, got {type(files_affected).__name__}")
|
|
154
|
+
|
|
155
|
+
# Pattern 4: Range validation for complexity_hint
|
|
156
|
+
if complexity_hint is not None and not (1 <= complexity_hint <= 10):
|
|
157
|
+
raise ValueError(f"complexity_hint must be between 1 and 10, got {complexity_hint}")
|
|
158
|
+
|
|
159
|
+
# Step 1: Match bug type from description
|
|
160
|
+
bug_type = self._classify_bug_type(bug_description)
|
|
161
|
+
|
|
162
|
+
# Step 2: Find similar patterns
|
|
163
|
+
similar_patterns = self._find_similar_patterns(
|
|
164
|
+
bug_type=bug_type, files_affected=files_affected or []
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
# Step 3: If no similar patterns, use fallback logic
|
|
168
|
+
if not similar_patterns:
|
|
169
|
+
return self._fallback_recommendation(
|
|
170
|
+
bug_description=bug_description, complexity_hint=complexity_hint
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# Step 4: Analyze tier distribution in similar patterns
|
|
174
|
+
tier_analysis = self._analyze_tier_distribution(similar_patterns)
|
|
175
|
+
|
|
176
|
+
# Step 5: Select tier with highest success rate
|
|
177
|
+
recommended_tier, confidence = self._select_tier(tier_analysis)
|
|
178
|
+
|
|
179
|
+
# Step 6: Calculate expected cost and attempts
|
|
180
|
+
cost_estimate = self._estimate_cost(similar_patterns, recommended_tier)
|
|
181
|
+
|
|
182
|
+
return TierRecommendationResult(
|
|
183
|
+
tier=recommended_tier,
|
|
184
|
+
confidence=confidence,
|
|
185
|
+
reasoning=self._generate_reasoning(
|
|
186
|
+
bug_type=bug_type,
|
|
187
|
+
tier=recommended_tier,
|
|
188
|
+
confidence=confidence,
|
|
189
|
+
similar_count=len(similar_patterns),
|
|
190
|
+
),
|
|
191
|
+
expected_cost=cost_estimate["avg_cost"],
|
|
192
|
+
expected_attempts=cost_estimate["avg_attempts"],
|
|
193
|
+
similar_patterns_count=len(similar_patterns),
|
|
194
|
+
fallback_used=False,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
def _classify_bug_type(self, description: str) -> str:
|
|
198
|
+
"""Classify bug type from description using keyword matching."""
|
|
199
|
+
desc_lower = description.lower()
|
|
200
|
+
|
|
201
|
+
# Define bug type keywords
|
|
202
|
+
bug_type_keywords = {
|
|
203
|
+
"integration_error": ["integration", "import", "module", "package"],
|
|
204
|
+
"type_mismatch": ["type", "annotation", "mypy", "typing"],
|
|
205
|
+
"import_error": ["import", "module", "cannot import", "no module"],
|
|
206
|
+
"syntax_error": ["syntax", "invalid syntax", "parse error"],
|
|
207
|
+
"runtime_error": ["runtime", "exception", "traceback"],
|
|
208
|
+
"test_failure": ["test fail", "assertion", "pytest"],
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
for bug_type, keywords in bug_type_keywords.items():
|
|
212
|
+
if any(kw in desc_lower for kw in keywords):
|
|
213
|
+
return bug_type
|
|
214
|
+
|
|
215
|
+
return "unknown"
|
|
216
|
+
|
|
217
|
+
def _find_similar_patterns(self, bug_type: str, files_affected: list[str]) -> list[dict]:
|
|
218
|
+
"""Find patterns similar to current bug.
|
|
219
|
+
|
|
220
|
+
Raises:
|
|
221
|
+
TypeError: If files_affected is not a list
|
|
222
|
+
"""
|
|
223
|
+
# Pattern 5: Type validation
|
|
224
|
+
if not isinstance(files_affected, list):
|
|
225
|
+
raise TypeError(f"files_affected must be list, got {type(files_affected).__name__}")
|
|
226
|
+
|
|
227
|
+
similar = []
|
|
228
|
+
|
|
229
|
+
# Match by bug type
|
|
230
|
+
similar.extend(self.bug_type_index.get(bug_type, []))
|
|
231
|
+
|
|
232
|
+
# Match by file patterns
|
|
233
|
+
if files_affected:
|
|
234
|
+
for file in files_affected:
|
|
235
|
+
parts = Path(file).parts
|
|
236
|
+
if parts:
|
|
237
|
+
file_matches = self.file_pattern_index.get(parts[0], [])
|
|
238
|
+
# Add only if not already in similar list
|
|
239
|
+
for pattern in file_matches:
|
|
240
|
+
if pattern not in similar:
|
|
241
|
+
similar.append(pattern)
|
|
242
|
+
|
|
243
|
+
return similar
|
|
244
|
+
|
|
245
|
+
def _analyze_tier_distribution(self, patterns: list[dict]) -> dict[str, dict]:
|
|
246
|
+
"""Analyze tier success rates from similar patterns."""
|
|
247
|
+
tier_stats: dict[str, dict] = defaultdict(
|
|
248
|
+
lambda: {"count": 0, "total_cost": 0.0, "total_attempts": 0}
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
for pattern in patterns:
|
|
252
|
+
tp = pattern["tier_progression"]
|
|
253
|
+
successful_tier = tp["successful_tier"]
|
|
254
|
+
|
|
255
|
+
stats = tier_stats[successful_tier]
|
|
256
|
+
stats["count"] += 1
|
|
257
|
+
stats["total_cost"] += tp["cost_breakdown"]["total_cost"]
|
|
258
|
+
stats["total_attempts"] += tp["total_attempts"]
|
|
259
|
+
|
|
260
|
+
# Calculate averages
|
|
261
|
+
for _tier, stats in tier_stats.items():
|
|
262
|
+
count = stats["count"]
|
|
263
|
+
stats["success_rate"] = count / len(patterns)
|
|
264
|
+
stats["avg_cost"] = stats["total_cost"] / count
|
|
265
|
+
stats["avg_attempts"] = stats["total_attempts"] / count
|
|
266
|
+
|
|
267
|
+
return dict(tier_stats)
|
|
268
|
+
|
|
269
|
+
def _select_tier(self, tier_analysis: dict[str, dict]) -> tuple[str, float]:
|
|
270
|
+
"""Select best tier based on success rate and cost."""
|
|
271
|
+
if not tier_analysis:
|
|
272
|
+
return "CHEAP", 0.5
|
|
273
|
+
|
|
274
|
+
# Sort by success rate
|
|
275
|
+
sorted_tiers = sorted(
|
|
276
|
+
tier_analysis.items(), key=lambda x: x[1]["success_rate"], reverse=True
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
best_tier, stats = sorted_tiers[0]
|
|
280
|
+
confidence = stats["success_rate"]
|
|
281
|
+
|
|
282
|
+
return best_tier, confidence
|
|
283
|
+
|
|
284
|
+
def _estimate_cost(self, patterns: list[dict], tier: str) -> dict[str, float]:
|
|
285
|
+
"""Estimate cost and attempts for recommended tier."""
|
|
286
|
+
matching = [p for p in patterns if p["tier_progression"]["successful_tier"] == tier]
|
|
287
|
+
|
|
288
|
+
if not matching:
|
|
289
|
+
# Default estimates by tier
|
|
290
|
+
defaults = {
|
|
291
|
+
"CHEAP": {"avg_cost": 0.030, "avg_attempts": 1.5},
|
|
292
|
+
"CAPABLE": {"avg_cost": 0.150, "avg_attempts": 2.5},
|
|
293
|
+
"PREMIUM": {"avg_cost": 0.450, "avg_attempts": 1.0},
|
|
294
|
+
}
|
|
295
|
+
return defaults.get(tier, defaults["CHEAP"])
|
|
296
|
+
|
|
297
|
+
total_cost = sum(p["tier_progression"]["cost_breakdown"]["total_cost"] for p in matching)
|
|
298
|
+
total_attempts = sum(p["tier_progression"]["total_attempts"] for p in matching)
|
|
299
|
+
|
|
300
|
+
return {
|
|
301
|
+
"avg_cost": total_cost / len(matching),
|
|
302
|
+
"avg_attempts": total_attempts / len(matching),
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
def _fallback_recommendation(
|
|
306
|
+
self, bug_description: str, complexity_hint: int | None
|
|
307
|
+
) -> TierRecommendationResult:
|
|
308
|
+
"""Provide fallback recommendation when no historical data available."""
|
|
309
|
+
|
|
310
|
+
# Use complexity hint if provided
|
|
311
|
+
if complexity_hint is not None:
|
|
312
|
+
if complexity_hint <= 3:
|
|
313
|
+
tier = "CHEAP"
|
|
314
|
+
cost = 0.030
|
|
315
|
+
elif complexity_hint <= 7:
|
|
316
|
+
tier = "CAPABLE"
|
|
317
|
+
cost = 0.150
|
|
318
|
+
else:
|
|
319
|
+
tier = "PREMIUM"
|
|
320
|
+
cost = 0.450
|
|
321
|
+
|
|
322
|
+
return TierRecommendationResult(
|
|
323
|
+
tier=tier,
|
|
324
|
+
confidence=0.6,
|
|
325
|
+
reasoning=f"Based on complexity score {complexity_hint}/10",
|
|
326
|
+
expected_cost=cost,
|
|
327
|
+
expected_attempts=2.0,
|
|
328
|
+
similar_patterns_count=0,
|
|
329
|
+
fallback_used=True,
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
# Default: start with CHEAP tier (conservative)
|
|
333
|
+
return TierRecommendationResult(
|
|
334
|
+
tier="CHEAP",
|
|
335
|
+
confidence=0.5,
|
|
336
|
+
reasoning="No historical data - defaulting to CHEAP tier (conservative approach)",
|
|
337
|
+
expected_cost=0.030,
|
|
338
|
+
expected_attempts=1.5,
|
|
339
|
+
similar_patterns_count=0,
|
|
340
|
+
fallback_used=True,
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
def _generate_reasoning(
|
|
344
|
+
self, bug_type: str, tier: str, confidence: float, similar_count: int
|
|
345
|
+
) -> str:
|
|
346
|
+
"""Generate human-readable reasoning for recommendation."""
|
|
347
|
+
percent = int(confidence * 100)
|
|
348
|
+
|
|
349
|
+
if similar_count == 0:
|
|
350
|
+
return "No historical data - defaulting to CHEAP tier"
|
|
351
|
+
elif similar_count == 1:
|
|
352
|
+
return f"1 similar bug ({bug_type}) resolved at {tier} tier"
|
|
353
|
+
else:
|
|
354
|
+
return (
|
|
355
|
+
f"{percent}% of {similar_count} similar bugs ({bug_type}) resolved at {tier} tier"
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
def get_stats(self) -> dict:
|
|
359
|
+
"""Get overall statistics about pattern learning."""
|
|
360
|
+
if not self.patterns:
|
|
361
|
+
return {"total_patterns": 0, "message": "No patterns loaded"}
|
|
362
|
+
|
|
363
|
+
# Calculate tier distribution
|
|
364
|
+
tier_dist: dict[str, int] = defaultdict(int)
|
|
365
|
+
bug_type_dist: dict[str, int] = defaultdict(int)
|
|
366
|
+
total_savings = 0.0
|
|
367
|
+
|
|
368
|
+
for pattern in self.patterns:
|
|
369
|
+
tp = pattern["tier_progression"]
|
|
370
|
+
tier_dist[tp["successful_tier"]] += 1
|
|
371
|
+
bug_type_dist[pattern["bug_type"]] += 1
|
|
372
|
+
total_savings += tp["cost_breakdown"]["savings_percent"]
|
|
373
|
+
|
|
374
|
+
return {
|
|
375
|
+
"total_patterns": len(self.patterns),
|
|
376
|
+
"tier_distribution": dict(tier_dist),
|
|
377
|
+
"bug_type_distribution": dict(bug_type_dist),
|
|
378
|
+
"avg_savings_percent": round(total_savings / len(self.patterns), 1),
|
|
379
|
+
"patterns_by_tier": {
|
|
380
|
+
"CHEAP": tier_dist.get("CHEAP", 0),
|
|
381
|
+
"CAPABLE": tier_dist.get("CAPABLE", 0),
|
|
382
|
+
"PREMIUM": tier_dist.get("PREMIUM", 0),
|
|
383
|
+
},
|
|
384
|
+
}
|
attune/tools.py
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""Interactive user prompting tools.
|
|
2
|
+
|
|
3
|
+
Provides tools for asking users questions and getting structured responses.
|
|
4
|
+
This module implements the AskUserQuestion functionality used by the
|
|
5
|
+
meta-orchestrator for interactive agent team creation.
|
|
6
|
+
|
|
7
|
+
Integration with Claude Code:
|
|
8
|
+
When running Python code that calls this function, Claude Code will
|
|
9
|
+
detect the call and use its AskUserQuestion tool to prompt the user
|
|
10
|
+
in the IDE. This is implemented via a request/response IPC mechanism.
|
|
11
|
+
|
|
12
|
+
Created: 2026-01-29
|
|
13
|
+
"""
|
|
14
|
+
import json
|
|
15
|
+
import logging
|
|
16
|
+
import os
|
|
17
|
+
import tempfile
|
|
18
|
+
from collections.abc import Callable
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
# Global callback for custom AskUserQuestion implementations
|
|
25
|
+
_custom_ask_function: Callable | None = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def set_ask_user_question_handler(handler: Callable) -> None:
|
|
29
|
+
"""Set a custom handler for AskUserQuestion.
|
|
30
|
+
|
|
31
|
+
This allows integration with different UI systems (CLI, web, IDE).
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
handler: Callable that takes questions list and returns response dict
|
|
35
|
+
|
|
36
|
+
Example:
|
|
37
|
+
>>> def my_handler(questions):
|
|
38
|
+
... # Custom UI logic
|
|
39
|
+
... return {"Pattern": "sequential"}
|
|
40
|
+
>>> set_ask_user_question_handler(my_handler)
|
|
41
|
+
"""
|
|
42
|
+
global _custom_ask_function
|
|
43
|
+
_custom_ask_function = handler
|
|
44
|
+
logger.info("Custom AskUserQuestion handler registered")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def AskUserQuestion(questions: list[dict[str, Any]]) -> dict[str, Any]:
|
|
48
|
+
"""Ask user questions and get structured responses.
|
|
49
|
+
|
|
50
|
+
This function supports multiple integration modes:
|
|
51
|
+
1. Custom handler (via set_ask_user_question_handler)
|
|
52
|
+
2. Claude Code IPC (when running in Claude Code environment)
|
|
53
|
+
3. Fallback to NotImplementedError (prompts caller to use automatic mode)
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
questions: List of question dictionaries, each with:
|
|
57
|
+
- header: Short label for the question (str, max 12 chars)
|
|
58
|
+
- question: Full question text (str)
|
|
59
|
+
- multiSelect: Allow multiple selections (bool)
|
|
60
|
+
- options: List of option dicts with label and description
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Dictionary mapping question headers to selected answers
|
|
64
|
+
|
|
65
|
+
Raises:
|
|
66
|
+
NotImplementedError: If no handler available and not in Claude Code
|
|
67
|
+
RuntimeError: If user cancels or interaction fails
|
|
68
|
+
|
|
69
|
+
Example:
|
|
70
|
+
>>> response = AskUserQuestion(
|
|
71
|
+
... questions=[{
|
|
72
|
+
... "header": "Pattern",
|
|
73
|
+
... "question": "Which pattern to use?",
|
|
74
|
+
... "multiSelect": False,
|
|
75
|
+
... "options": [
|
|
76
|
+
... {"label": "sequential", "description": "One after another"},
|
|
77
|
+
... {"label": "parallel", "description": "All at once"}
|
|
78
|
+
... ]
|
|
79
|
+
... }]
|
|
80
|
+
... )
|
|
81
|
+
>>> response
|
|
82
|
+
{"Pattern": "sequential"}
|
|
83
|
+
"""
|
|
84
|
+
# Mode 1: Custom handler
|
|
85
|
+
if _custom_ask_function is not None:
|
|
86
|
+
logger.info("Using custom AskUserQuestion handler")
|
|
87
|
+
return _custom_ask_function(questions)
|
|
88
|
+
|
|
89
|
+
# Mode 2: Claude Code IPC
|
|
90
|
+
# When running inside Claude Code, we can use a file-based IPC mechanism
|
|
91
|
+
if _is_running_in_claude_code():
|
|
92
|
+
logger.info("Using Claude Code IPC for AskUserQuestion")
|
|
93
|
+
return _ask_via_claude_code_ipc(questions)
|
|
94
|
+
|
|
95
|
+
# Mode 3: Fallback - raise error with helpful message
|
|
96
|
+
logger.warning("No AskUserQuestion handler available")
|
|
97
|
+
raise NotImplementedError(
|
|
98
|
+
"AskUserQuestion requires either:\n"
|
|
99
|
+
"1. Custom handler via set_ask_user_question_handler()\n"
|
|
100
|
+
"2. Running in Claude Code environment\n"
|
|
101
|
+
"3. Using interactive=False for automatic mode\n\n"
|
|
102
|
+
"Use: orchestrator.analyze_and_compose(task, interactive=False)"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _is_running_in_claude_code() -> bool:
|
|
107
|
+
"""Check if code is running inside Claude Code environment.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
True if running in Claude Code, False otherwise
|
|
111
|
+
"""
|
|
112
|
+
# Check for Claude Code environment markers
|
|
113
|
+
return (
|
|
114
|
+
os.getenv("CLAUDE_CODE_SESSION") is not None
|
|
115
|
+
or os.getenv("CLAUDE_AGENT_MODE") is not None
|
|
116
|
+
or Path("/tmp/.claude-code").exists()
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _ask_via_claude_code_ipc(questions: list[dict[str, Any]]) -> dict[str, Any]:
|
|
121
|
+
"""Ask user questions via Claude Code IPC mechanism.
|
|
122
|
+
|
|
123
|
+
This creates a request file that Claude Code monitors, then waits for
|
|
124
|
+
the response file to be created with the user's answers.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
questions: List of question dictionaries
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
User's responses as a dictionary
|
|
131
|
+
|
|
132
|
+
Raises:
|
|
133
|
+
RuntimeError: If communication fails or times out
|
|
134
|
+
"""
|
|
135
|
+
import time
|
|
136
|
+
import uuid
|
|
137
|
+
|
|
138
|
+
request_id = str(uuid.uuid4())
|
|
139
|
+
ipc_dir = Path(tempfile.gettempdir()) / ".claude-code-ipc"
|
|
140
|
+
ipc_dir.mkdir(exist_ok=True)
|
|
141
|
+
|
|
142
|
+
request_file = ipc_dir / f"ask-request-{request_id}.json"
|
|
143
|
+
response_file = ipc_dir / f"ask-response-{request_id}.json"
|
|
144
|
+
|
|
145
|
+
try:
|
|
146
|
+
# Write request
|
|
147
|
+
request_data = {
|
|
148
|
+
"request_id": request_id,
|
|
149
|
+
"questions": questions,
|
|
150
|
+
"timestamp": time.time(),
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
request_file.write_text(json.dumps(request_data, indent=2))
|
|
154
|
+
logger.info(f"Wrote IPC request: {request_file}")
|
|
155
|
+
|
|
156
|
+
# Wait for response (max 60 seconds)
|
|
157
|
+
timeout = 60
|
|
158
|
+
start_time = time.time()
|
|
159
|
+
|
|
160
|
+
while time.time() - start_time < timeout:
|
|
161
|
+
if response_file.exists():
|
|
162
|
+
# Read response
|
|
163
|
+
response_data = json.loads(response_file.read_text())
|
|
164
|
+
logger.info(f"Received IPC response: {response_data}")
|
|
165
|
+
|
|
166
|
+
# Cleanup
|
|
167
|
+
request_file.unlink(missing_ok=True)
|
|
168
|
+
response_file.unlink(missing_ok=True)
|
|
169
|
+
|
|
170
|
+
return response_data.get("answers", {})
|
|
171
|
+
|
|
172
|
+
time.sleep(0.1) # Poll every 100ms
|
|
173
|
+
|
|
174
|
+
raise RuntimeError(
|
|
175
|
+
f"Timeout waiting for user response (waited {timeout}s). "
|
|
176
|
+
"User may have cancelled or Claude Code IPC is not active."
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
except Exception as e:
|
|
180
|
+
# Cleanup on error
|
|
181
|
+
request_file.unlink(missing_ok=True)
|
|
182
|
+
response_file.unlink(missing_ok=True)
|
|
183
|
+
raise RuntimeError(f"Claude Code IPC failed: {e}") from e
|
attune/trust/__init__.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Trust Management Module
|
|
2
|
+
|
|
3
|
+
Cross-domain transfer of reliability patterns to human-AI trust management.
|
|
4
|
+
Implements Level 5 (Systems Thinking) capability.
|
|
5
|
+
|
|
6
|
+
Pattern Source: Circuit Breaker (reliability/circuit_breaker.py)
|
|
7
|
+
Transfer: Protect user trust like protecting system stability
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from .circuit_breaker import (
|
|
11
|
+
TrustCircuitBreaker,
|
|
12
|
+
TrustConfig,
|
|
13
|
+
TrustDamageEvent,
|
|
14
|
+
TrustDamageType,
|
|
15
|
+
TrustRecoveryEvent,
|
|
16
|
+
TrustState,
|
|
17
|
+
create_trust_breaker,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"TrustCircuitBreaker",
|
|
22
|
+
"TrustConfig",
|
|
23
|
+
"TrustDamageEvent",
|
|
24
|
+
"TrustDamageType",
|
|
25
|
+
"TrustRecoveryEvent",
|
|
26
|
+
"TrustState",
|
|
27
|
+
"create_trust_breaker",
|
|
28
|
+
]
|