tapps-agents 3.5.39__py3-none-any.whl → 3.5.41__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.
Files changed (71) hide show
  1. tapps_agents/__init__.py +2 -2
  2. tapps_agents/agents/enhancer/agent.py +2728 -2728
  3. tapps_agents/agents/implementer/agent.py +35 -13
  4. tapps_agents/agents/reviewer/agent.py +43 -10
  5. tapps_agents/agents/reviewer/scoring.py +59 -68
  6. tapps_agents/agents/reviewer/tools/__init__.py +24 -0
  7. tapps_agents/agents/reviewer/tools/ruff_grouping.py +250 -0
  8. tapps_agents/agents/reviewer/tools/scoped_mypy.py +284 -0
  9. tapps_agents/beads/__init__.py +11 -0
  10. tapps_agents/beads/hydration.py +213 -0
  11. tapps_agents/beads/specs.py +206 -0
  12. tapps_agents/cli/commands/health.py +19 -3
  13. tapps_agents/cli/commands/simple_mode.py +842 -676
  14. tapps_agents/cli/commands/task.py +227 -0
  15. tapps_agents/cli/commands/top_level.py +13 -0
  16. tapps_agents/cli/main.py +658 -651
  17. tapps_agents/cli/parsers/top_level.py +1978 -1881
  18. tapps_agents/core/config.py +1622 -1622
  19. tapps_agents/core/init_project.py +3012 -2897
  20. tapps_agents/epic/markdown_sync.py +105 -0
  21. tapps_agents/epic/orchestrator.py +1 -2
  22. tapps_agents/epic/parser.py +427 -423
  23. tapps_agents/experts/adaptive_domain_detector.py +0 -2
  24. tapps_agents/experts/knowledge/api-design-integration/api-security-patterns.md +15 -15
  25. tapps_agents/experts/knowledge/api-design-integration/external-api-integration.md +19 -44
  26. tapps_agents/health/checks/outcomes.backup_20260204_064058.py +324 -0
  27. tapps_agents/health/checks/outcomes.backup_20260204_064256.py +324 -0
  28. tapps_agents/health/checks/outcomes.backup_20260204_064600.py +324 -0
  29. tapps_agents/health/checks/outcomes.py +134 -46
  30. tapps_agents/health/orchestrator.py +12 -4
  31. tapps_agents/hooks/__init__.py +33 -0
  32. tapps_agents/hooks/config.py +140 -0
  33. tapps_agents/hooks/events.py +135 -0
  34. tapps_agents/hooks/executor.py +128 -0
  35. tapps_agents/hooks/manager.py +143 -0
  36. tapps_agents/session/__init__.py +19 -0
  37. tapps_agents/session/manager.py +256 -0
  38. tapps_agents/simple_mode/code_snippet_handler.py +382 -0
  39. tapps_agents/simple_mode/intent_parser.py +29 -4
  40. tapps_agents/simple_mode/orchestrators/base.py +185 -59
  41. tapps_agents/simple_mode/orchestrators/build_orchestrator.py +2667 -2642
  42. tapps_agents/simple_mode/orchestrators/fix_orchestrator.py +2 -2
  43. tapps_agents/simple_mode/workflow_suggester.py +37 -3
  44. tapps_agents/workflow/agent_handlers/implementer_handler.py +18 -3
  45. tapps_agents/workflow/cursor_executor.py +2337 -2118
  46. tapps_agents/workflow/direct_execution_fallback.py +16 -3
  47. tapps_agents/workflow/message_formatter.py +2 -1
  48. tapps_agents/workflow/models.py +38 -1
  49. tapps_agents/workflow/parallel_executor.py +43 -4
  50. tapps_agents/workflow/parser.py +375 -357
  51. tapps_agents/workflow/rules_generator.py +337 -337
  52. tapps_agents/workflow/skill_invoker.py +9 -3
  53. {tapps_agents-3.5.39.dist-info → tapps_agents-3.5.41.dist-info}/METADATA +5 -1
  54. {tapps_agents-3.5.39.dist-info → tapps_agents-3.5.41.dist-info}/RECORD +58 -54
  55. tapps_agents/agents/analyst/SKILL.md +0 -85
  56. tapps_agents/agents/architect/SKILL.md +0 -80
  57. tapps_agents/agents/debugger/SKILL.md +0 -66
  58. tapps_agents/agents/designer/SKILL.md +0 -78
  59. tapps_agents/agents/documenter/SKILL.md +0 -95
  60. tapps_agents/agents/enhancer/SKILL.md +0 -189
  61. tapps_agents/agents/implementer/SKILL.md +0 -117
  62. tapps_agents/agents/improver/SKILL.md +0 -55
  63. tapps_agents/agents/ops/SKILL.md +0 -64
  64. tapps_agents/agents/orchestrator/SKILL.md +0 -238
  65. tapps_agents/agents/planner/story_template.md +0 -37
  66. tapps_agents/agents/reviewer/templates/quality-dashboard.html.j2 +0 -150
  67. tapps_agents/agents/tester/SKILL.md +0 -71
  68. {tapps_agents-3.5.39.dist-info → tapps_agents-3.5.41.dist-info}/WHEEL +0 -0
  69. {tapps_agents-3.5.39.dist-info → tapps_agents-3.5.41.dist-info}/entry_points.txt +0 -0
  70. {tapps_agents-3.5.39.dist-info → tapps_agents-3.5.41.dist-info}/licenses/LICENSE +0 -0
  71. {tapps_agents-3.5.39.dist-info → tapps_agents-3.5.41.dist-info}/top_level.txt +0 -0
