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
scripts/mcp_server.py ADDED
@@ -0,0 +1,289 @@
1
+ """
2
+ DevSquad MCP (Model Context Protocol) Server — For OpenClaw / Claude Code Tool Integration.
3
+
4
+ This server exposes MultiAgentDispatcher capabilities as MCP tools, enabling
5
+ any MCP-compatible AI agent (OpenClaw, Claude Code, Cursor, etc.) to invoke
6
+ multi-agent collaboration directly.
7
+
8
+ Usage:
9
+ python scripts/mcp_server.py # Start stdio transport (default)
10
+ python scripts/mcp_server.py --port 8080 # Start SSE transport
11
+
12
+ MCP Tools Exposed:
13
+ 1. multiagent_dispatch — Execute a multi-agent collaboration task
14
+ 2. multiagent_quick — Quick dispatch with format options
15
+ 3. multiagent_roles — List available roles
16
+ 4. multiagent_status — System status and capabilities
17
+ 5. multiagent_analyze — Analyze task intent (dry-run)
18
+
19
+ Dependencies (optional, graceful fallback):
20
+ pip install mcp # For MCP protocol support
21
+ """
22
+ import json
23
+ import sys
24
+ import os
25
+ import logging
26
+ from typing import Any, Dict, List, Optional
27
+
28
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
29
+
30
+ logging.basicConfig(level=logging.WARNING, format="%(asctime)s [%(levelname)s] %(message)s")
31
+ logger = logging.getLogger("DevSquad-MCP")
32
+
33
+ try:
34
+ from scripts.collaboration.input_validator import InputValidator
35
+ _validator = InputValidator()
36
+ except ImportError:
37
+ _validator = None
38
+
39
+ try:
40
+ from mcp.server.fastmcp import FastMCP
41
+ MCP_AVAILABLE = True
42
+ except ImportError:
43
+ MCP_AVAILABLE = False
44
+ logger.warning("MCP SDK not installed. Run: pip install mcp")
45
+
46
+ from scripts.collaboration.dispatcher import MultiAgentDispatcher
47
+ from scripts.collaboration.permission_guard import PermissionLevel
48
+ from scripts.collaboration.models import ROLE_REGISTRY
49
+
50
+
51
+ class DevSquadMCPServer:
52
+ """MCP Server wrapper for DevSquad."""
53
+
54
+ def __init__(self):
55
+ self._dispatcher: Optional[MultiAgentDispatcher] = None
56
+
57
+ def _get_dispatcher(self, **kwargs) -> MultiAgentDispatcher:
58
+ """Lazy-init dispatcher with caching."""
59
+ if self._dispatcher is None:
60
+ self._dispatcher = MultiAgentDispatcher(**kwargs)
61
+ return self._dispatcher
62
+
63
+ def shutdown(self):
64
+ """Clean up dispatcher."""
65
+ if self._dispatcher:
66
+ self._dispatcher.shutdown()
67
+ self._dispatcher = None
68
+
69
+
70
+ def create_mcp_server() -> "FastMCP":
71
+ """Create and configure the MCP server with all tools."""
72
+ if not MCP_AVAILABLE:
73
+ raise ImportError("MCP SDK not installed. Run: pip install mcp")
74
+
75
+ mcp = FastMCP("DevSquad")
76
+ server = DevSquadMCPServer()
77
+
78
+ @mcp.tool()
79
+ def multiagent_dispatch(
80
+ task: str,
81
+ roles: Optional[List[str]] = None,
82
+ mode: str = "auto",
83
+ output_format: str = "markdown",
84
+ dry_run: bool = False,
85
+ ) -> str:
86
+ """
87
+ Execute a full multi-agent collaboration task.
88
+
89
+ Args:
90
+ task: The task description to collaborate on.
91
+ roles: Optional list of roles (arch/pm/sec/test/coder/infra/ui).
92
+ If omitted, auto-matches based on task intent (supports CN+EN keywords).
93
+ mode: Execution mode — 'auto'(default), 'parallel', 'sequential', or 'consensus'.
94
+ output_format: Output format — 'markdown'(default), 'json', or 'compact'.
95
+ dry_run: If True, only analyze without execution.
96
+
97
+ Returns:
98
+ Markdown or JSON formatted collaboration result with findings,
99
+ conflicts resolution, and action items.
100
+ """
101
+ disp = server._get_dispatcher()
102
+ if _validator:
103
+ vresult = _validator.validate_task(task)
104
+ if not vresult.valid:
105
+ return json.dumps({"error": f"Invalid task: {vresult.reason}", "success": False})
106
+ task = vresult.sanitized_input or task
107
+ try:
108
+ result = disp.dispatch(
109
+ task=task,
110
+ roles=roles,
111
+ mode=mode,
112
+ dry_run=dry_run,
113
+ )
114
+ if output_format == "json":
115
+ return json.dumps({
116
+ "success": result.success,
117
+ "matched_roles": getattr(result, 'matched_roles', None),
118
+ "summary": result.summary,
119
+ "report": result.to_markdown(),
120
+ }, ensure_ascii=False, indent=2)
121
+ elif output_format == "compact":
122
+ return result.summary
123
+ return result.to_markdown()
124
+ except Exception as e:
125
+ logger.error(f"Dispatch error: {e}", exc_info=True)
126
+ return json.dumps({"error": "Internal error occurred", "success": False})
127
+
128
+ @mcp.tool()
129
+ def multiagent_quick(
130
+ task: str,
131
+ output_format: str = "structured",
132
+ include_action_items: bool = True,
133
+ include_timing: bool = False,
134
+ ) -> str:
135
+ """
136
+ Quick dispatch with simplified interface and 3 output formats.
137
+
138
+ Args:
139
+ task: Task description.
140
+ output_format: 'structured'(default table), 'compact'(one-line), or 'detailed'(full).
141
+ include_action_items: Include H/M/L priority action items.
142
+ include_timing: Include execution timing data.
143
+
144
+ Returns:
145
+ Formatted collaboration result optimized for quick reading.
146
+ """
147
+ disp = server._get_dispatcher()
148
+ if _validator:
149
+ vresult = _validator.validate_task(task)
150
+ if not vresult.valid:
151
+ return json.dumps({"error": f"Invalid task: {vresult.reason}", "success": False})
152
+ task = vresult.sanitized_input or task
153
+ try:
154
+ result = disp.quick_dispatch(
155
+ task=task,
156
+ output_format=output_format,
157
+ include_action_items=include_action_items,
158
+ include_timing=include_timing,
159
+ )
160
+ return result.to_markdown() if hasattr(result, 'to_markdown') else str(result)
161
+ except Exception as e:
162
+ logger.error(f"Quick dispatch error: {e}", exc_info=True)
163
+ return json.dumps({"error": "Internal error occurred", "success": False})
164
+
165
+ @mcp.tool()
166
+ def multiagent_roles(format: str = "text") -> str:
167
+ """
168
+ List all available agent roles with descriptions.
169
+
170
+ Args:
171
+ format: 'text' or 'json'.
172
+
173
+ Returns:
174
+ Role list with descriptions showing expertise areas.
175
+ """
176
+ roles = {}
177
+ for rid, rdef in ROLE_REGISTRY.items():
178
+ display_id = rdef.aliases[0] if rdef.aliases else rid
179
+ status_tag = " [planned]" if rdef.status == "planned" else ""
180
+ roles[display_id] = f"{rdef.description}{status_tag}"
181
+ if format == "json":
182
+ return json.dumps(roles, ensure_ascii=False, indent=2)
183
+ lines = [f"**{role}** — {desc}" for role, desc in roles.items()]
184
+ return "\n".join(lines)
185
+
186
+ @mcp.tool()
187
+ def multiagent_status() -> str:
188
+ """
189
+ Get system status, version info, and capability summary.
190
+
191
+ Returns:
192
+ JSON with version, status, available roles/modes, and module info.
193
+ """
194
+ disp = server._get_dispatcher()
195
+ try:
196
+ stats = disp.get_status() if hasattr(disp, 'get_status') else {}
197
+ return json.dumps({
198
+ "name": "DevSquad",
199
+ "version": "3.6.0",
200
+ "status": "ready",
201
+ "modules": 48,
202
+ "tests": 1548,
203
+ "roles": 7,
204
+ "modes": ["auto", "parallel", "sequential", "consensus"],
205
+ "backends": ["mock", "openai", "anthropic"],
206
+ "languages": ["zh", "en", "ja"],
207
+ "features": {
208
+ "memory_bridge": True,
209
+ "mce_adapter": True,
210
+ "workbuddy_claw": True,
211
+ "context_compression": True,
212
+ "permission_guard": True,
213
+ "skill_learning": True,
214
+ "streaming": True,
215
+ "checkpoint": True,
216
+ "workflow_engine": True,
217
+ "prompt_injection_detection": True,
218
+ "i18n": True,
219
+ },
220
+ }, ensure_ascii=False, indent=2)
221
+ except Exception as e:
222
+ return json.dumps({"name": "DevSquad", "version": "3.6.0", "status": "ready", "error": "Internal error occurred"})
223
+
224
+ @mcp.tool()
225
+ def multiagent_analyze(task: str) -> str:
226
+ """
227
+ Analyze a task's intent and suggest optimal role configuration (dry-run).
228
+
229
+ Args:
230
+ task: Task description to analyze.
231
+
232
+ Returns:
233
+ Analysis including suggested roles, estimated complexity, and mode recommendation.
234
+ """
235
+ disp = server._get_dispatcher(enable_warmup=False)
236
+ if _validator:
237
+ vresult = _validator.validate_task(task)
238
+ if not vresult.valid:
239
+ return json.dumps({"error": f"Invalid task: {vresult.reason}"})
240
+ task = vresult.sanitized_input or task
241
+ try:
242
+ result = disp.dispatch(task, dry_run=True)
243
+ return json.dumps({
244
+ "task": task,
245
+ "suggested_roles": getattr(result, 'matched_roles', []),
246
+ "summary": result.summary,
247
+ "complexity": "estimated from task analysis",
248
+ "recommended_mode": "auto",
249
+ }, ensure_ascii=False, indent=2)
250
+ except Exception as e:
251
+ return json.dumps({"error": "Internal error occurred"}, ensure_ascii=False, indent=2)
252
+
253
+ @mcp.tool()
254
+ def multiagent_shutdown() -> str:
255
+ """
256
+ Shutdown the DevSquad dispatcher and free resources.
257
+ Call this when done to clean up memory and connections.
258
+ """
259
+ server.shutdown()
260
+ return json.dumps({"status": "shutdown_complete"})
261
+
262
+ return mcp
263
+
264
+
265
+ def main():
266
+ """Start the MCP server."""
267
+ import argparse
268
+
269
+ parser = argparse.ArgumentParser(description="DevSquad MCP Server")
270
+ parser.add_argument("--port", "-p", type=int, default=None, help="SSE transport port (default: stdio)")
271
+ parser.add_argument("--host", default="127.0.0.1", help="SSE host (default: 127.0.0.1)")
272
+ args = parser.parse_args()
273
+
274
+ if not MCP_AVAILABLE:
275
+ print("ERROR: MCP SDK required. Install with: pip install mcp", file=sys.stderr)
276
+ sys.exit(1)
277
+
278
+ mcp = create_mcp_server()
279
+
280
+ if args.port:
281
+ logger.info(f"Starting SSE server on {args.host}:{args.port}")
282
+ mcp.run(transport="sse", host=args.host, port=args.port)
283
+ else:
284
+ logger.info("Starting stdio server")
285
+ mcp.run(transport="stdio")
286
+
287
+
288
+ if __name__ == "__main__":
289
+ main()
skills/__init__.py ADDED
@@ -0,0 +1,32 @@
1
+ """
2
+ DevSquad V3.6.0 — Layered Sub-Skill Architecture
3
+
4
+ This package provides fine-grained, independently usable skills that wrap
5
+ DevSquad's core modules. Each sub-skill is a thin handler (~50 lines) that
6
+ imports from the existing collaboration layer — no duplicated logic.
7
+
8
+ Architecture:
9
+ skills/
10
+ ├── dispatch/ → MultiAgentDispatcher (7-role orchestration)
11
+ ├── intent/ → IntentWorkflowMapper (6 intents × 3 languages)
12
+ ├── review/ → FiveAxisConsensusEngine (5-axis code review)
13
+ ├── security/ → Security audit + PermissionGuard
14
+ ├── test/ → TestQualityGuard + test strategy
15
+ └── retrospective/ → RetrospectiveEngine + pattern extraction
16
+
17
+ Usage (standalone):
18
+ from skills.dispatch.handler import DispatchSkill
19
+ result = DispatchSkill().run("Fix login bug")
20
+
21
+ Usage (via registry):
22
+ from skills import SkillRegistry
23
+ skill = SkillRegistry.get("dispatch")
24
+ result = skill.run("Fix login bug")
25
+
26
+ All sub-skills work with Mock backend by default (no API key needed).
27
+ """
28
+
29
+ from .registry import BaseSkill, get_skill, list_skills, discover_all
30
+
31
+ __all__ = ["BaseSkill", "get_skill", "list_skills", "discover_all"]
32
+ __version__ = "3.6.0"
@@ -0,0 +1,52 @@
1
+ """Dispatch Skill — One-click multi-agent task orchestration."""
2
+
3
+ import sys
4
+ import os
5
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
6
+
7
+ from skills.registry import BaseSkill
8
+
9
+ _dispatcher = None
10
+
11
+
12
+ def _get_dispatcher():
13
+ global _dispatcher
14
+ if _dispatcher is None:
15
+ from scripts.collaboration.dispatcher import MultiAgentDispatcher
16
+ _dispatcher = MultiAgentDispatcher()
17
+ return _dispatcher
18
+
19
+
20
+ class DispatchSkill(BaseSkill):
21
+ name = "dispatch"
22
+ description = "Multi-agent task dispatch: submit a task → auto-match roles → parallel execution → structured report"
23
+ version = "3.6.0"
24
+
25
+ def run(self, task: str, roles: list = None, mode: str = "auto",
26
+ dry_run: bool = False) -> dict:
27
+ d = _get_dispatcher()
28
+ result = d.dispatch(
29
+ task_description=task,
30
+ roles=roles,
31
+ mode=mode,
32
+ dry_run=dry_run,
33
+ )
34
+ timing = result.timing if hasattr(result, 'timing') and isinstance(result.timing, dict) else {}
35
+ return {
36
+ "success": result.success,
37
+ "matched_roles": result.matched_roles,
38
+ "worker_results": result.worker_results,
39
+ "report": getattr(result, 'report', None),
40
+ "intent_match": result.intent_match,
41
+ "five_axis_result": result.five_axis_result,
42
+ "timing": {"total_s": round(timing.get("total", 0), 3)},
43
+ "status": "success" if result.success else "failed",
44
+ }
45
+
46
+ def quick(self, task: str, **kwargs) -> dict:
47
+ return self.run(task, **kwargs)
48
+
49
+ def roles_info(self) -> list:
50
+ from scripts.collaboration.role_matcher import RoleMatcher
51
+ rm = RoleMatcher()
52
+ return [{"id": r.role_id, "name": r.name, "keywords": r.keywords[:5]} for r in rm.roles]
@@ -0,0 +1,59 @@
1
+ """Intent Detection Skill — 用户意图自动识别与工作流映射。"""
2
+
3
+ import sys
4
+ import os
5
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
6
+
7
+ from skills.registry import BaseSkill
8
+
9
+
10
+ class IntentSkill(BaseSkill):
11
+ name = "intent"
12
+ description = "Detect user intent from natural language and map to workflow chain (6 intents × 3 languages)"
13
+ version = "3.6.0"
14
+
15
+ INTENT_MAP = {
16
+ "bug_fix": "🐛 Bug修复",
17
+ "new_feature": "✨ 新功能开发",
18
+ "code_review": "🔍 代码评审",
19
+ "refactor": "♻️ 重构优化",
20
+ "investigation": "🔬 问题排查",
21
+ "documentation": "📝 文档编写",
22
+ }
23
+
24
+ def detect(self, text: str, lang: str = "auto") -> dict:
25
+ from scripts.collaboration.intent_workflow_mapper import IntentWorkflowMapper
26
+ mapper = IntentWorkflowMapper()
27
+ result = mapper.detect_intent(text, lang=lang)
28
+ icon = self.INTENT_MAP.get(result.intent_type, "❓")
29
+ return {
30
+ "intent": result.intent_type,
31
+ "label": f"{icon} {result.intent_type}",
32
+ "confidence": round(result.confidence, 4),
33
+ "lang": lang,
34
+ "required_roles": result.required_roles or [],
35
+ "optional_roles": result.optional_roles or [],
36
+ "workflow_chain": result.workflow_chain or [],
37
+ "gates": [],
38
+ }
39
+
40
+ def batch_detect(self, texts: list, **kwargs) -> list:
41
+ return [self.detect(t, **kwargs) for t in texts]
42
+
43
+ def list_intents(self) -> list:
44
+ return [
45
+ {"type": k, "label": v, "description": self._describe(k)}
46
+ for k, v in self.INTENT_MAP.items()
47
+ ]
48
+
49
+ @staticmethod
50
+ def _describe(intent_type: str) -> str:
51
+ descs = {
52
+ "bug_fix": "Fix defects, errors, or unexpected behavior",
53
+ "new_feature": "Build new functionality or features",
54
+ "code_review": "Review code quality, security, performance",
55
+ "refactor": "Improve code structure without changing behavior",
56
+ "investigation": "Diagnose root cause of problems",
57
+ "documentation": "Create or update documentation",
58
+ }
59
+ return descs.get(intent_type, "")
skills/registry.py ADDED
@@ -0,0 +1,67 @@
1
+ """Sub-skill registry: discover and instantiate skills by name."""
2
+
3
+ import importlib
4
+ from pathlib import Path
5
+ from typing import Dict, Optional, Type
6
+
7
+ _SKILL_DIR = Path(__file__).parent
8
+ _AVAILABLE_SKILLS: Dict[str, Type] = {}
9
+
10
+
11
+ class BaseSkill:
12
+ """Base class for all DevSquad sub-skills."""
13
+
14
+ name: str = ""
15
+ description: str = ""
16
+ version: str = "3.6.0"
17
+
18
+ def run(self, *args, **kwargs):
19
+ raise NotImplementedError
20
+
21
+ def info(self) -> dict:
22
+ return {"name": self.name, "description": self.description, "version": self.version}
23
+
24
+
25
+ def _load_skill(name: str) -> Optional[Type[BaseSkill]]:
26
+ skill_dir = _SKILL_DIR / name
27
+ if not skill_dir.is_dir():
28
+ return None
29
+ handler_path = skill_dir / "handler.py"
30
+ if not handler_path.exists():
31
+ return None
32
+ try:
33
+ mod = importlib.import_module(f"skills.{name}.handler")
34
+ for attr_name in dir(mod):
35
+ attr = getattr(mod, attr_name)
36
+ if isinstance(attr, type) and issubclass(attr, BaseSkill) and attr is not BaseSkill:
37
+ return attr
38
+ except Exception:
39
+ pass
40
+ return None
41
+
42
+
43
+ def get_skill(name: str) -> BaseSkill:
44
+ """Get a skill instance by name. Lazy-loads on first access."""
45
+ if name not in _AVAILABLE_SKILLS:
46
+ cls = _load_skill(name)
47
+ if cls is None:
48
+ raise ValueError(f"Skill '{name}' not found. Available: {list_skills()}")
49
+ _AVAILABLE_SKILLS[name] = cls
50
+ return _AVAILABLE_SKILLS[name]()
51
+
52
+
53
+ def list_skills() -> list:
54
+ """List all available sub-skill names."""
55
+ if not _AVAILABLE_SKILLS:
56
+ for d in sorted(_SKILL_DIR.iterdir()):
57
+ if d.is_dir() and (d / "handler.py").exists():
58
+ cls = _load_skill(d.name)
59
+ if cls:
60
+ _AVAILABLE_SKILLS[d.name] = cls
61
+ return list(_AVAILABLE_SKILLS.keys())
62
+
63
+
64
+ def discover_all() -> Dict[str, BaseSkill]:
65
+ """Instantiate all available skills."""
66
+ names = list_skills()
67
+ return {name: get_skill(name) for name in names}
File without changes
@@ -0,0 +1,125 @@
1
+ """Retrospective Skill - V3.6.0"""
2
+
3
+ from skills.registry import BaseSkill
4
+ from scripts.collaboration.retrospective import RetrospectiveEngine
5
+ from scripts.collaboration.models import (
6
+ StructuredGoal,
7
+ GoalItem,
8
+ GoalItemStatus,
9
+ AnchorResult,
10
+ AnchorTrigger,
11
+ )
12
+
13
+
14
+ class RetrospectiveSkill(BaseSkill):
15
+ name = "retrospective"
16
+ description = "项目复盘 - 提取改进模式/生成可执行建议/复盘摘要"
17
+ version = "3.6.0"
18
+
19
+ CAPABILITIES = [
20
+ "post_dispatch_analysis",
21
+ "pattern_extraction",
22
+ "improvement_suggestion",
23
+ ]
24
+
25
+ OUTPUT_FORMATS = ["findings", "patterns", "improvements", "summary"]
26
+
27
+ def run(self, action="info", **kwargs):
28
+ actions = {
29
+ "run_retrospective": self.run_retrospective,
30
+ "extract_patterns": self.extract_patterns,
31
+ "generate_improvements": self.generate_improvements,
32
+ "summary": self.summary,
33
+ }
34
+ fn = actions.get(action)
35
+ if not fn:
36
+ return {"error": f"Unknown action: {action}. Available: {list(actions.keys())}"}
37
+ return fn(**kwargs)
38
+
39
+ def _mock_goal(self, task: str = "") -> StructuredGoal:
40
+ desc = task or "模拟任务:完成功能开发并修复已知问题"
41
+ return StructuredGoal(
42
+ goal_id="retro_mock_001",
43
+ original_description=desc,
44
+ items=[
45
+ GoalItem(item_id="item_001", description=desc, status=GoalItemStatus.FULLY_COVERED, coverage_score=1.0),
46
+ ],
47
+ )
48
+
49
+ def _mock_anchor_history(self) -> list:
50
+ return [
51
+ AnchorResult(aligned=True, trigger=AnchorTrigger.MILESTONE, coverage=0.8, drift_score=0.05),
52
+ AnchorResult(aligned=True, trigger=AnchorTrigger.PHASE_GATE, coverage=0.9, drift_score=0.02),
53
+ AnchorResult(aligned=False, trigger=AnchorTrigger.DIRECTION_CHANGE, coverage=0.7, drift_score=0.35),
54
+ AnchorResult(aligned=True, trigger=AnchorTrigger.STEP_COMPLETE, coverage=0.95, drift_score=0.01),
55
+ ]
56
+
57
+ def run_retrospective(self, dispatch_result: dict = None, task: str = "") -> dict:
58
+ engine = RetrospectiveEngine()
59
+ goal = self._mock_goal(task)
60
+ history = self._mock_anchor_history()
61
+ report = engine.run(goal=goal, anchor_history=history)
62
+ result = report.to_dict() if hasattr(report, 'to_dict') else {
63
+ "task_goal": report.task_goal,
64
+ "deviations": [d.__dict__ for d in report.deviations],
65
+ "redundant_steps": report.redundant_steps,
66
+ "improvements": report.improvements,
67
+ "summary": report.summary,
68
+ }
69
+ if dispatch_result:
70
+ result["dispatch_context"] = dispatch_result
71
+ return result
72
+
73
+ def extract_patterns(self, history_items: list = None) -> dict:
74
+ items = history_items or [
75
+ {"type": "goal_drift", "count": 3},
76
+ {"type": "redundant_step", "count": 2},
77
+ {"type": "late_error_detection", "count": 4},
78
+ ]
79
+ patterns = {}
80
+ for item in items:
81
+ t = item.get("type", "unknown")
82
+ patterns[t] = patterns.get(t, 0) + item.get("count", 1)
83
+ top_patterns = sorted(patterns.items(), key=lambda x: x[1], reverse=True)[:5]
84
+ return {
85
+ "total_items": len(items),
86
+ "patterns": [{"type": t, "occurrences": c} for t, c in top_patterns],
87
+ "insight": f"最频繁模式: {top_patterns[0][0] if top_patterns else 'N/A'} ({top_patterns[0][1] if top_patterns else 0}次)",
88
+ }
89
+
90
+ def generate_improvements(self, findings: list) -> dict:
91
+ if not findings:
92
+ findings = ["goal_drift", "missing_anchor_checks"]
93
+ improvement_map = {
94
+ "goal_drift": "增加中间锚点检查以尽早发现目标偏移",
95
+ "missing_anchor_checks": "在关键决策点添加 AnchorCheck 验证",
96
+ "redundant_steps": "优化工作流消除重复步骤",
97
+ "late_error_detection": "前置错误检测点,缩短反馈循环",
98
+ "low_coverage": "补充缺失的测试维度和边界用例",
99
+ }
100
+ improvements = [improvement_map.get(f, f"针对 {f} 的改进建议") for f in findings]
101
+ return {
102
+ "findings_count": len(findings),
103
+ "improvements": improvements,
104
+ "priority": "high" if len(findings) > 3 else "medium",
105
+ }
106
+
107
+ def summary(self, retro_result: dict = None) -> dict:
108
+ if retro_result:
109
+ deviations = retro_result.get("deviations", [])
110
+ improvements = retro_result.get("improvements", [])
111
+ s = retro_result.get("summary", "")
112
+ else:
113
+ full = self.run_retrospective()
114
+ deviations = full.get("deviations", [])
115
+ improvements = full.get("improvements", [])
116
+ s = full.get("summary", "")
117
+ went_well = [f"目标覆盖率达标" if not deviations else "任务已完成"]
118
+ to_improve = improvements[:5] if improvements else ["保持当前流程"]
119
+ return {
120
+ "what_went_well": went_well,
121
+ "what_to_improve": to_improve,
122
+ "deviation_count": len(deviations),
123
+ "improvement_count": len(improvements),
124
+ "raw_summary": s,
125
+ }