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,317 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Standardized Role Template (V2) — Agent Skills SKILL.md Anatomy
5
+
6
+ Aligns with Google Agent Skills SKILL.md structure:
7
+ overview (What) → when_to_use/when_not_to_use (When)
8
+ → process_steps (How) → rationalizations/red_flags (Warnings)
9
+ → verification_requirements (Proof)
10
+
11
+ Spec reference: SPEC_V35_Agent_Skills_Quality_Framework.md Section 7.1
12
+ """
13
+
14
+ import hashlib
15
+ from dataclasses import dataclass, field
16
+ from datetime import datetime
17
+ from typing import Any, Dict, List, Optional
18
+
19
+
20
+ @dataclass
21
+ class StandardizedRoleTemplate:
22
+ """
23
+ Standardized role template following Agent Skills SKILL.md anatomy.
24
+
25
+ Structure:
26
+ 1. Identity: name, description, role_id, triggers
27
+ 2. Context: overview (what), when_to_use (+), when_not_to_use (-)
28
+ 3. Process: process_steps (how to execute)
29
+ 4. Quality: rationalizations (anti-patterns), red_flags (warnings)
30
+ 5. Verification: verification_requirements (proof needed)
31
+ 6. Execution: prompt_template (actual LLM prompt)
32
+ """
33
+
34
+ # === Identity Fields (Required) ===
35
+ name: str = ""
36
+ description: str = ""
37
+ role_id: str = ""
38
+ triggers: List[str] = field(default_factory=list)
39
+ version: str = "2.0.0"
40
+ author: str = ""
41
+ category: str = "general"
42
+ tags: List[str] = field(default_factory=list)
43
+
44
+ # === Context Fields (NEW in V2) ===
45
+ overview: str = "" # What this template does (1-2 sentences)
46
+ when_to_use: str = "" # Positive scenarios when to apply
47
+ when_not_to_use: str = "" # Negative scenarios when NOT to apply
48
+
49
+ # === Process Field (Structured steps) ===
50
+ process_steps: List[str] = field(default_factory=list) # Step-by-step guide
51
+
52
+ # === Quality Guardrails (NEW in V2, from P0-1 AntiRationalization) ===
53
+ rationalizations: List[Dict[str, str]] = field(default_factory=list)
54
+ # Format: [{"excuse": "...", "reality": "..."}]
55
+
56
+ # === Warning Flags (NEW in V2, from P0-2 VerificationGate) ===
57
+ red_flags: List[Dict[str, str]] = field(default_factory=list)
58
+ # Format: [{"flag": "...", "severity": "critical/warning", "description": "..."}]
59
+
60
+ # === Verification Requirements (NEW in V2) ===
61
+ verification_requirements: List[str] = field(default_factory=list)
62
+ # Format: ["Must have test output", "Build must succeed", ...]
63
+
64
+ # === Execution Field ===
65
+ prompt_template: str = "" # The actual prompt injected into Worker
66
+
67
+ # === Metadata (Auto-generated) ===
68
+ template_id: str = field(default="")
69
+ created_at: str = field(default="")
70
+ updated_at: str = field(default="")
71
+ rating: float = 0.0
72
+ install_count: int = 0
73
+
74
+ def __post_init__(self):
75
+ now = datetime.now().isoformat()
76
+ if not self.created_at:
77
+ object.__setattr__(self, 'created_at', now)
78
+ if not self.updated_at:
79
+ object.__setattr__(self, 'updated_at', now)
80
+
81
+ if not self.template_id:
82
+ raw = f"{self.name}:{self.role_id}:{self.author}:{now}"
83
+ tid = f"std-tpl-{hashlib.sha256(raw.encode()).hexdigest()[:12]}"
84
+ object.__setattr__(self, 'template_id', tid)
85
+
86
+ def validate(self) -> List[str]:
87
+ """Validate all required fields. Returns list of error messages."""
88
+ errors = []
89
+
90
+ if not self.name or len(self.name) < 2:
91
+ errors.append("name must be at least 2 characters")
92
+ if not self.role_id or len(self.role_id) < 2:
93
+ errors.append("role_id must be at least 2 characters")
94
+ if not self.author:
95
+ errors.append("author is required")
96
+
97
+ required_v2_fields = [
98
+ ('overview', 'overview is required for V2 templates'),
99
+ ('when_to_use', 'when_to_use is required for V2 templates'),
100
+ ('process_steps', 'process_steps must have at least 1 step'),
101
+ ('prompt_template', 'prompt_template is required'),
102
+ ]
103
+
104
+ for field_name, error_msg in required_v2_fields:
105
+ value = getattr(self, field_name)
106
+ if isinstance(value, list):
107
+ if len(value) == 0:
108
+ errors.append(error_msg)
109
+ elif not value:
110
+ errors.append(error_msg)
111
+
112
+ return errors
113
+
114
+ def is_valid(self) -> bool:
115
+ """Quick validity check."""
116
+ return len(self.validate()) == 0
117
+
118
+ def to_dict(self) -> Dict[str, Any]:
119
+ """Serialize to dictionary."""
120
+ result = {}
121
+ for key in self.__dataclass_fields__:
122
+ value = getattr(self, key)
123
+ if not key.startswith('_'):
124
+ result[key] = value
125
+ return result
126
+
127
+ @classmethod
128
+ def from_legacy(cls, legacy_data: Dict[str, Any]) -> 'StandardizedRoleTemplate':
129
+ """
130
+ Convert legacy RoleTemplate format to V2 standardized format.
131
+
132
+ Legacy format: {name, description, role_id, role_prompt, ...}
133
+ V2 format: Adds overview, when_to_use, process_steps, etc.
134
+ """
135
+ instance = cls(
136
+ name=legacy_data.get('name', ''),
137
+ description=legacy_data.get('description', ''),
138
+ role_id=legacy_data.get('role_id', ''),
139
+ triggers=legacy_data.get('triggers', []),
140
+ version=legacy_data.get('version', '2.0.0'),
141
+ author=legacy_data.get('author', ''),
142
+ category=legacy_data.get('category', 'general'),
143
+ tags=legacy_data.get('tags', []),
144
+ prompt_template=legacy_data.get('role_prompt', legacy_data.get('prompt_template', '')),
145
+ overview=legacy_data.get('description', ''), # Fallback
146
+ when_to_use=f"When working on tasks related to {legacy_data.get('category', 'general')}",
147
+ when_not_to_use="When the task does not match this role's expertise",
148
+ process_steps=[
149
+ f"Analyze task requirements as {legacy_data.get('role_id', 'this role')}",
150
+ f"Apply {legacy_data.get('name', 'this')} expertise to the problem",
151
+ "Document findings and recommendations",
152
+ ],
153
+ rationalizations=[],
154
+ red_flags=[],
155
+ verification_requirements=[
156
+ "Output must address the specific task requirements",
157
+ "Recommendations must be actionable and specific",
158
+ ],
159
+ )
160
+ return instance
161
+
162
+ @classmethod
163
+ def from_dict(cls, data: Dict[str, Any]) -> 'StandardizedRoleTemplate':
164
+ """Deserialize from dictionary."""
165
+ valid_keys = cls.__dataclass_fields__.keys()
166
+ filtered = {k: v for k, v in data.items() if k in valid_keys and not k.startswith('_')}
167
+ return cls(**filtered)
168
+
169
+ def to_markdown(self) -> str:
170
+ """Generate SKILL.md-style markdown representation."""
171
+ lines = [
172
+ f"# {self.name}",
173
+ f"",
174
+ f"> **Version**: {self.version} | **Author**: {self.author} | **Category**: {self.category}",
175
+ f"",
176
+ f"## Overview",
177
+ f"",
178
+ f"{self.overview}",
179
+ f"",
180
+ f"## When to Use",
181
+ f"",
182
+ f"- ✅ {self.when_to_use}",
183
+ f"- ❌ {self.when_not_to_use}",
184
+ f"",
185
+ ]
186
+
187
+ if self.process_steps:
188
+ lines.extend([
189
+ f"## Process Steps",
190
+ f"",
191
+ ])
192
+ for i, step in enumerate(self.process_steps, 1):
193
+ lines.append(f"{i}. {step}")
194
+ lines.append("")
195
+
196
+ if self.rationalizations:
197
+ lines.extend([
198
+ f"## Common Rationalizations (Anti-Patterns)",
199
+ f"",
200
+ "| Excuse | Reality |",
201
+ "|-------|--------|",
202
+ ])
203
+ for r in self.rationalizations:
204
+ lines.append(f"| {r.get('excuse', '')} | {r.get('reality', '')} |")
205
+ lines.append("")
206
+
207
+ if self.red_flags:
208
+ lines.extend([
209
+ f"## Red Flags (Warnings)",
210
+ f"",
211
+ ])
212
+ for rf in self.red_flags:
213
+ severity = rf.get('severity', 'warning').upper()
214
+ lines.append(f"- ⚠️ [{severity}] {rf.get('description', rf.get('flag', ''))}")
215
+ lines.append("")
216
+
217
+ if self.verification_requirements:
218
+ lines.extend([
219
+ f"## Verification Requirements",
220
+ f"",
221
+ ])
222
+ for vr in self.verification_requirements:
223
+ lines.append(f"- [ ] {vr}")
224
+ lines.append("")
225
+
226
+ if self.tags:
227
+ lines.extend([
228
+ f"**Tags**: {', '.join(self.tags)}",
229
+ f"",
230
+ ])
231
+
232
+ return "\n".join(lines)
233
+
234
+
235
+ def create_example_template() -> StandardizedRoleTemplate:
236
+ """Create an example standardized template for testing/documentation."""
237
+ return StandardizedRoleTemplate(
238
+ name="OWASP Security Auditor",
239
+ description="Security auditor with OWASP Top 10 focus",
240
+ role_id="security",
241
+ triggers=["security", "vulnerability", "audit", "OWASP"],
242
+ version="2.0.0",
243
+ author="DevSquad Team",
244
+ category="security",
245
+ tags=["owasp", "audit", "compliance", "web-security"],
246
+ overview=(
247
+ "Performs systematic security review following OWASP Top 10 guidelines. "
248
+ "Identifies vulnerabilities, assesses risk levels, and provides remediation guidance."
249
+ ),
250
+ when_to_use=(
251
+ "When reviewing code for security vulnerabilities, "
252
+ "conducting security audits, or assessing compliance with OWASP standards"
253
+ ),
254
+ when_not_to_use=(
255
+ "When the task is purely functional (no security implications), "
256
+ "or when a quick syntax check is sufficient without deep analysis"
257
+ ),
258
+ process_steps=[
259
+ "Map application components to OWASP categories (A01-A10)",
260
+ "Review each component against relevant OWASP checks",
261
+ "Document findings with severity (Critical/High/Medium/Low)",
262
+ "Provide specific remediation code examples",
263
+ "Summarize risk assessment and recommend priorities",
264
+ ],
265
+ rationalizations=[
266
+ {
267
+ "excuse": "This is internal tool, no need for full audit",
268
+ "reality": "Internal tools often face production exposure. Audit thoroughly.",
269
+ },
270
+ {
271
+ "excuse": "No known vulnerabilities in this codebase",
272
+ "reality": "Unknown vulnerabilities are the most dangerous. Systematic review finds hidden issues.",
273
+ },
274
+ {
275
+ "excuse": "Security review will slow down delivery",
276
+ "reality": "Security debt compounds faster than technical debt. Early review prevents costly rewrites.",
277
+ },
278
+ ],
279
+ red_flags=[
280
+ {
281
+ "flag": "no_input_validation",
282
+ "severity": "critical",
283
+ "description": "User input not validated/sanitized before processing",
284
+ },
285
+ {
286
+ "flag": "hardcoded_secrets",
287
+ "severity": "critical",
288
+ "description": "API keys, passwords, or tokens found in source code",
289
+ },
290
+ {
291
+ "flag": "sql_injection_risk",
292
+ "severity": "critical",
293
+ "description": "Dynamic SQL queries without parameterized statements",
294
+ },
295
+ {
296
+ "flag": "no_authentication_check",
297
+ "severity": "warning",
298
+ "description": "Endpoint lacks authentication verification",
299
+ },
300
+ ],
301
+ verification_requirements=[
302
+ "All OWASP A01-A10 categories reviewed",
303
+ "Each finding has severity classification",
304
+ "Remediation examples provided for Critical/High findings",
305
+ "Summary report includes risk score",
306
+ ],
307
+ prompt_template=(
308
+ "You are an OWASP Security Auditor. Follow these instructions:\n\n"
309
+ "1. Review the provided code against OWASP Top 10\n"
310
+ "2. For each vulnerability found:\n"
311
+ " - Classify severity (Critical/High/Medium/Low)\n"
312
+ " - Provide CVE/CWE reference if applicable\n"
313
+ " - Show vulnerable code and fixed code\n"
314
+ "3. Output structured report with findings table\n\n"
315
+ "Remember: Security is never 'good enough'. Be thorough."
316
+ ),
317
+ )
@@ -0,0 +1,237 @@
1
+ #!/usr/bin/env python3
2
+ import json
3
+ import re
4
+ import logging
5
+ from pathlib import Path
6
+ from typing import Dict, List, Optional
7
+ from datetime import datetime
8
+ from dataclasses import dataclass, field
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ @dataclass
14
+ class TaskCompletionResult:
15
+ task_id: str
16
+ is_completed: bool
17
+ completion_rate: float = 0.0
18
+ total_subtasks: int = 0
19
+ completed_subtasks: int = 0
20
+ failed_subtasks: int = 0
21
+ pending_subtasks: int = 0
22
+ details: List[Dict] = field(default_factory=list)
23
+ summary: str = ""
24
+
25
+
26
+ class TaskCompletionChecker:
27
+ """
28
+ Task completion checker for DevSquad dispatch results.
29
+
30
+ Features:
31
+ 1. Check WorkerResult success/failure status
32
+ 2. Track dispatch execution progress
33
+ 3. Support checkpoint-based resume
34
+ 4. Generate completion reports
35
+ """
36
+
37
+ def __init__(self, storage_path: str = "./task_progress"):
38
+ self.storage_path = Path(storage_path)
39
+ self.storage_path.mkdir(parents=True, exist_ok=True)
40
+ self.progress_file = self.storage_path / "progress.json"
41
+ self.progress = self._load_progress()
42
+
43
+ def _load_progress(self) -> Dict:
44
+ if self.progress_file.exists():
45
+ try:
46
+ with open(self.progress_file, 'r', encoding='utf-8') as f:
47
+ return json.load(f)
48
+ except Exception as e:
49
+ logger.warning("Failed to load progress: %s", e)
50
+ return self._create_empty_progress()
51
+
52
+ def _create_empty_progress(self) -> Dict:
53
+ return {
54
+ "last_update": datetime.now().isoformat(),
55
+ "dispatches": {},
56
+ }
57
+
58
+ def _save_progress(self):
59
+ try:
60
+ self.progress["last_update"] = datetime.now().isoformat()
61
+ with open(self.progress_file, 'w', encoding='utf-8') as f:
62
+ json.dump(self.progress, f, ensure_ascii=False, indent=2)
63
+ except Exception as e:
64
+ logger.warning("Failed to save progress: %s", e)
65
+
66
+ def check_dispatch_result(self, dispatch_result) -> TaskCompletionResult:
67
+ """
68
+ Check completion status of a DispatchResult.
69
+
70
+ Args:
71
+ dispatch_result: DispatchResult from MultiAgentDispatcher.dispatch()
72
+
73
+ Returns:
74
+ TaskCompletionResult with detailed completion info
75
+ """
76
+ worker_results = getattr(dispatch_result, 'worker_results', [])
77
+ task_id = getattr(dispatch_result, 'task_description', 'unknown')[:50]
78
+
79
+ total = len(worker_results)
80
+ completed = sum(1 for w in worker_results if w.get('success'))
81
+ failed = sum(1 for w in worker_results if not w.get('success'))
82
+ pending = 0
83
+
84
+ completion_rate = (completed / total * 100) if total > 0 else 0.0
85
+ is_completed = completed == total and total > 0
86
+
87
+ details = []
88
+ for wr in worker_results:
89
+ wr_detail = {
90
+ 'role': wr.get('role_id', wr.get('role', 'unknown')),
91
+ 'role_name': wr.get('role_name', wr.get('role', 'unknown')),
92
+ 'success': wr.get('success', False),
93
+ 'error': wr.get('error'),
94
+ 'output_preview': (str(wr.get('output', ''))[:100] if wr.get('output') else None),
95
+ }
96
+
97
+ # P0-2: Apply VerificationGate (P0-2)
98
+ try:
99
+ from scripts.collaboration.verification_gate import get_shared_gate
100
+ if not hasattr(self, '_vgate'):
101
+ self._vgate = get_shared_gate()
102
+ ctx = self._vgate.build_context_from_worker_result(wr)
103
+ gate_result = self._vgate.check(ctx)
104
+ if not gate_result.passed:
105
+ wr_detail['verification'] = {
106
+ 'passed': False,
107
+ 'verdict': gate_result.verdict,
108
+ 'red_flags': [rf.description for rf in gate_result.red_flags],
109
+ 'missing_evidence': [e.description for e in gate_result.missing_evidence],
110
+ }
111
+ # Track blocked workers for consensus awareness
112
+ if not hasattr(self, '_blocked_workers'):
113
+ self._blocked_workers = set()
114
+ self._blocked_workers.add(
115
+ wr.get('role_id', wr.get('role', 'unknown'))
116
+ )
117
+ else:
118
+ wr_detail['verification'] = {'passed': True, 'verdict': 'APPROVE'}
119
+ except Exception as ve:
120
+ logger.debug("VerificationGate error: %s", ve)
121
+
122
+ details.append(wr_detail)
123
+
124
+ summary_parts = []
125
+ if is_completed:
126
+ summary_parts.append(f"All {total} workers completed successfully.")
127
+ else:
128
+ summary_parts.append(f"{completed}/{total} workers succeeded, {failed} failed.")
129
+
130
+ result = TaskCompletionResult(
131
+ task_id=task_id,
132
+ is_completed=is_completed,
133
+ completion_rate=round(completion_rate, 2),
134
+ total_subtasks=total,
135
+ completed_subtasks=completed,
136
+ failed_subtasks=failed,
137
+ pending_subtasks=pending,
138
+ details=details,
139
+ summary=" ".join(summary_parts),
140
+ )
141
+
142
+ self._record_dispatch(task_id, result)
143
+ return result
144
+
145
+ def _record_dispatch(self, task_id: str, result: TaskCompletionResult):
146
+ self.progress["dispatches"][task_id] = {
147
+ "is_completed": result.is_completed,
148
+ "completion_rate": result.completion_rate,
149
+ "total": result.total_subtasks,
150
+ "completed": result.completed_subtasks,
151
+ "failed": result.failed_subtasks,
152
+ "checked_at": datetime.now().isoformat(),
153
+ }
154
+ self._save_progress()
155
+
156
+ def check_schedule_result(self, schedule_result) -> TaskCompletionResult:
157
+ """
158
+ Check completion status of a ScheduleResult from Coordinator.
159
+
160
+ Args:
161
+ schedule_result: ScheduleResult from Coordinator.execute_plan()
162
+
163
+ Returns:
164
+ TaskCompletionResult with detailed completion info
165
+ """
166
+ total = schedule_result.total_tasks
167
+ completed = schedule_result.completed_tasks
168
+ failed = schedule_result.failed_tasks
169
+ pending = total - completed - failed
170
+
171
+ completion_rate = (completed / total * 100) if total > 0 else 0.0
172
+ is_completed = completed == total and total > 0
173
+
174
+ details = []
175
+ for wr in schedule_result.results:
176
+ details.append({
177
+ 'role': wr.worker_id,
178
+ 'success': wr.success,
179
+ 'error': wr.error,
180
+ 'duration': wr.duration_seconds,
181
+ })
182
+
183
+ summary_parts = []
184
+ if is_completed:
185
+ summary_parts.append(f"All {total} tasks completed successfully.")
186
+ else:
187
+ summary_parts.append(f"{completed}/{total} tasks succeeded, {failed} failed.")
188
+
189
+ if schedule_result.errors:
190
+ summary_parts.append(f"Errors: {len(schedule_result.errors)}")
191
+
192
+ return TaskCompletionResult(
193
+ task_id=schedule_result.results[0].task_id if schedule_result.results else "unknown",
194
+ is_completed=is_completed,
195
+ completion_rate=round(completion_rate, 2),
196
+ total_subtasks=total,
197
+ completed_subtasks=completed,
198
+ failed_subtasks=failed,
199
+ pending_subtasks=max(0, pending),
200
+ details=details,
201
+ summary=" ".join(summary_parts),
202
+ )
203
+
204
+ def get_dispatch_history(self) -> Dict:
205
+ return self.progress.get("dispatches", {})
206
+
207
+ def get_completion_summary(self) -> str:
208
+ dispatches = self.progress.get("dispatches", {})
209
+ if not dispatches:
210
+ return "No dispatch history found."
211
+
212
+ total = len(dispatches)
213
+ completed = sum(1 for d in dispatches.values() if d.get("is_completed"))
214
+ avg_rate = sum(d.get("completion_rate", 0) for d in dispatches.values()) / total
215
+
216
+ lines = [
217
+ f"# Task Completion Summary",
218
+ f"",
219
+ f"- Total dispatches: {total}",
220
+ f"- Fully completed: {completed}",
221
+ f"- Average completion rate: {avg_rate:.1f}%",
222
+ f"",
223
+ ]
224
+
225
+ for task_id, data in dispatches.items():
226
+ status = "✅" if data.get("is_completed") else "❌"
227
+ lines.append(f"- {status} {task_id}: {data.get('completion_rate', 0):.1f}%")
228
+
229
+ return "\n".join(lines)
230
+
231
+ def is_task_completed(self, task_id: str) -> bool:
232
+ dispatch_data = self.progress.get("dispatches", {}).get(task_id)
233
+ return dispatch_data.get("is_completed", False) if dispatch_data else False
234
+
235
+ def reset_progress(self):
236
+ self.progress = self._create_empty_progress()
237
+ self._save_progress()