devsquad 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.
Files changed (95) hide show
  1. devsquad-3.6.0.dist-info/METADATA +944 -0
  2. devsquad-3.6.0.dist-info/RECORD +95 -0
  3. devsquad-3.6.0.dist-info/WHEEL +5 -0
  4. devsquad-3.6.0.dist-info/entry_points.txt +2 -0
  5. devsquad-3.6.0.dist-info/licenses/LICENSE +21 -0
  6. devsquad-3.6.0.dist-info/top_level.txt +2 -0
  7. scripts/__init__.py +0 -0
  8. scripts/ai_semantic_matcher.py +512 -0
  9. scripts/alert_manager.py +505 -0
  10. scripts/api/__init__.py +43 -0
  11. scripts/api/models.py +386 -0
  12. scripts/api/routes/__init__.py +20 -0
  13. scripts/api/routes/dispatch.py +348 -0
  14. scripts/api/routes/lifecycle.py +330 -0
  15. scripts/api/routes/metrics_gates.py +347 -0
  16. scripts/api_server.py +318 -0
  17. scripts/auth.py +451 -0
  18. scripts/cli/__init__.py +1 -0
  19. scripts/cli/cli_visual.py +642 -0
  20. scripts/cli.py +1094 -0
  21. scripts/collaboration/__init__.py +212 -0
  22. scripts/collaboration/_version.py +1 -0
  23. scripts/collaboration/agent_briefing.py +656 -0
  24. scripts/collaboration/ai_semantic_matcher.py +260 -0
  25. scripts/collaboration/anchor_checker.py +281 -0
  26. scripts/collaboration/anti_rationalization.py +470 -0
  27. scripts/collaboration/async_integration_example.py +255 -0
  28. scripts/collaboration/batch_scheduler.py +149 -0
  29. scripts/collaboration/checkpoint_manager.py +561 -0
  30. scripts/collaboration/ci_feedback_adapter.py +351 -0
  31. scripts/collaboration/code_map_generator.py +247 -0
  32. scripts/collaboration/concern_pack_loader.py +352 -0
  33. scripts/collaboration/confidence_score.py +496 -0
  34. scripts/collaboration/config_loader.py +188 -0
  35. scripts/collaboration/consensus.py +244 -0
  36. scripts/collaboration/context_compressor.py +533 -0
  37. scripts/collaboration/coordinator.py +668 -0
  38. scripts/collaboration/dispatcher.py +1636 -0
  39. scripts/collaboration/dual_layer_context.py +128 -0
  40. scripts/collaboration/enhanced_worker.py +539 -0
  41. scripts/collaboration/feature_usage_tracker.py +206 -0
  42. scripts/collaboration/five_axis_consensus.py +334 -0
  43. scripts/collaboration/input_validator.py +401 -0
  44. scripts/collaboration/integration_example.py +287 -0
  45. scripts/collaboration/intent_workflow_mapper.py +350 -0
  46. scripts/collaboration/language_parsers.py +269 -0
  47. scripts/collaboration/lifecycle_protocol.py +1446 -0
  48. scripts/collaboration/llm_backend.py +453 -0
  49. scripts/collaboration/llm_cache.py +448 -0
  50. scripts/collaboration/llm_cache_async.py +347 -0
  51. scripts/collaboration/llm_retry.py +387 -0
  52. scripts/collaboration/llm_retry_async.py +389 -0
  53. scripts/collaboration/mce_adapter.py +597 -0
  54. scripts/collaboration/memory_bridge.py +1607 -0
  55. scripts/collaboration/models.py +537 -0
  56. scripts/collaboration/null_providers.py +297 -0
  57. scripts/collaboration/operation_classifier.py +289 -0
  58. scripts/collaboration/output_slicer.py +225 -0
  59. scripts/collaboration/performance_monitor.py +462 -0
  60. scripts/collaboration/permission_guard.py +865 -0
  61. scripts/collaboration/prompt_assembler.py +756 -0
  62. scripts/collaboration/prompt_variant_generator.py +483 -0
  63. scripts/collaboration/protocols.py +267 -0
  64. scripts/collaboration/report_formatter.py +352 -0
  65. scripts/collaboration/retrospective.py +279 -0
  66. scripts/collaboration/role_matcher.py +92 -0
  67. scripts/collaboration/role_template_market.py +352 -0
  68. scripts/collaboration/rule_collector.py +678 -0
  69. scripts/collaboration/scratchpad.py +346 -0
  70. scripts/collaboration/skill_registry.py +151 -0
  71. scripts/collaboration/skillifier.py +878 -0
  72. scripts/collaboration/standardized_role_template.py +317 -0
  73. scripts/collaboration/task_completion_checker.py +237 -0
  74. scripts/collaboration/test_quality_guard.py +695 -0
  75. scripts/collaboration/unified_gate_engine.py +598 -0
  76. scripts/collaboration/usage_tracker.py +309 -0
  77. scripts/collaboration/user_friendly_error.py +176 -0
  78. scripts/collaboration/verification_gate.py +312 -0
  79. scripts/collaboration/warmup_manager.py +635 -0
  80. scripts/collaboration/worker.py +513 -0
  81. scripts/collaboration/workflow_engine.py +684 -0
  82. scripts/dashboard.py +1088 -0
  83. scripts/generate_benchmark_report.py +786 -0
  84. scripts/history_manager.py +604 -0
  85. scripts/mcp_server.py +289 -0
  86. skills/__init__.py +32 -0
  87. skills/dispatch/handler.py +52 -0
  88. skills/intent/handler.py +59 -0
  89. skills/registry.py +67 -0
  90. skills/retrospective/__init__.py +0 -0
  91. skills/retrospective/handler.py +125 -0
  92. skills/review/handler.py +356 -0
  93. skills/security/handler.py +454 -0
  94. skills/test/__init__.py +0 -0
  95. skills/test/handler.py +78 -0
