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,371 @@
|
|
|
1
|
+
"""Command Markdown Parser
|
|
2
|
+
|
|
3
|
+
Parses command markdown files with optional YAML frontmatter.
|
|
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
|
+
import re
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from attune_llm.commands.models import CommandCategory, CommandConfig, CommandMetadata
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
# YAML frontmatter regex pattern (matches --- at start)
|
|
25
|
+
FRONTMATTER_PATTERN = re.compile(
|
|
26
|
+
r"^---\s*\n(.*?)\n---\s*\n",
|
|
27
|
+
re.DOTALL,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# Pattern to extract title from first line (for files without frontmatter)
|
|
31
|
+
TITLE_PATTERN = re.compile(r"^#?\s*(.+?)(?:\s*-\s*(.+))?$")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class CommandParser:
|
|
35
|
+
"""Parser for command markdown files.
|
|
36
|
+
|
|
37
|
+
Supports two formats:
|
|
38
|
+
1. With YAML frontmatter (preferred for new commands)
|
|
39
|
+
2. Plain markdown (legacy format, extracts name from filename/title)
|
|
40
|
+
|
|
41
|
+
Example with frontmatter:
|
|
42
|
+
---
|
|
43
|
+
name: compact
|
|
44
|
+
description: Strategic context compaction
|
|
45
|
+
category: context
|
|
46
|
+
aliases: [comp]
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Overview
|
|
50
|
+
This command preserves collaboration state...
|
|
51
|
+
|
|
52
|
+
Example without frontmatter:
|
|
53
|
+
Create a git commit with a well-formatted message.
|
|
54
|
+
|
|
55
|
+
## Execution Steps
|
|
56
|
+
...
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
# Category inference from command name patterns
|
|
60
|
+
CATEGORY_PATTERNS: dict[str, CommandCategory] = {
|
|
61
|
+
r"commit|pr|review-pr": CommandCategory.GIT,
|
|
62
|
+
r"test|coverage": CommandCategory.TEST,
|
|
63
|
+
r"docs|manage-docs|explain": CommandCategory.DOCS,
|
|
64
|
+
r"security|scan": CommandCategory.SECURITY,
|
|
65
|
+
r"bench|profile|perf": CommandCategory.PERFORMANCE,
|
|
66
|
+
r"pattern|learn|evaluate": CommandCategory.LEARNING,
|
|
67
|
+
r"compact|context|memory": CommandCategory.CONTEXT,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
def __init__(self):
|
|
71
|
+
"""Initialize the parser."""
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
def parse_file(self, file_path: str | Path) -> CommandConfig:
|
|
75
|
+
"""Parse a command markdown file.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
file_path: Path to the command markdown file
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
CommandConfig instance
|
|
82
|
+
|
|
83
|
+
Raises:
|
|
84
|
+
FileNotFoundError: If file doesn't exist
|
|
85
|
+
ValueError: If file format is invalid
|
|
86
|
+
|
|
87
|
+
"""
|
|
88
|
+
file_path = Path(file_path)
|
|
89
|
+
|
|
90
|
+
if not file_path.exists():
|
|
91
|
+
raise FileNotFoundError(f"Command file not found: {file_path}")
|
|
92
|
+
|
|
93
|
+
with open(file_path, encoding="utf-8") as f:
|
|
94
|
+
content = f.read()
|
|
95
|
+
|
|
96
|
+
return self.parse_content(content, source=file_path)
|
|
97
|
+
|
|
98
|
+
def parse_content(
|
|
99
|
+
self,
|
|
100
|
+
content: str,
|
|
101
|
+
source: Path | str | None = None,
|
|
102
|
+
) -> CommandConfig:
|
|
103
|
+
"""Parse command markdown content.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
content: Markdown content (optionally with YAML frontmatter)
|
|
107
|
+
source: Source file path (used for name inference)
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
CommandConfig instance
|
|
111
|
+
|
|
112
|
+
"""
|
|
113
|
+
source_path = Path(source) if source else None
|
|
114
|
+
source_str = str(source) if source else "unknown"
|
|
115
|
+
|
|
116
|
+
# Try to extract frontmatter
|
|
117
|
+
match = FRONTMATTER_PATTERN.match(content)
|
|
118
|
+
|
|
119
|
+
if match:
|
|
120
|
+
# Has frontmatter
|
|
121
|
+
frontmatter_yaml = match.group(1)
|
|
122
|
+
body = content[match.end() :].strip()
|
|
123
|
+
metadata = self._parse_frontmatter(frontmatter_yaml, source_str)
|
|
124
|
+
else:
|
|
125
|
+
# No frontmatter - infer from content
|
|
126
|
+
body = content.strip()
|
|
127
|
+
metadata = self._infer_metadata(body, source_path)
|
|
128
|
+
|
|
129
|
+
# Ensure name is set
|
|
130
|
+
if not metadata.name:
|
|
131
|
+
if source_path:
|
|
132
|
+
metadata.name = source_path.stem
|
|
133
|
+
else:
|
|
134
|
+
raise ValueError(f"Cannot determine command name: {source_str}")
|
|
135
|
+
|
|
136
|
+
# Extract description from first line if not set
|
|
137
|
+
if not metadata.description:
|
|
138
|
+
metadata.description = self._extract_description(body)
|
|
139
|
+
|
|
140
|
+
return CommandConfig(
|
|
141
|
+
name=metadata.name,
|
|
142
|
+
description=metadata.description,
|
|
143
|
+
body=body,
|
|
144
|
+
metadata=metadata,
|
|
145
|
+
source_file=source_path,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
def _parse_frontmatter(
|
|
149
|
+
self,
|
|
150
|
+
yaml_content: str,
|
|
151
|
+
source: str,
|
|
152
|
+
) -> CommandMetadata:
|
|
153
|
+
"""Parse YAML frontmatter into metadata.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
yaml_content: YAML content from frontmatter
|
|
157
|
+
source: Source identifier for errors
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
CommandMetadata instance
|
|
161
|
+
|
|
162
|
+
"""
|
|
163
|
+
try:
|
|
164
|
+
import yaml
|
|
165
|
+
|
|
166
|
+
data = yaml.safe_load(yaml_content) or {}
|
|
167
|
+
except ImportError:
|
|
168
|
+
logger.warning("PyYAML not installed, using basic parsing")
|
|
169
|
+
data = self._basic_yaml_parse(yaml_content)
|
|
170
|
+
except Exception as e:
|
|
171
|
+
raise ValueError(f"Invalid YAML frontmatter in {source}: {e}")
|
|
172
|
+
|
|
173
|
+
return CommandMetadata.from_dict(data)
|
|
174
|
+
|
|
175
|
+
def _basic_yaml_parse(self, yaml_content: str) -> dict[str, Any]:
|
|
176
|
+
"""Basic YAML parsing without PyYAML (for simple key: value pairs).
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
yaml_content: Simple YAML content
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
Parsed dictionary
|
|
183
|
+
|
|
184
|
+
"""
|
|
185
|
+
result: dict[str, Any] = {}
|
|
186
|
+
|
|
187
|
+
for line in yaml_content.strip().split("\n"):
|
|
188
|
+
line = line.strip()
|
|
189
|
+
if not line or line.startswith("#"):
|
|
190
|
+
continue
|
|
191
|
+
|
|
192
|
+
if ":" in line:
|
|
193
|
+
key, value = line.split(":", 1)
|
|
194
|
+
key = key.strip()
|
|
195
|
+
value = value.strip()
|
|
196
|
+
|
|
197
|
+
# Handle simple arrays [a, b, c]
|
|
198
|
+
if value.startswith("[") and value.endswith("]"):
|
|
199
|
+
value = [v.strip().strip("'\"") for v in value[1:-1].split(",") if v.strip()]
|
|
200
|
+
# Handle booleans
|
|
201
|
+
elif value.lower() in ("true", "yes"):
|
|
202
|
+
value = True
|
|
203
|
+
elif value.lower() in ("false", "no"):
|
|
204
|
+
value = False
|
|
205
|
+
# Handle strings
|
|
206
|
+
else:
|
|
207
|
+
value = value.strip("'\"")
|
|
208
|
+
|
|
209
|
+
result[key] = value
|
|
210
|
+
|
|
211
|
+
return result
|
|
212
|
+
|
|
213
|
+
def _infer_metadata(
|
|
214
|
+
self,
|
|
215
|
+
body: str,
|
|
216
|
+
source_path: Path | None,
|
|
217
|
+
) -> CommandMetadata:
|
|
218
|
+
"""Infer metadata from content when no frontmatter present.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
body: Markdown body content
|
|
222
|
+
source_path: Source file path
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
CommandMetadata with inferred values
|
|
226
|
+
|
|
227
|
+
"""
|
|
228
|
+
name = ""
|
|
229
|
+
description = ""
|
|
230
|
+
|
|
231
|
+
# Get name from filename
|
|
232
|
+
if source_path:
|
|
233
|
+
name = source_path.stem
|
|
234
|
+
|
|
235
|
+
# Try to extract description from first line
|
|
236
|
+
first_line = body.split("\n")[0].strip() if body else ""
|
|
237
|
+
if first_line:
|
|
238
|
+
# Remove markdown heading prefix
|
|
239
|
+
if first_line.startswith("#"):
|
|
240
|
+
first_line = first_line.lstrip("#").strip()
|
|
241
|
+
|
|
242
|
+
# Check for "title - description" format
|
|
243
|
+
title_match = TITLE_PATTERN.match(first_line)
|
|
244
|
+
if title_match:
|
|
245
|
+
if title_match.group(2):
|
|
246
|
+
description = title_match.group(2).strip()
|
|
247
|
+
else:
|
|
248
|
+
description = title_match.group(1).strip()
|
|
249
|
+
|
|
250
|
+
# Infer category from name
|
|
251
|
+
category = self._infer_category(name)
|
|
252
|
+
|
|
253
|
+
return CommandMetadata(
|
|
254
|
+
name=name,
|
|
255
|
+
description=description,
|
|
256
|
+
category=category,
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
def _infer_category(self, name: str) -> CommandCategory:
|
|
260
|
+
"""Infer command category from name.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
name: Command name
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
Inferred category
|
|
267
|
+
|
|
268
|
+
"""
|
|
269
|
+
name_lower = name.lower()
|
|
270
|
+
|
|
271
|
+
for pattern, category in self.CATEGORY_PATTERNS.items():
|
|
272
|
+
if re.search(pattern, name_lower):
|
|
273
|
+
return category
|
|
274
|
+
|
|
275
|
+
return CommandCategory.UTILITY
|
|
276
|
+
|
|
277
|
+
def _extract_description(self, body: str) -> str:
|
|
278
|
+
"""Extract description from body content.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
body: Markdown body
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
Extracted description
|
|
285
|
+
|
|
286
|
+
"""
|
|
287
|
+
if not body:
|
|
288
|
+
return ""
|
|
289
|
+
|
|
290
|
+
lines = body.strip().split("\n")
|
|
291
|
+
first_line = lines[0].strip()
|
|
292
|
+
|
|
293
|
+
# Remove markdown heading prefix
|
|
294
|
+
if first_line.startswith("#"):
|
|
295
|
+
first_line = first_line.lstrip("#").strip()
|
|
296
|
+
|
|
297
|
+
# Check for "title - description" format
|
|
298
|
+
if " - " in first_line:
|
|
299
|
+
return first_line.split(" - ", 1)[1].strip()
|
|
300
|
+
|
|
301
|
+
# Use first line if it looks like a description
|
|
302
|
+
if first_line and not first_line.startswith("```"):
|
|
303
|
+
return first_line[:200] # Limit length
|
|
304
|
+
|
|
305
|
+
return ""
|
|
306
|
+
|
|
307
|
+
def validate_file(self, file_path: str | Path) -> list[str]:
|
|
308
|
+
"""Validate a command file without fully parsing.
|
|
309
|
+
|
|
310
|
+
Args:
|
|
311
|
+
file_path: Path to validate
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
List of validation error messages (empty if valid)
|
|
315
|
+
|
|
316
|
+
"""
|
|
317
|
+
errors: list[str] = []
|
|
318
|
+
file_path = Path(file_path)
|
|
319
|
+
|
|
320
|
+
if not file_path.exists():
|
|
321
|
+
return [f"File not found: {file_path}"]
|
|
322
|
+
|
|
323
|
+
try:
|
|
324
|
+
with open(file_path, encoding="utf-8") as f:
|
|
325
|
+
content = f.read()
|
|
326
|
+
except OSError as e:
|
|
327
|
+
return [f"Cannot read file: {e}"]
|
|
328
|
+
|
|
329
|
+
# Check if file has content
|
|
330
|
+
if not content.strip():
|
|
331
|
+
errors.append("File is empty")
|
|
332
|
+
return errors
|
|
333
|
+
|
|
334
|
+
# Check frontmatter if present
|
|
335
|
+
match = FRONTMATTER_PATTERN.match(content)
|
|
336
|
+
if match:
|
|
337
|
+
try:
|
|
338
|
+
import yaml
|
|
339
|
+
|
|
340
|
+
frontmatter = yaml.safe_load(match.group(1)) or {}
|
|
341
|
+
|
|
342
|
+
# Validate name if provided
|
|
343
|
+
name = frontmatter.get("name", "")
|
|
344
|
+
if name and not re.match(r"^[a-z0-9][-a-z0-9]*$", name):
|
|
345
|
+
errors.append(
|
|
346
|
+
f"Invalid command name '{name}'. "
|
|
347
|
+
"Use lowercase letters, numbers, and hyphens."
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
# Validate category if provided
|
|
351
|
+
category = frontmatter.get("category", "")
|
|
352
|
+
if category:
|
|
353
|
+
try:
|
|
354
|
+
CommandCategory(category)
|
|
355
|
+
except ValueError:
|
|
356
|
+
valid = ", ".join(c.value for c in CommandCategory)
|
|
357
|
+
errors.append(f"Invalid category '{category}'. Valid: {valid}")
|
|
358
|
+
|
|
359
|
+
except ImportError:
|
|
360
|
+
pass # Skip YAML validation if not installed
|
|
361
|
+
except Exception as e:
|
|
362
|
+
errors.append(f"Invalid YAML frontmatter: {e}")
|
|
363
|
+
|
|
364
|
+
# Check body has content
|
|
365
|
+
body_start = match.end() if match else 0
|
|
366
|
+
body = content[body_start:].strip()
|
|
367
|
+
|
|
368
|
+
if not body:
|
|
369
|
+
errors.append("Command has no body content")
|
|
370
|
+
|
|
371
|
+
return errors
|