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.
- devsquad-3.6.0.dist-info/METADATA +944 -0
- devsquad-3.6.0.dist-info/RECORD +95 -0
- devsquad-3.6.0.dist-info/WHEEL +5 -0
- devsquad-3.6.0.dist-info/entry_points.txt +2 -0
- devsquad-3.6.0.dist-info/licenses/LICENSE +21 -0
- devsquad-3.6.0.dist-info/top_level.txt +2 -0
- scripts/__init__.py +0 -0
- scripts/ai_semantic_matcher.py +512 -0
- scripts/alert_manager.py +505 -0
- scripts/api/__init__.py +43 -0
- scripts/api/models.py +386 -0
- scripts/api/routes/__init__.py +20 -0
- scripts/api/routes/dispatch.py +348 -0
- scripts/api/routes/lifecycle.py +330 -0
- scripts/api/routes/metrics_gates.py +347 -0
- scripts/api_server.py +318 -0
- scripts/auth.py +451 -0
- scripts/cli/__init__.py +1 -0
- scripts/cli/cli_visual.py +642 -0
- scripts/cli.py +1094 -0
- scripts/collaboration/__init__.py +212 -0
- scripts/collaboration/_version.py +1 -0
- scripts/collaboration/agent_briefing.py +656 -0
- scripts/collaboration/ai_semantic_matcher.py +260 -0
- scripts/collaboration/anchor_checker.py +281 -0
- scripts/collaboration/anti_rationalization.py +470 -0
- scripts/collaboration/async_integration_example.py +255 -0
- scripts/collaboration/batch_scheduler.py +149 -0
- scripts/collaboration/checkpoint_manager.py +561 -0
- scripts/collaboration/ci_feedback_adapter.py +351 -0
- scripts/collaboration/code_map_generator.py +247 -0
- scripts/collaboration/concern_pack_loader.py +352 -0
- scripts/collaboration/confidence_score.py +496 -0
- scripts/collaboration/config_loader.py +188 -0
- scripts/collaboration/consensus.py +244 -0
- scripts/collaboration/context_compressor.py +533 -0
- scripts/collaboration/coordinator.py +668 -0
- scripts/collaboration/dispatcher.py +1636 -0
- scripts/collaboration/dual_layer_context.py +128 -0
- scripts/collaboration/enhanced_worker.py +539 -0
- scripts/collaboration/feature_usage_tracker.py +206 -0
- scripts/collaboration/five_axis_consensus.py +334 -0
- scripts/collaboration/input_validator.py +401 -0
- scripts/collaboration/integration_example.py +287 -0
- scripts/collaboration/intent_workflow_mapper.py +350 -0
- scripts/collaboration/language_parsers.py +269 -0
- scripts/collaboration/lifecycle_protocol.py +1446 -0
- scripts/collaboration/llm_backend.py +453 -0
- scripts/collaboration/llm_cache.py +448 -0
- scripts/collaboration/llm_cache_async.py +347 -0
- scripts/collaboration/llm_retry.py +387 -0
- scripts/collaboration/llm_retry_async.py +389 -0
- scripts/collaboration/mce_adapter.py +597 -0
- scripts/collaboration/memory_bridge.py +1607 -0
- scripts/collaboration/models.py +537 -0
- scripts/collaboration/null_providers.py +297 -0
- scripts/collaboration/operation_classifier.py +289 -0
- scripts/collaboration/output_slicer.py +225 -0
- scripts/collaboration/performance_monitor.py +462 -0
- scripts/collaboration/permission_guard.py +865 -0
- scripts/collaboration/prompt_assembler.py +756 -0
- scripts/collaboration/prompt_variant_generator.py +483 -0
- scripts/collaboration/protocols.py +267 -0
- scripts/collaboration/report_formatter.py +352 -0
- scripts/collaboration/retrospective.py +279 -0
- scripts/collaboration/role_matcher.py +92 -0
- scripts/collaboration/role_template_market.py +352 -0
- scripts/collaboration/rule_collector.py +678 -0
- scripts/collaboration/scratchpad.py +346 -0
- scripts/collaboration/skill_registry.py +151 -0
- scripts/collaboration/skillifier.py +878 -0
- scripts/collaboration/standardized_role_template.py +317 -0
- scripts/collaboration/task_completion_checker.py +237 -0
- scripts/collaboration/test_quality_guard.py +695 -0
- scripts/collaboration/unified_gate_engine.py +598 -0
- scripts/collaboration/usage_tracker.py +309 -0
- scripts/collaboration/user_friendly_error.py +176 -0
- scripts/collaboration/verification_gate.py +312 -0
- scripts/collaboration/warmup_manager.py +635 -0
- scripts/collaboration/worker.py +513 -0
- scripts/collaboration/workflow_engine.py +684 -0
- scripts/dashboard.py +1088 -0
- scripts/generate_benchmark_report.py +786 -0
- scripts/history_manager.py +604 -0
- scripts/mcp_server.py +289 -0
- skills/__init__.py +32 -0
- skills/dispatch/handler.py +52 -0
- skills/intent/handler.py +59 -0
- skills/registry.py +67 -0
- skills/retrospective/__init__.py +0 -0
- skills/retrospective/handler.py +125 -0
- skills/review/handler.py +356 -0
- skills/security/handler.py +454 -0
- skills/test/__init__.py +0 -0
- 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
|
+
}
|