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,267 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ DevSquad Protocol Interface Definitions
5
+
6
+ Defines abstract interfaces (Protocol) for core DevSquad modules, supporting:
7
+ - Multiple implementations (Redis, filesystem, memory, etc.)
8
+ - Degradation mechanism (Null Provider)
9
+ - Dependency injection and test mocking
10
+ - Runtime availability detection
11
+
12
+ Based on Python 3.8+ typing.Protocol (structural subtyping).
13
+
14
+ Version: v1.0
15
+ Created: 2026-05-01
16
+ """
17
+
18
+ from typing import Protocol, Optional, Dict, Any, List, Callable
19
+
20
+
21
+ # ============================================================================
22
+ # CacheProvider: LLM response cache interface
23
+ # ============================================================================
24
+
25
+ class CacheProvider(Protocol):
26
+ """
27
+ LLM response cache interface.
28
+
29
+ Use cases:
30
+ - Cache LLM API responses to reduce duplicate calls
31
+ - Support multiple backends (filesystem, Redis, memory, etc.)
32
+ - Provide TTL expiration mechanism
33
+
34
+ Implementations:
35
+ - LLMCache (filesystem-based)
36
+ - RedisCache (Redis-based)
37
+ - MemoryCache (in-memory)
38
+ - NullCacheProvider (no-op, for degradation)
39
+ """
40
+
41
+ def get(self, prompt: str, backend: str, model: str) -> Optional[str]:
42
+ """Retrieve cached response. Returns None on cache miss."""
43
+ ...
44
+
45
+ def set(self, prompt: str, response: str, backend: str, model: str, ttl: Optional[int] = None) -> None:
46
+ """Store response in cache. ttl is optional expiration in seconds."""
47
+ ...
48
+
49
+ def clear(self) -> None:
50
+ """Clear all cached entries."""
51
+ ...
52
+
53
+ def is_available(self) -> bool:
54
+ """Check if cache is available. False triggers degradation."""
55
+ ...
56
+
57
+ def get_stats(self) -> Dict[str, Any]:
58
+ """Return cache statistics (hit_count, miss_count, hit_rate, etc.)."""
59
+ ...
60
+
61
+
62
+ # ============================================================================
63
+ # RetryProvider: LLM call retry interface
64
+ # ============================================================================
65
+
66
+ class RetryProvider(Protocol):
67
+ """
68
+ LLM call retry interface.
69
+
70
+ Use cases:
71
+ - Handle LLM API transient failures (network timeout, rate limiting, etc.)
72
+ - Support exponential backoff strategies
73
+ - Provide fallback mechanism
74
+
75
+ Implementations:
76
+ - LLMRetry (tenacity-based)
77
+ - SimpleRetry (simple retry)
78
+ - NullRetryProvider (no-op, for degradation)
79
+ """
80
+
81
+ def retry_with_fallback(
82
+ self,
83
+ func: Callable[[], Any],
84
+ max_attempts: int = 3,
85
+ fallback: Optional[Callable[[], Any]] = None
86
+ ) -> Any:
87
+ """Execute function with retry. Falls back to fallback if all attempts fail."""
88
+ ...
89
+
90
+ def is_available(self) -> bool:
91
+ """Check if retry mechanism is available."""
92
+ ...
93
+
94
+ def get_stats(self) -> Dict[str, Any]:
95
+ """Return retry statistics (total_attempts, success_count, etc.)."""
96
+ ...
97
+
98
+
99
+ # ============================================================================
100
+ # MonitorProvider: Performance monitoring interface
101
+ # ============================================================================
102
+
103
+ class MonitorProvider(Protocol):
104
+ """
105
+ Performance monitoring interface.
106
+
107
+ Use cases:
108
+ - Record LLM call performance metrics
109
+ - Generate performance reports
110
+ - Support multiple backends (file, database, monitoring system)
111
+
112
+ Implementations:
113
+ - PerformanceMonitor (file-based)
114
+ - PrometheusMonitor (Prometheus-based)
115
+ - NullMonitorProvider (no-op, for degradation)
116
+ """
117
+
118
+ def record_llm_call(
119
+ self,
120
+ backend: str,
121
+ model: str,
122
+ duration: float,
123
+ token_count: int,
124
+ success: bool,
125
+ metadata: Optional[Dict[str, Any]] = None
126
+ ) -> None:
127
+ """Record an LLM API call."""
128
+ ...
129
+
130
+ def record_agent_execution(
131
+ self,
132
+ agent_role: str,
133
+ task: str,
134
+ duration: float,
135
+ success: bool,
136
+ metadata: Optional[Dict[str, Any]] = None
137
+ ) -> None:
138
+ """Record an agent execution."""
139
+ ...
140
+
141
+ def generate_report(self, output_path: str) -> None:
142
+ """Generate performance report to output_path."""
143
+ ...
144
+
145
+ def is_available(self) -> bool:
146
+ """Check if monitoring is available."""
147
+ ...
148
+
149
+ def get_stats(self) -> Dict[str, Any]:
150
+ """Return monitoring statistics (total_llm_calls, avg_duration, etc.)."""
151
+ ...
152
+
153
+
154
+ # ============================================================================
155
+ # MemoryProvider: Personalization memory interface
156
+ # ============================================================================
157
+
158
+ class MemoryProvider(Protocol):
159
+ """
160
+ Personalization memory interface.
161
+
162
+ Use cases:
163
+ - Store and retrieve user preferences and rules
164
+ - Support external memory systems (e.g., CarryMem)
165
+ - Inject rules into agent prompts
166
+
167
+ Implementations:
168
+ - CarryMemAdapter (CarryMem integration)
169
+ - LocalMemory (local file storage)
170
+ - NullMemoryProvider (no-op, for degradation)
171
+ """
172
+
173
+ def get_rules(self, user_id: str, context: Optional[Dict[str, Any]] = None) -> List[str]:
174
+ """Retrieve user rules. Returns list of rule strings."""
175
+ ...
176
+
177
+ def add_rule(self, user_id: str, rule: str, metadata: Optional[Dict[str, Any]] = None) -> None:
178
+ """Add a user rule."""
179
+ ...
180
+
181
+ def update_rule(self, user_id: str, rule_id: str, rule: str) -> None:
182
+ """Update an existing user rule."""
183
+ ...
184
+
185
+ def delete_rule(self, user_id: str, rule_id: str) -> None:
186
+ """Delete a user rule."""
187
+ ...
188
+
189
+ def is_available(self) -> bool:
190
+ """Check if memory system is available."""
191
+ ...
192
+
193
+ def get_stats(self) -> Dict[str, Any]:
194
+ """Return memory statistics (total_users, total_rules, etc.)."""
195
+ ...
196
+
197
+ def match_rules(self, task_description: str, user_id: str,
198
+ role: Optional[str] = None, max_rules: int = 5) -> List[Dict[str, Any]]:
199
+ """Match rules based on task description and role.
200
+
201
+ Returns list of rule dicts with keys:
202
+ - rule_type: str (forbid/avoid/always)
203
+ - trigger: str (condition that activates the rule)
204
+ - action: str (what the rule requires)
205
+ - relevance_score: float (0.0-1.0)
206
+ - rule_id: str (unique identifier)
207
+ - override: bool (whether rule cannot be overridden)
208
+ """
209
+ ...
210
+
211
+ def format_rules_as_prompt(self, rules: List[Dict[str, Any]]) -> str:
212
+ """Format matched rules as injectable prompt text.
213
+
214
+ Args:
215
+ rules: List of rule dicts from match_rules()
216
+
217
+ Returns:
218
+ Formatted string ready for prompt injection, or empty string if no rules.
219
+ """
220
+ ...
221
+
222
+
223
+ # ============================================================================
224
+ # Exception definitions
225
+ # ============================================================================
226
+
227
+ class ProviderError(Exception):
228
+ """Base exception for all provider errors."""
229
+ pass
230
+
231
+
232
+ class CacheError(ProviderError):
233
+ """Cache operation error."""
234
+ pass
235
+
236
+
237
+ class RetryError(ProviderError):
238
+ """Retry operation error."""
239
+ pass
240
+
241
+
242
+ class MonitorError(ProviderError):
243
+ """Monitor operation error."""
244
+ pass
245
+
246
+
247
+ class MemoryProviderError(ProviderError):
248
+ """Memory provider operation error."""
249
+ pass
250
+
251
+
252
+ # ============================================================================
253
+ # Version info
254
+ # ============================================================================
255
+
256
+ __version__ = "1.0.0"
257
+ __all__ = [
258
+ "CacheProvider",
259
+ "RetryProvider",
260
+ "MonitorProvider",
261
+ "MemoryProvider",
262
+ "ProviderError",
263
+ "CacheError",
264
+ "RetryError",
265
+ "MonitorError",
266
+ "MemoryProviderError",
267
+ ]
@@ -0,0 +1,352 @@
1
+ #!/usr/bin/env python3
2
+ import re
3
+ from typing import Dict, List, Any
4
+
5
+ from .models import ROLE_REGISTRY
6
+
7
+ ROLE_TEMPLATES = {rid: {"name": rdef.name, "prompt": rdef.prompt, "keywords": rdef.keywords} for rid, rdef in ROLE_REGISTRY.items()}
8
+
9
+ _I18N_SUMMARY = {
10
+ "zh": {
11
+ "task_done": "任务「{task}」已完成多Agent协作。",
12
+ "roles": "参与角色: {roles} ({count}个)",
13
+ "workers_ok": "执行结果: {done}/{total} 个Worker成功",
14
+ "duration": "协作耗时: {dur:.2f}s",
15
+ "sp_findings": "Scratchpad关键发现: {sp}",
16
+ },
17
+ "en": {
18
+ "task_done": "Task \"{task}\" completed with multi-agent collaboration.",
19
+ "roles": "Roles: {roles} ({count})",
20
+ "workers_ok": "Workers: {done}/{total} succeeded",
21
+ "duration": "Duration: {dur:.2f}s",
22
+ "sp_findings": "Scratchpad findings: {sp}",
23
+ },
24
+ "ja": {
25
+ "task_done": "タスク「{task}」がマルチエージェントコラボレーションで完了しました。",
26
+ "roles": "参加ロール: {roles} ({count})",
27
+ "workers_ok": "実行結果: {done}/{total} Worker成功",
28
+ "duration": "所要時間: {dur:.2f}s",
29
+ "sp_findings": "スクラッチパッド発見: {sp}",
30
+ },
31
+ }
32
+
33
+ _ROLE_I18N = {
34
+ "zh": {"architect": "架构师", "product-manager": "产品经理", "security": "安全专家",
35
+ "tester": "测试专家", "solo-coder": "开发者", "devops": "运维工程师", "ui-designer": "UI设计师"},
36
+ "en": {"architect": "Architect", "product-manager": "Product Manager", "security": "Security Expert",
37
+ "tester": "Tester", "solo-coder": "Coder", "devops": "DevOps", "ui-designer": "UI Designer"},
38
+ "ja": {"architect": "アーキテクト", "product-manager": "プロダクトマネージャー", "security": "セキュリティ専門家",
39
+ "tester": "テスター", "solo-coder": "コーダー", "devops": "DevOps", "ui-designer": "UIデザイナー"},
40
+ }
41
+
42
+
43
+ class ReportFormatter:
44
+ """Report formatting engine for DispatchResult."""
45
+
46
+ def __init__(self, lang: str = "zh"):
47
+ self.lang = lang
48
+ self._t = _I18N_SUMMARY.get(lang, _I18N_SUMMARY["zh"])
49
+ self._role_names = _ROLE_I18N.get(lang, _ROLE_I18N["zh"])
50
+
51
+ def build_summary(self, task: str, roles: List[str],
52
+ exec_result, sp_summary: str) -> str:
53
+ t = self._t
54
+ role_names = [self._role_names.get(r, ROLE_TEMPLATES.get(r, {}).get("name", r)) for r in roles]
55
+ parts = [
56
+ t["task_done"].format(task=task[:80]),
57
+ t["roles"].format(roles=", ".join(role_names), count=len(roles)),
58
+ ]
59
+ if exec_result.results:
60
+ done = sum(1 for r in exec_result.results if r.success)
61
+ parts.append(t["workers_ok"].format(done=done, total=len(exec_result.results)))
62
+ if exec_result.duration_seconds:
63
+ parts.append(t["duration"].format(dur=exec_result.duration_seconds))
64
+ if sp_summary:
65
+ parts.append(t["sp_findings"].format(sp=sp_summary[:200]))
66
+ return "\n".join(parts)
67
+
68
+ def format_structured_report(self, result,
69
+ include_action_items: bool = True,
70
+ include_timing: bool = False) -> str:
71
+ """
72
+ Generate structured report (v3.2 UI Designer spec).
73
+
74
+ Report hierarchy: Summary card -> Role assignments -> Key findings
75
+ -> Conflict resolution -> Action items
76
+ """
77
+ lines = []
78
+ status_icon = "✅" if result.success else "❌"
79
+ status_text = "协作完成" if result.success else "协作异常"
80
+
81
+ lines.append(f"# {status_icon} Multi-Agent 协作报告")
82
+ lines.append("")
83
+
84
+ lines.append("---")
85
+ lines.append(f"## 📋 任务摘要")
86
+ lines.append("")
87
+ lines.append(f"| 项目 | 内容 |")
88
+ lines.append(f"|------|------|")
89
+ lines.append(f"| **任务** | {result.task_description[:100]} |")
90
+ lines.append(f"| **状态** | {status_text} |")
91
+ lines.append(f"| **参与角色** | {len(result.matched_roles)} 个 ({', '.join(result.matched_roles)}) |")
92
+ lines.append(f"| **总耗时** | {result.duration_seconds:.2f}s |")
93
+
94
+ if result.worker_results:
95
+ success_count = sum(1 for w in result.worker_results if w.get('success'))
96
+ total_count = len(result.worker_results)
97
+ lines.append(f"| **执行成功率** | {success_count}/{total_count} ({success_count/total_count*100:.0f}%) |")
98
+
99
+ if result.errors:
100
+ lines.append(f"| **错误数** | {len(result.errors)} |")
101
+ lines.append("")
102
+ lines.append("---")
103
+ lines.append("")
104
+
105
+ if result.worker_results:
106
+ lines.append("## 👥 角色分配与产出")
107
+ lines.append("")
108
+ lines.append("| 角色 | 状态 | 核心产出 (预览) |")
109
+ lines.append("|------|------|----------------|")
110
+
111
+ for wr in result.worker_results:
112
+ role_name = wr.get('role', 'unknown')
113
+ role_display = ROLE_TEMPLATES.get(role_name, {}).get('name', role_name)
114
+ status_icon = '✅' if wr.get('success') else '❌'
115
+ output_preview = (wr.get('output') or '(无输出)')[:80].replace('\n', ' ')
116
+ lines.append(f"| **{role_display}** | {status_icon} | {output_preview} |")
117
+
118
+ lines.append("")
119
+ lines.append("---")
120
+ lines.append("")
121
+
122
+ if result.scratchpad_summary:
123
+ lines.append("## 🔍 关键发现")
124
+ lines.append("")
125
+ findings = self.extract_findings(result.scratchpad_summary)
126
+ if findings:
127
+ for i, finding in enumerate(findings[:8], 1):
128
+ lines.append(f"{i}. {finding}")
129
+ else:
130
+ lines.append(f"> {result.scratchpad_summary[:300]}")
131
+ lines.append("")
132
+ lines.append("---")
133
+ lines.append("")
134
+
135
+ if result.consensus_records:
136
+ lines.append("## 🗳️ 共识决策与冲突解决")
137
+ lines.append("")
138
+
139
+ for cr in result.consensus_records:
140
+ topic = cr.get('topic', '未知议题')
141
+ outcome = cr.get('outcome', '')
142
+ decision = cr.get('final_decision', '')
143
+
144
+ if outcome == 'APPROVED':
145
+ badge = "🟢 通过"
146
+ elif outcome == 'REJECTED':
147
+ badge = "🔴 否决"
148
+ elif outcome == 'SPLIT':
149
+ badge = "🟡 分歧"
150
+ elif outcome == 'ESCALATED':
151
+ badge = "🔵 升级"
152
+ else:
153
+ badge = "⏪ 超时"
154
+
155
+ votes_for = cr.get('votes_for', 0)
156
+ votes_against = cr.get('votes_against', 0)
157
+ votes_abstain = cr.get('votes_abstain', 0)
158
+
159
+ lines.append(f"- **{topic}** `{badge}`")
160
+ lines.append(f" - 投票: ✅{votes_for} ❌{votes_against} ⚪{votes_abstain}")
161
+ if decision:
162
+ lines.append(f" - 决策: {decision[:100]}")
163
+ lines.append("")
164
+
165
+ lines.append("---")
166
+ lines.append("")
167
+
168
+ if include_action_items:
169
+ action_items = self.generate_action_items(result)
170
+ if action_items:
171
+ lines.append("## 📌 行动项建议")
172
+ lines.append("")
173
+ for i, item in enumerate(action_items, 1):
174
+ priority = item.get('priority', 'M')
175
+ priority_badge = {'H': '🔴高', 'M': '🟡中', 'L': '🟢低'}.get(priority, '⚪')
176
+ lines.append(f"{i}. [{priority_badge}] {item['text']}")
177
+ lines.append("")
178
+
179
+ ext_sections = []
180
+
181
+ if result.compression_info:
182
+ ci = result.compression_info
183
+ ext_sections.append(
184
+ f"**上下文压缩**: 级别={ci.get('level','N/A')} | "
185
+ f"{ci.get('original_tokens',0)}→{ci.get('compressed_tokens',0)} tokens | "
186
+ f"节省 {ci.get('reduction_pct',0)}%"
187
+ )
188
+
189
+ if result.memory_stats:
190
+ ms = result.memory_stats
191
+ ext_sections.append(
192
+ f"**记忆系统**: 总记忆={ms.get('total_memories',0)} | "
193
+ f"捕获次数={ms.get('total_captures',0)}"
194
+ )
195
+
196
+ if result.skill_proposals and len(result.skill_proposals) > 0:
197
+ proposals = [f"{p.get('title','')}({p.get('confidence',0):.0%})" for p in result.skill_proposals[:3]]
198
+ ext_sections.append(f"**技能提案**: {', '.join(proposals)}")
199
+
200
+ if result.permission_checks:
201
+ allowed = sum(1 for pc in result.permission_checks if pc.get('allowed'))
202
+ total_pc = len(result.permission_checks)
203
+ ext_sections.append(f"**权限检查**: {allowed}/{total_pc} 通过")
204
+
205
+ if ext_sections:
206
+ lines.append("---")
207
+ lines.append("")
208
+ lines.append("> **系统信息**")
209
+ for section in ext_sections:
210
+ lines.append(f"> {section}")
211
+ lines.append("")
212
+
213
+ if include_timing and result.details.get('timing'):
214
+ timing = result.details['timing']
215
+ lines.append("")
216
+ lines.append("<details>")
217
+ lines.append("<summary>⏱️ 各阶段耗时详情</summary>")
218
+ lines.append("")
219
+ lines.append("| 阶段 | 耗时(s) |")
220
+ lines.append("|------|---------|")
221
+ for stage, duration in timing.items():
222
+ if duration > 0.001:
223
+ lines.append(f"| {stage} | {duration:.3f} |")
224
+ lines.append("")
225
+ lines.append("</details>")
226
+ lines.append("")
227
+
228
+ if result.errors:
229
+ lines.append("")
230
+ lines.append("> ⚠️ **错误/警告**:")
231
+ for err in result.errors[:5]:
232
+ lines.append(f"> - {err[:150]}")
233
+
234
+ return "\n".join(lines)
235
+
236
+ def format_compact_report(self, result) -> str:
237
+ """Generate compact report suitable for terminal quick view."""
238
+ status = "✅" if result.success else "❌"
239
+ roles_str = ", ".join(result.matched_roles) if result.matched_roles else "无"
240
+
241
+ parts = [
242
+ f"[{status}] 任务: {result.task_description[:60]}",
243
+ f"角色: {roles_str} ({len(result.matched_roles)}个)",
244
+ f"耗时: {result.duration_seconds:.2f}s",
245
+ ]
246
+
247
+ if result.worker_results:
248
+ done = sum(1 for w in result.worker_results if w.get('success'))
249
+ parts.append(f"Worker: {done}/{len(result.worker_results)} 成功")
250
+
251
+ if result.scratchpad_summary:
252
+ parts.append(f"发现: {result.scratchpad_summary[:120]}")
253
+
254
+ if result.consensus_records:
255
+ approved = sum(1 for c in result.consensus_records if c.get('outcome') == 'APPROVED')
256
+ parts.append(f"共识: {approved}/{len(result.consensus_records)} 通过")
257
+
258
+ if result.errors:
259
+ parts.append(f"错误: {len(result.errors)} 个")
260
+
261
+ return "\n".join(parts)
262
+
263
+ def extract_findings(self, scratchpad_summary: str) -> List[str]:
264
+ """
265
+ Extract key findings from Scratchpad summary text.
266
+
267
+ Supports: numbered lists, bullet lists, semicolon-separated, sentence splitting.
268
+ """
269
+ if not scratchpad_summary:
270
+ return []
271
+
272
+ findings = []
273
+ text = scratchpad_summary.strip()
274
+
275
+ numbered = re.findall(r'(?:^|\n)\s*(\d+)[\.\、\)]\s*(.+?)(?=\n\s*\d+[\.\、\)]|\Z)', text, re.MULTILINE)
276
+ if numbered and len(numbered) >= 2:
277
+ findings = [item.strip() for _, item in numbered]
278
+ return [f for f in findings if f]
279
+
280
+ bulleted = re.findall(r'(?:^|\n)\s*[-*•]\s*(.+?)(?=\n\s*[-*•]|\Z)', text, re.MULTILINE)
281
+ if bulleted and len(bulleted) >= 2:
282
+ findings = [item.strip() for item in bulleted]
283
+ return [f for f in findings if f]
284
+
285
+ if ';' in text and text.count(';') >= 2:
286
+ findings = [f.strip() for f in text.split(';') if f.strip()]
287
+ return findings[:10]
288
+
289
+ sentences = re.split(r'[。!?.!?\n]+', text)
290
+ findings = [s.strip() for s in sentences if len(s.strip()) >= 10]
291
+ return findings[:8]
292
+
293
+ def generate_action_items(self, result) -> List[Dict[str, str]]:
294
+ """
295
+ Auto-generate action item suggestions based on dispatch result.
296
+
297
+ Rules:
298
+ - Errors -> High priority fix suggestions
299
+ - Unresolved conflicts -> Medium priority manual review
300
+ - All success -> Low priority follow-up optimization
301
+ - Memory data -> Suggest reviewing historical decisions
302
+ """
303
+ items = []
304
+
305
+ if result.errors:
306
+ items.append({
307
+ 'priority': 'H',
308
+ 'text': f"修复 {len(result.errors)} 个执行错误,首要关注: {result.errors[0][:80]}"
309
+ })
310
+
311
+ unresolved = [c for c in result.consensus_records
312
+ if c.get('outcome') in ('SPLIT', 'ESCALATED', 'TIMEOUT')]
313
+ if unresolved:
314
+ items.append({
315
+ 'priority': 'H' if len(unresolved) > 2 else 'M',
316
+ 'text': f"人工审核 {len(unresolved)} 个未决共识议题: {', '.join([u.get('topic','') for u in unresolved[:3]])}"
317
+ })
318
+
319
+ failed_workers = [w for w in result.worker_results if not w.get('success')]
320
+ if failed_workers:
321
+ roles_failed = [ROLE_TEMPLATES.get(w.get('role',''), {}).get('name', w.get('role','')) for w in failed_workers]
322
+ items.append({
323
+ 'priority': 'M',
324
+ 'text': f"排查以下角色执行失败原因: {', '.join(roles_failed[:3])}"
325
+ })
326
+
327
+ if result.success and not result.errors:
328
+ if result.memory_stats and result.memory_stats.get('total_memories', 0) > 0:
329
+ items.append({
330
+ 'priority': 'L',
331
+ 'text': f"回顾历史记忆 (共{result.memory_stats['total_memories']}条),提取可复用经验"
332
+ })
333
+
334
+ if result.skill_proposals and len(result.skill_proposals) > 0:
335
+ top_proposal = result.skill_proposals[0]
336
+ items.append({
337
+ 'priority': 'L',
338
+ 'text': f"评估新技能提案「{top_proposal.get('title','')}」(置信度{top_proposal.get('confidence',0):.0%})是否值得固化"
339
+ })
340
+
341
+ items.append({
342
+ 'priority': 'L',
343
+ 'text': "任务已完成,可归档此协作记录供未来参考"
344
+ })
345
+
346
+ if not items:
347
+ items.append({
348
+ 'priority': 'M',
349
+ 'text': "审查各角色产出内容,确认是否符合预期"
350
+ })
351
+
352
+ return items[:6]