tapps-agents 3.6.0__py3-none-any.whl → 3.6.1__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.
- tapps_agents/__init__.py +2 -2
- tapps_agents/agents/__init__.py +22 -22
- tapps_agents/agents/analyst/__init__.py +5 -5
- tapps_agents/agents/architect/__init__.py +5 -5
- tapps_agents/agents/architect/agent.py +1033 -1033
- tapps_agents/agents/architect/pattern_detector.py +75 -75
- tapps_agents/agents/cleanup/__init__.py +7 -7
- tapps_agents/agents/cleanup/agent.py +445 -445
- tapps_agents/agents/debugger/__init__.py +7 -7
- tapps_agents/agents/debugger/agent.py +310 -310
- tapps_agents/agents/debugger/error_analyzer.py +437 -437
- tapps_agents/agents/designer/__init__.py +5 -5
- tapps_agents/agents/designer/agent.py +786 -786
- tapps_agents/agents/designer/visual_designer.py +638 -638
- tapps_agents/agents/documenter/__init__.py +7 -7
- tapps_agents/agents/documenter/agent.py +531 -531
- tapps_agents/agents/documenter/doc_generator.py +472 -472
- tapps_agents/agents/documenter/doc_validator.py +393 -393
- tapps_agents/agents/documenter/framework_doc_updater.py +493 -493
- tapps_agents/agents/enhancer/__init__.py +7 -7
- tapps_agents/agents/evaluator/__init__.py +7 -7
- tapps_agents/agents/evaluator/agent.py +443 -443
- tapps_agents/agents/evaluator/priority_evaluator.py +641 -641
- tapps_agents/agents/evaluator/quality_analyzer.py +147 -147
- tapps_agents/agents/evaluator/report_generator.py +344 -344
- tapps_agents/agents/evaluator/usage_analyzer.py +192 -192
- tapps_agents/agents/evaluator/workflow_analyzer.py +189 -189
- tapps_agents/agents/implementer/__init__.py +7 -7
- tapps_agents/agents/implementer/agent.py +798 -798
- tapps_agents/agents/implementer/auto_fix.py +1119 -1119
- tapps_agents/agents/implementer/code_generator.py +73 -73
- tapps_agents/agents/improver/__init__.py +1 -1
- tapps_agents/agents/improver/agent.py +753 -753
- tapps_agents/agents/ops/__init__.py +1 -1
- tapps_agents/agents/ops/agent.py +619 -619
- tapps_agents/agents/ops/dependency_analyzer.py +600 -600
- tapps_agents/agents/orchestrator/__init__.py +5 -5
- tapps_agents/agents/orchestrator/agent.py +522 -522
- tapps_agents/agents/planner/__init__.py +7 -7
- tapps_agents/agents/planner/agent.py +1127 -1127
- tapps_agents/agents/reviewer/__init__.py +24 -24
- tapps_agents/agents/reviewer/agent.py +3513 -3513
- tapps_agents/agents/reviewer/aggregator.py +213 -213
- tapps_agents/agents/reviewer/batch_review.py +448 -448
- tapps_agents/agents/reviewer/cache.py +443 -443
- tapps_agents/agents/reviewer/context7_enhancer.py +630 -630
- tapps_agents/agents/reviewer/context_detector.py +203 -203
- tapps_agents/agents/reviewer/docker_compose_validator.py +158 -158
- tapps_agents/agents/reviewer/dockerfile_validator.py +176 -176
- tapps_agents/agents/reviewer/error_handling.py +126 -126
- tapps_agents/agents/reviewer/feedback_generator.py +490 -490
- tapps_agents/agents/reviewer/influxdb_validator.py +316 -316
- tapps_agents/agents/reviewer/issue_tracking.py +169 -169
- tapps_agents/agents/reviewer/library_detector.py +295 -295
- tapps_agents/agents/reviewer/library_patterns.py +268 -268
- tapps_agents/agents/reviewer/maintainability_scorer.py +593 -593
- tapps_agents/agents/reviewer/metric_strategies.py +276 -276
- tapps_agents/agents/reviewer/mqtt_validator.py +160 -160
- tapps_agents/agents/reviewer/output_enhancer.py +105 -105
- tapps_agents/agents/reviewer/pattern_detector.py +241 -241
- tapps_agents/agents/reviewer/performance_scorer.py +357 -357
- tapps_agents/agents/reviewer/phased_review.py +516 -516
- tapps_agents/agents/reviewer/progressive_review.py +435 -435
- tapps_agents/agents/reviewer/react_scorer.py +331 -331
- tapps_agents/agents/reviewer/score_constants.py +228 -228
- tapps_agents/agents/reviewer/score_validator.py +507 -507
- tapps_agents/agents/reviewer/scorer_registry.py +373 -373
- tapps_agents/agents/reviewer/service_discovery.py +534 -534
- tapps_agents/agents/reviewer/tools/parallel_executor.py +581 -581
- tapps_agents/agents/reviewer/tools/ruff_grouping.py +250 -250
- tapps_agents/agents/reviewer/tools/scoped_mypy.py +284 -284
- tapps_agents/agents/reviewer/typescript_scorer.py +1142 -1142
- tapps_agents/agents/reviewer/validation.py +208 -208
- tapps_agents/agents/reviewer/websocket_validator.py +132 -132
- tapps_agents/agents/tester/__init__.py +7 -7
- tapps_agents/agents/tester/accessibility_auditor.py +309 -309
- tapps_agents/agents/tester/agent.py +1080 -1080
- tapps_agents/agents/tester/batch_generator.py +54 -54
- tapps_agents/agents/tester/context_learner.py +51 -51
- tapps_agents/agents/tester/coverage_analyzer.py +386 -386
- tapps_agents/agents/tester/coverage_test_generator.py +290 -290
- tapps_agents/agents/tester/debug_enhancer.py +238 -238
- tapps_agents/agents/tester/device_emulator.py +241 -241
- tapps_agents/agents/tester/integration_generator.py +62 -62
- tapps_agents/agents/tester/network_recorder.py +300 -300
- tapps_agents/agents/tester/performance_monitor.py +320 -320
- tapps_agents/agents/tester/test_fixer.py +316 -316
- tapps_agents/agents/tester/test_generator.py +632 -632
- tapps_agents/agents/tester/trace_manager.py +234 -234
- tapps_agents/agents/tester/visual_regression.py +291 -291
- tapps_agents/analysis/pattern_detector.py +36 -36
- tapps_agents/beads/hydration.py +213 -213
- tapps_agents/beads/parse.py +32 -32
- tapps_agents/beads/specs.py +206 -206
- tapps_agents/cli/__init__.py +9 -9
- tapps_agents/cli/__main__.py +8 -8
- tapps_agents/cli/base.py +478 -478
- tapps_agents/cli/command_classifier.py +72 -72
- tapps_agents/cli/commands/__init__.py +2 -2
- tapps_agents/cli/commands/analyst.py +173 -173
- tapps_agents/cli/commands/architect.py +109 -109
- tapps_agents/cli/commands/cleanup_agent.py +92 -92
- tapps_agents/cli/commands/common.py +126 -126
- tapps_agents/cli/commands/debugger.py +90 -90
- tapps_agents/cli/commands/designer.py +112 -112
- tapps_agents/cli/commands/documenter.py +136 -136
- tapps_agents/cli/commands/enhancer.py +110 -110
- tapps_agents/cli/commands/evaluator.py +255 -255
- tapps_agents/cli/commands/implementer.py +301 -301
- tapps_agents/cli/commands/improver.py +91 -91
- tapps_agents/cli/commands/knowledge.py +111 -111
- tapps_agents/cli/commands/learning.py +172 -172
- tapps_agents/cli/commands/observability.py +283 -283
- tapps_agents/cli/commands/ops.py +135 -135
- tapps_agents/cli/commands/orchestrator.py +116 -116
- tapps_agents/cli/commands/planner.py +237 -237
- tapps_agents/cli/commands/reviewer.py +1872 -1872
- tapps_agents/cli/commands/status.py +285 -285
- tapps_agents/cli/commands/task.py +227 -227
- tapps_agents/cli/commands/tester.py +191 -191
- tapps_agents/cli/feedback.py +936 -936
- tapps_agents/cli/formatters.py +608 -608
- tapps_agents/cli/help/__init__.py +7 -7
- tapps_agents/cli/help/static_help.py +425 -425
- tapps_agents/cli/network_detection.py +110 -110
- tapps_agents/cli/output_compactor.py +274 -274
- tapps_agents/cli/parsers/__init__.py +2 -2
- tapps_agents/cli/parsers/analyst.py +186 -186
- tapps_agents/cli/parsers/architect.py +167 -167
- tapps_agents/cli/parsers/cleanup_agent.py +228 -228
- tapps_agents/cli/parsers/debugger.py +116 -116
- tapps_agents/cli/parsers/designer.py +182 -182
- tapps_agents/cli/parsers/documenter.py +134 -134
- tapps_agents/cli/parsers/enhancer.py +113 -113
- tapps_agents/cli/parsers/evaluator.py +213 -213
- tapps_agents/cli/parsers/implementer.py +168 -168
- tapps_agents/cli/parsers/improver.py +132 -132
- tapps_agents/cli/parsers/ops.py +159 -159
- tapps_agents/cli/parsers/orchestrator.py +98 -98
- tapps_agents/cli/parsers/planner.py +145 -145
- tapps_agents/cli/parsers/reviewer.py +462 -462
- tapps_agents/cli/parsers/tester.py +124 -124
- tapps_agents/cli/progress_heartbeat.py +254 -254
- tapps_agents/cli/streaming_progress.py +336 -336
- tapps_agents/cli/utils/__init__.py +6 -6
- tapps_agents/cli/utils/agent_lifecycle.py +48 -48
- tapps_agents/cli/utils/error_formatter.py +82 -82
- tapps_agents/cli/utils/error_recovery.py +188 -188
- tapps_agents/cli/utils/output_handler.py +59 -59
- tapps_agents/cli/utils/prompt_enhancer.py +319 -319
- tapps_agents/cli/validators/__init__.py +9 -9
- tapps_agents/cli/validators/command_validator.py +81 -81
- tapps_agents/context7/__init__.py +112 -112
- tapps_agents/context7/agent_integration.py +869 -869
- tapps_agents/context7/analytics.py +382 -382
- tapps_agents/context7/analytics_dashboard.py +299 -299
- tapps_agents/context7/async_cache.py +681 -681
- tapps_agents/context7/backup_client.py +958 -958
- tapps_agents/context7/cache_locking.py +194 -194
- tapps_agents/context7/cache_metadata.py +214 -214
- tapps_agents/context7/cache_prewarm.py +488 -488
- tapps_agents/context7/cache_structure.py +168 -168
- tapps_agents/context7/cache_warming.py +604 -604
- tapps_agents/context7/circuit_breaker.py +376 -376
- tapps_agents/context7/cleanup.py +461 -461
- tapps_agents/context7/commands.py +858 -858
- tapps_agents/context7/credential_validation.py +276 -276
- tapps_agents/context7/cross_reference_resolver.py +168 -168
- tapps_agents/context7/cross_references.py +424 -424
- tapps_agents/context7/doc_manager.py +225 -225
- tapps_agents/context7/fuzzy_matcher.py +369 -369
- tapps_agents/context7/kb_cache.py +404 -404
- tapps_agents/context7/language_detector.py +219 -219
- tapps_agents/context7/library_detector.py +725 -725
- tapps_agents/context7/lookup.py +738 -738
- tapps_agents/context7/metadata.py +258 -258
- tapps_agents/context7/refresh_queue.py +300 -300
- tapps_agents/context7/security.py +373 -373
- tapps_agents/context7/staleness_policies.py +278 -278
- tapps_agents/context7/tiles_integration.py +47 -47
- tapps_agents/continuous_bug_fix/__init__.py +20 -20
- tapps_agents/continuous_bug_fix/bug_finder.py +306 -306
- tapps_agents/continuous_bug_fix/bug_fix_coordinator.py +177 -177
- tapps_agents/continuous_bug_fix/commit_manager.py +178 -178
- tapps_agents/continuous_bug_fix/continuous_bug_fixer.py +322 -322
- tapps_agents/continuous_bug_fix/proactive_bug_finder.py +285 -285
- tapps_agents/core/__init__.py +298 -298
- tapps_agents/core/adaptive_cache_config.py +432 -432
- tapps_agents/core/agent_base.py +647 -647
- tapps_agents/core/agent_cache.py +466 -466
- tapps_agents/core/agent_learning.py +1865 -1865
- tapps_agents/core/analytics_dashboard.py +563 -563
- tapps_agents/core/analytics_enhancements.py +597 -597
- tapps_agents/core/anonymization.py +274 -274
- tapps_agents/core/ast_parser.py +228 -228
- tapps_agents/core/async_file_ops.py +402 -402
- tapps_agents/core/best_practice_consultant.py +299 -299
- tapps_agents/core/brownfield_analyzer.py +299 -299
- tapps_agents/core/brownfield_review.py +541 -541
- tapps_agents/core/browser_controller.py +513 -513
- tapps_agents/core/capability_registry.py +418 -418
- tapps_agents/core/change_impact_analyzer.py +190 -190
- tapps_agents/core/checkpoint_manager.py +377 -377
- tapps_agents/core/code_generator.py +329 -329
- tapps_agents/core/code_validator.py +276 -276
- tapps_agents/core/command_registry.py +327 -327
- tapps_agents/core/context_gathering/__init__.py +2 -2
- tapps_agents/core/context_gathering/repository_explorer.py +28 -28
- tapps_agents/core/context_intelligence/__init__.py +2 -2
- tapps_agents/core/context_intelligence/relevance_scorer.py +24 -24
- tapps_agents/core/context_intelligence/token_budget_manager.py +27 -27
- tapps_agents/core/context_manager.py +240 -240
- tapps_agents/core/cursor_feedback_monitor.py +146 -146
- tapps_agents/core/cursor_verification.py +290 -290
- tapps_agents/core/customization_loader.py +280 -280
- tapps_agents/core/customization_schema.py +260 -260
- tapps_agents/core/customization_template.py +238 -238
- tapps_agents/core/debug_logger.py +124 -124
- tapps_agents/core/design_validator.py +298 -298
- tapps_agents/core/diagram_generator.py +226 -226
- tapps_agents/core/docker_utils.py +232 -232
- tapps_agents/core/document_generator.py +617 -617
- tapps_agents/core/domain_detector.py +30 -30
- tapps_agents/core/error_envelope.py +454 -454
- tapps_agents/core/error_handler.py +270 -270
- tapps_agents/core/estimation_tracker.py +189 -189
- tapps_agents/core/eval_prompt_engine.py +116 -116
- tapps_agents/core/evaluation_base.py +119 -119
- tapps_agents/core/evaluation_models.py +320 -320
- tapps_agents/core/evaluation_orchestrator.py +225 -225
- tapps_agents/core/evaluators/__init__.py +7 -7
- tapps_agents/core/evaluators/architectural_evaluator.py +205 -205
- tapps_agents/core/evaluators/behavioral_evaluator.py +160 -160
- tapps_agents/core/evaluators/performance_profile_evaluator.py +160 -160
- tapps_agents/core/evaluators/security_posture_evaluator.py +148 -148
- tapps_agents/core/evaluators/spec_compliance_evaluator.py +181 -181
- tapps_agents/core/exceptions.py +107 -107
- tapps_agents/core/expert_config_generator.py +293 -293
- tapps_agents/core/export_schema.py +202 -202
- tapps_agents/core/external_feedback_models.py +102 -102
- tapps_agents/core/external_feedback_storage.py +213 -213
- tapps_agents/core/fallback_strategy.py +314 -314
- tapps_agents/core/feedback_analyzer.py +162 -162
- tapps_agents/core/feedback_collector.py +178 -178
- tapps_agents/core/git_operations.py +445 -445
- tapps_agents/core/hardware_profiler.py +151 -151
- tapps_agents/core/instructions.py +324 -324
- tapps_agents/core/io_guardrails.py +69 -69
- tapps_agents/core/issue_manifest.py +249 -249
- tapps_agents/core/issue_schema.py +139 -139
- tapps_agents/core/json_utils.py +128 -128
- tapps_agents/core/knowledge_graph.py +446 -446
- tapps_agents/core/language_detector.py +296 -296
- tapps_agents/core/learning_confidence.py +242 -242
- tapps_agents/core/learning_dashboard.py +246 -246
- tapps_agents/core/learning_decision.py +384 -384
- tapps_agents/core/learning_explainability.py +578 -578
- tapps_agents/core/learning_export.py +287 -287
- tapps_agents/core/learning_integration.py +228 -228
- tapps_agents/core/llm_behavior.py +232 -232
- tapps_agents/core/long_duration_support.py +786 -786
- tapps_agents/core/mcp_setup.py +106 -106
- tapps_agents/core/memory_integration.py +396 -396
- tapps_agents/core/meta_learning.py +666 -666
- tapps_agents/core/module_path_sanitizer.py +199 -199
- tapps_agents/core/multi_agent_orchestrator.py +382 -382
- tapps_agents/core/network_errors.py +125 -125
- tapps_agents/core/nfr_validator.py +336 -336
- tapps_agents/core/offline_mode.py +158 -158
- tapps_agents/core/output_contracts.py +300 -300
- tapps_agents/core/output_formatter.py +300 -300
- tapps_agents/core/path_normalizer.py +174 -174
- tapps_agents/core/path_validator.py +322 -322
- tapps_agents/core/pattern_library.py +250 -250
- tapps_agents/core/performance_benchmark.py +301 -301
- tapps_agents/core/performance_monitor.py +184 -184
- tapps_agents/core/playwright_mcp_controller.py +771 -771
- tapps_agents/core/policy_loader.py +135 -135
- tapps_agents/core/progress.py +166 -166
- tapps_agents/core/project_profile.py +354 -354
- tapps_agents/core/project_type_detector.py +454 -454
- tapps_agents/core/prompt_base.py +223 -223
- tapps_agents/core/prompt_learning/__init__.py +2 -2
- tapps_agents/core/prompt_learning/learning_loop.py +24 -24
- tapps_agents/core/prompt_learning/project_prompt_store.py +25 -25
- tapps_agents/core/prompt_learning/skills_prompt_analyzer.py +35 -35
- tapps_agents/core/prompt_optimization/__init__.py +6 -6
- tapps_agents/core/prompt_optimization/ab_tester.py +114 -114
- tapps_agents/core/prompt_optimization/correlation_analyzer.py +160 -160
- tapps_agents/core/prompt_optimization/progressive_refiner.py +129 -129
- tapps_agents/core/prompt_optimization/prompt_library.py +37 -37
- tapps_agents/core/requirements_evaluator.py +431 -431
- tapps_agents/core/resource_aware_executor.py +449 -449
- tapps_agents/core/resource_monitor.py +343 -343
- tapps_agents/core/resume_handler.py +298 -298
- tapps_agents/core/retry_handler.py +197 -197
- tapps_agents/core/review_checklists.py +479 -479
- tapps_agents/core/role_loader.py +201 -201
- tapps_agents/core/role_template_loader.py +201 -201
- tapps_agents/core/runtime_mode.py +60 -60
- tapps_agents/core/security_scanner.py +342 -342
- tapps_agents/core/skill_agent_registry.py +194 -194
- tapps_agents/core/skill_integration.py +208 -208
- tapps_agents/core/skill_loader.py +492 -492
- tapps_agents/core/skill_template.py +341 -341
- tapps_agents/core/skill_validator.py +478 -478
- tapps_agents/core/stack_analyzer.py +35 -35
- tapps_agents/core/startup.py +174 -174
- tapps_agents/core/storage_manager.py +397 -397
- tapps_agents/core/storage_models.py +166 -166
- tapps_agents/core/story_evaluator.py +410 -410
- tapps_agents/core/subprocess_utils.py +170 -170
- tapps_agents/core/task_duration.py +296 -296
- tapps_agents/core/task_memory.py +582 -582
- tapps_agents/core/task_state.py +226 -226
- tapps_agents/core/tech_stack_priorities.py +208 -208
- tapps_agents/core/temp_directory.py +194 -194
- tapps_agents/core/template_merger.py +600 -600
- tapps_agents/core/template_selector.py +280 -280
- tapps_agents/core/test_generator.py +286 -286
- tapps_agents/core/tiered_context.py +253 -253
- tapps_agents/core/token_monitor.py +345 -345
- tapps_agents/core/traceability.py +254 -254
- tapps_agents/core/trajectory_tracker.py +50 -50
- tapps_agents/core/unicode_safe.py +143 -143
- tapps_agents/core/unified_cache_config.py +170 -170
- tapps_agents/core/unified_state.py +324 -324
- tapps_agents/core/validate_cursor_setup.py +237 -237
- tapps_agents/core/validation_registry.py +136 -136
- tapps_agents/core/validators/__init__.py +4 -4
- tapps_agents/core/validators/python_validator.py +87 -87
- tapps_agents/core/verification_agent.py +90 -90
- tapps_agents/core/visual_feedback.py +644 -644
- tapps_agents/core/workflow_validator.py +197 -197
- tapps_agents/core/worktree.py +367 -367
- tapps_agents/docker/__init__.py +10 -10
- tapps_agents/docker/analyzer.py +186 -186
- tapps_agents/docker/debugger.py +229 -229
- tapps_agents/docker/error_patterns.py +216 -216
- tapps_agents/epic/__init__.py +22 -22
- tapps_agents/epic/beads_sync.py +115 -115
- tapps_agents/epic/markdown_sync.py +105 -105
- tapps_agents/epic/models.py +96 -96
- tapps_agents/experts/__init__.py +163 -163
- tapps_agents/experts/agent_integration.py +243 -243
- tapps_agents/experts/auto_generator.py +331 -331
- tapps_agents/experts/base_expert.py +536 -536
- tapps_agents/experts/builtin_registry.py +261 -261
- tapps_agents/experts/business_metrics.py +565 -565
- tapps_agents/experts/cache.py +266 -266
- tapps_agents/experts/confidence_breakdown.py +306 -306
- tapps_agents/experts/confidence_calculator.py +336 -336
- tapps_agents/experts/confidence_metrics.py +236 -236
- tapps_agents/experts/domain_config.py +311 -311
- tapps_agents/experts/domain_detector.py +550 -550
- tapps_agents/experts/domain_utils.py +84 -84
- tapps_agents/experts/expert_config.py +113 -113
- tapps_agents/experts/expert_engine.py +465 -465
- tapps_agents/experts/expert_registry.py +744 -744
- tapps_agents/experts/expert_synthesizer.py +70 -70
- tapps_agents/experts/governance.py +197 -197
- tapps_agents/experts/history_logger.py +312 -312
- tapps_agents/experts/knowledge/README.md +180 -180
- tapps_agents/experts/knowledge/accessibility/accessible-forms.md +331 -331
- tapps_agents/experts/knowledge/accessibility/aria-patterns.md +344 -344
- tapps_agents/experts/knowledge/accessibility/color-contrast.md +285 -285
- tapps_agents/experts/knowledge/accessibility/keyboard-navigation.md +332 -332
- tapps_agents/experts/knowledge/accessibility/screen-readers.md +282 -282
- tapps_agents/experts/knowledge/accessibility/semantic-html.md +355 -355
- tapps_agents/experts/knowledge/accessibility/testing-accessibility.md +369 -369
- tapps_agents/experts/knowledge/accessibility/wcag-2.1.md +296 -296
- tapps_agents/experts/knowledge/accessibility/wcag-2.2.md +211 -211
- tapps_agents/experts/knowledge/agent-learning/best-practices.md +715 -715
- tapps_agents/experts/knowledge/agent-learning/pattern-extraction.md +282 -282
- tapps_agents/experts/knowledge/agent-learning/prompt-optimization.md +320 -320
- tapps_agents/experts/knowledge/ai-frameworks/model-optimization.md +90 -90
- tapps_agents/experts/knowledge/ai-frameworks/openvino-patterns.md +260 -260
- tapps_agents/experts/knowledge/api-design-integration/api-gateway-patterns.md +309 -309
- tapps_agents/experts/knowledge/api-design-integration/api-security-patterns.md +521 -521
- tapps_agents/experts/knowledge/api-design-integration/api-versioning.md +421 -421
- tapps_agents/experts/knowledge/api-design-integration/async-protocol-patterns.md +61 -61
- tapps_agents/experts/knowledge/api-design-integration/contract-testing.md +221 -221
- tapps_agents/experts/knowledge/api-design-integration/external-api-integration.md +489 -489
- tapps_agents/experts/knowledge/api-design-integration/fastapi-patterns.md +360 -360
- tapps_agents/experts/knowledge/api-design-integration/fastapi-testing.md +262 -262
- tapps_agents/experts/knowledge/api-design-integration/graphql-patterns.md +582 -582
- tapps_agents/experts/knowledge/api-design-integration/grpc-best-practices.md +499 -499
- tapps_agents/experts/knowledge/api-design-integration/mqtt-patterns.md +455 -455
- tapps_agents/experts/knowledge/api-design-integration/rate-limiting.md +507 -507
- tapps_agents/experts/knowledge/api-design-integration/restful-api-design.md +618 -618
- tapps_agents/experts/knowledge/api-design-integration/websocket-patterns.md +480 -480
- tapps_agents/experts/knowledge/cloud-infrastructure/cloud-native-patterns.md +175 -175
- tapps_agents/experts/knowledge/cloud-infrastructure/container-health-checks.md +261 -261
- tapps_agents/experts/knowledge/cloud-infrastructure/containerization.md +222 -222
- tapps_agents/experts/knowledge/cloud-infrastructure/cost-optimization.md +122 -122
- tapps_agents/experts/knowledge/cloud-infrastructure/disaster-recovery.md +153 -153
- tapps_agents/experts/knowledge/cloud-infrastructure/dockerfile-patterns.md +285 -285
- tapps_agents/experts/knowledge/cloud-infrastructure/infrastructure-as-code.md +187 -187
- tapps_agents/experts/knowledge/cloud-infrastructure/kubernetes-patterns.md +253 -253
- tapps_agents/experts/knowledge/cloud-infrastructure/multi-cloud-strategies.md +155 -155
- tapps_agents/experts/knowledge/cloud-infrastructure/serverless-architecture.md +200 -200
- tapps_agents/experts/knowledge/code-quality-analysis/README.md +16 -16
- tapps_agents/experts/knowledge/code-quality-analysis/code-metrics.md +137 -137
- tapps_agents/experts/knowledge/code-quality-analysis/complexity-analysis.md +181 -181
- tapps_agents/experts/knowledge/code-quality-analysis/technical-debt-patterns.md +191 -191
- tapps_agents/experts/knowledge/data-privacy-compliance/anonymization.md +313 -313
- tapps_agents/experts/knowledge/data-privacy-compliance/ccpa.md +255 -255
- tapps_agents/experts/knowledge/data-privacy-compliance/consent-management.md +282 -282
- tapps_agents/experts/knowledge/data-privacy-compliance/data-minimization.md +275 -275
- tapps_agents/experts/knowledge/data-privacy-compliance/data-retention.md +297 -297
- tapps_agents/experts/knowledge/data-privacy-compliance/data-subject-rights.md +383 -383
- tapps_agents/experts/knowledge/data-privacy-compliance/encryption-privacy.md +285 -285
- tapps_agents/experts/knowledge/data-privacy-compliance/gdpr.md +344 -344
- tapps_agents/experts/knowledge/data-privacy-compliance/hipaa.md +385 -385
- tapps_agents/experts/knowledge/data-privacy-compliance/privacy-by-design.md +280 -280
- tapps_agents/experts/knowledge/database-data-management/acid-vs-cap.md +164 -164
- tapps_agents/experts/knowledge/database-data-management/backup-and-recovery.md +182 -182
- tapps_agents/experts/knowledge/database-data-management/data-modeling.md +172 -172
- tapps_agents/experts/knowledge/database-data-management/database-design.md +187 -187
- tapps_agents/experts/knowledge/database-data-management/flux-query-optimization.md +342 -342
- tapps_agents/experts/knowledge/database-data-management/influxdb-connection-patterns.md +432 -432
- tapps_agents/experts/knowledge/database-data-management/influxdb-patterns.md +442 -442
- tapps_agents/experts/knowledge/database-data-management/migration-strategies.md +216 -216
- tapps_agents/experts/knowledge/database-data-management/nosql-patterns.md +259 -259
- tapps_agents/experts/knowledge/database-data-management/scalability-patterns.md +184 -184
- tapps_agents/experts/knowledge/database-data-management/sql-optimization.md +175 -175
- tapps_agents/experts/knowledge/database-data-management/time-series-modeling.md +444 -444
- tapps_agents/experts/knowledge/development-workflow/README.md +16 -16
- tapps_agents/experts/knowledge/development-workflow/automation-best-practices.md +216 -216
- tapps_agents/experts/knowledge/development-workflow/build-strategies.md +198 -198
- tapps_agents/experts/knowledge/development-workflow/deployment-patterns.md +205 -205
- tapps_agents/experts/knowledge/development-workflow/git-workflows.md +205 -205
- tapps_agents/experts/knowledge/documentation-knowledge-management/README.md +16 -16
- tapps_agents/experts/knowledge/documentation-knowledge-management/api-documentation-patterns.md +231 -231
- tapps_agents/experts/knowledge/documentation-knowledge-management/documentation-standards.md +191 -191
- tapps_agents/experts/knowledge/documentation-knowledge-management/knowledge-management.md +171 -171
- tapps_agents/experts/knowledge/documentation-knowledge-management/technical-writing-guide.md +192 -192
- tapps_agents/experts/knowledge/observability-monitoring/alerting-patterns.md +461 -461
- tapps_agents/experts/knowledge/observability-monitoring/apm-tools.md +459 -459
- tapps_agents/experts/knowledge/observability-monitoring/distributed-tracing.md +367 -367
- tapps_agents/experts/knowledge/observability-monitoring/logging-strategies.md +478 -478
- tapps_agents/experts/knowledge/observability-monitoring/metrics-and-monitoring.md +510 -510
- tapps_agents/experts/knowledge/observability-monitoring/observability-best-practices.md +492 -492
- tapps_agents/experts/knowledge/observability-monitoring/open-telemetry.md +573 -573
- tapps_agents/experts/knowledge/observability-monitoring/slo-sli-sla.md +419 -419
- tapps_agents/experts/knowledge/performance/anti-patterns.md +284 -284
- tapps_agents/experts/knowledge/performance/api-performance.md +256 -256
- tapps_agents/experts/knowledge/performance/caching.md +327 -327
- tapps_agents/experts/knowledge/performance/database-performance.md +252 -252
- tapps_agents/experts/knowledge/performance/optimization-patterns.md +327 -327
- tapps_agents/experts/knowledge/performance/profiling.md +297 -297
- tapps_agents/experts/knowledge/performance/resource-management.md +293 -293
- tapps_agents/experts/knowledge/performance/scalability.md +306 -306
- tapps_agents/experts/knowledge/security/owasp-top10.md +209 -209
- tapps_agents/experts/knowledge/security/secure-coding-practices.md +207 -207
- tapps_agents/experts/knowledge/security/threat-modeling.md +220 -220
- tapps_agents/experts/knowledge/security/vulnerability-patterns.md +342 -342
- tapps_agents/experts/knowledge/software-architecture/docker-compose-patterns.md +314 -314
- tapps_agents/experts/knowledge/software-architecture/microservices-patterns.md +379 -379
- tapps_agents/experts/knowledge/software-architecture/service-communication.md +316 -316
- tapps_agents/experts/knowledge/testing/best-practices.md +310 -310
- tapps_agents/experts/knowledge/testing/coverage-analysis.md +293 -293
- tapps_agents/experts/knowledge/testing/mocking.md +256 -256
- tapps_agents/experts/knowledge/testing/test-automation.md +276 -276
- tapps_agents/experts/knowledge/testing/test-data.md +271 -271
- tapps_agents/experts/knowledge/testing/test-design-patterns.md +280 -280
- tapps_agents/experts/knowledge/testing/test-maintenance.md +236 -236
- tapps_agents/experts/knowledge/testing/test-strategies.md +311 -311
- tapps_agents/experts/knowledge/user-experience/information-architecture.md +325 -325
- tapps_agents/experts/knowledge/user-experience/interaction-design.md +363 -363
- tapps_agents/experts/knowledge/user-experience/prototyping.md +293 -293
- tapps_agents/experts/knowledge/user-experience/usability-heuristics.md +337 -337
- tapps_agents/experts/knowledge/user-experience/usability-testing.md +311 -311
- tapps_agents/experts/knowledge/user-experience/user-journeys.md +296 -296
- tapps_agents/experts/knowledge/user-experience/user-research.md +373 -373
- tapps_agents/experts/knowledge/user-experience/ux-principles.md +340 -340
- tapps_agents/experts/knowledge_freshness.py +321 -321
- tapps_agents/experts/knowledge_ingestion.py +438 -438
- tapps_agents/experts/knowledge_need_detector.py +93 -93
- tapps_agents/experts/knowledge_validator.py +382 -382
- tapps_agents/experts/observability.py +440 -440
- tapps_agents/experts/passive_notifier.py +238 -238
- tapps_agents/experts/proactive_orchestrator.py +32 -32
- tapps_agents/experts/rag_chunker.py +205 -205
- tapps_agents/experts/rag_embedder.py +152 -152
- tapps_agents/experts/rag_evaluation.py +299 -299
- tapps_agents/experts/rag_index.py +303 -303
- tapps_agents/experts/rag_metrics.py +293 -293
- tapps_agents/experts/rag_safety.py +263 -263
- tapps_agents/experts/report_generator.py +296 -296
- tapps_agents/experts/setup_wizard.py +441 -441
- tapps_agents/experts/simple_rag.py +431 -431
- tapps_agents/experts/vector_rag.py +354 -354
- tapps_agents/experts/weight_distributor.py +304 -304
- tapps_agents/health/__init__.py +24 -24
- tapps_agents/health/base.py +75 -75
- tapps_agents/health/checks/__init__.py +22 -22
- tapps_agents/health/checks/automation.py +127 -127
- tapps_agents/health/checks/context7_cache.py +210 -210
- tapps_agents/health/checks/environment.py +116 -116
- tapps_agents/health/checks/execution.py +170 -170
- tapps_agents/health/checks/knowledge_base.py +187 -187
- tapps_agents/health/checks/outcomes.backup_20260204_064058.py +324 -0
- tapps_agents/health/checks/outcomes.backup_20260204_064256.py +324 -0
- tapps_agents/health/checks/outcomes.backup_20260204_064600.py +324 -0
- tapps_agents/health/checks/outcomes.py +324 -324
- tapps_agents/health/collector.py +280 -280
- tapps_agents/health/dashboard.py +137 -137
- tapps_agents/health/metrics.py +151 -151
- tapps_agents/health/registry.py +166 -166
- tapps_agents/hooks/__init__.py +33 -33
- tapps_agents/hooks/config.py +140 -140
- tapps_agents/hooks/events.py +135 -135
- tapps_agents/hooks/executor.py +128 -128
- tapps_agents/hooks/manager.py +143 -143
- tapps_agents/integration/__init__.py +8 -8
- tapps_agents/integration/service_integrator.py +121 -121
- tapps_agents/integrations/__init__.py +10 -10
- tapps_agents/integrations/clawdbot.py +525 -525
- tapps_agents/integrations/memory_bridge.py +356 -356
- tapps_agents/mcp/__init__.py +18 -18
- tapps_agents/mcp/gateway.py +112 -112
- tapps_agents/mcp/servers/__init__.py +13 -13
- tapps_agents/mcp/servers/analysis.py +204 -204
- tapps_agents/mcp/servers/context7.py +198 -198
- tapps_agents/mcp/servers/filesystem.py +218 -218
- tapps_agents/mcp/servers/git.py +201 -201
- tapps_agents/mcp/tool_registry.py +115 -115
- tapps_agents/quality/__init__.py +54 -54
- tapps_agents/quality/coverage_analyzer.py +379 -379
- tapps_agents/quality/enforcement.py +82 -82
- tapps_agents/quality/gates/__init__.py +37 -37
- tapps_agents/quality/gates/approval_gate.py +255 -255
- tapps_agents/quality/gates/base.py +84 -84
- tapps_agents/quality/gates/exceptions.py +43 -43
- tapps_agents/quality/gates/policy_gate.py +195 -195
- tapps_agents/quality/gates/registry.py +239 -239
- tapps_agents/quality/gates/security_gate.py +156 -156
- tapps_agents/quality/quality_gates.py +369 -369
- tapps_agents/quality/secret_scanner.py +335 -335
- tapps_agents/resources/__init__.py +5 -0
- tapps_agents/resources/claude/__init__.py +1 -0
- tapps_agents/resources/claude/commands/README.md +156 -0
- tapps_agents/resources/claude/commands/__init__.py +1 -0
- tapps_agents/resources/claude/commands/build-fix.md +22 -0
- tapps_agents/resources/claude/commands/build.md +77 -0
- tapps_agents/resources/claude/commands/debug.md +53 -0
- tapps_agents/resources/claude/commands/design.md +68 -0
- tapps_agents/resources/claude/commands/docs.md +53 -0
- tapps_agents/resources/claude/commands/e2e.md +22 -0
- tapps_agents/resources/claude/commands/fix.md +54 -0
- tapps_agents/resources/claude/commands/implement.md +53 -0
- tapps_agents/resources/claude/commands/improve.md +53 -0
- tapps_agents/resources/claude/commands/library-docs.md +64 -0
- tapps_agents/resources/claude/commands/lint.md +52 -0
- tapps_agents/resources/claude/commands/plan.md +65 -0
- tapps_agents/resources/claude/commands/refactor-clean.md +21 -0
- tapps_agents/resources/claude/commands/refactor.md +55 -0
- tapps_agents/resources/claude/commands/review.md +67 -0
- tapps_agents/resources/claude/commands/score.md +60 -0
- tapps_agents/resources/claude/commands/security-review.md +22 -0
- tapps_agents/resources/claude/commands/security-scan.md +54 -0
- tapps_agents/resources/claude/commands/tdd.md +24 -0
- tapps_agents/resources/claude/commands/test-coverage.md +21 -0
- tapps_agents/resources/claude/commands/test.md +54 -0
- tapps_agents/resources/claude/commands/update-codemaps.md +20 -0
- tapps_agents/resources/claude/commands/update-docs.md +21 -0
- tapps_agents/resources/claude/skills/__init__.py +1 -0
- tapps_agents/resources/claude/skills/analyst/SKILL.md +272 -0
- tapps_agents/resources/claude/skills/analyst/__init__.py +1 -0
- tapps_agents/resources/claude/skills/architect/SKILL.md +282 -0
- tapps_agents/resources/claude/skills/architect/__init__.py +1 -0
- tapps_agents/resources/claude/skills/backend-patterns/SKILL.md +30 -0
- tapps_agents/resources/claude/skills/backend-patterns/__init__.py +1 -0
- tapps_agents/resources/claude/skills/coding-standards/SKILL.md +29 -0
- tapps_agents/resources/claude/skills/coding-standards/__init__.py +1 -0
- tapps_agents/resources/claude/skills/debugger/SKILL.md +203 -0
- tapps_agents/resources/claude/skills/debugger/__init__.py +1 -0
- tapps_agents/resources/claude/skills/designer/SKILL.md +243 -0
- tapps_agents/resources/claude/skills/designer/__init__.py +1 -0
- tapps_agents/resources/claude/skills/documenter/SKILL.md +252 -0
- tapps_agents/resources/claude/skills/documenter/__init__.py +1 -0
- tapps_agents/resources/claude/skills/enhancer/SKILL.md +307 -0
- tapps_agents/resources/claude/skills/enhancer/__init__.py +1 -0
- tapps_agents/resources/claude/skills/evaluator/SKILL.md +204 -0
- tapps_agents/resources/claude/skills/evaluator/__init__.py +1 -0
- tapps_agents/resources/claude/skills/frontend-patterns/SKILL.md +29 -0
- tapps_agents/resources/claude/skills/frontend-patterns/__init__.py +1 -0
- tapps_agents/resources/claude/skills/implementer/SKILL.md +188 -0
- tapps_agents/resources/claude/skills/implementer/__init__.py +1 -0
- tapps_agents/resources/claude/skills/improver/SKILL.md +218 -0
- tapps_agents/resources/claude/skills/improver/__init__.py +1 -0
- tapps_agents/resources/claude/skills/ops/SKILL.md +281 -0
- tapps_agents/resources/claude/skills/ops/__init__.py +1 -0
- tapps_agents/resources/claude/skills/orchestrator/SKILL.md +390 -0
- tapps_agents/resources/claude/skills/orchestrator/__init__.py +1 -0
- tapps_agents/resources/claude/skills/planner/SKILL.md +254 -0
- tapps_agents/resources/claude/skills/planner/__init__.py +1 -0
- tapps_agents/resources/claude/skills/reviewer/SKILL.md +434 -0
- tapps_agents/resources/claude/skills/reviewer/__init__.py +1 -0
- tapps_agents/resources/claude/skills/security-review/SKILL.md +31 -0
- tapps_agents/resources/claude/skills/security-review/__init__.py +1 -0
- tapps_agents/resources/claude/skills/simple-mode/SKILL.md +695 -0
- tapps_agents/resources/claude/skills/simple-mode/__init__.py +1 -0
- tapps_agents/resources/claude/skills/tester/SKILL.md +219 -0
- tapps_agents/resources/claude/skills/tester/__init__.py +1 -0
- tapps_agents/resources/cursor/.cursorignore +35 -0
- tapps_agents/resources/cursor/__init__.py +1 -0
- tapps_agents/resources/cursor/commands/__init__.py +1 -0
- tapps_agents/resources/cursor/commands/build-fix.md +11 -0
- tapps_agents/resources/cursor/commands/build.md +11 -0
- tapps_agents/resources/cursor/commands/e2e.md +11 -0
- tapps_agents/resources/cursor/commands/fix.md +11 -0
- tapps_agents/resources/cursor/commands/refactor-clean.md +11 -0
- tapps_agents/resources/cursor/commands/review.md +11 -0
- tapps_agents/resources/cursor/commands/security-review.md +11 -0
- tapps_agents/resources/cursor/commands/tdd.md +11 -0
- tapps_agents/resources/cursor/commands/test-coverage.md +11 -0
- tapps_agents/resources/cursor/commands/test.md +11 -0
- tapps_agents/resources/cursor/commands/update-codemaps.md +10 -0
- tapps_agents/resources/cursor/commands/update-docs.md +11 -0
- tapps_agents/resources/cursor/rules/__init__.py +1 -0
- tapps_agents/resources/cursor/rules/agent-capabilities.mdc +687 -0
- tapps_agents/resources/cursor/rules/coding-style.mdc +31 -0
- tapps_agents/resources/cursor/rules/command-reference.mdc +2081 -0
- tapps_agents/resources/cursor/rules/cursor-mode-usage.mdc +125 -0
- tapps_agents/resources/cursor/rules/git-workflow.mdc +29 -0
- tapps_agents/resources/cursor/rules/performance.mdc +29 -0
- tapps_agents/resources/cursor/rules/project-context.mdc +163 -0
- tapps_agents/resources/cursor/rules/project-profiling.mdc +197 -0
- tapps_agents/resources/cursor/rules/quick-reference.mdc +630 -0
- tapps_agents/resources/cursor/rules/security.mdc +32 -0
- tapps_agents/resources/cursor/rules/simple-mode.mdc +500 -0
- tapps_agents/resources/cursor/rules/testing.mdc +31 -0
- tapps_agents/resources/cursor/rules/when-to-use.mdc +156 -0
- tapps_agents/resources/cursor/rules/workflow-presets.mdc +179 -0
- tapps_agents/resources/customizations/__init__.py +1 -0
- tapps_agents/resources/customizations/example-custom.yaml +83 -0
- tapps_agents/resources/hooks/__init__.py +1 -0
- tapps_agents/resources/hooks/templates/README.md +5 -0
- tapps_agents/resources/hooks/templates/__init__.py +1 -0
- tapps_agents/resources/hooks/templates/add-project-context.yaml +8 -0
- tapps_agents/resources/hooks/templates/auto-format-js.yaml +10 -0
- tapps_agents/resources/hooks/templates/auto-format-python.yaml +10 -0
- tapps_agents/resources/hooks/templates/git-commit-check.yaml +7 -0
- tapps_agents/resources/hooks/templates/notify-on-complete.yaml +8 -0
- tapps_agents/resources/hooks/templates/quality-gate.yaml +8 -0
- tapps_agents/resources/hooks/templates/security-scan-on-edit.yaml +10 -0
- tapps_agents/resources/hooks/templates/session-end-log.yaml +7 -0
- tapps_agents/resources/hooks/templates/show-beads-ready.yaml +8 -0
- tapps_agents/resources/hooks/templates/test-on-edit.yaml +10 -0
- tapps_agents/resources/hooks/templates/update-docs-on-complete.yaml +8 -0
- tapps_agents/resources/hooks/templates/user-prompt-log.yaml +7 -0
- tapps_agents/resources/scripts/__init__.py +1 -0
- tapps_agents/resources/scripts/set_bd_path.ps1 +51 -0
- tapps_agents/resources/workflows/__init__.py +1 -0
- tapps_agents/resources/workflows/presets/__init__.py +1 -0
- tapps_agents/resources/workflows/presets/brownfield-analysis.yaml +235 -0
- tapps_agents/resources/workflows/presets/fix.yaml +78 -0
- tapps_agents/resources/workflows/presets/full-sdlc.yaml +122 -0
- tapps_agents/resources/workflows/presets/quality.yaml +82 -0
- tapps_agents/resources/workflows/presets/rapid-dev.yaml +84 -0
- tapps_agents/session/__init__.py +19 -19
- tapps_agents/session/manager.py +256 -256
- tapps_agents/simple_mode/__init__.py +66 -66
- tapps_agents/simple_mode/agent_contracts.py +357 -357
- tapps_agents/simple_mode/beads_hooks.py +151 -151
- tapps_agents/simple_mode/code_snippet_handler.py +382 -382
- tapps_agents/simple_mode/documentation_manager.py +395 -395
- tapps_agents/simple_mode/documentation_reader.py +187 -187
- tapps_agents/simple_mode/file_inference.py +292 -292
- tapps_agents/simple_mode/framework_change_detector.py +268 -268
- tapps_agents/simple_mode/intent_parser.py +510 -510
- tapps_agents/simple_mode/learning_progression.py +358 -358
- tapps_agents/simple_mode/nl_handler.py +700 -700
- tapps_agents/simple_mode/onboarding.py +253 -253
- tapps_agents/simple_mode/orchestrators/__init__.py +38 -38
- tapps_agents/simple_mode/orchestrators/breakdown_orchestrator.py +49 -49
- tapps_agents/simple_mode/orchestrators/brownfield_orchestrator.py +135 -135
- tapps_agents/simple_mode/orchestrators/deliverable_checklist.py +349 -349
- tapps_agents/simple_mode/orchestrators/enhance_orchestrator.py +53 -53
- tapps_agents/simple_mode/orchestrators/epic_orchestrator.py +122 -122
- tapps_agents/simple_mode/orchestrators/explore_orchestrator.py +184 -184
- tapps_agents/simple_mode/orchestrators/plan_analysis_orchestrator.py +206 -206
- tapps_agents/simple_mode/orchestrators/pr_orchestrator.py +237 -237
- tapps_agents/simple_mode/orchestrators/refactor_orchestrator.py +222 -222
- tapps_agents/simple_mode/orchestrators/requirements_tracer.py +262 -262
- tapps_agents/simple_mode/orchestrators/resume_orchestrator.py +210 -210
- tapps_agents/simple_mode/orchestrators/review_orchestrator.py +161 -161
- tapps_agents/simple_mode/orchestrators/test_orchestrator.py +82 -82
- tapps_agents/simple_mode/output_aggregator.py +340 -340
- tapps_agents/simple_mode/result_formatters.py +598 -598
- tapps_agents/simple_mode/step_dependencies.py +382 -382
- tapps_agents/simple_mode/step_results.py +276 -276
- tapps_agents/simple_mode/streaming.py +388 -388
- tapps_agents/simple_mode/variations.py +129 -129
- tapps_agents/simple_mode/visual_feedback.py +238 -238
- tapps_agents/simple_mode/zero_config.py +274 -274
- tapps_agents/suggestions/__init__.py +8 -8
- tapps_agents/suggestions/inline_suggester.py +52 -52
- tapps_agents/templates/__init__.py +8 -8
- tapps_agents/templates/microservice_generator.py +274 -274
- tapps_agents/utils/env_validator.py +291 -291
- tapps_agents/workflow/__init__.py +171 -171
- tapps_agents/workflow/acceptance_verifier.py +132 -132
- tapps_agents/workflow/agent_handlers/__init__.py +41 -41
- tapps_agents/workflow/agent_handlers/analyst_handler.py +75 -75
- tapps_agents/workflow/agent_handlers/architect_handler.py +107 -107
- tapps_agents/workflow/agent_handlers/base.py +84 -84
- tapps_agents/workflow/agent_handlers/debugger_handler.py +100 -100
- tapps_agents/workflow/agent_handlers/designer_handler.py +110 -110
- tapps_agents/workflow/agent_handlers/documenter_handler.py +94 -94
- tapps_agents/workflow/agent_handlers/implementer_handler.py +235 -235
- tapps_agents/workflow/agent_handlers/ops_handler.py +62 -62
- tapps_agents/workflow/agent_handlers/orchestrator_handler.py +43 -43
- tapps_agents/workflow/agent_handlers/planner_handler.py +98 -98
- tapps_agents/workflow/agent_handlers/registry.py +119 -119
- tapps_agents/workflow/agent_handlers/reviewer_handler.py +119 -119
- tapps_agents/workflow/agent_handlers/tester_handler.py +69 -69
- tapps_agents/workflow/analytics_accessor.py +337 -337
- tapps_agents/workflow/analytics_alerts.py +416 -416
- tapps_agents/workflow/analytics_dashboard_cursor.py +281 -281
- tapps_agents/workflow/analytics_dual_write.py +103 -103
- tapps_agents/workflow/analytics_integration.py +119 -119
- tapps_agents/workflow/analytics_query_parser.py +278 -278
- tapps_agents/workflow/analytics_visualizer.py +259 -259
- tapps_agents/workflow/artifact_helper.py +204 -204
- tapps_agents/workflow/audit_logger.py +263 -263
- tapps_agents/workflow/auto_execution_config.py +340 -340
- tapps_agents/workflow/auto_progression.py +586 -586
- tapps_agents/workflow/branch_cleanup.py +349 -349
- tapps_agents/workflow/checkpoint.py +256 -256
- tapps_agents/workflow/checkpoint_manager.py +178 -178
- tapps_agents/workflow/code_artifact.py +179 -179
- tapps_agents/workflow/common_enums.py +96 -96
- tapps_agents/workflow/confirmation_handler.py +130 -130
- tapps_agents/workflow/context_analyzer.py +222 -222
- tapps_agents/workflow/context_artifact.py +230 -230
- tapps_agents/workflow/cursor_chat.py +94 -94
- tapps_agents/workflow/cursor_skill_helper.py +516 -516
- tapps_agents/workflow/dependency_resolver.py +244 -244
- tapps_agents/workflow/design_artifact.py +156 -156
- tapps_agents/workflow/detector.py +751 -751
- tapps_agents/workflow/direct_execution_fallback.py +301 -301
- tapps_agents/workflow/docs_artifact.py +168 -168
- tapps_agents/workflow/enforcer.py +389 -389
- tapps_agents/workflow/enhancement_artifact.py +142 -142
- tapps_agents/workflow/error_recovery.py +806 -806
- tapps_agents/workflow/event_bus.py +183 -183
- tapps_agents/workflow/event_log.py +612 -612
- tapps_agents/workflow/events.py +63 -63
- tapps_agents/workflow/exceptions.py +43 -43
- tapps_agents/workflow/execution_graph.py +498 -498
- tapps_agents/workflow/execution_plan.py +126 -126
- tapps_agents/workflow/file_utils.py +186 -186
- tapps_agents/workflow/gate_evaluator.py +182 -182
- tapps_agents/workflow/gate_integration.py +200 -200
- tapps_agents/workflow/graph_visualizer.py +130 -130
- tapps_agents/workflow/health_checker.py +206 -206
- tapps_agents/workflow/logging_helper.py +243 -243
- tapps_agents/workflow/manifest.py +582 -582
- tapps_agents/workflow/marker_writer.py +250 -250
- tapps_agents/workflow/messaging.py +325 -325
- tapps_agents/workflow/metadata_models.py +91 -91
- tapps_agents/workflow/metrics_integration.py +226 -226
- tapps_agents/workflow/migration_utils.py +116 -116
- tapps_agents/workflow/models.py +148 -148
- tapps_agents/workflow/nlp_config.py +198 -198
- tapps_agents/workflow/nlp_error_handler.py +207 -207
- tapps_agents/workflow/nlp_executor.py +163 -163
- tapps_agents/workflow/nlp_parser.py +528 -528
- tapps_agents/workflow/observability_dashboard.py +451 -451
- tapps_agents/workflow/observer.py +170 -170
- tapps_agents/workflow/ops_artifact.py +257 -257
- tapps_agents/workflow/output_passing.py +214 -214
- tapps_agents/workflow/parallel_executor.py +463 -463
- tapps_agents/workflow/planning_artifact.py +179 -179
- tapps_agents/workflow/preset_loader.py +285 -285
- tapps_agents/workflow/preset_recommender.py +270 -270
- tapps_agents/workflow/progress_logger.py +145 -145
- tapps_agents/workflow/progress_manager.py +303 -303
- tapps_agents/workflow/progress_monitor.py +186 -186
- tapps_agents/workflow/progress_updates.py +423 -423
- tapps_agents/workflow/quality_artifact.py +158 -158
- tapps_agents/workflow/quality_loopback.py +101 -101
- tapps_agents/workflow/recommender.py +387 -387
- tapps_agents/workflow/remediation_loop.py +166 -166
- tapps_agents/workflow/result_aggregator.py +300 -300
- tapps_agents/workflow/review_artifact.py +185 -185
- tapps_agents/workflow/schema_validator.py +522 -522
- tapps_agents/workflow/session_handoff.py +178 -178
- tapps_agents/workflow/skill_invoker.py +648 -648
- tapps_agents/workflow/state_manager.py +756 -756
- tapps_agents/workflow/state_persistence_config.py +331 -331
- tapps_agents/workflow/status_monitor.py +449 -449
- tapps_agents/workflow/step_checkpoint.py +314 -314
- tapps_agents/workflow/step_details.py +201 -201
- tapps_agents/workflow/story_models.py +147 -147
- tapps_agents/workflow/streaming.py +416 -416
- tapps_agents/workflow/suggestion_engine.py +552 -552
- tapps_agents/workflow/testing_artifact.py +186 -186
- tapps_agents/workflow/timeline.py +158 -158
- tapps_agents/workflow/token_integration.py +209 -209
- tapps_agents/workflow/validation.py +217 -217
- tapps_agents/workflow/visual_feedback.py +391 -391
- tapps_agents/workflow/workflow_chain.py +95 -95
- tapps_agents/workflow/workflow_summary.py +219 -219
- tapps_agents/workflow/worktree_manager.py +724 -724
- {tapps_agents-3.6.0.dist-info → tapps_agents-3.6.1.dist-info}/METADATA +672 -672
- tapps_agents-3.6.1.dist-info/RECORD +883 -0
- {tapps_agents-3.6.0.dist-info → tapps_agents-3.6.1.dist-info}/licenses/LICENSE +22 -22
- tapps_agents-3.6.0.dist-info/RECORD +0 -758
- {tapps_agents-3.6.0.dist-info → tapps_agents-3.6.1.dist-info}/WHEEL +0 -0
- {tapps_agents-3.6.0.dist-info → tapps_agents-3.6.1.dist-info}/entry_points.txt +0 -0
- {tapps_agents-3.6.0.dist-info → tapps_agents-3.6.1.dist-info}/top_level.txt +0 -0
tapps_agents/cli/feedback.py
CHANGED
|
@@ -1,936 +1,936 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Centralized feedback system for CLI commands.
|
|
3
|
-
|
|
4
|
-
Provides consistent, user-friendly output with support for quiet, normal, and verbose modes.
|
|
5
|
-
All CLI commands should use this module instead of direct print() calls.
|
|
6
|
-
"""
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
|
-
import json
|
|
10
|
-
import os
|
|
11
|
-
import sys
|
|
12
|
-
import time
|
|
13
|
-
from datetime import UTC, datetime
|
|
14
|
-
from enum import Enum
|
|
15
|
-
from typing import Any
|
|
16
|
-
|
|
17
|
-
# Try to import version, with fallback to importlib.metadata
|
|
18
|
-
try:
|
|
19
|
-
from .. import __version__ as PACKAGE_VERSION
|
|
20
|
-
except (ImportError, AttributeError):
|
|
21
|
-
# Fallback: use importlib.metadata (standard library, Python 3.8+)
|
|
22
|
-
try:
|
|
23
|
-
from importlib.metadata import version
|
|
24
|
-
PACKAGE_VERSION = version("tapps-agents")
|
|
25
|
-
except Exception:
|
|
26
|
-
# Last resort: try reading from __init__.py directly
|
|
27
|
-
try:
|
|
28
|
-
import importlib.util
|
|
29
|
-
import pathlib
|
|
30
|
-
init_path = pathlib.Path(__file__).parent.parent / "__init__.py"
|
|
31
|
-
if init_path.exists():
|
|
32
|
-
spec = importlib.util.spec_from_file_location("tapps_agents_init", init_path)
|
|
33
|
-
if spec and spec.loader:
|
|
34
|
-
module = importlib.util.module_from_spec(spec)
|
|
35
|
-
spec.loader.exec_module(module)
|
|
36
|
-
PACKAGE_VERSION = getattr(module, "__version__", "unknown")
|
|
37
|
-
else:
|
|
38
|
-
PACKAGE_VERSION = "unknown"
|
|
39
|
-
else:
|
|
40
|
-
PACKAGE_VERSION = "unknown"
|
|
41
|
-
except Exception:
|
|
42
|
-
PACKAGE_VERSION = "unknown"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class VerbosityLevel(Enum):
|
|
46
|
-
"""Verbosity levels for CLI output."""
|
|
47
|
-
QUIET = "quiet"
|
|
48
|
-
NORMAL = "normal"
|
|
49
|
-
VERBOSE = "verbose"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
class ProgressMode(Enum):
|
|
53
|
-
"""Progress rendering modes for CLI output."""
|
|
54
|
-
|
|
55
|
-
AUTO = "auto"
|
|
56
|
-
OFF = "off"
|
|
57
|
-
PLAIN = "plain"
|
|
58
|
-
RICH = "rich"
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def _is_interactive_tty() -> bool:
|
|
62
|
-
"""
|
|
63
|
-
Best-effort detection of an interactive TTY.
|
|
64
|
-
|
|
65
|
-
Notes:
|
|
66
|
-
- We treat stderr as the "UI" stream for progress.
|
|
67
|
-
- On CI / non-interactive sessions we default to non-animated output.
|
|
68
|
-
"""
|
|
69
|
-
|
|
70
|
-
try:
|
|
71
|
-
return bool(sys.stderr.isatty())
|
|
72
|
-
except Exception:
|
|
73
|
-
return False
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def _env_truthy(name: str) -> bool:
|
|
77
|
-
val = os.environ.get(name)
|
|
78
|
-
if val is None:
|
|
79
|
-
return False
|
|
80
|
-
return val.strip().lower() not in {"0", "false", "no", "off", ""}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def _should_disable_animated_progress() -> bool:
|
|
84
|
-
"""
|
|
85
|
-
Prefer deterministic output on CI/log files, and honor common env flags.
|
|
86
|
-
|
|
87
|
-
References (patterns widely used by modern CLIs):
|
|
88
|
-
- NO_COLOR: disable color
|
|
89
|
-
- CI/GITHUB_ACTIONS: reduce animation/noise
|
|
90
|
-
- TAPPS_PROGRESS=off: explicit disable
|
|
91
|
-
"""
|
|
92
|
-
|
|
93
|
-
if _env_truthy("CI") or _env_truthy("GITHUB_ACTIONS"):
|
|
94
|
-
return True
|
|
95
|
-
if _env_truthy("TAPPS_NO_PROGRESS") or _env_truthy("NO_PROGRESS"):
|
|
96
|
-
return True
|
|
97
|
-
progress_env = os.environ.get("TAPPS_PROGRESS", "").strip().lower()
|
|
98
|
-
if progress_env in {"off", "0", "false", "no"}:
|
|
99
|
-
return True
|
|
100
|
-
return False
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
class _PlainSpinner:
|
|
104
|
-
"""A tiny spinner that advances on each render call (no background thread)."""
|
|
105
|
-
|
|
106
|
-
_frames = "|/-\\"
|
|
107
|
-
|
|
108
|
-
def __init__(self) -> None:
|
|
109
|
-
self._i = 0
|
|
110
|
-
|
|
111
|
-
def next(self) -> str:
|
|
112
|
-
ch = self._frames[self._i % len(self._frames)]
|
|
113
|
-
self._i += 1
|
|
114
|
-
return ch
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
class _RichProgressRenderer:
|
|
118
|
-
"""
|
|
119
|
-
Rich-based renderer that supports:
|
|
120
|
-
- Indeterminate spinner (Status)
|
|
121
|
-
- Determinate progress bar (Progress + Live)
|
|
122
|
-
|
|
123
|
-
This is intentionally lightweight and driven by explicit update() calls,
|
|
124
|
-
so it works without threads and plays nicely with existing code paths.
|
|
125
|
-
"""
|
|
126
|
-
|
|
127
|
-
def __init__(self) -> None:
|
|
128
|
-
from rich.console import Console
|
|
129
|
-
from rich.live import Live
|
|
130
|
-
from rich.progress import (
|
|
131
|
-
BarColumn,
|
|
132
|
-
Progress,
|
|
133
|
-
SpinnerColumn,
|
|
134
|
-
TaskProgressColumn,
|
|
135
|
-
TextColumn,
|
|
136
|
-
TimeElapsedColumn,
|
|
137
|
-
TimeRemainingColumn,
|
|
138
|
-
)
|
|
139
|
-
from rich.status import Status
|
|
140
|
-
|
|
141
|
-
self._Console = Console
|
|
142
|
-
self._Live = Live
|
|
143
|
-
self._Progress = Progress
|
|
144
|
-
self._Status = Status
|
|
145
|
-
|
|
146
|
-
self._BarColumn = BarColumn
|
|
147
|
-
self._SpinnerColumn = SpinnerColumn
|
|
148
|
-
self._TextColumn = TextColumn
|
|
149
|
-
self._TaskProgressColumn = TaskProgressColumn
|
|
150
|
-
self._TimeElapsedColumn = TimeElapsedColumn
|
|
151
|
-
self._TimeRemainingColumn = TimeRemainingColumn
|
|
152
|
-
|
|
153
|
-
self._console = Console(stderr=True, highlight=False, soft_wrap=True)
|
|
154
|
-
|
|
155
|
-
self._status: Status | None = None
|
|
156
|
-
self._progress: Progress | None = None
|
|
157
|
-
self._live: Live | None = None
|
|
158
|
-
self._task_id: int | None = None
|
|
159
|
-
|
|
160
|
-
def update(
|
|
161
|
-
self,
|
|
162
|
-
message: str,
|
|
163
|
-
percentage: int | None,
|
|
164
|
-
show_progress_bar: bool,
|
|
165
|
-
) -> None:
|
|
166
|
-
if show_progress_bar and percentage is not None:
|
|
167
|
-
self._stop_status()
|
|
168
|
-
self._ensure_progress()
|
|
169
|
-
assert self._progress is not None
|
|
170
|
-
assert self._task_id is not None
|
|
171
|
-
|
|
172
|
-
pct = max(0, min(100, int(percentage)))
|
|
173
|
-
self._progress.update(self._task_id, description=message, completed=pct)
|
|
174
|
-
return
|
|
175
|
-
|
|
176
|
-
# Indeterminate / spinner-only
|
|
177
|
-
self._stop_progress()
|
|
178
|
-
self._ensure_status(message)
|
|
179
|
-
assert self._status is not None
|
|
180
|
-
self._status.update(status=message)
|
|
181
|
-
|
|
182
|
-
def clear(self) -> None:
|
|
183
|
-
self._stop_status()
|
|
184
|
-
self._stop_progress()
|
|
185
|
-
|
|
186
|
-
def _ensure_status(self, message: str) -> None:
|
|
187
|
-
if self._status is None:
|
|
188
|
-
self._status = self._console.status(message, spinner="dots")
|
|
189
|
-
self._status.start()
|
|
190
|
-
|
|
191
|
-
def _stop_status(self) -> None:
|
|
192
|
-
if self._status is not None:
|
|
193
|
-
try:
|
|
194
|
-
self._status.stop()
|
|
195
|
-
finally:
|
|
196
|
-
self._status = None
|
|
197
|
-
|
|
198
|
-
def _ensure_progress(self) -> None:
|
|
199
|
-
if self._progress is None:
|
|
200
|
-
# “2025 modern” defaults: clean, minimal, informative.
|
|
201
|
-
self._progress = self._Progress(
|
|
202
|
-
self._SpinnerColumn(style="cyan"),
|
|
203
|
-
self._TextColumn("[bold]{task.description}"),
|
|
204
|
-
self._BarColumn(bar_width=24, complete_style="cyan", finished_style="green"),
|
|
205
|
-
self._TaskProgressColumn(),
|
|
206
|
-
self._TimeElapsedColumn(),
|
|
207
|
-
self._TimeRemainingColumn(compact=True),
|
|
208
|
-
console=self._console,
|
|
209
|
-
)
|
|
210
|
-
self._task_id = self._progress.add_task("Working…", total=100, completed=0)
|
|
211
|
-
|
|
212
|
-
if self._live is None:
|
|
213
|
-
self._live = self._Live(self._progress, console=self._console, refresh_per_second=12)
|
|
214
|
-
self._live.start()
|
|
215
|
-
|
|
216
|
-
def _stop_progress(self) -> None:
|
|
217
|
-
if self._live is not None:
|
|
218
|
-
try:
|
|
219
|
-
self._live.stop()
|
|
220
|
-
finally:
|
|
221
|
-
self._live = None
|
|
222
|
-
|
|
223
|
-
self._progress = None
|
|
224
|
-
self._task_id = None
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
class ProgressTracker:
|
|
228
|
-
"""Tracks progress for multi-step operations."""
|
|
229
|
-
|
|
230
|
-
def __init__(
|
|
231
|
-
self,
|
|
232
|
-
total_steps: int,
|
|
233
|
-
operation_name: str = "Operation",
|
|
234
|
-
feedback_manager: "FeedbackManager | None" = None,
|
|
235
|
-
):
|
|
236
|
-
"""
|
|
237
|
-
Initialize progress tracker.
|
|
238
|
-
|
|
239
|
-
Args:
|
|
240
|
-
total_steps: Total number of steps
|
|
241
|
-
operation_name: Name of the operation
|
|
242
|
-
feedback_manager: FeedbackManager instance for output
|
|
243
|
-
"""
|
|
244
|
-
self.total_steps = total_steps
|
|
245
|
-
self.current_step = 0
|
|
246
|
-
self.operation_name = operation_name
|
|
247
|
-
self.feedback = feedback_manager or FeedbackManager.get_instance()
|
|
248
|
-
self.start_time = time.time()
|
|
249
|
-
self.last_update_time = 0
|
|
250
|
-
self.update_interval = 1.0 # Minimum seconds between updates
|
|
251
|
-
|
|
252
|
-
def update(self, step: int | None = None, message: str | None = None) -> None:
|
|
253
|
-
"""
|
|
254
|
-
Update progress.
|
|
255
|
-
|
|
256
|
-
Args:
|
|
257
|
-
step: Current step number (if None, increments by 1)
|
|
258
|
-
message: Optional message for this step
|
|
259
|
-
"""
|
|
260
|
-
if step is not None:
|
|
261
|
-
self.current_step = step
|
|
262
|
-
else:
|
|
263
|
-
self.current_step += 1
|
|
264
|
-
|
|
265
|
-
current_time = time.time()
|
|
266
|
-
elapsed = current_time - self.start_time
|
|
267
|
-
|
|
268
|
-
# Throttle updates
|
|
269
|
-
if current_time - self.last_update_time < self.update_interval:
|
|
270
|
-
return
|
|
271
|
-
|
|
272
|
-
self.last_update_time = current_time
|
|
273
|
-
|
|
274
|
-
percentage = int((self.current_step / self.total_steps) * 100) if self.total_steps > 0 else 0
|
|
275
|
-
|
|
276
|
-
# Determine if we should show progress based on elapsed time
|
|
277
|
-
if elapsed < 5:
|
|
278
|
-
# Quick operation - no progress bar needed
|
|
279
|
-
if self.feedback.verbosity == VerbosityLevel.VERBOSE:
|
|
280
|
-
self.feedback.progress(
|
|
281
|
-
f"{self.operation_name}... (step {self.current_step}/{self.total_steps})",
|
|
282
|
-
show_progress_bar=False,
|
|
283
|
-
)
|
|
284
|
-
elif elapsed < 30:
|
|
285
|
-
# Medium operation - simple progress
|
|
286
|
-
self.feedback.progress(
|
|
287
|
-
f"{self.operation_name}... (step {self.current_step}/{self.total_steps})",
|
|
288
|
-
show_progress_bar=False,
|
|
289
|
-
)
|
|
290
|
-
else:
|
|
291
|
-
# Long operation - detailed progress bar
|
|
292
|
-
self.feedback.progress(
|
|
293
|
-
f"{self.operation_name}... (step {self.current_step}/{self.total_steps})",
|
|
294
|
-
percentage=percentage,
|
|
295
|
-
show_progress_bar=True,
|
|
296
|
-
)
|
|
297
|
-
|
|
298
|
-
def complete(self, message: str | None = None) -> None:
|
|
299
|
-
"""
|
|
300
|
-
Mark progress as complete.
|
|
301
|
-
|
|
302
|
-
Args:
|
|
303
|
-
message: Optional completion message
|
|
304
|
-
"""
|
|
305
|
-
self.current_step = self.total_steps
|
|
306
|
-
elapsed = time.time() - self.start_time
|
|
307
|
-
if message:
|
|
308
|
-
self.feedback.success(f"{self.operation_name} completed: {message} (took {elapsed:.1f}s)")
|
|
309
|
-
else:
|
|
310
|
-
self.feedback.success(f"{self.operation_name} completed (took {elapsed:.1f}s)")
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
class FeedbackManager:
|
|
314
|
-
"""Manages CLI feedback output with verbosity control."""
|
|
315
|
-
|
|
316
|
-
_instance: "FeedbackManager | None" = None
|
|
317
|
-
_verbosity: VerbosityLevel = VerbosityLevel.NORMAL
|
|
318
|
-
_format_type: str = "text"
|
|
319
|
-
_progress_mode: ProgressMode = ProgressMode.AUTO
|
|
320
|
-
|
|
321
|
-
def __init__(self):
|
|
322
|
-
"""Initialize feedback manager."""
|
|
323
|
-
self.verbosity = FeedbackManager._verbosity
|
|
324
|
-
self.format_type = FeedbackManager._format_type
|
|
325
|
-
self.progress_mode = FeedbackManager._progress_mode
|
|
326
|
-
self.operation_start_time: float | None = None
|
|
327
|
-
|
|
328
|
-
self._plain_spinner = _PlainSpinner()
|
|
329
|
-
self._rich: _RichProgressRenderer | None = None
|
|
330
|
-
self._heartbeat: Any | None = None # ProgressHeartbeat instance
|
|
331
|
-
|
|
332
|
-
@classmethod
|
|
333
|
-
def get_instance(cls) -> "FeedbackManager":
|
|
334
|
-
"""Get or create singleton instance."""
|
|
335
|
-
if cls._instance is None:
|
|
336
|
-
cls._instance = cls()
|
|
337
|
-
return cls._instance
|
|
338
|
-
|
|
339
|
-
@classmethod
|
|
340
|
-
def set_verbosity(cls, verbosity: VerbosityLevel) -> None:
|
|
341
|
-
"""Set global verbosity level."""
|
|
342
|
-
cls._verbosity = verbosity
|
|
343
|
-
if cls._instance:
|
|
344
|
-
cls._instance.verbosity = verbosity
|
|
345
|
-
|
|
346
|
-
@classmethod
|
|
347
|
-
def set_format(cls, format_type: str) -> None:
|
|
348
|
-
"""Set global output format."""
|
|
349
|
-
cls._format_type = format_type
|
|
350
|
-
if cls._instance:
|
|
351
|
-
cls._instance.format_type = format_type
|
|
352
|
-
|
|
353
|
-
@classmethod
|
|
354
|
-
def set_progress_mode(cls, progress_mode: ProgressMode) -> None:
|
|
355
|
-
"""Set global progress rendering mode."""
|
|
356
|
-
cls._progress_mode = progress_mode
|
|
357
|
-
if cls._instance:
|
|
358
|
-
cls._instance.progress_mode = progress_mode
|
|
359
|
-
|
|
360
|
-
@classmethod
|
|
361
|
-
def get_verbosity(cls) -> VerbosityLevel:
|
|
362
|
-
"""Get current verbosity level."""
|
|
363
|
-
return cls._verbosity
|
|
364
|
-
|
|
365
|
-
def info(self, message: str, details: dict[str, Any] | None = None) -> None:
|
|
366
|
-
"""
|
|
367
|
-
Output informational message.
|
|
368
|
-
|
|
369
|
-
Args:
|
|
370
|
-
message: Message text
|
|
371
|
-
details: Optional additional details
|
|
372
|
-
"""
|
|
373
|
-
if self.verbosity == VerbosityLevel.QUIET:
|
|
374
|
-
return
|
|
375
|
-
|
|
376
|
-
# Status messages always go to stderr as plain text (even in JSON mode)
|
|
377
|
-
# This prevents PowerShell from trying to parse JSON status messages
|
|
378
|
-
# Only final results go to stdout as JSON
|
|
379
|
-
prefix = "[INFO] " if self.verbosity == VerbosityLevel.VERBOSE else ""
|
|
380
|
-
print(f"{prefix}{message}", file=sys.stderr)
|
|
381
|
-
if details and self.verbosity == VerbosityLevel.VERBOSE:
|
|
382
|
-
for key, value in details.items():
|
|
383
|
-
print(f" {key}: {value}", file=sys.stderr)
|
|
384
|
-
|
|
385
|
-
def _stop_heartbeat(self) -> None:
|
|
386
|
-
"""Stop the heartbeat if it's running."""
|
|
387
|
-
if self._heartbeat:
|
|
388
|
-
try:
|
|
389
|
-
self._heartbeat.stop()
|
|
390
|
-
except Exception:
|
|
391
|
-
pass
|
|
392
|
-
finally:
|
|
393
|
-
self._heartbeat = None
|
|
394
|
-
|
|
395
|
-
def success(
|
|
396
|
-
self,
|
|
397
|
-
message: str,
|
|
398
|
-
data: dict[str, Any] | None = None,
|
|
399
|
-
summary: dict[str, Any] | None = None,
|
|
400
|
-
) -> None:
|
|
401
|
-
"""
|
|
402
|
-
Output success message.
|
|
403
|
-
|
|
404
|
-
Args:
|
|
405
|
-
message: Success message
|
|
406
|
-
data: Optional result data
|
|
407
|
-
summary: Optional summary metrics
|
|
408
|
-
"""
|
|
409
|
-
if self.verbosity == VerbosityLevel.QUIET and data is None:
|
|
410
|
-
return
|
|
411
|
-
|
|
412
|
-
# Stop heartbeat when operation completes
|
|
413
|
-
self._stop_heartbeat()
|
|
414
|
-
|
|
415
|
-
# Calculate duration
|
|
416
|
-
duration_str = ""
|
|
417
|
-
if self.operation_start_time:
|
|
418
|
-
duration = time.time() - self.operation_start_time
|
|
419
|
-
if duration < 1:
|
|
420
|
-
duration_str = f" ({duration*1000:.0f}ms)"
|
|
421
|
-
elif duration < 60:
|
|
422
|
-
duration_str = f" ({duration:.1f}s)"
|
|
423
|
-
else:
|
|
424
|
-
minutes = int(duration // 60)
|
|
425
|
-
seconds = int(duration % 60)
|
|
426
|
-
duration_str = f" ({minutes}m {seconds}s)"
|
|
427
|
-
|
|
428
|
-
if self.format_type == "json":
|
|
429
|
-
output = {
|
|
430
|
-
"success": True,
|
|
431
|
-
"message": message,
|
|
432
|
-
}
|
|
433
|
-
if data:
|
|
434
|
-
output["data"] = data
|
|
435
|
-
if summary:
|
|
436
|
-
output["summary"] = summary
|
|
437
|
-
if self.operation_start_time:
|
|
438
|
-
duration_ms = int((time.time() - self.operation_start_time) * 1000)
|
|
439
|
-
output["metadata"] = {
|
|
440
|
-
"timestamp": datetime.now(UTC).isoformat(),
|
|
441
|
-
"duration_ms": duration_ms,
|
|
442
|
-
"version": PACKAGE_VERSION,
|
|
443
|
-
}
|
|
444
|
-
# Show success indicator to stderr, then JSON result to stdout
|
|
445
|
-
if self.verbosity != VerbosityLevel.QUIET:
|
|
446
|
-
print(f"[SUCCESS] {message}{duration_str}", file=sys.stderr)
|
|
447
|
-
print(json.dumps(output, indent=2), file=sys.stdout)
|
|
448
|
-
sys.stdout.flush()
|
|
449
|
-
else:
|
|
450
|
-
# Text format
|
|
451
|
-
print(f"[SUCCESS] {message}{duration_str}", file=sys.stderr)
|
|
452
|
-
if summary and self.verbosity != VerbosityLevel.QUIET:
|
|
453
|
-
for key, value in summary.items():
|
|
454
|
-
print(f" {key}: {value}", file=sys.stderr)
|
|
455
|
-
# Data goes to stdout for parsing (only in quiet mode or if explicitly requested)
|
|
456
|
-
if data and self.verbosity == VerbosityLevel.QUIET:
|
|
457
|
-
print(json.dumps(data, indent=2), file=sys.stdout)
|
|
458
|
-
sys.stdout.flush()
|
|
459
|
-
|
|
460
|
-
def warning(self, message: str, remediation: str | None = None) -> None:
|
|
461
|
-
"""
|
|
462
|
-
Output warning message.
|
|
463
|
-
|
|
464
|
-
Args:
|
|
465
|
-
message: Warning message
|
|
466
|
-
remediation: Optional remediation suggestion
|
|
467
|
-
"""
|
|
468
|
-
if self.verbosity == VerbosityLevel.QUIET:
|
|
469
|
-
return
|
|
470
|
-
|
|
471
|
-
# Warnings always go to stderr as plain text (even in JSON mode)
|
|
472
|
-
print(f"[WARN] Warning: {message}", file=sys.stderr)
|
|
473
|
-
if remediation:
|
|
474
|
-
print(f" Suggestion: {remediation}", file=sys.stderr)
|
|
475
|
-
|
|
476
|
-
def error(
|
|
477
|
-
self,
|
|
478
|
-
message: str,
|
|
479
|
-
error_code: str = "error",
|
|
480
|
-
context: dict[str, Any] | None = None,
|
|
481
|
-
remediation: str | None = None,
|
|
482
|
-
exit_code: int = 1,
|
|
483
|
-
) -> None:
|
|
484
|
-
"""
|
|
485
|
-
Output error message and exit.
|
|
486
|
-
|
|
487
|
-
Args:
|
|
488
|
-
message: Error message
|
|
489
|
-
error_code: Error code identifier
|
|
490
|
-
context: Optional context details
|
|
491
|
-
remediation: Optional remediation suggestion
|
|
492
|
-
exit_code: Exit code to use
|
|
493
|
-
"""
|
|
494
|
-
# Stop heartbeat on error
|
|
495
|
-
self._stop_heartbeat()
|
|
496
|
-
|
|
497
|
-
# Enhanced diagnostics for path-related errors
|
|
498
|
-
is_path_error = (
|
|
499
|
-
"path" in error_code.lower() or
|
|
500
|
-
"path.relative()" in message or
|
|
501
|
-
"relative path" in message.lower() or
|
|
502
|
-
"absolute path" in message.lower()
|
|
503
|
-
)
|
|
504
|
-
|
|
505
|
-
# Errors always go to stderr as plain text for visibility
|
|
506
|
-
# In JSON mode, also output structured error to stdout for parsing
|
|
507
|
-
print(f"[ERROR] {error_code}: {message}", file=sys.stderr)
|
|
508
|
-
|
|
509
|
-
# Enhanced context display for path errors
|
|
510
|
-
if context:
|
|
511
|
-
if is_path_error:
|
|
512
|
-
print("\nPath Error Details:", file=sys.stderr)
|
|
513
|
-
# Show received path if available
|
|
514
|
-
if "received_path" in context or "path" in context:
|
|
515
|
-
received = context.get("received_path") or context.get("path", "unknown")
|
|
516
|
-
print(f" Received: {received}", file=sys.stderr)
|
|
517
|
-
# Show project root if available
|
|
518
|
-
if "project_root" in context:
|
|
519
|
-
print(f" Project root: {context['project_root']}", file=sys.stderr)
|
|
520
|
-
# Show expected format
|
|
521
|
-
if "expected_format" in context:
|
|
522
|
-
print(f" Expected: {context['expected_format']}", file=sys.stderr)
|
|
523
|
-
# Show other context
|
|
524
|
-
for key, value in context.items():
|
|
525
|
-
if key not in ("received_path", "path", "project_root", "expected_format"):
|
|
526
|
-
print(f" {key}: {value}", file=sys.stderr)
|
|
527
|
-
else:
|
|
528
|
-
# Standard context display
|
|
529
|
-
for key, value in context.items():
|
|
530
|
-
print(f" {key}: {value}", file=sys.stderr)
|
|
531
|
-
|
|
532
|
-
if remediation:
|
|
533
|
-
print(f" Suggestion: {remediation}", file=sys.stderr)
|
|
534
|
-
|
|
535
|
-
# Additional path error guidance
|
|
536
|
-
if is_path_error and not remediation:
|
|
537
|
-
print("\nPath Error Guidance:", file=sys.stderr)
|
|
538
|
-
print(" • Use relative paths: 'src/file.py' instead of absolute paths", file=sys.stderr)
|
|
539
|
-
print(" • Run commands from project root directory", file=sys.stderr)
|
|
540
|
-
print(" • Paths are automatically normalized - try again", file=sys.stderr)
|
|
541
|
-
|
|
542
|
-
if self.format_type == "json":
|
|
543
|
-
output = {
|
|
544
|
-
"success": False,
|
|
545
|
-
"error": {
|
|
546
|
-
"code": error_code,
|
|
547
|
-
"message": message,
|
|
548
|
-
},
|
|
549
|
-
}
|
|
550
|
-
if context:
|
|
551
|
-
output["error"]["context"] = context
|
|
552
|
-
if remediation:
|
|
553
|
-
output["error"]["remediation"] = remediation
|
|
554
|
-
print(json.dumps(output, indent=2), file=sys.stdout)
|
|
555
|
-
sys.stdout.flush()
|
|
556
|
-
|
|
557
|
-
sys.exit(exit_code)
|
|
558
|
-
|
|
559
|
-
def progress(
|
|
560
|
-
self,
|
|
561
|
-
message: str,
|
|
562
|
-
percentage: int | None = None,
|
|
563
|
-
show_progress_bar: bool = False,
|
|
564
|
-
) -> None:
|
|
565
|
-
"""
|
|
566
|
-
Output progress message.
|
|
567
|
-
|
|
568
|
-
Args:
|
|
569
|
-
message: Progress message
|
|
570
|
-
percentage: Optional percentage (0-100)
|
|
571
|
-
show_progress_bar: Whether to show progress bar
|
|
572
|
-
"""
|
|
573
|
-
if self.verbosity == VerbosityLevel.QUIET:
|
|
574
|
-
return
|
|
575
|
-
|
|
576
|
-
# Progress updates always go to stderr as plain text (even in JSON mode)
|
|
577
|
-
# This prevents PowerShell from trying to parse JSON progress messages
|
|
578
|
-
# Only final results go to stdout as JSON
|
|
579
|
-
mode = self._resolve_progress_mode()
|
|
580
|
-
if mode == ProgressMode.OFF:
|
|
581
|
-
return
|
|
582
|
-
|
|
583
|
-
if mode == ProgressMode.RICH:
|
|
584
|
-
try:
|
|
585
|
-
if self._rich is None:
|
|
586
|
-
self._rich = _RichProgressRenderer()
|
|
587
|
-
self._rich.update(message, percentage, show_progress_bar)
|
|
588
|
-
return
|
|
589
|
-
except Exception:
|
|
590
|
-
# Never fail a command due to progress rendering.
|
|
591
|
-
self._rich = None
|
|
592
|
-
mode = ProgressMode.PLAIN
|
|
593
|
-
|
|
594
|
-
# Plain text fallback - use ASCII-safe characters for Windows compatibility
|
|
595
|
-
from ..core.unicode_safe import safe_print, safe_format_progress_bar
|
|
596
|
-
|
|
597
|
-
spinner = self._plain_spinner.next()
|
|
598
|
-
if show_progress_bar and percentage is not None:
|
|
599
|
-
bar = safe_format_progress_bar(percentage, width=24)
|
|
600
|
-
safe_print(f"{spinner} {message} {bar}", file=sys.stderr, end="\r")
|
|
601
|
-
sys.stderr.flush()
|
|
602
|
-
else:
|
|
603
|
-
safe_print(f"{spinner} {message}", file=sys.stderr, end="\r")
|
|
604
|
-
sys.stderr.flush()
|
|
605
|
-
|
|
606
|
-
def clear_progress(self) -> None:
|
|
607
|
-
"""Clear the current progress line."""
|
|
608
|
-
if self.verbosity != VerbosityLevel.QUIET:
|
|
609
|
-
if self._rich is not None:
|
|
610
|
-
try:
|
|
611
|
-
self._rich.clear()
|
|
612
|
-
except Exception:
|
|
613
|
-
pass
|
|
614
|
-
finally:
|
|
615
|
-
self._rich = None
|
|
616
|
-
print("", file=sys.stderr) # New line to clear progress
|
|
617
|
-
|
|
618
|
-
def _resolve_progress_mode(self) -> ProgressMode:
|
|
619
|
-
"""
|
|
620
|
-
Decide which progress renderer to use for this invocation.
|
|
621
|
-
|
|
622
|
-
Rules:
|
|
623
|
-
- JSON output: keep progress as JSON events (handled earlier)
|
|
624
|
-
- Quiet: already handled earlier
|
|
625
|
-
- OFF: explicit disable
|
|
626
|
-
- AUTO: Rich when interactive TTY and not on CI / explicitly disabled
|
|
627
|
-
"""
|
|
628
|
-
|
|
629
|
-
mode = self.progress_mode
|
|
630
|
-
if mode == ProgressMode.AUTO:
|
|
631
|
-
if not _is_interactive_tty() or _should_disable_animated_progress():
|
|
632
|
-
return ProgressMode.PLAIN
|
|
633
|
-
return ProgressMode.RICH
|
|
634
|
-
return mode
|
|
635
|
-
|
|
636
|
-
def output_result(
|
|
637
|
-
self,
|
|
638
|
-
data: dict[str, Any] | str,
|
|
639
|
-
message: str | None = None,
|
|
640
|
-
warnings: list[str] | None = None,
|
|
641
|
-
compact: bool = True, # Default to compact mode to prevent Cursor crashes
|
|
642
|
-
**kwargs: Any, # Accept additional kwargs for backward compatibility
|
|
643
|
-
) -> None:
|
|
644
|
-
"""
|
|
645
|
-
Output final result data.
|
|
646
|
-
|
|
647
|
-
Args:
|
|
648
|
-
data: Result data (dict for JSON, str for text)
|
|
649
|
-
message: Optional summary message
|
|
650
|
-
warnings: Optional list of warnings
|
|
651
|
-
compact: If True (default), remove verbose debug data to prevent large output
|
|
652
|
-
"""
|
|
653
|
-
# Apply output compaction to prevent overwhelming Cursor's terminal
|
|
654
|
-
# Large outputs can cause Cursor to crash with "Connection Error"
|
|
655
|
-
if compact and isinstance(data, dict):
|
|
656
|
-
from .output_compactor import clean_debug_output, limit_output_size
|
|
657
|
-
data = clean_debug_output(data)
|
|
658
|
-
data = limit_output_size(data)
|
|
659
|
-
|
|
660
|
-
if self.format_type == "json":
|
|
661
|
-
output: dict[str, Any] = {
|
|
662
|
-
"success": True,
|
|
663
|
-
}
|
|
664
|
-
if message:
|
|
665
|
-
output["message"] = message
|
|
666
|
-
if isinstance(data, dict):
|
|
667
|
-
output["data"] = data
|
|
668
|
-
else:
|
|
669
|
-
output["data"] = {"content": data}
|
|
670
|
-
if warnings:
|
|
671
|
-
output["warnings"] = warnings
|
|
672
|
-
if self.operation_start_time:
|
|
673
|
-
duration_ms = int((time.time() - self.operation_start_time) * 1000)
|
|
674
|
-
output["metadata"] = {
|
|
675
|
-
"timestamp": datetime.now(UTC).isoformat(),
|
|
676
|
-
"duration_ms": duration_ms,
|
|
677
|
-
"version": PACKAGE_VERSION,
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
# Issue 8 Fix: Detect instruction objects and add Cursor AI execution directive
|
|
681
|
-
# When an instruction object is present, add clear directives for Cursor AI
|
|
682
|
-
data_dict = output.get("data", {})
|
|
683
|
-
if isinstance(data_dict, dict) and "instruction" in data_dict:
|
|
684
|
-
instruction = data_dict.get("instruction", {})
|
|
685
|
-
|
|
686
|
-
# Extract file path - handle different instruction types:
|
|
687
|
-
# 1. TestGenerationInstruction: target_file (test file path for tester instructions)
|
|
688
|
-
# 2. CodeGenerationInstruction: file_path
|
|
689
|
-
# 3. GenericInstruction: parameters.file_path
|
|
690
|
-
# 4. Tester agent result: test_file (test file path, not source)
|
|
691
|
-
# 5. Fallback: file from data_dict
|
|
692
|
-
# Note: For tester instructions, target_file points to test file (not source file)
|
|
693
|
-
# TODO: Store source file path in tester agent result for better traceability
|
|
694
|
-
file_path = (
|
|
695
|
-
instruction.get("target_file") or # TestGenerationInstruction (test file)
|
|
696
|
-
instruction.get("file_path") or # CodeGenerationInstruction
|
|
697
|
-
data_dict.get("test_file") or # Tester agent result (test file)
|
|
698
|
-
instruction.get("parameters", {}).get("file_path") or # GenericInstruction
|
|
699
|
-
data_dict.get("file") or # Fallback
|
|
700
|
-
"unknown"
|
|
701
|
-
)
|
|
702
|
-
command = instruction.get("command", "improve")
|
|
703
|
-
agent_name = instruction.get("agent_name", "improver")
|
|
704
|
-
|
|
705
|
-
# Print clear text directive to stderr BEFORE JSON (Cursor AI sees this in chat)
|
|
706
|
-
# This helps Cursor AI recognize that it should execute the instruction
|
|
707
|
-
print("\n" + "=" * 80, file=sys.stderr)
|
|
708
|
-
print("⚠️ CURSOR AI: EXECUTION REQUIRED", file=sys.stderr)
|
|
709
|
-
print("=" * 80, file=sys.stderr)
|
|
710
|
-
print(f"\nThis command returned an instruction object that YOU MUST EXECUTE.", file=sys.stderr)
|
|
711
|
-
print(f"\nDO NOT just return this JSON - you must:", file=sys.stderr)
|
|
712
|
-
print(f" 1. Read the file: {file_path}", file=sys.stderr)
|
|
713
|
-
print(f" 2. Extract the 'prompt' field from the instruction object below", file=sys.stderr)
|
|
714
|
-
print(f" 3. Use that prompt as your directive to improve/refactor/optimize the code", file=sys.stderr)
|
|
715
|
-
print(f" 4. Actually edit the file with improvements", file=sys.stderr)
|
|
716
|
-
print(f" 5. Explain what improvements you made and why", file=sys.stderr)
|
|
717
|
-
print(f"\nThe instruction is in: data.instruction.prompt", file=sys.stderr)
|
|
718
|
-
print(f"Execute using: @{agent_name} *{command} or follow the prompt directly.", file=sys.stderr)
|
|
719
|
-
print("=" * 80 + "\n", file=sys.stderr)
|
|
720
|
-
sys.stderr.flush()
|
|
721
|
-
|
|
722
|
-
# Also add execution directive in JSON for programmatic use
|
|
723
|
-
output["_cursor_execution_directive"] = {
|
|
724
|
-
"action": "execute_instruction",
|
|
725
|
-
"description": "This result contains an instruction object that must be executed. DO NOT just return this JSON - you must execute the instruction by improving the code.",
|
|
726
|
-
"steps": [
|
|
727
|
-
f"1. Read the file: {file_path}",
|
|
728
|
-
"2. Extract the 'prompt' field from data.instruction as your improvement directive",
|
|
729
|
-
"3. Actually improve/refactor/optimize the code by editing the file according to the prompt",
|
|
730
|
-
"4. Apply all improvements (best practices, type hints, documentation, etc.)",
|
|
731
|
-
"5. Explain what improvements you made and why",
|
|
732
|
-
],
|
|
733
|
-
"critical": "DO NOT just return this instruction object - you must execute it by improving the code!",
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
# JSON output always goes to stdout
|
|
737
|
-
print(json.dumps(output, indent=2), file=sys.stdout)
|
|
738
|
-
sys.stdout.flush()
|
|
739
|
-
else:
|
|
740
|
-
# Text format
|
|
741
|
-
if message and self.verbosity != VerbosityLevel.QUIET:
|
|
742
|
-
print(f"\n{message}", file=sys.stderr)
|
|
743
|
-
sys.stderr.flush()
|
|
744
|
-
if warnings and self.verbosity != VerbosityLevel.QUIET:
|
|
745
|
-
for warning in warnings:
|
|
746
|
-
print(f"[WARN] {warning}", file=sys.stderr)
|
|
747
|
-
sys.stderr.flush()
|
|
748
|
-
# Data goes to stdout
|
|
749
|
-
if isinstance(data, dict):
|
|
750
|
-
# For text mode, format dict nicely
|
|
751
|
-
if self.verbosity == VerbosityLevel.QUIET:
|
|
752
|
-
# In quiet mode, just output minimal data
|
|
753
|
-
print(json.dumps(data, indent=2), file=sys.stdout)
|
|
754
|
-
else:
|
|
755
|
-
# Format for readability
|
|
756
|
-
for key, value in data.items():
|
|
757
|
-
if isinstance(value, (dict, list)):
|
|
758
|
-
print(f"{key}:", file=sys.stdout)
|
|
759
|
-
print(json.dumps(value, indent=2), file=sys.stdout)
|
|
760
|
-
else:
|
|
761
|
-
print(f"{key}: {value}", file=sys.stdout)
|
|
762
|
-
else:
|
|
763
|
-
print(data, file=sys.stdout)
|
|
764
|
-
sys.stdout.flush()
|
|
765
|
-
|
|
766
|
-
def start_operation(self, operation_name: str, description: str | None = None) -> None:
|
|
767
|
-
"""
|
|
768
|
-
Start timing an operation and optionally start a heartbeat.
|
|
769
|
-
|
|
770
|
-
Args:
|
|
771
|
-
operation_name: Name of the operation
|
|
772
|
-
description: Optional description of what the operation does
|
|
773
|
-
"""
|
|
774
|
-
self.operation_start_time = time.time()
|
|
775
|
-
|
|
776
|
-
# Always show clear START indicator (unless quiet mode)
|
|
777
|
-
if self.verbosity != VerbosityLevel.QUIET:
|
|
778
|
-
if description:
|
|
779
|
-
self.info(f"[START] {operation_name} - {description}")
|
|
780
|
-
else:
|
|
781
|
-
self.info(f"[START] {operation_name}...")
|
|
782
|
-
|
|
783
|
-
# Start heartbeat for long-running operations (after 2 seconds)
|
|
784
|
-
if self.verbosity != VerbosityLevel.QUIET:
|
|
785
|
-
try:
|
|
786
|
-
from .progress_heartbeat import ProgressHeartbeat
|
|
787
|
-
self._heartbeat = ProgressHeartbeat(
|
|
788
|
-
message=f"{operation_name}...",
|
|
789
|
-
start_delay=2.0,
|
|
790
|
-
update_interval=1.0,
|
|
791
|
-
feedback_manager=self,
|
|
792
|
-
)
|
|
793
|
-
self._heartbeat.start()
|
|
794
|
-
except Exception:
|
|
795
|
-
# Don't fail if heartbeat can't start
|
|
796
|
-
self._heartbeat = None
|
|
797
|
-
|
|
798
|
-
def get_duration_ms(self) -> int | None:
|
|
799
|
-
"""Get operation duration in milliseconds."""
|
|
800
|
-
if self.operation_start_time:
|
|
801
|
-
return int((time.time() - self.operation_start_time) * 1000)
|
|
802
|
-
return None
|
|
803
|
-
|
|
804
|
-
def running(self, message: str, step: int | None = None, total_steps: int | None = None) -> None:
|
|
805
|
-
"""
|
|
806
|
-
Output running status message.
|
|
807
|
-
|
|
808
|
-
Args:
|
|
809
|
-
message: Running status message
|
|
810
|
-
step: Current step number (optional)
|
|
811
|
-
total_steps: Total number of steps (optional)
|
|
812
|
-
"""
|
|
813
|
-
if self.verbosity == VerbosityLevel.QUIET:
|
|
814
|
-
return
|
|
815
|
-
|
|
816
|
-
# Format step information if provided
|
|
817
|
-
step_info = ""
|
|
818
|
-
if step is not None and total_steps is not None:
|
|
819
|
-
step_info = f" (step {step}/{total_steps})"
|
|
820
|
-
elif step is not None:
|
|
821
|
-
step_info = f" (step {step})"
|
|
822
|
-
|
|
823
|
-
# Running status always goes to stderr as plain text
|
|
824
|
-
print(f"[RUNNING] {message}{step_info}", file=sys.stderr)
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
def suggest_simple_mode(
|
|
828
|
-
task_type: str,
|
|
829
|
-
description: str | None = None,
|
|
830
|
-
file_path: str | None = None,
|
|
831
|
-
) -> None:
|
|
832
|
-
"""
|
|
833
|
-
Suggest using @simple-mode for better outcomes.
|
|
834
|
-
|
|
835
|
-
Call this at the start of CLI commands that could benefit from workflow orchestration.
|
|
836
|
-
|
|
837
|
-
Args:
|
|
838
|
-
task_type: Type of task - "build", "fix", "review", "test", "refactor"
|
|
839
|
-
description: Optional description for the task
|
|
840
|
-
file_path: Optional file path for the task
|
|
841
|
-
"""
|
|
842
|
-
feedback = FeedbackManager.get_instance()
|
|
843
|
-
|
|
844
|
-
# Don't show in quiet mode
|
|
845
|
-
if feedback.verbosity == VerbosityLevel.QUIET:
|
|
846
|
-
return
|
|
847
|
-
|
|
848
|
-
# Build the suggestion message
|
|
849
|
-
commands = {
|
|
850
|
-
"build": f'@simple-mode *build "{description or "your feature description"}"',
|
|
851
|
-
"implement": f'@simple-mode *build "{description or "your feature description"}"',
|
|
852
|
-
"fix": f'@simple-mode *fix {file_path or "<file>"} "{description or "error description"}"',
|
|
853
|
-
"debug": f'@simple-mode *fix {file_path or "<file>"} "{description or "error description"}"',
|
|
854
|
-
"review": f'@simple-mode *review {file_path or "<file>"}',
|
|
855
|
-
"test": f'@simple-mode *test {file_path or "<file>"}',
|
|
856
|
-
"refactor": f'@simple-mode *refactor {file_path or "<file>"}',
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
simple_mode_cmd = commands.get(task_type, f'@simple-mode *build "{description or "your task"}"')
|
|
860
|
-
|
|
861
|
-
# Print tip box
|
|
862
|
-
print(file=sys.stderr)
|
|
863
|
-
print("╭" + "─" * 68 + "╮", file=sys.stderr)
|
|
864
|
-
print("│ TIP: For better outcomes, use @simple-mode in Cursor IDE │", file=sys.stderr)
|
|
865
|
-
print("│ │", file=sys.stderr)
|
|
866
|
-
# Truncate command if too long
|
|
867
|
-
if len(simple_mode_cmd) > 60:
|
|
868
|
-
display_cmd = simple_mode_cmd[:57] + "..."
|
|
869
|
-
else:
|
|
870
|
-
display_cmd = simple_mode_cmd
|
|
871
|
-
print(f"│ {display_cmd:<62} │", file=sys.stderr)
|
|
872
|
-
print("│ │", file=sys.stderr)
|
|
873
|
-
print("│ This orchestrates a complete workflow with quality gates. │", file=sys.stderr)
|
|
874
|
-
print("╰" + "─" * 68 + "╯", file=sys.stderr)
|
|
875
|
-
print(file=sys.stderr)
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
# Convenience functions for easy access
|
|
879
|
-
def get_feedback() -> FeedbackManager:
|
|
880
|
-
"""Get the feedback manager instance."""
|
|
881
|
-
return FeedbackManager.get_instance()
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
def info(message: str, details: dict[str, Any] | None = None) -> None:
|
|
885
|
-
"""Output informational message."""
|
|
886
|
-
get_feedback().info(message, details)
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
def success(
|
|
890
|
-
message: str,
|
|
891
|
-
data: dict[str, Any] | None = None,
|
|
892
|
-
summary: dict[str, Any] | None = None,
|
|
893
|
-
) -> None:
|
|
894
|
-
"""Output success message."""
|
|
895
|
-
get_feedback().success(message, data, summary)
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
def warning(message: str, remediation: str | None = None) -> None:
|
|
899
|
-
"""Output warning message."""
|
|
900
|
-
get_feedback().warning(message, remediation)
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
def error(
|
|
904
|
-
message: str,
|
|
905
|
-
error_code: str = "error",
|
|
906
|
-
context: dict[str, Any] | None = None,
|
|
907
|
-
remediation: str | None = None,
|
|
908
|
-
exit_code: int = 1,
|
|
909
|
-
) -> None:
|
|
910
|
-
"""Output error message and exit."""
|
|
911
|
-
get_feedback().error(message, error_code, context, remediation, exit_code)
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
def progress(
|
|
915
|
-
message: str,
|
|
916
|
-
percentage: int | None = None,
|
|
917
|
-
show_progress_bar: bool = False,
|
|
918
|
-
) -> None:
|
|
919
|
-
"""Output progress message."""
|
|
920
|
-
get_feedback().progress(message, percentage, show_progress_bar)
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
def clear_progress() -> None:
|
|
924
|
-
"""Clear the current progress line."""
|
|
925
|
-
get_feedback().clear_progress()
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
def output_result(
|
|
929
|
-
data: dict[str, Any] | str,
|
|
930
|
-
message: str | None = None,
|
|
931
|
-
warnings: list[str] | None = None,
|
|
932
|
-
compact: bool = True,
|
|
933
|
-
) -> None:
|
|
934
|
-
"""Output final result data."""
|
|
935
|
-
get_feedback().output_result(data, message, warnings, compact=compact)
|
|
936
|
-
|
|
1
|
+
"""
|
|
2
|
+
Centralized feedback system for CLI commands.
|
|
3
|
+
|
|
4
|
+
Provides consistent, user-friendly output with support for quiet, normal, and verbose modes.
|
|
5
|
+
All CLI commands should use this module instead of direct print() calls.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
import time
|
|
13
|
+
from datetime import UTC, datetime
|
|
14
|
+
from enum import Enum
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
# Try to import version, with fallback to importlib.metadata
|
|
18
|
+
try:
|
|
19
|
+
from .. import __version__ as PACKAGE_VERSION
|
|
20
|
+
except (ImportError, AttributeError):
|
|
21
|
+
# Fallback: use importlib.metadata (standard library, Python 3.8+)
|
|
22
|
+
try:
|
|
23
|
+
from importlib.metadata import version
|
|
24
|
+
PACKAGE_VERSION = version("tapps-agents")
|
|
25
|
+
except Exception:
|
|
26
|
+
# Last resort: try reading from __init__.py directly
|
|
27
|
+
try:
|
|
28
|
+
import importlib.util
|
|
29
|
+
import pathlib
|
|
30
|
+
init_path = pathlib.Path(__file__).parent.parent / "__init__.py"
|
|
31
|
+
if init_path.exists():
|
|
32
|
+
spec = importlib.util.spec_from_file_location("tapps_agents_init", init_path)
|
|
33
|
+
if spec and spec.loader:
|
|
34
|
+
module = importlib.util.module_from_spec(spec)
|
|
35
|
+
spec.loader.exec_module(module)
|
|
36
|
+
PACKAGE_VERSION = getattr(module, "__version__", "unknown")
|
|
37
|
+
else:
|
|
38
|
+
PACKAGE_VERSION = "unknown"
|
|
39
|
+
else:
|
|
40
|
+
PACKAGE_VERSION = "unknown"
|
|
41
|
+
except Exception:
|
|
42
|
+
PACKAGE_VERSION = "unknown"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class VerbosityLevel(Enum):
|
|
46
|
+
"""Verbosity levels for CLI output."""
|
|
47
|
+
QUIET = "quiet"
|
|
48
|
+
NORMAL = "normal"
|
|
49
|
+
VERBOSE = "verbose"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ProgressMode(Enum):
|
|
53
|
+
"""Progress rendering modes for CLI output."""
|
|
54
|
+
|
|
55
|
+
AUTO = "auto"
|
|
56
|
+
OFF = "off"
|
|
57
|
+
PLAIN = "plain"
|
|
58
|
+
RICH = "rich"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _is_interactive_tty() -> bool:
|
|
62
|
+
"""
|
|
63
|
+
Best-effort detection of an interactive TTY.
|
|
64
|
+
|
|
65
|
+
Notes:
|
|
66
|
+
- We treat stderr as the "UI" stream for progress.
|
|
67
|
+
- On CI / non-interactive sessions we default to non-animated output.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
return bool(sys.stderr.isatty())
|
|
72
|
+
except Exception:
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _env_truthy(name: str) -> bool:
|
|
77
|
+
val = os.environ.get(name)
|
|
78
|
+
if val is None:
|
|
79
|
+
return False
|
|
80
|
+
return val.strip().lower() not in {"0", "false", "no", "off", ""}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _should_disable_animated_progress() -> bool:
|
|
84
|
+
"""
|
|
85
|
+
Prefer deterministic output on CI/log files, and honor common env flags.
|
|
86
|
+
|
|
87
|
+
References (patterns widely used by modern CLIs):
|
|
88
|
+
- NO_COLOR: disable color
|
|
89
|
+
- CI/GITHUB_ACTIONS: reduce animation/noise
|
|
90
|
+
- TAPPS_PROGRESS=off: explicit disable
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
if _env_truthy("CI") or _env_truthy("GITHUB_ACTIONS"):
|
|
94
|
+
return True
|
|
95
|
+
if _env_truthy("TAPPS_NO_PROGRESS") or _env_truthy("NO_PROGRESS"):
|
|
96
|
+
return True
|
|
97
|
+
progress_env = os.environ.get("TAPPS_PROGRESS", "").strip().lower()
|
|
98
|
+
if progress_env in {"off", "0", "false", "no"}:
|
|
99
|
+
return True
|
|
100
|
+
return False
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class _PlainSpinner:
|
|
104
|
+
"""A tiny spinner that advances on each render call (no background thread)."""
|
|
105
|
+
|
|
106
|
+
_frames = "|/-\\"
|
|
107
|
+
|
|
108
|
+
def __init__(self) -> None:
|
|
109
|
+
self._i = 0
|
|
110
|
+
|
|
111
|
+
def next(self) -> str:
|
|
112
|
+
ch = self._frames[self._i % len(self._frames)]
|
|
113
|
+
self._i += 1
|
|
114
|
+
return ch
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class _RichProgressRenderer:
|
|
118
|
+
"""
|
|
119
|
+
Rich-based renderer that supports:
|
|
120
|
+
- Indeterminate spinner (Status)
|
|
121
|
+
- Determinate progress bar (Progress + Live)
|
|
122
|
+
|
|
123
|
+
This is intentionally lightweight and driven by explicit update() calls,
|
|
124
|
+
so it works without threads and plays nicely with existing code paths.
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
def __init__(self) -> None:
|
|
128
|
+
from rich.console import Console
|
|
129
|
+
from rich.live import Live
|
|
130
|
+
from rich.progress import (
|
|
131
|
+
BarColumn,
|
|
132
|
+
Progress,
|
|
133
|
+
SpinnerColumn,
|
|
134
|
+
TaskProgressColumn,
|
|
135
|
+
TextColumn,
|
|
136
|
+
TimeElapsedColumn,
|
|
137
|
+
TimeRemainingColumn,
|
|
138
|
+
)
|
|
139
|
+
from rich.status import Status
|
|
140
|
+
|
|
141
|
+
self._Console = Console
|
|
142
|
+
self._Live = Live
|
|
143
|
+
self._Progress = Progress
|
|
144
|
+
self._Status = Status
|
|
145
|
+
|
|
146
|
+
self._BarColumn = BarColumn
|
|
147
|
+
self._SpinnerColumn = SpinnerColumn
|
|
148
|
+
self._TextColumn = TextColumn
|
|
149
|
+
self._TaskProgressColumn = TaskProgressColumn
|
|
150
|
+
self._TimeElapsedColumn = TimeElapsedColumn
|
|
151
|
+
self._TimeRemainingColumn = TimeRemainingColumn
|
|
152
|
+
|
|
153
|
+
self._console = Console(stderr=True, highlight=False, soft_wrap=True)
|
|
154
|
+
|
|
155
|
+
self._status: Status | None = None
|
|
156
|
+
self._progress: Progress | None = None
|
|
157
|
+
self._live: Live | None = None
|
|
158
|
+
self._task_id: int | None = None
|
|
159
|
+
|
|
160
|
+
def update(
|
|
161
|
+
self,
|
|
162
|
+
message: str,
|
|
163
|
+
percentage: int | None,
|
|
164
|
+
show_progress_bar: bool,
|
|
165
|
+
) -> None:
|
|
166
|
+
if show_progress_bar and percentage is not None:
|
|
167
|
+
self._stop_status()
|
|
168
|
+
self._ensure_progress()
|
|
169
|
+
assert self._progress is not None
|
|
170
|
+
assert self._task_id is not None
|
|
171
|
+
|
|
172
|
+
pct = max(0, min(100, int(percentage)))
|
|
173
|
+
self._progress.update(self._task_id, description=message, completed=pct)
|
|
174
|
+
return
|
|
175
|
+
|
|
176
|
+
# Indeterminate / spinner-only
|
|
177
|
+
self._stop_progress()
|
|
178
|
+
self._ensure_status(message)
|
|
179
|
+
assert self._status is not None
|
|
180
|
+
self._status.update(status=message)
|
|
181
|
+
|
|
182
|
+
def clear(self) -> None:
|
|
183
|
+
self._stop_status()
|
|
184
|
+
self._stop_progress()
|
|
185
|
+
|
|
186
|
+
def _ensure_status(self, message: str) -> None:
|
|
187
|
+
if self._status is None:
|
|
188
|
+
self._status = self._console.status(message, spinner="dots")
|
|
189
|
+
self._status.start()
|
|
190
|
+
|
|
191
|
+
def _stop_status(self) -> None:
|
|
192
|
+
if self._status is not None:
|
|
193
|
+
try:
|
|
194
|
+
self._status.stop()
|
|
195
|
+
finally:
|
|
196
|
+
self._status = None
|
|
197
|
+
|
|
198
|
+
def _ensure_progress(self) -> None:
|
|
199
|
+
if self._progress is None:
|
|
200
|
+
# “2025 modern” defaults: clean, minimal, informative.
|
|
201
|
+
self._progress = self._Progress(
|
|
202
|
+
self._SpinnerColumn(style="cyan"),
|
|
203
|
+
self._TextColumn("[bold]{task.description}"),
|
|
204
|
+
self._BarColumn(bar_width=24, complete_style="cyan", finished_style="green"),
|
|
205
|
+
self._TaskProgressColumn(),
|
|
206
|
+
self._TimeElapsedColumn(),
|
|
207
|
+
self._TimeRemainingColumn(compact=True),
|
|
208
|
+
console=self._console,
|
|
209
|
+
)
|
|
210
|
+
self._task_id = self._progress.add_task("Working…", total=100, completed=0)
|
|
211
|
+
|
|
212
|
+
if self._live is None:
|
|
213
|
+
self._live = self._Live(self._progress, console=self._console, refresh_per_second=12)
|
|
214
|
+
self._live.start()
|
|
215
|
+
|
|
216
|
+
def _stop_progress(self) -> None:
|
|
217
|
+
if self._live is not None:
|
|
218
|
+
try:
|
|
219
|
+
self._live.stop()
|
|
220
|
+
finally:
|
|
221
|
+
self._live = None
|
|
222
|
+
|
|
223
|
+
self._progress = None
|
|
224
|
+
self._task_id = None
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class ProgressTracker:
|
|
228
|
+
"""Tracks progress for multi-step operations."""
|
|
229
|
+
|
|
230
|
+
def __init__(
|
|
231
|
+
self,
|
|
232
|
+
total_steps: int,
|
|
233
|
+
operation_name: str = "Operation",
|
|
234
|
+
feedback_manager: "FeedbackManager | None" = None,
|
|
235
|
+
):
|
|
236
|
+
"""
|
|
237
|
+
Initialize progress tracker.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
total_steps: Total number of steps
|
|
241
|
+
operation_name: Name of the operation
|
|
242
|
+
feedback_manager: FeedbackManager instance for output
|
|
243
|
+
"""
|
|
244
|
+
self.total_steps = total_steps
|
|
245
|
+
self.current_step = 0
|
|
246
|
+
self.operation_name = operation_name
|
|
247
|
+
self.feedback = feedback_manager or FeedbackManager.get_instance()
|
|
248
|
+
self.start_time = time.time()
|
|
249
|
+
self.last_update_time = 0
|
|
250
|
+
self.update_interval = 1.0 # Minimum seconds between updates
|
|
251
|
+
|
|
252
|
+
def update(self, step: int | None = None, message: str | None = None) -> None:
|
|
253
|
+
"""
|
|
254
|
+
Update progress.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
step: Current step number (if None, increments by 1)
|
|
258
|
+
message: Optional message for this step
|
|
259
|
+
"""
|
|
260
|
+
if step is not None:
|
|
261
|
+
self.current_step = step
|
|
262
|
+
else:
|
|
263
|
+
self.current_step += 1
|
|
264
|
+
|
|
265
|
+
current_time = time.time()
|
|
266
|
+
elapsed = current_time - self.start_time
|
|
267
|
+
|
|
268
|
+
# Throttle updates
|
|
269
|
+
if current_time - self.last_update_time < self.update_interval:
|
|
270
|
+
return
|
|
271
|
+
|
|
272
|
+
self.last_update_time = current_time
|
|
273
|
+
|
|
274
|
+
percentage = int((self.current_step / self.total_steps) * 100) if self.total_steps > 0 else 0
|
|
275
|
+
|
|
276
|
+
# Determine if we should show progress based on elapsed time
|
|
277
|
+
if elapsed < 5:
|
|
278
|
+
# Quick operation - no progress bar needed
|
|
279
|
+
if self.feedback.verbosity == VerbosityLevel.VERBOSE:
|
|
280
|
+
self.feedback.progress(
|
|
281
|
+
f"{self.operation_name}... (step {self.current_step}/{self.total_steps})",
|
|
282
|
+
show_progress_bar=False,
|
|
283
|
+
)
|
|
284
|
+
elif elapsed < 30:
|
|
285
|
+
# Medium operation - simple progress
|
|
286
|
+
self.feedback.progress(
|
|
287
|
+
f"{self.operation_name}... (step {self.current_step}/{self.total_steps})",
|
|
288
|
+
show_progress_bar=False,
|
|
289
|
+
)
|
|
290
|
+
else:
|
|
291
|
+
# Long operation - detailed progress bar
|
|
292
|
+
self.feedback.progress(
|
|
293
|
+
f"{self.operation_name}... (step {self.current_step}/{self.total_steps})",
|
|
294
|
+
percentage=percentage,
|
|
295
|
+
show_progress_bar=True,
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
def complete(self, message: str | None = None) -> None:
|
|
299
|
+
"""
|
|
300
|
+
Mark progress as complete.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
message: Optional completion message
|
|
304
|
+
"""
|
|
305
|
+
self.current_step = self.total_steps
|
|
306
|
+
elapsed = time.time() - self.start_time
|
|
307
|
+
if message:
|
|
308
|
+
self.feedback.success(f"{self.operation_name} completed: {message} (took {elapsed:.1f}s)")
|
|
309
|
+
else:
|
|
310
|
+
self.feedback.success(f"{self.operation_name} completed (took {elapsed:.1f}s)")
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
class FeedbackManager:
|
|
314
|
+
"""Manages CLI feedback output with verbosity control."""
|
|
315
|
+
|
|
316
|
+
_instance: "FeedbackManager | None" = None
|
|
317
|
+
_verbosity: VerbosityLevel = VerbosityLevel.NORMAL
|
|
318
|
+
_format_type: str = "text"
|
|
319
|
+
_progress_mode: ProgressMode = ProgressMode.AUTO
|
|
320
|
+
|
|
321
|
+
def __init__(self):
|
|
322
|
+
"""Initialize feedback manager."""
|
|
323
|
+
self.verbosity = FeedbackManager._verbosity
|
|
324
|
+
self.format_type = FeedbackManager._format_type
|
|
325
|
+
self.progress_mode = FeedbackManager._progress_mode
|
|
326
|
+
self.operation_start_time: float | None = None
|
|
327
|
+
|
|
328
|
+
self._plain_spinner = _PlainSpinner()
|
|
329
|
+
self._rich: _RichProgressRenderer | None = None
|
|
330
|
+
self._heartbeat: Any | None = None # ProgressHeartbeat instance
|
|
331
|
+
|
|
332
|
+
@classmethod
|
|
333
|
+
def get_instance(cls) -> "FeedbackManager":
|
|
334
|
+
"""Get or create singleton instance."""
|
|
335
|
+
if cls._instance is None:
|
|
336
|
+
cls._instance = cls()
|
|
337
|
+
return cls._instance
|
|
338
|
+
|
|
339
|
+
@classmethod
|
|
340
|
+
def set_verbosity(cls, verbosity: VerbosityLevel) -> None:
|
|
341
|
+
"""Set global verbosity level."""
|
|
342
|
+
cls._verbosity = verbosity
|
|
343
|
+
if cls._instance:
|
|
344
|
+
cls._instance.verbosity = verbosity
|
|
345
|
+
|
|
346
|
+
@classmethod
|
|
347
|
+
def set_format(cls, format_type: str) -> None:
|
|
348
|
+
"""Set global output format."""
|
|
349
|
+
cls._format_type = format_type
|
|
350
|
+
if cls._instance:
|
|
351
|
+
cls._instance.format_type = format_type
|
|
352
|
+
|
|
353
|
+
@classmethod
|
|
354
|
+
def set_progress_mode(cls, progress_mode: ProgressMode) -> None:
|
|
355
|
+
"""Set global progress rendering mode."""
|
|
356
|
+
cls._progress_mode = progress_mode
|
|
357
|
+
if cls._instance:
|
|
358
|
+
cls._instance.progress_mode = progress_mode
|
|
359
|
+
|
|
360
|
+
@classmethod
|
|
361
|
+
def get_verbosity(cls) -> VerbosityLevel:
|
|
362
|
+
"""Get current verbosity level."""
|
|
363
|
+
return cls._verbosity
|
|
364
|
+
|
|
365
|
+
def info(self, message: str, details: dict[str, Any] | None = None) -> None:
|
|
366
|
+
"""
|
|
367
|
+
Output informational message.
|
|
368
|
+
|
|
369
|
+
Args:
|
|
370
|
+
message: Message text
|
|
371
|
+
details: Optional additional details
|
|
372
|
+
"""
|
|
373
|
+
if self.verbosity == VerbosityLevel.QUIET:
|
|
374
|
+
return
|
|
375
|
+
|
|
376
|
+
# Status messages always go to stderr as plain text (even in JSON mode)
|
|
377
|
+
# This prevents PowerShell from trying to parse JSON status messages
|
|
378
|
+
# Only final results go to stdout as JSON
|
|
379
|
+
prefix = "[INFO] " if self.verbosity == VerbosityLevel.VERBOSE else ""
|
|
380
|
+
print(f"{prefix}{message}", file=sys.stderr)
|
|
381
|
+
if details and self.verbosity == VerbosityLevel.VERBOSE:
|
|
382
|
+
for key, value in details.items():
|
|
383
|
+
print(f" {key}: {value}", file=sys.stderr)
|
|
384
|
+
|
|
385
|
+
def _stop_heartbeat(self) -> None:
|
|
386
|
+
"""Stop the heartbeat if it's running."""
|
|
387
|
+
if self._heartbeat:
|
|
388
|
+
try:
|
|
389
|
+
self._heartbeat.stop()
|
|
390
|
+
except Exception:
|
|
391
|
+
pass
|
|
392
|
+
finally:
|
|
393
|
+
self._heartbeat = None
|
|
394
|
+
|
|
395
|
+
def success(
|
|
396
|
+
self,
|
|
397
|
+
message: str,
|
|
398
|
+
data: dict[str, Any] | None = None,
|
|
399
|
+
summary: dict[str, Any] | None = None,
|
|
400
|
+
) -> None:
|
|
401
|
+
"""
|
|
402
|
+
Output success message.
|
|
403
|
+
|
|
404
|
+
Args:
|
|
405
|
+
message: Success message
|
|
406
|
+
data: Optional result data
|
|
407
|
+
summary: Optional summary metrics
|
|
408
|
+
"""
|
|
409
|
+
if self.verbosity == VerbosityLevel.QUIET and data is None:
|
|
410
|
+
return
|
|
411
|
+
|
|
412
|
+
# Stop heartbeat when operation completes
|
|
413
|
+
self._stop_heartbeat()
|
|
414
|
+
|
|
415
|
+
# Calculate duration
|
|
416
|
+
duration_str = ""
|
|
417
|
+
if self.operation_start_time:
|
|
418
|
+
duration = time.time() - self.operation_start_time
|
|
419
|
+
if duration < 1:
|
|
420
|
+
duration_str = f" ({duration*1000:.0f}ms)"
|
|
421
|
+
elif duration < 60:
|
|
422
|
+
duration_str = f" ({duration:.1f}s)"
|
|
423
|
+
else:
|
|
424
|
+
minutes = int(duration // 60)
|
|
425
|
+
seconds = int(duration % 60)
|
|
426
|
+
duration_str = f" ({minutes}m {seconds}s)"
|
|
427
|
+
|
|
428
|
+
if self.format_type == "json":
|
|
429
|
+
output = {
|
|
430
|
+
"success": True,
|
|
431
|
+
"message": message,
|
|
432
|
+
}
|
|
433
|
+
if data:
|
|
434
|
+
output["data"] = data
|
|
435
|
+
if summary:
|
|
436
|
+
output["summary"] = summary
|
|
437
|
+
if self.operation_start_time:
|
|
438
|
+
duration_ms = int((time.time() - self.operation_start_time) * 1000)
|
|
439
|
+
output["metadata"] = {
|
|
440
|
+
"timestamp": datetime.now(UTC).isoformat(),
|
|
441
|
+
"duration_ms": duration_ms,
|
|
442
|
+
"version": PACKAGE_VERSION,
|
|
443
|
+
}
|
|
444
|
+
# Show success indicator to stderr, then JSON result to stdout
|
|
445
|
+
if self.verbosity != VerbosityLevel.QUIET:
|
|
446
|
+
print(f"[SUCCESS] {message}{duration_str}", file=sys.stderr)
|
|
447
|
+
print(json.dumps(output, indent=2), file=sys.stdout)
|
|
448
|
+
sys.stdout.flush()
|
|
449
|
+
else:
|
|
450
|
+
# Text format
|
|
451
|
+
print(f"[SUCCESS] {message}{duration_str}", file=sys.stderr)
|
|
452
|
+
if summary and self.verbosity != VerbosityLevel.QUIET:
|
|
453
|
+
for key, value in summary.items():
|
|
454
|
+
print(f" {key}: {value}", file=sys.stderr)
|
|
455
|
+
# Data goes to stdout for parsing (only in quiet mode or if explicitly requested)
|
|
456
|
+
if data and self.verbosity == VerbosityLevel.QUIET:
|
|
457
|
+
print(json.dumps(data, indent=2), file=sys.stdout)
|
|
458
|
+
sys.stdout.flush()
|
|
459
|
+
|
|
460
|
+
def warning(self, message: str, remediation: str | None = None) -> None:
|
|
461
|
+
"""
|
|
462
|
+
Output warning message.
|
|
463
|
+
|
|
464
|
+
Args:
|
|
465
|
+
message: Warning message
|
|
466
|
+
remediation: Optional remediation suggestion
|
|
467
|
+
"""
|
|
468
|
+
if self.verbosity == VerbosityLevel.QUIET:
|
|
469
|
+
return
|
|
470
|
+
|
|
471
|
+
# Warnings always go to stderr as plain text (even in JSON mode)
|
|
472
|
+
print(f"[WARN] Warning: {message}", file=sys.stderr)
|
|
473
|
+
if remediation:
|
|
474
|
+
print(f" Suggestion: {remediation}", file=sys.stderr)
|
|
475
|
+
|
|
476
|
+
def error(
|
|
477
|
+
self,
|
|
478
|
+
message: str,
|
|
479
|
+
error_code: str = "error",
|
|
480
|
+
context: dict[str, Any] | None = None,
|
|
481
|
+
remediation: str | None = None,
|
|
482
|
+
exit_code: int = 1,
|
|
483
|
+
) -> None:
|
|
484
|
+
"""
|
|
485
|
+
Output error message and exit.
|
|
486
|
+
|
|
487
|
+
Args:
|
|
488
|
+
message: Error message
|
|
489
|
+
error_code: Error code identifier
|
|
490
|
+
context: Optional context details
|
|
491
|
+
remediation: Optional remediation suggestion
|
|
492
|
+
exit_code: Exit code to use
|
|
493
|
+
"""
|
|
494
|
+
# Stop heartbeat on error
|
|
495
|
+
self._stop_heartbeat()
|
|
496
|
+
|
|
497
|
+
# Enhanced diagnostics for path-related errors
|
|
498
|
+
is_path_error = (
|
|
499
|
+
"path" in error_code.lower() or
|
|
500
|
+
"path.relative()" in message or
|
|
501
|
+
"relative path" in message.lower() or
|
|
502
|
+
"absolute path" in message.lower()
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
# Errors always go to stderr as plain text for visibility
|
|
506
|
+
# In JSON mode, also output structured error to stdout for parsing
|
|
507
|
+
print(f"[ERROR] {error_code}: {message}", file=sys.stderr)
|
|
508
|
+
|
|
509
|
+
# Enhanced context display for path errors
|
|
510
|
+
if context:
|
|
511
|
+
if is_path_error:
|
|
512
|
+
print("\nPath Error Details:", file=sys.stderr)
|
|
513
|
+
# Show received path if available
|
|
514
|
+
if "received_path" in context or "path" in context:
|
|
515
|
+
received = context.get("received_path") or context.get("path", "unknown")
|
|
516
|
+
print(f" Received: {received}", file=sys.stderr)
|
|
517
|
+
# Show project root if available
|
|
518
|
+
if "project_root" in context:
|
|
519
|
+
print(f" Project root: {context['project_root']}", file=sys.stderr)
|
|
520
|
+
# Show expected format
|
|
521
|
+
if "expected_format" in context:
|
|
522
|
+
print(f" Expected: {context['expected_format']}", file=sys.stderr)
|
|
523
|
+
# Show other context
|
|
524
|
+
for key, value in context.items():
|
|
525
|
+
if key not in ("received_path", "path", "project_root", "expected_format"):
|
|
526
|
+
print(f" {key}: {value}", file=sys.stderr)
|
|
527
|
+
else:
|
|
528
|
+
# Standard context display
|
|
529
|
+
for key, value in context.items():
|
|
530
|
+
print(f" {key}: {value}", file=sys.stderr)
|
|
531
|
+
|
|
532
|
+
if remediation:
|
|
533
|
+
print(f" Suggestion: {remediation}", file=sys.stderr)
|
|
534
|
+
|
|
535
|
+
# Additional path error guidance
|
|
536
|
+
if is_path_error and not remediation:
|
|
537
|
+
print("\nPath Error Guidance:", file=sys.stderr)
|
|
538
|
+
print(" • Use relative paths: 'src/file.py' instead of absolute paths", file=sys.stderr)
|
|
539
|
+
print(" • Run commands from project root directory", file=sys.stderr)
|
|
540
|
+
print(" • Paths are automatically normalized - try again", file=sys.stderr)
|
|
541
|
+
|
|
542
|
+
if self.format_type == "json":
|
|
543
|
+
output = {
|
|
544
|
+
"success": False,
|
|
545
|
+
"error": {
|
|
546
|
+
"code": error_code,
|
|
547
|
+
"message": message,
|
|
548
|
+
},
|
|
549
|
+
}
|
|
550
|
+
if context:
|
|
551
|
+
output["error"]["context"] = context
|
|
552
|
+
if remediation:
|
|
553
|
+
output["error"]["remediation"] = remediation
|
|
554
|
+
print(json.dumps(output, indent=2), file=sys.stdout)
|
|
555
|
+
sys.stdout.flush()
|
|
556
|
+
|
|
557
|
+
sys.exit(exit_code)
|
|
558
|
+
|
|
559
|
+
def progress(
|
|
560
|
+
self,
|
|
561
|
+
message: str,
|
|
562
|
+
percentage: int | None = None,
|
|
563
|
+
show_progress_bar: bool = False,
|
|
564
|
+
) -> None:
|
|
565
|
+
"""
|
|
566
|
+
Output progress message.
|
|
567
|
+
|
|
568
|
+
Args:
|
|
569
|
+
message: Progress message
|
|
570
|
+
percentage: Optional percentage (0-100)
|
|
571
|
+
show_progress_bar: Whether to show progress bar
|
|
572
|
+
"""
|
|
573
|
+
if self.verbosity == VerbosityLevel.QUIET:
|
|
574
|
+
return
|
|
575
|
+
|
|
576
|
+
# Progress updates always go to stderr as plain text (even in JSON mode)
|
|
577
|
+
# This prevents PowerShell from trying to parse JSON progress messages
|
|
578
|
+
# Only final results go to stdout as JSON
|
|
579
|
+
mode = self._resolve_progress_mode()
|
|
580
|
+
if mode == ProgressMode.OFF:
|
|
581
|
+
return
|
|
582
|
+
|
|
583
|
+
if mode == ProgressMode.RICH:
|
|
584
|
+
try:
|
|
585
|
+
if self._rich is None:
|
|
586
|
+
self._rich = _RichProgressRenderer()
|
|
587
|
+
self._rich.update(message, percentage, show_progress_bar)
|
|
588
|
+
return
|
|
589
|
+
except Exception:
|
|
590
|
+
# Never fail a command due to progress rendering.
|
|
591
|
+
self._rich = None
|
|
592
|
+
mode = ProgressMode.PLAIN
|
|
593
|
+
|
|
594
|
+
# Plain text fallback - use ASCII-safe characters for Windows compatibility
|
|
595
|
+
from ..core.unicode_safe import safe_print, safe_format_progress_bar
|
|
596
|
+
|
|
597
|
+
spinner = self._plain_spinner.next()
|
|
598
|
+
if show_progress_bar and percentage is not None:
|
|
599
|
+
bar = safe_format_progress_bar(percentage, width=24)
|
|
600
|
+
safe_print(f"{spinner} {message} {bar}", file=sys.stderr, end="\r")
|
|
601
|
+
sys.stderr.flush()
|
|
602
|
+
else:
|
|
603
|
+
safe_print(f"{spinner} {message}", file=sys.stderr, end="\r")
|
|
604
|
+
sys.stderr.flush()
|
|
605
|
+
|
|
606
|
+
def clear_progress(self) -> None:
|
|
607
|
+
"""Clear the current progress line."""
|
|
608
|
+
if self.verbosity != VerbosityLevel.QUIET:
|
|
609
|
+
if self._rich is not None:
|
|
610
|
+
try:
|
|
611
|
+
self._rich.clear()
|
|
612
|
+
except Exception:
|
|
613
|
+
pass
|
|
614
|
+
finally:
|
|
615
|
+
self._rich = None
|
|
616
|
+
print("", file=sys.stderr) # New line to clear progress
|
|
617
|
+
|
|
618
|
+
def _resolve_progress_mode(self) -> ProgressMode:
|
|
619
|
+
"""
|
|
620
|
+
Decide which progress renderer to use for this invocation.
|
|
621
|
+
|
|
622
|
+
Rules:
|
|
623
|
+
- JSON output: keep progress as JSON events (handled earlier)
|
|
624
|
+
- Quiet: already handled earlier
|
|
625
|
+
- OFF: explicit disable
|
|
626
|
+
- AUTO: Rich when interactive TTY and not on CI / explicitly disabled
|
|
627
|
+
"""
|
|
628
|
+
|
|
629
|
+
mode = self.progress_mode
|
|
630
|
+
if mode == ProgressMode.AUTO:
|
|
631
|
+
if not _is_interactive_tty() or _should_disable_animated_progress():
|
|
632
|
+
return ProgressMode.PLAIN
|
|
633
|
+
return ProgressMode.RICH
|
|
634
|
+
return mode
|
|
635
|
+
|
|
636
|
+
def output_result(
|
|
637
|
+
self,
|
|
638
|
+
data: dict[str, Any] | str,
|
|
639
|
+
message: str | None = None,
|
|
640
|
+
warnings: list[str] | None = None,
|
|
641
|
+
compact: bool = True, # Default to compact mode to prevent Cursor crashes
|
|
642
|
+
**kwargs: Any, # Accept additional kwargs for backward compatibility
|
|
643
|
+
) -> None:
|
|
644
|
+
"""
|
|
645
|
+
Output final result data.
|
|
646
|
+
|
|
647
|
+
Args:
|
|
648
|
+
data: Result data (dict for JSON, str for text)
|
|
649
|
+
message: Optional summary message
|
|
650
|
+
warnings: Optional list of warnings
|
|
651
|
+
compact: If True (default), remove verbose debug data to prevent large output
|
|
652
|
+
"""
|
|
653
|
+
# Apply output compaction to prevent overwhelming Cursor's terminal
|
|
654
|
+
# Large outputs can cause Cursor to crash with "Connection Error"
|
|
655
|
+
if compact and isinstance(data, dict):
|
|
656
|
+
from .output_compactor import clean_debug_output, limit_output_size
|
|
657
|
+
data = clean_debug_output(data)
|
|
658
|
+
data = limit_output_size(data)
|
|
659
|
+
|
|
660
|
+
if self.format_type == "json":
|
|
661
|
+
output: dict[str, Any] = {
|
|
662
|
+
"success": True,
|
|
663
|
+
}
|
|
664
|
+
if message:
|
|
665
|
+
output["message"] = message
|
|
666
|
+
if isinstance(data, dict):
|
|
667
|
+
output["data"] = data
|
|
668
|
+
else:
|
|
669
|
+
output["data"] = {"content": data}
|
|
670
|
+
if warnings:
|
|
671
|
+
output["warnings"] = warnings
|
|
672
|
+
if self.operation_start_time:
|
|
673
|
+
duration_ms = int((time.time() - self.operation_start_time) * 1000)
|
|
674
|
+
output["metadata"] = {
|
|
675
|
+
"timestamp": datetime.now(UTC).isoformat(),
|
|
676
|
+
"duration_ms": duration_ms,
|
|
677
|
+
"version": PACKAGE_VERSION,
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
# Issue 8 Fix: Detect instruction objects and add Cursor AI execution directive
|
|
681
|
+
# When an instruction object is present, add clear directives for Cursor AI
|
|
682
|
+
data_dict = output.get("data", {})
|
|
683
|
+
if isinstance(data_dict, dict) and "instruction" in data_dict:
|
|
684
|
+
instruction = data_dict.get("instruction", {})
|
|
685
|
+
|
|
686
|
+
# Extract file path - handle different instruction types:
|
|
687
|
+
# 1. TestGenerationInstruction: target_file (test file path for tester instructions)
|
|
688
|
+
# 2. CodeGenerationInstruction: file_path
|
|
689
|
+
# 3. GenericInstruction: parameters.file_path
|
|
690
|
+
# 4. Tester agent result: test_file (test file path, not source)
|
|
691
|
+
# 5. Fallback: file from data_dict
|
|
692
|
+
# Note: For tester instructions, target_file points to test file (not source file)
|
|
693
|
+
# TODO: Store source file path in tester agent result for better traceability
|
|
694
|
+
file_path = (
|
|
695
|
+
instruction.get("target_file") or # TestGenerationInstruction (test file)
|
|
696
|
+
instruction.get("file_path") or # CodeGenerationInstruction
|
|
697
|
+
data_dict.get("test_file") or # Tester agent result (test file)
|
|
698
|
+
instruction.get("parameters", {}).get("file_path") or # GenericInstruction
|
|
699
|
+
data_dict.get("file") or # Fallback
|
|
700
|
+
"unknown"
|
|
701
|
+
)
|
|
702
|
+
command = instruction.get("command", "improve")
|
|
703
|
+
agent_name = instruction.get("agent_name", "improver")
|
|
704
|
+
|
|
705
|
+
# Print clear text directive to stderr BEFORE JSON (Cursor AI sees this in chat)
|
|
706
|
+
# This helps Cursor AI recognize that it should execute the instruction
|
|
707
|
+
print("\n" + "=" * 80, file=sys.stderr)
|
|
708
|
+
print("⚠️ CURSOR AI: EXECUTION REQUIRED", file=sys.stderr)
|
|
709
|
+
print("=" * 80, file=sys.stderr)
|
|
710
|
+
print(f"\nThis command returned an instruction object that YOU MUST EXECUTE.", file=sys.stderr)
|
|
711
|
+
print(f"\nDO NOT just return this JSON - you must:", file=sys.stderr)
|
|
712
|
+
print(f" 1. Read the file: {file_path}", file=sys.stderr)
|
|
713
|
+
print(f" 2. Extract the 'prompt' field from the instruction object below", file=sys.stderr)
|
|
714
|
+
print(f" 3. Use that prompt as your directive to improve/refactor/optimize the code", file=sys.stderr)
|
|
715
|
+
print(f" 4. Actually edit the file with improvements", file=sys.stderr)
|
|
716
|
+
print(f" 5. Explain what improvements you made and why", file=sys.stderr)
|
|
717
|
+
print(f"\nThe instruction is in: data.instruction.prompt", file=sys.stderr)
|
|
718
|
+
print(f"Execute using: @{agent_name} *{command} or follow the prompt directly.", file=sys.stderr)
|
|
719
|
+
print("=" * 80 + "\n", file=sys.stderr)
|
|
720
|
+
sys.stderr.flush()
|
|
721
|
+
|
|
722
|
+
# Also add execution directive in JSON for programmatic use
|
|
723
|
+
output["_cursor_execution_directive"] = {
|
|
724
|
+
"action": "execute_instruction",
|
|
725
|
+
"description": "This result contains an instruction object that must be executed. DO NOT just return this JSON - you must execute the instruction by improving the code.",
|
|
726
|
+
"steps": [
|
|
727
|
+
f"1. Read the file: {file_path}",
|
|
728
|
+
"2. Extract the 'prompt' field from data.instruction as your improvement directive",
|
|
729
|
+
"3. Actually improve/refactor/optimize the code by editing the file according to the prompt",
|
|
730
|
+
"4. Apply all improvements (best practices, type hints, documentation, etc.)",
|
|
731
|
+
"5. Explain what improvements you made and why",
|
|
732
|
+
],
|
|
733
|
+
"critical": "DO NOT just return this instruction object - you must execute it by improving the code!",
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
# JSON output always goes to stdout
|
|
737
|
+
print(json.dumps(output, indent=2), file=sys.stdout)
|
|
738
|
+
sys.stdout.flush()
|
|
739
|
+
else:
|
|
740
|
+
# Text format
|
|
741
|
+
if message and self.verbosity != VerbosityLevel.QUIET:
|
|
742
|
+
print(f"\n{message}", file=sys.stderr)
|
|
743
|
+
sys.stderr.flush()
|
|
744
|
+
if warnings and self.verbosity != VerbosityLevel.QUIET:
|
|
745
|
+
for warning in warnings:
|
|
746
|
+
print(f"[WARN] {warning}", file=sys.stderr)
|
|
747
|
+
sys.stderr.flush()
|
|
748
|
+
# Data goes to stdout
|
|
749
|
+
if isinstance(data, dict):
|
|
750
|
+
# For text mode, format dict nicely
|
|
751
|
+
if self.verbosity == VerbosityLevel.QUIET:
|
|
752
|
+
# In quiet mode, just output minimal data
|
|
753
|
+
print(json.dumps(data, indent=2), file=sys.stdout)
|
|
754
|
+
else:
|
|
755
|
+
# Format for readability
|
|
756
|
+
for key, value in data.items():
|
|
757
|
+
if isinstance(value, (dict, list)):
|
|
758
|
+
print(f"{key}:", file=sys.stdout)
|
|
759
|
+
print(json.dumps(value, indent=2), file=sys.stdout)
|
|
760
|
+
else:
|
|
761
|
+
print(f"{key}: {value}", file=sys.stdout)
|
|
762
|
+
else:
|
|
763
|
+
print(data, file=sys.stdout)
|
|
764
|
+
sys.stdout.flush()
|
|
765
|
+
|
|
766
|
+
def start_operation(self, operation_name: str, description: str | None = None) -> None:
|
|
767
|
+
"""
|
|
768
|
+
Start timing an operation and optionally start a heartbeat.
|
|
769
|
+
|
|
770
|
+
Args:
|
|
771
|
+
operation_name: Name of the operation
|
|
772
|
+
description: Optional description of what the operation does
|
|
773
|
+
"""
|
|
774
|
+
self.operation_start_time = time.time()
|
|
775
|
+
|
|
776
|
+
# Always show clear START indicator (unless quiet mode)
|
|
777
|
+
if self.verbosity != VerbosityLevel.QUIET:
|
|
778
|
+
if description:
|
|
779
|
+
self.info(f"[START] {operation_name} - {description}")
|
|
780
|
+
else:
|
|
781
|
+
self.info(f"[START] {operation_name}...")
|
|
782
|
+
|
|
783
|
+
# Start heartbeat for long-running operations (after 2 seconds)
|
|
784
|
+
if self.verbosity != VerbosityLevel.QUIET:
|
|
785
|
+
try:
|
|
786
|
+
from .progress_heartbeat import ProgressHeartbeat
|
|
787
|
+
self._heartbeat = ProgressHeartbeat(
|
|
788
|
+
message=f"{operation_name}...",
|
|
789
|
+
start_delay=2.0,
|
|
790
|
+
update_interval=1.0,
|
|
791
|
+
feedback_manager=self,
|
|
792
|
+
)
|
|
793
|
+
self._heartbeat.start()
|
|
794
|
+
except Exception:
|
|
795
|
+
# Don't fail if heartbeat can't start
|
|
796
|
+
self._heartbeat = None
|
|
797
|
+
|
|
798
|
+
def get_duration_ms(self) -> int | None:
|
|
799
|
+
"""Get operation duration in milliseconds."""
|
|
800
|
+
if self.operation_start_time:
|
|
801
|
+
return int((time.time() - self.operation_start_time) * 1000)
|
|
802
|
+
return None
|
|
803
|
+
|
|
804
|
+
def running(self, message: str, step: int | None = None, total_steps: int | None = None) -> None:
|
|
805
|
+
"""
|
|
806
|
+
Output running status message.
|
|
807
|
+
|
|
808
|
+
Args:
|
|
809
|
+
message: Running status message
|
|
810
|
+
step: Current step number (optional)
|
|
811
|
+
total_steps: Total number of steps (optional)
|
|
812
|
+
"""
|
|
813
|
+
if self.verbosity == VerbosityLevel.QUIET:
|
|
814
|
+
return
|
|
815
|
+
|
|
816
|
+
# Format step information if provided
|
|
817
|
+
step_info = ""
|
|
818
|
+
if step is not None and total_steps is not None:
|
|
819
|
+
step_info = f" (step {step}/{total_steps})"
|
|
820
|
+
elif step is not None:
|
|
821
|
+
step_info = f" (step {step})"
|
|
822
|
+
|
|
823
|
+
# Running status always goes to stderr as plain text
|
|
824
|
+
print(f"[RUNNING] {message}{step_info}", file=sys.stderr)
|
|
825
|
+
|
|
826
|
+
|
|
827
|
+
def suggest_simple_mode(
|
|
828
|
+
task_type: str,
|
|
829
|
+
description: str | None = None,
|
|
830
|
+
file_path: str | None = None,
|
|
831
|
+
) -> None:
|
|
832
|
+
"""
|
|
833
|
+
Suggest using @simple-mode for better outcomes.
|
|
834
|
+
|
|
835
|
+
Call this at the start of CLI commands that could benefit from workflow orchestration.
|
|
836
|
+
|
|
837
|
+
Args:
|
|
838
|
+
task_type: Type of task - "build", "fix", "review", "test", "refactor"
|
|
839
|
+
description: Optional description for the task
|
|
840
|
+
file_path: Optional file path for the task
|
|
841
|
+
"""
|
|
842
|
+
feedback = FeedbackManager.get_instance()
|
|
843
|
+
|
|
844
|
+
# Don't show in quiet mode
|
|
845
|
+
if feedback.verbosity == VerbosityLevel.QUIET:
|
|
846
|
+
return
|
|
847
|
+
|
|
848
|
+
# Build the suggestion message
|
|
849
|
+
commands = {
|
|
850
|
+
"build": f'@simple-mode *build "{description or "your feature description"}"',
|
|
851
|
+
"implement": f'@simple-mode *build "{description or "your feature description"}"',
|
|
852
|
+
"fix": f'@simple-mode *fix {file_path or "<file>"} "{description or "error description"}"',
|
|
853
|
+
"debug": f'@simple-mode *fix {file_path or "<file>"} "{description or "error description"}"',
|
|
854
|
+
"review": f'@simple-mode *review {file_path or "<file>"}',
|
|
855
|
+
"test": f'@simple-mode *test {file_path or "<file>"}',
|
|
856
|
+
"refactor": f'@simple-mode *refactor {file_path or "<file>"}',
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
simple_mode_cmd = commands.get(task_type, f'@simple-mode *build "{description or "your task"}"')
|
|
860
|
+
|
|
861
|
+
# Print tip box
|
|
862
|
+
print(file=sys.stderr)
|
|
863
|
+
print("╭" + "─" * 68 + "╮", file=sys.stderr)
|
|
864
|
+
print("│ TIP: For better outcomes, use @simple-mode in Cursor IDE │", file=sys.stderr)
|
|
865
|
+
print("│ │", file=sys.stderr)
|
|
866
|
+
# Truncate command if too long
|
|
867
|
+
if len(simple_mode_cmd) > 60:
|
|
868
|
+
display_cmd = simple_mode_cmd[:57] + "..."
|
|
869
|
+
else:
|
|
870
|
+
display_cmd = simple_mode_cmd
|
|
871
|
+
print(f"│ {display_cmd:<62} │", file=sys.stderr)
|
|
872
|
+
print("│ │", file=sys.stderr)
|
|
873
|
+
print("│ This orchestrates a complete workflow with quality gates. │", file=sys.stderr)
|
|
874
|
+
print("╰" + "─" * 68 + "╯", file=sys.stderr)
|
|
875
|
+
print(file=sys.stderr)
|
|
876
|
+
|
|
877
|
+
|
|
878
|
+
# Convenience functions for easy access
|
|
879
|
+
def get_feedback() -> FeedbackManager:
|
|
880
|
+
"""Get the feedback manager instance."""
|
|
881
|
+
return FeedbackManager.get_instance()
|
|
882
|
+
|
|
883
|
+
|
|
884
|
+
def info(message: str, details: dict[str, Any] | None = None) -> None:
|
|
885
|
+
"""Output informational message."""
|
|
886
|
+
get_feedback().info(message, details)
|
|
887
|
+
|
|
888
|
+
|
|
889
|
+
def success(
|
|
890
|
+
message: str,
|
|
891
|
+
data: dict[str, Any] | None = None,
|
|
892
|
+
summary: dict[str, Any] | None = None,
|
|
893
|
+
) -> None:
|
|
894
|
+
"""Output success message."""
|
|
895
|
+
get_feedback().success(message, data, summary)
|
|
896
|
+
|
|
897
|
+
|
|
898
|
+
def warning(message: str, remediation: str | None = None) -> None:
|
|
899
|
+
"""Output warning message."""
|
|
900
|
+
get_feedback().warning(message, remediation)
|
|
901
|
+
|
|
902
|
+
|
|
903
|
+
def error(
|
|
904
|
+
message: str,
|
|
905
|
+
error_code: str = "error",
|
|
906
|
+
context: dict[str, Any] | None = None,
|
|
907
|
+
remediation: str | None = None,
|
|
908
|
+
exit_code: int = 1,
|
|
909
|
+
) -> None:
|
|
910
|
+
"""Output error message and exit."""
|
|
911
|
+
get_feedback().error(message, error_code, context, remediation, exit_code)
|
|
912
|
+
|
|
913
|
+
|
|
914
|
+
def progress(
|
|
915
|
+
message: str,
|
|
916
|
+
percentage: int | None = None,
|
|
917
|
+
show_progress_bar: bool = False,
|
|
918
|
+
) -> None:
|
|
919
|
+
"""Output progress message."""
|
|
920
|
+
get_feedback().progress(message, percentage, show_progress_bar)
|
|
921
|
+
|
|
922
|
+
|
|
923
|
+
def clear_progress() -> None:
|
|
924
|
+
"""Clear the current progress line."""
|
|
925
|
+
get_feedback().clear_progress()
|
|
926
|
+
|
|
927
|
+
|
|
928
|
+
def output_result(
|
|
929
|
+
data: dict[str, Any] | str,
|
|
930
|
+
message: str | None = None,
|
|
931
|
+
warnings: list[str] | None = None,
|
|
932
|
+
compact: bool = True,
|
|
933
|
+
) -> None:
|
|
934
|
+
"""Output final result data."""
|
|
935
|
+
get_feedback().output_result(data, message, warnings, compact=compact)
|
|
936
|
+
|