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,878 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Skillifier - Automatic Skill Generation System
5
+
6
+ Analyzes successful execution histories, extracts reusable patterns,
7
+ generates Skill proposals with quality validation, and publishes to SkillRegistry.
8
+
9
+ Core workflow:
10
+ ExecutionRecord → Pattern Extraction → Generalization → SkillProposal → Validation → Publish
11
+ """
12
+
13
+ import re
14
+ import json
15
+ import uuid
16
+ import hashlib
17
+ import threading
18
+ from dataclasses import dataclass, field
19
+ from datetime import datetime, timedelta
20
+ from enum import Enum
21
+ from typing import Any, Callable, Dict, List, Optional, Set, Tuple
22
+
23
+ try:
24
+ from .permission_guard import ActionType as PGActionType
25
+ except ImportError:
26
+ class PGActionType(Enum):
27
+ FILE_READ = "file_read"
28
+ FILE_CREATE = "file_create"
29
+ FILE_MODIFY = "file_modify"
30
+ FILE_DELETE = "file_delete"
31
+ SHELL_EXECUTE = "shell_execute"
32
+ NETWORK_REQUEST = "network_request"
33
+ GIT_OPERATION = "git_operation"
34
+ ENVIRONMENT = "environment"
35
+ PROCESS_SPAWN = "process_spawn"
36
+
37
+
38
+ class ProposalStatus(Enum):
39
+ DRAFT = "draft"
40
+ REVIEWING = "reviewing"
41
+ APPROVED = "approved"
42
+ PUBLISHED = "published"
43
+ REJECTED = "rejected"
44
+
45
+
46
+ class SkillCategory(Enum):
47
+ CODE_GENERATION = "code-generation"
48
+ CODE_REVIEW = "code-review"
49
+ TESTING = "testing"
50
+ DEPLOYMENT = "deployment"
51
+ REFACTORING = "refactoring"
52
+ DOCUMENTATION = "documentation"
53
+ ANALYSIS = "analysis"
54
+ INTEGRATION = "integration"
55
+ SECURITY = "security"
56
+ PERFORMANCE = "performance"
57
+ AUTO_GENERATED = "auto-generated"
58
+
59
+
60
+ # ============================================================
61
+ # Data Models - Execution Layer
62
+ # ============================================================
63
+
64
+ @dataclass
65
+ class ExecutionStep:
66
+ step_order: int
67
+ action_type: PGActionType
68
+ target: str
69
+ description: str
70
+ outcome: str = "success"
71
+ duration_ms: int = 0
72
+ input_data: Optional[str] = None
73
+ output_data: Optional[str] = None
74
+
75
+ def to_dict(self) -> Dict:
76
+ return {
77
+ "step_order": self.step_order,
78
+ "action_type": self.action_type.value,
79
+ "target": self.target,
80
+ "description": self.description,
81
+ "outcome": self.outcome,
82
+ "duration_ms": self.duration_ms,
83
+ }
84
+
85
+ @classmethod
86
+ def from_dict(cls, d: Dict) -> "ExecutionStep":
87
+ return cls(
88
+ step_order=d.get("step_order", 0),
89
+ action_type=PGActionType(d.get("action_type", "file_read")),
90
+ target=d.get("target", ""),
91
+ description=d.get("description", ""),
92
+ outcome=d.get("outcome", "success"),
93
+ duration_ms=d.get("duration_ms", 0),
94
+ )
95
+
96
+
97
+ @dataclass
98
+ class ExecutionRecord:
99
+ record_id: str = field(default_factory=lambda: f"er-{uuid.uuid4().hex[:12]}")
100
+ task_description: str = ""
101
+ start_time: datetime = field(default_factory=datetime.now)
102
+ end_time: Optional[datetime] = None
103
+ duration_seconds: float = 0.0
104
+ success: bool = True
105
+ worker_id: str = ""
106
+ role_id: str = ""
107
+ steps: List[ExecutionStep] = field(default_factory=list)
108
+ artifacts: List[str] = field(default_factory=list)
109
+ metadata: Dict[str, Any] = field(default_factory=dict)
110
+
111
+ def finalize(self):
112
+ if self.end_time is None:
113
+ self.end_time = datetime.now()
114
+ if self.duration_seconds == 0.0 and self.start_time:
115
+ delta = self.end_time - self.start_time
116
+ self.duration_seconds = max(0.0, delta.total_seconds())
117
+
118
+ def to_dict(self) -> Dict:
119
+ return {
120
+ "record_id": self.record_id,
121
+ "task_description": self.task_description,
122
+ "success": self.success,
123
+ "worker_id": self.worker_id,
124
+ "role_id": self.role_id,
125
+ "step_count": len(self.steps),
126
+ "duration_seconds": round(self.duration_seconds, 2),
127
+ "artifacts": self.artifacts,
128
+ }
129
+
130
+ @classmethod
131
+ def from_dict(cls, d: Dict) -> "ExecutionRecord":
132
+ return cls(
133
+ record_id=d.get("record_id", f"er-{uuid.uuid4().hex[:12]}"),
134
+ task_description=d.get("task_description", ""),
135
+ success=d.get("success", True),
136
+ worker_id=d.get("worker_id", ""),
137
+ role_id=d.get("role_id", ""),
138
+ )
139
+
140
+
141
+ # ============================================================
142
+ # Data Models - Pattern Layer
143
+ # ============================================================
144
+
145
+ @dataclass
146
+ class PatternStep:
147
+ action_type: PGActionType
148
+ target_pattern: str
149
+ description_template: str
150
+ is_required: bool = True
151
+ estimated_risk: float = 0.0
152
+
153
+ def to_dict(self) -> Dict:
154
+ return {
155
+ "action_type": self.action_type.value,
156
+ "target_pattern": self.target_pattern,
157
+ "description_template": self.description_template,
158
+ "is_required": self.is_required,
159
+ "estimated_risk": self.estimated_risk,
160
+ }
161
+
162
+
163
+ @dataclass
164
+ class SuccessPattern:
165
+ pattern_id: str = field(default_factory=lambda: f"sp-{uuid.uuid4().hex[:10]}")
166
+ name: str = ""
167
+ description: str = ""
168
+ source_records: List[str] = field(default_factory=list)
169
+ steps_template: List[PatternStep] = field(default_factory=list)
170
+ trigger_keywords: List[str] = field(default_factory=list)
171
+ applicable_roles: List[str] = field(default_factory=list)
172
+ frequency: int = 1
173
+ confidence: float = 0.5
174
+ avg_success_rate: float = 1.0
175
+ created_at: datetime = field(default_factory=datetime.now)
176
+
177
+ def to_dict(self) -> Dict:
178
+ return {
179
+ "pattern_id": self.pattern_id,
180
+ "name": self.name,
181
+ "description": self.description,
182
+ "frequency": self.frequency,
183
+ "confidence": round(self.confidence, 3),
184
+ "avg_success_rate": round(self.avg_success_rate, 3),
185
+ "step_count": len(self.steps_template),
186
+ "trigger_keywords": self.trigger_keywords,
187
+ "source_record_count": len(self.source_records),
188
+ }
189
+
190
+
191
+ # ============================================================
192
+ # Data Models - Skill Proposal Layer
193
+ # ============================================================
194
+
195
+ @dataclass
196
+ class SkillStepDef:
197
+ step_number: int
198
+ action_type: PGActionType
199
+ target_pattern: str
200
+ description: str
201
+ is_required: bool = True
202
+
203
+
204
+ @dataclass
205
+ class ValidationResult:
206
+ score: float = 0.0
207
+ completeness: float = 0.0
208
+ specificity: float = 0.0
209
+ repeatability: float = 0.0
210
+ safety: float = 0.0
211
+ issues: List[str] = field(default_factory=list)
212
+ suggestions: List[str] = field(default_factory=list)
213
+
214
+ def grade(self) -> str:
215
+ if self.score >= 85:
216
+ return "A"
217
+ elif self.score >= 70:
218
+ return "B"
219
+ elif self.score >= 55:
220
+ return "C"
221
+ else:
222
+ return "D"
223
+
224
+ def to_dict(self) -> Dict:
225
+ return {
226
+ "score": round(self.score, 1),
227
+ "grade": self.grade(),
228
+ "completeness": round(self.completeness, 1),
229
+ "specificity": round(self.specificity, 1),
230
+ "repeatability": round(self.repeatability, 1),
231
+ "safety": round(self.safety, 1),
232
+ "issues": self.issues,
233
+ "suggestions": self.suggestions,
234
+ }
235
+
236
+
237
+ @dataclass
238
+ class SkillProposal:
239
+ proposal_id: str = field(default_factory=lambda: f"prop-{uuid.uuid4().hex[:10]}")
240
+ name: str = ""
241
+ slug: str = ""
242
+ version: str = "1.0.0"
243
+ description: str = ""
244
+ category: str = "auto-generated"
245
+ trigger_conditions: List[str] = field(default_factory=list)
246
+ steps: List[SkillStepDef] = field(default_factory=list)
247
+ required_roles: List[str] = field(default_factory=list)
248
+ input_schema: Dict[str, Any] = field(default_factory=dict)
249
+ output_schema: Dict[str, Any] = field(default_factory=dict)
250
+ acceptance_criteria: List[str] = field(default_factory=list)
251
+ source_pattern: Optional[str] = None
252
+ quality_score: float = 0.0
253
+ validation_result: Optional[ValidationResult] = None
254
+ status: ProposalStatus = ProposalStatus.DRAFT
255
+ created_at: datetime = field(default_factory=datetime.now)
256
+ approved_by: Optional[str] = None
257
+ published_at: Optional[datetime] = None
258
+
259
+ def _generate_slug(self):
260
+ slug = re.sub(r'[^a-zA-Z0-9\s-]', '', self.name.lower())
261
+ slug = re.sub(r'\s+', '-', slug.strip())
262
+ return slug or "unnamed-skill"
263
+
264
+ def to_dict(self) -> Dict:
265
+ d = {
266
+ "proposal_id": self.proposal_id,
267
+ "name": self.name,
268
+ "slug": self.slug,
269
+ "version": self.version,
270
+ "description": self.description,
271
+ "category": self.category,
272
+ "status": self.status.value,
273
+ "step_count": len(self.steps),
274
+ "quality_score": round(self.quality_score, 1),
275
+ "created_at": self.created_at.isoformat(),
276
+ }
277
+ if self.validation_result:
278
+ d["validation"] = self.validation_result.to_dict()
279
+ if self.published_at:
280
+ d["published_at"] = self.published_at.isoformat()
281
+ return d
282
+
283
+
284
+ # ============================================================
285
+ # Skillifier Core Engine
286
+ # ============================================================
287
+
288
+ class Skillifier:
289
+ """Automatic skill generation from execution history analysis"""
290
+
291
+ DEFAULT_MIN_OCCURRENCES = 2
292
+ DEFAULT_MIN_CONFIDENCE = 0.6
293
+
294
+ CATEGORY_KEYWORDS = {
295
+ SkillCategory.CODE_GENERATION: ["create", "generate", "init", "setup", "build",
296
+ "implement", "develop", "write", "new"],
297
+ SkillCategory.CODE_REVIEW: ["review", "audit", "inspect", "check", "analyze-code",
298
+ "lint", "quality"],
299
+ SkillCategory.TESTING: ["test", "spec", "verify", "assert", "coverage", "pytest",
300
+ "unittest", "e2e"],
301
+ SkillCategory.DEPLOYMENT: ["deploy", "release", "ship", "publish", "ci/cd",
302
+ "docker", "kubernetes", "production"],
303
+ SkillCategory.REFACTORING: ["refactor", "cleanup", "optimize", "restructure",
304
+ "simplify", "improve"],
305
+ SkillCategory.DOCUMENTATION: ["document", "readme", "api-doc", "comment",
306
+ "wiki", "guide", "manual"],
307
+ SkillCategory.ANALYSIS: ["analyze", "diagnose", "investigate", "profile",
308
+ "benchmark", "measure"],
309
+ SkillCategory.INTEGRATION: ["integrate", "connect", "configure", "setup-env",
310
+ "pipeline", "workflow"],
311
+ SkillCategory.SECURITY: ["security", "vulnerability", "auth", "permission",
312
+ "encrypt", "scan"],
313
+ SkillCategory.PERFORMANCE: ["performance", "speed", "cache", "optimize-fast",
314
+ "latency", "throughput"],
315
+ }
316
+
317
+ def __init__(self,
318
+ min_pattern_occurrences: int = DEFAULT_MIN_OCCURRENCES,
319
+ min_confidence: float = DEFAULT_MIN_CONFIDENCE,
320
+ auto_analyze: bool = True):
321
+ self.min_occurrences = min_pattern_occurrences
322
+ self.min_confidence = min_confidence
323
+ self.auto_analyze = auto_analyze
324
+ self._records: List[ExecutionRecord] = []
325
+ self._patterns: List[SuccessPattern] = []
326
+ self._proposals: Dict[str, SkillProposal] = {}
327
+ self._lock = threading.RLock()
328
+
329
+ # ================================================================
330
+ # Record Management
331
+ # ================================================================
332
+
333
+ def record_execution(self, record: ExecutionRecord) -> None:
334
+ with self._lock:
335
+ record.finalize()
336
+ self._records.append(record)
337
+
338
+ def get_records(self, since: datetime = None, until: datetime = None,
339
+ success_only: bool = True) -> List[ExecutionRecord]:
340
+ results = self._records
341
+ if since:
342
+ results = [r for r in results if r.start_time >= since]
343
+ if until:
344
+ results = [r for r in results if r.start_time <= until]
345
+ if success_only:
346
+ results = [r for r in results if r.success]
347
+ return list(results)
348
+
349
+ # ================================================================
350
+ # Pattern Extraction
351
+ # ================================================================
352
+
353
+ def analyze_history(self, since: datetime = None,
354
+ until: datetime = None) -> List[SuccessPattern]:
355
+ with self._lock:
356
+ records = self.get_records(since=since, until=until, success_only=True)
357
+ if len(records) < self.min_occurrences:
358
+ return []
359
+
360
+ clusters = self._cluster_sequences(records)
361
+ patterns = []
362
+ for cluster_records in clusters.values():
363
+ if len(cluster_records) < self.min_occurrences:
364
+ continue
365
+ pattern = self._build_pattern_from_cluster(cluster_records)
366
+ if pattern.confidence >= self.min_confidence:
367
+ patterns.append(pattern)
368
+ existing_ids = {p.pattern_id for p in self._patterns}
369
+ if pattern.pattern_id not in existing_ids:
370
+ self._patterns.append(pattern)
371
+
372
+ patterns.sort(key=lambda p: p.confidence, reverse=True)
373
+ return patterns
374
+
375
+ def _cluster_sequences(self, records: List[ExecutionRecord]) -> Dict[int, List[ExecutionRecord]]:
376
+ clusters: Dict[int, List[ExecutionRecord]] = {}
377
+ cluster_id = 0
378
+
379
+ for record in records:
380
+ if len(record.steps) == 0:
381
+ continue
382
+ best_cluster = -1
383
+ best_similarity = -1.0
384
+
385
+ for cid, members in clusters.items():
386
+ rep = members[0]
387
+ sim = self._sequence_similarity(record.steps, rep.steps)
388
+ if sim > best_similarity and sim > 0.45:
389
+ best_similarity = sim
390
+ best_cluster = cid
391
+
392
+ if best_cluster >= 0:
393
+ clusters[best_cluster].append(record)
394
+ else:
395
+ clusters[cluster_id] = [record]
396
+ cluster_id += 1
397
+
398
+ return clusters
399
+
400
+ def _sequence_similarity(self, seq_a: List[ExecutionStep],
401
+ seq_b: List[ExecutionStep]) -> float:
402
+ if not seq_a or not seq_b:
403
+ return 0.0
404
+ len_a, len_b = len(seq_a), len(seq_b)
405
+ if abs(len_a - len_b) > max(len_a, len_b) * 0.7:
406
+ return 0.0
407
+
408
+ n = min(len_a, len_b)
409
+ total_sim = 0.0
410
+ match_count = 0
411
+ for i in range(n):
412
+ sim = self._step_similarity(seq_a[i], seq_b[i])
413
+ total_sim += sim
414
+ if sim > 0.3:
415
+ match_count += 1
416
+
417
+ base_score = total_sim / max(n, 1)
418
+ length_penalty = 1.0 - abs(len_a - len_b) / max(len_a, len_b, 1)
419
+ match_ratio = match_count / max(n, 1)
420
+ return base_score * 0.5 + length_penalty * 0.25 + match_ratio * 0.25
421
+
422
+ def _step_similarity(self, a: ExecutionStep, b: ExecutionStep) -> float:
423
+ if a.action_type != b.action_type:
424
+ return 0.0
425
+ score = 0.4
426
+ if self._extension_match(a.target, b.target):
427
+ score += 0.3
428
+ elif self._directory_match(a.target, b.target):
429
+ score += 0.15
430
+ word_overlap = self._word_overlap(a.description, b.description)
431
+ score += 0.2 * word_overlap
432
+ if a.outcome == b.outcome == "success":
433
+ score += 0.1
434
+ return min(1.0, score)
435
+
436
+ def _extension_match(self, target_a: str, target_b: str) -> bool:
437
+ ext_a = self._get_extension(target_a)
438
+ ext_b = self._get_extension(target_b)
439
+ return ext_a and ext_b and ext_a == ext_b
440
+
441
+ def _directory_match(self, target_a: str, target_b: str) -> bool:
442
+ parts_a = target_a.replace("\\", "/").rstrip("/").split("/")
443
+ parts_b = target_b.replace("\\", "/").rstrip("/").split("/")
444
+ if len(parts_a) < 2 or len(parts_b) < 2:
445
+ return False
446
+ return parts_a[:-1] == parts_b[:-1]
447
+
448
+ @staticmethod
449
+ def _get_extension(path: str) -> str:
450
+ if "." in path:
451
+ return path.rsplit(".", 1)[-1].lower()
452
+ return ""
453
+
454
+ @staticmethod
455
+ def _word_overlap(text_a: str, text_b: str) -> float:
456
+ words_a = set(re.findall(r'\w{2,}', text_a.lower()))
457
+ words_b = set(re.findall(r'\w{2,}', text_b.lower()))
458
+ if not words_a or not words_b:
459
+ return 0.0
460
+ intersection = words_a & words_b
461
+ union = words_a | words_b
462
+ return len(intersection) / len(union)
463
+
464
+ def _build_pattern_from_cluster(self, records: List[ExecutionRecord]) -> SuccessPattern:
465
+ rep = records[0]
466
+ all_steps = [r.steps for r in records if r.steps]
467
+ if not all_steps:
468
+ return SuccessPattern(name="empty-pattern")
469
+
470
+ min_len = min(len(s) for s in all_steps)
471
+ template_steps = []
472
+ for i in range(min_len):
473
+ step_samples = [s[i] for s in all_steps if i < len(s)]
474
+ if step_samples:
475
+ ps = self._generalize_step(step_samples)
476
+ template_steps.append(ps)
477
+
478
+ keywords = self._extract_trigger_keywords(rep.task_description, rep.steps)
479
+ roles = list(set(r.role_id for r in records if r.role_id))
480
+ success_rate = sum(1 for r in records if r.success) / max(len(records), 1)
481
+ confidence = self._calculate_confidence(records, template_steps)
482
+
483
+ name = self._generate_pattern_name(rep.task_description, template_steps)
484
+
485
+ return SuccessPattern(
486
+ name=name,
487
+ description=f"Auto-extracted pattern from {len(records)} executions",
488
+ source_records=[r.record_id for r in records],
489
+ steps_template=template_steps,
490
+ trigger_keywords=keywords,
491
+ applicable_roles=roles,
492
+ frequency=len(records),
493
+ confidence=confidence,
494
+ avg_success_rate=success_rate,
495
+ )
496
+
497
+ def _generalize_step(self, step_samples: List[ExecutionStep]) -> PatternStep:
498
+ sample = step_samples[0]
499
+ targets = [s.target for s in step_samples]
500
+ generalized_target = self._generalize_target(targets)
501
+ descriptions = [s.description for s in step_samples]
502
+ desc_template = self._generalize_description(descriptions)
503
+
504
+ risk_scores = []
505
+ has_error = any(s.outcome != "success" for s in step_samples)
506
+ avg_duration = sum(s.duration_ms for s in step_samples) / max(len(step_samples), 1)
507
+
508
+ return PatternStep(
509
+ action_type=sample.action_type,
510
+ target_pattern=generalized_target,
511
+ description_template=desc_template,
512
+ is_required=not has_error,
513
+ estimated_risk=min(1.0, avg_duration / 5000.0),
514
+ )
515
+
516
+ def _generalize_target(self, targets: List[str]) -> str:
517
+ if not targets:
518
+ return "*"
519
+ extensions = set(self._get_extension(t) for t in targets)
520
+ directories = set("/".join(t.replace("\\", "/").rstrip("/").split("/")[:-1])
521
+ for t in targets if "/" in t or "\\" in t)
522
+
523
+ if len(extensions) == 1 and list(extensions)[0]:
524
+ ext = list(extensions)[0]
525
+ if len(directories) == 1:
526
+ dir_part = list(directories)[0]
527
+ return f"{dir_part}/*.{ext}"
528
+ return f"*.{ext}"
529
+ if len(directories) == 1:
530
+ return f"{list(directories)[0]}/*"
531
+ return "*"
532
+
533
+ def _generalize_description(self, descriptions: List[str]) -> str:
534
+ if not descriptions:
535
+ return ""
536
+ common_words = []
537
+ if descriptions:
538
+ word_counts: Dict[str, int] = {}
539
+ for desc in descriptions:
540
+ for w in re.findall(r'\w{3,}', desc.lower()):
541
+ word_counts[w] = word_counts.get(w, 0) + 1
542
+ threshold = max(len(descriptions) * 0.6, 1)
543
+ common_words = [w for w, c in sorted(word_counts.items(), key=lambda x: -x[1])
544
+ if c >= threshold][:8]
545
+ return " ".join(common_words) if common_words else descriptions[0]
546
+
547
+ def _extract_trigger_keywords(self, task_desc: str,
548
+ steps: List[ExecutionStep]) -> List[str]:
549
+ keywords = set()
550
+ text = (task_desc + " " + " ".join(s.description for s in steps)).lower()
551
+ important = re.findall(r'\b\w{4,}\b', text)
552
+ stop_words = {"that", "with", "from", "this", "they", "have", "been",
553
+ "were", "their", "will", "would", "could", "should", "into"}
554
+ for w in important:
555
+ if w not in stop_words and len(w) >= 4:
556
+ keywords.add(w)
557
+ return sorted(list(keywords))[:10]
558
+
559
+ def _calculate_confidence(self, records: List[ExecutionRecord],
560
+ steps: List[PatternStep]) -> float:
561
+ freq_factor = min(1.0, len(records) / 10.0)
562
+ success_factor = sum(1 for r in records if r.success) / max(len(records), 1)
563
+ consistency = 1.0
564
+ if len(records) >= 2:
565
+ lengths = [len(r.steps) for r in records]
566
+ avg_len = sum(lengths) / len(lengths)
567
+ variance = sum((l - avg_len) ** 2 for l in lengths) / len(lengths)
568
+ consistency = max(0.3, 1.0 - variance / (avg_len ** 2 + 1))
569
+ step_quality = sum(1.0 - ps.estimated_risk for ps in steps) / max(len(steps), 1)
570
+ return (freq_factor * 0.3 + success_factor * 0.3 +
571
+ consistency * 0.2 + step_quality * 0.2)
572
+
573
+ def _generate_pattern_name(self, task_desc: str,
574
+ steps: List[PatternStep]) -> str:
575
+ if task_desc:
576
+ words = re.findall(r'[A-Za-z][A-Za-z0-9 ]{2,20}', task_desc)
577
+ if words:
578
+ return words[0].strip().title() + " Pattern"
579
+ if steps:
580
+ action_names = {s.action_type.value.replace("_", " ").title() for s in steps}
581
+ return " & ".join(sorted(action_names)[:3]) + " Pattern"
582
+ return "Unnamed Pattern"
583
+
584
+ # ================================================================
585
+ # Skill Generation
586
+ # ================================================================
587
+
588
+ def generate_skill(self, pattern: SuccessPattern) -> SkillProposal:
589
+ steps = [
590
+ SkillStepDef(
591
+ step_number=i + 1,
592
+ action_type=ps.action_type,
593
+ target_pattern=ps.target_pattern,
594
+ description=ps.description_template or ps.action_type.value,
595
+ is_required=ps.is_required,
596
+ )
597
+ for i, ps in enumerate(pattern.steps_template)
598
+ ]
599
+
600
+ category = self._classify_category(pattern)
601
+ slug = self._make_slug(pattern.name)
602
+ desc = self._generate_description(pattern, steps)
603
+ input_schema = self._infer_input_schema(steps)
604
+ output_schema = self._infer_output_schema(pattern)
605
+ acceptance = self._infer_acceptance_criteria(pattern)
606
+
607
+ proposal = SkillProposal(
608
+ name=pattern.name.replace(" Pattern", "").strip(),
609
+ slug=slug,
610
+ description=desc,
611
+ category=category.value,
612
+ trigger_conditions=list(pattern.trigger_keywords),
613
+ steps=steps,
614
+ required_roles=list(pattern.applicable_roles),
615
+ input_schema=input_schema,
616
+ output_schema=output_schema,
617
+ acceptance_criteria=acceptance,
618
+ source_pattern=pattern.pattern_id,
619
+ quality_score=pattern.confidence * 100,
620
+ status=ProposalStatus.DRAFT,
621
+ )
622
+ proposal.slug = proposal._generate_slug()
623
+
624
+ validation = self.validate_skill(proposal)
625
+ proposal.validation_result = validation
626
+ proposal.quality_score = validation.score
627
+ self._proposals[proposal.proposal_id] = proposal
628
+ return proposal
629
+
630
+ def _classify_category(self, pattern: SuccessPattern) -> SkillCategory:
631
+ text = (pattern.name + " " +
632
+ " ".join(pattern.trigger_keywords) +
633
+ " ".join(ps.description_template for ps in pattern.steps_template)).lower()
634
+ best_cat = SkillCategory.AUTO_GENERATED
635
+ best_score = 0
636
+ for cat, keywords in self.CATEGORY_KEYWORDS.items():
637
+ score = sum(1 for kw in keywords if kw in text)
638
+ if score > best_score:
639
+ best_score = score
640
+ best_cat = cat
641
+ return best_cat
642
+
643
+ def _make_slug(self, name: str) -> str:
644
+ slug = re.sub(r'[^a-z0-9\s-]', '', name.lower())
645
+ slug = re.sub(r'\s+', '-', slug.strip())
646
+ return slug or "auto-skill"
647
+
648
+ def _generate_description(self, pattern: SuccessPattern,
649
+ steps: List[SkillStepDef]) -> str:
650
+ action_types = [s.action_type.value.replace("_", " ") for s in steps]
651
+ unique_actions = list(dict.fromkeys(action_types))
652
+ return (f"Auto-generated skill: {' → '.join(unique_actions[:5])}. "
653
+ f"Based on {pattern.frequency} successful executions. "
654
+ f"Confidence: {pattern.confidence:.0%}.")
655
+
656
+ def _infer_input_schema(self, steps: List[SkillStepDef]) -> Dict[str, Any]:
657
+ schema: Dict[str, Any] = {}
658
+ has_wildcard = any("*" in s.target_pattern for s in steps)
659
+ if has_wildcard:
660
+ schema["target_path"] = {"type": "string", "description": "Target file/directory path"}
661
+ has_shell = any(s.action_type == PGActionType.SHELL_EXECUTE for s in steps)
662
+ if has_shell:
663
+ schema["command_args"] = {"type": "string", "description": "Command arguments"}
664
+ return schema
665
+
666
+ def _infer_output_schema(self, pattern: SuccessPattern) -> Dict[str, Any]:
667
+ create_steps = [ps for ps in pattern.steps_template
668
+ if ps.action_type in (PGActionType.FILE_CREATE,
669
+ PGActionType.FILE_MODIFY)]
670
+ if create_steps:
671
+ return {
672
+ "created_files": {"type": "array", "description": "Files created/modified"},
673
+ "artifacts": {"type": "array", "description": "Output artifacts"},
674
+ }
675
+ return {}
676
+
677
+ def _infer_acceptance_criteria(self, pattern: SuccessPattern) -> List[str]:
678
+ criteria = []
679
+ if pattern.avg_success_rate >= 0.9:
680
+ criteria.append(f"Historical success rate >= {pattern.avg_success_rate:.0%}")
681
+ if pattern.frequency >= 3:
682
+ criteria.append(f"Verified across {pattern.frequency}+ executions")
683
+ if pattern.steps_template:
684
+ criteria.append(f"All {len(pattern.steps_template)} steps completed successfully")
685
+ return criteria
686
+
687
+ # ================================================================
688
+ # Quality Validation (5-Dimension Scoring)
689
+ # ================================================================
690
+
691
+ def validate_skill(self, proposal: SkillProposal) -> ValidationResult:
692
+ completeness = self._score_completeness(proposal)
693
+ specificity = self._score_specificity(proposal)
694
+ repeatability = self._score_repeatability(proposal)
695
+ safety = self._score_safety(proposal)
696
+ practicality = self._score_practicality(proposal)
697
+
698
+ score = (completeness * 0.25 + specificity * 0.20 +
699
+ repeatability * 0.20 + safety * 0.20 + practicality * 0.15)
700
+
701
+ issues = []
702
+ suggestions = []
703
+
704
+ if completeness < 60:
705
+ issues.append("步骤定义不完整,缺少关键信息")
706
+ if len(proposal.steps) == 0:
707
+ issues.append("无任何步骤定义")
708
+ if len(proposal.steps) > 20:
709
+ issues.append(f"步骤过多({len(proposal.steps)}步),建议拆分")
710
+ if safety < 50:
711
+ issues.append("包含高风险操作")
712
+ if len(proposal.trigger_conditions) < 3:
713
+ issues.append("触发条件过于宽泛")
714
+ suggestions.append("增加更多触发关键词以提高特异性")
715
+ if repeatability < 50:
716
+ suggestions.append("需要更多成功执行记录来提高可重复性")
717
+ if completeness < 80:
718
+ suggestions.append("补充输入输出Schema和验收标准")
719
+
720
+ result = ValidationResult(
721
+ score=round(score, 1),
722
+ completeness=round(completeness, 1),
723
+ specificity=round(specificity, 1),
724
+ repeatability=round(repeatability, 1),
725
+ safety=round(safety, 1),
726
+ issues=issues,
727
+ suggestions=suggestions,
728
+ )
729
+ proposal.validation_result = result
730
+ return result
731
+
732
+ def _score_completeness(self, p: SkillProposal) -> float:
733
+ score = 100.0
734
+ if not p.name:
735
+ score -= 30
736
+ if not p.description:
737
+ score -= 15
738
+ if not p.steps:
739
+ score -= 40
740
+ if not p.input_schema:
741
+ score -= 10
742
+ if not p.output_schema:
743
+ score -= 5
744
+ if not p.acceptance_criteria:
745
+ score -= 10
746
+ for s in p.steps:
747
+ if not s.description or s.description == s.action_type.value:
748
+ score -= 3
749
+ return max(0.0, score)
750
+
751
+ def _score_specificity(self, p: SkillProposal) -> float:
752
+ score = 80.0
753
+ kw_count = len(p.trigger_conditions)
754
+ score += min(20, kw_count * 3)
755
+ generic_patterns = sum(1 for s in p.steps if s.target_pattern == "*")
756
+ score -= generic_patterns * 10
757
+ return max(0.0, min(100.0, score))
758
+
759
+ def _score_repeatability(self, p: SkillProposal) -> float:
760
+ score = 60.0
761
+ if p.source_pattern:
762
+ pattern = next((pat for pat in self._patterns
763
+ if pat.pattern_id == p.source_pattern), None)
764
+ if pattern:
765
+ score += min(30, pattern.frequency * 5)
766
+ score += pattern.avg_success_rate * 10
767
+ return max(0.0, min(100.0, score))
768
+
769
+ def _score_safety(self, p: SkillProposal) -> float:
770
+ score = 100.0
771
+ high_risk_types = {PGActionType.FILE_DELETE, PGActionType.SHELL_EXECUTE,
772
+ PGActionType.PROCESS_SPAWN}
773
+ for s in p.steps:
774
+ if s.action_type in high_risk_types:
775
+ score -= 15
776
+ if "*" in s.target_pattern and s.action_type in high_risk_types:
777
+ score -= 10
778
+ return max(0.0, score)
779
+
780
+ def _score_practicality(self, p: SkillProposal) -> float:
781
+ score = 70.0
782
+ n_steps = len(p.steps)
783
+ if 3 <= n_steps <= 15:
784
+ score += 20
785
+ elif n_steps < 3:
786
+ score -= 15
787
+ elif n_steps <= 20:
788
+ score += 5
789
+ else:
790
+ score -= 20
791
+ return max(0.0, min(100.0, score))
792
+
793
+ # ================================================================
794
+ # Publishing & Discovery
795
+ # ================================================================
796
+
797
+ def approve_and_publish(self, proposal_id: str,
798
+ approver: str = "system") -> bool:
799
+ with self._lock:
800
+ proposal = self._proposals.get(proposal_id)
801
+ if not proposal:
802
+ return False
803
+ if proposal.status == ProposalStatus.PUBLISHED:
804
+ return True
805
+ proposal.status = ProposalStatus.PUBLISHED
806
+ proposal.approved_by = approver
807
+ proposal.published_at = datetime.now()
808
+ return True
809
+
810
+ def suggest_skills_for_task(self, task_description: str) -> List[SkillProposal]:
811
+ task_lower = task_description.lower()
812
+ task_words = set(re.findall(r'\w{3,}', task_lower))
813
+ scored = []
814
+ for prop in self._proposals.values():
815
+ if prop.status not in (ProposalStatus.APPROVED, ProposalStatus.PUBLISHED):
816
+ continue
817
+ score = 0.0
818
+ for tc in prop.trigger_conditions:
819
+ if tc.lower() in task_lower:
820
+ score += 2.0
821
+ for tw in task_words:
822
+ if tw in tc.lower():
823
+ score += 0.5
824
+ if prop.quality_score >= 70:
825
+ score += 1.0
826
+ if score > 0:
827
+ scored.append((score, prop))
828
+
829
+ scored.sort(key=lambda x: -x[0])
830
+ return [p for _, p in scored[:10]]
831
+
832
+ # ================================================================
833
+ # Query & Export
834
+ # ================================================================
835
+
836
+ def get_pattern_library(self) -> List[SuccessPattern]:
837
+ return list(self._patterns)
838
+
839
+ def get_proposals(self, status=None) -> List[SkillProposal]:
840
+ props = list(self._proposals.values())
841
+ if status:
842
+ props = [p for p in props if p.status == status]
843
+ return props
844
+
845
+ def export_patterns(self) -> str:
846
+ return json.dumps([p.to_dict() for p in self._patterns],
847
+ indent=2, ensure_ascii=False, default=str)
848
+
849
+ def export_state(self) -> Dict:
850
+ with self._lock:
851
+ return {
852
+ "records_count": len(self._records),
853
+ "patterns_count": len(self._patterns),
854
+ "proposals_count": len(self._proposals),
855
+ "patterns": [p.to_dict() for p in self._patterns],
856
+ "proposal_ids": list(self._proposals.keys()),
857
+ }
858
+
859
+ def get_statistics(self) -> Dict[str, Any]:
860
+ with self._lock:
861
+ published = sum(1 for p in self._proposals.values()
862
+ if p.status == ProposalStatus.PUBLISHED)
863
+ avg_confidence = 0.0
864
+ if self._patterns:
865
+ avg_confidence = sum(p.confidence for p in self._patterns) / len(self._patterns)
866
+ avg_quality = 0.0
867
+ validated = [p for p in self._proposals.values() if p.validation_result]
868
+ if validated:
869
+ avg_quality = sum(p.quality_score for p in validated) / len(validated)
870
+ return {
871
+ "total_records": len(self._records),
872
+ "successful_records": sum(1 for r in self._records if r.success),
873
+ "total_patterns": len(self._patterns),
874
+ "total_proposals": len(self._proposals),
875
+ "published_skills": published,
876
+ "avg_pattern_confidence": round(avg_confidence, 3),
877
+ "avg_quality_score": round(avg_quality, 1),
878
+ }