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,389 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Async LLM Retry and Fallback Module
5
+
6
+ Provides asynchronous retry logic with exponential backoff, circuit breaker,
7
+ and multi-backend fallback for LLM API calls.
8
+
9
+ Features:
10
+ - Async exponential backoff retry
11
+ - Circuit breaker pattern (async-safe)
12
+ - Multi-backend fallback
13
+ - Rate limit detection
14
+ - Statistics tracking
15
+
16
+ Usage:
17
+ from scripts.collaboration import async_retry_with_fallback
18
+
19
+ @async_retry_with_fallback(max_retries=3, fallback_backends=["openai", "anthropic"])
20
+ async def call_llm(prompt: str, backend: str = "openai"):
21
+ return await your_async_api_call(prompt, backend)
22
+
23
+ result = await call_llm("Hello, world!")
24
+ """
25
+
26
+ import asyncio
27
+ import logging
28
+ import random
29
+ import time
30
+ from dataclasses import dataclass
31
+ from datetime import datetime
32
+ from typing import Optional, List, Callable, Any, Dict
33
+ from functools import wraps
34
+
35
+ logger = logging.getLogger(__name__)
36
+
37
+
38
+ class RateLimitError(Exception):
39
+ """Raised when rate limit is exceeded"""
40
+ pass
41
+
42
+
43
+ class CircuitBreakerError(Exception):
44
+ """Raised when circuit breaker is open"""
45
+ pass
46
+
47
+
48
+ @dataclass
49
+ class RetryConfig:
50
+ """Configuration for retry behavior"""
51
+ max_retries: int = 3
52
+ initial_delay: float = 1.0
53
+ max_delay: float = 60.0
54
+ exponential_base: float = 2.0
55
+ jitter: bool = True
56
+
57
+
58
+ @dataclass
59
+ class CircuitBreakerState:
60
+ """Circuit breaker state for a backend"""
61
+ failure_count: int = 0
62
+ last_failure_time: Optional[datetime] = None
63
+ state: str = "closed" # closed, open, half_open
64
+ failure_threshold: int = 5
65
+ timeout_seconds: int = 60
66
+
67
+
68
+ class AsyncLLMRetryManager:
69
+ """
70
+ Async retry manager with circuit breaker and fallback.
71
+
72
+ Thread-safe and -compatible implementation.
73
+ """
74
+
75
+ def __init__(self):
76
+ """Initialize async retry manager"""
77
+ self.circuit_breakers: Dict[str, CircuitBreakerState] = {}
78
+ self.stats = {
79
+ "total_calls": 0,
80
+ "successful_calls": 0,
81
+ "failed_calls": 0,
82
+ "retries": 0,
83
+ "fallbacks": 0,
84
+ "circuit_breaker_trips": 0
85
+ }
86
+ self._lock = asyncio.Lock()
87
+
88
+ logger.info("AsyncLLMRetryManager initialized")
89
+
90
+ def _calculate_delay(self, attempt: int, config: RetryConfig) -> float:
91
+ """Calculate delay for retry attempt with exponential backoff"""
92
+ delay = min(
93
+ config.initial_delay * (config.exponential_base ** attempt),
94
+ config.max_delay
95
+ )
96
+
97
+ if config.jitter:
98
+ # Add jitter: random value between 50% and 150% of delay
99
+ delay = delay * (0.5 + random.random())
100
+
101
+ return delay
102
+
103
+ def _is_retryable_error(self, error: Exception) -> bool:
104
+ """Check if error is retryable"""
105
+ error_str = str(error).lower()
106
+ retryable_patterns = [
107
+ "timeout", "connection", "network", "unavailable",
108
+ "503", "502", "504", "429"
109
+ ]
110
+ return any(pattern in error_str for pattern in retryable_patterns)
111
+
112
+ def _is_rate_limit_error(self, error: Exception) -> bool:
113
+ """Check if error is rate limit related"""
114
+ error_str = str(error).lower()
115
+ return "429" in error_str or "rate limit" in error_str
116
+
117
+ def _get_circuit_breaker(self, backend: str) -> CircuitBreakerState:
118
+ """Get or create circuit breaker for backend"""
119
+ if backend not in self.circuit_breakers:
120
+ self.circuit_breakers[backend] = CircuitBreakerState()
121
+ return self.circuit_breakers[backend]
122
+
123
+ async def _check_circuit_breaker(self, backend: str):
124
+ """Check if circuit breaker allows request"""
125
+ async with self._lock:
126
+ cb = self._get_circuit_breaker(backend)
127
+
128
+ if cb.state == "open":
129
+ # Check if timeout has passed
130
+ if cb.last_failure_time:
131
+ elapsed = (datetime.now() - cb.last_failure_time).total_seconds()
132
+ if elapsed > cb.timeout_seconds:
133
+ # Move to half-open state
134
+ cb.state = "half_open"
135
+ logger.info(f"Circuit breaker half-open: {backend}")
136
+ else:
137
+ raise CircuitBreakerError(f"Circuit breaker open for {backend}")
138
+
139
+ async def _record_success(self, backend: str):
140
+ """Record successful call"""
141
+ async with self._lock:
142
+ cb = self._get_circuit_breaker(backend)
143
+
144
+ if cb.state == "half_open":
145
+ # Close circuit breaker
146
+ cb.state = "closed"
147
+ cb.failure_count = 0
148
+ logger.info(f"Circuit breaker closed: {backend}")
149
+
150
+ async def _record_failure(self, backend: str, error: Exception):
151
+ """Record failed call"""
152
+ async with self._lock:
153
+ cb = self._get_circuit_breaker(backend)
154
+ cb.failure_count += 1
155
+ cb.last_failure_time = datetime.now()
156
+
157
+ if cb.failure_count >= cb.failure_threshold:
158
+ if cb.state != "open":
159
+ cb.state = "open"
160
+ self.stats["circuit_breaker_trips"] += 1
161
+ logger.warning(f"Circuit breaker opened: {backend} (failures: {cb.failure_count})")
162
+
163
+ async def retry_with_fallback(
164
+ self,
165
+ func: Callable,
166
+ args: tuple,
167
+ kwargs: dict,
168
+ config: RetryConfig,
169
+ fallback_backends: Optional[List[str]],
170
+ current_backend: Optional[str]
171
+ ) -> Any:
172
+ """
173
+ Execute async function with retry and fallback logic.
174
+
175
+ Args:
176
+ func: Async function to execute
177
+ args: Positional arguments
178
+ kwargs: Keyword arguments
179
+ config: Retry configuration
180
+ fallback_backends: List of fallback backends
181
+ current_backend: Current backend name
182
+
183
+ Returns:
184
+ Function result
185
+
186
+ Raises:
187
+ Exception: If all retries and fallbacks fail
188
+ """
189
+ self.stats["total_calls"] += 1
190
+ last_error = None
191
+
192
+ # Check circuit breaker first
193
+ if current_backend:
194
+ try:
195
+ await self._check_circuit_breaker(current_backend)
196
+ except CircuitBreakerError as e:
197
+ logger.warning(str(e))
198
+ # Circuit breaker open, try fallback immediately
199
+ if fallback_backends:
200
+ return await self._try_fallback(
201
+ func, args, kwargs, config, fallback_backends, current_backend
202
+ )
203
+ raise
204
+
205
+ # Try with retries
206
+ for attempt in range(config.max_retries):
207
+ try:
208
+ result = await func(*args, **kwargs)
209
+ if current_backend:
210
+ await self._record_success(current_backend)
211
+ self.stats["successful_calls"] += 1
212
+ return result
213
+
214
+ except Exception as e:
215
+ last_error = e
216
+
217
+ if current_backend:
218
+ await self._record_failure(current_backend, e)
219
+
220
+ # Check if retryable
221
+ if not self._is_retryable_error(e):
222
+ logger.error(f"Non-retryable error: {e}")
223
+ break
224
+
225
+ # Last attempt, no need to delay
226
+ if attempt < config.max_retries - 1:
227
+ delay = self._calculate_delay(attempt, config)
228
+
229
+ # Rate limit errors need longer delay
230
+ if self._is_rate_limit_error(e):
231
+ delay *= 3
232
+ logger.warning(f"Rate limit detected, waiting {delay:.1f}s")
233
+
234
+ logger.info(
235
+ f"Retry attempt {attempt + 1}/{config.max_retries} "
236
+ f"after {delay:.1f}s delay"
237
+ )
238
+ await asyncio.sleep(delay)
239
+ self.stats["retries"] += 1
240
+
241
+ # Primary backend failed, try fallback
242
+ if fallback_backends:
243
+ try:
244
+ return await self._try_fallback(
245
+ func, args, kwargs, config, fallback_backends, current_backend
246
+ )
247
+ except Exception as fallback_error:
248
+ logger.error(f"All fallback attempts failed: {fallback_error}")
249
+ last_error = fallback_error
250
+
251
+ # All attempts failed
252
+ self.stats["failed_calls"] += 1
253
+ raise last_error
254
+
255
+ async def _try_fallback(
256
+ self,
257
+ func: Callable,
258
+ args: tuple,
259
+ kwargs: dict,
260
+ config: RetryConfig,
261
+ fallback_backends: List[str],
262
+ exclude_backend: Optional[str]
263
+ ) -> Any:
264
+ """Try fallback to alternative backends"""
265
+ for backend in fallback_backends:
266
+ if backend == exclude_backend:
267
+ continue
268
+
269
+ try:
270
+ await self._check_circuit_breaker(backend)
271
+ except CircuitBreakerError:
272
+ logger.warning(f"Skipping {backend} (circuit breaker open)")
273
+ continue
274
+
275
+ logger.info(f"Attempting fallback to {backend}")
276
+ self.stats["fallbacks"] += 1
277
+
278
+ # Update backend parameter in kwargs
279
+ fallback_kwargs = kwargs.copy()
280
+ if "backend" in fallback_kwargs:
281
+ fallback_kwargs["backend"] = backend
282
+
283
+ try:
284
+ result = await func(*args, **fallback_kwargs)
285
+ await self._record_success(backend)
286
+ logger.info(f"Fallback to {backend} successful")
287
+ return result
288
+ except Exception as e:
289
+ await self._record_failure(backend, e)
290
+ logger.warning(f"Fallback to {backend} failed: {e}")
291
+ continue
292
+
293
+ raise Exception("All fallback backends failed")
294
+
295
+ def get_stats(self) -> Dict[str, Any]:
296
+ """Get retry statistics"""
297
+ return self.stats.copy()
298
+
299
+ async def reset_circuit_breaker(self, backend: str):
300
+ """Manually reset circuit breaker for a backend"""
301
+ async with self._lock:
302
+ if backend in self.circuit_breakers:
303
+ cb = self.circuit_breakers[backend]
304
+ cb.state = "closed"
305
+ cb.failure_count = 0
306
+ cb.last_failure_time = None
307
+ logger.info(f"Circuit breaker reset: {backend}")
308
+
309
+
310
+ # Global async retry manager
311
+ _global_async_retry_manager: Optional[AsyncLLMRetryManager] = None
312
+
313
+
314
+ def get_async_retry_manager() -> AsyncLLMRetryManager:
315
+ """Get global async retry manager instance (singleton)"""
316
+ global _global_async_retry_manager
317
+ if _global_async_retry_manager is None:
318
+ _global_async_retry_manager = AsyncLLMRetryManager()
319
+ return _global_async_retry_manager
320
+
321
+
322
+ def async_retry_with_fallback(
323
+ max_retries: int = 3,
324
+ initial_delay: float = 1.0,
325
+ max_delay: float = 60.0,
326
+ exponential_base: float = 2.0,
327
+ jitter: bool = True,
328
+ fallback_backends: Optional[List[str]] = None
329
+ ):
330
+ """
331
+ Decorator for async functions with retry and fallback.
332
+
333
+ Args:
334
+ max_retries: Maximum number of retries
335
+ initial_delay: Initial delay in seconds
336
+ max_delay: Maximum delay in seconds
337
+ exponential_base: Base for exponential backoff
338
+ jitter: Whether to add random jitter
339
+ fallback_backends: List of fallback backends
340
+
341
+ Usage:
342
+ @async_retry_with_fallback(max_retries=3, fallback_backends=["openai", "anthropic"])
343
+ async def call_llm(prompt: str, backend: str = "openai"):
344
+ return await your_api_call(prompt, backend)
345
+ """
346
+ def decorator(func: Callable):
347
+ @wraps(func)
348
+ async def wrapper(*args, **kwargs):
349
+ config = RetryConfig(
350
+ max_retries=max_retries,
351
+ initial_delay=initial_delay,
352
+ max_delay=max_delay,
353
+ exponential_base=exponential_base,
354
+ jitter=jitter
355
+ )
356
+
357
+ # Extract current backend from kwargs
358
+ current_backend = kwargs.get("backend")
359
+
360
+ manager = get_async_retry_manager()
361
+ return await manager.retry_with_fallback(
362
+ func, args, kwargs, config, fallback_backends, current_backend
363
+ )
364
+
365
+ return wrapper
366
+ return decorator
367
+
368
+
369
+ if __name__ == "__main__":
370
+ # Example usage
371
+ async def main():
372
+ @async_retry_with_fallback(max_retries=3, fallback_backends=["backup"])
373
+ async def test_func(value: int, backend: str = "primary"):
374
+ print(f"Calling {backend} with {value}")
375
+ if value < 3:
376
+ raise Exception("503 Service Unavailable")
377
+ return f"Success from {backend}"
378
+
379
+ try:
380
+ result = await test_func(1)
381
+ print(f"Result: {result}")
382
+ except Exception as e:
383
+ print(f"Failed: {e}")
384
+
385
+ # Print stats
386
+ manager = get_async_retry_manager()
387
+ print(f"Stats: {manager.get_stats()}")
388
+
389
+ asyncio.run(main())