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.
- devsquad-3.6.0.dist-info/METADATA +944 -0
- devsquad-3.6.0.dist-info/RECORD +95 -0
- devsquad-3.6.0.dist-info/WHEEL +5 -0
- devsquad-3.6.0.dist-info/entry_points.txt +2 -0
- devsquad-3.6.0.dist-info/licenses/LICENSE +21 -0
- devsquad-3.6.0.dist-info/top_level.txt +2 -0
- scripts/__init__.py +0 -0
- scripts/ai_semantic_matcher.py +512 -0
- scripts/alert_manager.py +505 -0
- scripts/api/__init__.py +43 -0
- scripts/api/models.py +386 -0
- scripts/api/routes/__init__.py +20 -0
- scripts/api/routes/dispatch.py +348 -0
- scripts/api/routes/lifecycle.py +330 -0
- scripts/api/routes/metrics_gates.py +347 -0
- scripts/api_server.py +318 -0
- scripts/auth.py +451 -0
- scripts/cli/__init__.py +1 -0
- scripts/cli/cli_visual.py +642 -0
- scripts/cli.py +1094 -0
- scripts/collaboration/__init__.py +212 -0
- scripts/collaboration/_version.py +1 -0
- scripts/collaboration/agent_briefing.py +656 -0
- scripts/collaboration/ai_semantic_matcher.py +260 -0
- scripts/collaboration/anchor_checker.py +281 -0
- scripts/collaboration/anti_rationalization.py +470 -0
- scripts/collaboration/async_integration_example.py +255 -0
- scripts/collaboration/batch_scheduler.py +149 -0
- scripts/collaboration/checkpoint_manager.py +561 -0
- scripts/collaboration/ci_feedback_adapter.py +351 -0
- scripts/collaboration/code_map_generator.py +247 -0
- scripts/collaboration/concern_pack_loader.py +352 -0
- scripts/collaboration/confidence_score.py +496 -0
- scripts/collaboration/config_loader.py +188 -0
- scripts/collaboration/consensus.py +244 -0
- scripts/collaboration/context_compressor.py +533 -0
- scripts/collaboration/coordinator.py +668 -0
- scripts/collaboration/dispatcher.py +1636 -0
- scripts/collaboration/dual_layer_context.py +128 -0
- scripts/collaboration/enhanced_worker.py +539 -0
- scripts/collaboration/feature_usage_tracker.py +206 -0
- scripts/collaboration/five_axis_consensus.py +334 -0
- scripts/collaboration/input_validator.py +401 -0
- scripts/collaboration/integration_example.py +287 -0
- scripts/collaboration/intent_workflow_mapper.py +350 -0
- scripts/collaboration/language_parsers.py +269 -0
- scripts/collaboration/lifecycle_protocol.py +1446 -0
- scripts/collaboration/llm_backend.py +453 -0
- scripts/collaboration/llm_cache.py +448 -0
- scripts/collaboration/llm_cache_async.py +347 -0
- scripts/collaboration/llm_retry.py +387 -0
- scripts/collaboration/llm_retry_async.py +389 -0
- scripts/collaboration/mce_adapter.py +597 -0
- scripts/collaboration/memory_bridge.py +1607 -0
- scripts/collaboration/models.py +537 -0
- scripts/collaboration/null_providers.py +297 -0
- scripts/collaboration/operation_classifier.py +289 -0
- scripts/collaboration/output_slicer.py +225 -0
- scripts/collaboration/performance_monitor.py +462 -0
- scripts/collaboration/permission_guard.py +865 -0
- scripts/collaboration/prompt_assembler.py +756 -0
- scripts/collaboration/prompt_variant_generator.py +483 -0
- scripts/collaboration/protocols.py +267 -0
- scripts/collaboration/report_formatter.py +352 -0
- scripts/collaboration/retrospective.py +279 -0
- scripts/collaboration/role_matcher.py +92 -0
- scripts/collaboration/role_template_market.py +352 -0
- scripts/collaboration/rule_collector.py +678 -0
- scripts/collaboration/scratchpad.py +346 -0
- scripts/collaboration/skill_registry.py +151 -0
- scripts/collaboration/skillifier.py +878 -0
- scripts/collaboration/standardized_role_template.py +317 -0
- scripts/collaboration/task_completion_checker.py +237 -0
- scripts/collaboration/test_quality_guard.py +695 -0
- scripts/collaboration/unified_gate_engine.py +598 -0
- scripts/collaboration/usage_tracker.py +309 -0
- scripts/collaboration/user_friendly_error.py +176 -0
- scripts/collaboration/verification_gate.py +312 -0
- scripts/collaboration/warmup_manager.py +635 -0
- scripts/collaboration/worker.py +513 -0
- scripts/collaboration/workflow_engine.py +684 -0
- scripts/dashboard.py +1088 -0
- scripts/generate_benchmark_report.py +786 -0
- scripts/history_manager.py +604 -0
- scripts/mcp_server.py +289 -0
- skills/__init__.py +32 -0
- skills/dispatch/handler.py +52 -0
- skills/intent/handler.py +59 -0
- skills/registry.py +67 -0
- skills/retrospective/__init__.py +0 -0
- skills/retrospective/handler.py +125 -0
- skills/review/handler.py +356 -0
- skills/security/handler.py +454 -0
- skills/test/__init__.py +0 -0
- skills/test/handler.py +78 -0
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Role Template Market
|
|
5
|
+
|
|
6
|
+
A marketplace for sharing and discovering role templates:
|
|
7
|
+
- Users can publish their custom role templates
|
|
8
|
+
- Community members can browse, install, and rate templates
|
|
9
|
+
- Templates include role prompts, rules, and configuration presets
|
|
10
|
+
- Compatible with SkillRegistry for cross-session persistence
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
from scripts.collaboration.role_template_market import RoleTemplateMarket
|
|
14
|
+
|
|
15
|
+
market = RoleTemplateMarket()
|
|
16
|
+
market.publish(template)
|
|
17
|
+
results = market.search("security auditor")
|
|
18
|
+
market.install(results[0].template_id)
|
|
19
|
+
|
|
20
|
+
Version: v1.0
|
|
21
|
+
Created: 2026-05-01
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
import hashlib
|
|
26
|
+
import logging
|
|
27
|
+
import os
|
|
28
|
+
import re
|
|
29
|
+
from typing import Dict, List, Any, Optional
|
|
30
|
+
from dataclasses import dataclass, field, asdict
|
|
31
|
+
from datetime import datetime
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
|
|
34
|
+
logger = logging.getLogger(__name__)
|
|
35
|
+
|
|
36
|
+
_SAFE_ID_RE = re.compile(r'[^\w\-.]')
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class RoleTemplate:
|
|
41
|
+
"""A reusable role template definition."""
|
|
42
|
+
template_id: str = ""
|
|
43
|
+
name: str = ""
|
|
44
|
+
description: str = ""
|
|
45
|
+
role_id: str = ""
|
|
46
|
+
role_prompt: str = ""
|
|
47
|
+
author: str = ""
|
|
48
|
+
version: str = "1.0.0"
|
|
49
|
+
category: str = "general"
|
|
50
|
+
tags: List[str] = field(default_factory=list)
|
|
51
|
+
rules: List[Dict[str, Any]] = field(default_factory=list)
|
|
52
|
+
config_overrides: Dict[str, Any] = field(default_factory=dict)
|
|
53
|
+
rating: float = 0.0
|
|
54
|
+
install_count: int = 0
|
|
55
|
+
created_at: str = field(default_factory=lambda: datetime.now().isoformat())
|
|
56
|
+
updated_at: str = field(default_factory=lambda: datetime.now().isoformat())
|
|
57
|
+
|
|
58
|
+
def __post_init__(self):
|
|
59
|
+
if not self.template_id:
|
|
60
|
+
raw = f"{self.name}:{self.role_id}:{self.author}:{datetime.now().isoformat()}"
|
|
61
|
+
self.template_id = f"tpl-{hashlib.md5(raw.encode()).hexdigest()[:8]}"
|
|
62
|
+
|
|
63
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
64
|
+
return asdict(self)
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
def from_dict(cls, data: Dict[str, Any]) -> 'RoleTemplate':
|
|
68
|
+
valid_keys = cls.__dataclass_fields__.keys()
|
|
69
|
+
return cls(**{k: v for k, v in data.items() if k in valid_keys})
|
|
70
|
+
|
|
71
|
+
def validate(self) -> List[str]:
|
|
72
|
+
"""Validate template fields. Returns list of error messages."""
|
|
73
|
+
errors = []
|
|
74
|
+
if not self.name or len(self.name) < 2:
|
|
75
|
+
errors.append("name must be at least 2 characters")
|
|
76
|
+
if not self.role_id or len(self.role_id) < 2:
|
|
77
|
+
errors.append("role_id must be at least 2 characters")
|
|
78
|
+
if not self.role_prompt or len(self.role_prompt) < 10:
|
|
79
|
+
errors.append("role_prompt must be at least 10 characters")
|
|
80
|
+
if not self.author:
|
|
81
|
+
errors.append("author is required")
|
|
82
|
+
if self.rating < 0 or self.rating > 5:
|
|
83
|
+
errors.append("rating must be between 0 and 5")
|
|
84
|
+
return errors
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass
|
|
88
|
+
class TemplateRating:
|
|
89
|
+
"""A user rating for a template."""
|
|
90
|
+
template_id: str = ""
|
|
91
|
+
user_id: str = ""
|
|
92
|
+
score: float = 0.0
|
|
93
|
+
comment: str = ""
|
|
94
|
+
created_at: str = field(default_factory=lambda: datetime.now().isoformat())
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class RoleTemplateMarket:
|
|
98
|
+
"""
|
|
99
|
+
Role Template Market - discover, share, and install role templates.
|
|
100
|
+
|
|
101
|
+
Features:
|
|
102
|
+
- Publish custom role templates for community use
|
|
103
|
+
- Search templates by keyword, category, or tags
|
|
104
|
+
- Rate and review templates
|
|
105
|
+
- Install templates for use in dispatch sessions
|
|
106
|
+
- Export/import templates as JSON files
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
def __init__(self, storage_dir: Optional[str] = None):
|
|
110
|
+
"""
|
|
111
|
+
Initialize Role Template Market.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
storage_dir: Directory for template persistence
|
|
115
|
+
(default: data/role_templates)
|
|
116
|
+
"""
|
|
117
|
+
self.storage_dir = Path(storage_dir or "data/role_templates")
|
|
118
|
+
self.storage_dir.mkdir(parents=True, exist_ok=True)
|
|
119
|
+
self._templates: Dict[str, RoleTemplate] = {}
|
|
120
|
+
self._ratings: Dict[str, List[TemplateRating]] = {}
|
|
121
|
+
self._load_from_disk()
|
|
122
|
+
|
|
123
|
+
def _load_from_disk(self):
|
|
124
|
+
"""Load templates from disk storage."""
|
|
125
|
+
templates_file = self.storage_dir / "templates.json"
|
|
126
|
+
if templates_file.exists():
|
|
127
|
+
try:
|
|
128
|
+
data = json.loads(templates_file.read_text(encoding='utf-8'))
|
|
129
|
+
for tpl_data in data:
|
|
130
|
+
tpl = RoleTemplate.from_dict(tpl_data)
|
|
131
|
+
self._templates[tpl.template_id] = tpl
|
|
132
|
+
except Exception as e:
|
|
133
|
+
logger.warning("Failed to load templates: %s", e)
|
|
134
|
+
|
|
135
|
+
ratings_file = self.storage_dir / "ratings.json"
|
|
136
|
+
if ratings_file.exists():
|
|
137
|
+
try:
|
|
138
|
+
data = json.loads(ratings_file.read_text(encoding='utf-8'))
|
|
139
|
+
for r_data in data:
|
|
140
|
+
rating = TemplateRating(**r_data)
|
|
141
|
+
tid = rating.template_id
|
|
142
|
+
if tid not in self._ratings:
|
|
143
|
+
self._ratings[tid] = []
|
|
144
|
+
self._ratings[tid].append(rating)
|
|
145
|
+
except Exception as e:
|
|
146
|
+
logger.warning("Failed to load ratings: %s", e)
|
|
147
|
+
|
|
148
|
+
def _save_to_disk(self):
|
|
149
|
+
"""Persist templates and ratings to disk."""
|
|
150
|
+
try:
|
|
151
|
+
templates_file = self.storage_dir / "templates.json"
|
|
152
|
+
templates_data = [tpl.to_dict() for tpl in self._templates.values()]
|
|
153
|
+
templates_file.write_text(
|
|
154
|
+
json.dumps(templates_data, indent=2, ensure_ascii=False),
|
|
155
|
+
encoding='utf-8'
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
ratings_file = self.storage_dir / "ratings.json"
|
|
159
|
+
all_ratings = []
|
|
160
|
+
for ratings_list in self._ratings.values():
|
|
161
|
+
all_ratings.extend([asdict(r) for r in ratings_list])
|
|
162
|
+
ratings_file.write_text(
|
|
163
|
+
json.dumps(all_ratings, indent=2, ensure_ascii=False),
|
|
164
|
+
encoding='utf-8'
|
|
165
|
+
)
|
|
166
|
+
except Exception as e:
|
|
167
|
+
logger.warning("Failed to save templates: %s", e)
|
|
168
|
+
|
|
169
|
+
def publish(self, template: RoleTemplate) -> str:
|
|
170
|
+
"""
|
|
171
|
+
Publish a role template to the market.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
template: RoleTemplate to publish
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
str: template_id of the published template
|
|
178
|
+
|
|
179
|
+
Raises:
|
|
180
|
+
ValueError: If template validation fails
|
|
181
|
+
"""
|
|
182
|
+
errors = template.validate()
|
|
183
|
+
if errors:
|
|
184
|
+
raise ValueError(f"Template validation failed: {'; '.join(errors)}")
|
|
185
|
+
|
|
186
|
+
self._templates[template.template_id] = template
|
|
187
|
+
self._save_to_disk()
|
|
188
|
+
logger.info("Published template: %s (%s)", template.name, template.template_id)
|
|
189
|
+
return template.template_id
|
|
190
|
+
|
|
191
|
+
def search(self, query: str = "", category: str = "",
|
|
192
|
+
tags: List[str] = None, limit: int = 20) -> List[RoleTemplate]:
|
|
193
|
+
"""
|
|
194
|
+
Search templates by keyword, category, or tags.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
query: Search keyword (matches name, description, role_id)
|
|
198
|
+
category: Filter by category
|
|
199
|
+
tags: Filter by tags (OR match)
|
|
200
|
+
limit: Maximum results to return
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
List of matching RoleTemplate objects, sorted by rating
|
|
204
|
+
"""
|
|
205
|
+
results = []
|
|
206
|
+
query_lower = query.lower() if query else ""
|
|
207
|
+
|
|
208
|
+
for tpl in self._templates.values():
|
|
209
|
+
if category and tpl.category != category:
|
|
210
|
+
continue
|
|
211
|
+
|
|
212
|
+
if tags:
|
|
213
|
+
if not any(t in tpl.tags for t in tags):
|
|
214
|
+
continue
|
|
215
|
+
|
|
216
|
+
if query_lower:
|
|
217
|
+
searchable = f"{tpl.name} {tpl.description} {tpl.role_id} {' '.join(tpl.tags)}".lower()
|
|
218
|
+
if query_lower not in searchable:
|
|
219
|
+
continue
|
|
220
|
+
|
|
221
|
+
results.append(tpl)
|
|
222
|
+
|
|
223
|
+
results.sort(key=lambda t: t.rating, reverse=True)
|
|
224
|
+
return results[:limit]
|
|
225
|
+
|
|
226
|
+
def get(self, template_id: str) -> Optional[RoleTemplate]:
|
|
227
|
+
"""Get a template by ID."""
|
|
228
|
+
return self._templates.get(template_id)
|
|
229
|
+
|
|
230
|
+
def install(self, template_id: str) -> Optional[RoleTemplate]:
|
|
231
|
+
"""
|
|
232
|
+
Install a template for use in dispatch sessions.
|
|
233
|
+
|
|
234
|
+
Increments install count and returns the template.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
template_id: Template ID to install
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
RoleTemplate if found, None otherwise
|
|
241
|
+
"""
|
|
242
|
+
tpl = self._templates.get(template_id)
|
|
243
|
+
if not tpl:
|
|
244
|
+
return None
|
|
245
|
+
|
|
246
|
+
tpl.install_count += 1
|
|
247
|
+
tpl.updated_at = datetime.now().isoformat()
|
|
248
|
+
self._save_to_disk()
|
|
249
|
+
|
|
250
|
+
install_dir = self.storage_dir / "installed"
|
|
251
|
+
install_dir.mkdir(exist_ok=True)
|
|
252
|
+
safe_id = _SAFE_ID_RE.sub('_', template_id)
|
|
253
|
+
install_file = install_dir / f"{safe_id}.json"
|
|
254
|
+
install_file.write_text(
|
|
255
|
+
json.dumps(tpl.to_dict(), indent=2, ensure_ascii=False),
|
|
256
|
+
encoding='utf-8'
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
logger.info("Installed template: %s (%s)", tpl.name, template_id)
|
|
260
|
+
return tpl
|
|
261
|
+
|
|
262
|
+
def rate(self, template_id: str, user_id: str, score: float,
|
|
263
|
+
comment: str = "") -> bool:
|
|
264
|
+
"""
|
|
265
|
+
Rate a template.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
template_id: Template ID to rate
|
|
269
|
+
user_id: Rater's user ID
|
|
270
|
+
score: Rating score (0-5)
|
|
271
|
+
comment: Optional comment
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
True if rating was recorded, False if template not found
|
|
275
|
+
"""
|
|
276
|
+
tpl = self._templates.get(template_id)
|
|
277
|
+
if not tpl:
|
|
278
|
+
return False
|
|
279
|
+
|
|
280
|
+
if score < 0 or score > 5:
|
|
281
|
+
raise ValueError("Score must be between 0 and 5")
|
|
282
|
+
|
|
283
|
+
rating = TemplateRating(
|
|
284
|
+
template_id=template_id,
|
|
285
|
+
user_id=user_id,
|
|
286
|
+
score=score,
|
|
287
|
+
comment=comment,
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
if template_id not in self._ratings:
|
|
291
|
+
self._ratings[template_id] = []
|
|
292
|
+
self._ratings[template_id].append(rating)
|
|
293
|
+
|
|
294
|
+
all_scores = [r.score for r in self._ratings[template_id]]
|
|
295
|
+
tpl.rating = round(sum(all_scores) / len(all_scores), 1)
|
|
296
|
+
tpl.updated_at = datetime.now().isoformat()
|
|
297
|
+
|
|
298
|
+
self._save_to_disk()
|
|
299
|
+
return True
|
|
300
|
+
|
|
301
|
+
def list_categories(self) -> List[str]:
|
|
302
|
+
"""List all available template categories."""
|
|
303
|
+
return sorted(set(tpl.category for tpl in self._templates.values()))
|
|
304
|
+
|
|
305
|
+
def get_popular(self, limit: int = 10) -> List[RoleTemplate]:
|
|
306
|
+
"""Get most popular templates by install count."""
|
|
307
|
+
sorted_tpls = sorted(
|
|
308
|
+
self._templates.values(),
|
|
309
|
+
key=lambda t: t.install_count,
|
|
310
|
+
reverse=True
|
|
311
|
+
)
|
|
312
|
+
return sorted_tpls[:limit]
|
|
313
|
+
|
|
314
|
+
def export_template(self, template_id: str, output_path: str) -> bool:
|
|
315
|
+
"""Export a template to a JSON file."""
|
|
316
|
+
tpl = self._templates.get(template_id)
|
|
317
|
+
if not tpl:
|
|
318
|
+
return False
|
|
319
|
+
try:
|
|
320
|
+
Path(output_path).write_text(
|
|
321
|
+
json.dumps(tpl.to_dict(), indent=2, ensure_ascii=False),
|
|
322
|
+
encoding='utf-8'
|
|
323
|
+
)
|
|
324
|
+
return True
|
|
325
|
+
except Exception as e:
|
|
326
|
+
logger.warning("Export failed: %s", e)
|
|
327
|
+
return False
|
|
328
|
+
|
|
329
|
+
def import_template(self, input_path: str) -> Optional[str]:
|
|
330
|
+
"""Import a template from a JSON file."""
|
|
331
|
+
try:
|
|
332
|
+
data = json.loads(Path(input_path).read_text(encoding='utf-8'))
|
|
333
|
+
tpl = RoleTemplate.from_dict(data)
|
|
334
|
+
return self.publish(tpl)
|
|
335
|
+
except Exception as e:
|
|
336
|
+
logger.warning("Import failed: %s", e)
|
|
337
|
+
return None
|
|
338
|
+
|
|
339
|
+
def get_stats(self) -> Dict[str, Any]:
|
|
340
|
+
"""Get market statistics."""
|
|
341
|
+
return {
|
|
342
|
+
"total_templates": len(self._templates),
|
|
343
|
+
"total_categories": len(self.list_categories()),
|
|
344
|
+
"total_ratings": sum(len(r) for r in self._ratings.values()),
|
|
345
|
+
"total_installs": sum(t.install_count for t in self._templates.values()),
|
|
346
|
+
"avg_rating": round(
|
|
347
|
+
sum(t.rating for t in self._templates.values()) / len(self._templates), 1
|
|
348
|
+
) if self._templates else 0.0,
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
__all__ = ["RoleTemplate", "TemplateRating", "RoleTemplateMarket"]
|