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
scripts/dashboard.py ADDED
@@ -0,0 +1,1088 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ DevSquad V3.6.0-C Dashboard (Streamlit Production-Grade)
5
+
6
+ Interactive web dashboard for lifecycle visualization, monitoring,
7
+ and task dispatch.
8
+
9
+ Features:
10
+ - Real-time lifecycle phase status
11
+ - CLI command mapping visualization
12
+ - Gate status monitoring
13
+ - Performance metrics (real API data)
14
+ - Task dispatch interface
15
+ - Admin pages (user management & system config)
16
+ - Auto-refresh with countdown timer
17
+
18
+ Usage:
19
+ streamlit run scripts/dashboard.py
20
+
21
+ Requirements:
22
+ streamlit>=1.28.0
23
+ """
24
+
25
+ import sys
26
+ import os
27
+ import time
28
+ import requests
29
+ from datetime import datetime
30
+ from typing import Any, Dict, List, Optional, Tuple
31
+
32
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
33
+
34
+ import streamlit as st
35
+
36
+ from scripts.auth import AuthManager, User, UserRole
37
+
38
+
39
+ class DashboardConfig:
40
+ """Dashboard configuration and styling."""
41
+
42
+ PAGE_TITLE = "🔄 DevSquad Lifecycle Dashboard"
43
+ PAGE_ICON = "🚀"
44
+ LAYOUT = "wide"
45
+
46
+ COLOR_SCHEME = {
47
+ "primary": "#4A90D9",
48
+ "success": "#2ca02c",
49
+ "warning": "#ff7f0e",
50
+ "danger": "#d62728",
51
+ "info": "#17becf",
52
+ "background": "#ffffff",
53
+ "text": "#333333",
54
+ }
55
+
56
+ PHASE_COLORS = {
57
+ "pending": "#95a5a6",
58
+ "running": "#3498db",
59
+ "completed": "#27ae60",
60
+ "failed": "#e74c3c",
61
+ "skipped": "#f39c12",
62
+ "blocked": "#c0392b",
63
+ }
64
+
65
+ CORE_ROLES = [
66
+ "architect",
67
+ "product-manager",
68
+ "security",
69
+ "tester",
70
+ "solo-coder",
71
+ "devops",
72
+ "ui-designer",
73
+ ]
74
+
75
+ ROLE_ICONS = {
76
+ "architect": "🏗️",
77
+ "product-manager": "📋",
78
+ "security": "🔒",
79
+ "tester": "🧪",
80
+ "solo-coder": "💻",
81
+ "devops": "⚙️",
82
+ "ui-designer": "🎨",
83
+ }
84
+
85
+ ROLE_NAMES = {
86
+ "architect": "架构师",
87
+ "product-manager": "产品经理",
88
+ "security": "安全专家",
89
+ "tester": "测试专家",
90
+ "solo-coder": "开发者",
91
+ "devops": "运维工程师",
92
+ "ui-designer": "UI设计师",
93
+ }
94
+
95
+
96
+ @st.cache_resource
97
+ def load_lifecycle_protocol():
98
+ """Load and cache the lifecycle protocol."""
99
+ try:
100
+ from scripts.collaboration.lifecycle_protocol import (
101
+ get_shared_protocol,
102
+ VIEW_MAPPINGS,
103
+ FULL_LIFECYCLE_PHASES,
104
+ )
105
+ return {
106
+ "protocol": get_shared_protocol(),
107
+ "mappings": VIEW_MAPPINGS,
108
+ "phases": FULL_LIFECYCLE_PHASES,
109
+ }
110
+ except Exception as e:
111
+ st.error(f"Failed to load lifecycle protocol: {e}")
112
+ return None
113
+
114
+
115
+ @st.cache_resource
116
+ def get_dispatcher():
117
+ """Initialize and cache the MultiAgentDispatcher."""
118
+ try:
119
+ from scripts.collaboration.dispatcher import MultiAgentDispatcher
120
+ return MultiAgentDispatcher(
121
+ enable_warmup=True,
122
+ enable_compression=True,
123
+ enable_permission=True,
124
+ enable_memory=True,
125
+ enable_skillify=True,
126
+ lang="auto",
127
+ )
128
+ except Exception as e:
129
+ st.error(f"Failed to initialize dispatcher: {e}")
130
+ return None
131
+
132
+
133
+ def set_page_config():
134
+ """Configure Streamlit page settings."""
135
+ st.set_page_config(
136
+ page_title=DashboardConfig.PAGE_TITLE,
137
+ page_icon=DashboardConfig.PAGE_ICON,
138
+ layout=DashboardConfig.LAYOUT,
139
+ initial_sidebar_state="expanded",
140
+ )
141
+
142
+
143
+ def apply_custom_css():
144
+ """Apply custom CSS for production-grade visual appearance."""
145
+ st.markdown("""
146
+ <style>
147
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
148
+
149
+ * {
150
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
151
+ }
152
+
153
+ .main-header {
154
+ font-size: 2.5rem;
155
+ font-weight: 700;
156
+ color: #4A90D9;
157
+ text-align: center;
158
+ padding: 1rem 0;
159
+ background: linear-gradient(135deg, #4A90D9 0%, #357ABD 100%);
160
+ -webkit-background-clip: text;
161
+ -webkit-text-fill-color: transparent;
162
+ background-clip: text;
163
+ }
164
+
165
+ .metric-card {
166
+ background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
167
+ border-radius: 12px;
168
+ padding: 1.5rem;
169
+ margin: 0.5rem 0;
170
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.07);
171
+ border: 1px solid #e9ecef;
172
+ transition: all 0.3s ease;
173
+ }
174
+
175
+ .metric-card:hover {
176
+ transform: translateY(-2px);
177
+ box-shadow: 0 8px 12px rgba(0, 0, 0, 0.12);
178
+ }
179
+
180
+ .phase-card {
181
+ background-color: white;
182
+ border-radius: 10px;
183
+ padding: 1.25rem;
184
+ margin: 0.5rem 0;
185
+ border-left: 4px solid #4A90D9;
186
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.06);
187
+ transition: all 0.3s ease;
188
+ }
189
+
190
+ .phase-card:hover {
191
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
192
+ }
193
+
194
+ .status-badge {
195
+ display: inline-block;
196
+ padding: 0.35rem 0.85rem;
197
+ border-radius: 9999px;
198
+ font-size: 0.875rem;
199
+ font-weight: 600;
200
+ letter-spacing: 0.02em;
201
+ }
202
+
203
+ .status-success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
204
+ .status-warning { background-color: #fff3cd; color: #856404; border: 1px solid #ffeaa7; }
205
+ .status-danger { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
206
+ .status-info { background-color: #d1ecf1; color: #0c5460; border: 1px solid #bee5eb; }
207
+ .status-secondary { background-color: #e2e3e5; color: #383d41; border: 1px solid #d6d8db; }
208
+
209
+ .task-input textarea {
210
+ border: 2px solid #4A90D9 !important;
211
+ border-radius: 8px !important;
212
+ font-family: 'Monaco', 'Menlo', monospace !important;
213
+ font-size: 14px !important;
214
+ }
215
+
216
+ .primary-btn {
217
+ background: linear-gradient(135deg, #4A90D9 0%, #357ABD 100%) !important;
218
+ color: white !important;
219
+ border: none !important;
220
+ border-radius: 8px !important;
221
+ padding: 0.65rem 1.5rem !important;
222
+ font-weight: 600 !important;
223
+ transition: all 0.3s ease !important;
224
+ }
225
+
226
+ .primary-btn:hover {
227
+ transform: translateY(-1px) !important;
228
+ box-shadow: 0 4px 12px rgba(74, 144, 217, 0.35) !important;
229
+ }
230
+
231
+ [data-testid="stMetric"] {
232
+ background-color: white;
233
+ padding: 1rem;
234
+ border-radius: 10px;
235
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.06);
236
+ }
237
+
238
+ .loading-spinner {
239
+ display: inline-block;
240
+ width: 20px;
241
+ height: 20px;
242
+ border: 3px solid rgba(74, 144, 217, 0.3);
243
+ border-top-color: #4A90D9;
244
+ border-radius: 50%;
245
+ animation: spin 1s linear infinite;
246
+ }
247
+
248
+ @keyframes spin {
249
+ to { transform: rotate(360deg); }
250
+ }
251
+
252
+ .countdown-timer {
253
+ font-size: 0.9rem;
254
+ color: #666;
255
+ font-weight: 500;
256
+ padding: 0.5rem 1rem;
257
+ background: #f8f9fa;
258
+ border-radius: 20px;
259
+ display: inline-block;
260
+ }
261
+
262
+ div[data-testid="stExpander"] {
263
+ border: 1px solid #dee2e6;
264
+ border-radius: 8px;
265
+ }
266
+
267
+ h2, h3 {
268
+ color: #2c3e50 !important;
269
+ font-weight: 600 !important;
270
+ }
271
+ </style>
272
+ """, unsafe_allow_html=True)
273
+
274
+
275
+ def render_header(current_user: Optional[User] = None):
276
+ """Render the main dashboard header with enhanced styling."""
277
+ st.markdown('<div class="main-header">🚀 DevSquad Lifecycle Dashboard</div>', unsafe_allow_html=True)
278
+
279
+ col1, col2, col3, col4 = st.columns([2, 1, 1, 1])
280
+ with col1:
281
+ st.markdown("**Plan C Architecture** | CLI View Layer over 11-Phase Lifecycle")
282
+ if current_user:
283
+ role_badge_class = "status-info" if current_user.role == UserRole.ADMIN else "status-success"
284
+ st.markdown(
285
+ f'<span class="status-badge {role_badge_class}">👤 {current_user.name} ({current_user.role.value})</span>',
286
+ unsafe_allow_html=True
287
+ )
288
+ with col2:
289
+ st.markdown(f"**Last Updated:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
290
+ with col3:
291
+ if st.button("🔄 Refresh", key="refresh_header"):
292
+ st.rerun()
293
+ with col4:
294
+ if st.session_state.get('auto_refresh', False):
295
+ remaining = st.session_state.get('refresh_countdown', 30)
296
+ st.markdown(
297
+ f'<div class="countdown-timer">⏱️ Auto-refresh in {remaining}s</div>',
298
+ unsafe_allow_html=True
299
+ )
300
+
301
+
302
+ def render_metrics_overview(protocol_data):
303
+ """Render key metrics overview cards with enhanced styling."""
304
+ if not protocol_data:
305
+ return
306
+
307
+ protocol = protocol_data["protocol"]
308
+ phases = protocol_data["phases"]
309
+
310
+ try:
311
+ status = protocol.get_status()
312
+
313
+ total_phases = len(phases)
314
+ completed = len(status.get("completed_phases", []))
315
+ running = len(status.get("running_phases", []))
316
+ failed = len(status.get("failed_phases", []))
317
+
318
+ completion_rate = (completed / total_phases * 100) if total_phases > 0 else 0
319
+
320
+ col1, col2, col3, col4, col5 = st.columns(5)
321
+
322
+ with col1:
323
+ st.metric(
324
+ label="Total Phases",
325
+ value=f"{total_phases}",
326
+ delta=None,
327
+ help="Total number of lifecycle phases"
328
+ )
329
+
330
+ with col2:
331
+ delta_color = "normal" if completion_rate >= 50 else "inverse"
332
+ st.metric(
333
+ label="Completed",
334
+ value=f"{completed}",
335
+ delta=f"{completion_rate:.1f}%",
336
+ delta_color=delta_color,
337
+ help="Phases successfully completed"
338
+ )
339
+
340
+ with col3:
341
+ st.metric(
342
+ label="Running",
343
+ value=f"{running}",
344
+ delta_color="off",
345
+ help="Phases currently executing"
346
+ )
347
+
348
+ with col4:
349
+ st.metric(
350
+ label="Failed",
351
+ value=f"{failed}",
352
+ delta_color="inverse" if failed > 0 else "normal",
353
+ help="Phases that failed execution"
354
+ )
355
+
356
+ with col5:
357
+ progress_color = "#27ae60" if completion_rate >= 70 else "#ff7f0e" if completion_rate >= 40 else "#d62728"
358
+ st.metric(
359
+ label="Progress",
360
+ value=f"{completion_rate:.1f}%",
361
+ delta=None,
362
+ help="Overall project progress",
363
+ delta_color="normal"
364
+ )
365
+
366
+ except Exception as e:
367
+ st.warning(f"Could not load metrics: {e}")
368
+
369
+
370
+ def render_phase_timeline(protocol_data):
371
+ """Render interactive phase timeline visualization with enhanced cards."""
372
+ if not protocol_data:
373
+ return
374
+
375
+ st.subheader("📋 Phase Timeline")
376
+
377
+ phases = protocol_data["phases"]
378
+ protocol = protocol_data["protocol"]
379
+
380
+ try:
381
+ status = protocol.get_status()
382
+ completed_phases = set(status.get("completed_phases", []))
383
+ running_phases = set(status.get("running_phases", []))
384
+ failed_phases = set(status.get("failed_phases", []))
385
+
386
+ for idx, phase in enumerate(phases, 1):
387
+ phase_id = phase.phase_id
388
+
389
+ if phase_id in completed_phases:
390
+ status_icon = "✅"
391
+ status_text = "Completed"
392
+ badge_class = "status-success"
393
+ elif phase_id in running_phases:
394
+ status_icon = "🔄"
395
+ status_text = "Running"
396
+ badge_class = "status-info"
397
+ elif phase_id in failed_phases:
398
+ status_icon = "❌"
399
+ status_text = "Failed"
400
+ badge_class = "status-danger"
401
+ else:
402
+ status_icon = "⏳"
403
+ status_text = "Pending"
404
+ badge_class = "status-secondary"
405
+
406
+ with st.container():
407
+ st.markdown('<div class="phase-card">', unsafe_allow_html=True)
408
+ col1, col2, col3, col4 = st.columns([1, 3, 2, 2])
409
+
410
+ with col1:
411
+ st.markdown(f"**P{idx:02d}**")
412
+
413
+ with col2:
414
+ st.markdown(f"**{phase.name}**")
415
+ desc = phase.description[:80] + ("..." if len(phase.description) > 80 else "")
416
+ st.caption(desc)
417
+
418
+ with col3:
419
+ st.code(phase.role_id)
420
+
421
+ with col4:
422
+ st.markdown(
423
+ f'<span class="status-badge {badge_class}">{status_icon} {status_text}</span>',
424
+ unsafe_allow_html=True
425
+ )
426
+
427
+ st.markdown('</div>', unsafe_allow_html=True)
428
+ st.divider()
429
+
430
+ except Exception as e:
431
+ st.error(f"Error rendering timeline: {e}")
432
+
433
+
434
+ def render_cli_mapping_table(protocol_data):
435
+ """Render CLI command to phase mapping table."""
436
+ if not protocol_data:
437
+ return
438
+
439
+ st.subheader("🔗 CLI Command Mapping (Plan C View Layer)")
440
+
441
+ mappings = protocol_data["mappings"]
442
+ protocol = protocol_data["protocol"]
443
+
444
+ mapping_data = []
445
+ for cmd_name, mapping in mappings.items():
446
+ try:
447
+ resolved_phases = protocol.resolve_command_to_phases(cmd_name)
448
+ phase_ids = [p.phase_id for p in resolved_phases] if resolved_phases else mapping.phases
449
+
450
+ mapping_data.append({
451
+ "Command": cmd_name.upper(),
452
+ "Phases": ", ".join(phase_ids),
453
+ "Phase Count": len(phase_ids),
454
+ "Mode": mapping.mode or "N/A",
455
+ "Gate": mapping.gate or "None",
456
+ })
457
+ except Exception:
458
+ mapping_data.append({
459
+ "Command": cmd_name.upper(),
460
+ "Phases": ", ".join(mapping.phases),
461
+ "Phase Count": len(mapping.phases),
462
+ "Mode": mapping.mode or "N/A",
463
+ "Gate": mapping.gate or "None",
464
+ })
465
+
466
+ if mapping_data:
467
+ st.dataframe(
468
+ mapping_data,
469
+ use_container_width=True,
470
+ hide_index=True,
471
+ column_config={
472
+ "Command": st.column_config.TextColumn("Command", width="medium"),
473
+ "Phases": st.column_config.TextColumn("Mapped Phases", width="large"),
474
+ "Phase Count": st.column_config.NumberColumn("Count", width="small"),
475
+ "Mode": st.column_config.TextColumn("Mode", width="small"),
476
+ "Gate": st.column_config.TextColumn("Gate", width="medium"),
477
+ }
478
+ )
479
+
480
+
481
+ def render_gate_status_panel(protocol_data):
482
+ """Render gate status monitoring panel."""
483
+ if not protocol_data:
484
+ return
485
+
486
+ st.subheader("🚧 Gate Status Monitor")
487
+
488
+ protocol = protocol_data["protocol"]
489
+
490
+ try:
491
+ gate_results = {}
492
+
493
+ test_commands = ["spec", "plan", "build", "test", "review", "ship"]
494
+ for cmd in test_commands:
495
+ try:
496
+ result = protocol.check_command_gate(cmd)
497
+ gate_results[cmd] = result
498
+ except Exception:
499
+ pass
500
+
501
+ if gate_results:
502
+ for cmd, result in gate_results.items():
503
+ passed = getattr(result, 'passed', False)
504
+ verdict = getattr(result, 'verdict', 'UNKNOWN')
505
+
506
+ col1, col2, col3 = st.columns([2, 2, 3])
507
+
508
+ with col1:
509
+ st.markdown(f"**{cmd.upper()}**")
510
+
511
+ with col2:
512
+ if passed:
513
+ st.success(f"✅ {verdict}")
514
+ else:
515
+ st.error(f"❌ {verdict}")
516
+
517
+ with col3:
518
+ red_flags = getattr(result, 'red_flags', [])
519
+ missing = getattr(result, 'missing_evidence', [])
520
+
521
+ flags_text = f"🚩 {len(red_flags)} flags" if red_flags else "No flags"
522
+ missing_text = f"📋 {len(missing)} missing" if missing else "Complete"
523
+
524
+ st.markdown(f"{flags_text} | {missing_text}")
525
+
526
+ st.divider()
527
+
528
+ except Exception as e:
529
+ st.warning(f"Could not load gate status: {e}")
530
+
531
+
532
+ def fetch_api_metrics():
533
+ """
534
+ Fetch real metrics from API server.
535
+ Returns dict if successful, None if API unreachable.
536
+ """
537
+ try:
538
+ response = requests.get(
539
+ "http://localhost:8000/api/v1/metrics/current",
540
+ timeout=3
541
+ )
542
+ if response.status_code == 200:
543
+ return response.json()
544
+ return None
545
+ except (requests.ConnectionError, requests.Timeout):
546
+ return None
547
+ except Exception:
548
+ return None
549
+
550
+
551
+ def render_performance_panel():
552
+ """Render performance metrics panel with real API data."""
553
+ st.subheader("📊 System Performance")
554
+
555
+ with st.spinner("Connecting to API Server..."):
556
+ metrics_data = fetch_api_metrics()
557
+
558
+ if metrics_data:
559
+ st.success("✅ Connected to API Server")
560
+
561
+ col1, col2 = st.columns(2)
562
+
563
+ with col1:
564
+ st.markdown("**Response Time Metrics**")
565
+ real_metrics = [
566
+ {"Metric": "Avg Response Time", "Value": f"{metrics_data.get('avg_response_time_ms', 'N/A')}ms"},
567
+ {"Metric": "P95 Latency", "Value": f"{metrics_data.get('p95_latency_ms', 'N/A')}ms"},
568
+ {"Metric": "Success Rate", "Value": f"{metrics_data.get('success_rate', 'N/A')}%"},
569
+ {"Metric": "Throughput", "Value": f"{metrics_data.get('throughput', 'N/A')} req/s"},
570
+ ]
571
+ st.dataframe(real_metrics, use_container_width=True, hide_index=True)
572
+
573
+ with col2:
574
+ st.markdown("**Resource Utilization**")
575
+ cpu_usage = metrics_data.get('cpu_usage_percent', 0)
576
+ mem_usage = metrics_data.get('memory_usage_percent', 0)
577
+
578
+ st.progress(cpu_usage / 100 if cpu_usage > 0 else 0, text=f"CPU Usage: {cpu_usage:.1f}%")
579
+ st.progress(mem_usage / 100 if mem_usage > 0 else 0, text=f"Memory Usage: {mem_usage:.1f}%")
580
+
581
+ st.caption("*Real-time data from API Server*")
582
+
583
+ col3, col4 = st.columns(2)
584
+ with col3:
585
+ st.metric(
586
+ label="Completion Rate",
587
+ value=f"{metrics_data.get('completion_rate', 0)}%",
588
+ help="Lifecycle phase completion rate"
589
+ )
590
+ with col4:
591
+ st.metric(
592
+ label="Active Phases",
593
+ value=metrics_data.get('running_phases', 0),
594
+ help="Currently running phases"
595
+ )
596
+ else:
597
+ st.error("❌ **API Server 未运行**")
598
+ st.info("""
599
+ **无法连接到 localhost:8000**
600
+
601
+ 请确保 DevSquad API Server 正在运行:
602
+ ```bash
603
+ python scripts/api_server.py
604
+ ```
605
+
606
+ 或者使用以下命令启动:
607
+ ```bash
608
+ uvicorn scripts.api_server:app --host 0.0.0.0 --port 8000
609
+ ```
610
+ """)
611
+
612
+ st.markdown("**模拟数据预览(仅用于演示)**")
613
+ with st.expander("查看示例数据格式"):
614
+ sample_data = [
615
+ {"Metric": "Avg Response Time", "Value": "~250ms (expected)"},
616
+ {"Metric": "P95 Latency", "Value": "~1200ms (expected)"},
617
+ {"Metric": "Success Rate", "Value": "~98.5% (expected)"},
618
+ {"Metric": "Throughput", "Value": "~120 req/s (expected)"},
619
+ ]
620
+ st.dataframe(sample_data, use_container_width=True, hide_index=True)
621
+
622
+
623
+ def render_task_dispatch_page(dispatcher):
624
+ """Render the Task Dispatch page with full functionality."""
625
+ st.header("🎯 Task Dispatch")
626
+ st.markdown("---")
627
+
628
+ col_input, col_config = st.columns([3, 2])
629
+
630
+ with col_input:
631
+ st.markdown("**Task Description**")
632
+ task_description = st.text_area(
633
+ "Enter your task description...",
634
+ height=150,
635
+ placeholder="Example: Design a RESTful API for user authentication with JWT tokens...",
636
+ help="Describe the task you want the multi-agent team to work on"
637
+ )
638
+
639
+ with col_config:
640
+ st.markdown("**Configuration**")
641
+
642
+ selected_roles = st.multiselect(
643
+ "Select Roles (leave empty for auto-match)",
644
+ options=DashboardConfig.CORE_ROLES,
645
+ format_func=lambda x: f"{DashboardConfig.ROLE_ICONS.get(x, '🤖')} {DashboardConfig.ROLE_NAMES.get(x, x)}",
646
+ default=[],
647
+ help="Choose specific roles or let AI auto-match based on task"
648
+ )
649
+
650
+ execution_mode = st.selectbox(
651
+ "Execution Mode",
652
+ options=["auto", "parallel", "sequential", "consensus"],
653
+ format_func=lambda x: {
654
+ "auto": "🤖 Auto (Recommended)",
655
+ "parallel": "⚡ Parallel (Fastest)",
656
+ "sequential": "📋 Sequential (Ordered)",
657
+ "consensus": "🗳️ Consensus (Thorough)"
658
+ }.get(x, x),
659
+ index=0,
660
+ help="How agents should collaborate on this task"
661
+ )
662
+
663
+ language = st.selectbox(
664
+ "Output Language",
665
+ options=["auto", "zh", "en", "ja"],
666
+ format_func=lambda x: {
667
+ "auto": "🌐 Auto-detect",
668
+ "zh": "🇨🇳 中文",
669
+ "en": "🇺🇸 English",
670
+ "ja": "🇯🇵 日本語"
671
+ }.get(x, x),
672
+ index=0,
673
+ help="Language for output and reports"
674
+ )
675
+
676
+ backend = st.selectbox(
677
+ "LLM Backend",
678
+ options=["mock", "openai", "anthropic"],
679
+ format_func=lambda x: {
680
+ "mock": "🎭 Mock (Demo)",
681
+ "openai": "🟢 OpenAI GPT",
682
+ "anthropic": "🟣 Anthropic Claude"
683
+ }.get(x, x),
684
+ index=0,
685
+ help="AI backend to use for agent responses"
686
+ )
687
+
688
+ st.markdown("---")
689
+
690
+ col_submit, col_clear = st.columns([1, 4])
691
+
692
+ with col_submit:
693
+ submit_disabled = not task_description.strip()
694
+ if st.button(
695
+ "🚀 Submit Task",
696
+ type="primary",
697
+ disabled=submit_disabled,
698
+ use_container_width=True,
699
+ help="Dispatch task to multi-agent system"
700
+ ):
701
+ if not dispatcher:
702
+ st.error("Dispatcher not initialized. Please check logs.")
703
+ return
704
+
705
+ with st.spinner("🔄 Dispatching task to multi-agent system..."):
706
+ try:
707
+ start_time = time.time()
708
+
709
+ result = dispatcher.dispatch(
710
+ task_description=task_description.strip(),
711
+ roles=selected_roles if selected_roles else None,
712
+ mode=execution_mode,
713
+ )
714
+
715
+ duration = time.time() - start_time
716
+
717
+ st.session_state['last_dispatch_result'] = result
718
+ st.session_state['dispatch_duration'] = duration
719
+
720
+ st.success(f"✅ Task completed in {duration:.2f}s")
721
+ st.rerun()
722
+
723
+ except Exception as e:
724
+ st.error(f"❌ Dispatch failed: {e}")
725
+ st.exception(e)
726
+
727
+ with col_clear:
728
+ if st.button("🗑️ Clear Results", use_container_width=True):
729
+ if 'last_dispatch_result' in st.session_state:
730
+ del st.session_state['last_dispatch_result']
731
+ if 'dispatch_duration' in st.session_state:
732
+ del st.session_state['dispatch_duration']
733
+ st.rerun()
734
+
735
+ st.markdown("---")
736
+
737
+ if 'last_dispatch_result' in st.session_state:
738
+ result = st.session_state['last_dispatch_result']
739
+ duration = st.session_state.get('dispatch_duration', 0)
740
+
741
+ render_dispatch_result(result, duration)
742
+
743
+
744
+ def render_dispatch_result(result, duration: float):
745
+ """Render dispatch result with metadata and formatted report."""
746
+ st.subheader("📊 Dispatch Result")
747
+
748
+ col_meta, col_timing = st.columns([2, 1])
749
+
750
+ with col_meta:
751
+ st.markdown("**Metadata**")
752
+
753
+ intent_match = result.intent_match
754
+ if intent_match:
755
+ st.markdown(f"- **Intent Type**: `{intent_match.get('intent_type', 'N/A')}`")
756
+ workflow_chain = intent_match.get('workflow_chain', [])
757
+ if workflow_chain:
758
+ st.markdown(f"- **Workflow**: `{' → '.join(workflow_chain[:5])}`")
759
+ st.markdown(f"- **Confidence**: `{intent_match.get('confidence', 0):.1%}`")
760
+
761
+ matched_roles = result.matched_roles
762
+ if matched_roles:
763
+ roles_display = []
764
+ for role_id in matched_roles:
765
+ icon = DashboardConfig.ROLE_ICONS.get(role_id, '🤖')
766
+ name = DashboardConfig.ROLE_NAMES.get(role_id, role_id)
767
+ roles_display.append(f"{icon} {name}")
768
+ st.markdown(f"- **Matched Roles**: {', '.join(roles_display)}")
769
+
770
+ st.markdown(f"- **Status**: {'✅ Success' if result.success else '❌ Failed'}")
771
+ st.markdown(f"- **Duration**: `{duration:.2f}s`")
772
+
773
+ with col_timing:
774
+ st.markdown("**Timing Breakdown**")
775
+ timing = result.details.get('timing', {})
776
+ if timing:
777
+ timing_data = [{"Step": k, "Time (s)": f"{v:.3f}"} for k, v in sorted(timing.items(), key=lambda x: x[1], reverse=True)]
778
+ st.dataframe(timing_data, use_container_width=True, hide_index=True)
779
+
780
+ st.markdown("---")
781
+
782
+ tabs_report, tabs_raw = st.tabs(["📝 Formatted Report", "🔍 Raw Data"])
783
+
784
+ with tabs_report:
785
+ st.markdown(result.summary or result.to_markdown())
786
+
787
+ with tabs_raw:
788
+ st.json(result.to_dict())
789
+
790
+
791
+ def render_admin_users_page(auth: AuthManager):
792
+ """Render Admin User Management page."""
793
+ st.header("👥 User Management")
794
+ st.markdown("---")
795
+
796
+ credentials = auth.credentials
797
+
798
+ if not credentials:
799
+ st.info("No users configured in deployment.yaml")
800
+ return
801
+
802
+ st.markdown(f"**Total Users:** {len(credentials)}")
803
+
804
+ for username, cred in credentials.items():
805
+ with st.expander(f"👤 {username}", expanded=False):
806
+ col1, col2, col3 = st.columns(3)
807
+
808
+ with col1:
809
+ st.markdown(f"**Name:** {cred.get('name', username)}")
810
+ st.markdown(f"**Email:** {cred.get('email', 'N/A')}")
811
+
812
+ with col2:
813
+ role = cred.get('role', 'viewer')
814
+ role_badge = {
815
+ "admin": "status-danger",
816
+ "operator": "status-warning",
817
+ "viewer": "status-secondary"
818
+ }.get(role, "status-secondary")
819
+ st.markdown(
820
+ f'<span class="status-badge {role_badge}">{role.upper()}</span>',
821
+ unsafe_allow_html=True
822
+ )
823
+
824
+ with col3:
825
+ new_role = st.selectbox(
826
+ f"Change Role",
827
+ options=["admin", "operator", "viewer"],
828
+ index=["admin", "operator", "viewer"].index(role) if role in ["admin", "operator", "viewer"] else 2,
829
+ key=f"role_{username}",
830
+ disabled=True,
831
+ help="Role changes require editing config/deployment.yaml"
832
+ )
833
+ st.caption("ℹ️ Edit config file to change roles")
834
+
835
+ st.markdown("---")
836
+ st.info("""
837
+ **User Management Notes:**
838
+ - Users are configured in `config/deployment.yaml`
839
+ - To add/edit users, modify the `authentication.credentials.usernames` section
840
+ - Restart the dashboard after configuration changes
841
+ """)
842
+
843
+
844
+ def render_admin_system_config_page():
845
+ """Render Admin System Configuration page (read-only)."""
846
+ st.header("⚙️ System Configuration")
847
+ st.markdown("---")
848
+
849
+ st.markdown("**Environment Variables**")
850
+
851
+ env_vars = {
852
+ "PYTHONPATH": os.environ.get("PYTHONPATH", "Not set"),
853
+ "HOME": os.environ.get("HOME", "Not set"),
854
+ "LANG": os.environ.get("LANG", "Not set"),
855
+ "TERM": os.environ.get("TERM", "Not set"),
856
+ }
857
+
858
+ env_data = [{"Variable": k, "Value": v} for k, v in env_vars.items()]
859
+ st.dataframe(env_data, use_container_width=True, hide_index=True)
860
+
861
+ st.markdown("---")
862
+
863
+ st.markdown("**System Information**")
864
+
865
+ sys_info = [
866
+ {"Item": "Python Version", "Value": f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"},
867
+ {"Item": "Platform", "Value": sys.platform},
868
+ {"Item": "Working Directory", "Value": os.getcwd()},
869
+ {"Item": "DevSquad Version", "Value": "V3.6.0"},
870
+ {"Item": "Dashboard Mode", "Value": "Production-Grade"},
871
+ ]
872
+ st.dataframe(sys_info, use_container_width=True, hide_index=True)
873
+
874
+ st.markdown("---")
875
+
876
+ st.markdown("**Configuration Status**")
877
+
878
+ config_items = [
879
+ {"Component": "Authentication", "Status": "✅ Enabled" if auth.credentials else "⚠️ No Config"},
880
+ {"Component": "Lifecycle Protocol", "Status": "✅ Loaded" if load_lifecycle_protocol() else "❌ Failed"},
881
+ {"Component": "Multi-Agent Dispatcher", "Status": "✅ Ready" if get_dispatcher() else "❌ Not Initialized"},
882
+ {"Component": "Auto-Refresh", "Status": f"{'✅ Active' if st.session_state.get('auto_refresh', False) else '⚪ Inactive'}"},
883
+ ]
884
+ st.dataframe(config_items, use_container_width=True, hide_index=True)
885
+
886
+ st.markdown("---")
887
+ st.caption("ℹ️ This page is read-only. Configuration changes require editing source files.")
888
+
889
+
890
+ def render_action_panel(protocol_data):
891
+ """Render action control panel."""
892
+ st.subheader("🎮 Control Panel")
893
+
894
+ col1, col2, col3 = st.columns(3)
895
+
896
+ with col1:
897
+ if st.button("▶️ Run All Phases", type="primary", use_container_width=True):
898
+ st.session_state['action'] = 'run_all'
899
+ st.success("Initiated full lifecycle run...")
900
+ st.rerun()
901
+
902
+ with col2:
903
+ if st.button("🔄 Reset State", use_container_width=True):
904
+ st.session_state['action'] = 'reset'
905
+ st.info("Resetting lifecycle state...")
906
+ st.rerun()
907
+
908
+ with col3:
909
+ if st.button("📊 Generate Report", use_container_width=True):
910
+ st.session_state['action'] = 'report'
911
+ st.success("Generating benchmark report...")
912
+
913
+
914
+ def handle_auto_refresh():
915
+ """Handle auto-refresh logic with countdown timer."""
916
+ auto_refresh = st.session_state.get('auto_refresh', False)
917
+
918
+ if auto_refresh:
919
+ if 'refresh_start_time' not in st.session_state:
920
+ st.session_state.refresh_start_time = time.time()
921
+
922
+ elapsed = time.time() - st.session_state.refresh_start_time
923
+ remaining = max(0, 30 - int(elapsed))
924
+
925
+ st.session_state['refresh_countdown'] = remaining
926
+
927
+ if remaining <= 0:
928
+ st.session_state.refresh_start_time = time.time()
929
+ st.rerun()
930
+
931
+
932
+ def render_sidebar(auth: AuthManager, current_user: Optional[User] = None):
933
+ """Render sidebar navigation and controls with new pages."""
934
+ with st.sidebar:
935
+ st.header("Navigation")
936
+
937
+ page_options = [
938
+ "Overview",
939
+ "Phases",
940
+ "Mapping",
941
+ "Gates",
942
+ "Performance",
943
+ "Task Dispatch",
944
+ ]
945
+
946
+ page_captions = [
947
+ "System overview & metrics",
948
+ "Phase timeline & details",
949
+ "CLI command mapping",
950
+ "Gate status monitor",
951
+ "Performance metrics",
952
+ "Create & manage tasks",
953
+ ]
954
+
955
+ page = st.radio(
956
+ "Go to",
957
+ page_options,
958
+ captions=page_captions,
959
+ label_visibility="collapsed"
960
+ )
961
+
962
+ st.divider()
963
+
964
+ if current_user:
965
+ auth.get_login_button()
966
+
967
+ st.header("Settings")
968
+
969
+ auto_refresh = st.checkbox("⏱️ Auto Refresh (30s)", value=st.session_state.get('auto_refresh', False))
970
+ st.session_state['auto_refresh'] = auto_refresh
971
+
972
+ show_details = st.checkbox("📋 Show Details", value=True)
973
+
974
+ st.divider()
975
+
976
+ st.header("Quick Actions")
977
+
978
+ if st.button("🔄 Refresh Data", use_container_width=True):
979
+ st.rerun()
980
+
981
+ if st.button("📥 Export Status", use_container_width=True):
982
+ st.success("Status exported!")
983
+
984
+ if current_user and current_user.role.value == "admin":
985
+ st.divider()
986
+ st.header("⚙️ Admin")
987
+
988
+ if st.button("👥 Manage Users", use_container_width=True):
989
+ st.session_state.page = "admin_users"
990
+
991
+ if st.button("⚙️ System Config", use_container_width=True):
992
+ st.session_state.page = "system_config"
993
+
994
+ return page
995
+
996
+
997
+ def render_footer(current_user: Optional[User] = None):
998
+ """Render dashboard footer with version and session info."""
999
+ st.markdown("---")
1000
+
1001
+ col1, col2, col3 = st.columns([2, 1, 1])
1002
+
1003
+ with col1:
1004
+ st.markdown(
1005
+ "<center>**DevSquad V3.6.0** | Plan C | Production Ready | 🎨 Enhanced UI</center>",
1006
+ unsafe_allow_html=True
1007
+ )
1008
+
1009
+ with col2:
1010
+ if current_user:
1011
+ st.caption(f"Session: {current_user.session_id[:8]}...")
1012
+
1013
+ with col3:
1014
+ st.caption(f"© {datetime.now().year} DevSquad Team")
1015
+
1016
+
1017
+ def main():
1018
+ """Main dashboard entry point."""
1019
+ set_page_config()
1020
+ apply_custom_css()
1021
+
1022
+ auth = AuthManager(config_path=os.path.join(
1023
+ os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
1024
+ "config",
1025
+ "deployment.yaml"
1026
+ ))
1027
+
1028
+ auth.authenticate_streamlit()
1029
+
1030
+ current_user = auth.get_current_user()
1031
+
1032
+ page = render_sidebar(auth, current_user)
1033
+
1034
+ handle_auto_refresh()
1035
+
1036
+ render_header(current_user)
1037
+
1038
+ protocol_data = load_lifecycle_protocol()
1039
+ dispatcher = get_dispatcher()
1040
+
1041
+ can_execute = current_user.can_execute_phases() if current_user else False
1042
+
1043
+ if page == "Overview":
1044
+ render_metrics_overview(protocol_data)
1045
+ st.divider()
1046
+ if can_execute:
1047
+ render_action_panel(protocol_data)
1048
+ else:
1049
+ st.info("🔒 Phase execution requires Operator or Admin role")
1050
+
1051
+ elif page == "Phases":
1052
+ render_phase_timeline(protocol_data)
1053
+
1054
+ elif page == "Mapping":
1055
+ render_cli_mapping_table(protocol_data)
1056
+
1057
+ elif page == "Gates":
1058
+ render_gate_status_panel(protocol_data)
1059
+
1060
+ elif page == "Performance":
1061
+ render_performance_panel()
1062
+
1063
+ elif page == "Task Dispatch":
1064
+ if can_execute:
1065
+ render_task_dispatch_page(dispatcher)
1066
+ else:
1067
+ st.error("🔒 Task dispatch requires Operator or Admin role")
1068
+ st.info("Please contact your administrator to request elevated permissions.")
1069
+
1070
+ if st.session_state.get('page') == 'admin_users':
1071
+ if current_user and current_user.role.value == "admin":
1072
+ render_admin_users_page(auth)
1073
+ else:
1074
+ st.error("🔒 Access denied. Admin role required.")
1075
+
1076
+ if st.session_state.get('page') == 'system_config':
1077
+ if current_user and current_user.role.value == "admin":
1078
+ render_admin_system_config_page()
1079
+ else:
1080
+ st.error("🔒 Access denied. Admin role required.")
1081
+
1082
+ st.divider()
1083
+
1084
+ render_footer(current_user)
1085
+
1086
+
1087
+ if __name__ == "__main__":
1088
+ main()