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,821 @@
|
|
|
1
|
+
"""Manage_Documentation - Multi-Agent Workflow
|
|
2
|
+
|
|
3
|
+
.. deprecated:: 4.3.0
|
|
4
|
+
This workflow is deprecated in favor of the meta-workflow system.
|
|
5
|
+
Use ``empathy meta-workflow run manage-docs`` instead.
|
|
6
|
+
See docs/CREWAI_MIGRATION.md for migration guide.
|
|
7
|
+
|
|
8
|
+
Makes sure that new program files are fully documented and existing documents
|
|
9
|
+
are updated when associate program files are changed.
|
|
10
|
+
|
|
11
|
+
Pattern: Crew
|
|
12
|
+
- Multiple specialized AI agents collaborate on the task
|
|
13
|
+
- Process Type: sequential
|
|
14
|
+
- Agents: 4
|
|
15
|
+
|
|
16
|
+
Generated with: empathy workflow new Manage_Documentation
|
|
17
|
+
See: docs/guides/WORKFLOW_PATTERNS.md
|
|
18
|
+
|
|
19
|
+
Copyright 2025 Smart-AI-Memory
|
|
20
|
+
Licensed under Fair Source License 0.9
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import asyncio
|
|
24
|
+
import os
|
|
25
|
+
import warnings
|
|
26
|
+
from dataclasses import dataclass, field
|
|
27
|
+
from datetime import datetime
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
from typing import Any
|
|
30
|
+
|
|
31
|
+
# Try to import the LLM executor for actual AI calls
|
|
32
|
+
EmpathyLLMExecutor = None
|
|
33
|
+
ExecutionContext = None
|
|
34
|
+
HAS_EXECUTOR = False
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
from attune.models import ExecutionContext as _ExecutionContext
|
|
38
|
+
from attune.models.empathy_executor import EmpathyLLMExecutor as _EmpathyLLMExecutor
|
|
39
|
+
|
|
40
|
+
EmpathyLLMExecutor = _EmpathyLLMExecutor
|
|
41
|
+
ExecutionContext = _ExecutionContext
|
|
42
|
+
HAS_EXECUTOR = True
|
|
43
|
+
except ImportError:
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
# Try to import the ProjectIndex for file tracking
|
|
47
|
+
ProjectIndex = None
|
|
48
|
+
HAS_PROJECT_INDEX = False
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
from attune.project_index import ProjectIndex as _ProjectIndex
|
|
52
|
+
|
|
53
|
+
ProjectIndex = _ProjectIndex
|
|
54
|
+
HAS_PROJECT_INDEX = True
|
|
55
|
+
except ImportError:
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass
|
|
60
|
+
class ManageDocumentationCrewResult:
|
|
61
|
+
"""Result from ManageDocumentationCrew execution."""
|
|
62
|
+
|
|
63
|
+
success: bool
|
|
64
|
+
findings: list[dict] = field(default_factory=list)
|
|
65
|
+
recommendations: list[str] = field(default_factory=list)
|
|
66
|
+
files_analyzed: int = 0
|
|
67
|
+
docs_needing_update: int = 0
|
|
68
|
+
new_docs_needed: int = 0
|
|
69
|
+
confidence: float = 0.0
|
|
70
|
+
cost: float = 0.0
|
|
71
|
+
duration_ms: int = 0
|
|
72
|
+
formatted_report: str = ""
|
|
73
|
+
|
|
74
|
+
def to_dict(self) -> dict:
|
|
75
|
+
return {
|
|
76
|
+
"success": self.success,
|
|
77
|
+
"findings": self.findings,
|
|
78
|
+
"recommendations": self.recommendations,
|
|
79
|
+
"files_analyzed": self.files_analyzed,
|
|
80
|
+
"docs_needing_update": self.docs_needing_update,
|
|
81
|
+
"new_docs_needed": self.new_docs_needed,
|
|
82
|
+
"confidence": self.confidence,
|
|
83
|
+
"cost": self.cost,
|
|
84
|
+
"duration_ms": self.duration_ms,
|
|
85
|
+
"formatted_report": self.formatted_report,
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@dataclass
|
|
90
|
+
class Agent:
|
|
91
|
+
"""Agent configuration for the crew with XML-enhanced prompting."""
|
|
92
|
+
|
|
93
|
+
role: str
|
|
94
|
+
goal: str
|
|
95
|
+
backstory: str
|
|
96
|
+
expertise_level: str = "expert"
|
|
97
|
+
use_xml_structure: bool = True # Enable XML-enhanced prompting by default
|
|
98
|
+
|
|
99
|
+
def get_system_prompt(self) -> str:
|
|
100
|
+
"""Generate XML-enhanced system prompt for this agent."""
|
|
101
|
+
if not self.use_xml_structure:
|
|
102
|
+
# Legacy format for backward compatibility
|
|
103
|
+
return f"""You are a {self.role} with {self.expertise_level}-level expertise.
|
|
104
|
+
|
|
105
|
+
Goal: {self.goal}
|
|
106
|
+
|
|
107
|
+
Background: {self.backstory}
|
|
108
|
+
|
|
109
|
+
Provide thorough, actionable analysis. Be specific and cite file paths when relevant."""
|
|
110
|
+
|
|
111
|
+
# XML-enhanced format (Anthropic best practice)
|
|
112
|
+
return f"""<agent_role>
|
|
113
|
+
You are a {self.role} with {self.expertise_level}-level expertise.
|
|
114
|
+
</agent_role>
|
|
115
|
+
|
|
116
|
+
<agent_goal>
|
|
117
|
+
{self.goal}
|
|
118
|
+
</agent_goal>
|
|
119
|
+
|
|
120
|
+
<agent_backstory>
|
|
121
|
+
{self.backstory}
|
|
122
|
+
</agent_backstory>
|
|
123
|
+
|
|
124
|
+
<instructions>
|
|
125
|
+
1. Carefully review all provided context data
|
|
126
|
+
2. Think through your analysis step-by-step
|
|
127
|
+
3. Provide thorough, actionable analysis
|
|
128
|
+
4. Be specific and cite file paths when relevant
|
|
129
|
+
5. Structure your output according to the requested format
|
|
130
|
+
</instructions>
|
|
131
|
+
|
|
132
|
+
<output_structure>
|
|
133
|
+
Always structure your response as:
|
|
134
|
+
|
|
135
|
+
<thinking>
|
|
136
|
+
[Your step-by-step reasoning process]
|
|
137
|
+
- What you observe in the context
|
|
138
|
+
- How you analyze the situation
|
|
139
|
+
- What conclusions you draw
|
|
140
|
+
</thinking>
|
|
141
|
+
|
|
142
|
+
<answer>
|
|
143
|
+
[Your final output in the requested format]
|
|
144
|
+
</answer>
|
|
145
|
+
</output_structure>"""
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@dataclass
|
|
149
|
+
class Task:
|
|
150
|
+
"""Task configuration for the crew with XML-enhanced prompting."""
|
|
151
|
+
|
|
152
|
+
description: str
|
|
153
|
+
expected_output: str
|
|
154
|
+
agent: Agent
|
|
155
|
+
|
|
156
|
+
def get_user_prompt(self, context: dict) -> str:
|
|
157
|
+
"""Generate XML-enhanced user prompt for this task with context."""
|
|
158
|
+
if not self.agent.use_xml_structure:
|
|
159
|
+
# Legacy format for backward compatibility
|
|
160
|
+
context_str = "\n".join(f"- {k}: {v}" for k, v in context.items() if v)
|
|
161
|
+
return f"""{self.description}
|
|
162
|
+
|
|
163
|
+
Context:
|
|
164
|
+
{context_str}
|
|
165
|
+
|
|
166
|
+
Expected output format: {self.expected_output}"""
|
|
167
|
+
|
|
168
|
+
# XML-enhanced format (Anthropic best practice)
|
|
169
|
+
# Build structured context with proper XML tags
|
|
170
|
+
context_sections = []
|
|
171
|
+
for key, value in context.items():
|
|
172
|
+
if value:
|
|
173
|
+
# Use underscores for tag names
|
|
174
|
+
tag_name = key.replace(" ", "_").replace("-", "_").lower()
|
|
175
|
+
# Wrap in appropriate tags
|
|
176
|
+
context_sections.append(f"<{tag_name}>\n{value}\n</{tag_name}>")
|
|
177
|
+
|
|
178
|
+
context_xml = "\n".join(context_sections)
|
|
179
|
+
|
|
180
|
+
return f"""<task_description>
|
|
181
|
+
{self.description}
|
|
182
|
+
</task_description>
|
|
183
|
+
|
|
184
|
+
<context>
|
|
185
|
+
{context_xml}
|
|
186
|
+
</context>
|
|
187
|
+
|
|
188
|
+
<expected_output>
|
|
189
|
+
{self.expected_output}
|
|
190
|
+
</expected_output>
|
|
191
|
+
|
|
192
|
+
<instructions>
|
|
193
|
+
1. Review all context data in the <context> tags above
|
|
194
|
+
2. Structure your response using <thinking> and <answer> tags as defined in your system prompt
|
|
195
|
+
3. Match the expected output format exactly
|
|
196
|
+
4. Be thorough and specific in your analysis
|
|
197
|
+
</instructions>"""
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def parse_xml_response(response: str) -> dict:
|
|
201
|
+
"""Parse XML-structured agent response.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
response: Raw agent response potentially containing XML tags
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
Dictionary with 'thinking', 'answer', and 'raw' keys
|
|
208
|
+
"""
|
|
209
|
+
import re
|
|
210
|
+
|
|
211
|
+
thinking_match = re.search(r"<thinking>(.*?)</thinking>", response, re.DOTALL)
|
|
212
|
+
answer_match = re.search(r"<answer>(.*?)</answer>", response, re.DOTALL)
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
"thinking": thinking_match.group(1).strip() if thinking_match else "",
|
|
216
|
+
"answer": answer_match.group(1).strip() if answer_match else response.strip(),
|
|
217
|
+
"raw": response,
|
|
218
|
+
"has_structure": bool(thinking_match and answer_match),
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def format_manage_docs_report(result: ManageDocumentationCrewResult, path: str) -> str:
|
|
223
|
+
"""Format documentation management output as a human-readable report.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
result: The ManageDocumentationCrewResult
|
|
227
|
+
path: The path that was analyzed
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
Formatted report string
|
|
231
|
+
"""
|
|
232
|
+
lines = []
|
|
233
|
+
|
|
234
|
+
# Header with confidence
|
|
235
|
+
confidence = result.confidence
|
|
236
|
+
if confidence >= 0.8:
|
|
237
|
+
confidence_icon = "🟢"
|
|
238
|
+
confidence_text = "HIGH CONFIDENCE"
|
|
239
|
+
elif confidence >= 0.5:
|
|
240
|
+
confidence_icon = "🟡"
|
|
241
|
+
confidence_text = "MODERATE CONFIDENCE"
|
|
242
|
+
else:
|
|
243
|
+
confidence_icon = "🔴"
|
|
244
|
+
confidence_text = "LOW CONFIDENCE (Mock Mode)"
|
|
245
|
+
|
|
246
|
+
lines.append("=" * 60)
|
|
247
|
+
lines.append("DOCUMENTATION SYNC REPORT")
|
|
248
|
+
lines.append("=" * 60)
|
|
249
|
+
lines.append("")
|
|
250
|
+
lines.append(f"Path Analyzed: {path}")
|
|
251
|
+
lines.append(f"Confidence: {confidence_icon} {confidence_text} ({confidence:.0%})")
|
|
252
|
+
lines.append("")
|
|
253
|
+
|
|
254
|
+
# Summary
|
|
255
|
+
lines.append("-" * 60)
|
|
256
|
+
lines.append("SUMMARY")
|
|
257
|
+
lines.append("-" * 60)
|
|
258
|
+
lines.append(f"Files Analyzed: {result.files_analyzed}")
|
|
259
|
+
lines.append(f"Docs Needing Update: {result.docs_needing_update}")
|
|
260
|
+
lines.append(f"New Docs Needed: {result.new_docs_needed}")
|
|
261
|
+
lines.append(f"Duration: {result.duration_ms}ms ({result.duration_ms / 1000:.1f}s)")
|
|
262
|
+
lines.append(f"Cost: ${result.cost:.4f}")
|
|
263
|
+
lines.append("")
|
|
264
|
+
|
|
265
|
+
# Agent Findings
|
|
266
|
+
if result.findings:
|
|
267
|
+
lines.append("-" * 60)
|
|
268
|
+
lines.append("AGENT FINDINGS")
|
|
269
|
+
lines.append("-" * 60)
|
|
270
|
+
for i, finding in enumerate(result.findings, 1):
|
|
271
|
+
agent = finding.get("agent", f"Agent {i}")
|
|
272
|
+
response = finding.get("response", "")
|
|
273
|
+
thinking = finding.get("thinking", "")
|
|
274
|
+
answer = finding.get("answer", "")
|
|
275
|
+
has_xml = finding.get("has_xml_structure", False)
|
|
276
|
+
cost = finding.get("cost", 0.0)
|
|
277
|
+
|
|
278
|
+
lines.append(
|
|
279
|
+
f"\n{i}. {agent} (Cost: ${cost:.4f}) {'🔬 XML-Structured' if has_xml else ''}"
|
|
280
|
+
)
|
|
281
|
+
lines.append(" " + "-" * 54)
|
|
282
|
+
|
|
283
|
+
# Show thinking and answer separately if available
|
|
284
|
+
if has_xml and thinking:
|
|
285
|
+
lines.append(" 💭 Thinking:")
|
|
286
|
+
if len(thinking) > 300:
|
|
287
|
+
lines.append(f" {thinking[:300]}...")
|
|
288
|
+
else:
|
|
289
|
+
lines.append(f" {thinking}")
|
|
290
|
+
lines.append("")
|
|
291
|
+
lines.append(" ✅ Answer:")
|
|
292
|
+
if len(answer) > 300:
|
|
293
|
+
lines.append(f" {answer[:300]}...")
|
|
294
|
+
else:
|
|
295
|
+
lines.append(f" {answer}")
|
|
296
|
+
else:
|
|
297
|
+
# Fallback to original response
|
|
298
|
+
if len(response) > 500:
|
|
299
|
+
lines.append(f" {response[:500]}...")
|
|
300
|
+
lines.append(f" [Truncated - {len(response)} chars total]")
|
|
301
|
+
else:
|
|
302
|
+
lines.append(f" {response}")
|
|
303
|
+
lines.append("")
|
|
304
|
+
|
|
305
|
+
# Recommendations
|
|
306
|
+
if result.recommendations:
|
|
307
|
+
lines.append("-" * 60)
|
|
308
|
+
lines.append("RECOMMENDATIONS")
|
|
309
|
+
lines.append("-" * 60)
|
|
310
|
+
for i, rec in enumerate(result.recommendations, 1):
|
|
311
|
+
lines.append(f"{i}. {rec}")
|
|
312
|
+
lines.append("")
|
|
313
|
+
|
|
314
|
+
# Next Steps
|
|
315
|
+
lines.append("-" * 60)
|
|
316
|
+
lines.append("NEXT STEPS")
|
|
317
|
+
lines.append("-" * 60)
|
|
318
|
+
lines.append("1. Review agent findings above for specific files")
|
|
319
|
+
lines.append("2. Prioritize documentation updates based on impact")
|
|
320
|
+
lines.append("3. Use 'Generate Docs' workflow for auto-generation")
|
|
321
|
+
lines.append("4. Run this workflow periodically to keep docs in sync")
|
|
322
|
+
lines.append("")
|
|
323
|
+
|
|
324
|
+
# Footer
|
|
325
|
+
lines.append("=" * 60)
|
|
326
|
+
if result.success:
|
|
327
|
+
lines.append("✅ Documentation sync analysis complete")
|
|
328
|
+
else:
|
|
329
|
+
lines.append("❌ Documentation sync analysis failed")
|
|
330
|
+
lines.append("=" * 60)
|
|
331
|
+
|
|
332
|
+
return "\n".join(lines)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
class ManageDocumentationCrew:
|
|
336
|
+
"""Manage_Documentation - Documentation management crew.
|
|
337
|
+
|
|
338
|
+
Makes sure that new program files are fully documented and existing
|
|
339
|
+
documents are updated when associated program files are changed.
|
|
340
|
+
|
|
341
|
+
Process Type: sequential
|
|
342
|
+
|
|
343
|
+
Agents:
|
|
344
|
+
- Analyst: Scans codebase to identify documentation gaps
|
|
345
|
+
- Reviewer: Cross-checks findings and validates accuracy
|
|
346
|
+
- Synthesizer: Combines findings into actionable recommendations
|
|
347
|
+
- Manager: Coordinates actions and prioritizes work
|
|
348
|
+
|
|
349
|
+
Usage:
|
|
350
|
+
crew = ManageDocumentationCrew()
|
|
351
|
+
result = await crew.execute(path="./src", context={})
|
|
352
|
+
"""
|
|
353
|
+
|
|
354
|
+
name = "Manage_Documentation"
|
|
355
|
+
description = "Makes sure that new program files are fully documented and existing documents are updated when associated program files are changed."
|
|
356
|
+
process_type = "sequential"
|
|
357
|
+
|
|
358
|
+
def __init__(self, project_root: str = ".", **kwargs: Any):
|
|
359
|
+
"""Initialize the crew with configured agents.
|
|
360
|
+
|
|
361
|
+
.. deprecated:: 4.3.0
|
|
362
|
+
Use meta-workflow system instead: ``empathy meta-workflow run manage-docs``
|
|
363
|
+
"""
|
|
364
|
+
warnings.warn(
|
|
365
|
+
"ManageDocumentationCrew is deprecated since v4.3.0. "
|
|
366
|
+
"Use meta-workflow system instead: empathy meta-workflow run manage-docs. "
|
|
367
|
+
"See docs/CREWAI_MIGRATION.md for migration guide.",
|
|
368
|
+
DeprecationWarning,
|
|
369
|
+
stacklevel=2,
|
|
370
|
+
)
|
|
371
|
+
self.config = kwargs
|
|
372
|
+
self.project_root = project_root
|
|
373
|
+
self._executor = None
|
|
374
|
+
self._project_index = None
|
|
375
|
+
self._total_cost = 0.0
|
|
376
|
+
self._total_input_tokens = 0
|
|
377
|
+
self._total_output_tokens = 0
|
|
378
|
+
|
|
379
|
+
# Initialize executor if available
|
|
380
|
+
if HAS_EXECUTOR and EmpathyLLMExecutor is not None:
|
|
381
|
+
api_key = os.getenv("ANTHROPIC_API_KEY")
|
|
382
|
+
if api_key:
|
|
383
|
+
try:
|
|
384
|
+
self._executor = EmpathyLLMExecutor(
|
|
385
|
+
provider="anthropic",
|
|
386
|
+
api_key=api_key,
|
|
387
|
+
)
|
|
388
|
+
except Exception:
|
|
389
|
+
pass
|
|
390
|
+
|
|
391
|
+
# Initialize ProjectIndex if available
|
|
392
|
+
if HAS_PROJECT_INDEX and ProjectIndex is not None:
|
|
393
|
+
try:
|
|
394
|
+
self._project_index = ProjectIndex(project_root)
|
|
395
|
+
if not self._project_index.load():
|
|
396
|
+
# Index doesn't exist or is stale, refresh it
|
|
397
|
+
print(" [ProjectIndex] Building index (first run)...")
|
|
398
|
+
self._project_index.refresh()
|
|
399
|
+
except Exception as e:
|
|
400
|
+
print(f" [ProjectIndex] Warning: Could not load index: {e}")
|
|
401
|
+
|
|
402
|
+
# Define agents
|
|
403
|
+
self.analyst = Agent(
|
|
404
|
+
role="Documentation Analyst",
|
|
405
|
+
goal="Scan the codebase to identify files lacking documentation and find stale docs",
|
|
406
|
+
backstory="Expert analyst who understands code structure, docstrings, and documentation best practices. Skilled at identifying gaps between code and documentation.",
|
|
407
|
+
expertise_level="expert",
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
self.reviewer = Agent(
|
|
411
|
+
role="Documentation Reviewer",
|
|
412
|
+
goal="Cross-check findings and validate accuracy of the analysis",
|
|
413
|
+
backstory="Experienced technical writer and reviewer focused on quality, correctness, and ensuring documentation matches actual code behavior.",
|
|
414
|
+
expertise_level="expert",
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
self.synthesizer = Agent(
|
|
418
|
+
role="Documentation Synthesizer",
|
|
419
|
+
goal="Combine findings into actionable, prioritized recommendations",
|
|
420
|
+
backstory="Strategic thinker who excels at synthesis and prioritization. Creates clear action plans that developers can follow.",
|
|
421
|
+
expertise_level="expert",
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
self.manager = Agent(
|
|
425
|
+
role="Documentation Manager",
|
|
426
|
+
goal="Coordinate actions of other agents and prioritize documentation work",
|
|
427
|
+
backstory="Understands the documentation needs of the project and the capability of other agents. Makes decisions about what to document first based on impact and effort.",
|
|
428
|
+
expertise_level="world-class",
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
# Store all agents
|
|
432
|
+
self.agents = [self.analyst, self.reviewer, self.synthesizer, self.manager]
|
|
433
|
+
|
|
434
|
+
def define_tasks(self) -> list[Task]:
|
|
435
|
+
"""Define the tasks for this crew."""
|
|
436
|
+
return [
|
|
437
|
+
Task(
|
|
438
|
+
description="Analyze the codebase to identify: 1) Python files without docstrings, 2) Functions/classes missing documentation, 3) README files that may be outdated, 4) Missing API documentation",
|
|
439
|
+
expected_output="JSON list of findings with: file_path, issue_type (missing_docstring|stale_doc|no_readme), severity (high|medium|low), details",
|
|
440
|
+
agent=self.analyst,
|
|
441
|
+
),
|
|
442
|
+
Task(
|
|
443
|
+
description="Review and validate the analysis findings. Check if flagged files truly need documentation updates. Identify any false positives.",
|
|
444
|
+
expected_output="Validated findings with confidence scores (0-1) and notes on any false positives removed",
|
|
445
|
+
agent=self.reviewer,
|
|
446
|
+
),
|
|
447
|
+
Task(
|
|
448
|
+
description="Synthesize validated findings into a prioritized action plan. Group by module/area, estimate effort, and create clear next steps.",
|
|
449
|
+
expected_output="Prioritized list of documentation tasks with: priority (1-5), task description, estimated effort (small|medium|large), files involved",
|
|
450
|
+
agent=self.synthesizer,
|
|
451
|
+
),
|
|
452
|
+
]
|
|
453
|
+
|
|
454
|
+
async def _call_llm(
|
|
455
|
+
self,
|
|
456
|
+
agent: Agent,
|
|
457
|
+
task: Task,
|
|
458
|
+
context: dict,
|
|
459
|
+
task_type: str = "code_analysis",
|
|
460
|
+
) -> tuple[str, int, int, float]:
|
|
461
|
+
"""Call the LLM with agent/task configuration.
|
|
462
|
+
|
|
463
|
+
Returns: (response_text, input_tokens, output_tokens, cost)
|
|
464
|
+
"""
|
|
465
|
+
system_prompt = agent.get_system_prompt()
|
|
466
|
+
user_prompt = task.get_user_prompt(context)
|
|
467
|
+
|
|
468
|
+
if self._executor is not None and ExecutionContext is not None:
|
|
469
|
+
try:
|
|
470
|
+
exec_context = ExecutionContext(
|
|
471
|
+
workflow_name=self.name,
|
|
472
|
+
step_name=agent.role.lower().replace(" ", "_"),
|
|
473
|
+
task_type=task_type,
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
response = await self._executor.run(
|
|
477
|
+
task_type=task_type,
|
|
478
|
+
prompt=user_prompt,
|
|
479
|
+
system=system_prompt,
|
|
480
|
+
context=exec_context,
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
return (
|
|
484
|
+
response.content,
|
|
485
|
+
response.tokens_input,
|
|
486
|
+
response.tokens_output,
|
|
487
|
+
response.cost_estimate or 0.0,
|
|
488
|
+
)
|
|
489
|
+
except Exception as e:
|
|
490
|
+
# Fallback to mock response on error
|
|
491
|
+
return self._mock_response(agent, task, context, str(e))
|
|
492
|
+
else:
|
|
493
|
+
# No executor available - return mock response
|
|
494
|
+
return self._mock_response(agent, task, context, "No LLM executor configured")
|
|
495
|
+
|
|
496
|
+
def _mock_response(
|
|
497
|
+
self,
|
|
498
|
+
agent: Agent,
|
|
499
|
+
task: Task,
|
|
500
|
+
context: dict,
|
|
501
|
+
reason: str,
|
|
502
|
+
) -> tuple[str, int, int, float]:
|
|
503
|
+
"""Generate a mock response when LLM is not available."""
|
|
504
|
+
mock_findings = {
|
|
505
|
+
"Documentation Analyst": f"""[Mock Analysis - {reason}]
|
|
506
|
+
|
|
507
|
+
Based on scanning the path: {context.get("path", ".")}
|
|
508
|
+
|
|
509
|
+
Findings:
|
|
510
|
+
1. {{
|
|
511
|
+
"file_path": "src/example.py",
|
|
512
|
+
"issue_type": "missing_docstring",
|
|
513
|
+
"severity": "medium",
|
|
514
|
+
"details": "Module lacks module-level docstring"
|
|
515
|
+
}}
|
|
516
|
+
2. {{
|
|
517
|
+
"file_path": "README.md",
|
|
518
|
+
"issue_type": "stale_doc",
|
|
519
|
+
"severity": "low",
|
|
520
|
+
"details": "README may not reflect recent changes"
|
|
521
|
+
}}
|
|
522
|
+
|
|
523
|
+
Note: This is a mock response. Configure ANTHROPIC_API_KEY for real analysis.""",
|
|
524
|
+
"Documentation Reviewer": f"""[Mock Review - {reason}]
|
|
525
|
+
|
|
526
|
+
Validated Findings:
|
|
527
|
+
- Finding 1: VALID (confidence: 0.8) - Missing docstrings are a real issue
|
|
528
|
+
- Finding 2: NEEDS_VERIFICATION (confidence: 0.5) - Stale docs need manual check
|
|
529
|
+
|
|
530
|
+
False Positives Removed: 0
|
|
531
|
+
|
|
532
|
+
Note: This is a mock response. Configure ANTHROPIC_API_KEY for real analysis.""",
|
|
533
|
+
"Documentation Synthesizer": f"""[Mock Synthesis - {reason}]
|
|
534
|
+
|
|
535
|
+
Prioritized Action Plan:
|
|
536
|
+
|
|
537
|
+
1. Priority 1 (High) - Add module docstrings
|
|
538
|
+
- Effort: small
|
|
539
|
+
- Files: src/example.py
|
|
540
|
+
|
|
541
|
+
2. Priority 3 (Medium) - Review README accuracy
|
|
542
|
+
- Effort: medium
|
|
543
|
+
- Files: README.md
|
|
544
|
+
|
|
545
|
+
Note: This is a mock response. Configure ANTHROPIC_API_KEY for real analysis.""",
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
response = mock_findings.get(agent.role, f"Mock response for {agent.role}")
|
|
549
|
+
return (response, 0, 0, 0.0)
|
|
550
|
+
|
|
551
|
+
def _scan_directory(self, path: str) -> dict:
|
|
552
|
+
"""Scan directory for Python files and documentation."""
|
|
553
|
+
path_obj = Path(path)
|
|
554
|
+
if not path_obj.exists():
|
|
555
|
+
return {"error": f"Path does not exist: {path}"}
|
|
556
|
+
|
|
557
|
+
python_files = []
|
|
558
|
+
doc_files = []
|
|
559
|
+
|
|
560
|
+
# Find Python files
|
|
561
|
+
for py_file in path_obj.rglob("*.py"):
|
|
562
|
+
if "__pycache__" not in str(py_file):
|
|
563
|
+
python_files.append(str(py_file))
|
|
564
|
+
|
|
565
|
+
# Find documentation files
|
|
566
|
+
for pattern in ["*.md", "*.rst", "*.txt"]:
|
|
567
|
+
for doc_file in path_obj.rglob(pattern):
|
|
568
|
+
doc_files.append(str(doc_file))
|
|
569
|
+
|
|
570
|
+
return {
|
|
571
|
+
"python_files": python_files[:50], # Limit for context
|
|
572
|
+
"python_file_count": len(python_files),
|
|
573
|
+
"doc_files": doc_files[:20],
|
|
574
|
+
"doc_file_count": len(doc_files),
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
def _get_index_context(self) -> dict[str, Any]:
|
|
578
|
+
"""Get documentation context from ProjectIndex if available."""
|
|
579
|
+
if self._project_index is None:
|
|
580
|
+
return {}
|
|
581
|
+
|
|
582
|
+
try:
|
|
583
|
+
return self._project_index.get_context_for_workflow("documentation")
|
|
584
|
+
except Exception as e:
|
|
585
|
+
print(f" [ProjectIndex] Warning: Could not get context: {e}")
|
|
586
|
+
return {}
|
|
587
|
+
|
|
588
|
+
async def execute(
|
|
589
|
+
self,
|
|
590
|
+
path: str = ".",
|
|
591
|
+
context: dict | None = None,
|
|
592
|
+
**kwargs: Any,
|
|
593
|
+
) -> ManageDocumentationCrewResult:
|
|
594
|
+
"""Execute the documentation management crew.
|
|
595
|
+
|
|
596
|
+
Args:
|
|
597
|
+
path: Path to analyze for documentation gaps
|
|
598
|
+
context: Additional context for agents
|
|
599
|
+
**kwargs: Additional arguments
|
|
600
|
+
|
|
601
|
+
Returns:
|
|
602
|
+
ManageDocumentationCrewResult with findings and recommendations
|
|
603
|
+
|
|
604
|
+
"""
|
|
605
|
+
started_at = datetime.now()
|
|
606
|
+
context = context or {}
|
|
607
|
+
|
|
608
|
+
# Try to get rich context from ProjectIndex
|
|
609
|
+
index_context = self._get_index_context()
|
|
610
|
+
|
|
611
|
+
if index_context:
|
|
612
|
+
print(" [ProjectIndex] Using indexed file data")
|
|
613
|
+
doc_stats = index_context.get("documentation_stats", {})
|
|
614
|
+
|
|
615
|
+
# Build context from index
|
|
616
|
+
agent_context = {
|
|
617
|
+
"path": path,
|
|
618
|
+
"python_files": f"{doc_stats.get('total_python_files', 0)} Python files indexed",
|
|
619
|
+
"files_with_docstrings": f"{doc_stats.get('files_with_docstrings', 0)} files ({doc_stats.get('docstring_coverage_pct', 0):.1f}% coverage)",
|
|
620
|
+
"files_without_docstrings": f"{doc_stats.get('files_without_docstrings', 0)} files need docstrings",
|
|
621
|
+
"type_hint_coverage": f"{doc_stats.get('type_hint_coverage_pct', 0):.1f}%",
|
|
622
|
+
"high_impact_undocumented": doc_stats.get("priority_files", []),
|
|
623
|
+
"doc_files": f"{doc_stats.get('doc_file_count', 0)} documentation files",
|
|
624
|
+
"total_loc_undocumented": doc_stats.get("loc_undocumented", 0),
|
|
625
|
+
# New: modification tracking
|
|
626
|
+
"recently_modified_source_count": doc_stats.get(
|
|
627
|
+
"recently_modified_source_count",
|
|
628
|
+
0,
|
|
629
|
+
),
|
|
630
|
+
"stale_docs_count": doc_stats.get("stale_docs_count", 0),
|
|
631
|
+
**context,
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
# Add sample of files needing docs
|
|
635
|
+
files_without_docs = index_context.get("files_without_docstrings", [])
|
|
636
|
+
if files_without_docs:
|
|
637
|
+
agent_context["sample_undocumented"] = [f["path"] for f in files_without_docs[:10]]
|
|
638
|
+
|
|
639
|
+
# Add recently modified source files (last 7 days)
|
|
640
|
+
recent_source = index_context.get("recently_modified_source", [])
|
|
641
|
+
if recent_source:
|
|
642
|
+
agent_context["recently_modified_source_files"] = [
|
|
643
|
+
{"path": f["path"], "modified": f.get("last_modified")}
|
|
644
|
+
for f in recent_source[:10]
|
|
645
|
+
]
|
|
646
|
+
|
|
647
|
+
# Add docs that may need review (source changed after doc)
|
|
648
|
+
docs_needing_review = index_context.get("docs_needing_review", [])
|
|
649
|
+
if docs_needing_review:
|
|
650
|
+
stale_docs = [d for d in docs_needing_review if d.get("source_modified_after_doc")]
|
|
651
|
+
agent_context["stale_docs"] = [
|
|
652
|
+
{
|
|
653
|
+
"doc": d["doc_file"],
|
|
654
|
+
"related_source": d["related_source_files"][:3],
|
|
655
|
+
"days_since_update": d["days_since_doc_update"],
|
|
656
|
+
}
|
|
657
|
+
for d in stale_docs[:5]
|
|
658
|
+
]
|
|
659
|
+
|
|
660
|
+
scan_results = {
|
|
661
|
+
"python_file_count": doc_stats.get("total_python_files", 0),
|
|
662
|
+
"doc_file_count": doc_stats.get("doc_file_count", 0),
|
|
663
|
+
"python_files": [f["path"] for f in files_without_docs[:50]],
|
|
664
|
+
"doc_files": [f["path"] for f in index_context.get("doc_files", [])[:20]],
|
|
665
|
+
"recently_modified_count": doc_stats.get("recently_modified_source_count", 0),
|
|
666
|
+
"stale_docs_count": doc_stats.get("stale_docs_count", 0),
|
|
667
|
+
}
|
|
668
|
+
else:
|
|
669
|
+
# Fallback to directory scanning
|
|
670
|
+
print(" [Fallback] Scanning directory manually")
|
|
671
|
+
scan_results = self._scan_directory(path)
|
|
672
|
+
if "error" in scan_results:
|
|
673
|
+
return ManageDocumentationCrewResult(
|
|
674
|
+
success=False,
|
|
675
|
+
findings=[{"error": scan_results["error"]}],
|
|
676
|
+
recommendations=["Fix the path and try again"],
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
# Build context for agents
|
|
680
|
+
agent_context = {
|
|
681
|
+
"path": path,
|
|
682
|
+
"python_files": f"{scan_results['python_file_count']} files found",
|
|
683
|
+
"sample_files": ", ".join(scan_results["python_files"][:10]),
|
|
684
|
+
"doc_files": f"{scan_results['doc_file_count']} doc files found",
|
|
685
|
+
**context,
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
# Get tasks
|
|
689
|
+
tasks = self.define_tasks()
|
|
690
|
+
|
|
691
|
+
# Execute tasks sequentially (crew pattern)
|
|
692
|
+
all_findings: list[dict] = []
|
|
693
|
+
all_responses: list[str] = []
|
|
694
|
+
|
|
695
|
+
for i, task in enumerate(tasks):
|
|
696
|
+
print(f" [{i + 1}/{len(tasks)}] {task.agent.role}: {task.description[:50]}...")
|
|
697
|
+
|
|
698
|
+
# Add previous task output to context
|
|
699
|
+
if all_responses:
|
|
700
|
+
agent_context["previous_analysis"] = all_responses[-1][:2000]
|
|
701
|
+
|
|
702
|
+
# Determine task type for routing
|
|
703
|
+
task_type = "code_analysis"
|
|
704
|
+
if "review" in task.agent.role.lower():
|
|
705
|
+
task_type = "code_analysis" # Could be "review" if defined
|
|
706
|
+
elif "synth" in task.agent.role.lower():
|
|
707
|
+
task_type = "summarize"
|
|
708
|
+
|
|
709
|
+
# Call LLM
|
|
710
|
+
response, in_tokens, out_tokens, cost = await self._call_llm(
|
|
711
|
+
agent=task.agent,
|
|
712
|
+
task=task,
|
|
713
|
+
context=agent_context,
|
|
714
|
+
task_type=task_type,
|
|
715
|
+
)
|
|
716
|
+
|
|
717
|
+
# Track metrics
|
|
718
|
+
self._total_input_tokens += in_tokens
|
|
719
|
+
self._total_output_tokens += out_tokens
|
|
720
|
+
self._total_cost += cost
|
|
721
|
+
|
|
722
|
+
# Parse XML-structured response if available
|
|
723
|
+
parsed = parse_xml_response(response)
|
|
724
|
+
|
|
725
|
+
# Store full response for next agent's context
|
|
726
|
+
all_responses.append(response)
|
|
727
|
+
|
|
728
|
+
# Store findings with parsed structure
|
|
729
|
+
all_findings.append(
|
|
730
|
+
{
|
|
731
|
+
"agent": task.agent.role,
|
|
732
|
+
"task": task.description[:100],
|
|
733
|
+
"response": response[:1000], # Truncate for result
|
|
734
|
+
"thinking": parsed["thinking"][:500] if parsed["thinking"] else "",
|
|
735
|
+
"answer": parsed["answer"][:500] if parsed["answer"] else response[:500],
|
|
736
|
+
"has_xml_structure": parsed["has_structure"],
|
|
737
|
+
"tokens": {"input": in_tokens, "output": out_tokens},
|
|
738
|
+
"cost": cost,
|
|
739
|
+
},
|
|
740
|
+
)
|
|
741
|
+
|
|
742
|
+
# Manager coordination (final synthesis)
|
|
743
|
+
manager_context = {
|
|
744
|
+
"path": path,
|
|
745
|
+
"analyst_findings": all_responses[0][:1500] if len(all_responses) > 0 else "",
|
|
746
|
+
"reviewer_validation": all_responses[1][:1500] if len(all_responses) > 1 else "",
|
|
747
|
+
"synthesizer_plan": all_responses[2][:1500] if len(all_responses) > 2 else "",
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
print(f" [Final] {self.manager.role}: Coordinating final output...")
|
|
751
|
+
|
|
752
|
+
manager_task = Task(
|
|
753
|
+
description="Review all agent outputs and create a final executive summary with the top 3-5 prioritized actions for improving documentation.",
|
|
754
|
+
expected_output="Executive summary with: 1) Overall documentation health score (0-100), 2) Top priorities, 3) Quick wins, 4) Estimated total effort",
|
|
755
|
+
agent=self.manager,
|
|
756
|
+
)
|
|
757
|
+
|
|
758
|
+
final_response, in_tokens, out_tokens, cost = await self._call_llm(
|
|
759
|
+
agent=self.manager,
|
|
760
|
+
task=manager_task,
|
|
761
|
+
context=manager_context,
|
|
762
|
+
task_type="summarize",
|
|
763
|
+
)
|
|
764
|
+
|
|
765
|
+
self._total_input_tokens += in_tokens
|
|
766
|
+
self._total_output_tokens += out_tokens
|
|
767
|
+
self._total_cost += cost
|
|
768
|
+
|
|
769
|
+
# Calculate duration
|
|
770
|
+
duration_ms = int((datetime.now() - started_at).total_seconds() * 1000)
|
|
771
|
+
|
|
772
|
+
# Build recommendations from final response
|
|
773
|
+
recommendations = [
|
|
774
|
+
f"Documentation analysis complete for {path}",
|
|
775
|
+
f"Analyzed {scan_results['python_file_count']} Python files",
|
|
776
|
+
f"Found {scan_results['doc_file_count']} documentation files",
|
|
777
|
+
]
|
|
778
|
+
|
|
779
|
+
# Add synthesized recommendations if available
|
|
780
|
+
if len(all_responses) > 2:
|
|
781
|
+
recommendations.append("See synthesizer output for prioritized action plan")
|
|
782
|
+
|
|
783
|
+
# Create result
|
|
784
|
+
result = ManageDocumentationCrewResult(
|
|
785
|
+
success=True,
|
|
786
|
+
findings=all_findings,
|
|
787
|
+
recommendations=recommendations,
|
|
788
|
+
files_analyzed=scan_results["python_file_count"],
|
|
789
|
+
docs_needing_update=0, # Would be parsed from actual LLM response
|
|
790
|
+
new_docs_needed=0, # Would be parsed from actual LLM response
|
|
791
|
+
confidence=0.75 if self._executor else 0.3,
|
|
792
|
+
cost=self._total_cost,
|
|
793
|
+
duration_ms=duration_ms,
|
|
794
|
+
)
|
|
795
|
+
|
|
796
|
+
# Generate formatted report
|
|
797
|
+
result.formatted_report = format_manage_docs_report(result, path)
|
|
798
|
+
|
|
799
|
+
return result
|
|
800
|
+
|
|
801
|
+
|
|
802
|
+
# CLI entry point for testing
|
|
803
|
+
if __name__ == "__main__":
|
|
804
|
+
import sys
|
|
805
|
+
|
|
806
|
+
async def main():
|
|
807
|
+
path = sys.argv[1] if len(sys.argv) > 1 else "."
|
|
808
|
+
print(f"ManageDocumentationCrew - Analyzing: {path}\n")
|
|
809
|
+
|
|
810
|
+
crew = ManageDocumentationCrew()
|
|
811
|
+
print(f"Crew: {crew.name}")
|
|
812
|
+
print(f"Agents: {len(crew.agents)}")
|
|
813
|
+
print(f"LLM Executor: {'Available' if crew._executor else 'Not configured (using mocks)'}")
|
|
814
|
+
print()
|
|
815
|
+
|
|
816
|
+
result = await crew.execute(path=path)
|
|
817
|
+
|
|
818
|
+
# Print formatted report
|
|
819
|
+
print("\n" + result.formatted_report)
|
|
820
|
+
|
|
821
|
+
asyncio.run(main())
|