@@ -1,676 +1,842 @@
1
- """
2
- Simple Mode command handlers.
3
- """
4
-
5
- import sys
6
- import yaml
7
- from pathlib import Path
8
-
9
- from ...core.config import ProjectConfig, load_config, save_config
10
- from ...simple_mode.error_handling import SimpleModeErrorHandler
11
- from ...simple_mode.learning_progression import LearningProgressionTracker
12
- from ...simple_mode.onboarding import OnboardingWizard
13
- from ...simple_mode.zero_config import ZeroConfigMode
14
- from ...workflow.executor import WorkflowExecutor
15
- from ...workflow.preset_loader import PresetLoader
16
- from ..feedback import get_feedback
17
-
18
-
19
- def handle_simple_mode_command(args: object) -> None:
20
- """Handle simple-mode command."""
21
- command = getattr(args, "command", None)
22
-
23
- if command == "on":
24
- handle_simple_mode_on()
25
- elif command == "off":
26
- handle_simple_mode_off()
27
- elif command == "status":
28
- handle_simple_mode_status(args)
29
- elif command == "init":
30
- handle_simple_mode_init()
31
- elif command == "configure" or command == "config":
32
- handle_simple_mode_configure()
33
- elif command == "progress":
34
- handle_simple_mode_progress()
35
- elif command == "full":
36
- handle_simple_mode_full(args)
37
- elif command == "build":
38
- handle_simple_mode_build(args)
39
- elif command == "resume":
40
- handle_simple_mode_resume(args)
41
- elif command == "enhance":
42
- handle_simple_mode_enhance(args)
43
- elif command == "breakdown":
44
- handle_simple_mode_breakdown(args)
45
- elif command == "todo":
46
- handle_simple_mode_todo(args)
47
- else:
48
- feedback = get_feedback()
49
- available_commands = ["on", "off", "status", "init", "configure", "progress", "full", "build", "resume", "enhance", "breakdown", "todo"]
50
- command_list = ", ".join(available_commands)
51
- feedback.error(
52
- f"Invalid simple-mode command: {command or '(none provided)'}",
53
- error_code="invalid_command",
54
- context={"command": command, "available_commands": available_commands},
55
- remediation=f"Use one of: {command_list}",
56
- exit_code=2,
57
- )
58
-
59
-
60
- def handle_simple_mode_on() -> None:
61
- """Enable Simple Mode."""
62
- feedback = get_feedback()
63
- error_handler = SimpleModeErrorHandler()
64
- feedback.start_operation("Enabling Simple Mode")
65
-
66
- # Find config file
67
- config_path = find_config_file()
68
- if not config_path:
69
- error_handler.handle_error(
70
- "config_not_found",
71
- error_message="No config file found",
72
- context={"suggestion": "Run 'tapps-agents init' first"},
73
- )
74
- feedback.error(
75
- "No config file found",
76
- error_code="config_not_found",
77
- remediation="Run 'tapps-agents init' first",
78
- exit_code=2,
79
- )
80
-
81
- # Load config
82
- feedback.running("Loading configuration...", step=2, total_steps=3)
83
- config = load_config(config_path)
84
- config.simple_mode.enabled = True
85
-
86
- # Save config
87
- feedback.running("Saving configuration...", step=3, total_steps=3)
88
- save_config(config_path, config)
89
-
90
- feedback.clear_progress()
91
- feedback.success("Simple Mode enabled")
92
- print("\nSimple Mode is now enabled.")
93
- print("You can use natural language commands like:")
94
- print(" • 'Build a user authentication feature'")
95
- print(" 'Review my authentication code'")
96
- print(" 'Fix the error in auth.py'")
97
- print(" 'Add tests for service.py'")
98
-
99
-
100
- def handle_simple_mode_off() -> None:
101
- """Disable Simple Mode."""
102
- feedback = get_feedback()
103
- error_handler = SimpleModeErrorHandler()
104
- feedback.start_operation("Disable Simple Mode", "Disabling Simple Mode in configuration")
105
-
106
- # Find config file
107
- feedback.running("Locating configuration file...", step=1, total_steps=3)
108
- config_path = find_config_file()
109
- if not config_path:
110
- error_handler.handle_error(
111
- "config_not_found",
112
- error_message="No config file found",
113
- context={"suggestion": "Run 'tapps-agents init' first"},
114
- )
115
- feedback.error(
116
- "No config file found",
117
- error_code="config_not_found",
118
- remediation="Run 'tapps-agents init' first",
119
- exit_code=2,
120
- )
121
-
122
- # Load config
123
- config = load_config(config_path)
124
- config.simple_mode.enabled = False
125
-
126
- # Save config
127
- save_config(config_path, config)
128
-
129
- feedback.clear_progress()
130
- feedback.success("Simple Mode disabled")
131
- print("\nSimple Mode is now disabled.")
132
- print("You can use agent-specific commands like:")
133
- print(" • @reviewer *review")
134
- print(" @implementer *implement")
135
- print(" @tester *test")
136
-
137
-
138
- def handle_simple_mode_status(args: object) -> None:
139
- """Check Simple Mode status."""
140
- feedback = get_feedback()
141
- output_format = getattr(args, "format", "text")
142
- feedback.format_type = output_format
143
-
144
- feedback.start_operation("Simple Mode Status", "Checking Simple Mode configuration status")
145
- feedback.running("Loading configuration...", step=1, total_steps=2)
146
-
147
- # Find config file
148
- config_path = find_config_file()
149
- if not config_path:
150
- # No config file - use defaults
151
- config = ProjectConfig()
152
- else:
153
- feedback.running("Loading configuration...", step=2, total_steps=2)
154
- config = load_config(config_path)
155
-
156
- feedback.clear_progress()
157
-
158
- status = {
159
- "enabled": config.simple_mode.enabled,
160
- "auto_detect": config.simple_mode.auto_detect,
161
- "show_advanced": config.simple_mode.show_advanced,
162
- "natural_language": config.simple_mode.natural_language,
163
- "config_file": str(config_path) if config_path else None,
164
- }
165
-
166
- if output_format == "json":
167
- feedback.output_result(status, message="Simple Mode status retrieved")
168
- else:
169
- feedback.success("Simple Mode status retrieved")
170
- print("\n" + "=" * 60)
171
- print("Simple Mode Status")
172
- print("=" * 60)
173
- print(f"\nEnabled: {'Yes' if status['enabled'] else 'No'}")
174
- print(f"Auto-detect: {'Yes' if status['auto_detect'] else 'No'}")
175
- print(f"Show advanced: {'Yes' if status['show_advanced'] else 'No'}")
176
- print(f"Natural language: {'Yes' if status['natural_language'] else 'No'}")
177
- if status["config_file"]:
178
- print(f"\nConfig file: {status['config_file']}")
179
-
180
-
181
- def find_config_file() -> Path | None:
182
- """Find .tapps-agents/config.yaml in current or parent directories."""
183
- current = Path.cwd()
184
- for parent in [current] + list(current.parents):
185
- candidate = parent / ".tapps-agents" / "config.yaml"
186
- if candidate.exists():
187
- return candidate
188
- return None
189
-
190
-
191
- def handle_simple_mode_init() -> None:
192
- """Run the Simple Mode onboarding wizard."""
193
- wizard = OnboardingWizard()
194
- wizard.run()
195
-
196
-
197
- def handle_simple_mode_configure() -> None:
198
- """Run the Simple Mode configuration wizard."""
199
- zero_config = ZeroConfigMode()
200
- zero_config.run_configuration_wizard()
201
-
202
-
203
- def handle_simple_mode_progress() -> None:
204
- """Show Simple Mode learning progression."""
205
- tracker = LearningProgressionTracker()
206
- tracker.show_progression_indicator()
207
-
208
-
209
- def handle_simple_mode_todo(args: object) -> None:
210
- """Handle simple-mode todo - forwards to bd. §3.5."""
211
- from ...beads import is_available, run_bd
212
-
213
- root = Path.cwd()
214
- if not is_available(root):
215
- get_feedback().error(
216
- "bd not found. Install to tools/bd or add bd to PATH. See docs/BEADS_INTEGRATION.md.",
217
- error_code="bd_not_found",
218
- exit_code=1,
219
- )
220
- return
221
- bd_args = list(getattr(args, "args", None) or [])
222
- r = run_bd(root, bd_args, capture_output=False)
223
- sys.exit(r.returncode if r.returncode is not None else 0)
224
-
225
-
226
- def handle_simple_mode_enhance(args: object) -> None:
227
- """Handle simple-mode enhance - @simple-mode *enhance. §3.4."""
228
- feedback = get_feedback()
229
- prompt = getattr(args, "prompt", None) or ""
230
- if not str(prompt).strip():
231
- feedback.error("Prompt required", error_code="prompt_required", remediation='Use: tapps-agents simple-mode enhance --prompt "your prompt"', exit_code=2)
232
- return
233
- feedback.start_operation("Enhance prompt")
234
- from ...simple_mode.intent_parser import Intent, IntentType
235
- from ...simple_mode.orchestrators.enhance_orchestrator import EnhanceOrchestrator
236
- from ...core.config import load_config
237
- import asyncio
238
-
239
- config = load_config()
240
- intent = Intent(type=IntentType.ENHANCE, confidence=1.0, parameters={"prompt": str(prompt).strip(), "quick": getattr(args, "quick", False)}, original_input=prompt)
241
- orch = EnhanceOrchestrator(project_root=Path.cwd(), config=config)
242
- try:
243
- r = asyncio.run(orch.execute(intent, intent.parameters))
244
- feedback.clear_progress()
245
- if r.get("success"):
246
- feedback.success("Enhancement complete")
247
- print(r.get("enhanced_prompt", str(r)))
248
- else:
249
- feedback.error(r.get("error", "Unknown error"), error_code="enhance_failed", exit_code=1)
250
- except Exception as e:
251
- feedback.error(str(e), error_code="enhance_error", exit_code=1)
252
-
253
-
254
- def handle_simple_mode_breakdown(args: object) -> None:
255
- """Handle simple-mode breakdown - @simple-mode *breakdown. §3.4."""
256
- feedback = get_feedback()
257
- prompt = getattr(args, "prompt", None) or ""
258
- if not str(prompt).strip():
259
- feedback.error("Prompt required", error_code="prompt_required", remediation='Use: tapps-agents simple-mode breakdown --prompt "your goal"', exit_code=2)
260
- return
261
- feedback.start_operation("Breakdown tasks")
262
- from ...simple_mode.intent_parser import Intent, IntentType
263
- from ...simple_mode.orchestrators.breakdown_orchestrator import BreakdownOrchestrator
264
- from ...core.config import load_config
265
- import asyncio
266
-
267
- config = load_config()
268
- intent = Intent(type=IntentType.BREAKDOWN, confidence=1.0, parameters={"prompt": str(prompt).strip()}, original_input=prompt)
269
- orch = BreakdownOrchestrator(project_root=Path.cwd(), config=config)
270
- try:
271
- r = asyncio.run(orch.execute(intent, intent.parameters))
272
- feedback.clear_progress()
273
- if r.get("success"):
274
- feedback.success("Breakdown complete")
275
- print(r.get("plan", r))
276
- else:
277
- feedback.error(r.get("error", "Unknown error"), error_code="breakdown_failed", exit_code=1)
278
- except Exception as e:
279
- feedback.error(str(e), error_code="breakdown_error", exit_code=1)
280
-
281
-
282
- def handle_simple_mode_resume(args: object) -> None:
283
- """Handle simple-mode resume command."""
284
- feedback = get_feedback()
285
- feedback.start_operation("Resuming Workflow")
286
-
287
- from ...simple_mode.orchestrators.resume_orchestrator import ResumeOrchestrator
288
- from ...simple_mode.intent_parser import IntentParser
289
-
290
- # Check for --list flag
291
- list_workflows = getattr(args, "list", False)
292
- if list_workflows:
293
- orchestrator = ResumeOrchestrator(project_root=Path.cwd())
294
- workflows = orchestrator.list_available_workflows()
295
-
296
- if not workflows:
297
- feedback.success("No workflows available to resume")
298
- print("\nNo workflows found with checkpoints.")
299
- return
300
-
301
- feedback.success(f"Found {len(workflows)} workflow(s) available to resume")
302
- print(f"\n{'='*60}")
303
- print("Available Workflows to Resume")
304
- print(f"{'='*60}\n")
305
-
306
- for wf in workflows:
307
- print(f"Workflow ID: {wf['workflow_id']}")
308
- print(f" Last Step: {wf['last_step']} (Step {wf['last_step_number']})")
309
- print(f" Completed: {wf['completed_at']}")
310
- print()
311
-
312
- return
313
-
314
- # Get workflow_id
315
- workflow_id = getattr(args, "workflow_id", None)
316
- if not workflow_id:
317
- feedback.error(
318
- "Workflow ID required",
319
- error_code="workflow_id_required",
320
- remediation="Provide workflow_id or use --list to see available workflows",
321
- exit_code=2,
322
- )
323
- return
324
-
325
- # Validate if requested
326
- validate = getattr(args, "validate", False)
327
- if validate:
328
- feedback.running("Validating workflow state...", step=1, total_steps=2)
329
-
330
- # Resume workflow
331
- feedback.running(f"Resuming workflow: {workflow_id}...", step=2, total_steps=2)
332
-
333
- try:
334
- orchestrator = ResumeOrchestrator(project_root=Path.cwd())
335
- parser = IntentParser()
336
- intent = parser.parse(f"resume {workflow_id}")
337
-
338
- import asyncio
339
-
340
- result = asyncio.run(orchestrator.execute(intent, {"workflow_id": workflow_id}))
341
-
342
- feedback.clear_progress()
343
- feedback.success(f"Workflow {workflow_id} ready to resume")
344
-
345
- print(f"\n{'='*60}")
346
- print(f"Resume Workflow: {workflow_id}")
347
- print(f"{'='*60}")
348
- print(f"Resume from: Step {result['resume_step']}")
349
- print(f"Completed steps: {', '.join(map(str, result['completed_steps']))}")
350
- print(f"Last checkpoint: {result['checkpoint']['step_name']}")
351
- print(f"\n{result['message']}")
352
- print("\nNote: Resume execution logic will be integrated with BuildOrchestrator")
353
-
354
- except Exception as e:
355
- feedback.clear_progress()
356
- feedback.error(
357
- f"Failed to resume workflow: {e}",
358
- error_code="resume_failed",
359
- context={"workflow_id": workflow_id},
360
- remediation="Check that workflow_id exists and checkpoints are valid",
361
- exit_code=1,
362
- )
363
-
364
-
365
- def handle_simple_mode_build(args: object) -> None:
366
- """Handle simple-mode build command - runs build workflow for new features."""
367
- feedback = get_feedback()
368
- feedback.start_operation("Starting Simple Mode Build Workflow")
369
-
370
- # Validate arguments before execution
371
- from ..validators.command_validator import CommandValidator
372
- from ..utils.error_formatter import ErrorFormatter
373
-
374
- validator = CommandValidator()
375
- validation_result = validator.validate_build_command(args)
376
-
377
- if not validation_result.valid:
378
- formatter = ErrorFormatter()
379
- error_msg = formatter.format_validation_error(validation_result)
380
- feedback.error(
381
- "Command validation failed",
382
- error_code="validation_error",
383
- context={"errors": validation_result.errors},
384
- remediation=validation_result.suggestions[0] if validation_result.suggestions else "Check command arguments",
385
- exit_code=2,
386
- )
387
- # Print detailed error message
388
- print("\n" + error_msg, file=sys.stderr)
389
- return
390
-
391
- # Get arguments (validated)
392
- user_prompt = getattr(args, "prompt", None)
393
- target_file = getattr(args, "file", None)
394
- fast_mode = getattr(args, "fast", False)
395
- preset_arg = getattr(args, "preset", None)
396
- auto_mode = getattr(args, "auto", False)
397
- no_auto_checkpoint = getattr(args, "no_auto_checkpoint", False)
398
- checkpoint_debug = getattr(args, "checkpoint_debug", False)
399
-
400
- # Auto-suggest preset from scope when neither --fast nor --preset set (SIMPLE_MODE_FEEDBACK_REVIEW)
401
- if not fast_mode and preset_arg is None and user_prompt:
402
- from ...simple_mode.workflow_suggester import WorkflowSuggester
403
- suggester = WorkflowSuggester()
404
- suggested = suggester.suggest_build_preset(user_prompt)
405
- if suggested == "minimal":
406
- fast_mode = True
407
- preset_arg = "minimal"
408
- else:
409
- preset_arg = suggested
410
- # Log without prompting user
411
- if preset_arg:
412
- feedback.info(f"Preset auto-selected from scope: {preset_arg}")
413
-
414
- # Explicit --preset overrides: minimal => fast_mode
415
- if preset_arg == "minimal" and not getattr(args, "fast", False):
416
- fast_mode = True
417
-
418
- print(f"\n{'='*60}")
419
- print("Simple Mode Build Workflow")
420
- print(f"{'='*60}")
421
- print(f"Feature: {user_prompt}")
422
- if fast_mode:
423
- print("Mode: Fast (skipping documentation steps 1-4)")
424
- else:
425
- print("Mode: Complete (all 7 steps)")
426
- print()
427
-
428
- # Load config
429
- from ...core.config import load_config
430
- from ...core.path_normalizer import normalize_for_cli, normalize_project_root
431
- from ...core.runtime_mode import detect_runtime_mode, is_cursor_mode
432
- from ...simple_mode.intent_parser import Intent, IntentParser, IntentType
433
- from ...simple_mode.orchestrators.build_orchestrator import BuildOrchestrator
434
-
435
- config = load_config()
436
- project_root = normalize_project_root(Path.cwd())
437
-
438
- # Normalize target file path if provided
439
- if target_file:
440
- try:
441
- target_file = normalize_for_cli(target_file, project_root)
442
- except Exception as e:
443
- feedback.warning(f"Path normalization warning: {e}. Using path as-is.")
444
- # Continue with original path - let workflow executor handle it
445
-
446
- # Create intent
447
- parser = IntentParser()
448
- intent = Intent(
449
- type=IntentType.BUILD,
450
- confidence=1.0,
451
- parameters={
452
- "description": user_prompt,
453
- "file": target_file,
454
- "no_auto_checkpoint": no_auto_checkpoint,
455
- "checkpoint_debug": checkpoint_debug,
456
- },
457
- original_input=user_prompt,
458
- )
459
-
460
- # Initialize orchestrator
461
- orchestrator = BuildOrchestrator(
462
- project_root=project_root,
463
- config=config,
464
- )
465
-
466
- # Check runtime mode
467
- runtime_mode = detect_runtime_mode()
468
- feedback.clear_progress()
469
-
470
- # Workflow preview (if not auto mode)
471
- if not auto_mode:
472
- print("\n" + "=" * 60)
473
- print("Workflow Preview")
474
- print("=" * 60)
475
- print(f"Feature: {user_prompt}")
476
- print(f"Mode: {'Fast' if fast_mode else 'Complete'} ({'4' if fast_mode else '7'} steps)")
477
- print("\nSteps to Execute:")
478
- if fast_mode:
479
- steps = [
480
- (5, "Implement code", "~5s"),
481
- (6, "Review code quality", "~2s"),
482
- (7, "Generate tests", "~2s"),
483
- ]
484
- else:
485
- steps = [
486
- (1, "Enhance prompt (requirements analysis)", "~2s"),
487
- (2, "Create user stories", "~2s"),
488
- (3, "Design architecture", "~3s"),
489
- (4, "Design API/data models", "~3s"),
490
- (5, "Implement code", "~5s"),
491
- (6, "Review code quality", "~2s"),
492
- (7, "Generate tests", "~2s"),
493
- ]
494
- for step_num, step_name, est_time in steps:
495
- print(f" {step_num}. {step_name:<45} {est_time}")
496
- total_est = sum(float(s[2].replace("~", "").replace("s", "")) for s in steps)
497
- print(f"\nEstimated Total Time: ~{total_est:.0f}s")
498
- print(f"Configuration: automation.level=2, fast_mode={str(fast_mode).lower()}")
499
- print("\n" + "=" * 60)
500
- sys.stdout.flush()
501
-
502
- print("\nExecuting build workflow...")
503
- print(f"Runtime mode: {runtime_mode.value}")
504
- print(f"Auto-execution: {'enabled' if auto_mode else 'disabled'}")
505
-
506
- from ...core.unicode_safe import safe_print
507
- if is_cursor_mode():
508
- safe_print("[OK] Running in Cursor mode - using direct execution\n")
509
- else:
510
- safe_print("[OK] Running in headless mode - direct execution with terminal output\n")
511
-
512
- sys.stdout.flush()
513
-
514
- # Initialize status reporter
515
- from ..utils.status_reporter import StatusReporter
516
- total_steps = 4 if fast_mode else 7
517
- status_reporter = StatusReporter(total_steps=total_steps)
518
-
519
- # Define callbacks for real-time status reporting
520
- def on_step_start(step_num: int, step_name: str) -> None:
521
- """Callback when a step starts."""
522
- status_reporter.start_step(step_num, step_name)
523
-
524
- def on_step_complete(step_num: int, step_name: str, status: str) -> None:
525
- """Callback when a step completes."""
526
- status_reporter.complete_step(step_num, step_name, status)
527
-
528
- def on_step_error(step_num: int, step_name: str, error: Exception) -> None:
529
- """Callback when a step errors."""
530
- status_reporter.complete_step(step_num, step_name, "failed")
531
-
532
- try:
533
- import asyncio
534
-
535
- result = asyncio.run(
536
- orchestrator.execute(
537
- intent=intent,
538
- parameters=intent.parameters,
539
- fast_mode=fast_mode,
540
- on_step_start=on_step_start,
541
- on_step_complete=on_step_complete,
542
- on_step_error=on_step_error,
543
- )
544
- )
545
-
546
- # Print execution summary
547
- status_reporter.print_summary()
548
-
549
- if result.get("success", False):
550
- feedback.success("Simple Mode Build Workflow completed successfully")
551
- print("\n✅ Build workflow completed successfully!")
552
- if "workflow_id" in result:
553
- print(f"\nWorkflow ID: {result['workflow_id']}")
554
- if "steps_executed" in result:
555
- print(f"Steps executed: {len(result['steps_executed'])}")
556
- else:
557
- feedback.error(
558
- "Build workflow execution failed",
559
- error_code="build_workflow_failed",
560
- context={"error": result.get("error", "Unknown error")},
561
- exit_code=1,
562
- )
563
- except Exception as e:
564
- feedback.error(
565
- "Build workflow execution error",
566
- error_code="build_workflow_error",
567
- context={"error": str(e)},
568
- exit_code=1,
569
- )
570
-
571
-
572
- def handle_simple_mode_full(args: object) -> None:
573
- """Handle simple-mode full command - runs the Full SDLC workflow (requirements -> security -> docs)."""
574
- feedback = get_feedback()
575
- feedback.start_operation("Starting Full SDLC Workflow")
576
-
577
- # Load the full SDLC workflow preset (matches docs: @simple-mode *full)
578
- loader = PresetLoader()
579
- workflow = loader.load_preset("full-sdlc")
580
-
581
- if not workflow:
582
- feedback.error(
583
- "Workflow not found",
584
- error_code="workflow_not_found",
585
- context={"preset": "full-sdlc"},
586
- remediation="Ensure full-sdlc.yaml exists in workflows/presets/",
587
- exit_code=1,
588
- )
589
- return
590
-
591
- print(f"\n{'='*60}")
592
- print(f"Starting: {workflow.name}")
593
- print(f"{'='*60}")
594
- print(f"Description: {workflow.description}")
595
- print(f"Steps: {len(workflow.steps)}")
596
- print("\nThis workflow includes:")
597
- print(" • Full SDLC lifecycle (requirements -> implementation -> testing)")
598
- print(" Automatic quality gates with scoring")
599
- print(" Development loopbacks if scores aren't good enough")
600
- print(" Test execution and validation")
601
- print(" • Security scanning")
602
- print(" • Documentation generation")
603
- print()
604
-
605
- # Get optional arguments
606
- target_file = getattr(args, "file", None)
607
- user_prompt = getattr(args, "prompt", None)
608
- auto_mode = getattr(args, "auto", False)
609
- no_auto_checkpoint = getattr(args, "no_auto_checkpoint", False)
610
- checkpoint_debug = getattr(args, "checkpoint_debug", False)
611
-
612
- # Execute with auto_mode
613
- executor = WorkflowExecutor(auto_detect=False, auto_mode=auto_mode)
614
-
615
- # Pass checkpoint flags to executor
616
- if no_auto_checkpoint or checkpoint_debug:
617
- executor.parameters = executor.parameters or {}
618
- if no_auto_checkpoint:
619
- executor.parameters["no_auto_checkpoint"] = True
620
- if checkpoint_debug:
621
- executor.parameters["checkpoint_debug"] = True
622
-
623
- if user_prompt:
624
- executor.user_prompt = user_prompt
625
-
626
- # Check runtime mode
627
- from ...core.runtime_mode import is_cursor_mode, detect_runtime_mode
628
- runtime_mode = detect_runtime_mode()
629
-
630
- # Stop spinner before async execution (spinner may interfere with async)
631
- feedback.clear_progress()
632
-
633
- print("Executing workflow steps...")
634
- print(f"Runtime mode: {runtime_mode.value}")
635
- print(f"Auto-execution: {'enabled' if auto_mode else 'disabled'}")
636
-
637
- from ...core.unicode_safe import safe_print
638
- if is_cursor_mode():
639
- safe_print("[OK] Running in Cursor mode - using direct execution and Cursor Skills\n")
640
- else:
641
- safe_print("[OK] Running in headless mode - direct execution with terminal output\n")
642
-
643
- sys.stdout.flush()
644
-
645
- try:
646
- import asyncio
647
- state = asyncio.run(executor.execute(workflow=workflow, target_file=target_file))
648
-
649
- if state.status == "completed":
650
- feedback.success("Full SDLC Workflow completed successfully")
651
- print("\n✅ Workflow completed successfully!")
652
- print(f"\nWorkflow ID: {state.workflow_id}")
653
- print(f"Steps completed: {len(state.completed_steps)}/{len(workflow.steps)}")
654
- else:
655
- feedback.error(
656
- "Workflow execution failed",
657
- error_code="workflow_execution_failed",
658
- context={"status": state.status, "error": state.error if hasattr(state, 'error') else "Unknown error"},
659
- exit_code=1,
660
- )
661
- except TimeoutError as e:
662
- feedback.error(
663
- "Workflow timeout",
664
- error_code="workflow_timeout",
665
- context={"error": str(e)},
666
- remediation="Increase timeout in config (workflow.timeout_seconds) or check for blocking operations",
667
- exit_code=1,
668
- )
669
- except Exception as e:
670
- feedback.error(
671
- "Workflow execution error",
672
- error_code="workflow_execution_error",
673
- context={"error": str(e)},
674
- exit_code=1,
675
- )
676
-
1
+ """
2
+ Simple Mode command handlers.
3
+ """
4
+
5
+ import sys
6
+ import yaml
7
+ from pathlib import Path
8
+
9
+ from ...core.config import ProjectConfig, load_config, save_config
10
+ from ...simple_mode.error_handling import SimpleModeErrorHandler
11
+ from ...simple_mode.learning_progression import LearningProgressionTracker
12
+ from ...simple_mode.onboarding import OnboardingWizard
13
+ from ...simple_mode.zero_config import ZeroConfigMode
14
+ from ...workflow.executor import WorkflowExecutor
15
+ from ...workflow.preset_loader import PresetLoader
16
+ from ..feedback import get_feedback
17
+
18
+
19
+ def handle_simple_mode_command(args: object) -> None:
20
+ """Handle simple-mode command."""
21
+ command = getattr(args, "command", None)
22
+
23
+ if command == "on":
24
+ handle_simple_mode_on()
25
+ elif command == "off":
26
+ handle_simple_mode_off()
27
+ elif command == "status":
28
+ handle_simple_mode_status(args)
29
+ elif command == "init":
30
+ handle_simple_mode_init()
31
+ elif command == "configure" or command == "config":
32
+ handle_simple_mode_configure()
33
+ elif command == "progress":
34
+ handle_simple_mode_progress()
35
+ elif command == "full":
36
+ handle_simple_mode_full(args)
37
+ elif command == "build":
38
+ handle_simple_mode_build(args)
39
+ elif command == "resume":
40
+ handle_simple_mode_resume(args)
41
+ elif command == "enhance":
42
+ handle_simple_mode_enhance(args)
43
+ elif command == "breakdown":
44
+ handle_simple_mode_breakdown(args)
45
+ elif command == "todo":
46
+ handle_simple_mode_todo(args)
47
+ elif command == "epic":
48
+ handle_simple_mode_epic(args)
49
+ elif command == "epic-status":
50
+ handle_simple_mode_epic_status(args)
51
+ else:
52
+ feedback = get_feedback()
53
+ available_commands = ["on", "off", "status", "init", "configure", "progress", "full", "build", "resume", "enhance", "breakdown", "todo", "epic", "epic-status"]
54
+ command_list = ", ".join(available_commands)
55
+ feedback.error(
56
+ f"Invalid simple-mode command: {command or '(none provided)'}",
57
+ error_code="invalid_command",
58
+ context={"command": command, "available_commands": available_commands},
59
+ remediation=f"Use one of: {command_list}",
60
+ exit_code=2,
61
+ )
62
+
63
+
64
+ def handle_simple_mode_on() -> None:
65
+ """Enable Simple Mode."""
66
+ feedback = get_feedback()
67
+ error_handler = SimpleModeErrorHandler()
68
+ feedback.start_operation("Enabling Simple Mode")
69
+
70
+ # Find config file
71
+ config_path = find_config_file()
72
+ if not config_path:
73
+ error_handler.handle_error(
74
+ "config_not_found",
75
+ error_message="No config file found",
76
+ context={"suggestion": "Run 'tapps-agents init' first"},
77
+ )
78
+ feedback.error(
79
+ "No config file found",
80
+ error_code="config_not_found",
81
+ remediation="Run 'tapps-agents init' first",
82
+ exit_code=2,
83
+ )
84
+
85
+ # Load config
86
+ feedback.running("Loading configuration...", step=2, total_steps=3)
87
+ config = load_config(config_path)
88
+ config.simple_mode.enabled = True
89
+
90
+ # Save config
91
+ feedback.running("Saving configuration...", step=3, total_steps=3)
92
+ save_config(config_path, config)
93
+
94
+ feedback.clear_progress()
95
+ feedback.success("Simple Mode enabled")
96
+ print("\nSimple Mode is now enabled.")
97
+ print("You can use natural language commands like:")
98
+ print(" • 'Build a user authentication feature'")
99
+ print(" • 'Review my authentication code'")
100
+ print(" • 'Fix the error in auth.py'")
101
+ print(" 'Add tests for service.py'")
102
+
103
+
104
+ def handle_simple_mode_off() -> None:
105
+ """Disable Simple Mode."""
106
+ feedback = get_feedback()
107
+ error_handler = SimpleModeErrorHandler()
108
+ feedback.start_operation("Disable Simple Mode", "Disabling Simple Mode in configuration")
109
+
110
+ # Find config file
111
+ feedback.running("Locating configuration file...", step=1, total_steps=3)
112
+ config_path = find_config_file()
113
+ if not config_path:
114
+ error_handler.handle_error(
115
+ "config_not_found",
116
+ error_message="No config file found",
117
+ context={"suggestion": "Run 'tapps-agents init' first"},
118
+ )
119
+ feedback.error(
120
+ "No config file found",
121
+ error_code="config_not_found",
122
+ remediation="Run 'tapps-agents init' first",
123
+ exit_code=2,
124
+ )
125
+
126
+ # Load config
127
+ config = load_config(config_path)
128
+ config.simple_mode.enabled = False
129
+
130
+ # Save config
131
+ save_config(config_path, config)
132
+
133
+ feedback.clear_progress()
134
+ feedback.success("Simple Mode disabled")
135
+ print("\nSimple Mode is now disabled.")
136
+ print("You can use agent-specific commands like:")
137
+ print(" • @reviewer *review")
138
+ print(" • @implementer *implement")
139
+ print(" @tester *test")
140
+
141
+
142
+ def handle_simple_mode_status(args: object) -> None:
143
+ """Check Simple Mode status."""
144
+ feedback = get_feedback()
145
+ output_format = getattr(args, "format", "text")
146
+ feedback.format_type = output_format
147
+
148
+ feedback.start_operation("Simple Mode Status", "Checking Simple Mode configuration status")
149
+ feedback.running("Loading configuration...", step=1, total_steps=2)
150
+
151
+ # Find config file
152
+ config_path = find_config_file()
153
+ if not config_path:
154
+ # No config file - use defaults
155
+ config = ProjectConfig()
156
+ else:
157
+ feedback.running("Loading configuration...", step=2, total_steps=2)
158
+ config = load_config(config_path)
159
+
160
+ feedback.clear_progress()
161
+
162
+ status = {
163
+ "enabled": config.simple_mode.enabled,
164
+ "auto_detect": config.simple_mode.auto_detect,
165
+ "show_advanced": config.simple_mode.show_advanced,
166
+ "natural_language": config.simple_mode.natural_language,
167
+ "config_file": str(config_path) if config_path else None,
168
+ }
169
+
170
+ if output_format == "json":
171
+ feedback.output_result(status, message="Simple Mode status retrieved")
172
+ else:
173
+ feedback.success("Simple Mode status retrieved")
174
+ print("\n" + "=" * 60)
175
+ print("Simple Mode Status")
176
+ print("=" * 60)
177
+ print(f"\nEnabled: {'Yes' if status['enabled'] else 'No'}")
178
+ print(f"Auto-detect: {'Yes' if status['auto_detect'] else 'No'}")
179
+ print(f"Show advanced: {'Yes' if status['show_advanced'] else 'No'}")
180
+ print(f"Natural language: {'Yes' if status['natural_language'] else 'No'}")
181
+ if status["config_file"]:
182
+ print(f"\nConfig file: {status['config_file']}")
183
+
184
+
185
+ def find_config_file() -> Path | None:
186
+ """Find .tapps-agents/config.yaml in current or parent directories."""
187
+ current = Path.cwd()
188
+ for parent in [current] + list(current.parents):
189
+ candidate = parent / ".tapps-agents" / "config.yaml"
190
+ if candidate.exists():
191
+ return candidate
192
+ return None
193
+
194
+
195
+ def handle_simple_mode_init() -> None:
196
+ """Run the Simple Mode onboarding wizard."""
197
+ wizard = OnboardingWizard()
198
+ wizard.run()
199
+
200
+
201
+ def handle_simple_mode_configure() -> None:
202
+ """Run the Simple Mode configuration wizard."""
203
+ zero_config = ZeroConfigMode()
204
+ zero_config.run_configuration_wizard()
205
+
206
+
207
+ def handle_simple_mode_progress() -> None:
208
+ """Show Simple Mode learning progression."""
209
+ tracker = LearningProgressionTracker()
210
+ tracker.show_progression_indicator()
211
+
212
+
213
+ def handle_simple_mode_todo(args: object) -> None:
214
+ """Handle simple-mode todo - forwards to bd. §3.5."""
215
+ from ...beads import is_available, run_bd
216
+
217
+ root = Path.cwd()
218
+ if not is_available(root):
219
+ get_feedback().error(
220
+ "bd not found. Install to tools/bd or add bd to PATH. See docs/BEADS_INTEGRATION.md.",
221
+ error_code="bd_not_found",
222
+ exit_code=1,
223
+ )
224
+ return
225
+ bd_args = list(getattr(args, "args", None) or [])
226
+ r = run_bd(root, bd_args, capture_output=False)
227
+ sys.exit(r.returncode if r.returncode is not None else 0)
228
+
229
+
230
+ def handle_simple_mode_enhance(args: object) -> None:
231
+ """Handle simple-mode enhance - @simple-mode *enhance. §3.4."""
232
+ feedback = get_feedback()
233
+ prompt = getattr(args, "prompt", None) or ""
234
+ if not str(prompt).strip():
235
+ feedback.error("Prompt required", error_code="prompt_required", remediation='Use: tapps-agents simple-mode enhance --prompt "your prompt"', exit_code=2)
236
+ return
237
+ feedback.start_operation("Enhance prompt")
238
+ from ...simple_mode.intent_parser import Intent, IntentType
239
+ from ...simple_mode.orchestrators.enhance_orchestrator import EnhanceOrchestrator
240
+ from ...core.config import load_config
241
+ import asyncio
242
+
243
+ config = load_config()
244
+ intent = Intent(type=IntentType.ENHANCE, confidence=1.0, parameters={"prompt": str(prompt).strip(), "quick": getattr(args, "quick", False)}, original_input=prompt)
245
+ orch = EnhanceOrchestrator(project_root=Path.cwd(), config=config)
246
+ try:
247
+ r = asyncio.run(orch.execute(intent, intent.parameters))
248
+ feedback.clear_progress()
249
+ if r.get("success"):
250
+ feedback.success("Enhancement complete")
251
+ print(r.get("enhanced_prompt", str(r)))
252
+ else:
253
+ feedback.error(r.get("error", "Unknown error"), error_code="enhance_failed", exit_code=1)
254
+ except Exception as e:
255
+ feedback.error(str(e), error_code="enhance_error", exit_code=1)
256
+
257
+
258
+ def handle_simple_mode_breakdown(args: object) -> None:
259
+ """Handle simple-mode breakdown - @simple-mode *breakdown. §3.4."""
260
+ feedback = get_feedback()
261
+ prompt = getattr(args, "prompt", None) or ""
262
+ if not str(prompt).strip():
263
+ feedback.error("Prompt required", error_code="prompt_required", remediation='Use: tapps-agents simple-mode breakdown --prompt "your goal"', exit_code=2)
264
+ return
265
+ feedback.start_operation("Breakdown tasks")
266
+ from ...simple_mode.intent_parser import Intent, IntentType
267
+ from ...simple_mode.orchestrators.breakdown_orchestrator import BreakdownOrchestrator
268
+ from ...core.config import load_config
269
+ import asyncio
270
+
271
+ config = load_config()
272
+ intent = Intent(type=IntentType.BREAKDOWN, confidence=1.0, parameters={"prompt": str(prompt).strip()}, original_input=prompt)
273
+ orch = BreakdownOrchestrator(project_root=Path.cwd(), config=config)
274
+ try:
275
+ r = asyncio.run(orch.execute(intent, intent.parameters))
276
+ feedback.clear_progress()
277
+ if r.get("success"):
278
+ feedback.success("Breakdown complete")
279
+ print(r.get("plan", r))
280
+ else:
281
+ feedback.error(r.get("error", "Unknown error"), error_code="breakdown_failed", exit_code=1)
282
+ except Exception as e:
283
+ feedback.error(str(e), error_code="breakdown_error", exit_code=1)
284
+
285
+
286
+ def handle_simple_mode_resume(args: object) -> None:
287
+ """Handle simple-mode resume command."""
288
+ feedback = get_feedback()
289
+ feedback.start_operation("Resuming Workflow")
290
+
291
+ from ...simple_mode.orchestrators.resume_orchestrator import ResumeOrchestrator
292
+ from ...simple_mode.intent_parser import IntentParser
293
+
294
+ # Check for --list flag
295
+ list_workflows = getattr(args, "list", False)
296
+ if list_workflows:
297
+ orchestrator = ResumeOrchestrator(project_root=Path.cwd())
298
+ workflows = orchestrator.list_available_workflows()
299
+
300
+ if not workflows:
301
+ feedback.success("No workflows available to resume")
302
+ print("\nNo workflows found with checkpoints.")
303
+ return
304
+
305
+ feedback.success(f"Found {len(workflows)} workflow(s) available to resume")
306
+ print(f"\n{'='*60}")
307
+ print("Available Workflows to Resume")
308
+ print(f"{'='*60}\n")
309
+
310
+ for wf in workflows:
311
+ print(f"Workflow ID: {wf['workflow_id']}")
312
+ print(f" Last Step: {wf['last_step']} (Step {wf['last_step_number']})")
313
+ print(f" Completed: {wf['completed_at']}")
314
+ print()
315
+
316
+ return
317
+
318
+ # Get workflow_id
319
+ workflow_id = getattr(args, "workflow_id", None)
320
+ if not workflow_id:
321
+ feedback.error(
322
+ "Workflow ID required",
323
+ error_code="workflow_id_required",
324
+ remediation="Provide workflow_id or use --list to see available workflows",
325
+ exit_code=2,
326
+ )
327
+ return
328
+
329
+ # Validate if requested
330
+ validate = getattr(args, "validate", False)
331
+ if validate:
332
+ feedback.running("Validating workflow state...", step=1, total_steps=2)
333
+
334
+ # Resume workflow
335
+ feedback.running(f"Resuming workflow: {workflow_id}...", step=2, total_steps=2)
336
+
337
+ try:
338
+ orchestrator = ResumeOrchestrator(project_root=Path.cwd())
339
+ parser = IntentParser()
340
+ intent = parser.parse(f"resume {workflow_id}")
341
+
342
+ import asyncio
343
+
344
+ result = asyncio.run(orchestrator.execute(intent, {"workflow_id": workflow_id}))
345
+
346
+ feedback.clear_progress()
347
+ feedback.success(f"Workflow {workflow_id} ready to resume")
348
+
349
+ print(f"\n{'='*60}")
350
+ print(f"Resume Workflow: {workflow_id}")
351
+ print(f"{'='*60}")
352
+ print(f"Resume from: Step {result['resume_step']}")
353
+ print(f"Completed steps: {', '.join(map(str, result['completed_steps']))}")
354
+ print(f"Last checkpoint: {result['checkpoint']['step_name']}")
355
+ print(f"\n{result['message']}")
356
+ print("\nNote: Resume execution logic will be integrated with BuildOrchestrator")
357
+
358
+ except Exception as e:
359
+ feedback.clear_progress()
360
+ feedback.error(
361
+ f"Failed to resume workflow: {e}",
362
+ error_code="resume_failed",
363
+ context={"workflow_id": workflow_id},
364
+ remediation="Check that workflow_id exists and checkpoints are valid",
365
+ exit_code=1,
366
+ )
367
+
368
+
369
+ def handle_simple_mode_build(args: object) -> None:
370
+ """Handle simple-mode build command - runs build workflow for new features."""
371
+ feedback = get_feedback()
372
+ feedback.start_operation("Starting Simple Mode Build Workflow")
373
+
374
+ # Validate arguments before execution
375
+ from ..validators.command_validator import CommandValidator
376
+ from ..utils.error_formatter import ErrorFormatter
377
+
378
+ validator = CommandValidator()
379
+ validation_result = validator.validate_build_command(args)
380
+
381
+ if not validation_result.valid:
382
+ formatter = ErrorFormatter()
383
+ error_msg = formatter.format_validation_error(validation_result)
384
+ feedback.error(
385
+ "Command validation failed",
386
+ error_code="validation_error",
387
+ context={"errors": validation_result.errors},
388
+ remediation=validation_result.suggestions[0] if validation_result.suggestions else "Check command arguments",
389
+ exit_code=2,
390
+ )
391
+ # Print detailed error message
392
+ print("\n" + error_msg, file=sys.stderr)
393
+ return
394
+
395
+ # Get arguments (validated)
396
+ user_prompt = getattr(args, "prompt", None)
397
+ target_file = getattr(args, "file", None)
398
+ fast_mode = getattr(args, "fast", False)
399
+ preset_arg = getattr(args, "preset", None)
400
+ auto_mode = getattr(args, "auto", False)
401
+ no_auto_checkpoint = getattr(args, "no_auto_checkpoint", False)
402
+ checkpoint_debug = getattr(args, "checkpoint_debug", False)
403
+
404
+ # Auto-suggest preset from scope when neither --fast nor --preset set (SIMPLE_MODE_FEEDBACK_REVIEW)
405
+ if not fast_mode and preset_arg is None and user_prompt:
406
+ from ...simple_mode.workflow_suggester import WorkflowSuggester
407
+ suggester = WorkflowSuggester()
408
+ suggested = suggester.suggest_build_preset(user_prompt)
409
+ if suggested == "minimal":
410
+ fast_mode = True
411
+ preset_arg = "minimal"
412
+ else:
413
+ preset_arg = suggested
414
+ # Log without prompting user
415
+ if preset_arg:
416
+ feedback.info(f"Preset auto-selected from scope: {preset_arg}")
417
+
418
+ # Explicit --preset overrides: minimal => fast_mode
419
+ if preset_arg == "minimal" and not getattr(args, "fast", False):
420
+ fast_mode = True
421
+
422
+ print(f"\n{'='*60}")
423
+ print("Simple Mode Build Workflow")
424
+ print(f"{'='*60}")
425
+ print(f"Feature: {user_prompt}")
426
+ if fast_mode:
427
+ print("Mode: Fast (skipping documentation steps 1-4)")
428
+ else:
429
+ print("Mode: Complete (all 7 steps)")
430
+ print()
431
+
432
+ # Load config
433
+ from ...core.config import load_config
434
+ from ...core.path_normalizer import normalize_for_cli, normalize_project_root
435
+ from ...core.runtime_mode import detect_runtime_mode, is_cursor_mode
436
+ from ...simple_mode.intent_parser import Intent, IntentParser, IntentType
437
+ from ...simple_mode.orchestrators.build_orchestrator import BuildOrchestrator
438
+
439
+ config = load_config()
440
+ project_root = normalize_project_root(Path.cwd())
441
+
442
+ # Normalize target file path if provided
443
+ if target_file:
444
+ try:
445
+ target_file = normalize_for_cli(target_file, project_root)
446
+ except Exception as e:
447
+ feedback.warning(f"Path normalization warning: {e}. Using path as-is.")
448
+ # Continue with original path - let workflow executor handle it
449
+
450
+ # Create intent
451
+ parser = IntentParser()
452
+ intent = Intent(
453
+ type=IntentType.BUILD,
454
+ confidence=1.0,
455
+ parameters={
456
+ "description": user_prompt,
457
+ "file": target_file,
458
+ "no_auto_checkpoint": no_auto_checkpoint,
459
+ "checkpoint_debug": checkpoint_debug,
460
+ },
461
+ original_input=user_prompt,
462
+ )
463
+
464
+ # Initialize orchestrator
465
+ orchestrator = BuildOrchestrator(
466
+ project_root=project_root,
467
+ config=config,
468
+ )
469
+
470
+ # Check runtime mode
471
+ runtime_mode = detect_runtime_mode()
472
+ feedback.clear_progress()
473
+
474
+ # Workflow preview (if not auto mode)
475
+ if not auto_mode:
476
+ print("\n" + "=" * 60)
477
+ print("Workflow Preview")
478
+ print("=" * 60)
479
+ print(f"Feature: {user_prompt}")
480
+ print(f"Mode: {'Fast' if fast_mode else 'Complete'} ({'4' if fast_mode else '7'} steps)")
481
+ print("\nSteps to Execute:")
482
+ if fast_mode:
483
+ steps = [
484
+ (5, "Implement code", "~5s"),
485
+ (6, "Review code quality", "~2s"),
486
+ (7, "Generate tests", "~2s"),
487
+ ]
488
+ else:
489
+ steps = [
490
+ (1, "Enhance prompt (requirements analysis)", "~2s"),
491
+ (2, "Create user stories", "~2s"),
492
+ (3, "Design architecture", "~3s"),
493
+ (4, "Design API/data models", "~3s"),
494
+ (5, "Implement code", "~5s"),
495
+ (6, "Review code quality", "~2s"),
496
+ (7, "Generate tests", "~2s"),
497
+ ]
498
+ for step_num, step_name, est_time in steps:
499
+ print(f" {step_num}. {step_name:<45} {est_time}")
500
+ total_est = sum(float(s[2].replace("~", "").replace("s", "")) for s in steps)
501
+ print(f"\nEstimated Total Time: ~{total_est:.0f}s")
502
+ print(f"Configuration: automation.level=2, fast_mode={str(fast_mode).lower()}")
503
+ print("\n" + "=" * 60)
504
+ sys.stdout.flush()
505
+
506
+ print("\nExecuting build workflow...")
507
+ print(f"Runtime mode: {runtime_mode.value}")
508
+ print(f"Auto-execution: {'enabled' if auto_mode else 'disabled'}")
509
+
510
+ from ...core.unicode_safe import safe_print
511
+ if is_cursor_mode():
512
+ safe_print("[OK] Running in Cursor mode - using direct execution\n")
513
+ else:
514
+ safe_print("[OK] Running in headless mode - direct execution with terminal output\n")
515
+
516
+ sys.stdout.flush()
517
+
518
+ # Initialize status reporter
519
+ from ..utils.status_reporter import StatusReporter
520
+ total_steps = 4 if fast_mode else 7
521
+ status_reporter = StatusReporter(total_steps=total_steps)
522
+
523
+ # Define callbacks for real-time status reporting
524
+ def on_step_start(step_num: int, step_name: str) -> None:
525
+ """Callback when a step starts."""
526
+ status_reporter.start_step(step_num, step_name)
527
+
528
+ def on_step_complete(step_num: int, step_name: str, status: str) -> None:
529
+ """Callback when a step completes."""
530
+ status_reporter.complete_step(step_num, step_name, status)
531
+
532
+ def on_step_error(step_num: int, step_name: str, error: Exception) -> None:
533
+ """Callback when a step errors."""
534
+ status_reporter.complete_step(step_num, step_name, "failed")
535
+
536
+ try:
537
+ import asyncio
538
+
539
+ result = asyncio.run(
540
+ orchestrator.execute(
541
+ intent=intent,
542
+ parameters=intent.parameters,
543
+ fast_mode=fast_mode,
544
+ on_step_start=on_step_start,
545
+ on_step_complete=on_step_complete,
546
+ on_step_error=on_step_error,
547
+ )
548
+ )
549
+
550
+ # Print execution summary
551
+ status_reporter.print_summary()
552
+
553
+ if result.get("success", False):
554
+ feedback.success("Simple Mode Build Workflow completed successfully")
555
+ print("\n✅ Build workflow completed successfully!")
556
+ if "workflow_id" in result:
557
+ print(f"\nWorkflow ID: {result['workflow_id']}")
558
+ if "steps_executed" in result:
559
+ print(f"Steps executed: {len(result['steps_executed'])}")
560
+ else:
561
+ feedback.error(
562
+ "Build workflow execution failed",
563
+ error_code="build_workflow_failed",
564
+ context={"error": result.get("error", "Unknown error")},
565
+ exit_code=1,
566
+ )
567
+ except Exception as e:
568
+ feedback.error(
569
+ "Build workflow execution error",
570
+ error_code="build_workflow_error",
571
+ context={"error": str(e)},
572
+ exit_code=1,
573
+ )
574
+
575
+
576
+ def handle_simple_mode_full(args: object) -> None:
577
+ """Handle simple-mode full command - runs the Full SDLC workflow (requirements -> security -> docs)."""
578
+ feedback = get_feedback()
579
+ feedback.start_operation("Starting Full SDLC Workflow")
580
+
581
+ # Load the full SDLC workflow preset (matches docs: @simple-mode *full)
582
+ loader = PresetLoader()
583
+ workflow = loader.load_preset("full-sdlc")
584
+
585
+ if not workflow:
586
+ feedback.error(
587
+ "Workflow not found",
588
+ error_code="workflow_not_found",
589
+ context={"preset": "full-sdlc"},
590
+ remediation="Ensure full-sdlc.yaml exists in workflows/presets/",
591
+ exit_code=1,
592
+ )
593
+ return
594
+
595
+ print(f"\n{'='*60}")
596
+ print(f"Starting: {workflow.name}")
597
+ print(f"{'='*60}")
598
+ print(f"Description: {workflow.description}")
599
+ print(f"Steps: {len(workflow.steps)}")
600
+ print("\nThis workflow includes:")
601
+ print(" • Full SDLC lifecycle (requirements -> implementation -> testing)")
602
+ print(" • Automatic quality gates with scoring")
603
+ print(" • Development loopbacks if scores aren't good enough")
604
+ print(" • Test execution and validation")
605
+ print(" • Security scanning")
606
+ print(" • Documentation generation")
607
+ print()
608
+
609
+ # Get optional arguments
610
+ target_file = getattr(args, "file", None)
611
+ user_prompt = getattr(args, "prompt", None)
612
+ auto_mode = getattr(args, "auto", False)
613
+ no_auto_checkpoint = getattr(args, "no_auto_checkpoint", False)
614
+ checkpoint_debug = getattr(args, "checkpoint_debug", False)
615
+
616
+ # Execute with auto_mode
617
+ executor = WorkflowExecutor(auto_detect=False, auto_mode=auto_mode)
618
+
619
+ # Pass checkpoint flags to executor
620
+ if no_auto_checkpoint or checkpoint_debug:
621
+ executor.parameters = executor.parameters or {}
622
+ if no_auto_checkpoint:
623
+ executor.parameters["no_auto_checkpoint"] = True
624
+ if checkpoint_debug:
625
+ executor.parameters["checkpoint_debug"] = True
626
+
627
+ if user_prompt:
628
+ executor.user_prompt = user_prompt
629
+
630
+ # Check runtime mode
631
+ from ...core.runtime_mode import is_cursor_mode, detect_runtime_mode
632
+ runtime_mode = detect_runtime_mode()
633
+
634
+ # Stop spinner before async execution (spinner may interfere with async)
635
+ feedback.clear_progress()
636
+
637
+ print("Executing workflow steps...")
638
+ print(f"Runtime mode: {runtime_mode.value}")
639
+ print(f"Auto-execution: {'enabled' if auto_mode else 'disabled'}")
640
+
641
+ from ...core.unicode_safe import safe_print
642
+ if is_cursor_mode():
643
+ safe_print("[OK] Running in Cursor mode - using direct execution and Cursor Skills\n")
644
+ else:
645
+ safe_print("[OK] Running in headless mode - direct execution with terminal output\n")
646
+
647
+ sys.stdout.flush()
648
+
649
+ try:
650
+ import asyncio
651
+ state = asyncio.run(executor.execute(workflow=workflow, target_file=target_file))
652
+
653
+ if state.status == "completed":
654
+ feedback.success("Full SDLC Workflow completed successfully")
655
+ print("\n✅ Workflow completed successfully!")
656
+ print(f"\nWorkflow ID: {state.workflow_id}")
657
+ print(f"Steps completed: {len(state.completed_steps)}/{len(workflow.steps)}")
658
+ else:
659
+ feedback.error(
660
+ "Workflow execution failed",
661
+ error_code="workflow_execution_failed",
662
+ context={"status": state.status, "error": state.error if hasattr(state, 'error') else "Unknown error"},
663
+ exit_code=1,
664
+ )
665
+ except TimeoutError as e:
666
+ feedback.error(
667
+ "Workflow timeout",
668
+ error_code="workflow_timeout",
669
+ context={"error": str(e)},
670
+ remediation="Increase timeout in config (workflow.timeout_seconds) or check for blocking operations",
671
+ exit_code=1,
672
+ )
673
+ except Exception as e:
674
+ feedback.error(
675
+ "Workflow execution error",
676
+ error_code="workflow_execution_error",
677
+ context={"error": str(e)},
678
+ exit_code=1,
679
+ )
680
+
681
+
682
+ def handle_simple_mode_epic(args: object) -> None:
683
+ """Handle simple-mode epic command - execute Epic document (all stories in dependency order)."""
684
+ feedback = get_feedback()
685
+ epic_path = getattr(args, "epic_path", None)
686
+
687
+ if not epic_path:
688
+ feedback.error(
689
+ "Epic path required",
690
+ error_code="epic_path_required",
691
+ remediation="Use: tapps-agents simple-mode epic <path> (e.g. stories/enh-002-critical-enhancements.md)",
692
+ exit_code=2,
693
+ )
694
+ return
695
+
696
+ feedback.start_operation("Executing Epic")
697
+ project_root = Path.cwd()
698
+ epic_path_resolved = Path(epic_path)
699
+ if not epic_path_resolved.is_absolute():
700
+ if (project_root / epic_path_resolved).exists():
701
+ epic_path_resolved = project_root / epic_path_resolved
702
+ elif (project_root / "docs" / "prd" / epic_path_resolved.name).exists():
703
+ epic_path_resolved = project_root / "docs" / "prd" / epic_path_resolved.name
704
+ else:
705
+ epic_path_resolved = project_root / epic_path_resolved
706
+
707
+ if not epic_path_resolved.exists():
708
+ feedback.error(
709
+ "Epic document not found",
710
+ error_code="epic_not_found",
711
+ context={"path": str(epic_path_resolved)},
712
+ remediation="Provide a valid path to an Epic markdown file",
713
+ exit_code=2,
714
+ )
715
+ return
716
+
717
+ quality_threshold = getattr(args, "quality_threshold", 70.0)
718
+ critical_threshold = getattr(args, "critical_service_threshold", 80.0)
719
+ auto_mode = getattr(args, "auto", False)
720
+ enforce_quality_gates = not getattr(args, "no_quality_gates", False)
721
+
722
+ from ...core.config import load_config
723
+ from ...simple_mode.intent_parser import Intent, IntentType
724
+ from ...simple_mode.orchestrators.epic_orchestrator import EpicOrchestrator
725
+
726
+ config = load_config()
727
+ orchestrator = EpicOrchestrator(
728
+ project_root=project_root,
729
+ config=config,
730
+ )
731
+
732
+ intent = Intent(
733
+ type=IntentType.EPIC,
734
+ confidence=1.0,
735
+ parameters={
736
+ "epic_path": str(epic_path_resolved),
737
+ "quality_threshold": quality_threshold,
738
+ "critical_service_threshold": critical_threshold,
739
+ "auto_mode": auto_mode,
740
+ "enforce_quality_gates": enforce_quality_gates,
741
+ },
742
+ original_input=str(epic_path_resolved),
743
+ )
744
+
745
+ feedback.clear_progress()
746
+ print(f"\n{'='*60}")
747
+ print("Epic Execution")
748
+ print(f"{'='*60}")
749
+ print(f"Epic: {epic_path_resolved}")
750
+ print(f"Quality threshold: {quality_threshold}")
751
+ print(f"Auto mode: {auto_mode}")
752
+ print()
753
+
754
+ try:
755
+ import asyncio
756
+ result = asyncio.run(orchestrator.execute(intent, intent.parameters))
757
+
758
+ if result.get("success", False):
759
+ feedback.success("Epic execution completed")
760
+ print("\n[OK] Epic execution completed!")
761
+ print(f" Epic: {result.get('epic_number')} - {result.get('epic_title', '')}")
762
+ print(f" Completion: {result.get('completion_percentage', 0):.1f}%")
763
+ print(f" Done: {result.get('done_stories', 0)}/{result.get('total_stories', 0)} stories")
764
+ if result.get("failed_stories", 0) > 0:
765
+ print(f" Failed: {result.get('failed_stories', 0)} stories")
766
+ report_path = result.get("report_path")
767
+ if report_path:
768
+ print(f"\nReport saved to: {report_path}")
769
+ else:
770
+ feedback.error(
771
+ "Epic execution failed",
772
+ error_code="epic_execution_failed",
773
+ context={"error": result.get("error", "Unknown error")},
774
+ exit_code=1,
775
+ )
776
+ except Exception as e:
777
+ feedback.error(
778
+ "Epic execution error",
779
+ error_code="epic_execution_error",
780
+ context={"error": str(e)},
781
+ exit_code=1,
782
+ )
783
+
784
+
785
+ def handle_simple_mode_epic_status(args: object) -> None:
786
+ """Update epic markdown with execution status from report JSON."""
787
+ feedback = get_feedback()
788
+ epic_path = getattr(args, "epic_path", None)
789
+ report_path = getattr(args, "report", None)
790
+
791
+ if not epic_path:
792
+ feedback.error(
793
+ "Epic path required",
794
+ error_code="epic_path_required",
795
+ remediation="Use: tapps-agents simple-mode epic-status <path> (e.g. stories/enh-002-critical-enhancements.md)",
796
+ exit_code=2,
797
+ )
798
+ return
799
+
800
+ project_root = Path.cwd()
801
+ epic_path_resolved = Path(epic_path)
802
+ if not epic_path_resolved.is_absolute():
803
+ if (project_root / epic_path_resolved).exists():
804
+ epic_path_resolved = project_root / epic_path_resolved
805
+ elif (project_root / "docs" / "prd" / epic_path_resolved.name).exists():
806
+ epic_path_resolved = project_root / "docs" / "prd" / epic_path_resolved.name
807
+ else:
808
+ epic_path_resolved = project_root / epic_path_resolved
809
+
810
+ try:
811
+ from ...epic.markdown_sync import update_epic_markdown_from_report
812
+
813
+ update_epic_markdown_from_report(
814
+ epic_path_resolved,
815
+ report_path=report_path,
816
+ project_root=project_root,
817
+ )
818
+ feedback.success("Epic markdown updated with execution status")
819
+ print(f"\n[OK] Updated {epic_path_resolved} with status from report.")
820
+ except FileNotFoundError as e:
821
+ feedback.error(
822
+ "File not found",
823
+ error_code="epic_status_file_not_found",
824
+ context={"error": str(e)},
825
+ remediation="Run 'simple-mode epic <path>' first to generate the report, or pass --report <path>.",
826
+ exit_code=2,
827
+ )
828
+ except ValueError as e:
829
+ feedback.error(
830
+ "Invalid epic or report",
831
+ error_code="epic_status_invalid",
832
+ context={"error": str(e)},
833
+ exit_code=2,
834
+ )
835
+ except Exception as e:
836
+ feedback.error(
837
+ "Epic status update failed",
838
+ error_code="epic_status_update_failed",
839
+ context={"error": str(e)},
840
+ exit_code=1,
841
+ )
842
+