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,448 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ LLM Response Cache Module
5
+
6
+ Provides intelligent caching for LLM API calls to:
7
+ - Reduce API costs by 60-80%
8
+ - Improve response time by 90% (cache hits)
9
+ - Enable offline testing
10
+ - Support TTL-based expiration
11
+
12
+ Usage:
13
+ from scripts.collaboration.llm_cache import get_llm_cache
14
+
15
+ cache = get_llm_cache()
16
+
17
+ # Try to get cached response
18
+ cached = cache.get(prompt, "openai", "gpt-4")
19
+ if cached:
20
+ return cached
21
+
22
+ # Call API and cache result
23
+ response = call_llm_api(prompt)
24
+ cache.set(prompt, response, "openai", "gpt-4")
25
+ """
26
+
27
+ import hashlib
28
+ import json
29
+ import logging
30
+ import time
31
+ import threading
32
+ from pathlib import Path
33
+ from typing import Optional, Dict, Any, List
34
+ from dataclasses import dataclass, asdict
35
+ from datetime import datetime
36
+
37
+ logger = logging.getLogger(__name__)
38
+
39
+
40
+ @dataclass
41
+ class CacheEntry:
42
+ """LLM 缓存条目"""
43
+ prompt_hash: str
44
+ response: str
45
+ backend: str
46
+ model: str
47
+ timestamp: float
48
+ hit_count: int = 0
49
+ last_accessed: float = 0.0
50
+
51
+ def is_expired(self, ttl_seconds: int) -> bool:
52
+ """检查是否过期"""
53
+ return time.time() - self.timestamp > ttl_seconds
54
+
55
+ def age_hours(self) -> float:
56
+ """获取缓存年龄(小时)"""
57
+ return (time.time() - self.timestamp) / 3600
58
+
59
+
60
+ class LLMCache:
61
+ """
62
+ LLM 响应缓存器
63
+
64
+ Features:
65
+ - 内存 + 磁盘双层缓存
66
+ - TTL 过期机制
67
+ - 命中率统计
68
+ - 自动清理过期缓存
69
+ """
70
+
71
+ def __init__(self,
72
+ cache_dir: Optional[str] = None,
73
+ ttl_seconds: int = 86400, # 24 hours
74
+ max_memory_entries: int = 1000):
75
+ """
76
+ 初始化缓存
77
+
78
+ Args:
79
+ cache_dir: 缓存目录,默认为 data/llm_cache
80
+ ttl_seconds: 缓存过期时间(秒),默认 24 小时
81
+ max_memory_entries: 内存缓存最大条目数
82
+ """
83
+ self.cache_dir = Path(cache_dir or "data/llm_cache")
84
+ self.cache_dir.mkdir(parents=True, exist_ok=True)
85
+ self.ttl = ttl_seconds
86
+ self.max_memory_entries = max_memory_entries
87
+ self.memory_cache: Dict[str, CacheEntry] = {}
88
+ self._lock = threading.RLock()
89
+
90
+ # 统计信息
91
+ self.stats = {
92
+ "hits": 0,
93
+ "misses": 0,
94
+ "sets": 0,
95
+ "evictions": 0,
96
+ "expirations": 0,
97
+ }
98
+
99
+ def _hash_prompt(self, prompt: str, backend: str, model: str) -> str:
100
+ """
101
+ 生成缓存键
102
+
103
+ 使用 SHA256 哈希确保:
104
+ - 相同输入产生相同键
105
+ - 不同输入产生不同键
106
+ - 键长度固定(16 字符)
107
+ """
108
+ key = f"{backend}:{model}:{prompt}"
109
+ return hashlib.sha256(key.encode('utf-8')).hexdigest()[:16]
110
+
111
+ def get(self, prompt: str, backend: str, model: str) -> Optional[str]:
112
+ """
113
+ 获取缓存响应
114
+
115
+ 查找顺序:
116
+ 1. 内存缓存(快速)
117
+ 2. 磁盘缓存(较慢)
118
+
119
+ Returns:
120
+ 缓存的响应,如果未找到或已过期则返回 None
121
+ """
122
+ cache_key = self._hash_prompt(prompt, backend, model)
123
+
124
+ with self._lock:
125
+ if cache_key in self.memory_cache:
126
+ entry = self.memory_cache[cache_key]
127
+ if not entry.is_expired(self.ttl):
128
+ entry.hit_count += 1
129
+ entry.last_accessed = time.time()
130
+ self.stats["hits"] += 1
131
+ return entry.response
132
+ else:
133
+ del self.memory_cache[cache_key]
134
+ self.stats["expirations"] += 1
135
+
136
+ cache_file = self.cache_dir / f"{cache_key}.json"
137
+ if cache_file.exists():
138
+ try:
139
+ data = json.loads(cache_file.read_text(encoding='utf-8'))
140
+ entry = CacheEntry(**data)
141
+
142
+ if not entry.is_expired(self.ttl):
143
+ entry.hit_count += 1
144
+ entry.last_accessed = time.time()
145
+ with self._lock:
146
+ self._add_to_memory(cache_key, entry)
147
+
148
+ try:
149
+ cache_file.write_text(json.dumps(asdict(entry)), encoding='utf-8')
150
+ except Exception:
151
+ pass
152
+
153
+ with self._lock:
154
+ self.stats["hits"] += 1
155
+ return entry.response
156
+ else:
157
+ try:
158
+ cache_file.unlink()
159
+ except Exception:
160
+ pass
161
+ with self._lock:
162
+ self.stats["expirations"] += 1
163
+ except Exception:
164
+ try:
165
+ cache_file.unlink()
166
+ except Exception:
167
+ pass
168
+
169
+ with self._lock:
170
+ self.stats["misses"] += 1
171
+ return None
172
+
173
+ def set(self, prompt: str, response: str, backend: str, model: str, ttl: Optional[int] = None):
174
+ """
175
+ 保存响应到缓存
176
+
177
+ 同时保存到:
178
+ - 内存缓存(快速访问)
179
+ - 磁盘缓存(持久化)
180
+
181
+ Args:
182
+ prompt: 提示词
183
+ response: LLM 响应
184
+ backend: LLM 后端
185
+ model: 模型名称
186
+ ttl: 过期时间(秒),None 表示使用默认 TTL
187
+ """
188
+ cache_key = self._hash_prompt(prompt, backend, model)
189
+ entry = CacheEntry(
190
+ prompt_hash=cache_key,
191
+ response=response,
192
+ backend=backend,
193
+ model=model,
194
+ timestamp=time.time(),
195
+ hit_count=0,
196
+ last_accessed=time.time()
197
+ )
198
+
199
+ with self._lock:
200
+ self._add_to_memory(cache_key, entry)
201
+
202
+ cache_file = self.cache_dir / f"{cache_key}.json"
203
+ try:
204
+ cache_file.write_text(json.dumps(asdict(entry)), encoding='utf-8')
205
+ with self._lock:
206
+ self.stats["sets"] += 1
207
+ except Exception as e:
208
+ logger.warning("Disk cache write failed: %s", e)
209
+
210
+ def _add_to_memory(self, key: str, entry: CacheEntry):
211
+ """添加到内存缓存,必要时执行 LRU 淘汰"""
212
+ if len(self.memory_cache) >= self.max_memory_entries:
213
+ # LRU 淘汰:删除最久未访问的条目
214
+ oldest_key = min(
215
+ self.memory_cache.keys(),
216
+ key=lambda k: self.memory_cache[k].last_accessed
217
+ )
218
+ del self.memory_cache[oldest_key]
219
+ self.stats["evictions"] += 1
220
+
221
+ self.memory_cache[key] = entry
222
+
223
+ def is_available(self) -> bool:
224
+ """
225
+ 检查缓存是否可用
226
+
227
+ Returns:
228
+ True 表示可用,False 表示不可用(需要降级)
229
+
230
+ 检查项:
231
+ - 缓存目录是否存在
232
+ - 缓存目录是否可写
233
+ """
234
+ try:
235
+ # 检查缓存目录是否存在且可写
236
+ if not self.cache_dir.exists():
237
+ return False
238
+
239
+ # 尝试创建测试文件
240
+ test_file = self.cache_dir / ".test_write"
241
+ try:
242
+ test_file.write_text("test")
243
+ test_file.unlink()
244
+ return True
245
+ except Exception:
246
+ return False
247
+ except Exception:
248
+ return False
249
+
250
+ def get_stats(self) -> Dict[str, Any]:
251
+ """
252
+ 获取缓存统计信息
253
+
254
+ Returns:
255
+ 包含命中率、条目数等统计信息的字典
256
+ """
257
+ total_requests = self.stats["hits"] + self.stats["misses"]
258
+ hit_rate = self.stats["hits"] / total_requests if total_requests > 0 else 0.0
259
+
260
+ # 统计磁盘缓存
261
+ try:
262
+ disk_entries = len(list(self.cache_dir.glob("*.json")))
263
+ except Exception:
264
+ disk_entries = 0
265
+
266
+ # 计算总命中次数
267
+ total_hits = sum(e.hit_count for e in self.memory_cache.values())
268
+
269
+ # 计算总大小(兼容 Protocol 接口)
270
+ try:
271
+ total_size = sum(f.stat().st_size for f in self.cache_dir.glob("*.json"))
272
+ except Exception:
273
+ total_size = 0
274
+
275
+ return {
276
+ # Protocol 要求的字段
277
+ "hit_count": self.stats["hits"],
278
+ "miss_count": self.stats["misses"],
279
+ "hit_rate": hit_rate,
280
+ "total_size": total_size,
281
+ "entry_count": len(self.memory_cache) + disk_entries,
282
+
283
+ # 额外的统计信息
284
+ "total_requests": total_requests,
285
+ "hits": self.stats["hits"],
286
+ "misses": self.stats["misses"],
287
+ "hit_rate_percent": f"{hit_rate * 100:.1f}%",
288
+ "memory_entries": len(self.memory_cache),
289
+ "disk_entries": disk_entries,
290
+ "total_hits": total_hits,
291
+ "sets": self.stats["sets"],
292
+ "evictions": self.stats["evictions"],
293
+ "expirations": self.stats["expirations"],
294
+ "cache_dir": str(self.cache_dir),
295
+ "ttl_hours": self.ttl / 3600,
296
+ }
297
+
298
+ def get_top_cached(self, limit: int = 10) -> List[Dict[str, Any]]:
299
+ """获取最常访问的缓存条目"""
300
+ sorted_entries = sorted(
301
+ self.memory_cache.items(),
302
+ key=lambda x: x[1].hit_count,
303
+ reverse=True
304
+ )[:limit]
305
+
306
+ return [
307
+ {
308
+ "hash": key,
309
+ "backend": entry.backend,
310
+ "model": entry.model,
311
+ "hit_count": entry.hit_count,
312
+ "age_hours": entry.age_hours(),
313
+ "response_preview": entry.response[:100] + "..." if len(entry.response) > 100 else entry.response
314
+ }
315
+ for key, entry in sorted_entries
316
+ ]
317
+
318
+ def clear(self):
319
+ """
320
+ 清空所有缓存
321
+
322
+ 实现 CacheProvider Protocol 接口。
323
+ 清空内存缓存和磁盘缓存,重置统计信息。
324
+ """
325
+ # 清空磁盘缓存
326
+ try:
327
+ for f in self.cache_dir.glob("*.json"):
328
+ try:
329
+ f.unlink()
330
+ except Exception as e:
331
+ logger.warning("Failed to delete cache file %s: %s", f, e)
332
+ except Exception as e:
333
+ logger.warning("Failed to clear disk cache: %s", e)
334
+
335
+ # 清空内存缓存
336
+ self.memory_cache.clear()
337
+
338
+ # 重置统计信息
339
+ self.stats = {k: 0 for k in self.stats}
340
+
341
+ def clear_old(self, older_than_hours: float):
342
+ """
343
+ 清除旧缓存(保留此方法以保持向后兼容)
344
+
345
+ Args:
346
+ older_than_hours: 清除超过指定小时数的缓存
347
+ """
348
+ threshold = time.time() - (older_than_hours * 3600)
349
+
350
+ # 清除内存
351
+ to_remove = [
352
+ k for k, v in self.memory_cache.items()
353
+ if v.timestamp < threshold
354
+ ]
355
+ for k in to_remove:
356
+ del self.memory_cache[k]
357
+
358
+ # 清除磁盘
359
+ for cache_file in self.cache_dir.glob("*.json"):
360
+ try:
361
+ data = json.loads(cache_file.read_text(encoding='utf-8'))
362
+ if data.get("timestamp", 0) < threshold:
363
+ cache_file.unlink()
364
+ except Exception as e:
365
+ logger.warning("Failed to clean cache file %s: %s", cache_file, e)
366
+
367
+ def invalidate(self, prompt: str, backend: str, model: str):
368
+ """使特定缓存失效"""
369
+ cache_key = self._hash_prompt(prompt, backend, model)
370
+
371
+ # 从内存删除
372
+ if cache_key in self.memory_cache:
373
+ del self.memory_cache[cache_key]
374
+
375
+ # 从磁盘删除
376
+ cache_file = self.cache_dir / f"{cache_key}.json"
377
+ if cache_file.exists():
378
+ cache_file.unlink()
379
+
380
+ def export_stats_report(self) -> str:
381
+ """导出统计报告(Markdown 格式)"""
382
+ stats = self.get_stats()
383
+ top_cached = self.get_top_cached(5)
384
+
385
+ report = f"""# LLM Cache Statistics Report
386
+
387
+ **Generated**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
388
+
389
+ ## Overall Performance
390
+
391
+ | Metric | Value |
392
+ |--------|-------|
393
+ | Total Requests | {stats['total_requests']} |
394
+ | Cache Hits | {stats['hits']} |
395
+ | Cache Misses | {stats['misses']} |
396
+ | Hit Rate | {stats['hit_rate_percent']} |
397
+ | Memory Entries | {stats['memory_entries']} |
398
+ | Disk Entries | {stats['disk_entries']} |
399
+
400
+ ## Cache Operations
401
+
402
+ | Opera| Count |
403
+ |-----------|-------|
404
+ | Sets | {stats['sets']} |
405
+ | Evictions | {stats['evictions']} |
406
+ | Expirations | {stats['expirations']} |
407
+
408
+ ## Configuration
409
+
410
+ - Cache Directory: `{stats['cache_dir']}`
411
+ - TTL: {stats['ttl_hours']:.1f} hours
412
+ - Max Memory Entries: {self.max_memory_entries}
413
+
414
+ ## Top Cached Entries
415
+
416
+ """
417
+ for i, entry in enumerate(top_cached, 1):
418
+ report += f"{i}. **{entry['backend']}:{entry['model']}** - {entry['hit_count']} hits ({entry['age_hours']:.1f}h old)\n"
419
+
420
+ return report
421
+
422
+
423
+ # 全局单例
424
+ _cache_instance: Optional[LLMCache] = None
425
+
426
+
427
+ def get_llm_cache(cache_dir: Optional[str] = None,
428
+ ttl_seconds: int = 86400) -> LLMCache:
429
+ """
430
+ 获取全局 LLM 缓存实例(单例模式)
431
+
432
+ Args:
433
+ cache_dir: 缓存目录
434
+ ttl_seconds: 缓存过期时间(秒)
435
+
436
+ Returns:
437
+ LLMCache 实例
438
+ """
439
+ global _cache_instance
440
+ if _cache_instance is None:
441
+ _cache_instance = LLMCache(cache_dir, ttl_seconds)
442
+ return _cache_instance
443
+
444
+
445
+ def reset_cache():
446
+ """重置全局缓存实例(主要用于测试)"""
447
+ global _cache_instance
448
+ _cache_instance = None