@@ -0,0 +1,684 @@
1
+ #!/usr/bin/env python3
2
+ import json
3
+ import uuid
4
+ import logging
5
+ from pathlib import Path
6
+ from typing import Dict, List, Any, Optional, Callable
7
+ from datetime import datetime
8
+ from dataclasses import dataclass, field
9
+ from enum import Enum
10
+
11
+ from .checkpoint_manager import CheckpointManager, Checkpoint, CheckpointStatus, HandoffDocument
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class WorkflowStatus(Enum):
17
+ PENDING = "pending"
18
+ RUNNING = "running"
19
+ COMPLETED = "completed"
20
+ FAILED = "failed"
21
+ PAUSED = "paused"
22
+ WAITING_HANDOVER = "waiting_handover"
23
+
24
+
25
+ class StepStatus(Enum):
26
+ PENDING = "pending"
27
+ RUNNING = "running"
28
+ COMPLETED = "completed"
29
+ FAILED = "failed"
30
+ SKIPPED = "skipped"
31
+
32
+
33
+ @dataclass
34
+ class WorkflowStep:
35
+ step_id: str = field(default_factory=lambda: f"step-{uuid.uuid4().hex[:6]}")
36
+ name: str = ""
37
+ description: str = ""
38
+ role_id: str = ""
39
+ action: str = ""
40
+ inputs: Dict[str, Any] = field(default_factory=dict)
41
+ outputs: Dict[str, Any] = field(default_factory=dict)
42
+ conditions: Dict[str, Any] = field(default_factory=dict)
43
+ timeout: int = 3600
44
+ retry_count: int = 3
45
+ status: StepStatus = StepStatus.PENDING
46
+ result: Any = None
47
+ error: str = ""
48
+ dependencies: List[str] = field(default_factory=list)
49
+ artifacts_in: str = ""
50
+ artifacts_out: str = ""
51
+ gate_condition: str = ""
52
+ reviewers: List[str] = field(default_factory=list)
53
+ optional: bool = False
54
+ skip_reason: str = ""
55
+
56
+ def to_dict(self) -> Dict[str, Any]:
57
+ d = {
58
+ 'step_id': self.step_id, 'name': self.name, 'description': self.description,
59
+ 'role_id': self.role_id, 'action': self.action, 'inputs': self.inputs,
60
+ 'outputs': self.outputs, 'conditions': self.conditions, 'timeout': self.timeout,
61
+ 'retry_count': self.retry_count,
62
+ 'status': self.status.value if isinstance(self.status, StepStatus) else self.status,
63
+ 'result': self.result, 'error': self.error,
64
+ 'dependencies': self.dependencies, 'artifacts_in': self.artifacts_in,
65
+ 'artifacts_out': self.artifacts_out, 'gate_condition': self.gate_condition,
66
+ 'reviewers': self.reviewers, 'optional': self.optional, 'skip_reason': self.skip_reason,
67
+ }
68
+ return d
69
+
70
+ @classmethod
71
+ def from_dict(cls, data: Dict[str, Any]) -> 'WorkflowStep':
72
+ data_copy = dict(data)
73
+ if isinstance(data_copy.get('status'), str):
74
+ try:
75
+ data_copy['status'] = StepStatus(data_copy['status'])
76
+ except ValueError:
77
+ data_copy['status'] = StepStatus.PENDING
78
+ return cls(**data_copy)
79
+
80
+
81
+ @dataclass
82
+ class RequirementChange:
83
+ change_id: str = field(default_factory=lambda: f"cr-{uuid.uuid4().hex[:6]}")
84
+ description: str = ""
85
+ reason: str = ""
86
+ requested_by: str = ""
87
+ impact_analysis: Dict[str, Any] = field(default_factory=dict)
88
+ affected_phases: List[str] = field(default_factory=list)
89
+ review_result: str = "pending"
90
+ rollback_to: str = ""
91
+
92
+
93
+ @dataclass
94
+ class WorkflowDefinition:
95
+ workflow_id: str = field(default_factory=lambda: f"wf-{uuid.uuid4().hex[:8]}")
96
+ name: str = ""
97
+ description: str = ""
98
+ steps: List[WorkflowStep] = field(default_factory=list)
99
+ variables: Dict[str, Any] = field(default_factory=dict)
100
+ metadata: Dict[str, Any] = field(default_factory=dict)
101
+ created_at: str = field(default_factory=lambda: datetime.now().isoformat())
102
+
103
+ def to_dict(self) -> Dict[str, Any]:
104
+ return {
105
+ 'workflow_id': self.workflow_id, 'name': self.name, 'description': self.description,
106
+ 'steps': [s.to_dict() for s in self.steps], 'variables': self.variables,
107
+ 'metadata': self.metadata, 'created_at': self.created_at,
108
+ }
109
+
110
+
111
+ @dataclass
112
+ class WorkflowInstance:
113
+ instance_id: str = field(default_factory=lambda: f"inst-{uuid.uuid4().hex[:8]}")
114
+ workflow_id: str = ""
115
+ status: WorkflowStatus = WorkflowStatus.PENDING
116
+ current_step: Optional[str] = None
117
+ completed_steps: List[str] = field(default_factory=list)
118
+ failed_steps: List[str] = field(default_factory=list)
119
+ variables: Dict[str, Any] = field(default_factory=dict)
120
+ results: Dict[str, Any] = field(default_factory=dict)
121
+ started_at: Optional[str] = None
122
+ completed_at: Optional[str] = None
123
+ error: str = ""
124
+ checkpoint_id: Optional[str] = None
125
+ current_agent_id: Optional[str] = None
126
+ handoff_history: List[str] = field(default_factory=list)
127
+
128
+
129
+ class WorkflowEngine:
130
+ """
131
+ Simplified workflow engine for DevSquad.
132
+
133
+ Integrates with:
134
+ - CheckpointManager for state persistence
135
+ - Coordinator for task execution
136
+ - Dispatcher for role-based dispatching
137
+
138
+ Features:
139
+ 1. Task-to-workflow auto-splitting
140
+ 2. Step-by-step execution with checkpointing
141
+ 3. Agent handoff support
142
+ 4. Resume from checkpoint
143
+ """
144
+
145
+ def __init__(self, storage_path: str = "./workflows", coordinator=None, dispatcher=None):
146
+ self.storage_path = Path(storage_path)
147
+ self.storage_path.mkdir(parents=True, exist_ok=True)
148
+
149
+ self.definitions: Dict[str, WorkflowDefinition] = {}
150
+ self.instances: Dict[str, WorkflowInstance] = {}
151
+ self.executors: Dict[str, Callable] = {}
152
+
153
+ self.coordinator = coordinator
154
+ self.dispatcher = dispatcher
155
+ self.checkpoint_manager = CheckpointManager(
156
+ storage_path=str(self.storage_path / "checkpoints")
157
+ )
158
+ self.checkpoint_interval = 2
159
+
160
+ def create_workflow_from_task(
161
+ self,
162
+ task_title: str,
163
+ task_description: str = "",
164
+ target_agent: str = None,
165
+ ) -> WorkflowDefinition:
166
+ """
167
+ Create a workflow from a task description.
168
+
169
+ Automatically splits the task into steps based on keyword analysis.
170
+ """
171
+ steps = self._split_task_into_steps(task_title, task_description, target_agent)
172
+
173
+ definition = WorkflowDefinition(
174
+ name=task_title,
175
+ description=task_description,
176
+ steps=steps,
177
+ metadata={
178
+ 'target_agent': target_agent,
179
+ 'created_by': 'WorkflowEngine',
180
+ },
181
+ )
182
+
183
+ self.definitions[definition.workflow_id] = definition
184
+ logger.info("Workflow created: %s (%d steps)", definition.workflow_id, len(steps))
185
+ return definition
186
+
187
+ def _split_task_into_steps(self, task_title: str, task_description: str, target_agent: str = None) -> List[WorkflowStep]:
188
+ steps = []
189
+ task_text = f"{task_title} {task_description}".lower()
190
+
191
+ is_architecture = any(kw in task_text for kw in ['architecture', 'design', 'system', '架构', '设计'])
192
+ is_ui_design = any(kw in task_text for kw in ['ui', 'interface', 'frontend', '界面', '交互'])
193
+ is_development = any(kw in task_text for kw in ['develop', 'implement', 'code', '开发', '实现', '编码'])
194
+ is_testing = any(kw in task_text for kw in ['test', 'verify', 'quality', '测试', '验证'])
195
+ is_product = any(kw in task_text for kw in ['requirement', 'product', 'prd', '需求', '产品'])
196
+ is_deployment = any(kw in task_text for kw in ['deploy', 'release', 'ci/cd', '部署', '发布'])
197
+ is_security = any(kw in task_text for kw in ['security', 'auth', 'vulnerability', '安全', '认证'])
198
+
199
+ step_id = 1
200
+
201
+ if is_product or is_architecture:
202
+ steps.append(WorkflowStep(
203
+ step_id=f"step_{step_id}", name="Requirements Analysis",
204
+ description="Analyze task requirements and create detailed specification",
205
+ role_id="product-manager", action="analyze_requirements",
206
+ ))
207
+ step_id += 1
208
+
209
+ if is_architecture:
210
+ steps.append(WorkflowStep(
211
+ step_id=f"step_{step_id}", name="Architecture Design",
212
+ description="Design system architecture and technology selection",
213
+ role_id="architect", action="design_architecture",
214
+ ))
215
+ step_id += 1
216
+
217
+ if is_security:
218
+ steps.append(WorkflowStep(
219
+ step_id=f"step_{step_id}", name="Security Review",
220
+ description="Review security implications and recommend protections",
221
+ role_id="security", action="security_review",
222
+ ))
223
+ step_id += 1
224
+
225
+ if is_ui_design:
226
+ steps.append(WorkflowStep(
227
+ step_id=f"step_{step_id}", name="UI Design",
228
+ description="Design user interface and interaction flow",
229
+ role_id="ui-designer", action="design_ui",
230
+ ))
231
+ step_id += 1
232
+
233
+ if is_testing:
234
+ steps.append(WorkflowStep(
235
+ step_id=f"step_{step_id}", name="Test Design",
236
+ description="Create test strategy and test cases",
237
+ role_id="tester", action="design_tests",
238
+ ))
239
+ step_id += 1
240
+
241
+ if is_development:
242
+ steps.append(WorkflowStep(
243
+ step_id=f"step_{step_id}", name="Development",
244
+ description="Implement feature code",
245
+ role_id="solo-coder", action="develop",
246
+ ))
247
+ step_id += 1
248
+
249
+ if is_testing and is_development:
250
+ steps.append(WorkflowStep(
251
+ step_id=f"step_{step_id}", name="Test Execution",
252
+ description="Execute test cases and verify functionality",
253
+ role_id="tester", action="execute_tests",
254
+ ))
255
+ step_id += 1
256
+
257
+ if is_deployment:
258
+ steps.append(WorkflowStep(
259
+ step_id=f"step_{step_id}", name="Deployment",
260
+ description="Deploy and release the system",
261
+ role_id="devops", action="deploy",
262
+ ))
263
+
264
+ if not steps:
265
+ steps.append(WorkflowStep(
266
+ step_id="step_1", name="Task Execution",
267
+ description=task_description or task_title,
268
+ role_id=target_agent or "solo-coder", action="execute",
269
+ ))
270
+
271
+ return steps
272
+
273
+ def start_workflow(self, workflow_id: str, variables: Dict[str, Any] = None) -> Optional[WorkflowInstance]:
274
+ definition = self.definitions.get(workflow_id)
275
+ if not definition:
276
+ logger.warning("Workflow not found: %s", workflow_id)
277
+ return None
278
+
279
+ instance = WorkflowInstance(
280
+ workflow_id=workflow_id,
281
+ variables=variables or {},
282
+ status=WorkflowStatus.RUNNING,
283
+ started_at=datetime.now().isoformat(),
284
+ )
285
+
286
+ if definition.steps:
287
+ instance.current_step = definition.steps[0].step_id
288
+
289
+ self.instances[instance.instance_id] = instance
290
+ logger.info("Workflow started: %s", instance.instance_id)
291
+ return instance
292
+
293
+ def execute_step(self, instance_id: str, step_executor: Callable = None) -> Optional[WorkflowStep]:
294
+ instance = self.instances.get(instance_id)
295
+ if not instance:
296
+ return None
297
+
298
+ definition = self.definitions.get(instance.workflow_id)
299
+ if not definition:
300
+ return None
301
+
302
+ current_step = None
303
+ for step in definition.steps:
304
+ if step.step_id == instance.current_step:
305
+ current_step = step
306
+ break
307
+
308
+ if not current_step:
309
+ return None
310
+
311
+ current_step.status = StepStatus.RUNNING
312
+
313
+ try:
314
+ if step_executor:
315
+ result = step_executor(current_step, instance.variables)
316
+ elif current_step.action in self.executors:
317
+ result = self.executors[current_step.action](current_step, instance.variables)
318
+ else:
319
+ result = self._default_step_executor(current_step, instance.variables)
320
+
321
+ current_step.result = result
322
+ current_step.status = StepStatus.COMPLETED
323
+ instance.completed_steps.append(current_step.step_id)
324
+
325
+ if len(instance.completed_steps) % self.checkpoint_interval == 0:
326
+ self._save_checkpoint(instance, current_step)
327
+
328
+ next_step = self._get_next_step(definition, current_step)
329
+ if next_step:
330
+ instance.current_step = next_step.step_id
331
+ else:
332
+ instance.status = WorkflowStatus.COMPLETED
333
+ instance.completed_at = datetime.now().isoformat()
334
+
335
+ except Exception as e:
336
+ current_step.status = StepStatus.FAILED
337
+ current_step.error = str(e)
338
+ instance.failed_steps.append(current_step.step_id)
339
+ instance.error = str(e)
340
+ logger.warning("Step %s failed: %s", current_step.step_id, e)
341
+
342
+ return current_step
343
+
344
+ def _default_step_executor(self, step: WorkflowStep, variables: Dict[str, Any]) -> Any:
345
+ if self.dispatcher:
346
+ result = self.dispatcher.dispatch(
347
+ task_description=step.description,
348
+ roles=[step.role_id],
349
+ )
350
+ return {"dispatch_success": result.success, "summary": getattr(result, 'summary', '')[:200]}
351
+ return {"action": step.action, "role": step.role_id, "status": "mock_completed"}
352
+
353
+ def _get_next_step(self, definition: WorkflowDefinition, current_step: WorkflowStep) -> Optional[WorkflowStep]:
354
+ found_current = False
355
+ for step in definition.steps:
356
+ if found_current:
357
+ return step
358
+ if step.step_id == current_step.step_id:
359
+ found_current = True
360
+ return None
361
+
362
+ def _save_checkpoint(self, instance: WorkflowInstance, current_step: WorkflowStep):
363
+ definition = self.definitions.get(instance.workflow_id)
364
+ all_step_ids = [s.step_id for s in (definition.steps if definition else [])]
365
+ remaining = [sid for sid in all_step_ids if sid not in instance.completed_steps and sid not in instance.failed_steps]
366
+
367
+ checkpoint = self.checkpoint_manager.create_checkpoint_from_dispatch(
368
+ task_id=instance.instance_id,
369
+ step_name=current_step.name,
370
+ agent_id=current_step.role_id,
371
+ completed_steps=instance.completed_steps,
372
+ remaining_steps=remaining,
373
+ context=instance.variables,
374
+ outputs=instance.results,
375
+ )
376
+ instance.checkpoint_id = checkpoint.checkpoint_id
377
+
378
+ def resume_from_checkpoint(self, instance_id: str) -> Optional[WorkflowInstance]:
379
+ instance = self.instances.get(instance_id)
380
+ if not instance:
381
+ return None
382
+
383
+ if not instance.checkpoint_id:
384
+ logger.warning("No checkpoint found for instance: %s", instance_id)
385
+ return instance
386
+
387
+ checkpoint = self.checkpoint_manager.load_checkpoint(instance.checkpoint_id)
388
+ if not checkpoint:
389
+ logger.warning("Failed to load checkpoint: %s", instance.checkpoint_id)
390
+ return instance
391
+
392
+ instance.completed_steps = checkpoint.completed_steps
393
+ instance.variables = checkpoint.context_snapshot
394
+ instance.results = checkpoint.outputs
395
+
396
+ if checkpoint.remaining_steps:
397
+ instance.current_step = checkpoint.remaining_steps[0]
398
+ instance.status = WorkflowStatus.RUNNING
399
+ else:
400
+ instance.status = WorkflowStatus.COMPLETED
401
+
402
+ logger.info("Resumed instance %s from checkpoint %s", instance_id, instance.checkpoint_id)
403
+ return instance
404
+
405
+ def handoff(self, instance_id: str, from_agent: str, to_agent: str, reason: str = "") -> Optional[HandoffDocument]:
406
+ instance = self.instances.get(instance_id)
407
+ if not instance:
408
+ return None
409
+
410
+ definition = self.definitions.get(instance.workflow_id)
411
+ all_step_ids = [s.step_id for s in (definition.steps if definition else [])]
412
+ remaining = [sid for sid in all_step_ids if sid not in instance.completed_steps]
413
+
414
+ handoff = HandoffDocument(
415
+ task_id=instance_id,
416
+ from_agent=from_agent,
417
+ to_agent=to_agent,
418
+ completed_work=[f"Completed step: {sid}" for sid in instance.completed_steps],
419
+ current_state=instance.variables,
420
+ next_steps=remaining,
421
+ handoff_reason=reason or "agent_handoff",
422
+ )
423
+
424
+ self.checkpoint_manager.save_handoff(handoff)
425
+ instance.handoff_history.append(handoff.handoff_id)
426
+ instance.current_agent_id = to_agent
427
+
428
+ logger.info("Handoff: %s -> %s for instance %s", from_agent, to_agent, instance_id)
429
+ return handoff
430
+
431
+ def get_workflow_status(self, instance_id: str) -> Optional[Dict[str, Any]]:
432
+ instance = self.instances.get(instance_id)
433
+ if not instance:
434
+ return None
435
+
436
+ definition = self.definitions.get(instance.workflow_id)
437
+ total_steps = len(definition.steps) if definition else 0
438
+ completed = len(instance.completed_steps)
439
+ failed = len(instance.failed_steps)
440
+
441
+ return {
442
+ 'instance_id': instance_id,
443
+ 'workflow_id': instance.workflow_id,
444
+ 'status': instance.status.value,
445
+ 'progress': f"{completed}/{total_steps}",
446
+ 'completion_rate': (completed / total_steps * 100) if total_steps > 0 else 0,
447
+ 'current_step': instance.current_step,
448
+ 'failed_steps': instance.failed_steps,
449
+ 'has_checkpoint': instance.checkpoint_id is not None,
450
+ }
451
+
452
+ def register_executor(self, action: str, executor: Callable):
453
+ self.executors[action] = executor
454
+
455
+ def create_lifecycle(self, template_name: str = "full") -> WorkflowDefinition:
456
+ """
457
+ Create a workflow from a predefined lifecycle template.
458
+
459
+ Available templates: full, backend, frontend, internal_tool, minimal
460
+ """
461
+ if template_name not in LIFECYCLE_TEMPLATES:
462
+ raise ValueError(f"Unknown template: {template_name}. Available: {list(LIFECYCLE_TEMPLATES.keys())}")
463
+
464
+ phase_ids = LIFECYCLE_TEMPLATES[template_name]
465
+ steps = []
466
+ for pid in phase_ids:
467
+ pt = PHASE_TEMPLATES[pid]
468
+ steps.append(WorkflowStep(
469
+ step_id=pid,
470
+ name=pt["name"],
471
+ description=pt["description"],
472
+ role_id=pt["role_id"],
473
+ action=pt["action"],
474
+ dependencies=pt["dependencies"],
475
+ artifacts_in=pt["artifacts_in"],
476
+ artifacts_out=pt["artifacts_out"],
477
+ gate_condition=pt["gate_condition"],
478
+ reviewers=pt["reviewers"],
479
+ optional=pt["optional"],
480
+ ))
481
+
482
+ definition = WorkflowDefinition(
483
+ name=f"lifecycle-{template_name}",
484
+ description=f"DevSquad V3.6 {template_name} lifecycle ({len(steps)} phases)",
485
+ steps=steps,
486
+ metadata={'template': template_name, 'lifecycle_version': '3.6.0'},
487
+ )
488
+ self.definitions[definition.workflow_id] = definition
489
+ logger.info("Lifecycle workflow created: %s (%s, %d phases)", definition.workflow_id, template_name, len(steps))
490
+ return definition
491
+
492
+ def submit_change_request(
493
+ self,
494
+ instance_id: str,
495
+ description: str,
496
+ reason: str,
497
+ requested_by: str = "user",
498
+ ) -> Optional[RequirementChange]:
499
+ """
500
+ Submit a requirement change request for a running workflow.
501
+
502
+ Returns impact analysis and affected phases.
503
+ """
504
+ instance = self.instances.get(instance_id)
505
+ if not instance:
506
+ return None
507
+
508
+ if instance.status not in (WorkflowStatus.RUNNING, WorkflowStatus.PAUSED):
509
+ logger.warning("Cannot submit change request for instance %s with status %s", instance_id, instance.status.value)
510
+ return None
511
+
512
+ definition = self.definitions.get(instance.workflow_id)
513
+ if not definition:
514
+ return None
515
+
516
+ affected = []
517
+ for step in definition.steps:
518
+ if step.step_id not in instance.completed_steps:
519
+ affected.append(step.step_id)
520
+
521
+ earliest = None
522
+ for step in definition.steps:
523
+ if step.step_id not in instance.completed_steps:
524
+ earliest = step.step_id
525
+ break
526
+
527
+ sanitized_desc = description[:500]
528
+ sanitized_reason = reason[:500]
529
+ sanitized_by = requested_by[:100]
530
+
531
+ change_request = RequirementChange(
532
+ description=sanitized_desc,
533
+ reason=sanitized_reason,
534
+ requested_by=sanitized_by,
535
+ affected_phases=affected,
536
+ rollback_to=earliest or "",
537
+ )
538
+
539
+ logger.info("Change request submitted: %s (rollback_to=%s)", change_request.change_id, earliest)
540
+ return change_request
541
+
542
+
543
+ PHASE_TEMPLATES: Dict[str, Dict[str, Any]] = {
544
+ "P1": {
545
+ "name": "Requirements Analysis",
546
+ "description": "Analyze task requirements and create detailed specification",
547
+ "role_id": "product-manager",
548
+ "action": "analyze_requirements",
549
+ "dependencies": [],
550
+ "artifacts_in": "User raw requirements",
551
+ "artifacts_out": "User stories, acceptance criteria, priority matrix, NFRs",
552
+ "gate_condition": "Acceptance criteria quantifiable and unambiguous",
553
+ "reviewers": ["architect", "tester", "security", "ui-designer"],
554
+ "optional": False,
555
+ },
556
+ "P2": {
557
+ "name": "Architecture Design",
558
+ "description": "Design system architecture and technology selection",
559
+ "role_id": "architect",
560
+ "action": "design_architecture",
561
+ "dependencies": ["P1"],
562
+ "artifacts_in": "P1 deliverables",
563
+ "artifacts_out": "Architecture proposal, tech selection, service boundaries, quality attributes",
564
+ "gate_condition": "Architecture passes weighted consensus (>=70%)",
565
+ "reviewers": ["product-manager", "security", "devops"],
566
+ "optional": False,
567
+ },
568
+ "P3": {
569
+ "name": "Technical Design",
570
+ "description": "Detail architecture into developable technical specs",
571
+ "role_id": "architect",
572
+ "action": "design_technical",
573
+ "dependencies": ["P2"],
574
+ "artifacts_in": "P2 deliverables",
575
+ "artifacts_out": "API specs, interface definitions, tech constraints, tech risk assessment",
576
+ "gate_condition": "API specs unambiguous",
577
+ "reviewers": ["solo-coder", "tester"],
578
+ "optional": False,
579
+ },
580
+ "P4": {
581
+ "name": "Data Design",
582
+ "description": "Design data storage models and migration plans",
583
+ "role_id": "architect",
584
+ "action": "design_data",
585
+ "dependencies": ["P2"],
586
+ "artifacts_in": "P2 deliverables (+ P3 if available)",
587
+ "artifacts_out": "Data model (ER), table structure, index strategy, migration plan",
588
+ "gate_condition": "Data model 3NF or denormalization justified",
589
+ "reviewers": ["architect", "security"],
590
+ "optional": True,
591
+ },
592
+ "P5": {
593
+ "name": "Interaction Design",
594
+ "description": "Design user interaction flows and information architecture",
595
+ "role_id": "ui-designer",
596
+ "action": "design_interaction",
597
+ "dependencies": ["P1", "P3"],
598
+ "artifacts_in": "P1 + P3 deliverables",
599
+ "artifacts_out": "Interaction flows, information architecture, prototype, accessibility checklist",
600
+ "gate_condition": "Core flow usability verified",
601
+ "reviewers": ["product-manager", "tester", "security"],
602
+ "optional": True,
603
+ },
604
+ "P6": {
605
+ "name": "Security Review",
606
+ "description": "Review security implications and compliance",
607
+ "role_id": "security",
608
+ "action": "security_review",
609
+ "dependencies": ["P2", "P3"],
610
+ "artifacts_in": "P2 + P3 deliverables (+ P4, P5 if exist)",
611
+ "artifacts_out": "Threat model, vulnerability list, compliance report, security fixes",
612
+ "gate_condition": "No P0/P1 vulnerabilities, compliance green",
613
+ "reviewers": ["architect", "devops"],
614
+ "optional": True,
615
+ },
616
+ "P7": {
617
+ "name": "Test Planning",
618
+ "description": "Plan all test dimensions before development",
619
+ "role_id": "tester",
620
+ "action": "plan_tests",
621
+ "dependencies": ["P1", "P3"],
622
+ "artifacts_in": "P1 + P3 deliverables (+ P6 if exists)",
623
+ "artifacts_out": "Test plan (8 dimensions: functional/integration/performance/security/env/install/regression/acceptance)",
624
+ "gate_condition": "Test plan review passed",
625
+ "reviewers": ["architect", "security", "devops", "product-manager"],
626
+ "optional": False,
627
+ },
628
+ "P8": {
629
+ "name": "Implementation",
630
+ "description": "Implement feature code with testability",
631
+ "role_id": "solo-coder",
632
+ "action": "develop",
633
+ "dependencies": ["P3", "P7"],
634
+ "artifacts_in": "P3 + P6 (if exists) + P7 deliverables",
635
+ "artifacts_out": "Runnable code, code review report, unit tests, testability notes",
636
+ "gate_condition": "Code review passed, no P0 defects",
637
+ "reviewers": ["architect", "security", "tester", "solo-coder"],
638
+ "optional": False,
639
+ },
640
+ "P9": {
641
+ "name": "Test Execution",
642
+ "description": "Execute all test dimensions per P7 plan",
643
+ "role_id": "tester",
644
+ "action": "execute_tests",
645
+ "dependencies": ["P7", "P8"],
646
+ "artifacts_in": "P7 + P8 deliverables",
647
+ "artifacts_out": "Full test report, defect list",
648
+ "gate_condition": "Coverage>=80% + P7 plan 100% executed + no P0 defects",
649
+ "reviewers": ["architect", "product-manager", "security", "devops"],
650
+ "optional": False,
651
+ },
652
+ "P10": {
653
+ "name": "Deployment & Release",
654
+ "description": "Deploy and release the system to production",
655
+ "role_id": "devops",
656
+ "action": "deploy",
657
+ "dependencies": ["P9"],
658
+ "artifacts_in": "P9 deliverables",
659
+ "artifacts_out": "Deployment plan, release strategy, rollback plan, release checklist, IaC",
660
+ "gate_condition": "Deployment drill passed, rollback verified",
661
+ "reviewers": ["architect", "security", "tester"],
662
+ "optional": False,
663
+ },
664
+ "P11": {
665
+ "name": "Operations & Assurance",
666
+ "description": "Ensure system runs stably in production",
667
+ "role_id": "devops",
668
+ "action": "operate",
669
+ "dependencies": ["P10"],
670
+ "artifacts_in": "P10 deliverables",
671
+ "artifacts_out": "Monitoring dashboards, alert rules, incident response plans, performance baselines",
672
+ "gate_condition": "P99<target, alert coverage 100%",
673
+ "reviewers": ["architect", "devops"],
674
+ "optional": True,
675
+ },
676
+ }
677
+
678
+ LIFECYCLE_TEMPLATES: Dict[str, List[str]] = {
679
+ "full": ["P1", "P2", "P3", "P4", "P5", "P6", "P7", "P8", "P9", "P10", "P11"],
680
+ "backend": ["P1", "P2", "P3", "P4", "P6", "P7", "P8", "P9", "P10", "P11"],
681
+ "frontend": ["P1", "P2", "P3", "P5", "P7", "P8", "P9", "P10", "P11"],
682
+ "internal_tool": ["P1", "P2", "P3", "P7", "P8", "P9", "P10"],
683
+ "minimal": ["P1", "P3", "P7", "P8", "P9"],
684
+ }