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,301 @@
|
|
|
1
|
+
"""Command Loader
|
|
2
|
+
|
|
3
|
+
Loads commands from directory structures.
|
|
4
|
+
|
|
5
|
+
Architectural patterns inspired by everything-claude-code by Affaan Mustafa.
|
|
6
|
+
See: https://github.com/affaan-m/everything-claude-code (MIT License)
|
|
7
|
+
See: ACKNOWLEDGMENTS.md for full attribution.
|
|
8
|
+
|
|
9
|
+
Copyright 2025 Smart AI Memory, LLC
|
|
10
|
+
Licensed under Fair Source 0.9
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import logging
|
|
16
|
+
from collections.abc import Iterator
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from attune_llm.commands.models import CommandConfig
|
|
20
|
+
from attune_llm.commands.parser import CommandParser
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
# Default commands directory relative to project root
|
|
25
|
+
DEFAULT_COMMANDS_DIR = ".claude/commands"
|
|
26
|
+
|
|
27
|
+
# Files to skip when scanning
|
|
28
|
+
SKIP_FILES = frozenset(
|
|
29
|
+
{
|
|
30
|
+
"README.md",
|
|
31
|
+
"readme.md",
|
|
32
|
+
"CHANGELOG.md",
|
|
33
|
+
"changelog.md",
|
|
34
|
+
"INDEX.md",
|
|
35
|
+
"index.md",
|
|
36
|
+
}
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class CommandLoader:
|
|
41
|
+
"""Loader for discovering and loading command markdown files.
|
|
42
|
+
|
|
43
|
+
Scans directories for .md files with command definitions and loads
|
|
44
|
+
them into CommandConfig instances.
|
|
45
|
+
|
|
46
|
+
Example:
|
|
47
|
+
loader = CommandLoader()
|
|
48
|
+
|
|
49
|
+
# Load a single command
|
|
50
|
+
config = loader.load(".claude/commands/commit.md")
|
|
51
|
+
|
|
52
|
+
# Load all commands from a directory
|
|
53
|
+
commands = loader.load_directory(".claude/commands/")
|
|
54
|
+
|
|
55
|
+
# Discover and iterate commands lazily
|
|
56
|
+
for config in loader.discover(".claude/commands/"):
|
|
57
|
+
print(config.name)
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def __init__(self, parser: CommandParser | None = None):
|
|
61
|
+
"""Initialize the loader.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
parser: Optional custom parser instance
|
|
65
|
+
|
|
66
|
+
"""
|
|
67
|
+
self.parser = parser or CommandParser()
|
|
68
|
+
|
|
69
|
+
def load(self, file_path: str | Path) -> CommandConfig:
|
|
70
|
+
"""Load a single command file.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
file_path: Path to the command markdown file
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
CommandConfig instance
|
|
77
|
+
|
|
78
|
+
"""
|
|
79
|
+
return self.parser.parse_file(file_path)
|
|
80
|
+
|
|
81
|
+
def load_directory(
|
|
82
|
+
self,
|
|
83
|
+
directory: str | Path,
|
|
84
|
+
recursive: bool = False,
|
|
85
|
+
) -> dict[str, CommandConfig]:
|
|
86
|
+
"""Load all commands from a directory.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
directory: Directory to scan for .md files
|
|
90
|
+
recursive: If True, scan subdirectories
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Dictionary mapping command names to configs
|
|
94
|
+
|
|
95
|
+
"""
|
|
96
|
+
commands: dict[str, CommandConfig] = {}
|
|
97
|
+
|
|
98
|
+
for config in self.discover(directory, recursive=recursive):
|
|
99
|
+
if config.name in commands:
|
|
100
|
+
logger.warning(
|
|
101
|
+
"Duplicate command name '%s' - keeping first occurrence",
|
|
102
|
+
config.name,
|
|
103
|
+
)
|
|
104
|
+
continue
|
|
105
|
+
commands[config.name] = config
|
|
106
|
+
|
|
107
|
+
logger.info("Loaded %d command(s) from %s", len(commands), directory)
|
|
108
|
+
return commands
|
|
109
|
+
|
|
110
|
+
def discover(
|
|
111
|
+
self,
|
|
112
|
+
directory: str | Path,
|
|
113
|
+
recursive: bool = False,
|
|
114
|
+
) -> Iterator[CommandConfig]:
|
|
115
|
+
"""Discover and yield commands from a directory.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
directory: Directory to scan
|
|
119
|
+
recursive: If True, scan subdirectories
|
|
120
|
+
|
|
121
|
+
Yields:
|
|
122
|
+
CommandConfig instances
|
|
123
|
+
|
|
124
|
+
"""
|
|
125
|
+
directory = Path(directory)
|
|
126
|
+
|
|
127
|
+
if not directory.exists():
|
|
128
|
+
logger.warning("Commands directory not found: %s", directory)
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
if not directory.is_dir():
|
|
132
|
+
raise ValueError(f"Not a directory: {directory}")
|
|
133
|
+
|
|
134
|
+
# Get pattern for globbing
|
|
135
|
+
pattern = "**/*.md" if recursive else "*.md"
|
|
136
|
+
|
|
137
|
+
for file_path in sorted(directory.glob(pattern)):
|
|
138
|
+
if not file_path.is_file():
|
|
139
|
+
continue
|
|
140
|
+
|
|
141
|
+
# Skip non-command files
|
|
142
|
+
if file_path.name in SKIP_FILES:
|
|
143
|
+
continue
|
|
144
|
+
if file_path.name.startswith("_"):
|
|
145
|
+
continue
|
|
146
|
+
if file_path.name.startswith("."):
|
|
147
|
+
continue
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
config = self.parser.parse_file(file_path)
|
|
151
|
+
yield config
|
|
152
|
+
except ValueError as e:
|
|
153
|
+
logger.warning("Skipping invalid command file %s: %s", file_path, e)
|
|
154
|
+
except FileNotFoundError as e:
|
|
155
|
+
logger.warning("Command file not found %s: %s", file_path, e)
|
|
156
|
+
except OSError as e:
|
|
157
|
+
logger.error("Error loading command file %s: %s", file_path, e)
|
|
158
|
+
|
|
159
|
+
def validate_directory(
|
|
160
|
+
self,
|
|
161
|
+
directory: str | Path,
|
|
162
|
+
recursive: bool = False,
|
|
163
|
+
) -> dict[str, list[str]]:
|
|
164
|
+
"""Validate all command files in a directory.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
directory: Directory to validate
|
|
168
|
+
recursive: If True, scan subdirectories
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
Dictionary mapping file paths to lists of errors
|
|
172
|
+
|
|
173
|
+
"""
|
|
174
|
+
directory = Path(directory)
|
|
175
|
+
results: dict[str, list[str]] = {}
|
|
176
|
+
|
|
177
|
+
if not directory.exists():
|
|
178
|
+
return {str(directory): ["Directory not found"]}
|
|
179
|
+
|
|
180
|
+
pattern = "**/*.md" if recursive else "*.md"
|
|
181
|
+
|
|
182
|
+
for file_path in sorted(directory.glob(pattern)):
|
|
183
|
+
if not file_path.is_file():
|
|
184
|
+
continue
|
|
185
|
+
if file_path.name in SKIP_FILES:
|
|
186
|
+
continue
|
|
187
|
+
if file_path.name.startswith("_"):
|
|
188
|
+
continue
|
|
189
|
+
|
|
190
|
+
errors = self.parser.validate_file(file_path)
|
|
191
|
+
if errors:
|
|
192
|
+
results[str(file_path)] = errors
|
|
193
|
+
|
|
194
|
+
return results
|
|
195
|
+
|
|
196
|
+
def get_command_names(
|
|
197
|
+
self,
|
|
198
|
+
directory: str | Path,
|
|
199
|
+
recursive: bool = False,
|
|
200
|
+
) -> list[str]:
|
|
201
|
+
"""Get list of command names in a directory without fully loading.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
directory: Directory to scan
|
|
205
|
+
recursive: If True, scan subdirectories
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
List of command names
|
|
209
|
+
|
|
210
|
+
"""
|
|
211
|
+
names: list[str] = []
|
|
212
|
+
for config in self.discover(directory, recursive=recursive):
|
|
213
|
+
names.append(config.name)
|
|
214
|
+
return names
|
|
215
|
+
|
|
216
|
+
def find_command_file(
|
|
217
|
+
self,
|
|
218
|
+
name: str,
|
|
219
|
+
directory: str | Path,
|
|
220
|
+
) -> Path | None:
|
|
221
|
+
"""Find a command file by name.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
name: Command name to find
|
|
225
|
+
directory: Directory to search
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
Path to command file or None
|
|
229
|
+
|
|
230
|
+
"""
|
|
231
|
+
directory = Path(directory)
|
|
232
|
+
|
|
233
|
+
# Try exact match first
|
|
234
|
+
exact_path = directory / f"{name}.md"
|
|
235
|
+
if exact_path.exists():
|
|
236
|
+
return exact_path
|
|
237
|
+
|
|
238
|
+
# Search through files
|
|
239
|
+
for file_path in directory.glob("*.md"):
|
|
240
|
+
if file_path.stem == name:
|
|
241
|
+
return file_path
|
|
242
|
+
|
|
243
|
+
return None
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def load_commands_from_paths(
|
|
247
|
+
paths: list[str | Path],
|
|
248
|
+
parser: CommandParser | None = None,
|
|
249
|
+
) -> dict[str, CommandConfig]:
|
|
250
|
+
"""Load commands from multiple paths (files or directories).
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
paths: List of file or directory paths
|
|
254
|
+
parser: Optional custom parser
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
Dictionary mapping command names to configs
|
|
258
|
+
|
|
259
|
+
"""
|
|
260
|
+
loader = CommandLoader(parser=parser)
|
|
261
|
+
commands: dict[str, CommandConfig] = {}
|
|
262
|
+
|
|
263
|
+
for path in paths:
|
|
264
|
+
path = Path(path)
|
|
265
|
+
|
|
266
|
+
if path.is_file():
|
|
267
|
+
config = loader.load(path)
|
|
268
|
+
commands[config.name] = config
|
|
269
|
+
elif path.is_dir():
|
|
270
|
+
dir_commands = loader.load_directory(path)
|
|
271
|
+
commands.update(dir_commands)
|
|
272
|
+
else:
|
|
273
|
+
logger.warning("Path not found: %s", path)
|
|
274
|
+
|
|
275
|
+
return commands
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def get_default_commands_directory() -> Path:
|
|
279
|
+
"""Get the default commands directory.
|
|
280
|
+
|
|
281
|
+
Searches for .claude/commands/ starting from current directory
|
|
282
|
+
and walking up to find project root.
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
Path to commands directory
|
|
286
|
+
|
|
287
|
+
"""
|
|
288
|
+
current = Path.cwd()
|
|
289
|
+
|
|
290
|
+
# Walk up looking for .claude/commands/
|
|
291
|
+
for parent in [current, *current.parents]:
|
|
292
|
+
commands_dir = parent / DEFAULT_COMMANDS_DIR
|
|
293
|
+
if commands_dir.exists():
|
|
294
|
+
return commands_dir
|
|
295
|
+
|
|
296
|
+
# Also check for .claude directory as project root indicator
|
|
297
|
+
if (parent / ".claude").exists():
|
|
298
|
+
return commands_dir
|
|
299
|
+
|
|
300
|
+
# Fall back to current directory
|
|
301
|
+
return current / DEFAULT_COMMANDS_DIR
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"""Command Configuration Models
|
|
2
|
+
|
|
3
|
+
Data models for command definitions loaded from markdown files.
|
|
4
|
+
|
|
5
|
+
Architectural patterns inspired by everything-claude-code by Affaan Mustafa.
|
|
6
|
+
See: https://github.com/affaan-m/everything-claude-code (MIT License)
|
|
7
|
+
See: ACKNOWLEDGMENTS.md for full attribution.
|
|
8
|
+
|
|
9
|
+
Copyright 2025 Smart AI Memory, LLC
|
|
10
|
+
Licensed under Fair Source 0.9
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from dataclasses import dataclass, field
|
|
16
|
+
from datetime import datetime
|
|
17
|
+
from enum import Enum
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class CommandCategory(str, Enum):
|
|
23
|
+
"""Categories for commands."""
|
|
24
|
+
|
|
25
|
+
WORKFLOW = "workflow" # Multi-step workflows
|
|
26
|
+
GIT = "git" # Git operations
|
|
27
|
+
TEST = "test" # Testing related
|
|
28
|
+
DOCS = "docs" # Documentation
|
|
29
|
+
SECURITY = "security" # Security analysis
|
|
30
|
+
PERFORMANCE = "performance" # Performance tools
|
|
31
|
+
LEARNING = "learning" # Pattern learning
|
|
32
|
+
CONTEXT = "context" # Context management
|
|
33
|
+
UTILITY = "utility" # General utilities
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class CommandMetadata:
|
|
38
|
+
"""Metadata for a command extracted from YAML frontmatter.
|
|
39
|
+
|
|
40
|
+
Example frontmatter:
|
|
41
|
+
---
|
|
42
|
+
name: compact
|
|
43
|
+
description: Strategic context compaction
|
|
44
|
+
category: context
|
|
45
|
+
aliases: [comp, save-state]
|
|
46
|
+
hooks:
|
|
47
|
+
pre: PreCompact
|
|
48
|
+
post: PostCompact
|
|
49
|
+
requires_user_id: true
|
|
50
|
+
---
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
name: str
|
|
54
|
+
description: str = ""
|
|
55
|
+
category: CommandCategory = CommandCategory.UTILITY
|
|
56
|
+
aliases: list[str] = field(default_factory=list)
|
|
57
|
+
hooks: dict[str, str] = field(default_factory=dict)
|
|
58
|
+
requires_user_id: bool = False
|
|
59
|
+
requires_context: bool = False
|
|
60
|
+
tags: list[str] = field(default_factory=list)
|
|
61
|
+
author: str = ""
|
|
62
|
+
version: str = "1.0"
|
|
63
|
+
|
|
64
|
+
def to_dict(self) -> dict[str, Any]:
|
|
65
|
+
"""Convert to dictionary."""
|
|
66
|
+
return {
|
|
67
|
+
"name": self.name,
|
|
68
|
+
"description": self.description,
|
|
69
|
+
"category": self.category.value,
|
|
70
|
+
"aliases": self.aliases,
|
|
71
|
+
"hooks": self.hooks,
|
|
72
|
+
"requires_user_id": self.requires_user_id,
|
|
73
|
+
"requires_context": self.requires_context,
|
|
74
|
+
"tags": self.tags,
|
|
75
|
+
"author": self.author,
|
|
76
|
+
"version": self.version,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@classmethod
|
|
80
|
+
def from_dict(cls, data: dict[str, Any]) -> CommandMetadata:
|
|
81
|
+
"""Create from dictionary."""
|
|
82
|
+
category_str = data.get("category", "utility")
|
|
83
|
+
try:
|
|
84
|
+
category = CommandCategory(category_str)
|
|
85
|
+
except ValueError:
|
|
86
|
+
category = CommandCategory.UTILITY
|
|
87
|
+
|
|
88
|
+
return cls(
|
|
89
|
+
name=data.get("name", ""),
|
|
90
|
+
description=data.get("description", ""),
|
|
91
|
+
category=category,
|
|
92
|
+
aliases=data.get("aliases", []),
|
|
93
|
+
hooks=data.get("hooks", {}),
|
|
94
|
+
requires_user_id=data.get("requires_user_id", False),
|
|
95
|
+
requires_context=data.get("requires_context", False),
|
|
96
|
+
tags=data.get("tags", []),
|
|
97
|
+
author=data.get("author", ""),
|
|
98
|
+
version=data.get("version", "1.0"),
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass
|
|
103
|
+
class CommandConfig:
|
|
104
|
+
"""Complete configuration for a command.
|
|
105
|
+
|
|
106
|
+
Combines metadata from YAML frontmatter with the markdown body
|
|
107
|
+
that contains the command instructions.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
name: str
|
|
111
|
+
description: str
|
|
112
|
+
body: str # Markdown content with instructions
|
|
113
|
+
metadata: CommandMetadata
|
|
114
|
+
source_file: Path | None = None
|
|
115
|
+
loaded_at: datetime = field(default_factory=datetime.now)
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def aliases(self) -> list[str]:
|
|
119
|
+
"""Get command aliases."""
|
|
120
|
+
return self.metadata.aliases
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def category(self) -> CommandCategory:
|
|
124
|
+
"""Get command category."""
|
|
125
|
+
return self.metadata.category
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def hooks(self) -> dict[str, str]:
|
|
129
|
+
"""Get hook configuration."""
|
|
130
|
+
return self.metadata.hooks
|
|
131
|
+
|
|
132
|
+
def get_all_names(self) -> list[str]:
|
|
133
|
+
"""Get command name and all aliases."""
|
|
134
|
+
return [self.name] + self.aliases
|
|
135
|
+
|
|
136
|
+
def to_dict(self) -> dict[str, Any]:
|
|
137
|
+
"""Convert to dictionary."""
|
|
138
|
+
return {
|
|
139
|
+
"name": self.name,
|
|
140
|
+
"description": self.description,
|
|
141
|
+
"body": self.body,
|
|
142
|
+
"metadata": self.metadata.to_dict(),
|
|
143
|
+
"source_file": str(self.source_file) if self.source_file else None,
|
|
144
|
+
"loaded_at": self.loaded_at.isoformat(),
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
@classmethod
|
|
148
|
+
def from_dict(cls, data: dict[str, Any]) -> CommandConfig:
|
|
149
|
+
"""Create from dictionary."""
|
|
150
|
+
return cls(
|
|
151
|
+
name=data["name"],
|
|
152
|
+
description=data.get("description", ""),
|
|
153
|
+
body=data.get("body", ""),
|
|
154
|
+
metadata=CommandMetadata.from_dict(data.get("metadata", {})),
|
|
155
|
+
source_file=Path(data["source_file"]) if data.get("source_file") else None,
|
|
156
|
+
loaded_at=datetime.fromisoformat(data["loaded_at"])
|
|
157
|
+
if "loaded_at" in data
|
|
158
|
+
else datetime.now(),
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
def format_for_display(self) -> str:
|
|
162
|
+
"""Format command for display in help."""
|
|
163
|
+
aliases_str = ""
|
|
164
|
+
if self.aliases:
|
|
165
|
+
aliases_str = f" (aliases: {', '.join(self.aliases)})"
|
|
166
|
+
|
|
167
|
+
return f"/{self.name}{aliases_str} - {self.description}"
|
|
168
|
+
|
|
169
|
+
def format_full_help(self) -> str:
|
|
170
|
+
"""Format full help including body."""
|
|
171
|
+
lines = [
|
|
172
|
+
f"# /{self.name}",
|
|
173
|
+
"",
|
|
174
|
+
self.description,
|
|
175
|
+
"",
|
|
176
|
+
]
|
|
177
|
+
|
|
178
|
+
if self.aliases:
|
|
179
|
+
lines.extend(
|
|
180
|
+
[
|
|
181
|
+
"## Aliases",
|
|
182
|
+
", ".join(f"/{a}" for a in self.aliases),
|
|
183
|
+
"",
|
|
184
|
+
]
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
if self.metadata.tags:
|
|
188
|
+
lines.extend(
|
|
189
|
+
[
|
|
190
|
+
"## Tags",
|
|
191
|
+
", ".join(self.metadata.tags),
|
|
192
|
+
"",
|
|
193
|
+
]
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
lines.extend(
|
|
197
|
+
[
|
|
198
|
+
"## Instructions",
|
|
199
|
+
"",
|
|
200
|
+
self.body,
|
|
201
|
+
]
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
return "\n".join(lines)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@dataclass
|
|
208
|
+
class CommandResult:
|
|
209
|
+
"""Result from executing a command."""
|
|
210
|
+
|
|
211
|
+
command_name: str
|
|
212
|
+
success: bool
|
|
213
|
+
output: str = ""
|
|
214
|
+
error: str | None = None
|
|
215
|
+
duration_ms: float = 0.0
|
|
216
|
+
hooks_fired: list[str] = field(default_factory=list)
|
|
217
|
+
context_saved: bool = False
|
|
218
|
+
patterns_applied: list[str] = field(default_factory=list)
|
|
219
|
+
|
|
220
|
+
def to_dict(self) -> dict[str, Any]:
|
|
221
|
+
"""Convert to dictionary."""
|
|
222
|
+
return {
|
|
223
|
+
"command_name": self.command_name,
|
|
224
|
+
"success": self.success,
|
|
225
|
+
"output": self.output,
|
|
226
|
+
"error": self.error,
|
|
227
|
+
"duration_ms": self.duration_ms,
|
|
228
|
+
"hooks_fired": self.hooks_fired,
|
|
229
|
+
"context_saved": self.context_saved,
|
|
230
|
+
"patterns_applied": self.patterns_applied,
|
|
231
|
+
}
|