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,281 @@
|
|
|
1
|
+
"""XML validation for response verification.
|
|
2
|
+
|
|
3
|
+
Validates XML-structured responses with graceful fallbacks.
|
|
4
|
+
|
|
5
|
+
Copyright 2026 Smart-AI-Memory
|
|
6
|
+
Licensed under Fair Source License 0.9
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import xml.etree.ElementTree as ET
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class ValidationResult:
|
|
17
|
+
"""Result of XML validation.
|
|
18
|
+
|
|
19
|
+
Attributes:
|
|
20
|
+
is_valid: Whether XML is valid
|
|
21
|
+
error_message: Error message if invalid
|
|
22
|
+
parsed_data: Parsed data if valid
|
|
23
|
+
fallback_used: Whether fallback parsing was used
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
is_valid: bool
|
|
27
|
+
error_message: str | None = None
|
|
28
|
+
parsed_data: dict[str, Any] | None = None
|
|
29
|
+
fallback_used: bool = False
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class XMLValidator:
|
|
33
|
+
"""Validates XML responses with graceful fallbacks.
|
|
34
|
+
|
|
35
|
+
Supports:
|
|
36
|
+
- Well-formedness validation
|
|
37
|
+
- XSD schema validation (optional)
|
|
38
|
+
- Graceful fallback on validation errors
|
|
39
|
+
- Schema caching for performance
|
|
40
|
+
|
|
41
|
+
Usage:
|
|
42
|
+
validator = XMLValidator()
|
|
43
|
+
result = validator.validate("<thinking>...</thinking>")
|
|
44
|
+
|
|
45
|
+
if result.is_valid:
|
|
46
|
+
data = result.parsed_data
|
|
47
|
+
else:
|
|
48
|
+
# Use fallback parsing
|
|
49
|
+
data = fallback_parse(response)
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
schema_dir: str = ".attune/schemas",
|
|
55
|
+
strict: bool = False,
|
|
56
|
+
enable_xsd: bool = False,
|
|
57
|
+
):
|
|
58
|
+
"""Initialize validator.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
schema_dir: Directory containing XSD schemas
|
|
62
|
+
strict: If True, fail on validation errors. If False, use fallback.
|
|
63
|
+
enable_xsd: Enable XSD schema validation (requires lxml)
|
|
64
|
+
"""
|
|
65
|
+
self.schema_dir = Path(schema_dir)
|
|
66
|
+
self.strict = strict
|
|
67
|
+
self.enable_xsd = enable_xsd
|
|
68
|
+
self._schema_cache: dict[str, Any] = {}
|
|
69
|
+
|
|
70
|
+
# Try to import lxml for XSD validation
|
|
71
|
+
self._lxml_available = False
|
|
72
|
+
if enable_xsd:
|
|
73
|
+
try:
|
|
74
|
+
from lxml import etree as lxml_etree # noqa: F401
|
|
75
|
+
|
|
76
|
+
self._lxml_available = True
|
|
77
|
+
except ImportError:
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
def validate(self, xml_string: str, schema_name: str | None = None) -> ValidationResult:
|
|
81
|
+
"""Validate XML string.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
xml_string: XML content to validate
|
|
85
|
+
schema_name: Optional XSD schema name (e.g., "agent_response")
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
ValidationResult with validation status and parsed data
|
|
89
|
+
"""
|
|
90
|
+
# Step 1: Well-formedness validation
|
|
91
|
+
try:
|
|
92
|
+
root = ET.fromstring(xml_string) # nosec B314 - parsing trusted LLM responses
|
|
93
|
+
except ET.ParseError as e:
|
|
94
|
+
if self.strict:
|
|
95
|
+
return ValidationResult(
|
|
96
|
+
is_valid=False,
|
|
97
|
+
error_message=f"XML parsing failed: {e}",
|
|
98
|
+
fallback_used=False,
|
|
99
|
+
)
|
|
100
|
+
# Try fallback parsing
|
|
101
|
+
return self._fallback_parse(xml_string, str(e))
|
|
102
|
+
|
|
103
|
+
# Step 2: XSD schema validation (optional)
|
|
104
|
+
if schema_name and self.enable_xsd and self._lxml_available:
|
|
105
|
+
schema_result = self._validate_with_xsd(xml_string, schema_name)
|
|
106
|
+
if not schema_result.is_valid:
|
|
107
|
+
if self.strict:
|
|
108
|
+
return schema_result
|
|
109
|
+
# Continue with parsed data despite schema error
|
|
110
|
+
return ValidationResult(
|
|
111
|
+
is_valid=True,
|
|
112
|
+
parsed_data=self._extract_data(root),
|
|
113
|
+
fallback_used=True,
|
|
114
|
+
error_message=f"Schema validation failed: {schema_result.error_message}",
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Step 3: Extract structured data
|
|
118
|
+
parsed_data = self._extract_data(root)
|
|
119
|
+
|
|
120
|
+
return ValidationResult(
|
|
121
|
+
is_valid=True,
|
|
122
|
+
parsed_data=parsed_data,
|
|
123
|
+
fallback_used=False,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
def _validate_with_xsd(self, xml_string: str, schema_name: str) -> ValidationResult:
|
|
127
|
+
"""Validate XML against XSD schema.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
xml_string: XML content
|
|
131
|
+
schema_name: Schema file name (without .xsd extension)
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
ValidationResult
|
|
135
|
+
"""
|
|
136
|
+
if not self._lxml_available:
|
|
137
|
+
return ValidationResult(
|
|
138
|
+
is_valid=False,
|
|
139
|
+
error_message="lxml not available for XSD validation",
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
from lxml import etree as lxml_etree
|
|
144
|
+
except ImportError:
|
|
145
|
+
return ValidationResult(
|
|
146
|
+
is_valid=False,
|
|
147
|
+
error_message="lxml import failed",
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# Load schema
|
|
151
|
+
schema_path = self.schema_dir / f"{schema_name}.xsd"
|
|
152
|
+
if not schema_path.exists():
|
|
153
|
+
return ValidationResult(
|
|
154
|
+
is_valid=False,
|
|
155
|
+
error_message=f"Schema not found: {schema_path}",
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# Check cache
|
|
159
|
+
if schema_name not in self._schema_cache:
|
|
160
|
+
try:
|
|
161
|
+
schema_doc = lxml_etree.parse(str(schema_path))
|
|
162
|
+
self._schema_cache[schema_name] = lxml_etree.XMLSchema(schema_doc)
|
|
163
|
+
except Exception as e:
|
|
164
|
+
return ValidationResult(
|
|
165
|
+
is_valid=False,
|
|
166
|
+
error_message=f"Schema loading failed: {e}",
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
schema = self._schema_cache[schema_name]
|
|
170
|
+
|
|
171
|
+
# Validate
|
|
172
|
+
try:
|
|
173
|
+
xml_doc = lxml_etree.fromstring(xml_string.encode("utf-8"))
|
|
174
|
+
is_valid = schema.validate(xml_doc)
|
|
175
|
+
|
|
176
|
+
if not is_valid:
|
|
177
|
+
error_log = schema.error_log
|
|
178
|
+
return ValidationResult(
|
|
179
|
+
is_valid=False,
|
|
180
|
+
error_message=f"Schema validation failed: {error_log}",
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
return ValidationResult(is_valid=True)
|
|
184
|
+
|
|
185
|
+
except Exception as e:
|
|
186
|
+
return ValidationResult(
|
|
187
|
+
is_valid=False,
|
|
188
|
+
error_message=f"Validation error: {e}",
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
def _fallback_parse(self, xml_string: str, error: str) -> ValidationResult:
|
|
192
|
+
"""Attempt fallback parsing of malformed XML.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
xml_string: XML string that failed to parse
|
|
196
|
+
error: Parse error message
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
ValidationResult with fallback data
|
|
200
|
+
"""
|
|
201
|
+
# Try to extract data using regex patterns
|
|
202
|
+
import re
|
|
203
|
+
|
|
204
|
+
data: dict[str, Any] = {}
|
|
205
|
+
|
|
206
|
+
# Extract thinking content
|
|
207
|
+
thinking_match = re.search(r"<thinking[^>]*>(.*?)</thinking>", xml_string, re.DOTALL)
|
|
208
|
+
if thinking_match:
|
|
209
|
+
data["thinking"] = thinking_match.group(1).strip()
|
|
210
|
+
|
|
211
|
+
# Extract answer content
|
|
212
|
+
answer_match = re.search(r"<answer[^>]*>(.*?)</answer>", xml_string, re.DOTALL)
|
|
213
|
+
if answer_match:
|
|
214
|
+
data["answer"] = answer_match.group(1).strip()
|
|
215
|
+
|
|
216
|
+
# If we extracted something, consider it a partial success
|
|
217
|
+
if data:
|
|
218
|
+
return ValidationResult(
|
|
219
|
+
is_valid=True,
|
|
220
|
+
parsed_data=data,
|
|
221
|
+
fallback_used=True,
|
|
222
|
+
error_message=f"XML parsing failed, used fallback: {error}",
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
# Complete failure
|
|
226
|
+
return ValidationResult(
|
|
227
|
+
is_valid=False,
|
|
228
|
+
error_message=f"XML parsing and fallback failed: {error}",
|
|
229
|
+
fallback_used=True,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
def _extract_data(self, root: ET.Element) -> dict[str, Any]:
|
|
233
|
+
"""Extract structured data from parsed XML.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
root: Parsed XML root element
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
Dictionary with extracted data
|
|
240
|
+
"""
|
|
241
|
+
data: dict[str, Any] = {}
|
|
242
|
+
|
|
243
|
+
# Extract all child elements
|
|
244
|
+
for child in root:
|
|
245
|
+
# Handle nested elements
|
|
246
|
+
if len(child):
|
|
247
|
+
data[child.tag] = self._extract_data(child)
|
|
248
|
+
else:
|
|
249
|
+
# Leaf node - get text content
|
|
250
|
+
data[child.tag] = child.text if child.text else ""
|
|
251
|
+
|
|
252
|
+
# Also store root attributes
|
|
253
|
+
if root.attrib:
|
|
254
|
+
data["_attributes"] = dict(root.attrib)
|
|
255
|
+
|
|
256
|
+
return data
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def validate_xml_response(
|
|
260
|
+
response: str,
|
|
261
|
+
schema_name: str | None = None,
|
|
262
|
+
strict: bool = False,
|
|
263
|
+
) -> ValidationResult:
|
|
264
|
+
"""Convenience function to validate XML response.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
response: XML response string
|
|
268
|
+
schema_name: Optional XSD schema name
|
|
269
|
+
strict: If True, fail on validation errors
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
ValidationResult
|
|
273
|
+
|
|
274
|
+
Example:
|
|
275
|
+
>>> response = "<thinking>Analysis</thinking><answer>Result</answer>"
|
|
276
|
+
>>> result = validate_xml_response(response)
|
|
277
|
+
>>> if result.is_valid:
|
|
278
|
+
... print(result.parsed_data)
|
|
279
|
+
"""
|
|
280
|
+
validator = XMLValidator(strict=strict)
|
|
281
|
+
return validator.validate(response, schema_name)
|
attune/vscode_bridge.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""VS Code Extension Bridge
|
|
2
|
+
|
|
3
|
+
Provides functions to write data that the VS Code extension can pick up.
|
|
4
|
+
Enables Claude Code CLI output to appear in VS Code webview panels.
|
|
5
|
+
|
|
6
|
+
Copyright 2026 Smart-AI-Memory
|
|
7
|
+
Licensed under Fair Source License 0.9
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
from dataclasses import asdict, dataclass
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class ReviewFinding:
|
|
19
|
+
"""A code review finding."""
|
|
20
|
+
|
|
21
|
+
id: str
|
|
22
|
+
file: str
|
|
23
|
+
line: int
|
|
24
|
+
severity: str # 'critical' | 'high' | 'medium' | 'low' | 'info'
|
|
25
|
+
category: str # 'security' | 'performance' | 'maintainability' | 'style' | 'correctness'
|
|
26
|
+
message: str
|
|
27
|
+
column: int = 1
|
|
28
|
+
details: str | None = None
|
|
29
|
+
recommendation: str | None = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class CodeReviewResult:
|
|
34
|
+
"""Code review results for VS Code bridge."""
|
|
35
|
+
|
|
36
|
+
findings: list[dict[str, Any]]
|
|
37
|
+
summary: dict[str, Any]
|
|
38
|
+
verdict: str # 'approve' | 'approve_with_suggestions' | 'request_changes' | 'reject'
|
|
39
|
+
security_score: int
|
|
40
|
+
formatted_report: str
|
|
41
|
+
model_tier_used: str
|
|
42
|
+
timestamp: str
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def get_empathy_dir() -> Path:
|
|
46
|
+
"""Get the .empathy directory, creating if needed."""
|
|
47
|
+
empathy_dir = Path(".empathy")
|
|
48
|
+
empathy_dir.mkdir(exist_ok=True)
|
|
49
|
+
return empathy_dir
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def write_code_review_results(
|
|
53
|
+
findings: list[dict[str, Any]] | None = None,
|
|
54
|
+
summary: dict[str, Any] | None = None,
|
|
55
|
+
verdict: str = "approve_with_suggestions",
|
|
56
|
+
security_score: int = 85,
|
|
57
|
+
formatted_report: str = "",
|
|
58
|
+
model_tier_used: str = "capable",
|
|
59
|
+
) -> Path:
|
|
60
|
+
"""Write code review results for VS Code extension to pick up.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
findings: List of finding dicts with keys: id, file, line, severity, category, message
|
|
64
|
+
summary: Summary dict with keys: total_findings, by_severity, by_category, files_affected
|
|
65
|
+
verdict: One of 'approve', 'approve_with_suggestions', 'request_changes', 'reject'
|
|
66
|
+
security_score: 0-100 score
|
|
67
|
+
formatted_report: Markdown formatted report
|
|
68
|
+
model_tier_used: 'cheap', 'capable', or 'premium'
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Path to the written file
|
|
72
|
+
"""
|
|
73
|
+
findings = findings or []
|
|
74
|
+
|
|
75
|
+
# Build summary if not provided
|
|
76
|
+
if summary is None:
|
|
77
|
+
by_severity: dict[str, int] = {}
|
|
78
|
+
by_category: dict[str, int] = {}
|
|
79
|
+
files_affected: set[str] = set()
|
|
80
|
+
|
|
81
|
+
for f in findings:
|
|
82
|
+
sev = f.get("severity", "info")
|
|
83
|
+
cat = f.get("category", "correctness")
|
|
84
|
+
by_severity[sev] = by_severity.get(sev, 0) + 1
|
|
85
|
+
by_category[cat] = by_category.get(cat, 0) + 1
|
|
86
|
+
if f.get("file"):
|
|
87
|
+
files_affected.add(f["file"])
|
|
88
|
+
|
|
89
|
+
summary = {
|
|
90
|
+
"total_findings": len(findings),
|
|
91
|
+
"by_severity": by_severity,
|
|
92
|
+
"by_category": by_category,
|
|
93
|
+
"files_affected": list(files_affected),
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
result = CodeReviewResult(
|
|
97
|
+
findings=findings,
|
|
98
|
+
summary=summary,
|
|
99
|
+
verdict=verdict,
|
|
100
|
+
security_score=security_score,
|
|
101
|
+
formatted_report=formatted_report,
|
|
102
|
+
model_tier_used=model_tier_used,
|
|
103
|
+
timestamp=datetime.now().isoformat(),
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
output_path = get_empathy_dir() / "code-review-results.json"
|
|
107
|
+
|
|
108
|
+
with open(output_path, "w") as f:
|
|
109
|
+
json.dump(asdict(result), f, indent=2)
|
|
110
|
+
|
|
111
|
+
return output_path
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def write_pr_review_results(
|
|
115
|
+
pr_number: int | str,
|
|
116
|
+
title: str,
|
|
117
|
+
findings: list[dict[str, Any]],
|
|
118
|
+
verdict: str = "approve_with_suggestions",
|
|
119
|
+
summary_text: str = "",
|
|
120
|
+
) -> Path:
|
|
121
|
+
"""Write PR review results for VS Code extension.
|
|
122
|
+
|
|
123
|
+
Convenience wrapper for PR reviews from GitHub.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
pr_number: The PR number
|
|
127
|
+
title: PR title
|
|
128
|
+
findings: List of review findings
|
|
129
|
+
verdict: Review verdict
|
|
130
|
+
summary_text: Summary of the review
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
Path to the written file
|
|
134
|
+
"""
|
|
135
|
+
formatted_report = f"""## PR #{pr_number}: {title}
|
|
136
|
+
|
|
137
|
+
{summary_text}
|
|
138
|
+
|
|
139
|
+
### Findings ({len(findings)})
|
|
140
|
+
|
|
141
|
+
"""
|
|
142
|
+
for f in findings:
|
|
143
|
+
formatted_report += f"- **{f.get('severity', 'info').upper()}** [{f.get('file', 'unknown')}:{f.get('line', 0)}]: {f.get('message', '')}\n"
|
|
144
|
+
|
|
145
|
+
return write_code_review_results(
|
|
146
|
+
findings=findings,
|
|
147
|
+
verdict=verdict,
|
|
148
|
+
formatted_report=formatted_report,
|
|
149
|
+
model_tier_used="capable",
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# Quick helper for Claude Code to call
|
|
154
|
+
def send_to_vscode(
|
|
155
|
+
message: str,
|
|
156
|
+
findings: list[dict[str, Any]] | None = None,
|
|
157
|
+
verdict: str = "approve_with_suggestions",
|
|
158
|
+
) -> str:
|
|
159
|
+
"""Quick helper to send review results to VS Code.
|
|
160
|
+
|
|
161
|
+
Usage in Claude Code:
|
|
162
|
+
from attune.vscode_bridge import send_to_vscode
|
|
163
|
+
send_to_vscode("Review complete", findings=[...])
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
Confirmation message
|
|
167
|
+
"""
|
|
168
|
+
path = write_code_review_results(
|
|
169
|
+
findings=findings or [],
|
|
170
|
+
formatted_report=message,
|
|
171
|
+
verdict=verdict,
|
|
172
|
+
)
|
|
173
|
+
return f"Results written to {path} - VS Code will update automatically"
|