tapps-agents 3.5.40__py3-none-any.whl → 3.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/scoring.py +1566 -1566
- tapps_agents/agents/reviewer/service_discovery.py +534 -534
- tapps_agents/agents/reviewer/tools/__init__.py +41 -41
- 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/health.py +665 -665
- 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 -219
- tapps_agents/cli/commands/tester.py +191 -191
- tapps_agents/cli/commands/top_level.py +3586 -3586
- 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/artifact_context_builder.py +293 -0
- 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/config.py +33 -0
- 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.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/orchestrator.py +271 -271
- 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/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/base.py +185 -185
- 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/build_orchestrator.py +2700 -2667
- 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/fix_orchestrator.py +723 -723
- 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_executor.py +2337 -2196
- 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/message_formatter.py +188 -188
- 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 -111
- 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.5.40.dist-info → tapps_agents-3.6.0.dist-info}/METADATA +672 -672
- tapps_agents-3.6.0.dist-info/RECORD +758 -0
- {tapps_agents-3.5.40.dist-info → tapps_agents-3.6.0.dist-info}/licenses/LICENSE +22 -22
- tapps_agents/health/checks/outcomes.backup_20260204_064058.py +0 -324
- tapps_agents/health/checks/outcomes.backup_20260204_064256.py +0 -324
- tapps_agents/health/checks/outcomes.backup_20260204_064600.py +0 -324
- tapps_agents-3.5.40.dist-info/RECORD +0 -760
- {tapps_agents-3.5.40.dist-info → tapps_agents-3.6.0.dist-info}/WHEEL +0 -0
- {tapps_agents-3.5.40.dist-info → tapps_agents-3.6.0.dist-info}/entry_points.txt +0 -0
- {tapps_agents-3.5.40.dist-info → tapps_agents-3.6.0.dist-info}/top_level.txt +0 -0
|
@@ -1,1127 +1,1127 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Planner Agent - Creates user stories and task breakdowns
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import logging
|
|
6
|
-
import re
|
|
7
|
-
from datetime import datetime
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
from typing import Any
|
|
10
|
-
|
|
11
|
-
import yaml
|
|
12
|
-
|
|
13
|
-
from ...agents.analyst.agent import AnalystAgent
|
|
14
|
-
from ...context7.agent_integration import Context7AgentHelper, get_context7_helper
|
|
15
|
-
from ...core.agent_base import BaseAgent
|
|
16
|
-
from ...core.config import ProjectConfig, load_config
|
|
17
|
-
from ...core.instructions import GenericInstruction
|
|
18
|
-
from ...core.runtime_mode import is_cursor_mode
|
|
19
|
-
|
|
20
|
-
logger = logging.getLogger(__name__)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class PlannerAgent(BaseAgent):
|
|
24
|
-
"""
|
|
25
|
-
Planner Agent - Story/epic planning and task breakdown.
|
|
26
|
-
|
|
27
|
-
Permissions: Read, Write, Grep, Glob
|
|
28
|
-
|
|
29
|
-
⚠️ CRITICAL ACCURACY REQUIREMENT:
|
|
30
|
-
- NEVER make up, invent, or fabricate information - Only report verified facts
|
|
31
|
-
- ALWAYS verify claims by checking actual results, not just test pass/fail
|
|
32
|
-
- Verify API calls succeed - inspect response data, status codes, error messages
|
|
33
|
-
- Distinguish between code paths executing and actual functionality working
|
|
34
|
-
- Admit uncertainty explicitly when you cannot verify
|
|
35
|
-
"""
|
|
36
|
-
|
|
37
|
-
def __init__(self, config: ProjectConfig | None = None):
|
|
38
|
-
super().__init__(agent_id="planner", agent_name="Planner Agent", config=config)
|
|
39
|
-
# Use config if provided, otherwise load defaults
|
|
40
|
-
if config is None:
|
|
41
|
-
config = load_config()
|
|
42
|
-
self.config = config
|
|
43
|
-
|
|
44
|
-
# Story storage path (from config or default)
|
|
45
|
-
planner_config = config.agents.planner if config and config.agents else None
|
|
46
|
-
stories_dir_str = planner_config.stories_dir if planner_config else None
|
|
47
|
-
self.stories_dir = Path(stories_dir_str) if stories_dir_str else None
|
|
48
|
-
|
|
49
|
-
# Initialize Context7 helper (Enhancement: Universal Context7 integration)
|
|
50
|
-
self.context7: Context7AgentHelper | None = None
|
|
51
|
-
if config:
|
|
52
|
-
self.context7 = get_context7_helper(self, config)
|
|
53
|
-
|
|
54
|
-
def get_commands(self) -> list[dict[str, str]]:
|
|
55
|
-
"""Return available commands for planner agent"""
|
|
56
|
-
base_commands = super().get_commands()
|
|
57
|
-
return base_commands + [
|
|
58
|
-
{
|
|
59
|
-
"command": "*plan",
|
|
60
|
-
"description": "Create a plan for a feature/requirement",
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
"command": "*create-story",
|
|
64
|
-
"description": "Generate a user story from description",
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
"command": "*list-stories",
|
|
68
|
-
"description": "List all stories in the project",
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
"command": "*evaluate-stories",
|
|
72
|
-
"description": "Evaluate story quality using INVEST criteria",
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
"command": "*validate-stories",
|
|
76
|
-
"description": "Validate stories for completeness and quality",
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
"command": "*trace-stories",
|
|
80
|
-
"description": "Map stories to acceptance criteria and test cases",
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
"command": "*review-stories",
|
|
84
|
-
"description": "Structured review of stories with INVEST checklist",
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
"command": "*calibrate-estimates",
|
|
88
|
-
"description": "Get calibrated estimates based on historical accuracy",
|
|
89
|
-
},
|
|
90
|
-
]
|
|
91
|
-
|
|
92
|
-
async def run(self, command: str, **kwargs) -> dict[str, Any]:
|
|
93
|
-
"""
|
|
94
|
-
Execute planner agent command.
|
|
95
|
-
|
|
96
|
-
Commands:
|
|
97
|
-
- *plan <description>: Create a plan
|
|
98
|
-
- *create-story <description> [--epic=<epic>] [--priority=<priority>]: Create a story
|
|
99
|
-
- *list-stories [--epic=<epic>] [--status=<status>]: List stories
|
|
100
|
-
"""
|
|
101
|
-
await self.activate()
|
|
102
|
-
|
|
103
|
-
if command == "help":
|
|
104
|
-
return self._help()
|
|
105
|
-
|
|
106
|
-
elif command == "plan":
|
|
107
|
-
description = kwargs.get("description") or kwargs.get("text", "")
|
|
108
|
-
if not description:
|
|
109
|
-
return {"error": "Description required. Usage: *plan <description>"}
|
|
110
|
-
|
|
111
|
-
generate_doc = kwargs.get("generate_doc", False) or kwargs.get("generate-doc", False)
|
|
112
|
-
output_file = kwargs.get("output_file") or kwargs.get("output-file")
|
|
113
|
-
output_format = kwargs.get("output_format", "markdown") or kwargs.get("output-format", "markdown")
|
|
114
|
-
|
|
115
|
-
result = await self.create_plan(description)
|
|
116
|
-
|
|
117
|
-
# Generate document if requested
|
|
118
|
-
if generate_doc:
|
|
119
|
-
from ...core.document_generator import DocumentGenerator
|
|
120
|
-
doc_generator = DocumentGenerator(project_root=self._project_root)
|
|
121
|
-
|
|
122
|
-
# Determine output file if not provided
|
|
123
|
-
if not output_file:
|
|
124
|
-
docs_dir = self._project_root / "docs" / "plans"
|
|
125
|
-
docs_dir.mkdir(parents=True, exist_ok=True)
|
|
126
|
-
# Create filename from description
|
|
127
|
-
safe_name = "".join(c if c.isalnum() or c in (' ', '-', '_') else '_' for c in description[:50])
|
|
128
|
-
safe_name = safe_name.replace(' ', '_').lower()
|
|
129
|
-
output_file = docs_dir / f"{safe_name}_plan.{output_format if output_format != 'html' else 'html'}"
|
|
130
|
-
|
|
131
|
-
# Generate document
|
|
132
|
-
doc_path = doc_generator.generate_plan_doc(
|
|
133
|
-
plan_data=result,
|
|
134
|
-
output_file=output_file,
|
|
135
|
-
format=output_format,
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
result["document"] = {
|
|
139
|
-
"path": str(doc_path),
|
|
140
|
-
"format": output_format,
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return result
|
|
144
|
-
|
|
145
|
-
elif command == "create-story":
|
|
146
|
-
description = kwargs.get("description") or kwargs.get("text", "")
|
|
147
|
-
if not description:
|
|
148
|
-
return {
|
|
149
|
-
"error": "Description required. Usage: *create-story <description> [--epic=<epic>] [--priority=<priority>]"
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
epic = kwargs.get("epic")
|
|
153
|
-
priority = kwargs.get("priority", "medium")
|
|
154
|
-
generate_doc = kwargs.get("generate_doc", False) or kwargs.get("generate-doc", False)
|
|
155
|
-
output_file = kwargs.get("output_file") or kwargs.get("output-file")
|
|
156
|
-
output_format = kwargs.get("output_format", "markdown") or kwargs.get("output-format", "markdown")
|
|
157
|
-
|
|
158
|
-
result = await self.create_story(description, epic=epic, priority=priority)
|
|
159
|
-
|
|
160
|
-
# Generate document if requested
|
|
161
|
-
if generate_doc:
|
|
162
|
-
from ...core.document_generator import DocumentGenerator
|
|
163
|
-
doc_generator = DocumentGenerator(project_root=self._project_root)
|
|
164
|
-
|
|
165
|
-
# Determine output file if not provided
|
|
166
|
-
if not output_file:
|
|
167
|
-
stories_dir = self.stories_dir or (self._project_root / "stories")
|
|
168
|
-
stories_dir.mkdir(parents=True, exist_ok=True)
|
|
169
|
-
safe_name = "".join(c if c.isalnum() or c in (' ', '-', '_') else '_' for c in description[:50])
|
|
170
|
-
safe_name = safe_name.replace(' ', '_').lower()
|
|
171
|
-
output_file = stories_dir / f"{safe_name}_story.{output_format if output_format != 'html' else 'html'}"
|
|
172
|
-
|
|
173
|
-
# Generate document
|
|
174
|
-
doc_path = doc_generator.generate_user_story_doc(
|
|
175
|
-
story_data=result,
|
|
176
|
-
output_file=output_file,
|
|
177
|
-
format=output_format,
|
|
178
|
-
)
|
|
179
|
-
|
|
180
|
-
result["document"] = {
|
|
181
|
-
"path": str(doc_path),
|
|
182
|
-
"format": output_format,
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
return result
|
|
186
|
-
|
|
187
|
-
elif command == "list-stories":
|
|
188
|
-
epic_filter = kwargs.get("epic")
|
|
189
|
-
status_filter = kwargs.get("status")
|
|
190
|
-
return await self.list_stories(epic=epic_filter, status=status_filter)
|
|
191
|
-
|
|
192
|
-
elif command == "evaluate-stories":
|
|
193
|
-
stories = kwargs.get("stories", [])
|
|
194
|
-
if isinstance(stories, str):
|
|
195
|
-
# Try to load from file
|
|
196
|
-
story_path = Path(stories)
|
|
197
|
-
if story_path.exists():
|
|
198
|
-
import json
|
|
199
|
-
stories = json.loads(story_path.read_text(encoding="utf-8"))
|
|
200
|
-
else:
|
|
201
|
-
return {"error": f"Stories file not found: {stories}"}
|
|
202
|
-
|
|
203
|
-
return await self._evaluate_stories(stories)
|
|
204
|
-
|
|
205
|
-
elif command == "validate-stories":
|
|
206
|
-
stories = kwargs.get("stories", [])
|
|
207
|
-
if isinstance(stories, str):
|
|
208
|
-
# Try to load from file
|
|
209
|
-
story_path = Path(stories)
|
|
210
|
-
if story_path.exists():
|
|
211
|
-
import json
|
|
212
|
-
stories = json.loads(story_path.read_text(encoding="utf-8"))
|
|
213
|
-
else:
|
|
214
|
-
return {"error": f"Stories file not found: {stories}"}
|
|
215
|
-
|
|
216
|
-
return await self._validate_stories(stories)
|
|
217
|
-
|
|
218
|
-
elif command == "trace-stories":
|
|
219
|
-
stories = kwargs.get("stories", [])
|
|
220
|
-
test_cases = kwargs.get("test_cases", [])
|
|
221
|
-
output_file = kwargs.get("output_file", None)
|
|
222
|
-
|
|
223
|
-
return await self._trace_stories(stories, test_cases, output_file)
|
|
224
|
-
|
|
225
|
-
elif command == "review-stories":
|
|
226
|
-
stories = kwargs.get("stories", [])
|
|
227
|
-
if isinstance(stories, str):
|
|
228
|
-
story_path = Path(stories)
|
|
229
|
-
if story_path.exists():
|
|
230
|
-
import json
|
|
231
|
-
stories = json.loads(story_path.read_text(encoding="utf-8"))
|
|
232
|
-
else:
|
|
233
|
-
return {"error": f"Stories file not found: {stories}"}
|
|
234
|
-
|
|
235
|
-
return await self._review_stories(stories)
|
|
236
|
-
|
|
237
|
-
elif command == "calibrate-estimates":
|
|
238
|
-
estimated_points = kwargs.get("estimated_points", 0)
|
|
239
|
-
complexity = kwargs.get("complexity", "medium")
|
|
240
|
-
|
|
241
|
-
if estimated_points <= 0:
|
|
242
|
-
return {"error": "estimated_points must be greater than 0"}
|
|
243
|
-
|
|
244
|
-
return await self._calibrate_estimates(estimated_points, complexity)
|
|
245
|
-
|
|
246
|
-
else:
|
|
247
|
-
return {
|
|
248
|
-
"error": f"Unknown command: {command}. Use *help to see available commands."
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
async def create_plan(self, description: str) -> dict[str, Any]:
|
|
252
|
-
"""
|
|
253
|
-
Create a plan for a feature or requirement.
|
|
254
|
-
|
|
255
|
-
This uses the LLM to analyze the description and generate:
|
|
256
|
-
- Story breakdown
|
|
257
|
-
- Task estimates
|
|
258
|
-
- Dependencies
|
|
259
|
-
"""
|
|
260
|
-
# If in Cursor mode, return instruction for Cursor Skills
|
|
261
|
-
if is_cursor_mode():
|
|
262
|
-
prompt = f"""You are a software planning expert. Analyze the following requirement and create a detailed plan.
|
|
263
|
-
|
|
264
|
-
Requirement:
|
|
265
|
-
{description}
|
|
266
|
-
|
|
267
|
-
Generate a plan that includes:
|
|
268
|
-
1. Overview of the feature/requirement
|
|
269
|
-
2. List of user stories (with brief descriptions)
|
|
270
|
-
3. Estimated complexity for each story (1-5 scale)
|
|
271
|
-
4. Dependencies between stories
|
|
272
|
-
5. Suggested priority order
|
|
273
|
-
|
|
274
|
-
Format your response as structured text."""
|
|
275
|
-
|
|
276
|
-
# Prepare instruction for Cursor Skills
|
|
277
|
-
instruction = GenericInstruction(
|
|
278
|
-
agent_name="planner",
|
|
279
|
-
command="plan",
|
|
280
|
-
prompt=prompt,
|
|
281
|
-
parameters={"description": description},
|
|
282
|
-
)
|
|
283
|
-
|
|
284
|
-
return {
|
|
285
|
-
"type": "plan",
|
|
286
|
-
"description": description,
|
|
287
|
-
"instruction": instruction.to_dict(),
|
|
288
|
-
"skill_command": instruction.to_skill_command(),
|
|
289
|
-
"created_at": datetime.now().isoformat(),
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
# For CLI mode, actually generate the plan using analyst agent
|
|
293
|
-
analyst = None
|
|
294
|
-
try:
|
|
295
|
-
analyst = AnalystAgent(config=self.config)
|
|
296
|
-
await analyst.activate(project_root=self._project_root, offline_mode=False)
|
|
297
|
-
|
|
298
|
-
# Gather requirements first
|
|
299
|
-
requirements_result = await analyst.run(
|
|
300
|
-
"gather-requirements",
|
|
301
|
-
description=description,
|
|
302
|
-
)
|
|
303
|
-
|
|
304
|
-
# Check for errors in result
|
|
305
|
-
if isinstance(requirements_result, dict) and "error" in requirements_result:
|
|
306
|
-
error_msg = requirements_result.get("error", "Unknown error")
|
|
307
|
-
logger.warning(f"Analyst agent returned error: {error_msg}")
|
|
308
|
-
raise RuntimeError(f"Requirements gathering failed: {error_msg}")
|
|
309
|
-
|
|
310
|
-
# Extract requirements information
|
|
311
|
-
requirements = requirements_result.get("requirements", {})
|
|
312
|
-
if isinstance(requirements, dict):
|
|
313
|
-
functional_reqs = requirements.get("functional_requirements", [])
|
|
314
|
-
non_functional_reqs = requirements.get("non_functional_requirements", [])
|
|
315
|
-
else:
|
|
316
|
-
functional_reqs = []
|
|
317
|
-
non_functional_reqs = []
|
|
318
|
-
|
|
319
|
-
# Generate user stories in standard format
|
|
320
|
-
user_stories = await self._generate_user_stories(description, functional_reqs)
|
|
321
|
-
|
|
322
|
-
# Build plan structure with user stories
|
|
323
|
-
plan_text = self._format_plan_markdown(
|
|
324
|
-
description=description,
|
|
325
|
-
requirements_result=requirements_result,
|
|
326
|
-
functional_reqs=functional_reqs,
|
|
327
|
-
non_functional_reqs=non_functional_reqs,
|
|
328
|
-
user_stories=user_stories,
|
|
329
|
-
)
|
|
330
|
-
|
|
331
|
-
return {
|
|
332
|
-
"type": "plan",
|
|
333
|
-
"description": description,
|
|
334
|
-
"plan": plan_text,
|
|
335
|
-
"requirements": requirements,
|
|
336
|
-
"user_stories": user_stories,
|
|
337
|
-
"markdown": plan_text,
|
|
338
|
-
"created_at": datetime.now().isoformat(),
|
|
339
|
-
}
|
|
340
|
-
except (ConnectionError, TimeoutError, OSError) as e:
|
|
341
|
-
# Network-related errors
|
|
342
|
-
error_msg = f"Network error: {str(e)}"
|
|
343
|
-
logger.error(f"Network error generating plan: {e}", exc_info=True)
|
|
344
|
-
# Fallback to instruction if analyst fails
|
|
345
|
-
prompt = f"""You are a software planning expert. Analyze the following requirement and create a detailed plan.
|
|
346
|
-
|
|
347
|
-
Requirement:
|
|
348
|
-
{description}
|
|
349
|
-
|
|
350
|
-
Generate a plan that includes:
|
|
351
|
-
1. Overview of the feature/requirement
|
|
352
|
-
2. List of user stories (with brief descriptions)
|
|
353
|
-
3. Estimated complexity for each story (1-5 scale)
|
|
354
|
-
4. Dependencies between stories
|
|
355
|
-
5. Suggested priority order
|
|
356
|
-
|
|
357
|
-
Format your response as structured text."""
|
|
358
|
-
|
|
359
|
-
instruction = GenericInstruction(
|
|
360
|
-
agent_name="planner",
|
|
361
|
-
command="plan",
|
|
362
|
-
prompt=prompt,
|
|
363
|
-
parameters={"description": description},
|
|
364
|
-
)
|
|
365
|
-
|
|
366
|
-
return {
|
|
367
|
-
"type": "plan",
|
|
368
|
-
"description": description,
|
|
369
|
-
"error": error_msg,
|
|
370
|
-
"error_type": "network_error",
|
|
371
|
-
"instruction": instruction.to_dict(),
|
|
372
|
-
"skill_command": instruction.to_skill_command(),
|
|
373
|
-
"created_at": datetime.now().isoformat(),
|
|
374
|
-
}
|
|
375
|
-
except Exception as e:
|
|
376
|
-
# All other errors
|
|
377
|
-
error_msg = f"Error generating plan: {str(e)}"
|
|
378
|
-
logger.error(f"Error generating plan: {e}", exc_info=True)
|
|
379
|
-
# Fallback to instruction if analyst fails
|
|
380
|
-
prompt = f"""You are a software planning expert. Analyze the following requirement and create a detailed plan.
|
|
381
|
-
|
|
382
|
-
Requirement:
|
|
383
|
-
{description}
|
|
384
|
-
|
|
385
|
-
Generate a plan that includes:
|
|
386
|
-
1. Overview of the feature/requirement
|
|
387
|
-
2. List of user stories (with brief descriptions)
|
|
388
|
-
3. Estimated complexity for each story (1-5 scale)
|
|
389
|
-
4. Dependencies between stories
|
|
390
|
-
5. Suggested priority order
|
|
391
|
-
|
|
392
|
-
Format your response as structured text."""
|
|
393
|
-
|
|
394
|
-
instruction = GenericInstruction(
|
|
395
|
-
agent_name="planner",
|
|
396
|
-
command="plan",
|
|
397
|
-
prompt=prompt,
|
|
398
|
-
parameters={"description": description},
|
|
399
|
-
)
|
|
400
|
-
|
|
401
|
-
return {
|
|
402
|
-
"type": "plan",
|
|
403
|
-
"description": description,
|
|
404
|
-
"error": error_msg,
|
|
405
|
-
"error_type": "unknown_error",
|
|
406
|
-
"instruction": instruction.to_dict(),
|
|
407
|
-
"skill_command": instruction.to_skill_command(),
|
|
408
|
-
"created_at": datetime.now().isoformat(),
|
|
409
|
-
}
|
|
410
|
-
finally:
|
|
411
|
-
# Ensure analyst is always closed, even on error
|
|
412
|
-
if analyst is not None:
|
|
413
|
-
try:
|
|
414
|
-
await analyst.close()
|
|
415
|
-
except Exception as close_error:
|
|
416
|
-
logger.warning(f"Error closing analyst agent: {close_error}", exc_info=True)
|
|
417
|
-
|
|
418
|
-
async def create_story(
|
|
419
|
-
self, description: str, epic: str | None = None, priority: str = "medium"
|
|
420
|
-
) -> dict[str, Any]:
|
|
421
|
-
"""
|
|
422
|
-
Generate a user story from a description.
|
|
423
|
-
|
|
424
|
-
Args:
|
|
425
|
-
description: Story description
|
|
426
|
-
epic: Epic or feature area (optional)
|
|
427
|
-
priority: Priority (high/medium/low)
|
|
428
|
-
"""
|
|
429
|
-
# Generate story ID
|
|
430
|
-
story_id = self._generate_story_id(description, epic)
|
|
431
|
-
|
|
432
|
-
# Create story metadata
|
|
433
|
-
story_metadata = {
|
|
434
|
-
"story_id": story_id,
|
|
435
|
-
"title": self._extract_title(description),
|
|
436
|
-
"description": description,
|
|
437
|
-
"epic": epic or "general",
|
|
438
|
-
"domain": self._infer_domain(description),
|
|
439
|
-
"priority": priority,
|
|
440
|
-
"complexity": await self._estimate_complexity(description),
|
|
441
|
-
"status": "draft",
|
|
442
|
-
"created_at": datetime.now().isoformat(),
|
|
443
|
-
"created_by": "planner",
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
# Generate acceptance criteria and tasks using LLM
|
|
447
|
-
acceptance_criteria, tasks = await self._generate_story_details(description)
|
|
448
|
-
|
|
449
|
-
# Create story file
|
|
450
|
-
story_file = await self._write_story_file(
|
|
451
|
-
story_metadata, acceptance_criteria, tasks
|
|
452
|
-
)
|
|
453
|
-
|
|
454
|
-
return {
|
|
455
|
-
"type": "story",
|
|
456
|
-
"story_id": story_id,
|
|
457
|
-
"story_file": str(story_file),
|
|
458
|
-
"metadata": story_metadata,
|
|
459
|
-
"acceptance_criteria": acceptance_criteria,
|
|
460
|
-
"tasks": tasks,
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
async def list_stories(
|
|
464
|
-
self, epic: str | None = None, status: str | None = None
|
|
465
|
-
) -> dict[str, Any]:
|
|
466
|
-
"""
|
|
467
|
-
List all stories in the project.
|
|
468
|
-
|
|
469
|
-
Args:
|
|
470
|
-
epic: Filter by epic (optional)
|
|
471
|
-
status: Filter by status (optional)
|
|
472
|
-
"""
|
|
473
|
-
if self.stories_dir is None:
|
|
474
|
-
# Default to stories/ directory in project root
|
|
475
|
-
project_root = Path.cwd()
|
|
476
|
-
self.stories_dir = project_root / "stories"
|
|
477
|
-
|
|
478
|
-
stories = []
|
|
479
|
-
|
|
480
|
-
if not self.stories_dir.exists():
|
|
481
|
-
return {
|
|
482
|
-
"type": "list_stories",
|
|
483
|
-
"stories": [],
|
|
484
|
-
"count": 0,
|
|
485
|
-
"message": f"Stories directory not found: {self.stories_dir}",
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
# Find all story files
|
|
489
|
-
story_files = list(self.stories_dir.glob("story-*.md")) + list(
|
|
490
|
-
self.stories_dir.glob("story-*.yaml")
|
|
491
|
-
)
|
|
492
|
-
|
|
493
|
-
for story_file in story_files:
|
|
494
|
-
try:
|
|
495
|
-
metadata = self._read_story_metadata(story_file)
|
|
496
|
-
|
|
497
|
-
# Apply filters
|
|
498
|
-
if epic and metadata.get("epic") != epic:
|
|
499
|
-
continue
|
|
500
|
-
if status and metadata.get("status") != status:
|
|
501
|
-
continue
|
|
502
|
-
|
|
503
|
-
stories.append(
|
|
504
|
-
{
|
|
505
|
-
"story_id": metadata.get("story_id"),
|
|
506
|
-
"title": metadata.get("title"),
|
|
507
|
-
"epic": metadata.get("epic"),
|
|
508
|
-
"status": metadata.get("status"),
|
|
509
|
-
"priority": metadata.get("priority"),
|
|
510
|
-
"complexity": metadata.get("complexity"),
|
|
511
|
-
"file": str(story_file),
|
|
512
|
-
}
|
|
513
|
-
)
|
|
514
|
-
except Exception:
|
|
515
|
-
# Skip invalid story files
|
|
516
|
-
logger.debug(
|
|
517
|
-
"Skipping invalid story file %s", story_file, exc_info=True
|
|
518
|
-
)
|
|
519
|
-
continue # nosec B112 - best-effort story discovery
|
|
520
|
-
|
|
521
|
-
return {
|
|
522
|
-
"type": "list_stories",
|
|
523
|
-
"stories": stories,
|
|
524
|
-
"count": len(stories),
|
|
525
|
-
"filters": {"epic": epic, "status": status},
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
def _generate_story_id(self, description: str, epic: str | None = None) -> str:
|
|
529
|
-
"""Generate a unique story ID from description."""
|
|
530
|
-
# Create a slug from description
|
|
531
|
-
slug = re.sub(r"[^a-z0-9]+", "-", description.lower()[:50])
|
|
532
|
-
slug = slug.strip("-")
|
|
533
|
-
|
|
534
|
-
# Add epic prefix if provided
|
|
535
|
-
if epic:
|
|
536
|
-
epic_slug = re.sub(r"[^a-z0-9]+", "-", epic.lower())
|
|
537
|
-
return f"{epic_slug}-{slug[:30]}"
|
|
538
|
-
|
|
539
|
-
return f"story-{slug[:30]}"
|
|
540
|
-
|
|
541
|
-
def _extract_title(self, description: str) -> str:
|
|
542
|
-
"""Extract a short title from description."""
|
|
543
|
-
# Take first line or first 60 chars
|
|
544
|
-
title = description.split("\n")[0].strip()
|
|
545
|
-
if len(title) > 60:
|
|
546
|
-
title = title[:57] + "..."
|
|
547
|
-
return title
|
|
548
|
-
|
|
549
|
-
def _infer_domain(self, description: str) -> str:
|
|
550
|
-
"""Infer domain from description (basic heuristic)."""
|
|
551
|
-
description_lower = description.lower()
|
|
552
|
-
|
|
553
|
-
# Simple keyword matching (can be enhanced with LLM)
|
|
554
|
-
if any(word in description_lower for word in ["api", "endpoint", "service"]):
|
|
555
|
-
return "backend"
|
|
556
|
-
elif any(
|
|
557
|
-
word in description_lower
|
|
558
|
-
for word in ["ui", "interface", "component", "page"]
|
|
559
|
-
):
|
|
560
|
-
return "frontend"
|
|
561
|
-
elif any(word in description_lower for word in ["test", "testing", "spec"]):
|
|
562
|
-
return "testing"
|
|
563
|
-
elif any(
|
|
564
|
-
word in description_lower for word in ["documentation", "docs", "guide"]
|
|
565
|
-
):
|
|
566
|
-
return "documentation"
|
|
567
|
-
else:
|
|
568
|
-
return "general"
|
|
569
|
-
|
|
570
|
-
async def _estimate_complexity(self, description: str) -> int:
|
|
571
|
-
"""Estimate complexity (1-5 scale) using LLM."""
|
|
572
|
-
prompt = f"""Estimate the complexity of implementing this story on a scale of 1-5:
|
|
573
|
-
- 1: Trivial (simple change, <1 hour)
|
|
574
|
-
- 2: Easy (straightforward, 1-4 hours)
|
|
575
|
-
- 3: Medium (moderate effort, 1-2 days)
|
|
576
|
-
- 4: Complex (significant effort, 3-5 days)
|
|
577
|
-
- 5: Very Complex (major feature, 1+ weeks)
|
|
578
|
-
|
|
579
|
-
Story: {description}
|
|
580
|
-
|
|
581
|
-
Respond with ONLY a single number (1-5)."""
|
|
582
|
-
|
|
583
|
-
# Prepare instruction for Cursor Skills
|
|
584
|
-
instruction = GenericInstruction(
|
|
585
|
-
agent_name="planner",
|
|
586
|
-
command="estimate-complexity",
|
|
587
|
-
prompt=prompt,
|
|
588
|
-
parameters={"description": description},
|
|
589
|
-
)
|
|
590
|
-
|
|
591
|
-
# Return instruction - complexity will be determined by Cursor Skills
|
|
592
|
-
# Default to medium complexity for now
|
|
593
|
-
return {
|
|
594
|
-
"instruction": instruction.to_dict(),
|
|
595
|
-
"skill_command": instruction.to_skill_command(),
|
|
596
|
-
"estimated_complexity": 3, # Default
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
async def _generate_story_details(
|
|
600
|
-
self, description: str
|
|
601
|
-
) -> tuple[list[str], list[str]]:
|
|
602
|
-
"""Generate acceptance criteria and tasks using LLM."""
|
|
603
|
-
prompt = f"""Generate a user story breakdown for:
|
|
604
|
-
|
|
605
|
-
{description}
|
|
606
|
-
|
|
607
|
-
Provide:
|
|
608
|
-
1. Acceptance Criteria (list of 3-5 criteria, one per line, prefixed with "- ")
|
|
609
|
-
2. Tasks (list of 3-7 tasks, one per line, prefixed with "1. ", "2. ", etc.)
|
|
610
|
-
|
|
611
|
-
Format:
|
|
612
|
-
ACCEPTANCE CRITERIA:
|
|
613
|
-
- Criterion 1
|
|
614
|
-
- Criterion 2
|
|
615
|
-
|
|
616
|
-
TASKS:
|
|
617
|
-
1. Task 1
|
|
618
|
-
2. Task 2"""
|
|
619
|
-
|
|
620
|
-
# Prepare instruction for Cursor Skills
|
|
621
|
-
instruction = GenericInstruction(
|
|
622
|
-
agent_name="planner",
|
|
623
|
-
command="generate-story-details",
|
|
624
|
-
prompt=prompt,
|
|
625
|
-
parameters={"description": description},
|
|
626
|
-
)
|
|
627
|
-
|
|
628
|
-
# Return instruction - details will be generated by Cursor Skills
|
|
629
|
-
# Return basic criteria/tasks for now
|
|
630
|
-
acceptance_criteria = ["Story implementation meets requirements"]
|
|
631
|
-
tasks = ["Implement story requirements"]
|
|
632
|
-
|
|
633
|
-
return acceptance_criteria, tasks
|
|
634
|
-
|
|
635
|
-
async def _write_story_file(
|
|
636
|
-
self, metadata: dict[str, Any], acceptance_criteria: list[str], tasks: list[str]
|
|
637
|
-
) -> Path:
|
|
638
|
-
"""Write story to file (Markdown format)."""
|
|
639
|
-
if self.stories_dir is None:
|
|
640
|
-
project_root = Path.cwd()
|
|
641
|
-
self.stories_dir = project_root / "stories"
|
|
642
|
-
|
|
643
|
-
# Create stories directory if it doesn't exist
|
|
644
|
-
self.stories_dir.mkdir(parents=True, exist_ok=True)
|
|
645
|
-
|
|
646
|
-
story_id = metadata["story_id"]
|
|
647
|
-
story_file = self.stories_dir / f"{story_id}.md"
|
|
648
|
-
|
|
649
|
-
# Generate story content
|
|
650
|
-
content = f"""# {metadata['title']}
|
|
651
|
-
|
|
652
|
-
```yaml
|
|
653
|
-
story_id: {metadata['story_id']}
|
|
654
|
-
title: {metadata['title']}
|
|
655
|
-
description: |
|
|
656
|
-
{self._indent_yaml_multiline(metadata['description'])}
|
|
657
|
-
epic: {metadata['epic']}
|
|
658
|
-
domain: {metadata['domain']}
|
|
659
|
-
priority: {metadata['priority']}
|
|
660
|
-
complexity: {metadata['complexity']}
|
|
661
|
-
status: {metadata['status']}
|
|
662
|
-
created_at: {metadata['created_at']}
|
|
663
|
-
created_by: {metadata['created_by']}
|
|
664
|
-
```
|
|
665
|
-
|
|
666
|
-
## Description
|
|
667
|
-
|
|
668
|
-
{metadata['description']}
|
|
669
|
-
|
|
670
|
-
## Acceptance Criteria
|
|
671
|
-
|
|
672
|
-
{chr(10).join(f"- [ ] {ac}" for ac in acceptance_criteria)}
|
|
673
|
-
|
|
674
|
-
## Tasks
|
|
675
|
-
|
|
676
|
-
{chr(10).join(f"{i+1}. {task}" for i, task in enumerate(tasks))}
|
|
677
|
-
|
|
678
|
-
## Technical Notes
|
|
679
|
-
|
|
680
|
-
(Technical considerations, dependencies, etc.)
|
|
681
|
-
|
|
682
|
-
## Dependencies
|
|
683
|
-
|
|
684
|
-
- Related stories: []
|
|
685
|
-
- Blocks: []
|
|
686
|
-
- Blocked by: []
|
|
687
|
-
"""
|
|
688
|
-
|
|
689
|
-
story_file.write_text(content, encoding="utf-8")
|
|
690
|
-
return story_file
|
|
691
|
-
|
|
692
|
-
def _indent_yaml_multiline(self, text: str, indent: int = 2) -> str:
|
|
693
|
-
"""Indent multiline text for YAML."""
|
|
694
|
-
lines = text.split("\n")
|
|
695
|
-
return "\n".join(" " * indent + line for line in lines)
|
|
696
|
-
|
|
697
|
-
def _read_story_metadata(self, story_file: Path) -> dict[str, Any]:
|
|
698
|
-
"""Read story metadata from file."""
|
|
699
|
-
content = story_file.read_text(encoding="utf-8")
|
|
700
|
-
|
|
701
|
-
# Extract YAML frontmatter
|
|
702
|
-
yaml_match = re.search(r"```yaml\n(.*?)\n```", content, re.DOTALL)
|
|
703
|
-
if yaml_match:
|
|
704
|
-
try:
|
|
705
|
-
metadata = yaml.safe_load(yaml_match.group(1))
|
|
706
|
-
return metadata or {}
|
|
707
|
-
except yaml.YAMLError:
|
|
708
|
-
pass
|
|
709
|
-
|
|
710
|
-
return {}
|
|
711
|
-
|
|
712
|
-
async def _generate_user_stories(
|
|
713
|
-
self, description: str, functional_requirements: list[str]
|
|
714
|
-
) -> list[dict[str, Any]]:
|
|
715
|
-
"""Generate user stories in standard format from requirements."""
|
|
716
|
-
from ...core.mal import MAL
|
|
717
|
-
mal = MAL()
|
|
718
|
-
|
|
719
|
-
# Build prompt for user story generation
|
|
720
|
-
reqs_text = "\n".join(f"- {req}" for req in functional_requirements) if functional_requirements else description
|
|
721
|
-
|
|
722
|
-
prompt = f"""Generate user stories in the standard format "As a {{user}}, I want {{goal}}, so that {{benefit}}" from the following requirements:
|
|
723
|
-
|
|
724
|
-
Description: {description}
|
|
725
|
-
|
|
726
|
-
Functional Requirements:
|
|
727
|
-
{reqs_text}
|
|
728
|
-
|
|
729
|
-
For each user story, provide:
|
|
730
|
-
1. The story in standard format: "As a [user type], I want [goal], so that [benefit]"
|
|
731
|
-
2. Acceptance criteria (3-5 items)
|
|
732
|
-
3. Story points estimate (Fibonacci: 1, 2, 3, 5, 8, 13)
|
|
733
|
-
|
|
734
|
-
Format as JSON array with:
|
|
735
|
-
- story: The story text in standard format
|
|
736
|
-
- user: User type
|
|
737
|
-
- goal: What they want to do
|
|
738
|
-
- benefit: Why they want it
|
|
739
|
-
- acceptance_criteria: List of criteria
|
|
740
|
-
- story_points: Number (1-13)
|
|
741
|
-
"""
|
|
742
|
-
|
|
743
|
-
try:
|
|
744
|
-
response = await mal.generate(
|
|
745
|
-
prompt=prompt,
|
|
746
|
-
system_prompt="You are a product planner. Generate user stories in standard format from requirements.",
|
|
747
|
-
)
|
|
748
|
-
|
|
749
|
-
# Parse JSON response
|
|
750
|
-
import json as json_lib
|
|
751
|
-
try:
|
|
752
|
-
stories_data = json_lib.loads(response)
|
|
753
|
-
if not isinstance(stories_data, list):
|
|
754
|
-
stories_data = [stories_data]
|
|
755
|
-
except (json_lib.JSONDecodeError, ValueError):
|
|
756
|
-
# Fallback: create basic story from description
|
|
757
|
-
stories_data = [{
|
|
758
|
-
"story": f"As a user, I want {description.lower()}, so that I can accomplish my goal",
|
|
759
|
-
"user": "user",
|
|
760
|
-
"goal": description.lower(),
|
|
761
|
-
"benefit": "accomplish my goal",
|
|
762
|
-
"acceptance_criteria": ["Feature works as described", "Tests pass", "Documentation updated"],
|
|
763
|
-
"story_points": 3,
|
|
764
|
-
}]
|
|
765
|
-
|
|
766
|
-
return stories_data
|
|
767
|
-
except Exception as e:
|
|
768
|
-
logger.warning(f"Error generating user stories: {e}")
|
|
769
|
-
# Fallback: create basic story
|
|
770
|
-
return [{
|
|
771
|
-
"story": f"As a user, I want {description.lower()}, so that I can accomplish my goal",
|
|
772
|
-
"user": "user",
|
|
773
|
-
"goal": description.lower(),
|
|
774
|
-
"benefit": "accomplish my goal",
|
|
775
|
-
"acceptance_criteria": ["Feature works as described"],
|
|
776
|
-
"story_points": 3,
|
|
777
|
-
}]
|
|
778
|
-
|
|
779
|
-
def _format_plan_markdown(
|
|
780
|
-
self,
|
|
781
|
-
description: str,
|
|
782
|
-
requirements_result: dict[str, Any],
|
|
783
|
-
functional_reqs: list[str],
|
|
784
|
-
non_functional_reqs: list[str],
|
|
785
|
-
user_stories: list[dict[str, Any]],
|
|
786
|
-
) -> str:
|
|
787
|
-
"""Format plan as markdown document with user stories."""
|
|
788
|
-
lines = [
|
|
789
|
-
f"# Plan: {description}",
|
|
790
|
-
"",
|
|
791
|
-
"## Overview",
|
|
792
|
-
"",
|
|
793
|
-
requirements_result.get('summary', {}).get('overview', 'Feature implementation plan') if isinstance(requirements_result.get('summary'), dict) else 'Feature implementation plan',
|
|
794
|
-
"",
|
|
795
|
-
"## Requirements",
|
|
796
|
-
"",
|
|
797
|
-
"### Functional Requirements",
|
|
798
|
-
"",
|
|
799
|
-
]
|
|
800
|
-
|
|
801
|
-
if functional_reqs:
|
|
802
|
-
for req in functional_reqs:
|
|
803
|
-
lines.append(f"- {req}")
|
|
804
|
-
else:
|
|
805
|
-
lines.append("- Requirements analysis in progress")
|
|
806
|
-
|
|
807
|
-
lines.extend([
|
|
808
|
-
"",
|
|
809
|
-
"### Non-Functional Requirements",
|
|
810
|
-
"",
|
|
811
|
-
])
|
|
812
|
-
|
|
813
|
-
if non_functional_reqs:
|
|
814
|
-
for req in non_functional_reqs:
|
|
815
|
-
lines.append(f"- {req}")
|
|
816
|
-
else:
|
|
817
|
-
lines.append("- Requirements analysis in progress")
|
|
818
|
-
|
|
819
|
-
lines.extend([
|
|
820
|
-
"",
|
|
821
|
-
"## User Stories",
|
|
822
|
-
"",
|
|
823
|
-
])
|
|
824
|
-
|
|
825
|
-
if user_stories:
|
|
826
|
-
for i, story in enumerate(user_stories, 1):
|
|
827
|
-
story_text = story.get("story", f"Story {i}")
|
|
828
|
-
user = story.get("user", "user")
|
|
829
|
-
goal = story.get("goal", "")
|
|
830
|
-
benefit = story.get("benefit", "")
|
|
831
|
-
acceptance_criteria = story.get("acceptance_criteria", [])
|
|
832
|
-
story_points = story.get("story_points", 0)
|
|
833
|
-
|
|
834
|
-
lines.extend([
|
|
835
|
-
f"### Story {i}: {story_text}",
|
|
836
|
-
"",
|
|
837
|
-
f"**Story Points:** {story_points}",
|
|
838
|
-
"",
|
|
839
|
-
"**Acceptance Criteria:**",
|
|
840
|
-
"",
|
|
841
|
-
])
|
|
842
|
-
|
|
843
|
-
for ac in acceptance_criteria:
|
|
844
|
-
lines.append(f"- [ ] {ac}")
|
|
845
|
-
|
|
846
|
-
lines.append("")
|
|
847
|
-
else:
|
|
848
|
-
lines.append("(User stories to be generated)")
|
|
849
|
-
lines.append("")
|
|
850
|
-
|
|
851
|
-
lines.extend([
|
|
852
|
-
"## Estimated Complexity",
|
|
853
|
-
"",
|
|
854
|
-
"(To be estimated per story)",
|
|
855
|
-
"",
|
|
856
|
-
"## Dependencies",
|
|
857
|
-
"",
|
|
858
|
-
"(To be identified)",
|
|
859
|
-
"",
|
|
860
|
-
"## Priority Order",
|
|
861
|
-
"",
|
|
862
|
-
"(To be determined)",
|
|
863
|
-
"",
|
|
864
|
-
])
|
|
865
|
-
|
|
866
|
-
return "\n".join(lines)
|
|
867
|
-
|
|
868
|
-
def _help(self) -> dict[str, Any]:
|
|
869
|
-
"""
|
|
870
|
-
Return help information for Planner Agent.
|
|
871
|
-
|
|
872
|
-
Returns standardized help format with commands, examples, and usage notes.
|
|
873
|
-
This method is synchronous as it performs no I/O operations.
|
|
874
|
-
|
|
875
|
-
Returns:
|
|
876
|
-
dict: Help information with standardized format:
|
|
877
|
-
- type (str): Always "help"
|
|
878
|
-
- content (str): Formatted markdown help text containing:
|
|
879
|
-
- Available commands list
|
|
880
|
-
- Usage examples
|
|
881
|
-
- Story storage information
|
|
882
|
-
|
|
883
|
-
Note:
|
|
884
|
-
This method is called via agent.run("help") which handles async context.
|
|
885
|
-
Command list is cached via BaseAgent.get_commands() for performance.
|
|
886
|
-
"""
|
|
887
|
-
commands = self.get_commands()
|
|
888
|
-
command_lines = [
|
|
889
|
-
f"- **{cmd['command']}**: {cmd['description']}"
|
|
890
|
-
for cmd in commands
|
|
891
|
-
]
|
|
892
|
-
|
|
893
|
-
content = f"""# {self.agent_name} - Help
|
|
894
|
-
|
|
895
|
-
## Available Commands
|
|
896
|
-
|
|
897
|
-
{chr(10).join(command_lines)}
|
|
898
|
-
|
|
899
|
-
## Usage Examples
|
|
900
|
-
|
|
901
|
-
### Create a Plan
|
|
902
|
-
```
|
|
903
|
-
*plan Add user authentication with OAuth2 support
|
|
904
|
-
```
|
|
905
|
-
|
|
906
|
-
### Create a Story
|
|
907
|
-
```
|
|
908
|
-
*create-story User should be able to log in with Google
|
|
909
|
-
*create-story Add shopping cart --epic=checkout --priority=high
|
|
910
|
-
```
|
|
911
|
-
|
|
912
|
-
### List Stories
|
|
913
|
-
```
|
|
914
|
-
*list-stories
|
|
915
|
-
*list-stories --epic=checkout
|
|
916
|
-
*list-stories --status=draft
|
|
917
|
-
```
|
|
918
|
-
|
|
919
|
-
## Story Storage
|
|
920
|
-
|
|
921
|
-
Stories are saved to `stories/` directory in your project root.
|
|
922
|
-
Each story is saved as a Markdown file with YAML frontmatter.
|
|
923
|
-
"""
|
|
924
|
-
|
|
925
|
-
return {"type": "help", "content": content}
|
|
926
|
-
|
|
927
|
-
async def _evaluate_stories(self, stories: list[dict[str, Any]]) -> dict[str, Any]:
|
|
928
|
-
"""Evaluate story quality using INVEST criteria."""
|
|
929
|
-
from ...core.story_evaluator import StoryEvaluator
|
|
930
|
-
|
|
931
|
-
evaluator = StoryEvaluator()
|
|
932
|
-
results = []
|
|
933
|
-
|
|
934
|
-
for story in stories:
|
|
935
|
-
score = evaluator.evaluate(story)
|
|
936
|
-
results.append(
|
|
937
|
-
{
|
|
938
|
-
"story_id": story.get("id", "unknown"),
|
|
939
|
-
"title": story.get("title", "Untitled"),
|
|
940
|
-
"score": {
|
|
941
|
-
"overall": score.overall,
|
|
942
|
-
"independent": score.independent,
|
|
943
|
-
"negotiable": score.negotiable,
|
|
944
|
-
"valuable": score.valuable,
|
|
945
|
-
"estimable": score.estimable,
|
|
946
|
-
"small": score.small,
|
|
947
|
-
"testable": score.testable,
|
|
948
|
-
},
|
|
949
|
-
"issues": score.issues,
|
|
950
|
-
"strengths": score.strengths,
|
|
951
|
-
"recommendations": score.recommendations,
|
|
952
|
-
}
|
|
953
|
-
)
|
|
954
|
-
|
|
955
|
-
# Calculate average scores
|
|
956
|
-
avg_scores = {
|
|
957
|
-
"overall": sum(r["score"]["overall"] for r in results) / len(results) if results else 0.0,
|
|
958
|
-
"independent": sum(r["score"]["independent"] for r in results) / len(results) if results else 0.0,
|
|
959
|
-
"negotiable": sum(r["score"]["negotiable"] for r in results) / len(results) if results else 0.0,
|
|
960
|
-
"valuable": sum(r["score"]["valuable"] for r in results) / len(results) if results else 0.0,
|
|
961
|
-
"estimable": sum(r["score"]["estimable"] for r in results) / len(results) if results else 0.0,
|
|
962
|
-
"small": sum(r["score"]["small"] for r in results) / len(results) if results else 0.0,
|
|
963
|
-
"testable": sum(r["score"]["testable"] for r in results) / len(results) if results else 0.0,
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
return {
|
|
967
|
-
"success": True,
|
|
968
|
-
"stories_evaluated": len(results),
|
|
969
|
-
"average_scores": avg_scores,
|
|
970
|
-
"story_results": results,
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
async def _validate_stories(self, stories: list[dict[str, Any]]) -> dict[str, Any]:
|
|
974
|
-
"""Validate stories for completeness and quality."""
|
|
975
|
-
from ...core.story_evaluator import StoryEvaluator
|
|
976
|
-
|
|
977
|
-
evaluator = StoryEvaluator()
|
|
978
|
-
results = []
|
|
979
|
-
all_valid = True
|
|
980
|
-
|
|
981
|
-
for story in stories:
|
|
982
|
-
result = evaluator.validate(story)
|
|
983
|
-
all_valid = all_valid and result.is_valid
|
|
984
|
-
results.append(
|
|
985
|
-
{
|
|
986
|
-
"story_id": story.get("id", "unknown"),
|
|
987
|
-
"title": story.get("title", "Untitled"),
|
|
988
|
-
"is_valid": result.is_valid,
|
|
989
|
-
"score": {
|
|
990
|
-
"overall": result.score.overall,
|
|
991
|
-
"independent": result.score.independent,
|
|
992
|
-
"negotiable": result.score.negotiable,
|
|
993
|
-
"valuable": result.score.valuable,
|
|
994
|
-
"estimable": result.score.estimable,
|
|
995
|
-
"small": result.score.small,
|
|
996
|
-
"testable": result.score.testable,
|
|
997
|
-
},
|
|
998
|
-
"missing_elements": result.missing_elements,
|
|
999
|
-
"weak_acceptance_criteria": result.weak_acceptance_criteria,
|
|
1000
|
-
"dependency_issues": result.dependency_issues,
|
|
1001
|
-
"issues": result.score.issues,
|
|
1002
|
-
"recommendations": result.score.recommendations,
|
|
1003
|
-
}
|
|
1004
|
-
)
|
|
1005
|
-
|
|
1006
|
-
return {
|
|
1007
|
-
"success": True,
|
|
1008
|
-
"all_valid": all_valid,
|
|
1009
|
-
"stories_validated": len(results),
|
|
1010
|
-
"validation_results": results,
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
async def _trace_stories(
|
|
1014
|
-
self, stories: list[dict[str, Any]], test_cases: list[dict[str, Any]], output_file: str | None = None
|
|
1015
|
-
) -> dict[str, Any]:
|
|
1016
|
-
"""Map stories to acceptance criteria and test cases."""
|
|
1017
|
-
from ...core.traceability import TraceabilityManager
|
|
1018
|
-
|
|
1019
|
-
manager = TraceabilityManager()
|
|
1020
|
-
matrix = manager.get_matrix()
|
|
1021
|
-
|
|
1022
|
-
traceability_map = []
|
|
1023
|
-
|
|
1024
|
-
for story in stories:
|
|
1025
|
-
story_id = story.get("id", "unknown")
|
|
1026
|
-
matrix.add_story(story_id, story)
|
|
1027
|
-
|
|
1028
|
-
acceptance_criteria = story.get("acceptance_criteria", [])
|
|
1029
|
-
linked_tests = []
|
|
1030
|
-
|
|
1031
|
-
# Link test cases to acceptance criteria
|
|
1032
|
-
for test_case in test_cases:
|
|
1033
|
-
test_story_id = test_case.get("story_id") or test_case.get("related_story")
|
|
1034
|
-
if test_story_id == story_id:
|
|
1035
|
-
test_id = test_case.get("id", "unknown")
|
|
1036
|
-
linked_tests.append(test_id)
|
|
1037
|
-
|
|
1038
|
-
# Link story to test
|
|
1039
|
-
matrix.link("story", story_id, "test", test_id, "validates", 1.0)
|
|
1040
|
-
|
|
1041
|
-
traceability_map.append(
|
|
1042
|
-
{
|
|
1043
|
-
"story_id": story_id,
|
|
1044
|
-
"title": story.get("title", "Untitled"),
|
|
1045
|
-
"acceptance_criteria": acceptance_criteria,
|
|
1046
|
-
"linked_tests": linked_tests,
|
|
1047
|
-
"coverage": len(linked_tests) / len(acceptance_criteria) * 100.0 if acceptance_criteria else 0.0,
|
|
1048
|
-
}
|
|
1049
|
-
)
|
|
1050
|
-
|
|
1051
|
-
# Save matrix
|
|
1052
|
-
manager.save_matrix()
|
|
1053
|
-
|
|
1054
|
-
# Generate report
|
|
1055
|
-
report = {
|
|
1056
|
-
"stories_traced": len(traceability_map),
|
|
1057
|
-
"total_acceptance_criteria": sum(len(m["acceptance_criteria"]) for m in traceability_map),
|
|
1058
|
-
"total_tests": len(test_cases),
|
|
1059
|
-
"average_coverage": sum(m["coverage"] for m in traceability_map) / len(traceability_map) if traceability_map else 0.0,
|
|
1060
|
-
"traceability_map": traceability_map,
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
|
-
# Save to file if specified
|
|
1064
|
-
if output_file:
|
|
1065
|
-
output_path = Path(output_file)
|
|
1066
|
-
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
1067
|
-
import yaml
|
|
1068
|
-
with open(output_path, "w", encoding="utf-8") as f:
|
|
1069
|
-
yaml.dump(report, f, default_flow_style=False)
|
|
1070
|
-
|
|
1071
|
-
return {
|
|
1072
|
-
"success": True,
|
|
1073
|
-
"traceability_report": report,
|
|
1074
|
-
"matrix_file": str(manager.matrix_file),
|
|
1075
|
-
"output_file": str(output_path) if output_file else None,
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
async def _review_stories(self, stories: list[dict[str, Any]]) -> dict[str, Any]:
|
|
1079
|
-
"""Structured review of stories with INVEST checklist."""
|
|
1080
|
-
from ...core.review_checklists import StoryReviewChecklist
|
|
1081
|
-
|
|
1082
|
-
checklist = StoryReviewChecklist()
|
|
1083
|
-
results = []
|
|
1084
|
-
|
|
1085
|
-
for story in stories:
|
|
1086
|
-
result = checklist.review(story)
|
|
1087
|
-
results.append(
|
|
1088
|
-
{
|
|
1089
|
-
"story_id": story.get("id", "unknown"),
|
|
1090
|
-
"title": story.get("title", "Untitled"),
|
|
1091
|
-
"overall_score": result.overall_score,
|
|
1092
|
-
"items_checked": result.items_checked,
|
|
1093
|
-
"items_total": result.items_total,
|
|
1094
|
-
"critical_issues": result.critical_issues,
|
|
1095
|
-
"high_issues": result.high_issues,
|
|
1096
|
-
"medium_issues": result.medium_issues,
|
|
1097
|
-
"low_issues": result.low_issues,
|
|
1098
|
-
"recommendations": result.recommendations,
|
|
1099
|
-
}
|
|
1100
|
-
)
|
|
1101
|
-
|
|
1102
|
-
avg_score = sum(r["overall_score"] for r in results) / len(results) if results else 0.0
|
|
1103
|
-
|
|
1104
|
-
return {
|
|
1105
|
-
"success": True,
|
|
1106
|
-
"stories_reviewed": len(results),
|
|
1107
|
-
"average_score": avg_score,
|
|
1108
|
-
"review_results": results,
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
async def _calibrate_estimates(self, estimated_points: int, complexity: str) -> dict[str, Any]:
|
|
1112
|
-
"""Get calibrated estimates based on historical accuracy."""
|
|
1113
|
-
from ...core.estimation_tracker import EstimationTracker
|
|
1114
|
-
|
|
1115
|
-
tracker = EstimationTracker()
|
|
1116
|
-
tracker.load()
|
|
1117
|
-
|
|
1118
|
-
calibrated = tracker.get_calibrated_estimate(estimated_points, complexity)
|
|
1119
|
-
|
|
1120
|
-
return {
|
|
1121
|
-
"success": True,
|
|
1122
|
-
**calibrated,
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
async def close(self):
|
|
1126
|
-
"""Clean up resources"""
|
|
1127
|
-
pass
|
|
1
|
+
"""
|
|
2
|
+
Planner Agent - Creates user stories and task breakdowns
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import re
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
import yaml
|
|
12
|
+
|
|
13
|
+
from ...agents.analyst.agent import AnalystAgent
|
|
14
|
+
from ...context7.agent_integration import Context7AgentHelper, get_context7_helper
|
|
15
|
+
from ...core.agent_base import BaseAgent
|
|
16
|
+
from ...core.config import ProjectConfig, load_config
|
|
17
|
+
from ...core.instructions import GenericInstruction
|
|
18
|
+
from ...core.runtime_mode import is_cursor_mode
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class PlannerAgent(BaseAgent):
|
|
24
|
+
"""
|
|
25
|
+
Planner Agent - Story/epic planning and task breakdown.
|
|
26
|
+
|
|
27
|
+
Permissions: Read, Write, Grep, Glob
|
|
28
|
+
|
|
29
|
+
⚠️ CRITICAL ACCURACY REQUIREMENT:
|
|
30
|
+
- NEVER make up, invent, or fabricate information - Only report verified facts
|
|
31
|
+
- ALWAYS verify claims by checking actual results, not just test pass/fail
|
|
32
|
+
- Verify API calls succeed - inspect response data, status codes, error messages
|
|
33
|
+
- Distinguish between code paths executing and actual functionality working
|
|
34
|
+
- Admit uncertainty explicitly when you cannot verify
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, config: ProjectConfig | None = None):
|
|
38
|
+
super().__init__(agent_id="planner", agent_name="Planner Agent", config=config)
|
|
39
|
+
# Use config if provided, otherwise load defaults
|
|
40
|
+
if config is None:
|
|
41
|
+
config = load_config()
|
|
42
|
+
self.config = config
|
|
43
|
+
|
|
44
|
+
# Story storage path (from config or default)
|
|
45
|
+
planner_config = config.agents.planner if config and config.agents else None
|
|
46
|
+
stories_dir_str = planner_config.stories_dir if planner_config else None
|
|
47
|
+
self.stories_dir = Path(stories_dir_str) if stories_dir_str else None
|
|
48
|
+
|
|
49
|
+
# Initialize Context7 helper (Enhancement: Universal Context7 integration)
|
|
50
|
+
self.context7: Context7AgentHelper | None = None
|
|
51
|
+
if config:
|
|
52
|
+
self.context7 = get_context7_helper(self, config)
|
|
53
|
+
|
|
54
|
+
def get_commands(self) -> list[dict[str, str]]:
|
|
55
|
+
"""Return available commands for planner agent"""
|
|
56
|
+
base_commands = super().get_commands()
|
|
57
|
+
return base_commands + [
|
|
58
|
+
{
|
|
59
|
+
"command": "*plan",
|
|
60
|
+
"description": "Create a plan for a feature/requirement",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"command": "*create-story",
|
|
64
|
+
"description": "Generate a user story from description",
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"command": "*list-stories",
|
|
68
|
+
"description": "List all stories in the project",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"command": "*evaluate-stories",
|
|
72
|
+
"description": "Evaluate story quality using INVEST criteria",
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"command": "*validate-stories",
|
|
76
|
+
"description": "Validate stories for completeness and quality",
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"command": "*trace-stories",
|
|
80
|
+
"description": "Map stories to acceptance criteria and test cases",
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"command": "*review-stories",
|
|
84
|
+
"description": "Structured review of stories with INVEST checklist",
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"command": "*calibrate-estimates",
|
|
88
|
+
"description": "Get calibrated estimates based on historical accuracy",
|
|
89
|
+
},
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
async def run(self, command: str, **kwargs) -> dict[str, Any]:
|
|
93
|
+
"""
|
|
94
|
+
Execute planner agent command.
|
|
95
|
+
|
|
96
|
+
Commands:
|
|
97
|
+
- *plan <description>: Create a plan
|
|
98
|
+
- *create-story <description> [--epic=<epic>] [--priority=<priority>]: Create a story
|
|
99
|
+
- *list-stories [--epic=<epic>] [--status=<status>]: List stories
|
|
100
|
+
"""
|
|
101
|
+
await self.activate()
|
|
102
|
+
|
|
103
|
+
if command == "help":
|
|
104
|
+
return self._help()
|
|
105
|
+
|
|
106
|
+
elif command == "plan":
|
|
107
|
+
description = kwargs.get("description") or kwargs.get("text", "")
|
|
108
|
+
if not description:
|
|
109
|
+
return {"error": "Description required. Usage: *plan <description>"}
|
|
110
|
+
|
|
111
|
+
generate_doc = kwargs.get("generate_doc", False) or kwargs.get("generate-doc", False)
|
|
112
|
+
output_file = kwargs.get("output_file") or kwargs.get("output-file")
|
|
113
|
+
output_format = kwargs.get("output_format", "markdown") or kwargs.get("output-format", "markdown")
|
|
114
|
+
|
|
115
|
+
result = await self.create_plan(description)
|
|
116
|
+
|
|
117
|
+
# Generate document if requested
|
|
118
|
+
if generate_doc:
|
|
119
|
+
from ...core.document_generator import DocumentGenerator
|
|
120
|
+
doc_generator = DocumentGenerator(project_root=self._project_root)
|
|
121
|
+
|
|
122
|
+
# Determine output file if not provided
|
|
123
|
+
if not output_file:
|
|
124
|
+
docs_dir = self._project_root / "docs" / "plans"
|
|
125
|
+
docs_dir.mkdir(parents=True, exist_ok=True)
|
|
126
|
+
# Create filename from description
|
|
127
|
+
safe_name = "".join(c if c.isalnum() or c in (' ', '-', '_') else '_' for c in description[:50])
|
|
128
|
+
safe_name = safe_name.replace(' ', '_').lower()
|
|
129
|
+
output_file = docs_dir / f"{safe_name}_plan.{output_format if output_format != 'html' else 'html'}"
|
|
130
|
+
|
|
131
|
+
# Generate document
|
|
132
|
+
doc_path = doc_generator.generate_plan_doc(
|
|
133
|
+
plan_data=result,
|
|
134
|
+
output_file=output_file,
|
|
135
|
+
format=output_format,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
result["document"] = {
|
|
139
|
+
"path": str(doc_path),
|
|
140
|
+
"format": output_format,
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return result
|
|
144
|
+
|
|
145
|
+
elif command == "create-story":
|
|
146
|
+
description = kwargs.get("description") or kwargs.get("text", "")
|
|
147
|
+
if not description:
|
|
148
|
+
return {
|
|
149
|
+
"error": "Description required. Usage: *create-story <description> [--epic=<epic>] [--priority=<priority>]"
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
epic = kwargs.get("epic")
|
|
153
|
+
priority = kwargs.get("priority", "medium")
|
|
154
|
+
generate_doc = kwargs.get("generate_doc", False) or kwargs.get("generate-doc", False)
|
|
155
|
+
output_file = kwargs.get("output_file") or kwargs.get("output-file")
|
|
156
|
+
output_format = kwargs.get("output_format", "markdown") or kwargs.get("output-format", "markdown")
|
|
157
|
+
|
|
158
|
+
result = await self.create_story(description, epic=epic, priority=priority)
|
|
159
|
+
|
|
160
|
+
# Generate document if requested
|
|
161
|
+
if generate_doc:
|
|
162
|
+
from ...core.document_generator import DocumentGenerator
|
|
163
|
+
doc_generator = DocumentGenerator(project_root=self._project_root)
|
|
164
|
+
|
|
165
|
+
# Determine output file if not provided
|
|
166
|
+
if not output_file:
|
|
167
|
+
stories_dir = self.stories_dir or (self._project_root / "stories")
|
|
168
|
+
stories_dir.mkdir(parents=True, exist_ok=True)
|
|
169
|
+
safe_name = "".join(c if c.isalnum() or c in (' ', '-', '_') else '_' for c in description[:50])
|
|
170
|
+
safe_name = safe_name.replace(' ', '_').lower()
|
|
171
|
+
output_file = stories_dir / f"{safe_name}_story.{output_format if output_format != 'html' else 'html'}"
|
|
172
|
+
|
|
173
|
+
# Generate document
|
|
174
|
+
doc_path = doc_generator.generate_user_story_doc(
|
|
175
|
+
story_data=result,
|
|
176
|
+
output_file=output_file,
|
|
177
|
+
format=output_format,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
result["document"] = {
|
|
181
|
+
"path": str(doc_path),
|
|
182
|
+
"format": output_format,
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return result
|
|
186
|
+
|
|
187
|
+
elif command == "list-stories":
|
|
188
|
+
epic_filter = kwargs.get("epic")
|
|
189
|
+
status_filter = kwargs.get("status")
|
|
190
|
+
return await self.list_stories(epic=epic_filter, status=status_filter)
|
|
191
|
+
|
|
192
|
+
elif command == "evaluate-stories":
|
|
193
|
+
stories = kwargs.get("stories", [])
|
|
194
|
+
if isinstance(stories, str):
|
|
195
|
+
# Try to load from file
|
|
196
|
+
story_path = Path(stories)
|
|
197
|
+
if story_path.exists():
|
|
198
|
+
import json
|
|
199
|
+
stories = json.loads(story_path.read_text(encoding="utf-8"))
|
|
200
|
+
else:
|
|
201
|
+
return {"error": f"Stories file not found: {stories}"}
|
|
202
|
+
|
|
203
|
+
return await self._evaluate_stories(stories)
|
|
204
|
+
|
|
205
|
+
elif command == "validate-stories":
|
|
206
|
+
stories = kwargs.get("stories", [])
|
|
207
|
+
if isinstance(stories, str):
|
|
208
|
+
# Try to load from file
|
|
209
|
+
story_path = Path(stories)
|
|
210
|
+
if story_path.exists():
|
|
211
|
+
import json
|
|
212
|
+
stories = json.loads(story_path.read_text(encoding="utf-8"))
|
|
213
|
+
else:
|
|
214
|
+
return {"error": f"Stories file not found: {stories}"}
|
|
215
|
+
|
|
216
|
+
return await self._validate_stories(stories)
|
|
217
|
+
|
|
218
|
+
elif command == "trace-stories":
|
|
219
|
+
stories = kwargs.get("stories", [])
|
|
220
|
+
test_cases = kwargs.get("test_cases", [])
|
|
221
|
+
output_file = kwargs.get("output_file", None)
|
|
222
|
+
|
|
223
|
+
return await self._trace_stories(stories, test_cases, output_file)
|
|
224
|
+
|
|
225
|
+
elif command == "review-stories":
|
|
226
|
+
stories = kwargs.get("stories", [])
|
|
227
|
+
if isinstance(stories, str):
|
|
228
|
+
story_path = Path(stories)
|
|
229
|
+
if story_path.exists():
|
|
230
|
+
import json
|
|
231
|
+
stories = json.loads(story_path.read_text(encoding="utf-8"))
|
|
232
|
+
else:
|
|
233
|
+
return {"error": f"Stories file not found: {stories}"}
|
|
234
|
+
|
|
235
|
+
return await self._review_stories(stories)
|
|
236
|
+
|
|
237
|
+
elif command == "calibrate-estimates":
|
|
238
|
+
estimated_points = kwargs.get("estimated_points", 0)
|
|
239
|
+
complexity = kwargs.get("complexity", "medium")
|
|
240
|
+
|
|
241
|
+
if estimated_points <= 0:
|
|
242
|
+
return {"error": "estimated_points must be greater than 0"}
|
|
243
|
+
|
|
244
|
+
return await self._calibrate_estimates(estimated_points, complexity)
|
|
245
|
+
|
|
246
|
+
else:
|
|
247
|
+
return {
|
|
248
|
+
"error": f"Unknown command: {command}. Use *help to see available commands."
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async def create_plan(self, description: str) -> dict[str, Any]:
|
|
252
|
+
"""
|
|
253
|
+
Create a plan for a feature or requirement.
|
|
254
|
+
|
|
255
|
+
This uses the LLM to analyze the description and generate:
|
|
256
|
+
- Story breakdown
|
|
257
|
+
- Task estimates
|
|
258
|
+
- Dependencies
|
|
259
|
+
"""
|
|
260
|
+
# If in Cursor mode, return instruction for Cursor Skills
|
|
261
|
+
if is_cursor_mode():
|
|
262
|
+
prompt = f"""You are a software planning expert. Analyze the following requirement and create a detailed plan.
|
|
263
|
+
|
|
264
|
+
Requirement:
|
|
265
|
+
{description}
|
|
266
|
+
|
|
267
|
+
Generate a plan that includes:
|
|
268
|
+
1. Overview of the feature/requirement
|
|
269
|
+
2. List of user stories (with brief descriptions)
|
|
270
|
+
3. Estimated complexity for each story (1-5 scale)
|
|
271
|
+
4. Dependencies between stories
|
|
272
|
+
5. Suggested priority order
|
|
273
|
+
|
|
274
|
+
Format your response as structured text."""
|
|
275
|
+
|
|
276
|
+
# Prepare instruction for Cursor Skills
|
|
277
|
+
instruction = GenericInstruction(
|
|
278
|
+
agent_name="planner",
|
|
279
|
+
command="plan",
|
|
280
|
+
prompt=prompt,
|
|
281
|
+
parameters={"description": description},
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
return {
|
|
285
|
+
"type": "plan",
|
|
286
|
+
"description": description,
|
|
287
|
+
"instruction": instruction.to_dict(),
|
|
288
|
+
"skill_command": instruction.to_skill_command(),
|
|
289
|
+
"created_at": datetime.now().isoformat(),
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
# For CLI mode, actually generate the plan using analyst agent
|
|
293
|
+
analyst = None
|
|
294
|
+
try:
|
|
295
|
+
analyst = AnalystAgent(config=self.config)
|
|
296
|
+
await analyst.activate(project_root=self._project_root, offline_mode=False)
|
|
297
|
+
|
|
298
|
+
# Gather requirements first
|
|
299
|
+
requirements_result = await analyst.run(
|
|
300
|
+
"gather-requirements",
|
|
301
|
+
description=description,
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
# Check for errors in result
|
|
305
|
+
if isinstance(requirements_result, dict) and "error" in requirements_result:
|
|
306
|
+
error_msg = requirements_result.get("error", "Unknown error")
|
|
307
|
+
logger.warning(f"Analyst agent returned error: {error_msg}")
|
|
308
|
+
raise RuntimeError(f"Requirements gathering failed: {error_msg}")
|
|
309
|
+
|
|
310
|
+
# Extract requirements information
|
|
311
|
+
requirements = requirements_result.get("requirements", {})
|
|
312
|
+
if isinstance(requirements, dict):
|
|
313
|
+
functional_reqs = requirements.get("functional_requirements", [])
|
|
314
|
+
non_functional_reqs = requirements.get("non_functional_requirements", [])
|
|
315
|
+
else:
|
|
316
|
+
functional_reqs = []
|
|
317
|
+
non_functional_reqs = []
|
|
318
|
+
|
|
319
|
+
# Generate user stories in standard format
|
|
320
|
+
user_stories = await self._generate_user_stories(description, functional_reqs)
|
|
321
|
+
|
|
322
|
+
# Build plan structure with user stories
|
|
323
|
+
plan_text = self._format_plan_markdown(
|
|
324
|
+
description=description,
|
|
325
|
+
requirements_result=requirements_result,
|
|
326
|
+
functional_reqs=functional_reqs,
|
|
327
|
+
non_functional_reqs=non_functional_reqs,
|
|
328
|
+
user_stories=user_stories,
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
return {
|
|
332
|
+
"type": "plan",
|
|
333
|
+
"description": description,
|
|
334
|
+
"plan": plan_text,
|
|
335
|
+
"requirements": requirements,
|
|
336
|
+
"user_stories": user_stories,
|
|
337
|
+
"markdown": plan_text,
|
|
338
|
+
"created_at": datetime.now().isoformat(),
|
|
339
|
+
}
|
|
340
|
+
except (ConnectionError, TimeoutError, OSError) as e:
|
|
341
|
+
# Network-related errors
|
|
342
|
+
error_msg = f"Network error: {str(e)}"
|
|
343
|
+
logger.error(f"Network error generating plan: {e}", exc_info=True)
|
|
344
|
+
# Fallback to instruction if analyst fails
|
|
345
|
+
prompt = f"""You are a software planning expert. Analyze the following requirement and create a detailed plan.
|
|
346
|
+
|
|
347
|
+
Requirement:
|
|
348
|
+
{description}
|
|
349
|
+
|
|
350
|
+
Generate a plan that includes:
|
|
351
|
+
1. Overview of the feature/requirement
|
|
352
|
+
2. List of user stories (with brief descriptions)
|
|
353
|
+
3. Estimated complexity for each story (1-5 scale)
|
|
354
|
+
4. Dependencies between stories
|
|
355
|
+
5. Suggested priority order
|
|
356
|
+
|
|
357
|
+
Format your response as structured text."""
|
|
358
|
+
|
|
359
|
+
instruction = GenericInstruction(
|
|
360
|
+
agent_name="planner",
|
|
361
|
+
command="plan",
|
|
362
|
+
prompt=prompt,
|
|
363
|
+
parameters={"description": description},
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
return {
|
|
367
|
+
"type": "plan",
|
|
368
|
+
"description": description,
|
|
369
|
+
"error": error_msg,
|
|
370
|
+
"error_type": "network_error",
|
|
371
|
+
"instruction": instruction.to_dict(),
|
|
372
|
+
"skill_command": instruction.to_skill_command(),
|
|
373
|
+
"created_at": datetime.now().isoformat(),
|
|
374
|
+
}
|
|
375
|
+
except Exception as e:
|
|
376
|
+
# All other errors
|
|
377
|
+
error_msg = f"Error generating plan: {str(e)}"
|
|
378
|
+
logger.error(f"Error generating plan: {e}", exc_info=True)
|
|
379
|
+
# Fallback to instruction if analyst fails
|
|
380
|
+
prompt = f"""You are a software planning expert. Analyze the following requirement and create a detailed plan.
|
|
381
|
+
|
|
382
|
+
Requirement:
|
|
383
|
+
{description}
|
|
384
|
+
|
|
385
|
+
Generate a plan that includes:
|
|
386
|
+
1. Overview of the feature/requirement
|
|
387
|
+
2. List of user stories (with brief descriptions)
|
|
388
|
+
3. Estimated complexity for each story (1-5 scale)
|
|
389
|
+
4. Dependencies between stories
|
|
390
|
+
5. Suggested priority order
|
|
391
|
+
|
|
392
|
+
Format your response as structured text."""
|
|
393
|
+
|
|
394
|
+
instruction = GenericInstruction(
|
|
395
|
+
agent_name="planner",
|
|
396
|
+
command="plan",
|
|
397
|
+
prompt=prompt,
|
|
398
|
+
parameters={"description": description},
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
return {
|
|
402
|
+
"type": "plan",
|
|
403
|
+
"description": description,
|
|
404
|
+
"error": error_msg,
|
|
405
|
+
"error_type": "unknown_error",
|
|
406
|
+
"instruction": instruction.to_dict(),
|
|
407
|
+
"skill_command": instruction.to_skill_command(),
|
|
408
|
+
"created_at": datetime.now().isoformat(),
|
|
409
|
+
}
|
|
410
|
+
finally:
|
|
411
|
+
# Ensure analyst is always closed, even on error
|
|
412
|
+
if analyst is not None:
|
|
413
|
+
try:
|
|
414
|
+
await analyst.close()
|
|
415
|
+
except Exception as close_error:
|
|
416
|
+
logger.warning(f"Error closing analyst agent: {close_error}", exc_info=True)
|
|
417
|
+
|
|
418
|
+
async def create_story(
|
|
419
|
+
self, description: str, epic: str | None = None, priority: str = "medium"
|
|
420
|
+
) -> dict[str, Any]:
|
|
421
|
+
"""
|
|
422
|
+
Generate a user story from a description.
|
|
423
|
+
|
|
424
|
+
Args:
|
|
425
|
+
description: Story description
|
|
426
|
+
epic: Epic or feature area (optional)
|
|
427
|
+
priority: Priority (high/medium/low)
|
|
428
|
+
"""
|
|
429
|
+
# Generate story ID
|
|
430
|
+
story_id = self._generate_story_id(description, epic)
|
|
431
|
+
|
|
432
|
+
# Create story metadata
|
|
433
|
+
story_metadata = {
|
|
434
|
+
"story_id": story_id,
|
|
435
|
+
"title": self._extract_title(description),
|
|
436
|
+
"description": description,
|
|
437
|
+
"epic": epic or "general",
|
|
438
|
+
"domain": self._infer_domain(description),
|
|
439
|
+
"priority": priority,
|
|
440
|
+
"complexity": await self._estimate_complexity(description),
|
|
441
|
+
"status": "draft",
|
|
442
|
+
"created_at": datetime.now().isoformat(),
|
|
443
|
+
"created_by": "planner",
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
# Generate acceptance criteria and tasks using LLM
|
|
447
|
+
acceptance_criteria, tasks = await self._generate_story_details(description)
|
|
448
|
+
|
|
449
|
+
# Create story file
|
|
450
|
+
story_file = await self._write_story_file(
|
|
451
|
+
story_metadata, acceptance_criteria, tasks
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
return {
|
|
455
|
+
"type": "story",
|
|
456
|
+
"story_id": story_id,
|
|
457
|
+
"story_file": str(story_file),
|
|
458
|
+
"metadata": story_metadata,
|
|
459
|
+
"acceptance_criteria": acceptance_criteria,
|
|
460
|
+
"tasks": tasks,
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
async def list_stories(
|
|
464
|
+
self, epic: str | None = None, status: str | None = None
|
|
465
|
+
) -> dict[str, Any]:
|
|
466
|
+
"""
|
|
467
|
+
List all stories in the project.
|
|
468
|
+
|
|
469
|
+
Args:
|
|
470
|
+
epic: Filter by epic (optional)
|
|
471
|
+
status: Filter by status (optional)
|
|
472
|
+
"""
|
|
473
|
+
if self.stories_dir is None:
|
|
474
|
+
# Default to stories/ directory in project root
|
|
475
|
+
project_root = Path.cwd()
|
|
476
|
+
self.stories_dir = project_root / "stories"
|
|
477
|
+
|
|
478
|
+
stories = []
|
|
479
|
+
|
|
480
|
+
if not self.stories_dir.exists():
|
|
481
|
+
return {
|
|
482
|
+
"type": "list_stories",
|
|
483
|
+
"stories": [],
|
|
484
|
+
"count": 0,
|
|
485
|
+
"message": f"Stories directory not found: {self.stories_dir}",
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
# Find all story files
|
|
489
|
+
story_files = list(self.stories_dir.glob("story-*.md")) + list(
|
|
490
|
+
self.stories_dir.glob("story-*.yaml")
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
for story_file in story_files:
|
|
494
|
+
try:
|
|
495
|
+
metadata = self._read_story_metadata(story_file)
|
|
496
|
+
|
|
497
|
+
# Apply filters
|
|
498
|
+
if epic and metadata.get("epic") != epic:
|
|
499
|
+
continue
|
|
500
|
+
if status and metadata.get("status") != status:
|
|
501
|
+
continue
|
|
502
|
+
|
|
503
|
+
stories.append(
|
|
504
|
+
{
|
|
505
|
+
"story_id": metadata.get("story_id"),
|
|
506
|
+
"title": metadata.get("title"),
|
|
507
|
+
"epic": metadata.get("epic"),
|
|
508
|
+
"status": metadata.get("status"),
|
|
509
|
+
"priority": metadata.get("priority"),
|
|
510
|
+
"complexity": metadata.get("complexity"),
|
|
511
|
+
"file": str(story_file),
|
|
512
|
+
}
|
|
513
|
+
)
|
|
514
|
+
except Exception:
|
|
515
|
+
# Skip invalid story files
|
|
516
|
+
logger.debug(
|
|
517
|
+
"Skipping invalid story file %s", story_file, exc_info=True
|
|
518
|
+
)
|
|
519
|
+
continue # nosec B112 - best-effort story discovery
|
|
520
|
+
|
|
521
|
+
return {
|
|
522
|
+
"type": "list_stories",
|
|
523
|
+
"stories": stories,
|
|
524
|
+
"count": len(stories),
|
|
525
|
+
"filters": {"epic": epic, "status": status},
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
def _generate_story_id(self, description: str, epic: str | None = None) -> str:
|
|
529
|
+
"""Generate a unique story ID from description."""
|
|
530
|
+
# Create a slug from description
|
|
531
|
+
slug = re.sub(r"[^a-z0-9]+", "-", description.lower()[:50])
|
|
532
|
+
slug = slug.strip("-")
|
|
533
|
+
|
|
534
|
+
# Add epic prefix if provided
|
|
535
|
+
if epic:
|
|
536
|
+
epic_slug = re.sub(r"[^a-z0-9]+", "-", epic.lower())
|
|
537
|
+
return f"{epic_slug}-{slug[:30]}"
|
|
538
|
+
|
|
539
|
+
return f"story-{slug[:30]}"
|
|
540
|
+
|
|
541
|
+
def _extract_title(self, description: str) -> str:
|
|
542
|
+
"""Extract a short title from description."""
|
|
543
|
+
# Take first line or first 60 chars
|
|
544
|
+
title = description.split("\n")[0].strip()
|
|
545
|
+
if len(title) > 60:
|
|
546
|
+
title = title[:57] + "..."
|
|
547
|
+
return title
|
|
548
|
+
|
|
549
|
+
def _infer_domain(self, description: str) -> str:
|
|
550
|
+
"""Infer domain from description (basic heuristic)."""
|
|
551
|
+
description_lower = description.lower()
|
|
552
|
+
|
|
553
|
+
# Simple keyword matching (can be enhanced with LLM)
|
|
554
|
+
if any(word in description_lower for word in ["api", "endpoint", "service"]):
|
|
555
|
+
return "backend"
|
|
556
|
+
elif any(
|
|
557
|
+
word in description_lower
|
|
558
|
+
for word in ["ui", "interface", "component", "page"]
|
|
559
|
+
):
|
|
560
|
+
return "frontend"
|
|
561
|
+
elif any(word in description_lower for word in ["test", "testing", "spec"]):
|
|
562
|
+
return "testing"
|
|
563
|
+
elif any(
|
|
564
|
+
word in description_lower for word in ["documentation", "docs", "guide"]
|
|
565
|
+
):
|
|
566
|
+
return "documentation"
|
|
567
|
+
else:
|
|
568
|
+
return "general"
|
|
569
|
+
|
|
570
|
+
async def _estimate_complexity(self, description: str) -> int:
|
|
571
|
+
"""Estimate complexity (1-5 scale) using LLM."""
|
|
572
|
+
prompt = f"""Estimate the complexity of implementing this story on a scale of 1-5:
|
|
573
|
+
- 1: Trivial (simple change, <1 hour)
|
|
574
|
+
- 2: Easy (straightforward, 1-4 hours)
|
|
575
|
+
- 3: Medium (moderate effort, 1-2 days)
|
|
576
|
+
- 4: Complex (significant effort, 3-5 days)
|
|
577
|
+
- 5: Very Complex (major feature, 1+ weeks)
|
|
578
|
+
|
|
579
|
+
Story: {description}
|
|
580
|
+
|
|
581
|
+
Respond with ONLY a single number (1-5)."""
|
|
582
|
+
|
|
583
|
+
# Prepare instruction for Cursor Skills
|
|
584
|
+
instruction = GenericInstruction(
|
|
585
|
+
agent_name="planner",
|
|
586
|
+
command="estimate-complexity",
|
|
587
|
+
prompt=prompt,
|
|
588
|
+
parameters={"description": description},
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
# Return instruction - complexity will be determined by Cursor Skills
|
|
592
|
+
# Default to medium complexity for now
|
|
593
|
+
return {
|
|
594
|
+
"instruction": instruction.to_dict(),
|
|
595
|
+
"skill_command": instruction.to_skill_command(),
|
|
596
|
+
"estimated_complexity": 3, # Default
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
async def _generate_story_details(
|
|
600
|
+
self, description: str
|
|
601
|
+
) -> tuple[list[str], list[str]]:
|
|
602
|
+
"""Generate acceptance criteria and tasks using LLM."""
|
|
603
|
+
prompt = f"""Generate a user story breakdown for:
|
|
604
|
+
|
|
605
|
+
{description}
|
|
606
|
+
|
|
607
|
+
Provide:
|
|
608
|
+
1. Acceptance Criteria (list of 3-5 criteria, one per line, prefixed with "- ")
|
|
609
|
+
2. Tasks (list of 3-7 tasks, one per line, prefixed with "1. ", "2. ", etc.)
|
|
610
|
+
|
|
611
|
+
Format:
|
|
612
|
+
ACCEPTANCE CRITERIA:
|
|
613
|
+
- Criterion 1
|
|
614
|
+
- Criterion 2
|
|
615
|
+
|
|
616
|
+
TASKS:
|
|
617
|
+
1. Task 1
|
|
618
|
+
2. Task 2"""
|
|
619
|
+
|
|
620
|
+
# Prepare instruction for Cursor Skills
|
|
621
|
+
instruction = GenericInstruction(
|
|
622
|
+
agent_name="planner",
|
|
623
|
+
command="generate-story-details",
|
|
624
|
+
prompt=prompt,
|
|
625
|
+
parameters={"description": description},
|
|
626
|
+
)
|
|
627
|
+
|
|
628
|
+
# Return instruction - details will be generated by Cursor Skills
|
|
629
|
+
# Return basic criteria/tasks for now
|
|
630
|
+
acceptance_criteria = ["Story implementation meets requirements"]
|
|
631
|
+
tasks = ["Implement story requirements"]
|
|
632
|
+
|
|
633
|
+
return acceptance_criteria, tasks
|
|
634
|
+
|
|
635
|
+
async def _write_story_file(
|
|
636
|
+
self, metadata: dict[str, Any], acceptance_criteria: list[str], tasks: list[str]
|
|
637
|
+
) -> Path:
|
|
638
|
+
"""Write story to file (Markdown format)."""
|
|
639
|
+
if self.stories_dir is None:
|
|
640
|
+
project_root = Path.cwd()
|
|
641
|
+
self.stories_dir = project_root / "stories"
|
|
642
|
+
|
|
643
|
+
# Create stories directory if it doesn't exist
|
|
644
|
+
self.stories_dir.mkdir(parents=True, exist_ok=True)
|
|
645
|
+
|
|
646
|
+
story_id = metadata["story_id"]
|
|
647
|
+
story_file = self.stories_dir / f"{story_id}.md"
|
|
648
|
+
|
|
649
|
+
# Generate story content
|
|
650
|
+
content = f"""# {metadata['title']}
|
|
651
|
+
|
|
652
|
+
```yaml
|
|
653
|
+
story_id: {metadata['story_id']}
|
|
654
|
+
title: {metadata['title']}
|
|
655
|
+
description: |
|
|
656
|
+
{self._indent_yaml_multiline(metadata['description'])}
|
|
657
|
+
epic: {metadata['epic']}
|
|
658
|
+
domain: {metadata['domain']}
|
|
659
|
+
priority: {metadata['priority']}
|
|
660
|
+
complexity: {metadata['complexity']}
|
|
661
|
+
status: {metadata['status']}
|
|
662
|
+
created_at: {metadata['created_at']}
|
|
663
|
+
created_by: {metadata['created_by']}
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
## Description
|
|
667
|
+
|
|
668
|
+
{metadata['description']}
|
|
669
|
+
|
|
670
|
+
## Acceptance Criteria
|
|
671
|
+
|
|
672
|
+
{chr(10).join(f"- [ ] {ac}" for ac in acceptance_criteria)}
|
|
673
|
+
|
|
674
|
+
## Tasks
|
|
675
|
+
|
|
676
|
+
{chr(10).join(f"{i+1}. {task}" for i, task in enumerate(tasks))}
|
|
677
|
+
|
|
678
|
+
## Technical Notes
|
|
679
|
+
|
|
680
|
+
(Technical considerations, dependencies, etc.)
|
|
681
|
+
|
|
682
|
+
## Dependencies
|
|
683
|
+
|
|
684
|
+
- Related stories: []
|
|
685
|
+
- Blocks: []
|
|
686
|
+
- Blocked by: []
|
|
687
|
+
"""
|
|
688
|
+
|
|
689
|
+
story_file.write_text(content, encoding="utf-8")
|
|
690
|
+
return story_file
|
|
691
|
+
|
|
692
|
+
def _indent_yaml_multiline(self, text: str, indent: int = 2) -> str:
|
|
693
|
+
"""Indent multiline text for YAML."""
|
|
694
|
+
lines = text.split("\n")
|
|
695
|
+
return "\n".join(" " * indent + line for line in lines)
|
|
696
|
+
|
|
697
|
+
def _read_story_metadata(self, story_file: Path) -> dict[str, Any]:
|
|
698
|
+
"""Read story metadata from file."""
|
|
699
|
+
content = story_file.read_text(encoding="utf-8")
|
|
700
|
+
|
|
701
|
+
# Extract YAML frontmatter
|
|
702
|
+
yaml_match = re.search(r"```yaml\n(.*?)\n```", content, re.DOTALL)
|
|
703
|
+
if yaml_match:
|
|
704
|
+
try:
|
|
705
|
+
metadata = yaml.safe_load(yaml_match.group(1))
|
|
706
|
+
return metadata or {}
|
|
707
|
+
except yaml.YAMLError:
|
|
708
|
+
pass
|
|
709
|
+
|
|
710
|
+
return {}
|
|
711
|
+
|
|
712
|
+
async def _generate_user_stories(
|
|
713
|
+
self, description: str, functional_requirements: list[str]
|
|
714
|
+
) -> list[dict[str, Any]]:
|
|
715
|
+
"""Generate user stories in standard format from requirements."""
|
|
716
|
+
from ...core.mal import MAL
|
|
717
|
+
mal = MAL()
|
|
718
|
+
|
|
719
|
+
# Build prompt for user story generation
|
|
720
|
+
reqs_text = "\n".join(f"- {req}" for req in functional_requirements) if functional_requirements else description
|
|
721
|
+
|
|
722
|
+
prompt = f"""Generate user stories in the standard format "As a {{user}}, I want {{goal}}, so that {{benefit}}" from the following requirements:
|
|
723
|
+
|
|
724
|
+
Description: {description}
|
|
725
|
+
|
|
726
|
+
Functional Requirements:
|
|
727
|
+
{reqs_text}
|
|
728
|
+
|
|
729
|
+
For each user story, provide:
|
|
730
|
+
1. The story in standard format: "As a [user type], I want [goal], so that [benefit]"
|
|
731
|
+
2. Acceptance criteria (3-5 items)
|
|
732
|
+
3. Story points estimate (Fibonacci: 1, 2, 3, 5, 8, 13)
|
|
733
|
+
|
|
734
|
+
Format as JSON array with:
|
|
735
|
+
- story: The story text in standard format
|
|
736
|
+
- user: User type
|
|
737
|
+
- goal: What they want to do
|
|
738
|
+
- benefit: Why they want it
|
|
739
|
+
- acceptance_criteria: List of criteria
|
|
740
|
+
- story_points: Number (1-13)
|
|
741
|
+
"""
|
|
742
|
+
|
|
743
|
+
try:
|
|
744
|
+
response = await mal.generate(
|
|
745
|
+
prompt=prompt,
|
|
746
|
+
system_prompt="You are a product planner. Generate user stories in standard format from requirements.",
|
|
747
|
+
)
|
|
748
|
+
|
|
749
|
+
# Parse JSON response
|
|
750
|
+
import json as json_lib
|
|
751
|
+
try:
|
|
752
|
+
stories_data = json_lib.loads(response)
|
|
753
|
+
if not isinstance(stories_data, list):
|
|
754
|
+
stories_data = [stories_data]
|
|
755
|
+
except (json_lib.JSONDecodeError, ValueError):
|
|
756
|
+
# Fallback: create basic story from description
|
|
757
|
+
stories_data = [{
|
|
758
|
+
"story": f"As a user, I want {description.lower()}, so that I can accomplish my goal",
|
|
759
|
+
"user": "user",
|
|
760
|
+
"goal": description.lower(),
|
|
761
|
+
"benefit": "accomplish my goal",
|
|
762
|
+
"acceptance_criteria": ["Feature works as described", "Tests pass", "Documentation updated"],
|
|
763
|
+
"story_points": 3,
|
|
764
|
+
}]
|
|
765
|
+
|
|
766
|
+
return stories_data
|
|
767
|
+
except Exception as e:
|
|
768
|
+
logger.warning(f"Error generating user stories: {e}")
|
|
769
|
+
# Fallback: create basic story
|
|
770
|
+
return [{
|
|
771
|
+
"story": f"As a user, I want {description.lower()}, so that I can accomplish my goal",
|
|
772
|
+
"user": "user",
|
|
773
|
+
"goal": description.lower(),
|
|
774
|
+
"benefit": "accomplish my goal",
|
|
775
|
+
"acceptance_criteria": ["Feature works as described"],
|
|
776
|
+
"story_points": 3,
|
|
777
|
+
}]
|
|
778
|
+
|
|
779
|
+
def _format_plan_markdown(
|
|
780
|
+
self,
|
|
781
|
+
description: str,
|
|
782
|
+
requirements_result: dict[str, Any],
|
|
783
|
+
functional_reqs: list[str],
|
|
784
|
+
non_functional_reqs: list[str],
|
|
785
|
+
user_stories: list[dict[str, Any]],
|
|
786
|
+
) -> str:
|
|
787
|
+
"""Format plan as markdown document with user stories."""
|
|
788
|
+
lines = [
|
|
789
|
+
f"# Plan: {description}",
|
|
790
|
+
"",
|
|
791
|
+
"## Overview",
|
|
792
|
+
"",
|
|
793
|
+
requirements_result.get('summary', {}).get('overview', 'Feature implementation plan') if isinstance(requirements_result.get('summary'), dict) else 'Feature implementation plan',
|
|
794
|
+
"",
|
|
795
|
+
"## Requirements",
|
|
796
|
+
"",
|
|
797
|
+
"### Functional Requirements",
|
|
798
|
+
"",
|
|
799
|
+
]
|
|
800
|
+
|
|
801
|
+
if functional_reqs:
|
|
802
|
+
for req in functional_reqs:
|
|
803
|
+
lines.append(f"- {req}")
|
|
804
|
+
else:
|
|
805
|
+
lines.append("- Requirements analysis in progress")
|
|
806
|
+
|
|
807
|
+
lines.extend([
|
|
808
|
+
"",
|
|
809
|
+
"### Non-Functional Requirements",
|
|
810
|
+
"",
|
|
811
|
+
])
|
|
812
|
+
|
|
813
|
+
if non_functional_reqs:
|
|
814
|
+
for req in non_functional_reqs:
|
|
815
|
+
lines.append(f"- {req}")
|
|
816
|
+
else:
|
|
817
|
+
lines.append("- Requirements analysis in progress")
|
|
818
|
+
|
|
819
|
+
lines.extend([
|
|
820
|
+
"",
|
|
821
|
+
"## User Stories",
|
|
822
|
+
"",
|
|
823
|
+
])
|
|
824
|
+
|
|
825
|
+
if user_stories:
|
|
826
|
+
for i, story in enumerate(user_stories, 1):
|
|
827
|
+
story_text = story.get("story", f"Story {i}")
|
|
828
|
+
user = story.get("user", "user")
|
|
829
|
+
goal = story.get("goal", "")
|
|
830
|
+
benefit = story.get("benefit", "")
|
|
831
|
+
acceptance_criteria = story.get("acceptance_criteria", [])
|
|
832
|
+
story_points = story.get("story_points", 0)
|
|
833
|
+
|
|
834
|
+
lines.extend([
|
|
835
|
+
f"### Story {i}: {story_text}",
|
|
836
|
+
"",
|
|
837
|
+
f"**Story Points:** {story_points}",
|
|
838
|
+
"",
|
|
839
|
+
"**Acceptance Criteria:**",
|
|
840
|
+
"",
|
|
841
|
+
])
|
|
842
|
+
|
|
843
|
+
for ac in acceptance_criteria:
|
|
844
|
+
lines.append(f"- [ ] {ac}")
|
|
845
|
+
|
|
846
|
+
lines.append("")
|
|
847
|
+
else:
|
|
848
|
+
lines.append("(User stories to be generated)")
|
|
849
|
+
lines.append("")
|
|
850
|
+
|
|
851
|
+
lines.extend([
|
|
852
|
+
"## Estimated Complexity",
|
|
853
|
+
"",
|
|
854
|
+
"(To be estimated per story)",
|
|
855
|
+
"",
|
|
856
|
+
"## Dependencies",
|
|
857
|
+
"",
|
|
858
|
+
"(To be identified)",
|
|
859
|
+
"",
|
|
860
|
+
"## Priority Order",
|
|
861
|
+
"",
|
|
862
|
+
"(To be determined)",
|
|
863
|
+
"",
|
|
864
|
+
])
|
|
865
|
+
|
|
866
|
+
return "\n".join(lines)
|
|
867
|
+
|
|
868
|
+
def _help(self) -> dict[str, Any]:
|
|
869
|
+
"""
|
|
870
|
+
Return help information for Planner Agent.
|
|
871
|
+
|
|
872
|
+
Returns standardized help format with commands, examples, and usage notes.
|
|
873
|
+
This method is synchronous as it performs no I/O operations.
|
|
874
|
+
|
|
875
|
+
Returns:
|
|
876
|
+
dict: Help information with standardized format:
|
|
877
|
+
- type (str): Always "help"
|
|
878
|
+
- content (str): Formatted markdown help text containing:
|
|
879
|
+
- Available commands list
|
|
880
|
+
- Usage examples
|
|
881
|
+
- Story storage information
|
|
882
|
+
|
|
883
|
+
Note:
|
|
884
|
+
This method is called via agent.run("help") which handles async context.
|
|
885
|
+
Command list is cached via BaseAgent.get_commands() for performance.
|
|
886
|
+
"""
|
|
887
|
+
commands = self.get_commands()
|
|
888
|
+
command_lines = [
|
|
889
|
+
f"- **{cmd['command']}**: {cmd['description']}"
|
|
890
|
+
for cmd in commands
|
|
891
|
+
]
|
|
892
|
+
|
|
893
|
+
content = f"""# {self.agent_name} - Help
|
|
894
|
+
|
|
895
|
+
## Available Commands
|
|
896
|
+
|
|
897
|
+
{chr(10).join(command_lines)}
|
|
898
|
+
|
|
899
|
+
## Usage Examples
|
|
900
|
+
|
|
901
|
+
### Create a Plan
|
|
902
|
+
```
|
|
903
|
+
*plan Add user authentication with OAuth2 support
|
|
904
|
+
```
|
|
905
|
+
|
|
906
|
+
### Create a Story
|
|
907
|
+
```
|
|
908
|
+
*create-story User should be able to log in with Google
|
|
909
|
+
*create-story Add shopping cart --epic=checkout --priority=high
|
|
910
|
+
```
|
|
911
|
+
|
|
912
|
+
### List Stories
|
|
913
|
+
```
|
|
914
|
+
*list-stories
|
|
915
|
+
*list-stories --epic=checkout
|
|
916
|
+
*list-stories --status=draft
|
|
917
|
+
```
|
|
918
|
+
|
|
919
|
+
## Story Storage
|
|
920
|
+
|
|
921
|
+
Stories are saved to `stories/` directory in your project root.
|
|
922
|
+
Each story is saved as a Markdown file with YAML frontmatter.
|
|
923
|
+
"""
|
|
924
|
+
|
|
925
|
+
return {"type": "help", "content": content}
|
|
926
|
+
|
|
927
|
+
async def _evaluate_stories(self, stories: list[dict[str, Any]]) -> dict[str, Any]:
|
|
928
|
+
"""Evaluate story quality using INVEST criteria."""
|
|
929
|
+
from ...core.story_evaluator import StoryEvaluator
|
|
930
|
+
|
|
931
|
+
evaluator = StoryEvaluator()
|
|
932
|
+
results = []
|
|
933
|
+
|
|
934
|
+
for story in stories:
|
|
935
|
+
score = evaluator.evaluate(story)
|
|
936
|
+
results.append(
|
|
937
|
+
{
|
|
938
|
+
"story_id": story.get("id", "unknown"),
|
|
939
|
+
"title": story.get("title", "Untitled"),
|
|
940
|
+
"score": {
|
|
941
|
+
"overall": score.overall,
|
|
942
|
+
"independent": score.independent,
|
|
943
|
+
"negotiable": score.negotiable,
|
|
944
|
+
"valuable": score.valuable,
|
|
945
|
+
"estimable": score.estimable,
|
|
946
|
+
"small": score.small,
|
|
947
|
+
"testable": score.testable,
|
|
948
|
+
},
|
|
949
|
+
"issues": score.issues,
|
|
950
|
+
"strengths": score.strengths,
|
|
951
|
+
"recommendations": score.recommendations,
|
|
952
|
+
}
|
|
953
|
+
)
|
|
954
|
+
|
|
955
|
+
# Calculate average scores
|
|
956
|
+
avg_scores = {
|
|
957
|
+
"overall": sum(r["score"]["overall"] for r in results) / len(results) if results else 0.0,
|
|
958
|
+
"independent": sum(r["score"]["independent"] for r in results) / len(results) if results else 0.0,
|
|
959
|
+
"negotiable": sum(r["score"]["negotiable"] for r in results) / len(results) if results else 0.0,
|
|
960
|
+
"valuable": sum(r["score"]["valuable"] for r in results) / len(results) if results else 0.0,
|
|
961
|
+
"estimable": sum(r["score"]["estimable"] for r in results) / len(results) if results else 0.0,
|
|
962
|
+
"small": sum(r["score"]["small"] for r in results) / len(results) if results else 0.0,
|
|
963
|
+
"testable": sum(r["score"]["testable"] for r in results) / len(results) if results else 0.0,
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
return {
|
|
967
|
+
"success": True,
|
|
968
|
+
"stories_evaluated": len(results),
|
|
969
|
+
"average_scores": avg_scores,
|
|
970
|
+
"story_results": results,
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
async def _validate_stories(self, stories: list[dict[str, Any]]) -> dict[str, Any]:
|
|
974
|
+
"""Validate stories for completeness and quality."""
|
|
975
|
+
from ...core.story_evaluator import StoryEvaluator
|
|
976
|
+
|
|
977
|
+
evaluator = StoryEvaluator()
|
|
978
|
+
results = []
|
|
979
|
+
all_valid = True
|
|
980
|
+
|
|
981
|
+
for story in stories:
|
|
982
|
+
result = evaluator.validate(story)
|
|
983
|
+
all_valid = all_valid and result.is_valid
|
|
984
|
+
results.append(
|
|
985
|
+
{
|
|
986
|
+
"story_id": story.get("id", "unknown"),
|
|
987
|
+
"title": story.get("title", "Untitled"),
|
|
988
|
+
"is_valid": result.is_valid,
|
|
989
|
+
"score": {
|
|
990
|
+
"overall": result.score.overall,
|
|
991
|
+
"independent": result.score.independent,
|
|
992
|
+
"negotiable": result.score.negotiable,
|
|
993
|
+
"valuable": result.score.valuable,
|
|
994
|
+
"estimable": result.score.estimable,
|
|
995
|
+
"small": result.score.small,
|
|
996
|
+
"testable": result.score.testable,
|
|
997
|
+
},
|
|
998
|
+
"missing_elements": result.missing_elements,
|
|
999
|
+
"weak_acceptance_criteria": result.weak_acceptance_criteria,
|
|
1000
|
+
"dependency_issues": result.dependency_issues,
|
|
1001
|
+
"issues": result.score.issues,
|
|
1002
|
+
"recommendations": result.score.recommendations,
|
|
1003
|
+
}
|
|
1004
|
+
)
|
|
1005
|
+
|
|
1006
|
+
return {
|
|
1007
|
+
"success": True,
|
|
1008
|
+
"all_valid": all_valid,
|
|
1009
|
+
"stories_validated": len(results),
|
|
1010
|
+
"validation_results": results,
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
async def _trace_stories(
|
|
1014
|
+
self, stories: list[dict[str, Any]], test_cases: list[dict[str, Any]], output_file: str | None = None
|
|
1015
|
+
) -> dict[str, Any]:
|
|
1016
|
+
"""Map stories to acceptance criteria and test cases."""
|
|
1017
|
+
from ...core.traceability import TraceabilityManager
|
|
1018
|
+
|
|
1019
|
+
manager = TraceabilityManager()
|
|
1020
|
+
matrix = manager.get_matrix()
|
|
1021
|
+
|
|
1022
|
+
traceability_map = []
|
|
1023
|
+
|
|
1024
|
+
for story in stories:
|
|
1025
|
+
story_id = story.get("id", "unknown")
|
|
1026
|
+
matrix.add_story(story_id, story)
|
|
1027
|
+
|
|
1028
|
+
acceptance_criteria = story.get("acceptance_criteria", [])
|
|
1029
|
+
linked_tests = []
|
|
1030
|
+
|
|
1031
|
+
# Link test cases to acceptance criteria
|
|
1032
|
+
for test_case in test_cases:
|
|
1033
|
+
test_story_id = test_case.get("story_id") or test_case.get("related_story")
|
|
1034
|
+
if test_story_id == story_id:
|
|
1035
|
+
test_id = test_case.get("id", "unknown")
|
|
1036
|
+
linked_tests.append(test_id)
|
|
1037
|
+
|
|
1038
|
+
# Link story to test
|
|
1039
|
+
matrix.link("story", story_id, "test", test_id, "validates", 1.0)
|
|
1040
|
+
|
|
1041
|
+
traceability_map.append(
|
|
1042
|
+
{
|
|
1043
|
+
"story_id": story_id,
|
|
1044
|
+
"title": story.get("title", "Untitled"),
|
|
1045
|
+
"acceptance_criteria": acceptance_criteria,
|
|
1046
|
+
"linked_tests": linked_tests,
|
|
1047
|
+
"coverage": len(linked_tests) / len(acceptance_criteria) * 100.0 if acceptance_criteria else 0.0,
|
|
1048
|
+
}
|
|
1049
|
+
)
|
|
1050
|
+
|
|
1051
|
+
# Save matrix
|
|
1052
|
+
manager.save_matrix()
|
|
1053
|
+
|
|
1054
|
+
# Generate report
|
|
1055
|
+
report = {
|
|
1056
|
+
"stories_traced": len(traceability_map),
|
|
1057
|
+
"total_acceptance_criteria": sum(len(m["acceptance_criteria"]) for m in traceability_map),
|
|
1058
|
+
"total_tests": len(test_cases),
|
|
1059
|
+
"average_coverage": sum(m["coverage"] for m in traceability_map) / len(traceability_map) if traceability_map else 0.0,
|
|
1060
|
+
"traceability_map": traceability_map,
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
# Save to file if specified
|
|
1064
|
+
if output_file:
|
|
1065
|
+
output_path = Path(output_file)
|
|
1066
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
1067
|
+
import yaml
|
|
1068
|
+
with open(output_path, "w", encoding="utf-8") as f:
|
|
1069
|
+
yaml.dump(report, f, default_flow_style=False)
|
|
1070
|
+
|
|
1071
|
+
return {
|
|
1072
|
+
"success": True,
|
|
1073
|
+
"traceability_report": report,
|
|
1074
|
+
"matrix_file": str(manager.matrix_file),
|
|
1075
|
+
"output_file": str(output_path) if output_file else None,
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
async def _review_stories(self, stories: list[dict[str, Any]]) -> dict[str, Any]:
|
|
1079
|
+
"""Structured review of stories with INVEST checklist."""
|
|
1080
|
+
from ...core.review_checklists import StoryReviewChecklist
|
|
1081
|
+
|
|
1082
|
+
checklist = StoryReviewChecklist()
|
|
1083
|
+
results = []
|
|
1084
|
+
|
|
1085
|
+
for story in stories:
|
|
1086
|
+
result = checklist.review(story)
|
|
1087
|
+
results.append(
|
|
1088
|
+
{
|
|
1089
|
+
"story_id": story.get("id", "unknown"),
|
|
1090
|
+
"title": story.get("title", "Untitled"),
|
|
1091
|
+
"overall_score": result.overall_score,
|
|
1092
|
+
"items_checked": result.items_checked,
|
|
1093
|
+
"items_total": result.items_total,
|
|
1094
|
+
"critical_issues": result.critical_issues,
|
|
1095
|
+
"high_issues": result.high_issues,
|
|
1096
|
+
"medium_issues": result.medium_issues,
|
|
1097
|
+
"low_issues": result.low_issues,
|
|
1098
|
+
"recommendations": result.recommendations,
|
|
1099
|
+
}
|
|
1100
|
+
)
|
|
1101
|
+
|
|
1102
|
+
avg_score = sum(r["overall_score"] for r in results) / len(results) if results else 0.0
|
|
1103
|
+
|
|
1104
|
+
return {
|
|
1105
|
+
"success": True,
|
|
1106
|
+
"stories_reviewed": len(results),
|
|
1107
|
+
"average_score": avg_score,
|
|
1108
|
+
"review_results": results,
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
async def _calibrate_estimates(self, estimated_points: int, complexity: str) -> dict[str, Any]:
|
|
1112
|
+
"""Get calibrated estimates based on historical accuracy."""
|
|
1113
|
+
from ...core.estimation_tracker import EstimationTracker
|
|
1114
|
+
|
|
1115
|
+
tracker = EstimationTracker()
|
|
1116
|
+
tracker.load()
|
|
1117
|
+
|
|
1118
|
+
calibrated = tracker.get_calibrated_estimate(estimated_points, complexity)
|
|
1119
|
+
|
|
1120
|
+
return {
|
|
1121
|
+
"success": True,
|
|
1122
|
+
**calibrated,
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
async def close(self):
|
|
1126
|
+
"""Clean up resources"""
|
|
1127
|
+
pass
|