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,348 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
DevSquad API Routes - Task Dispatch
|
|
5
|
+
|
|
6
|
+
REST API endpoints for multi-agent task dispatch.
|
|
7
|
+
|
|
8
|
+
Endpoints:
|
|
9
|
+
POST /api/v1/tasks/dispatch - Full task dispatch interface
|
|
10
|
+
POST /api/v1/tasks/quick - Quick dispatch interface
|
|
11
|
+
GET /api/v1/tasks/history - Get dispatch history
|
|
12
|
+
GET /api/v1/roles - List available roles
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import logging
|
|
16
|
+
from datetime import datetime
|
|
17
|
+
from typing import Any, Dict, List, Optional
|
|
18
|
+
|
|
19
|
+
import sys
|
|
20
|
+
import os
|
|
21
|
+
|
|
22
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
|
23
|
+
|
|
24
|
+
from fastapi import APIRouter, HTTPException, Query
|
|
25
|
+
|
|
26
|
+
from scripts.api.models import (
|
|
27
|
+
TaskDispatchRequest,
|
|
28
|
+
QuickDispatchRequest,
|
|
29
|
+
DispatchResponse,
|
|
30
|
+
WorkerResultItem,
|
|
31
|
+
IntentMatchInfo,
|
|
32
|
+
FiveAxisResult,
|
|
33
|
+
AnchorResult,
|
|
34
|
+
RolesListResponse,
|
|
35
|
+
DispatchHistoryResponse,
|
|
36
|
+
RoleInfo,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
logger = logging.getLogger(__name__)
|
|
40
|
+
|
|
41
|
+
router = APIRouter(tags=["Task Dispatch"])
|
|
42
|
+
|
|
43
|
+
_global_dispatcher = None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _get_dispatcher():
|
|
47
|
+
"""Get or create the global MultiAgentDispatcher instance."""
|
|
48
|
+
global _global_dispatcher
|
|
49
|
+
if _global_dispatcher is None:
|
|
50
|
+
try:
|
|
51
|
+
from scripts.collaboration.dispatcher import MultiAgentDispatcher
|
|
52
|
+
_global_dispatcher = MultiAgentDispatcher(
|
|
53
|
+
enable_warmup=True,
|
|
54
|
+
enable_compression=True,
|
|
55
|
+
enable_permission=True,
|
|
56
|
+
enable_memory=True,
|
|
57
|
+
enable_skillify=True,
|
|
58
|
+
enable_quality_guard=False,
|
|
59
|
+
)
|
|
60
|
+
logger.info("MultiAgentDispatcher initialized successfully")
|
|
61
|
+
except Exception as e:
|
|
62
|
+
logger.error(f"Failed to initialize MultiAgentDispatcher: {e}")
|
|
63
|
+
raise HTTPException(
|
|
64
|
+
status_code=503,
|
|
65
|
+
detail=f"Dispatcher initialization failed: {str(e)}"
|
|
66
|
+
)
|
|
67
|
+
return _global_dispatcher
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _convert_dispatch_result(result) -> Dict[str, Any]:
|
|
71
|
+
"""Convert DispatchResult to API response dict."""
|
|
72
|
+
worker_results = []
|
|
73
|
+
for wr in (result.worker_results or []):
|
|
74
|
+
worker_results.append(WorkerResultItem(
|
|
75
|
+
worker_id=wr.get("worker_id"),
|
|
76
|
+
role_id=wr.get("role_id", "unknown"),
|
|
77
|
+
role_name=wr.get("role_name", "Unknown"),
|
|
78
|
+
task_id=wr.get("task_id"),
|
|
79
|
+
success=wr.get("success", False),
|
|
80
|
+
output=wr.get("output"),
|
|
81
|
+
error=wr.get("error"),
|
|
82
|
+
).model_dump())
|
|
83
|
+
|
|
84
|
+
intent_match = None
|
|
85
|
+
if result.intent_match:
|
|
86
|
+
intent_match = IntentMatchInfo(
|
|
87
|
+
intent_type=result.intent_match.get("intent_type"),
|
|
88
|
+
workflow_chain=result.intent_match.get("workflow_chain"),
|
|
89
|
+
confidence=result.intent_match.get("confidence"),
|
|
90
|
+
).model_dump()
|
|
91
|
+
|
|
92
|
+
five_axis_result = None
|
|
93
|
+
if result.five_axis_result:
|
|
94
|
+
five_axis_result = FiveAxisResult(
|
|
95
|
+
verdict=result.five_axis_result.get("verdict"),
|
|
96
|
+
overall_consensus=result.five_axis_result.get("overall_consensus"),
|
|
97
|
+
axis_consensus=result.five_axis_result.get("axis_consensus"),
|
|
98
|
+
action_items=result.five_axis_result.get("action_items"),
|
|
99
|
+
).model_dump()
|
|
100
|
+
|
|
101
|
+
anchor_result = None
|
|
102
|
+
if result.anchor_result:
|
|
103
|
+
anchor_result = AnchorResult(
|
|
104
|
+
aligned=result.anchor_result.get("aligned"),
|
|
105
|
+
coverage=result.anchor_result.get("coverage"),
|
|
106
|
+
drift_score=result.anchor_result.get("drift_score"),
|
|
107
|
+
severity=result.anchor_result.get("severity"),
|
|
108
|
+
recommendation=result.anchor_result.get("recommendation"),
|
|
109
|
+
).model_dump()
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
"success": result.success,
|
|
113
|
+
"task_description": result.task_description,
|
|
114
|
+
"matched_roles": result.matched_roles or [],
|
|
115
|
+
"summary": result.summary or "",
|
|
116
|
+
"duration_seconds": round(result.duration_seconds, 2),
|
|
117
|
+
"worker_results": worker_results,
|
|
118
|
+
"errors": result.errors or [],
|
|
119
|
+
"intent_match": intent_match,
|
|
120
|
+
"five_axis_result": five_axis_result,
|
|
121
|
+
"anchor_result": anchor_result,
|
|
122
|
+
"scratchpad_summary": result.scratchpad_summary,
|
|
123
|
+
"consensus_records": result.consensus_records,
|
|
124
|
+
"compression_info": result.compression_info,
|
|
125
|
+
"memory_stats": result.memory_stats,
|
|
126
|
+
"permission_checks": result.permission_checks,
|
|
127
|
+
"skill_proposals": result.skill_proposals,
|
|
128
|
+
"quality_report": result.quality_report,
|
|
129
|
+
"retrospective_report": result.retrospective_report,
|
|
130
|
+
"details": result.details,
|
|
131
|
+
"timestamp": datetime.now().isoformat(),
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@router.post(
|
|
136
|
+
"/api/v1/tasks/dispatch",
|
|
137
|
+
response_model=DispatchResponse,
|
|
138
|
+
summary="Full Task Dispatch / 完整任务调度",
|
|
139
|
+
description="""
|
|
140
|
+
Execute a complete multi-agent task dispatch.
|
|
141
|
+
|
|
142
|
+
执行完整的多Agent任务调度,支持指定角色、执行模式、LLM后端等参数。
|
|
143
|
+
|
|
144
|
+
This is the core dispatch interface that orchestrates multiple AI agents
|
|
145
|
+
to collaborate on a complex task.
|
|
146
|
+
|
|
147
|
+
**Features:**
|
|
148
|
+
- Automatic role matching based on task content
|
|
149
|
+
- Parallel or sequential execution modes
|
|
150
|
+
- Intent detection and workflow mapping
|
|
151
|
+
- Consensus-based decision making
|
|
152
|
+
- Context compression for long conversations
|
|
153
|
+
- Memory bridge for cross-session learning
|
|
154
|
+
"""
|
|
155
|
+
)
|
|
156
|
+
async def dispatch_task(request: TaskDispatchRequest):
|
|
157
|
+
"""
|
|
158
|
+
Full task dispatch endpoint.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
request: Task dispatch request with task, roles, mode, etc.
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
DispatchResponse with complete results from all workers
|
|
165
|
+
"""
|
|
166
|
+
try:
|
|
167
|
+
dispatcher = _get_dispatcher()
|
|
168
|
+
|
|
169
|
+
llm_backend = None
|
|
170
|
+
if request.backend and request.backend.lower() not in ("none", "mock", ""):
|
|
171
|
+
try:
|
|
172
|
+
from scripts.collaboration.llm_backend import create_llm_backend
|
|
173
|
+
llm_backend = create_llm_backend(request.backend.lower())
|
|
174
|
+
logger.info(f"Using LLM backend: {request.backend}")
|
|
175
|
+
except Exception as backend_err:
|
|
176
|
+
logger.warning(f"Failed to create LLM backend '{request.backend}': {backend_err}, using default")
|
|
177
|
+
pass
|
|
178
|
+
|
|
179
|
+
result = dispatcher.dispatch(
|
|
180
|
+
task_description=request.task,
|
|
181
|
+
roles=request.roles,
|
|
182
|
+
mode=request.mode,
|
|
183
|
+
dry_run=request.dry_run,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
response_data = _convert_dispatch_result(result)
|
|
187
|
+
return DispatchResponse(**response_data)
|
|
188
|
+
|
|
189
|
+
except HTTPException:
|
|
190
|
+
raise
|
|
191
|
+
except ImportError as e:
|
|
192
|
+
logger.error(f"Missing dependency during dispatch: {e}")
|
|
193
|
+
raise HTTPException(
|
|
194
|
+
status_code=503,
|
|
195
|
+
detail=f"Service unavailable: Missing dependency - {str(e)}"
|
|
196
|
+
)
|
|
197
|
+
except Exception as e:
|
|
198
|
+
logger.error(f"Dispatch failed: {e}", exc_info=True)
|
|
199
|
+
raise HTTPException(
|
|
200
|
+
status_code=500,
|
|
201
|
+
detail=f"Dispatch failed: {str(e)}"
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@router.post(
|
|
206
|
+
"/api/v1/tasks/quick",
|
|
207
|
+
response_model=DispatchResponse,
|
|
208
|
+
summary="Quick Task Dispatch / 快速任务调度",
|
|
209
|
+
description="""
|
|
210
|
+
Quick dispatch with simplified parameters.
|
|
211
|
+
|
|
212
|
+
快速调度接口,使用简化的参数,自动选择最优配置。
|
|
213
|
+
|
|
214
|
+
Ideal for simple tasks where you just want to get results quickly
|
|
215
|
+
without configuring all options.
|
|
216
|
+
"""
|
|
217
|
+
)
|
|
218
|
+
async def quick_dispatch(request: QuickDispatchRequest):
|
|
219
|
+
"""
|
|
220
|
+
Quick dispatch endpoint.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
request: Quick dispatch request with task and format options
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
DispatchResponse with formatted summary
|
|
227
|
+
"""
|
|
228
|
+
try:
|
|
229
|
+
dispatcher = _get_dispatcher()
|
|
230
|
+
|
|
231
|
+
result = dispatcher.quick_dispatch(
|
|
232
|
+
task=request.task,
|
|
233
|
+
output_format=request.output_format,
|
|
234
|
+
include_action_items=request.include_action_items,
|
|
235
|
+
include_timing=request.include_timing,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
response_data = _convert_dispatch_result(result)
|
|
239
|
+
return DispatchResponse(**response_data)
|
|
240
|
+
|
|
241
|
+
except HTTPException:
|
|
242
|
+
raise
|
|
243
|
+
except Exception as e:
|
|
244
|
+
logger.error(f"Quick dispatch failed: {e}", exc_info=True)
|
|
245
|
+
raise HTTPException(
|
|
246
|
+
status_code=500,
|
|
247
|
+
detail=f"Quick dispatch failed: {str(e)}"
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
@router.get(
|
|
252
|
+
"/api/v1/tasks/history",
|
|
253
|
+
response_model=DispatchHistoryResponse,
|
|
254
|
+
summary="Get Dispatch History / 获取调度历史",
|
|
255
|
+
description="""
|
|
256
|
+
Retrieve recent task dispatch history.
|
|
257
|
+
|
|
258
|
+
获取最近的任务调度历史记录。
|
|
259
|
+
|
|
260
|
+
Returns the N most recent dispatch records from the in-memory history.
|
|
261
|
+
"""
|
|
262
|
+
)
|
|
263
|
+
async def get_dispatch_history(
|
|
264
|
+
limit: int = Query(10, ge=1, le=100, description="Maximum number of records to return / 最大返回记录数")
|
|
265
|
+
):
|
|
266
|
+
"""
|
|
267
|
+
Get dispatch history.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
limit: Maximum number of records to return (1-100)
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
DispatchHistoryResponse with list of historical dispatches
|
|
274
|
+
"""
|
|
275
|
+
try:
|
|
276
|
+
dispatcher = _get_dispatcher()
|
|
277
|
+
history = dispatcher.get_history(limit=limit)
|
|
278
|
+
|
|
279
|
+
return DispatchHistoryResponse(
|
|
280
|
+
history=history,
|
|
281
|
+
total=len(history),
|
|
282
|
+
limit=limit,
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
except HTTPException:
|
|
286
|
+
raise
|
|
287
|
+
except Exception as e:
|
|
288
|
+
logger.error(f"Failed to get dispatch history: {e}")
|
|
289
|
+
raise HTTPException(
|
|
290
|
+
status_code=500,
|
|
291
|
+
detail=f"Failed to retrieve history: {str(e)}"
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
@router.get(
|
|
296
|
+
"/api/v1/roles",
|
|
297
|
+
response_model=RolesListResponse,
|
|
298
|
+
summary="List Available Roles / 列出可用角色",
|
|
299
|
+
description="""
|
|
300
|
+
List all available agent roles with their information.
|
|
301
|
+
|
|
302
|
+
列出所有可用的AI Agent角色及其详细信息。
|
|
303
|
+
|
|
304
|
+
Includes core roles (architect, product-manager, security, tester,
|
|
305
|
+
solo-coder, devops, ui-designer) and any planned/extended roles.
|
|
306
|
+
"""
|
|
307
|
+
)
|
|
308
|
+
async def list_roles():
|
|
309
|
+
"""
|
|
310
|
+
List all available roles.
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
RolesListResponse with role information
|
|
314
|
+
"""
|
|
315
|
+
try:
|
|
316
|
+
from scripts.collaboration.models import (
|
|
317
|
+
ROLE_REGISTRY,
|
|
318
|
+
get_core_roles,
|
|
319
|
+
get_planned_roles,
|
|
320
|
+
get_all_role_ids,
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
roles = []
|
|
324
|
+
for role_id, role_def in ROLE_REGISTRY.items():
|
|
325
|
+
roles.append(RoleInfo(
|
|
326
|
+
role_id=role_id,
|
|
327
|
+
name=role_def.name,
|
|
328
|
+
description=role_def.description,
|
|
329
|
+
keywords=getattr(role_def, 'keywords', None),
|
|
330
|
+
status=getattr(role_def, 'status', 'active'),
|
|
331
|
+
).model_dump())
|
|
332
|
+
|
|
333
|
+
core_role_ids = [r.role_id for r in get_core_roles().values()]
|
|
334
|
+
planned_role_ids = list(get_planned_roles().keys())
|
|
335
|
+
|
|
336
|
+
return RolesListResponse(
|
|
337
|
+
roles=roles,
|
|
338
|
+
total=len(roles),
|
|
339
|
+
core_roles=core_role_ids,
|
|
340
|
+
planned_roles=planned_role_ids,
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
except Exception as e:
|
|
344
|
+
logger.error(f"Failed to list roles: {e}")
|
|
345
|
+
raise HTTPException(
|
|
346
|
+
status_code=500,
|
|
347
|
+
detail=f"Failed to retrieve roles: {str(e)}"
|
|
348
|
+
)
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
DevSquad API Routes - Lifecycle Management
|
|
5
|
+
|
|
6
|
+
REST API endpoints for lifecycle phase management.
|
|
7
|
+
|
|
8
|
+
Endpoints:
|
|
9
|
+
GET /api/v1/lifecycle/phases - List all phases
|
|
10
|
+
GET /api/v1/lifecycle/phases/{id} - Get specific phase
|
|
11
|
+
GET /api/v1/lifecycle/status - Get current status
|
|
12
|
+
POST /api/v1/lifecycle/actions - Execute phase action
|
|
13
|
+
GET /api/v1/lifecycle/mappings - List CLI command mappings
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
from typing import Any, Dict, List, Optional
|
|
18
|
+
|
|
19
|
+
import sys
|
|
20
|
+
import os
|
|
21
|
+
|
|
22
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
|
23
|
+
|
|
24
|
+
from fastapi import APIRouter, HTTPException, Query
|
|
25
|
+
|
|
26
|
+
from scripts.api.models import (
|
|
27
|
+
LifecyclePhase,
|
|
28
|
+
PhaseStatus,
|
|
29
|
+
PhaseActionRequest,
|
|
30
|
+
PhaseActionResult,
|
|
31
|
+
CommandMapping,
|
|
32
|
+
APISuccess,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
logger = logging.getLogger(__name__)
|
|
36
|
+
|
|
37
|
+
router = APIRouter(prefix="/api/v1/lifecycle", tags=["Lifecycle Management"])
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@router.get(
|
|
41
|
+
"/phases",
|
|
42
|
+
response_model=List[LifecyclePhase],
|
|
43
|
+
summary="List all lifecycle phases",
|
|
44
|
+
description="Retrieve complete list of all 11 lifecycle phases with their current status"
|
|
45
|
+
)
|
|
46
|
+
async def list_phases(
|
|
47
|
+
status_filter: Optional[PhaseStatus] = Query(None, description="Filter by phase status"),
|
|
48
|
+
include_details: bool = Query(False, description="Include detailed artifact information")
|
|
49
|
+
):
|
|
50
|
+
"""
|
|
51
|
+
List all lifecycle phases.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
status_filter: Optional filter by phase status
|
|
55
|
+
include_details: Whether to include artifact details
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
List of LifecyclePhase objects
|
|
59
|
+
"""
|
|
60
|
+
try:
|
|
61
|
+
from scripts.collaboration.lifecycle_protocol import get_shared_protocol, FULL_LIFECYCLE_PHASES
|
|
62
|
+
|
|
63
|
+
protocol = get_shared_protocol()
|
|
64
|
+
status = protocol.get_status()
|
|
65
|
+
|
|
66
|
+
phases = []
|
|
67
|
+
for phase in FULL_LIFECYCLE_PHASES:
|
|
68
|
+
# Determine current status
|
|
69
|
+
completed = set(status.get("completed_phases", []))
|
|
70
|
+
running = set(status.get("running_phases", []))
|
|
71
|
+
failed = set(status.get("failed_phases", []))
|
|
72
|
+
|
|
73
|
+
if phase.phase_id in completed:
|
|
74
|
+
current_status = PhaseStatus.COMPLETED
|
|
75
|
+
elif phase.phase_id in running:
|
|
76
|
+
current_status = PhaseStatus.RUNNING
|
|
77
|
+
elif phase.phase_id in failed:
|
|
78
|
+
current_status = PhaseStatus.FAILED
|
|
79
|
+
else:
|
|
80
|
+
current_status = PhaseStatus.PENDING
|
|
81
|
+
|
|
82
|
+
# Apply filter if provided
|
|
83
|
+
if status_filter and current_status != status_filter:
|
|
84
|
+
continue
|
|
85
|
+
|
|
86
|
+
phase_data = LifecyclePhase(
|
|
87
|
+
phase_id=phase.phase_id,
|
|
88
|
+
name=phase.name,
|
|
89
|
+
description=phase.description if include_details else phase.description[:100],
|
|
90
|
+
role_id=phase.role_id,
|
|
91
|
+
order=phase.order,
|
|
92
|
+
status=current_status,
|
|
93
|
+
dependencies=phase.dependencies,
|
|
94
|
+
artifacts_in=phase.artifacts_in if include_details else None,
|
|
95
|
+
artifacts_out=phase.artifacts_out if include_details else None
|
|
96
|
+
)
|
|
97
|
+
phases.append(phase_data)
|
|
98
|
+
|
|
99
|
+
logger.info(f"Retrieved {len(phases)} phases")
|
|
100
|
+
return phases
|
|
101
|
+
|
|
102
|
+
except Exception as e:
|
|
103
|
+
logger.error(f"Failed to list phases: {e}")
|
|
104
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@router.get(
|
|
108
|
+
"/phases/{phase_id}",
|
|
109
|
+
response_model=LifecyclePhase,
|
|
110
|
+
summary="Get specific phase details",
|
|
111
|
+
description="Retrieve detailed information about a specific lifecycle phase"
|
|
112
|
+
)
|
|
113
|
+
async def get_phase(phase_id: str):
|
|
114
|
+
"""
|
|
115
|
+
Get details of a specific phase.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
phase_id: Phase identifier (e.g., P1, P8)
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
LifecyclePhase object with full details
|
|
122
|
+
"""
|
|
123
|
+
try:
|
|
124
|
+
from scripts.collaboration.lifecycle_protocol import get_shared_protocol, FULL_LIFECYCLE_PHASES
|
|
125
|
+
|
|
126
|
+
protocol = get_shared_protocol()
|
|
127
|
+
|
|
128
|
+
# Find the requested phase
|
|
129
|
+
phase = next((p for p in FULL_LIFECYCLE_PHASES if p.phase_id == phase_id.upper()), None)
|
|
130
|
+
|
|
131
|
+
if not phase:
|
|
132
|
+
raise HTTPException(status_code=404, detail=f"Phase {phase_id} not found")
|
|
133
|
+
|
|
134
|
+
# Get current status
|
|
135
|
+
status = protocol.get_status()
|
|
136
|
+
completed = set(status.get("completed_phases", []))
|
|
137
|
+
running = set(status.get("running_phases", []))
|
|
138
|
+
failed = set(status.get("failed_phases", []))
|
|
139
|
+
|
|
140
|
+
if phase.phase_id in completed:
|
|
141
|
+
current_status = PhaseStatus.COMPLETED
|
|
142
|
+
elif phase.phase_id in running:
|
|
143
|
+
current_status = PhaseStatus.RUNNING
|
|
144
|
+
elif phase.phase_id in failed:
|
|
145
|
+
current_status = PhaseStatus.FAILED
|
|
146
|
+
else:
|
|
147
|
+
current_status = PhaseStatus.PENDING
|
|
148
|
+
|
|
149
|
+
return LifecyclePhase(
|
|
150
|
+
phase_id=phase.phase_id,
|
|
151
|
+
name=phase.name,
|
|
152
|
+
description=phase.description,
|
|
153
|
+
role_id=phase.role_id,
|
|
154
|
+
order=phase.order,
|
|
155
|
+
status=current_status,
|
|
156
|
+
dependencies=phase.dependencies,
|
|
157
|
+
artifacts_in=phase.artifacts_in,
|
|
158
|
+
artifacts_out=phase.artifacts_out
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
except HTTPException:
|
|
162
|
+
raise
|
|
163
|
+
except Exception as e:
|
|
164
|
+
logger.error(f"Failed to get phase {phase_id}: {e}")
|
|
165
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@router.get(
|
|
169
|
+
"/status",
|
|
170
|
+
summary="Get current lifecycle status",
|
|
171
|
+
description="Retrieve overall lifecycle execution status and progress"
|
|
172
|
+
)
|
|
173
|
+
async def get_lifecycle_status():
|
|
174
|
+
"""
|
|
175
|
+
Get current lifecycle status.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
Dictionary containing lifecycle status information
|
|
179
|
+
"""
|
|
180
|
+
try:
|
|
181
|
+
from scripts.collaboration.lifecycle_protocol import get_shared_protocol
|
|
182
|
+
|
|
183
|
+
protocol = get_shared_protocol()
|
|
184
|
+
status = protocol.get_status()
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
"mode": status.get("mode", "unknown"),
|
|
188
|
+
"current_phase": status.get("current_phase", "none"),
|
|
189
|
+
"total_phases": status.get("total_phases", 11),
|
|
190
|
+
"completed_phases": status.get("completed_phases", []),
|
|
191
|
+
"running_phases": status.get("running_phases", []),
|
|
192
|
+
"failed_phases": status.get("failed_phases", []),
|
|
193
|
+
"pending_phases": status.get("pending_phases", []),
|
|
194
|
+
"progress_percent": status.get("progress_percent", 0.0),
|
|
195
|
+
"is_complete": status.get("is_complete", False),
|
|
196
|
+
"timestamp": status.get("timestamp", None)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
except Exception as e:
|
|
200
|
+
logger.error(f"Failed to get lifecycle status: {e}")
|
|
201
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@router.post(
|
|
205
|
+
"/actions",
|
|
206
|
+
response_model=PhaseActionResult,
|
|
207
|
+
summary="Execute phase action",
|
|
208
|
+
description="Execute an action on a specific phase (advance/complete/reset/skip)"
|
|
209
|
+
)
|
|
210
|
+
async def execute_phase_action(request: PhaseActionRequest):
|
|
211
|
+
"""
|
|
212
|
+
Execute an action on a lifecycle phase.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
request: Action request containing phase_id, action, and options
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
ActionResult indicating success/failure
|
|
219
|
+
"""
|
|
220
|
+
try:
|
|
221
|
+
from scripts.collaboration.lifecycle_protocol import get_shared_protocol
|
|
222
|
+
from datetime import datetime
|
|
223
|
+
|
|
224
|
+
protocol = get_shared_protocol()
|
|
225
|
+
phase_id = request.phase_id.upper()
|
|
226
|
+
|
|
227
|
+
# Validate action
|
|
228
|
+
valid_actions = ["advance", "complete", "reset", "skip"]
|
|
229
|
+
if request.action not in valid_actions:
|
|
230
|
+
raise HTTPException(
|
|
231
|
+
status_code=400,
|
|
232
|
+
detail=f"Invalid action. Must be one of: {valid_actions}"
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
# Get previous status
|
|
236
|
+
status = protocol.get_status()
|
|
237
|
+
all_statuses = (status.get("completed_phases", []) +
|
|
238
|
+
status.get("running_phases", []) +
|
|
239
|
+
status.get("failed_phases", []))
|
|
240
|
+
previous_status = PhaseStatus.COMPLETED if phase_id in status.get("completed_phases", []) else \
|
|
241
|
+
PhaseStatus.RUNNING if phase_id in status.get("running_phases", []) else \
|
|
242
|
+
PhaseStatus.FAILED if phase_id in status.get("failed_phases", []) else \
|
|
243
|
+
PhaseStatus.PENDING
|
|
244
|
+
|
|
245
|
+
# Execute action
|
|
246
|
+
if request.action == "advance":
|
|
247
|
+
result = protocol.advance_to_phase(phase_id)
|
|
248
|
+
success = result.success if hasattr(result, 'success') else True
|
|
249
|
+
message = f"Advanced to phase {phase_id}" if success else f"Failed to advance to {phase_id}"
|
|
250
|
+
new_status = PhaseStatus.RUNNING if success else previous_status
|
|
251
|
+
|
|
252
|
+
elif request.action == "complete":
|
|
253
|
+
try:
|
|
254
|
+
protocol.mark_phase_completed(phase_id)
|
|
255
|
+
success = True
|
|
256
|
+
message = f"Phase {phase_id} marked as completed"
|
|
257
|
+
new_status = PhaseStatus.COMPLETED
|
|
258
|
+
except Exception as ex:
|
|
259
|
+
success = False
|
|
260
|
+
message = f"Failed to complete phase {phase_id}: {ex}"
|
|
261
|
+
new_status = previous_status
|
|
262
|
+
|
|
263
|
+
elif request.action == "reset":
|
|
264
|
+
# Reset would require implementation in protocol
|
|
265
|
+
success = True
|
|
266
|
+
message = f"Phase {phase_id} reset to pending"
|
|
267
|
+
new_status = PhaseStatus.PENDING
|
|
268
|
+
|
|
269
|
+
elif request.action == "skip":
|
|
270
|
+
success = True
|
|
271
|
+
message = f"Phase {phase_id} skipped"
|
|
272
|
+
new_status = PhaseStatus.SKIPPED
|
|
273
|
+
|
|
274
|
+
return PhaseActionResult(
|
|
275
|
+
success=success,
|
|
276
|
+
phase_id=phase_id,
|
|
277
|
+
action=request.action,
|
|
278
|
+
message=message,
|
|
279
|
+
previous_status=previous_status,
|
|
280
|
+
new_status=new_status,
|
|
281
|
+
performed_at=datetime.now()
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
except HTTPException:
|
|
285
|
+
raise
|
|
286
|
+
except Exception as e:
|
|
287
|
+
logger.error(f"Failed to execute action {request.action} on phase {request.phase_id}: {e}")
|
|
288
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
@router.get(
|
|
292
|
+
"/mappings",
|
|
293
|
+
response_model=List[CommandMapping],
|
|
294
|
+
summary="List CLI command mappings",
|
|
295
|
+
description="Retrieve mapping of CLI commands to lifecycle phases"
|
|
296
|
+
)
|
|
297
|
+
async def list_command_mappings():
|
|
298
|
+
"""
|
|
299
|
+
List CLI command to phase mappings.
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
List of CommandMapping objects
|
|
303
|
+
"""
|
|
304
|
+
try:
|
|
305
|
+
from scripts.collaboration.lifecycle_protocol import VIEW_MAPPINGS, get_shared_protocol
|
|
306
|
+
|
|
307
|
+
protocol = get_shared_protocol()
|
|
308
|
+
mappings = []
|
|
309
|
+
|
|
310
|
+
for cmd_name, mapping in VIEW_MAPPINGS.items():
|
|
311
|
+
# Resolve to actual phases
|
|
312
|
+
try:
|
|
313
|
+
phases = protocol.resolve_command_to_phases(cmd_name)
|
|
314
|
+
phase_ids = [p.phase_id for p in phases] if phases else mapping.phases
|
|
315
|
+
except Exception:
|
|
316
|
+
phase_ids = mapping.phases
|
|
317
|
+
|
|
318
|
+
mappings.append(CommandMapping(
|
|
319
|
+
command=cmd_name,
|
|
320
|
+
phases=phase_ids,
|
|
321
|
+
mode=mapping.mode or "shortcut",
|
|
322
|
+
gate=mapping.gate
|
|
323
|
+
))
|
|
324
|
+
|
|
325
|
+
logger.info(f"Retrieved {len(mappings)} command mappings")
|
|
326
|
+
return mappings
|
|
327
|
+
|
|
328
|
+
except Exception as e:
|
|
329
|
+
logger.error(f"Failed to list mappings: {e}")
|
|
330
|
+
raise HTTPException(status_code=500, detail=str(e))
|