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,347 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Async LLM Cache Module
5
+
6
+ Provides asynchronous caching for LLM API responses to reduce costs and improve performance.
7
+ Compatible with asyncio-based applications.
8
+
9
+ Features:
10
+ - Async memory + disk dual-layer caching
11
+ - TTL-based expiration
12
+ - LRU eviction policy
13
+ - Thread-safe operations
14
+ - Statistics tracking
15
+
16
+ Usage:
17
+ from scripts.collaboration import get_async_llm_cache
18
+
19
+ cache = get_async_llm_cache()
20
+
21
+ # Try to get from cache
22
+ response = await cache.get(prompt, backend="openai", model="gpt-4")
23
+ if not response:
24
+ # Call API
25
+ response = await your_async_api_call(prompt)
26
+ # Save to cache
27
+ await cache.set(prompt, response, backend="openai", model="gpt-4")
28
+ """
29
+
30
+ import asyncio
31
+ import hashlib
32
+ import json
33
+ import logging
34
+ import os
35
+ import time
36
+ from dataclasses import dataclass, asdict
37
+ from datetime import datetime, timedelta
38
+ from pathlib import Path
39
+ from typing import Optional, Dict, List, Tuple
40
+ from collections import OrderedDict
41
+
42
+ logger = logging.getLogger(__name__)
43
+
44
+
45
+ @dataclass
46
+ class CacheEntry:
47
+ """Cache entry with metadata"""
48
+ prompt: str
49
+ response: str
50
+ backend: str
51
+ model: str
52
+ timestamp: float
53
+ ttl_seconds: int
54
+ hit_count: int = 0
55
+
56
+ def is_expired(self) -> bool:
57
+ """Check if entry has expired"""
58
+ age = time.time() - self.timestamp
59
+ return age > self.ttl_seconds
60
+
61
+ def age_hours(self) -> float:
62
+ """Get age in hours"""
63
+ return (time.time() - self.timestamp) / 3600
64
+
65
+
66
+ class AsyncLLMCache:
67
+ """
68
+ Async LLM response cache with memory and disk persistence.
69
+
70
+ Thread-safe and asyncio-compatible implementation.
71
+ """
72
+
73
+ def __init__(
74
+ self,
75
+ cache_dir: str = "data/llm_cache",
76
+ ttl_seconds: int = 86400, # 24 hours
77
+ max_memory_entries: int = 1000
78
+ ):
79
+ """
80
+ Initialize async cache.
81
+
82
+ Args:
83
+ cache_dir: Directory for disk cache
84
+ ttl_seconds: Time-to-live for cache entries (default: 24 hours)
85
+ max_memory_entries: Maximum entries in memory cache
86
+ """
87
+ self.cache_dir = Path(cache_dir)
88
+ self.cache_dir.mkdir(parents=True, exist_ok=True)
89
+
90
+ self.ttl_seconds = ttl_seconds
91
+ self.max_memory_entries = max_memory_entries
92
+
93
+ # Memory cache (LRU)
94
+ self._memory_cache: OrderedDict[str, CacheEntry] = OrderedDict()
95
+
96
+ # Statistics
97
+ self._stats = {
98
+ "hits": 0,
99
+ "misses": 0,
100
+ "sets": 0,
101
+ "evictions": 0
102
+ }
103
+
104
+ # Lock for thread safety
105
+ self._lock = asyncio.Lock()
106
+
107
+ logger.info(f"AsyncLLMCache initialized: dir={cache_dir}, ttl={ttl_seconds}s, max_memory={max_memory_entries}")
108
+
109
+ def _generate_cache_key(self, prompt: str, backend: str, model: str) -> str:
110
+ """Generate cache key from prompt, backend, and model"""
111
+ content = f"{backend}:{model}:{prompt}"
112
+ return hashlib.sha256(content.encode()).hexdigest()
113
+
114
+ def _get_disk_path(self, cache_key: str) -> Path:
115
+ """Get disk cache file path"""
116
+ # Use first 2 chars for subdirectory to avoid too many files in one dir
117
+ subdir = cache_key[:2]
118
+ return self.cache_dir / subdir / f"{cache_key}.json"
119
+
120
+ async def get(
121
+ self,
122
+ prompt: str,
123
+ backend: str,
124
+ model: str
125
+ ) -> Optional[str]:
126
+ """
127
+ Get cached response asynchronously.
128
+
129
+ Args:
130
+ prompt: Input prompt
131
+ backend: LLM backend (e.g., "openai", "anthropic")
132
+ model: Model name (e.g., "gpt-4", "claude-3")
133
+
134
+ Returns:
135
+ Cached response or None if not found/expired
136
+ """
137
+ cache_key = self._generate_cache_key(prompt, backend, model)
138
+
139
+ async with self._lock:
140
+ # Try memory cache first
141
+ if cache_key in self._memory_cache:
142
+ entry = self._memory_cache[cache_key]
143
+
144
+ if not entry.is_expired():
145
+ # Move to end (LRU)
146
+ self._memory_cache.move_to_end(cache_key)
147
+ entry.hit_count += 1
148
+ self._stats["hits"] += 1
149
+ logger.debug(f"Memory cache hit: {cache_key[:8]}... (age: {entry.age_hours():.1f}h)")
150
+ return entry.response
151
+ else:
152
+ # Expired, remove from memory
153
+ del self._memory_cache[cache_key]
154
+ logger.debug(f"Memory cache expired: {cache_key[:8]}...")
155
+
156
+ # Try disk cache
157
+ disk_path = self._get_disk_path(cache_key)
158
+ if disk_path.exists():
159
+ try:
160
+ # Read from disk asynchronously
161
+ loop = asyncio.get_event_loop()
162
+ data = await loop.run_in_executor(None, disk_path.read_text)
163
+ entry_dict = json.loads(data)
164
+ entry = CacheEntry(**entry_dict)
165
+
166
+ if not entry.is_expired():
167
+ # Load into memory cache
168
+ self._memory_cache[cache_key] = entry
169
+ self._memory_cache.move_to_end(cache_key)
170
+ entry.hit_count += 1
171
+ self._stats["hits"] += 1
172
+
173
+ # Evict if memory cache is full
174
+ await self._evict_if_needed()
175
+
176
+ logger.debug(f"Disk cache hit: {cache_key[:8]}... (age: {entry.age_hours():.1f}h)")
177
+ return entry.response
178
+ else:
179
+ # Expired, delete from disk
180
+ await loop.run_in_executor(None, disk_path.unlink)
181
+ logger.debug(f"Disk cache expired: {cache_key[:8]}...")
182
+ except Exception as e:
183
+ logger.warning(f"Error reading disk cache: {e}")
184
+
185
+ # Cache miss
186
+ self._stats["misses"] += 1
187
+ logger.debug(f"Cache miss: {cache_key[:8]}...")
188
+ return None
189
+
190
+ async def set(
191
+ self,
192
+ prompt: str,
193
+ response: str,
194
+ backend: str,
195
+ model: str,
196
+ ttl_seconds: Optional[int] = None
197
+ ):
198
+ """
199
+ Set cache entry asynchronously.
200
+
201
+ Args:
202
+ prompt: Input prompt
203
+ response: LLM response
204
+ backend: LLM backend
205
+ model: Model name
206
+ ttl_seconds: Custom TTL (optional, uses default if not provided)
207
+ """
208
+ cache_key = self._generate_cache_key(prompt, backend, model)
209
+ ttl = ttl_seconds or self.ttl_seconds
210
+
211
+ entry = CacheEntry(
212
+ prompt=prompt,
213
+ response=response,
214
+ backend=backend,
215
+ model=model,
216
+ timestamp=time.time(),
217
+ ttl_seconds=ttl,
218
+ hit_count=0
219
+ )
220
+
221
+ async with self._lock:
222
+ # Add to memory cache
223
+ self._memory_cache[cache_key] = entry
224
+ self._memory_cache.move_to_end(cache_key)
225
+ self._stats["sets"] += 1
226
+
227
+ # Evict if needed
228
+ await self._evict_if_needed()
229
+
230
+ # Save to disk asynchronously
231
+ disk_path = self._get_disk_path(cache_key)
232
+ disk_path.parent.mkdir(parents=True, exist_ok=True)
233
+
234
+ loop = asyncio.get_event_loop()
235
+ await loop.run_in_executor(
236
+ None,
237
+ disk_path.write_text,
238
+ json.dumps(asdict(entry), indent=2)
239
+ )
240
+
241
+ logger.debug(f"Cache set: {cache_key[:8]}... (ttl: {ttl}s)")
242
+
243
+ async def _evict_if_needed(self):
244
+ """Evict oldest entries if memory cache is full"""
245
+ while len(self._memory_cache) > self.max_memory_entries:
246
+ # Remove oldest (first) entry
247
+ oldest_key, _ = self._memory_cache.popitem(last=False)
248
+ self._stats["evictions"] += 1
249
+ logger.debug(f"Evicted from memory: {oldest_key[:8]}...")
250
+
251
+ async def clear(self, backend: Optional[str] = None):
252
+ """
253
+ Clear cache asynchronously.
254
+
255
+ Args:
256
+ backend: If provided, only clear entries for this backend
257
+ """
258
+ async with self._lock:
259
+ if backend:
260
+ # Clear specific backend
261
+ keys_to_remove = [
262
+ k for k, v in self._memory_cache.items()
263
+ if v.backend == backend
264
+ ]
265
+ for key in keys_to_remove:
266
+ del self._memory_cache[key]
267
+ logger.info(f"Cleared cache for backend: {backend}")
268
+ else:
269
+ # Clear all
270
+ self._memory_cache.clear()
271
+ logger.info("Cleared all cache")
272
+
273
+ def get_stats(self) -> Dict:
274
+ """Get cache statistics"""
275
+ total_requests = self._stats["hits"] + self._stats["misses"]
276
+ hit_rate = self._stats["hits"] / total_requests if total_requests > 0 else 0.0
277
+
278
+ return {
279
+ "hits": self._stats["hits"],
280
+ "misses": self._stats["misses"],
281
+ "sets": self._stats["sets"],
282
+ "evictions": self._stats["evictions"],
283
+ "hit_rate": hit_rate,
284
+ "memory_entries": len(self._memory_cache),
285
+ "max_memory_entries": self.max_memory_entries
286
+ }
287
+
288
+ async def export_stats_report(self) -> str:
289
+ """Export statistics as markdown report"""
290
+ stats = self.get_stats()
291
+
292
+ report = "# Async LLM Cache Statistics\n\n"
293
+ report += f"**Hit Rate**: {stats['hit_rate']:.1%}\n\n"
294
+ report += "| Metric | Value |\n"
295
+ report += "|--------|-------|\n"
296
+ report += f"| Cache Hits | {stats['hits']} |\n"
297
+ report += f"| Cache Misses | {stats['misses']} |\n"
298
+ report += f"| Cache Sets | {stats['sets']} |\n"
299
+ report += f"| Evictions | {stats['evictions']} |\n"
300
+ report += f"| Memory Entries | {stats['memory_entries']} / {stats['max_memory_entries']} |\n"
301
+
302
+ return report
303
+
304
+
305
+ # Global async cache instance
306
+ _global_async_cache: Optional[AsyncLLMCache] = None
307
+
308
+
309
+ def get_async_llm_cache() -> AsyncLLMCache:
310
+ """Get global async LLM cache instance (singleton)"""
311
+ global _global_async_cache
312
+ if _global_async_cache is None:
313
+ _global_async_cache = AsyncLLMCache()
314
+ return _global_async_cache
315
+
316
+
317
+ def reset_async_cache():
318
+ """Reset global async cache instance"""
319
+ global _global_async_cache
320
+ _global_async_cache = None
321
+
322
+
323
+ if __name__ == "__main__":
324
+ # Example usage
325
+ async def main():
326
+ cache = get_async_llm_cache()
327
+
328
+ # Set cache
329
+ await cache.set(
330
+ prompt="What is Python?",
331
+ response="Python is a programming language.",
332
+ backend="openai",
333
+ model="gpt-4"
334
+ )
335
+
336
+ # Get from cache
337
+ response = await cache.get(
338
+ prompt="What is Python?",
339
+ backend="openai",
340
+ model="gpt-4"
341
+ )
342
+ print(f"Cached response: {response}")
343
+
344
+ # Print stats
345
+ print(await cache.export_stats_report())
346
+
347
+ asyncio.run(main())