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,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))