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/auth.py ADDED
@@ -0,0 +1,451 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ DevSquad Authentication Module
5
+
6
+ Provides authentication and authorization for Streamlit Dashboard.
7
+
8
+ Features:
9
+ - Basic authentication with configurable credentials
10
+ - Role-based access control (admin/operator/viewer)
11
+ - Session management
12
+ - OAuth2 support (optional)
13
+
14
+ Usage:
15
+ from scripts.auth import AuthManager, require_auth, check_permission
16
+
17
+ auth = AuthManager(config_path="config/deployment.yaml")
18
+ auth.authenticate() # Call in Streamlit app
19
+ """
20
+
21
+ import hashlib
22
+ import logging
23
+ import os
24
+ import sys
25
+ from dataclasses import dataclass
26
+ from datetime import datetime, timedelta
27
+ from enum import Enum
28
+ from functools import wraps
29
+ from typing import Any, Dict, List, Optional, Tuple
30
+
31
+ import yaml
32
+
33
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
34
+
35
+ logger = logging.getLogger(__name__)
36
+
37
+
38
+ class UserRole(Enum):
39
+ """User roles for access control."""
40
+ ADMIN = "admin"
41
+ OPERATOR = "operator"
42
+ VIEWER = "viewer"
43
+
44
+
45
+ @dataclass
46
+ class User:
47
+ """Authenticated user information."""
48
+ username: str
49
+ email: str
50
+ name: str
51
+ role: UserRole
52
+ authenticated_at: datetime
53
+ session_id: str
54
+
55
+ def can_execute_phases(self) -> bool:
56
+ """Check if user can execute lifecycle phases."""
57
+ return self.role in [UserRole.ADMIN, UserRole.OPERATOR]
58
+
59
+ def can_view_metrics(self) -> bool:
60
+ """Check if user can view metrics."""
61
+ return True # All roles can view metrics
62
+
63
+ def can_modify_config(self) -> bool:
64
+ """Check if user can modify configuration."""
65
+ return self.role == UserRole.ADMIN
66
+
67
+
68
+ class AuthManager:
69
+ """
70
+ Authentication manager for DevSquad dashboard.
71
+
72
+ Handles user authentication, session management,
73
+ and role-based access control.
74
+ """
75
+
76
+ def __init__(self, config_path: Optional[str] = None):
77
+ """
78
+ Initialize authentication manager.
79
+
80
+ Args:
81
+ config_path: Path to deployment configuration file.
82
+ If None, uses default config/deployment.yaml
83
+ """
84
+ self.config_path = config_path or os.path.join(
85
+ os.path.dirname(os.path.dirname(__file__)),
86
+ "config",
87
+ "deployment.yaml"
88
+ )
89
+
90
+ self.config = self._load_config()
91
+ self.auth_enabled = self.config.get("authentication", {}).get("enabled", False)
92
+ self.credentials = self._get_credentials()
93
+ self.cookie_settings = self.config.get("authentication", {}).get("cookie", {})
94
+
95
+ # Validate configuration security
96
+ self._validate_config_security()
97
+
98
+ logger.info(f"AuthManager initialized (enabled={self.auth_enabled})")
99
+
100
+ def _load_config(self) -> Dict[str, Any]:
101
+ """Load deployment configuration from YAML file."""
102
+ try:
103
+ if os.path.exists(self.config_path):
104
+ with open(self.config_path, "r", encoding="utf-8") as f:
105
+ return yaml.safe_load(f) or {}
106
+ else:
107
+ logger.warning(f"Config file not found: {self.config_path}")
108
+ return {}
109
+ except Exception as e:
110
+ logger.error(f"Failed to load config: {e}")
111
+ return {}
112
+
113
+ def _get_credentials(self) -> Dict[str, Dict]:
114
+ """Get credentials from configuration."""
115
+ auth_config = self.config.get("authentication", {})
116
+ return auth_config.get("credentials", {}).get("usernames", {})
117
+
118
+ def _validate_config_security(self):
119
+ """
120
+ Validate configuration for security issues.
121
+
122
+ Warns about:
123
+ - Placeholder passwords
124
+ - Default session keys
125
+ - Insecure configurations
126
+ """
127
+ if not self.auth_enabled:
128
+ return
129
+
130
+ warnings = []
131
+
132
+ # Check for placeholder passwords
133
+ placeholder_patterns = [
134
+ "hashed_password_here",
135
+ "password",
136
+ "changeme",
137
+ "default",
138
+ "your_password",
139
+ ]
140
+
141
+ for username, cred in self.credentials.items():
142
+ password = cred.get("password", "")
143
+ if any(pattern in password.lower() for pattern in placeholder_patterns):
144
+ warnings.append(
145
+ f"User '{username}' has placeholder password: {password[:20]}..."
146
+ )
147
+
148
+ # Check for default session key
149
+ cookie_key = self.cookie_settings.get("key", "")
150
+ default_keys = [
151
+ "devsquad_session_key_change_in_production",
152
+ "change_this_key",
153
+ "default_secret_key",
154
+ ]
155
+
156
+ if any(key == cookie_key for key in default_keys):
157
+ warnings.append(
158
+ f"Using default session key: {cookie_key}. "
159
+ "Generate a secure key with: python -c \"import secrets; print(secrets.token_hex(32))\""
160
+ )
161
+
162
+ # Log warnings
163
+ if warnings:
164
+ logger.warning("=" * 60)
165
+ logger.warning("SECURITY WARNINGS - Configuration Issues Detected:")
166
+ for warning in warnings:
167
+ logger.warning(f" ⚠️ {warning}")
168
+ logger.warning("=" * 60)
169
+ logger.warning(
170
+ "Please update config/deployment.yaml with secure values before production use."
171
+ )
172
+
173
+ def _hash_password(self, password: str) -> str:
174
+ """Hash password using SHA-256."""
175
+ return hashlib.sha256(password.encode()).hexdigest()
176
+
177
+ def verify_credentials(self, username: str, password: str) -> Optional[User]:
178
+ """
179
+ Verify user credentials.
180
+
181
+ Args:
182
+ username: Username to verify
183
+ password: Plain text password
184
+
185
+ Returns:
186
+ User object if authenticated, None otherwise
187
+ """
188
+ if username not in self.credentials:
189
+ logger.warning(f"Login attempt for unknown user: {username}")
190
+ return None
191
+
192
+ cred = self.credentials[username]
193
+ stored_password_hash = cred.get("password", "")
194
+ input_password_hash = self._hash_password(password)
195
+
196
+ if input_password_hash != stored_password_hash:
197
+ logger.warning(f"Failed login attempt for user: {username}")
198
+ return None
199
+
200
+ # Create user object
201
+ try:
202
+ role = UserRole(cred.get("role", "viewer"))
203
+ except ValueError:
204
+ role = UserRole.VIEWER
205
+
206
+ user = User(
207
+ username=username,
208
+ email=cred.get("email", ""),
209
+ name=cred.get("name", username),
210
+ role=role,
211
+ authenticated_at=datetime.now(),
212
+ session_id=hashlib.md5(
213
+ f"{username}{datetime.now().isoformat()}".encode()
214
+ ).hexdigest()[:16]
215
+ )
216
+
217
+ logger.info(f"User authenticated: {username} (role={role.value})")
218
+ return user
219
+
220
+ def authenticate_streamlit(self):
221
+ """
222
+ Authenticate user in Streamlit application.
223
+
224
+ This method should be called at the beginning of a Streamlit app.
225
+ It handles the login form and session state management.
226
+ """
227
+ try:
228
+ import streamlit as st
229
+
230
+ if not self.auth_enabled:
231
+ # Authentication disabled, set default admin user
232
+ if "user" not in st.session_state:
233
+ st.session_state.user = User(
234
+ username="admin",
235
+ email="admin@devsquad.local",
236
+ name="Administrator",
237
+ role=UserRole.ADMIN,
238
+ authenticated_at=datetime.now(),
239
+ session_id="dev_mode"
240
+ )
241
+ return
242
+
243
+ # Check if already authenticated
244
+ if "user" in st.session_state:
245
+ return
246
+
247
+ # Show login form
248
+ st.title("🔐 DevSquad Login")
249
+
250
+ col1, col2 = st.columns([1, 2])
251
+
252
+ with col1:
253
+ st.image(
254
+ "https://streamlit.io/images/brand/streamlit-logo-primary-colormark-lighttext.png",
255
+ width=120
256
+ )
257
+
258
+ with col2:
259
+ st.markdown("""
260
+ ### Welcome to DevSquad Dashboard
261
+
262
+ Please enter your credentials to access the lifecycle monitoring system.
263
+ """)
264
+
265
+ with st.form("login_form"):
266
+ username = st.text_input("Username")
267
+ password = st.text_input("Password", type="password")
268
+ submitted = st.form_submit_button("Login", type="primary")
269
+
270
+ if submitted:
271
+ user = self.verify_credentials(username, password)
272
+ if user:
273
+ st.session_state.user = user
274
+ st.success(f"✅ Welcome, {user.name}!")
275
+ st.rerun()
276
+ else:
277
+ st.error("❌ Invalid username or password")
278
+
279
+ # Stop execution if not authenticated
280
+ st.stop()
281
+
282
+ except ImportError:
283
+ logger.warning("Streamlit not available, skipping authentication UI")
284
+
285
+ def get_current_user(self) -> Optional[User]:
286
+ """
287
+ Get currently authenticated user.
288
+
289
+ Returns:
290
+ User object if authenticated, None otherwise
291
+ """
292
+ try:
293
+ import streamlit as st
294
+ return st.session_state.get("user")
295
+ except ImportError:
296
+ return None
297
+
298
+ def logout(self):
299
+ """Logout current user."""
300
+ try:
301
+ import streamlit as st
302
+ if "user" in st.session_state:
303
+ del st.session_state.user
304
+ st.rerun()
305
+ except ImportError:
306
+ pass
307
+
308
+ def get_login_button(self):
309
+ """
310
+ Generate logout button for Streamlit sidebar.
311
+
312
+ Returns:
313
+ Streamlit button or None
314
+ """
315
+ try:
316
+ import streamlit as st
317
+
318
+ user = self.get_current_user()
319
+ if user and self.auth_enabled:
320
+ if st.sidebar.button("🚪 Logout"):
321
+ self.logout()
322
+
323
+ st.sidebar.markdown(f"""
324
+ **Logged in as:** {user.name}
325
+ **Role:** `{user.role.value}`
326
+ **Session:** `{user.session_id[:8]}...`
327
+ """, unsafe_allow_html=True)
328
+
329
+ except ImportError:
330
+ pass
331
+
332
+
333
+ def require_auth(func):
334
+ """
335
+ Decorator to require authentication for API endpoints.
336
+
337
+ Usage:
338
+ @require_auth
339
+ def protected_endpoint():
340
+ return {"message": "This is protected"}
341
+ """
342
+ @wraps(func)
343
+ def wrapper(*args, **kwargs):
344
+ _auth_instance = AuthManager.get_instance()
345
+ if _auth_instance and _auth_instance.enabled:
346
+ try:
347
+ import streamlit as st
348
+ user = st.session_state.get("user")
349
+ if not user:
350
+ logger.warning(f"Auth required but no session for {func.__name__}")
351
+ raise PermissionError("Authentication required")
352
+ except (ImportError, AttributeError):
353
+ pass
354
+ logger.debug(f"Auth check for {func.__name__}")
355
+ return func(*args, **kwargs)
356
+ return wrapper
357
+
358
+
359
+ def check_permission(required_role: UserRole = UserRole.VIEWER):
360
+ """
361
+ Decorator to check user permissions.
362
+
363
+ Args:
364
+ required_role: Minimum role required to access the resource
365
+ """
366
+ def decorator(func):
367
+ @wraps(func)
368
+ def wrapper(*args, **kwargs):
369
+ user = None
370
+ try:
371
+ import streamlit as st
372
+ user = st.session_state.get("user")
373
+ except (ImportError, AttributeError):
374
+ pass
375
+
376
+ if not user:
377
+ raise PermissionError("Not authenticated")
378
+
379
+ role_hierarchy = {
380
+ UserRole.VIEWER: 0,
381
+ UserRole.OPERATOR: 1,
382
+ UserRole.ADMIN: 2
383
+ }
384
+
385
+ if role_hierarchy.get(user.role, 0) < role_hierarchy.get(required_role, 0):
386
+ raise PermissionError(
387
+ f"Insufficient permissions. Required: {required_role.value}, "
388
+ f"Current: {user.role.value}"
389
+ )
390
+
391
+ return func(*args, **kwargs)
392
+ return wrapper
393
+ return decorator
394
+
395
+
396
+ def create_demo_credentials():
397
+ """
398
+ Create demo credentials for testing purposes.
399
+
400
+ Generates hashed passwords for default users.
401
+
402
+ Returns:
403
+ Dict with demo usernames and hashed passwords
404
+ """
405
+ demo_users = {
406
+ "admin": {
407
+ "email": "admin@devsquad.local",
408
+ "name": "Administrator",
409
+ "password": "admin123",
410
+ "role": "admin"
411
+ },
412
+ "operator": {
413
+ "email": "operator@devsquad.local",
414
+ "name": "Operator",
415
+ "password": "operator123",
416
+ "role": "operator"
417
+ },
418
+ "viewer": {
419
+ "email": "viewer@devsquad.local",
420
+ "name": "Viewer",
421
+ "password": "viewer123",
422
+ "role": "viewer"
423
+ }
424
+ }
425
+
426
+ # Hash passwords
427
+ auth_manager = AuthManager.__new__(AuthManager)
428
+ for username, info in demo_users.items():
429
+ info["password"] = auth_manager._hash_password(info["password"])
430
+
431
+ print("\n📋 Demo Credentials Created:")
432
+ print("=" * 50)
433
+ for username, info in demo_users.items():
434
+ print(f"\nUsername: {username}")
435
+ print(f"Password: (see below)")
436
+ print(f"Role: {info['role']}")
437
+ print(f"Hashed Password: {info['password']}")
438
+
439
+ print("\n" + "=" * 50)
440
+ print("⚠️ Plain-text passwords for demo:")
441
+ print(" admin: admin123")
442
+ print(" operator: operator123")
443
+ print(" viewer: viewer123")
444
+ print("=" * 50 + "\n")
445
+
446
+ return demo_users
447
+
448
+
449
+ if __name__ == "__main__":
450
+ # Demo: Create test credentials
451
+ create_demo_credentials()
@@ -0,0 +1 @@
1
+ # CLI Package