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
|
@@ -1,724 +1,724 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Worktree Manager - Manages Git worktrees for workflow steps.
|
|
3
|
-
|
|
4
|
-
This module handles worktree creation, artifact copying, and cleanup.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
|
-
import hashlib
|
|
10
|
-
import json
|
|
11
|
-
import logging
|
|
12
|
-
import re
|
|
13
|
-
import shutil
|
|
14
|
-
import subprocess # nosec B404 - fixed args, no shell
|
|
15
|
-
from datetime import datetime
|
|
16
|
-
from pathlib import Path
|
|
17
|
-
from typing import Any
|
|
18
|
-
|
|
19
|
-
from .models import Artifact, WorkflowStep
|
|
20
|
-
|
|
21
|
-
logger = logging.getLogger(__name__)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class WorktreeManager:
|
|
25
|
-
"""
|
|
26
|
-
Manages Git worktrees for workflow step isolation.
|
|
27
|
-
|
|
28
|
-
Each workflow step runs in its own worktree to prevent conflicts
|
|
29
|
-
and enable clean rollback.
|
|
30
|
-
"""
|
|
31
|
-
|
|
32
|
-
def __init__(self, project_root: Path):
|
|
33
|
-
"""
|
|
34
|
-
Initialize Worktree Manager.
|
|
35
|
-
|
|
36
|
-
Args:
|
|
37
|
-
project_root: Root directory for the project
|
|
38
|
-
"""
|
|
39
|
-
self.project_root = project_root
|
|
40
|
-
self.worktrees_dir = project_root / ".tapps-agents" / "worktrees"
|
|
41
|
-
|
|
42
|
-
@staticmethod
|
|
43
|
-
def _sanitize_component(value: str, *, max_len: int = 80) -> str:
|
|
44
|
-
"""
|
|
45
|
-
Sanitize a user-provided identifier into a safe path/branch component.
|
|
46
|
-
|
|
47
|
-
- Avoids path separators and Windows-invalid characters
|
|
48
|
-
- Keeps names reasonably short to mitigate MAX_PATH issues
|
|
49
|
-
"""
|
|
50
|
-
value = (value or "").strip()
|
|
51
|
-
if not value:
|
|
52
|
-
return "worktree"
|
|
53
|
-
|
|
54
|
-
# Replace separators and invalid chars with '-'
|
|
55
|
-
value = value.replace("\\", "-").replace("/", "-")
|
|
56
|
-
value = re.sub(r"[^A-Za-z0-9._-]+", "-", value)
|
|
57
|
-
value = re.sub(r"-{2,}", "-", value).strip("-")
|
|
58
|
-
|
|
59
|
-
if not value:
|
|
60
|
-
return "worktree"
|
|
61
|
-
|
|
62
|
-
if len(value) > max_len:
|
|
63
|
-
digest = hashlib.sha256(value.encode("utf-8")).hexdigest()[:10]
|
|
64
|
-
value = f"{value[: max_len - 11]}-{digest}"
|
|
65
|
-
return value
|
|
66
|
-
|
|
67
|
-
def _safe_worktree_name(self, worktree_name: str) -> str:
|
|
68
|
-
# Ensure no traversal; store only as a single directory name.
|
|
69
|
-
if Path(worktree_name).is_absolute() or any(
|
|
70
|
-
part in {".", ".."} for part in Path(worktree_name).parts
|
|
71
|
-
):
|
|
72
|
-
raise ValueError(f"Invalid worktree name: {worktree_name!r}")
|
|
73
|
-
return self._sanitize_component(worktree_name, max_len=80)
|
|
74
|
-
|
|
75
|
-
def _worktree_path_for(self, worktree_name: str) -> Path:
|
|
76
|
-
safe_name = self._safe_worktree_name(worktree_name)
|
|
77
|
-
return self.worktrees_dir / safe_name
|
|
78
|
-
|
|
79
|
-
def _branch_for(self, worktree_name: str) -> str:
|
|
80
|
-
safe_name = self._sanitize_component(worktree_name, max_len=90)
|
|
81
|
-
# Keep branch names well under git limits and Windows path quirks.
|
|
82
|
-
if len(safe_name) > 90:
|
|
83
|
-
safe_name = safe_name[:90]
|
|
84
|
-
return f"workflow/{safe_name}"
|
|
85
|
-
|
|
86
|
-
async def create_worktree(self, worktree_name: str) -> Path:
|
|
87
|
-
"""
|
|
88
|
-
Create a new Git worktree for a workflow step.
|
|
89
|
-
|
|
90
|
-
Args:
|
|
91
|
-
worktree_name: Name for the worktree
|
|
92
|
-
|
|
93
|
-
Returns:
|
|
94
|
-
Path to the worktree
|
|
95
|
-
"""
|
|
96
|
-
worktree_path = self._worktree_path_for(worktree_name)
|
|
97
|
-
worktree_path.parent.mkdir(parents=True, exist_ok=True)
|
|
98
|
-
|
|
99
|
-
# Check if worktree already exists
|
|
100
|
-
if worktree_path.exists():
|
|
101
|
-
# Clean up existing worktree
|
|
102
|
-
await self.remove_worktree(worktree_path.name)
|
|
103
|
-
|
|
104
|
-
# Create worktree using git command
|
|
105
|
-
try:
|
|
106
|
-
git_path = shutil.which("git") or "git"
|
|
107
|
-
branch = self._branch_for(worktree_path.name)
|
|
108
|
-
subprocess.run(
|
|
109
|
-
[
|
|
110
|
-
git_path,
|
|
111
|
-
"worktree",
|
|
112
|
-
"add",
|
|
113
|
-
str(worktree_path),
|
|
114
|
-
"-b",
|
|
115
|
-
branch,
|
|
116
|
-
],
|
|
117
|
-
cwd=self.project_root,
|
|
118
|
-
capture_output=True,
|
|
119
|
-
text=True,
|
|
120
|
-
check=True,
|
|
121
|
-
)
|
|
122
|
-
except subprocess.CalledProcessError:
|
|
123
|
-
# If branch already exists, attach the worktree to it.
|
|
124
|
-
try:
|
|
125
|
-
git_path = shutil.which("git") or "git"
|
|
126
|
-
branch = self._branch_for(worktree_path.name)
|
|
127
|
-
subprocess.run(
|
|
128
|
-
[git_path, "worktree", "add", str(worktree_path), branch],
|
|
129
|
-
cwd=self.project_root,
|
|
130
|
-
capture_output=True,
|
|
131
|
-
text=True,
|
|
132
|
-
check=True,
|
|
133
|
-
)
|
|
134
|
-
except subprocess.CalledProcessError:
|
|
135
|
-
# If worktree creation fails, create a regular directory
|
|
136
|
-
worktree_path.mkdir(parents=True, exist_ok=True)
|
|
137
|
-
# Copy project files
|
|
138
|
-
self._copy_project_files(worktree_path)
|
|
139
|
-
|
|
140
|
-
return worktree_path
|
|
141
|
-
|
|
142
|
-
def _copy_project_files(self, dest: Path) -> None:
|
|
143
|
-
"""Copy project files to worktree (fallback if git worktree fails)."""
|
|
144
|
-
# Create a fresh .tapps-agents folder in the worktree (do not recursively copy worktrees).
|
|
145
|
-
(dest / ".tapps-agents").mkdir(parents=True, exist_ok=True)
|
|
146
|
-
|
|
147
|
-
essential_items = [
|
|
148
|
-
# Core repo content
|
|
149
|
-
"tapps_agents",
|
|
150
|
-
"tests",
|
|
151
|
-
"docs",
|
|
152
|
-
"workflows",
|
|
153
|
-
# Key project files
|
|
154
|
-
"pyproject.toml",
|
|
155
|
-
"README.md",
|
|
156
|
-
"LICENSE",
|
|
157
|
-
]
|
|
158
|
-
|
|
159
|
-
# If capabilities exist, include them (but never copy worktrees).
|
|
160
|
-
capabilities_dir = self.project_root / ".tapps-agents" / "capabilities"
|
|
161
|
-
if capabilities_dir.exists():
|
|
162
|
-
essential_items.append(str(Path(".tapps-agents") / "capabilities"))
|
|
163
|
-
|
|
164
|
-
for item in essential_items:
|
|
165
|
-
src = self.project_root / item
|
|
166
|
-
if not src.exists():
|
|
167
|
-
continue
|
|
168
|
-
|
|
169
|
-
dst = dest / item
|
|
170
|
-
if src.is_dir():
|
|
171
|
-
shutil.copytree(
|
|
172
|
-
src,
|
|
173
|
-
dst,
|
|
174
|
-
dirs_exist_ok=True,
|
|
175
|
-
ignore=shutil.ignore_patterns(
|
|
176
|
-
".git",
|
|
177
|
-
".venv",
|
|
178
|
-
"__pycache__",
|
|
179
|
-
".pytest_cache",
|
|
180
|
-
".ruff_cache",
|
|
181
|
-
".mypy_cache",
|
|
182
|
-
"htmlcov",
|
|
183
|
-
"dist",
|
|
184
|
-
"build",
|
|
185
|
-
"reports",
|
|
186
|
-
"worktrees",
|
|
187
|
-
),
|
|
188
|
-
)
|
|
189
|
-
else:
|
|
190
|
-
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
191
|
-
shutil.copy2(src, dst)
|
|
192
|
-
|
|
193
|
-
async def copy_artifacts(
|
|
194
|
-
self, worktree_path: Path, artifacts: list[Artifact]
|
|
195
|
-
) -> None:
|
|
196
|
-
"""
|
|
197
|
-
Copy artifacts from previous steps to worktree.
|
|
198
|
-
|
|
199
|
-
Args:
|
|
200
|
-
worktree_path: Path to worktree
|
|
201
|
-
artifacts: List of artifacts to copy
|
|
202
|
-
"""
|
|
203
|
-
for artifact in artifacts:
|
|
204
|
-
# Ensure artifact is an Artifact object
|
|
205
|
-
if not isinstance(artifact, Artifact):
|
|
206
|
-
# Skip if not an Artifact object
|
|
207
|
-
continue
|
|
208
|
-
|
|
209
|
-
if not artifact.path:
|
|
210
|
-
continue
|
|
211
|
-
|
|
212
|
-
src_path = Path(artifact.path)
|
|
213
|
-
if not src_path.exists():
|
|
214
|
-
continue
|
|
215
|
-
|
|
216
|
-
# Determine destination path
|
|
217
|
-
if src_path.is_absolute() and self.project_root in src_path.parents:
|
|
218
|
-
# Relative to project root
|
|
219
|
-
rel_path = src_path.relative_to(self.project_root)
|
|
220
|
-
dest_path = worktree_path / rel_path
|
|
221
|
-
else:
|
|
222
|
-
# Use filename only
|
|
223
|
-
dest_path = worktree_path / src_path.name
|
|
224
|
-
|
|
225
|
-
# Copy file or directory
|
|
226
|
-
dest_path.parent.mkdir(parents=True, exist_ok=True)
|
|
227
|
-
if src_path.is_dir():
|
|
228
|
-
shutil.copytree(src_path, dest_path, dirs_exist_ok=True)
|
|
229
|
-
else:
|
|
230
|
-
shutil.copy2(src_path, dest_path)
|
|
231
|
-
|
|
232
|
-
async def extract_artifacts(
|
|
233
|
-
self, worktree_path: Path, step: WorkflowStep
|
|
234
|
-
) -> list[Artifact]:
|
|
235
|
-
"""
|
|
236
|
-
Extract artifacts created in worktree.
|
|
237
|
-
|
|
238
|
-
Args:
|
|
239
|
-
worktree_path: Path to worktree
|
|
240
|
-
step: Workflow step definition
|
|
241
|
-
|
|
242
|
-
Returns:
|
|
243
|
-
List of artifacts found
|
|
244
|
-
"""
|
|
245
|
-
artifacts: list[Artifact] = []
|
|
246
|
-
|
|
247
|
-
# Check for expected artifacts from step definition
|
|
248
|
-
if step.creates:
|
|
249
|
-
for artifact_name in step.creates:
|
|
250
|
-
artifact_path = worktree_path / artifact_name
|
|
251
|
-
if artifact_path.exists():
|
|
252
|
-
# Copy to main project
|
|
253
|
-
main_path = self.project_root / artifact_name
|
|
254
|
-
main_path.parent.mkdir(parents=True, exist_ok=True)
|
|
255
|
-
|
|
256
|
-
if artifact_path.is_dir():
|
|
257
|
-
shutil.copytree(artifact_path, main_path, dirs_exist_ok=True)
|
|
258
|
-
else:
|
|
259
|
-
shutil.copy2(artifact_path, main_path)
|
|
260
|
-
|
|
261
|
-
artifacts.append(
|
|
262
|
-
Artifact(
|
|
263
|
-
name=artifact_name,
|
|
264
|
-
path=str(main_path),
|
|
265
|
-
status="complete",
|
|
266
|
-
created_by=step.id,
|
|
267
|
-
created_at=datetime.now(),
|
|
268
|
-
metadata={"type": "file" if artifact_path.is_file() else "directory"},
|
|
269
|
-
)
|
|
270
|
-
)
|
|
271
|
-
|
|
272
|
-
# Also scan for common artifact patterns
|
|
273
|
-
common_patterns = [
|
|
274
|
-
"requirements.md",
|
|
275
|
-
"stories/*.md",
|
|
276
|
-
"architecture.md",
|
|
277
|
-
"api-specs/**/*",
|
|
278
|
-
"src/**/*.py",
|
|
279
|
-
"tests/**/*.py",
|
|
280
|
-
"docs/**/*.md",
|
|
281
|
-
]
|
|
282
|
-
|
|
283
|
-
for pattern in common_patterns:
|
|
284
|
-
for artifact_path in worktree_path.glob(pattern):
|
|
285
|
-
if artifact_path.is_file():
|
|
286
|
-
# Copy to main project
|
|
287
|
-
rel_path = artifact_path.relative_to(worktree_path)
|
|
288
|
-
main_path = self.project_root / rel_path
|
|
289
|
-
main_path.parent.mkdir(parents=True, exist_ok=True)
|
|
290
|
-
shutil.copy2(artifact_path, main_path)
|
|
291
|
-
|
|
292
|
-
artifacts.append(
|
|
293
|
-
Artifact(
|
|
294
|
-
name=artifact_path.name,
|
|
295
|
-
path=str(main_path),
|
|
296
|
-
status="complete",
|
|
297
|
-
created_by=step.id,
|
|
298
|
-
created_at=datetime.now(),
|
|
299
|
-
metadata={"type": "file"},
|
|
300
|
-
)
|
|
301
|
-
)
|
|
302
|
-
|
|
303
|
-
return artifacts
|
|
304
|
-
|
|
305
|
-
def _delete_branch(self, branch_name: str) -> bool:
|
|
306
|
-
"""
|
|
307
|
-
Delete a Git branch (safe delete with force fallback).
|
|
308
|
-
|
|
309
|
-
Args:
|
|
310
|
-
branch_name: Name of the branch to delete (e.g., "workflow/...")
|
|
311
|
-
|
|
312
|
-
Returns:
|
|
313
|
-
True if branch was deleted or didn't exist, False on error
|
|
314
|
-
"""
|
|
315
|
-
git_path = shutil.which("git") or "git"
|
|
316
|
-
|
|
317
|
-
# Verify branch exists
|
|
318
|
-
try:
|
|
319
|
-
subprocess.run(
|
|
320
|
-
[git_path, "rev-parse", "--verify", branch_name],
|
|
321
|
-
cwd=self.project_root,
|
|
322
|
-
capture_output=True,
|
|
323
|
-
text=True,
|
|
324
|
-
check=True,
|
|
325
|
-
)
|
|
326
|
-
except subprocess.CalledProcessError:
|
|
327
|
-
# Branch doesn't exist - treat as success
|
|
328
|
-
logger.debug(f"Branch {branch_name} doesn't exist, nothing to delete")
|
|
329
|
-
return True
|
|
330
|
-
|
|
331
|
-
# Attempt safe delete first
|
|
332
|
-
try:
|
|
333
|
-
subprocess.run(
|
|
334
|
-
[git_path, "branch", "-d", branch_name],
|
|
335
|
-
cwd=self.project_root,
|
|
336
|
-
capture_output=True,
|
|
337
|
-
text=True,
|
|
338
|
-
encoding="utf-8",
|
|
339
|
-
errors="replace",
|
|
340
|
-
check=True,
|
|
341
|
-
)
|
|
342
|
-
logger.info(f"Successfully deleted branch: {branch_name}")
|
|
343
|
-
return True
|
|
344
|
-
except subprocess.CalledProcessError:
|
|
345
|
-
# Safe delete failed (branch not merged), try force delete
|
|
346
|
-
try:
|
|
347
|
-
subprocess.run(
|
|
348
|
-
[git_path, "branch", "-D", branch_name],
|
|
349
|
-
cwd=self.project_root,
|
|
350
|
-
capture_output=True,
|
|
351
|
-
text=True,
|
|
352
|
-
encoding="utf-8",
|
|
353
|
-
errors="replace",
|
|
354
|
-
check=True,
|
|
355
|
-
)
|
|
356
|
-
logger.info(f"Successfully force-deleted branch: {branch_name}")
|
|
357
|
-
return True
|
|
358
|
-
except subprocess.CalledProcessError as e:
|
|
359
|
-
logger.warning(
|
|
360
|
-
f"Failed to delete branch {branch_name}: {e.stderr or 'unknown error'}"
|
|
361
|
-
)
|
|
362
|
-
return False
|
|
363
|
-
|
|
364
|
-
async def remove_worktree(
|
|
365
|
-
self, worktree_name: str, delete_branch: bool = True
|
|
366
|
-
) -> None:
|
|
367
|
-
"""
|
|
368
|
-
Remove a worktree and optionally its associated branch.
|
|
369
|
-
|
|
370
|
-
Args:
|
|
371
|
-
worktree_name: Name of the worktree to remove
|
|
372
|
-
delete_branch: If True, also delete the associated Git branch
|
|
373
|
-
"""
|
|
374
|
-
worktree_path = self._worktree_path_for(worktree_name)
|
|
375
|
-
branch_name = self._branch_for(worktree_name)
|
|
376
|
-
|
|
377
|
-
if not worktree_path.exists():
|
|
378
|
-
return
|
|
379
|
-
|
|
380
|
-
# Try to remove via git worktree
|
|
381
|
-
try:
|
|
382
|
-
git_path = shutil.which("git") or "git"
|
|
383
|
-
subprocess.run(
|
|
384
|
-
[git_path, "worktree", "remove", str(worktree_path), "--force"],
|
|
385
|
-
cwd=self.project_root,
|
|
386
|
-
capture_output=True,
|
|
387
|
-
check=False, # Don't fail if git command fails
|
|
388
|
-
)
|
|
389
|
-
except Exception:
|
|
390
|
-
pass
|
|
391
|
-
|
|
392
|
-
# Fallback: remove directory
|
|
393
|
-
if worktree_path.exists():
|
|
394
|
-
shutil.rmtree(worktree_path, ignore_errors=True)
|
|
395
|
-
|
|
396
|
-
# Delete the branch if requested
|
|
397
|
-
if delete_branch:
|
|
398
|
-
self._delete_branch(branch_name)
|
|
399
|
-
|
|
400
|
-
async def cleanup_all(self) -> None:
|
|
401
|
-
"""Clean up all worktrees."""
|
|
402
|
-
if not self.worktrees_dir.exists():
|
|
403
|
-
return
|
|
404
|
-
|
|
405
|
-
for worktree_dir in self.worktrees_dir.iterdir():
|
|
406
|
-
if worktree_dir.is_dir():
|
|
407
|
-
await self.remove_worktree(worktree_dir.name)
|
|
408
|
-
|
|
409
|
-
# Best-effort prune to clean up stale worktree metadata.
|
|
410
|
-
try:
|
|
411
|
-
git_path = shutil.which("git") or "git"
|
|
412
|
-
subprocess.run(
|
|
413
|
-
[git_path, "worktree", "prune"],
|
|
414
|
-
cwd=self.project_root,
|
|
415
|
-
capture_output=True,
|
|
416
|
-
check=False,
|
|
417
|
-
)
|
|
418
|
-
except Exception:
|
|
419
|
-
pass
|
|
420
|
-
|
|
421
|
-
def _check_working_tree_clean(self) -> bool:
|
|
422
|
-
"""
|
|
423
|
-
Check if the working tree is clean (no uncommitted changes).
|
|
424
|
-
|
|
425
|
-
Note: Untracked files are ignored as they don't affect merge operations.
|
|
426
|
-
|
|
427
|
-
Returns:
|
|
428
|
-
True if working tree is clean, False otherwise
|
|
429
|
-
"""
|
|
430
|
-
try:
|
|
431
|
-
git_path = shutil.which("git") or "git"
|
|
432
|
-
result = subprocess.run(
|
|
433
|
-
[git_path, "status", "--porcelain", "--untracked-files=no"],
|
|
434
|
-
cwd=self.project_root,
|
|
435
|
-
capture_output=True,
|
|
436
|
-
text=True,
|
|
437
|
-
check=True,
|
|
438
|
-
)
|
|
439
|
-
return not result.stdout.strip()
|
|
440
|
-
except subprocess.CalledProcessError:
|
|
441
|
-
return False
|
|
442
|
-
|
|
443
|
-
def _get_current_branch(self) -> str:
|
|
444
|
-
"""
|
|
445
|
-
Get the current branch name.
|
|
446
|
-
|
|
447
|
-
Returns:
|
|
448
|
-
Current branch name
|
|
449
|
-
|
|
450
|
-
Raises:
|
|
451
|
-
RuntimeError: If unable to determine current branch
|
|
452
|
-
"""
|
|
453
|
-
try:
|
|
454
|
-
git_path = shutil.which("git") or "git"
|
|
455
|
-
result = subprocess.run(
|
|
456
|
-
[git_path, "rev-parse", "--abbrev-ref", "HEAD"],
|
|
457
|
-
cwd=self.project_root,
|
|
458
|
-
capture_output=True,
|
|
459
|
-
text=True,
|
|
460
|
-
check=True,
|
|
461
|
-
)
|
|
462
|
-
return result.stdout.strip()
|
|
463
|
-
except subprocess.CalledProcessError as e:
|
|
464
|
-
raise RuntimeError(f"Failed to get current branch: {e.stderr}") from e
|
|
465
|
-
|
|
466
|
-
def _detect_conflicts(self) -> list[str]:
|
|
467
|
-
"""
|
|
468
|
-
Detect conflicted files in the working tree.
|
|
469
|
-
|
|
470
|
-
Returns:
|
|
471
|
-
List of file paths with conflicts
|
|
472
|
-
"""
|
|
473
|
-
conflicted_files: list[str] = []
|
|
474
|
-
try:
|
|
475
|
-
git_path = shutil.which("git") or "git"
|
|
476
|
-
result = subprocess.run(
|
|
477
|
-
[git_path, "diff", "--name-only", "--diff-filter=U"],
|
|
478
|
-
cwd=self.project_root,
|
|
479
|
-
capture_output=True,
|
|
480
|
-
text=True,
|
|
481
|
-
check=True,
|
|
482
|
-
)
|
|
483
|
-
for line in result.stdout.strip().split("\n"):
|
|
484
|
-
if line.strip():
|
|
485
|
-
conflicted_files.append(line.strip())
|
|
486
|
-
except subprocess.CalledProcessError:
|
|
487
|
-
# If command fails, try alternative method
|
|
488
|
-
try:
|
|
489
|
-
git_path = shutil.which("git") or "git"
|
|
490
|
-
result = subprocess.run(
|
|
491
|
-
[git_path, "status", "--porcelain"],
|
|
492
|
-
cwd=self.project_root,
|
|
493
|
-
capture_output=True,
|
|
494
|
-
text=True,
|
|
495
|
-
check=True,
|
|
496
|
-
)
|
|
497
|
-
for line in result.stdout.strip().split("\n"):
|
|
498
|
-
if line.strip() and line.startswith("UU"):
|
|
499
|
-
# UU indicates unmerged (conflicted) files
|
|
500
|
-
file_path = line[3:].strip()
|
|
501
|
-
conflicted_files.append(file_path)
|
|
502
|
-
except subprocess.CalledProcessError:
|
|
503
|
-
pass
|
|
504
|
-
return conflicted_files
|
|
505
|
-
|
|
506
|
-
def _write_conflict_report(
|
|
507
|
-
self,
|
|
508
|
-
worktree_name: str,
|
|
509
|
-
branch_name: str,
|
|
510
|
-
target_branch: str,
|
|
511
|
-
conflicted_files: list[str],
|
|
512
|
-
merge_error: str | None = None,
|
|
513
|
-
) -> Path:
|
|
514
|
-
"""
|
|
515
|
-
Write a conflict report to a JSON file.
|
|
516
|
-
|
|
517
|
-
Args:
|
|
518
|
-
worktree_name: Name of the worktree being merged
|
|
519
|
-
branch_name: Name of the worktree branch
|
|
520
|
-
target_branch: Target branch for merge
|
|
521
|
-
conflicted_files: List of conflicted file paths
|
|
522
|
-
merge_error: Optional error message from merge attempt
|
|
523
|
-
|
|
524
|
-
Returns:
|
|
525
|
-
Path to the conflict report file
|
|
526
|
-
"""
|
|
527
|
-
reports_dir = self.project_root / ".tapps-agents" / "reports"
|
|
528
|
-
reports_dir.mkdir(parents=True, exist_ok=True)
|
|
529
|
-
|
|
530
|
-
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
|
|
531
|
-
report_filename = f"merge-conflict-{worktree_name}-{timestamp}.json"
|
|
532
|
-
report_path = reports_dir / report_filename
|
|
533
|
-
|
|
534
|
-
report_data = {
|
|
535
|
-
"timestamp": datetime.now().isoformat(),
|
|
536
|
-
"worktree_name": worktree_name,
|
|
537
|
-
"worktree_branch": branch_name,
|
|
538
|
-
"target_branch": target_branch,
|
|
539
|
-
"conflicted_files": conflicted_files,
|
|
540
|
-
"conflict_count": len(conflicted_files),
|
|
541
|
-
"merge_error": merge_error,
|
|
542
|
-
"guidance": {
|
|
543
|
-
"resolution_steps": [
|
|
544
|
-
"1. Review conflicted files listed above",
|
|
545
|
-
"2. Manually resolve conflicts in each file",
|
|
546
|
-
"3. Mark conflicts as resolved: git add <file>",
|
|
547
|
-
"4. Complete merge: git commit",
|
|
548
|
-
"5. Or abort merge: git merge --abort",
|
|
549
|
-
],
|
|
550
|
-
"abort_command": "git merge --abort",
|
|
551
|
-
},
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
with open(report_path, "w", encoding="utf-8") as f:
|
|
555
|
-
json.dump(report_data, f, indent=2)
|
|
556
|
-
|
|
557
|
-
return report_path
|
|
558
|
-
|
|
559
|
-
async def merge_worktree(
|
|
560
|
-
self,
|
|
561
|
-
worktree_name: str,
|
|
562
|
-
target_branch: str | None = None,
|
|
563
|
-
) -> dict[str, Any]:
|
|
564
|
-
"""
|
|
565
|
-
Merge a worktree branch into the target branch.
|
|
566
|
-
|
|
567
|
-
Epic 1 / Story 1.5: Worktree Merge & Conflict Resolution
|
|
568
|
-
|
|
569
|
-
Args:
|
|
570
|
-
worktree_name: Name of the worktree to merge
|
|
571
|
-
target_branch: Target branch to merge into (defaults to current branch)
|
|
572
|
-
|
|
573
|
-
Returns:
|
|
574
|
-
Dictionary with merge result:
|
|
575
|
-
- success: bool
|
|
576
|
-
- has_conflicts: bool
|
|
577
|
-
- conflicted_files: list[str]
|
|
578
|
-
- conflict_report_path: Path | None
|
|
579
|
-
- error: str | None
|
|
580
|
-
|
|
581
|
-
Raises:
|
|
582
|
-
ValueError: If working tree is not clean or worktree doesn't exist
|
|
583
|
-
RuntimeError: If unable to determine current branch
|
|
584
|
-
"""
|
|
585
|
-
# Validate worktree exists
|
|
586
|
-
worktree_path = self._worktree_path_for(worktree_name)
|
|
587
|
-
if not worktree_path.exists():
|
|
588
|
-
raise ValueError(f"Worktree {worktree_name} does not exist")
|
|
589
|
-
|
|
590
|
-
# Check working tree is clean
|
|
591
|
-
if not self._check_working_tree_clean():
|
|
592
|
-
raise ValueError(
|
|
593
|
-
"Working tree is not clean. Please commit or stash changes before merging."
|
|
594
|
-
)
|
|
595
|
-
|
|
596
|
-
# Get target branch
|
|
597
|
-
if target_branch is None:
|
|
598
|
-
target_branch = self._get_current_branch()
|
|
599
|
-
|
|
600
|
-
# Get worktree branch name
|
|
601
|
-
branch_name = self._branch_for(worktree_name)
|
|
602
|
-
|
|
603
|
-
# Verify branch exists
|
|
604
|
-
git_path = shutil.which("git") or "git"
|
|
605
|
-
try:
|
|
606
|
-
subprocess.run(
|
|
607
|
-
[git_path, "rev-parse", "--verify", branch_name],
|
|
608
|
-
cwd=self.project_root,
|
|
609
|
-
capture_output=True,
|
|
610
|
-
check=True,
|
|
611
|
-
)
|
|
612
|
-
except subprocess.CalledProcessError:
|
|
613
|
-
return {
|
|
614
|
-
"success": False,
|
|
615
|
-
"has_conflicts": False,
|
|
616
|
-
"conflicted_files": [],
|
|
617
|
-
"conflict_report_path": None,
|
|
618
|
-
"error": f"Branch {branch_name} does not exist",
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
# Attempt merge
|
|
622
|
-
try:
|
|
623
|
-
subprocess.run(
|
|
624
|
-
[git_path, "merge", "--no-ff", "--no-commit", branch_name],
|
|
625
|
-
cwd=self.project_root,
|
|
626
|
-
capture_output=True,
|
|
627
|
-
text=True,
|
|
628
|
-
check=True,
|
|
629
|
-
)
|
|
630
|
-
|
|
631
|
-
# Merge succeeded without conflicts
|
|
632
|
-
# Complete the merge
|
|
633
|
-
subprocess.run(
|
|
634
|
-
[git_path, "commit", "--no-edit"],
|
|
635
|
-
cwd=self.project_root,
|
|
636
|
-
capture_output=True,
|
|
637
|
-
check=True,
|
|
638
|
-
)
|
|
639
|
-
|
|
640
|
-
return {
|
|
641
|
-
"success": True,
|
|
642
|
-
"has_conflicts": False,
|
|
643
|
-
"conflicted_files": [],
|
|
644
|
-
"conflict_report_path": None,
|
|
645
|
-
"error": None,
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
except subprocess.CalledProcessError as e:
|
|
649
|
-
# Merge failed - check for conflicts
|
|
650
|
-
conflicted_files = self._detect_conflicts()
|
|
651
|
-
|
|
652
|
-
if conflicted_files:
|
|
653
|
-
# Conflicts detected - write report
|
|
654
|
-
report_path = self._write_conflict_report(
|
|
655
|
-
worktree_name=worktree_name,
|
|
656
|
-
branch_name=branch_name,
|
|
657
|
-
target_branch=target_branch,
|
|
658
|
-
conflicted_files=conflicted_files,
|
|
659
|
-
merge_error=e.stderr,
|
|
660
|
-
)
|
|
661
|
-
|
|
662
|
-
return {
|
|
663
|
-
"success": False,
|
|
664
|
-
"has_conflicts": True,
|
|
665
|
-
"conflicted_files": conflicted_files,
|
|
666
|
-
"conflict_report_path": report_path,
|
|
667
|
-
"error": f"Merge conflicts detected in {len(conflicted_files)} file(s)",
|
|
668
|
-
}
|
|
669
|
-
else:
|
|
670
|
-
# Merge failed for other reasons
|
|
671
|
-
return {
|
|
672
|
-
"success": False,
|
|
673
|
-
"has_conflicts": False,
|
|
674
|
-
"conflicted_files": [],
|
|
675
|
-
"conflict_report_path": None,
|
|
676
|
-
"error": f"Merge failed: {e.stderr}",
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
async def abort_merge(self) -> bool:
|
|
680
|
-
"""
|
|
681
|
-
Abort an in-progress merge and restore working tree to pre-merge state.
|
|
682
|
-
|
|
683
|
-
Epic 1 / Story 1.5: Worktree Merge & Conflict Resolution
|
|
684
|
-
|
|
685
|
-
Returns:
|
|
686
|
-
True if merge was aborted successfully, False otherwise
|
|
687
|
-
"""
|
|
688
|
-
# Check if merge is in progress
|
|
689
|
-
git_path = shutil.which("git") or "git"
|
|
690
|
-
try:
|
|
691
|
-
result = subprocess.run(
|
|
692
|
-
[git_path, "rev-parse", "--verify", "MERGE_HEAD"],
|
|
693
|
-
cwd=self.project_root,
|
|
694
|
-
capture_output=True,
|
|
695
|
-
check=False,
|
|
696
|
-
)
|
|
697
|
-
if result.returncode != 0:
|
|
698
|
-
# No merge in progress
|
|
699
|
-
return False
|
|
700
|
-
except Exception:
|
|
701
|
-
return False
|
|
702
|
-
|
|
703
|
-
# Abort merge
|
|
704
|
-
try:
|
|
705
|
-
subprocess.run(
|
|
706
|
-
[git_path, "merge", "--abort"],
|
|
707
|
-
cwd=self.project_root,
|
|
708
|
-
capture_output=True,
|
|
709
|
-
check=True,
|
|
710
|
-
)
|
|
711
|
-
return True
|
|
712
|
-
except subprocess.CalledProcessError:
|
|
713
|
-
# If abort fails, try to reset hard to HEAD
|
|
714
|
-
try:
|
|
715
|
-
subprocess.run(
|
|
716
|
-
[git_path, "reset", "--hard", "HEAD"],
|
|
717
|
-
cwd=self.project_root,
|
|
718
|
-
capture_output=True,
|
|
719
|
-
check=True,
|
|
720
|
-
)
|
|
721
|
-
return True
|
|
722
|
-
except subprocess.CalledProcessError:
|
|
723
|
-
return False
|
|
724
|
-
|
|
1
|
+
"""
|
|
2
|
+
Worktree Manager - Manages Git worktrees for workflow steps.
|
|
3
|
+
|
|
4
|
+
This module handles worktree creation, artifact copying, and cleanup.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import hashlib
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
import re
|
|
13
|
+
import shutil
|
|
14
|
+
import subprocess # nosec B404 - fixed args, no shell
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from .models import Artifact, WorkflowStep
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class WorktreeManager:
|
|
25
|
+
"""
|
|
26
|
+
Manages Git worktrees for workflow step isolation.
|
|
27
|
+
|
|
28
|
+
Each workflow step runs in its own worktree to prevent conflicts
|
|
29
|
+
and enable clean rollback.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, project_root: Path):
|
|
33
|
+
"""
|
|
34
|
+
Initialize Worktree Manager.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
project_root: Root directory for the project
|
|
38
|
+
"""
|
|
39
|
+
self.project_root = project_root
|
|
40
|
+
self.worktrees_dir = project_root / ".tapps-agents" / "worktrees"
|
|
41
|
+
|
|
42
|
+
@staticmethod
|
|
43
|
+
def _sanitize_component(value: str, *, max_len: int = 80) -> str:
|
|
44
|
+
"""
|
|
45
|
+
Sanitize a user-provided identifier into a safe path/branch component.
|
|
46
|
+
|
|
47
|
+
- Avoids path separators and Windows-invalid characters
|
|
48
|
+
- Keeps names reasonably short to mitigate MAX_PATH issues
|
|
49
|
+
"""
|
|
50
|
+
value = (value or "").strip()
|
|
51
|
+
if not value:
|
|
52
|
+
return "worktree"
|
|
53
|
+
|
|
54
|
+
# Replace separators and invalid chars with '-'
|
|
55
|
+
value = value.replace("\\", "-").replace("/", "-")
|
|
56
|
+
value = re.sub(r"[^A-Za-z0-9._-]+", "-", value)
|
|
57
|
+
value = re.sub(r"-{2,}", "-", value).strip("-")
|
|
58
|
+
|
|
59
|
+
if not value:
|
|
60
|
+
return "worktree"
|
|
61
|
+
|
|
62
|
+
if len(value) > max_len:
|
|
63
|
+
digest = hashlib.sha256(value.encode("utf-8")).hexdigest()[:10]
|
|
64
|
+
value = f"{value[: max_len - 11]}-{digest}"
|
|
65
|
+
return value
|
|
66
|
+
|
|
67
|
+
def _safe_worktree_name(self, worktree_name: str) -> str:
|
|
68
|
+
# Ensure no traversal; store only as a single directory name.
|
|
69
|
+
if Path(worktree_name).is_absolute() or any(
|
|
70
|
+
part in {".", ".."} for part in Path(worktree_name).parts
|
|
71
|
+
):
|
|
72
|
+
raise ValueError(f"Invalid worktree name: {worktree_name!r}")
|
|
73
|
+
return self._sanitize_component(worktree_name, max_len=80)
|
|
74
|
+
|
|
75
|
+
def _worktree_path_for(self, worktree_name: str) -> Path:
|
|
76
|
+
safe_name = self._safe_worktree_name(worktree_name)
|
|
77
|
+
return self.worktrees_dir / safe_name
|
|
78
|
+
|
|
79
|
+
def _branch_for(self, worktree_name: str) -> str:
|
|
80
|
+
safe_name = self._sanitize_component(worktree_name, max_len=90)
|
|
81
|
+
# Keep branch names well under git limits and Windows path quirks.
|
|
82
|
+
if len(safe_name) > 90:
|
|
83
|
+
safe_name = safe_name[:90]
|
|
84
|
+
return f"workflow/{safe_name}"
|
|
85
|
+
|
|
86
|
+
async def create_worktree(self, worktree_name: str) -> Path:
|
|
87
|
+
"""
|
|
88
|
+
Create a new Git worktree for a workflow step.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
worktree_name: Name for the worktree
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
Path to the worktree
|
|
95
|
+
"""
|
|
96
|
+
worktree_path = self._worktree_path_for(worktree_name)
|
|
97
|
+
worktree_path.parent.mkdir(parents=True, exist_ok=True)
|
|
98
|
+
|
|
99
|
+
# Check if worktree already exists
|
|
100
|
+
if worktree_path.exists():
|
|
101
|
+
# Clean up existing worktree
|
|
102
|
+
await self.remove_worktree(worktree_path.name)
|
|
103
|
+
|
|
104
|
+
# Create worktree using git command
|
|
105
|
+
try:
|
|
106
|
+
git_path = shutil.which("git") or "git"
|
|
107
|
+
branch = self._branch_for(worktree_path.name)
|
|
108
|
+
subprocess.run(
|
|
109
|
+
[
|
|
110
|
+
git_path,
|
|
111
|
+
"worktree",
|
|
112
|
+
"add",
|
|
113
|
+
str(worktree_path),
|
|
114
|
+
"-b",
|
|
115
|
+
branch,
|
|
116
|
+
],
|
|
117
|
+
cwd=self.project_root,
|
|
118
|
+
capture_output=True,
|
|
119
|
+
text=True,
|
|
120
|
+
check=True,
|
|
121
|
+
)
|
|
122
|
+
except subprocess.CalledProcessError:
|
|
123
|
+
# If branch already exists, attach the worktree to it.
|
|
124
|
+
try:
|
|
125
|
+
git_path = shutil.which("git") or "git"
|
|
126
|
+
branch = self._branch_for(worktree_path.name)
|
|
127
|
+
subprocess.run(
|
|
128
|
+
[git_path, "worktree", "add", str(worktree_path), branch],
|
|
129
|
+
cwd=self.project_root,
|
|
130
|
+
capture_output=True,
|
|
131
|
+
text=True,
|
|
132
|
+
check=True,
|
|
133
|
+
)
|
|
134
|
+
except subprocess.CalledProcessError:
|
|
135
|
+
# If worktree creation fails, create a regular directory
|
|
136
|
+
worktree_path.mkdir(parents=True, exist_ok=True)
|
|
137
|
+
# Copy project files
|
|
138
|
+
self._copy_project_files(worktree_path)
|
|
139
|
+
|
|
140
|
+
return worktree_path
|
|
141
|
+
|
|
142
|
+
def _copy_project_files(self, dest: Path) -> None:
|
|
143
|
+
"""Copy project files to worktree (fallback if git worktree fails)."""
|
|
144
|
+
# Create a fresh .tapps-agents folder in the worktree (do not recursively copy worktrees).
|
|
145
|
+
(dest / ".tapps-agents").mkdir(parents=True, exist_ok=True)
|
|
146
|
+
|
|
147
|
+
essential_items = [
|
|
148
|
+
# Core repo content
|
|
149
|
+
"tapps_agents",
|
|
150
|
+
"tests",
|
|
151
|
+
"docs",
|
|
152
|
+
"workflows",
|
|
153
|
+
# Key project files
|
|
154
|
+
"pyproject.toml",
|
|
155
|
+
"README.md",
|
|
156
|
+
"LICENSE",
|
|
157
|
+
]
|
|
158
|
+
|
|
159
|
+
# If capabilities exist, include them (but never copy worktrees).
|
|
160
|
+
capabilities_dir = self.project_root / ".tapps-agents" / "capabilities"
|
|
161
|
+
if capabilities_dir.exists():
|
|
162
|
+
essential_items.append(str(Path(".tapps-agents") / "capabilities"))
|
|
163
|
+
|
|
164
|
+
for item in essential_items:
|
|
165
|
+
src = self.project_root / item
|
|
166
|
+
if not src.exists():
|
|
167
|
+
continue
|
|
168
|
+
|
|
169
|
+
dst = dest / item
|
|
170
|
+
if src.is_dir():
|
|
171
|
+
shutil.copytree(
|
|
172
|
+
src,
|
|
173
|
+
dst,
|
|
174
|
+
dirs_exist_ok=True,
|
|
175
|
+
ignore=shutil.ignore_patterns(
|
|
176
|
+
".git",
|
|
177
|
+
".venv",
|
|
178
|
+
"__pycache__",
|
|
179
|
+
".pytest_cache",
|
|
180
|
+
".ruff_cache",
|
|
181
|
+
".mypy_cache",
|
|
182
|
+
"htmlcov",
|
|
183
|
+
"dist",
|
|
184
|
+
"build",
|
|
185
|
+
"reports",
|
|
186
|
+
"worktrees",
|
|
187
|
+
),
|
|
188
|
+
)
|
|
189
|
+
else:
|
|
190
|
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
191
|
+
shutil.copy2(src, dst)
|
|
192
|
+
|
|
193
|
+
async def copy_artifacts(
|
|
194
|
+
self, worktree_path: Path, artifacts: list[Artifact]
|
|
195
|
+
) -> None:
|
|
196
|
+
"""
|
|
197
|
+
Copy artifacts from previous steps to worktree.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
worktree_path: Path to worktree
|
|
201
|
+
artifacts: List of artifacts to copy
|
|
202
|
+
"""
|
|
203
|
+
for artifact in artifacts:
|
|
204
|
+
# Ensure artifact is an Artifact object
|
|
205
|
+
if not isinstance(artifact, Artifact):
|
|
206
|
+
# Skip if not an Artifact object
|
|
207
|
+
continue
|
|
208
|
+
|
|
209
|
+
if not artifact.path:
|
|
210
|
+
continue
|
|
211
|
+
|
|
212
|
+
src_path = Path(artifact.path)
|
|
213
|
+
if not src_path.exists():
|
|
214
|
+
continue
|
|
215
|
+
|
|
216
|
+
# Determine destination path
|
|
217
|
+
if src_path.is_absolute() and self.project_root in src_path.parents:
|
|
218
|
+
# Relative to project root
|
|
219
|
+
rel_path = src_path.relative_to(self.project_root)
|
|
220
|
+
dest_path = worktree_path / rel_path
|
|
221
|
+
else:
|
|
222
|
+
# Use filename only
|
|
223
|
+
dest_path = worktree_path / src_path.name
|
|
224
|
+
|
|
225
|
+
# Copy file or directory
|
|
226
|
+
dest_path.parent.mkdir(parents=True, exist_ok=True)
|
|
227
|
+
if src_path.is_dir():
|
|
228
|
+
shutil.copytree(src_path, dest_path, dirs_exist_ok=True)
|
|
229
|
+
else:
|
|
230
|
+
shutil.copy2(src_path, dest_path)
|
|
231
|
+
|
|
232
|
+
async def extract_artifacts(
|
|
233
|
+
self, worktree_path: Path, step: WorkflowStep
|
|
234
|
+
) -> list[Artifact]:
|
|
235
|
+
"""
|
|
236
|
+
Extract artifacts created in worktree.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
worktree_path: Path to worktree
|
|
240
|
+
step: Workflow step definition
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
List of artifacts found
|
|
244
|
+
"""
|
|
245
|
+
artifacts: list[Artifact] = []
|
|
246
|
+
|
|
247
|
+
# Check for expected artifacts from step definition
|
|
248
|
+
if step.creates:
|
|
249
|
+
for artifact_name in step.creates:
|
|
250
|
+
artifact_path = worktree_path / artifact_name
|
|
251
|
+
if artifact_path.exists():
|
|
252
|
+
# Copy to main project
|
|
253
|
+
main_path = self.project_root / artifact_name
|
|
254
|
+
main_path.parent.mkdir(parents=True, exist_ok=True)
|
|
255
|
+
|
|
256
|
+
if artifact_path.is_dir():
|
|
257
|
+
shutil.copytree(artifact_path, main_path, dirs_exist_ok=True)
|
|
258
|
+
else:
|
|
259
|
+
shutil.copy2(artifact_path, main_path)
|
|
260
|
+
|
|
261
|
+
artifacts.append(
|
|
262
|
+
Artifact(
|
|
263
|
+
name=artifact_name,
|
|
264
|
+
path=str(main_path),
|
|
265
|
+
status="complete",
|
|
266
|
+
created_by=step.id,
|
|
267
|
+
created_at=datetime.now(),
|
|
268
|
+
metadata={"type": "file" if artifact_path.is_file() else "directory"},
|
|
269
|
+
)
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
# Also scan for common artifact patterns
|
|
273
|
+
common_patterns = [
|
|
274
|
+
"requirements.md",
|
|
275
|
+
"stories/*.md",
|
|
276
|
+
"architecture.md",
|
|
277
|
+
"api-specs/**/*",
|
|
278
|
+
"src/**/*.py",
|
|
279
|
+
"tests/**/*.py",
|
|
280
|
+
"docs/**/*.md",
|
|
281
|
+
]
|
|
282
|
+
|
|
283
|
+
for pattern in common_patterns:
|
|
284
|
+
for artifact_path in worktree_path.glob(pattern):
|
|
285
|
+
if artifact_path.is_file():
|
|
286
|
+
# Copy to main project
|
|
287
|
+
rel_path = artifact_path.relative_to(worktree_path)
|
|
288
|
+
main_path = self.project_root / rel_path
|
|
289
|
+
main_path.parent.mkdir(parents=True, exist_ok=True)
|
|
290
|
+
shutil.copy2(artifact_path, main_path)
|
|
291
|
+
|
|
292
|
+
artifacts.append(
|
|
293
|
+
Artifact(
|
|
294
|
+
name=artifact_path.name,
|
|
295
|
+
path=str(main_path),
|
|
296
|
+
status="complete",
|
|
297
|
+
created_by=step.id,
|
|
298
|
+
created_at=datetime.now(),
|
|
299
|
+
metadata={"type": "file"},
|
|
300
|
+
)
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
return artifacts
|
|
304
|
+
|
|
305
|
+
def _delete_branch(self, branch_name: str) -> bool:
|
|
306
|
+
"""
|
|
307
|
+
Delete a Git branch (safe delete with force fallback).
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
branch_name: Name of the branch to delete (e.g., "workflow/...")
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
True if branch was deleted or didn't exist, False on error
|
|
314
|
+
"""
|
|
315
|
+
git_path = shutil.which("git") or "git"
|
|
316
|
+
|
|
317
|
+
# Verify branch exists
|
|
318
|
+
try:
|
|
319
|
+
subprocess.run(
|
|
320
|
+
[git_path, "rev-parse", "--verify", branch_name],
|
|
321
|
+
cwd=self.project_root,
|
|
322
|
+
capture_output=True,
|
|
323
|
+
text=True,
|
|
324
|
+
check=True,
|
|
325
|
+
)
|
|
326
|
+
except subprocess.CalledProcessError:
|
|
327
|
+
# Branch doesn't exist - treat as success
|
|
328
|
+
logger.debug(f"Branch {branch_name} doesn't exist, nothing to delete")
|
|
329
|
+
return True
|
|
330
|
+
|
|
331
|
+
# Attempt safe delete first
|
|
332
|
+
try:
|
|
333
|
+
subprocess.run(
|
|
334
|
+
[git_path, "branch", "-d", branch_name],
|
|
335
|
+
cwd=self.project_root,
|
|
336
|
+
capture_output=True,
|
|
337
|
+
text=True,
|
|
338
|
+
encoding="utf-8",
|
|
339
|
+
errors="replace",
|
|
340
|
+
check=True,
|
|
341
|
+
)
|
|
342
|
+
logger.info(f"Successfully deleted branch: {branch_name}")
|
|
343
|
+
return True
|
|
344
|
+
except subprocess.CalledProcessError:
|
|
345
|
+
# Safe delete failed (branch not merged), try force delete
|
|
346
|
+
try:
|
|
347
|
+
subprocess.run(
|
|
348
|
+
[git_path, "branch", "-D", branch_name],
|
|
349
|
+
cwd=self.project_root,
|
|
350
|
+
capture_output=True,
|
|
351
|
+
text=True,
|
|
352
|
+
encoding="utf-8",
|
|
353
|
+
errors="replace",
|
|
354
|
+
check=True,
|
|
355
|
+
)
|
|
356
|
+
logger.info(f"Successfully force-deleted branch: {branch_name}")
|
|
357
|
+
return True
|
|
358
|
+
except subprocess.CalledProcessError as e:
|
|
359
|
+
logger.warning(
|
|
360
|
+
f"Failed to delete branch {branch_name}: {e.stderr or 'unknown error'}"
|
|
361
|
+
)
|
|
362
|
+
return False
|
|
363
|
+
|
|
364
|
+
async def remove_worktree(
|
|
365
|
+
self, worktree_name: str, delete_branch: bool = True
|
|
366
|
+
) -> None:
|
|
367
|
+
"""
|
|
368
|
+
Remove a worktree and optionally its associated branch.
|
|
369
|
+
|
|
370
|
+
Args:
|
|
371
|
+
worktree_name: Name of the worktree to remove
|
|
372
|
+
delete_branch: If True, also delete the associated Git branch
|
|
373
|
+
"""
|
|
374
|
+
worktree_path = self._worktree_path_for(worktree_name)
|
|
375
|
+
branch_name = self._branch_for(worktree_name)
|
|
376
|
+
|
|
377
|
+
if not worktree_path.exists():
|
|
378
|
+
return
|
|
379
|
+
|
|
380
|
+
# Try to remove via git worktree
|
|
381
|
+
try:
|
|
382
|
+
git_path = shutil.which("git") or "git"
|
|
383
|
+
subprocess.run(
|
|
384
|
+
[git_path, "worktree", "remove", str(worktree_path), "--force"],
|
|
385
|
+
cwd=self.project_root,
|
|
386
|
+
capture_output=True,
|
|
387
|
+
check=False, # Don't fail if git command fails
|
|
388
|
+
)
|
|
389
|
+
except Exception:
|
|
390
|
+
pass
|
|
391
|
+
|
|
392
|
+
# Fallback: remove directory
|
|
393
|
+
if worktree_path.exists():
|
|
394
|
+
shutil.rmtree(worktree_path, ignore_errors=True)
|
|
395
|
+
|
|
396
|
+
# Delete the branch if requested
|
|
397
|
+
if delete_branch:
|
|
398
|
+
self._delete_branch(branch_name)
|
|
399
|
+
|
|
400
|
+
async def cleanup_all(self) -> None:
|
|
401
|
+
"""Clean up all worktrees."""
|
|
402
|
+
if not self.worktrees_dir.exists():
|
|
403
|
+
return
|
|
404
|
+
|
|
405
|
+
for worktree_dir in self.worktrees_dir.iterdir():
|
|
406
|
+
if worktree_dir.is_dir():
|
|
407
|
+
await self.remove_worktree(worktree_dir.name)
|
|
408
|
+
|
|
409
|
+
# Best-effort prune to clean up stale worktree metadata.
|
|
410
|
+
try:
|
|
411
|
+
git_path = shutil.which("git") or "git"
|
|
412
|
+
subprocess.run(
|
|
413
|
+
[git_path, "worktree", "prune"],
|
|
414
|
+
cwd=self.project_root,
|
|
415
|
+
capture_output=True,
|
|
416
|
+
check=False,
|
|
417
|
+
)
|
|
418
|
+
except Exception:
|
|
419
|
+
pass
|
|
420
|
+
|
|
421
|
+
def _check_working_tree_clean(self) -> bool:
|
|
422
|
+
"""
|
|
423
|
+
Check if the working tree is clean (no uncommitted changes).
|
|
424
|
+
|
|
425
|
+
Note: Untracked files are ignored as they don't affect merge operations.
|
|
426
|
+
|
|
427
|
+
Returns:
|
|
428
|
+
True if working tree is clean, False otherwise
|
|
429
|
+
"""
|
|
430
|
+
try:
|
|
431
|
+
git_path = shutil.which("git") or "git"
|
|
432
|
+
result = subprocess.run(
|
|
433
|
+
[git_path, "status", "--porcelain", "--untracked-files=no"],
|
|
434
|
+
cwd=self.project_root,
|
|
435
|
+
capture_output=True,
|
|
436
|
+
text=True,
|
|
437
|
+
check=True,
|
|
438
|
+
)
|
|
439
|
+
return not result.stdout.strip()
|
|
440
|
+
except subprocess.CalledProcessError:
|
|
441
|
+
return False
|
|
442
|
+
|
|
443
|
+
def _get_current_branch(self) -> str:
|
|
444
|
+
"""
|
|
445
|
+
Get the current branch name.
|
|
446
|
+
|
|
447
|
+
Returns:
|
|
448
|
+
Current branch name
|
|
449
|
+
|
|
450
|
+
Raises:
|
|
451
|
+
RuntimeError: If unable to determine current branch
|
|
452
|
+
"""
|
|
453
|
+
try:
|
|
454
|
+
git_path = shutil.which("git") or "git"
|
|
455
|
+
result = subprocess.run(
|
|
456
|
+
[git_path, "rev-parse", "--abbrev-ref", "HEAD"],
|
|
457
|
+
cwd=self.project_root,
|
|
458
|
+
capture_output=True,
|
|
459
|
+
text=True,
|
|
460
|
+
check=True,
|
|
461
|
+
)
|
|
462
|
+
return result.stdout.strip()
|
|
463
|
+
except subprocess.CalledProcessError as e:
|
|
464
|
+
raise RuntimeError(f"Failed to get current branch: {e.stderr}") from e
|
|
465
|
+
|
|
466
|
+
def _detect_conflicts(self) -> list[str]:
|
|
467
|
+
"""
|
|
468
|
+
Detect conflicted files in the working tree.
|
|
469
|
+
|
|
470
|
+
Returns:
|
|
471
|
+
List of file paths with conflicts
|
|
472
|
+
"""
|
|
473
|
+
conflicted_files: list[str] = []
|
|
474
|
+
try:
|
|
475
|
+
git_path = shutil.which("git") or "git"
|
|
476
|
+
result = subprocess.run(
|
|
477
|
+
[git_path, "diff", "--name-only", "--diff-filter=U"],
|
|
478
|
+
cwd=self.project_root,
|
|
479
|
+
capture_output=True,
|
|
480
|
+
text=True,
|
|
481
|
+
check=True,
|
|
482
|
+
)
|
|
483
|
+
for line in result.stdout.strip().split("\n"):
|
|
484
|
+
if line.strip():
|
|
485
|
+
conflicted_files.append(line.strip())
|
|
486
|
+
except subprocess.CalledProcessError:
|
|
487
|
+
# If command fails, try alternative method
|
|
488
|
+
try:
|
|
489
|
+
git_path = shutil.which("git") or "git"
|
|
490
|
+
result = subprocess.run(
|
|
491
|
+
[git_path, "status", "--porcelain"],
|
|
492
|
+
cwd=self.project_root,
|
|
493
|
+
capture_output=True,
|
|
494
|
+
text=True,
|
|
495
|
+
check=True,
|
|
496
|
+
)
|
|
497
|
+
for line in result.stdout.strip().split("\n"):
|
|
498
|
+
if line.strip() and line.startswith("UU"):
|
|
499
|
+
# UU indicates unmerged (conflicted) files
|
|
500
|
+
file_path = line[3:].strip()
|
|
501
|
+
conflicted_files.append(file_path)
|
|
502
|
+
except subprocess.CalledProcessError:
|
|
503
|
+
pass
|
|
504
|
+
return conflicted_files
|
|
505
|
+
|
|
506
|
+
def _write_conflict_report(
|
|
507
|
+
self,
|
|
508
|
+
worktree_name: str,
|
|
509
|
+
branch_name: str,
|
|
510
|
+
target_branch: str,
|
|
511
|
+
conflicted_files: list[str],
|
|
512
|
+
merge_error: str | None = None,
|
|
513
|
+
) -> Path:
|
|
514
|
+
"""
|
|
515
|
+
Write a conflict report to a JSON file.
|
|
516
|
+
|
|
517
|
+
Args:
|
|
518
|
+
worktree_name: Name of the worktree being merged
|
|
519
|
+
branch_name: Name of the worktree branch
|
|
520
|
+
target_branch: Target branch for merge
|
|
521
|
+
conflicted_files: List of conflicted file paths
|
|
522
|
+
merge_error: Optional error message from merge attempt
|
|
523
|
+
|
|
524
|
+
Returns:
|
|
525
|
+
Path to the conflict report file
|
|
526
|
+
"""
|
|
527
|
+
reports_dir = self.project_root / ".tapps-agents" / "reports"
|
|
528
|
+
reports_dir.mkdir(parents=True, exist_ok=True)
|
|
529
|
+
|
|
530
|
+
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
|
|
531
|
+
report_filename = f"merge-conflict-{worktree_name}-{timestamp}.json"
|
|
532
|
+
report_path = reports_dir / report_filename
|
|
533
|
+
|
|
534
|
+
report_data = {
|
|
535
|
+
"timestamp": datetime.now().isoformat(),
|
|
536
|
+
"worktree_name": worktree_name,
|
|
537
|
+
"worktree_branch": branch_name,
|
|
538
|
+
"target_branch": target_branch,
|
|
539
|
+
"conflicted_files": conflicted_files,
|
|
540
|
+
"conflict_count": len(conflicted_files),
|
|
541
|
+
"merge_error": merge_error,
|
|
542
|
+
"guidance": {
|
|
543
|
+
"resolution_steps": [
|
|
544
|
+
"1. Review conflicted files listed above",
|
|
545
|
+
"2. Manually resolve conflicts in each file",
|
|
546
|
+
"3. Mark conflicts as resolved: git add <file>",
|
|
547
|
+
"4. Complete merge: git commit",
|
|
548
|
+
"5. Or abort merge: git merge --abort",
|
|
549
|
+
],
|
|
550
|
+
"abort_command": "git merge --abort",
|
|
551
|
+
},
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
with open(report_path, "w", encoding="utf-8") as f:
|
|
555
|
+
json.dump(report_data, f, indent=2)
|
|
556
|
+
|
|
557
|
+
return report_path
|
|
558
|
+
|
|
559
|
+
async def merge_worktree(
|
|
560
|
+
self,
|
|
561
|
+
worktree_name: str,
|
|
562
|
+
target_branch: str | None = None,
|
|
563
|
+
) -> dict[str, Any]:
|
|
564
|
+
"""
|
|
565
|
+
Merge a worktree branch into the target branch.
|
|
566
|
+
|
|
567
|
+
Epic 1 / Story 1.5: Worktree Merge & Conflict Resolution
|
|
568
|
+
|
|
569
|
+
Args:
|
|
570
|
+
worktree_name: Name of the worktree to merge
|
|
571
|
+
target_branch: Target branch to merge into (defaults to current branch)
|
|
572
|
+
|
|
573
|
+
Returns:
|
|
574
|
+
Dictionary with merge result:
|
|
575
|
+
- success: bool
|
|
576
|
+
- has_conflicts: bool
|
|
577
|
+
- conflicted_files: list[str]
|
|
578
|
+
- conflict_report_path: Path | None
|
|
579
|
+
- error: str | None
|
|
580
|
+
|
|
581
|
+
Raises:
|
|
582
|
+
ValueError: If working tree is not clean or worktree doesn't exist
|
|
583
|
+
RuntimeError: If unable to determine current branch
|
|
584
|
+
"""
|
|
585
|
+
# Validate worktree exists
|
|
586
|
+
worktree_path = self._worktree_path_for(worktree_name)
|
|
587
|
+
if not worktree_path.exists():
|
|
588
|
+
raise ValueError(f"Worktree {worktree_name} does not exist")
|
|
589
|
+
|
|
590
|
+
# Check working tree is clean
|
|
591
|
+
if not self._check_working_tree_clean():
|
|
592
|
+
raise ValueError(
|
|
593
|
+
"Working tree is not clean. Please commit or stash changes before merging."
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
# Get target branch
|
|
597
|
+
if target_branch is None:
|
|
598
|
+
target_branch = self._get_current_branch()
|
|
599
|
+
|
|
600
|
+
# Get worktree branch name
|
|
601
|
+
branch_name = self._branch_for(worktree_name)
|
|
602
|
+
|
|
603
|
+
# Verify branch exists
|
|
604
|
+
git_path = shutil.which("git") or "git"
|
|
605
|
+
try:
|
|
606
|
+
subprocess.run(
|
|
607
|
+
[git_path, "rev-parse", "--verify", branch_name],
|
|
608
|
+
cwd=self.project_root,
|
|
609
|
+
capture_output=True,
|
|
610
|
+
check=True,
|
|
611
|
+
)
|
|
612
|
+
except subprocess.CalledProcessError:
|
|
613
|
+
return {
|
|
614
|
+
"success": False,
|
|
615
|
+
"has_conflicts": False,
|
|
616
|
+
"conflicted_files": [],
|
|
617
|
+
"conflict_report_path": None,
|
|
618
|
+
"error": f"Branch {branch_name} does not exist",
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
# Attempt merge
|
|
622
|
+
try:
|
|
623
|
+
subprocess.run(
|
|
624
|
+
[git_path, "merge", "--no-ff", "--no-commit", branch_name],
|
|
625
|
+
cwd=self.project_root,
|
|
626
|
+
capture_output=True,
|
|
627
|
+
text=True,
|
|
628
|
+
check=True,
|
|
629
|
+
)
|
|
630
|
+
|
|
631
|
+
# Merge succeeded without conflicts
|
|
632
|
+
# Complete the merge
|
|
633
|
+
subprocess.run(
|
|
634
|
+
[git_path, "commit", "--no-edit"],
|
|
635
|
+
cwd=self.project_root,
|
|
636
|
+
capture_output=True,
|
|
637
|
+
check=True,
|
|
638
|
+
)
|
|
639
|
+
|
|
640
|
+
return {
|
|
641
|
+
"success": True,
|
|
642
|
+
"has_conflicts": False,
|
|
643
|
+
"conflicted_files": [],
|
|
644
|
+
"conflict_report_path": None,
|
|
645
|
+
"error": None,
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
except subprocess.CalledProcessError as e:
|
|
649
|
+
# Merge failed - check for conflicts
|
|
650
|
+
conflicted_files = self._detect_conflicts()
|
|
651
|
+
|
|
652
|
+
if conflicted_files:
|
|
653
|
+
# Conflicts detected - write report
|
|
654
|
+
report_path = self._write_conflict_report(
|
|
655
|
+
worktree_name=worktree_name,
|
|
656
|
+
branch_name=branch_name,
|
|
657
|
+
target_branch=target_branch,
|
|
658
|
+
conflicted_files=conflicted_files,
|
|
659
|
+
merge_error=e.stderr,
|
|
660
|
+
)
|
|
661
|
+
|
|
662
|
+
return {
|
|
663
|
+
"success": False,
|
|
664
|
+
"has_conflicts": True,
|
|
665
|
+
"conflicted_files": conflicted_files,
|
|
666
|
+
"conflict_report_path": report_path,
|
|
667
|
+
"error": f"Merge conflicts detected in {len(conflicted_files)} file(s)",
|
|
668
|
+
}
|
|
669
|
+
else:
|
|
670
|
+
# Merge failed for other reasons
|
|
671
|
+
return {
|
|
672
|
+
"success": False,
|
|
673
|
+
"has_conflicts": False,
|
|
674
|
+
"conflicted_files": [],
|
|
675
|
+
"conflict_report_path": None,
|
|
676
|
+
"error": f"Merge failed: {e.stderr}",
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
async def abort_merge(self) -> bool:
|
|
680
|
+
"""
|
|
681
|
+
Abort an in-progress merge and restore working tree to pre-merge state.
|
|
682
|
+
|
|
683
|
+
Epic 1 / Story 1.5: Worktree Merge & Conflict Resolution
|
|
684
|
+
|
|
685
|
+
Returns:
|
|
686
|
+
True if merge was aborted successfully, False otherwise
|
|
687
|
+
"""
|
|
688
|
+
# Check if merge is in progress
|
|
689
|
+
git_path = shutil.which("git") or "git"
|
|
690
|
+
try:
|
|
691
|
+
result = subprocess.run(
|
|
692
|
+
[git_path, "rev-parse", "--verify", "MERGE_HEAD"],
|
|
693
|
+
cwd=self.project_root,
|
|
694
|
+
capture_output=True,
|
|
695
|
+
check=False,
|
|
696
|
+
)
|
|
697
|
+
if result.returncode != 0:
|
|
698
|
+
# No merge in progress
|
|
699
|
+
return False
|
|
700
|
+
except Exception:
|
|
701
|
+
return False
|
|
702
|
+
|
|
703
|
+
# Abort merge
|
|
704
|
+
try:
|
|
705
|
+
subprocess.run(
|
|
706
|
+
[git_path, "merge", "--abort"],
|
|
707
|
+
cwd=self.project_root,
|
|
708
|
+
capture_output=True,
|
|
709
|
+
check=True,
|
|
710
|
+
)
|
|
711
|
+
return True
|
|
712
|
+
except subprocess.CalledProcessError:
|
|
713
|
+
# If abort fails, try to reset hard to HEAD
|
|
714
|
+
try:
|
|
715
|
+
subprocess.run(
|
|
716
|
+
[git_path, "reset", "--hard", "HEAD"],
|
|
717
|
+
cwd=self.project_root,
|
|
718
|
+
capture_output=True,
|
|
719
|
+
check=True,
|
|
720
|
+
)
|
|
721
|
+
return True
|
|
722
|
+
except subprocess.CalledProcessError:
|
|
723
|
+
return False
|
|
724
|
+
|