@pjmendonca/devflow 1.9.0
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.
- package/CHANGELOG.md +526 -0
- package/LICENSE +21 -0
- package/README.md +620 -0
- package/bin/devflow-checkpoint.js +10 -0
- package/bin/devflow-collab.js +10 -0
- package/bin/devflow-cost.js +10 -0
- package/bin/devflow-create-persona.js +10 -0
- package/bin/devflow-init.js +10 -0
- package/bin/devflow-memory.js +10 -0
- package/bin/devflow-new-doc.js +10 -0
- package/bin/devflow-personalize.js +10 -0
- package/bin/devflow-setup-checkpoint.js +10 -0
- package/bin/devflow-story.js +10 -0
- package/bin/devflow-tech-debt.js +10 -0
- package/bin/devflow-validate-overrides.js +10 -0
- package/bin/devflow-validate.js +10 -0
- package/bin/devflow-version.js +10 -0
- package/lib/constants.js +30 -0
- package/lib/exec-python.js +78 -0
- package/lib/python-check.js +178 -0
- package/package.json +64 -0
- package/tooling/.automation/agents/architect.md +135 -0
- package/tooling/.automation/agents/ba.md +70 -0
- package/tooling/.automation/agents/dev.md +79 -0
- package/tooling/.automation/agents/maintainer.md +97 -0
- package/tooling/.automation/agents/pm.md +116 -0
- package/tooling/.automation/agents/reviewer.md +141 -0
- package/tooling/.automation/agents/sm.md +61 -0
- package/tooling/.automation/agents/writer.md +193 -0
- package/tooling/.automation/config.ps1.template +61 -0
- package/tooling/.automation/config.sh.template +48 -0
- package/tooling/.automation/memory/.gitkeep +6 -0
- package/tooling/.automation/memory/knowledge/kg_integration-test.json +94 -0
- package/tooling/.automation/memory/knowledge/kg_test-story.json +300 -0
- package/tooling/.automation/memory/shared/shared_integration-test.json +30 -0
- package/tooling/.automation/memory/shared/shared_test-story.json +78 -0
- package/tooling/.automation/overrides/templates/README.md +113 -0
- package/tooling/.automation/overrides/templates/architect/README.md +27 -0
- package/tooling/.automation/overrides/templates/architect/cloud-native.yaml +92 -0
- package/tooling/.automation/overrides/templates/architect/enterprise-architect.yaml +85 -0
- package/tooling/.automation/overrides/templates/architect/pragmatic-minimalist.yaml +88 -0
- package/tooling/.automation/overrides/templates/ba/README.md +27 -0
- package/tooling/.automation/overrides/templates/ba/agile-storyteller.yaml +86 -0
- package/tooling/.automation/overrides/templates/ba/domain-expert.yaml +91 -0
- package/tooling/.automation/overrides/templates/ba/requirements-engineer.yaml +89 -0
- package/tooling/.automation/overrides/templates/dev/README.md +32 -0
- package/tooling/.automation/overrides/templates/dev/junior-mentored.yaml +39 -0
- package/tooling/.automation/overrides/templates/dev/performance-engineer.yaml +43 -0
- package/tooling/.automation/overrides/templates/dev/rapid-prototyper.yaml +52 -0
- package/tooling/.automation/overrides/templates/dev/security-focused.yaml +43 -0
- package/tooling/.automation/overrides/templates/dev/senior-fullstack.yaml +39 -0
- package/tooling/.automation/overrides/templates/maintainer/README.md +27 -0
- package/tooling/.automation/overrides/templates/maintainer/devops-maintainer.yaml +113 -0
- package/tooling/.automation/overrides/templates/maintainer/legacy-steward.yaml +94 -0
- package/tooling/.automation/overrides/templates/maintainer/oss-maintainer.yaml +94 -0
- package/tooling/.automation/overrides/templates/pm/README.md +27 -0
- package/tooling/.automation/overrides/templates/pm/agile-pm.yaml +91 -0
- package/tooling/.automation/overrides/templates/pm/hybrid-delivery.yaml +87 -0
- package/tooling/.automation/overrides/templates/pm/traditional-pm.yaml +91 -0
- package/tooling/.automation/overrides/templates/reviewer/README.md +11 -0
- package/tooling/.automation/overrides/templates/reviewer/mentoring-reviewer.yaml +45 -0
- package/tooling/.automation/overrides/templates/reviewer/quick-sanity.yaml +50 -0
- package/tooling/.automation/overrides/templates/reviewer/thorough-critic.yaml +48 -0
- package/tooling/.automation/overrides/templates/sm/README.md +11 -0
- package/tooling/.automation/overrides/templates/sm/agile-coach.yaml +52 -0
- package/tooling/.automation/overrides/templates/sm/startup-pm.yaml +50 -0
- package/tooling/.automation/overrides/templates/sm/technical-lead.yaml +47 -0
- package/tooling/.automation/overrides/templates/user-profile.template.yaml +62 -0
- package/tooling/.automation/overrides/templates/writer/README.md +27 -0
- package/tooling/.automation/overrides/templates/writer/api-documentarian.yaml +99 -0
- package/tooling/.automation/overrides/templates/writer/docs-as-code.yaml +108 -0
- package/tooling/.automation/overrides/templates/writer/user-guide-author.yaml +100 -0
- package/tooling/completions/DevflowCompletion.ps1 +213 -0
- package/tooling/completions/_run-story +116 -0
- package/tooling/completions/run-story-completion.bash +136 -0
- package/tooling/docs/DOC-STANDARD.md +717 -0
- package/tooling/docs/sprint-status.yaml.template +24 -0
- package/tooling/docs/templates/bug-report.md +234 -0
- package/tooling/docs/templates/migration-spec.md +274 -0
- package/tooling/docs/templates/refactor-spec.md +86 -0
- package/tooling/docs/templates/tech-debt.md +86 -0
- package/tooling/scripts/context_checkpoint.py +556 -0
- package/tooling/scripts/cost_dashboard.py +617 -0
- package/tooling/scripts/create-persona.py +690 -0
- package/tooling/scripts/create-persona.sh +435 -0
- package/tooling/scripts/init-project-workflow.ps1 +651 -0
- package/tooling/scripts/init-project-workflow.py +70 -0
- package/tooling/scripts/init-project-workflow.sh +746 -0
- package/tooling/scripts/lib/__init__.py +35 -0
- package/tooling/scripts/lib/agent_handoff.py +526 -0
- package/tooling/scripts/lib/agent_router.py +698 -0
- package/tooling/scripts/lib/checkpoint-integration.ps1 +245 -0
- package/tooling/scripts/lib/checkpoint-integration.sh +191 -0
- package/tooling/scripts/lib/claude-cli.ps1 +952 -0
- package/tooling/scripts/lib/claude-cli.sh +1293 -0
- package/tooling/scripts/lib/cost_config.py +222 -0
- package/tooling/scripts/lib/cost_display.py +443 -0
- package/tooling/scripts/lib/cost_tracker.py +710 -0
- package/tooling/scripts/lib/currency_converter.py +328 -0
- package/tooling/scripts/lib/errors.py +438 -0
- package/tooling/scripts/lib/override-loader.sh +286 -0
- package/tooling/scripts/lib/pair_programming.py +589 -0
- package/tooling/scripts/lib/shared_memory.py +637 -0
- package/tooling/scripts/lib/swarm_orchestrator.py +689 -0
- package/tooling/scripts/memory_summarize.py +324 -0
- package/tooling/scripts/new-doc.ps1 +405 -0
- package/tooling/scripts/new-doc.py +93 -0
- package/tooling/scripts/new-doc.sh +534 -0
- package/tooling/scripts/personalize_agent.py +385 -0
- package/tooling/scripts/rollback-migration.sh +540 -0
- package/tooling/scripts/run-collab.ps1 +251 -0
- package/tooling/scripts/run-collab.py +605 -0
- package/tooling/scripts/run-collab.sh +110 -0
- package/tooling/scripts/run-story.ps1 +490 -0
- package/tooling/scripts/run-story.py +387 -0
- package/tooling/scripts/run-story.sh +467 -0
- package/tooling/scripts/setup-checkpoint-service.ps1 +219 -0
- package/tooling/scripts/setup-checkpoint-service.py +87 -0
- package/tooling/scripts/setup-checkpoint-service.sh +236 -0
- package/tooling/scripts/tech-debt-tracker.py +608 -0
- package/tooling/scripts/update_version.py +244 -0
- package/tooling/scripts/validate-overrides.py +511 -0
- package/tooling/scripts/validate-overrides.sh +432 -0
- package/tooling/scripts/validate_setup.py +539 -0
|
@@ -0,0 +1,698 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Agent Router - Dynamic Agent Selection
|
|
4
|
+
|
|
5
|
+
Automatically selects the best agent(s) for a task based on:
|
|
6
|
+
- Task complexity analysis
|
|
7
|
+
- Code context and file types
|
|
8
|
+
- Historical performance data
|
|
9
|
+
- Task type detection (bug, feature, refactor, etc.)
|
|
10
|
+
|
|
11
|
+
Features:
|
|
12
|
+
- Smart task classification
|
|
13
|
+
- Multi-agent routing for complex tasks
|
|
14
|
+
- Confidence scoring
|
|
15
|
+
- Override support for manual selection
|
|
16
|
+
|
|
17
|
+
Usage:
|
|
18
|
+
from lib.agent_router import AgentRouter, TaskAnalysis
|
|
19
|
+
|
|
20
|
+
router = AgentRouter()
|
|
21
|
+
result = router.route("Fix authentication bug in login.py")
|
|
22
|
+
print(f"Recommended agents: {result.agents}")
|
|
23
|
+
print(f"Confidence: {result.confidence}")
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
import re
|
|
27
|
+
from dataclasses import dataclass, field
|
|
28
|
+
from enum import Enum
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from typing import Optional
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class TaskType(Enum):
|
|
34
|
+
"""Types of development tasks."""
|
|
35
|
+
|
|
36
|
+
FEATURE = "feature" # New functionality
|
|
37
|
+
BUGFIX = "bugfix" # Bug fix
|
|
38
|
+
REFACTOR = "refactor" # Code restructuring
|
|
39
|
+
SECURITY = "security" # Security-related
|
|
40
|
+
PERFORMANCE = "performance" # Performance optimization
|
|
41
|
+
DOCUMENTATION = "documentation" # Docs update
|
|
42
|
+
TESTING = "testing" # Test addition/fix
|
|
43
|
+
MIGRATION = "migration" # Data/code migration
|
|
44
|
+
TECH_DEBT = "tech_debt" # Technical debt
|
|
45
|
+
INVESTIGATION = "investigation" # Code exploration
|
|
46
|
+
ARCHITECTURE = "architecture" # Design decisions
|
|
47
|
+
QUICK_FIX = "quick_fix" # Small, targeted fix
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class Complexity(Enum):
|
|
51
|
+
"""Task complexity levels."""
|
|
52
|
+
|
|
53
|
+
TRIVIAL = 1 # Single file, simple change
|
|
54
|
+
LOW = 2 # Few files, straightforward
|
|
55
|
+
MEDIUM = 3 # Multiple files, some complexity
|
|
56
|
+
HIGH = 4 # Many files, complex logic
|
|
57
|
+
CRITICAL = 5 # System-wide, critical path
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass
|
|
61
|
+
class Agent:
|
|
62
|
+
"""Agent configuration."""
|
|
63
|
+
|
|
64
|
+
name: str
|
|
65
|
+
model: str
|
|
66
|
+
specialties: list[str]
|
|
67
|
+
cost_tier: str # low, medium, high
|
|
68
|
+
max_complexity: int # Max complexity this agent handles well
|
|
69
|
+
|
|
70
|
+
def __hash__(self):
|
|
71
|
+
return hash(self.name)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# Agent definitions with specialties
|
|
75
|
+
AGENTS = {
|
|
76
|
+
"SM": Agent(
|
|
77
|
+
name="SM",
|
|
78
|
+
model="sonnet",
|
|
79
|
+
specialties=["planning", "context", "coordination", "review"],
|
|
80
|
+
cost_tier="low",
|
|
81
|
+
max_complexity=5,
|
|
82
|
+
),
|
|
83
|
+
"DEV": Agent(
|
|
84
|
+
name="DEV",
|
|
85
|
+
model="opus",
|
|
86
|
+
specialties=["implementation", "coding", "feature", "bugfix"],
|
|
87
|
+
cost_tier="high",
|
|
88
|
+
max_complexity=5,
|
|
89
|
+
),
|
|
90
|
+
"BA": Agent(
|
|
91
|
+
name="BA",
|
|
92
|
+
model="sonnet",
|
|
93
|
+
specialties=["requirements", "analysis", "user-stories", "acceptance-criteria"],
|
|
94
|
+
cost_tier="low",
|
|
95
|
+
max_complexity=3,
|
|
96
|
+
),
|
|
97
|
+
"ARCHITECT": Agent(
|
|
98
|
+
name="ARCHITECT",
|
|
99
|
+
model="sonnet",
|
|
100
|
+
specialties=["design", "architecture", "patterns", "system-design", "scalability"],
|
|
101
|
+
cost_tier="low",
|
|
102
|
+
max_complexity=5,
|
|
103
|
+
),
|
|
104
|
+
"PM": Agent(
|
|
105
|
+
name="PM",
|
|
106
|
+
model="sonnet",
|
|
107
|
+
specialties=["planning", "prioritization", "stakeholders", "roadmap"],
|
|
108
|
+
cost_tier="low",
|
|
109
|
+
max_complexity=3,
|
|
110
|
+
),
|
|
111
|
+
"WRITER": Agent(
|
|
112
|
+
name="WRITER",
|
|
113
|
+
model="sonnet",
|
|
114
|
+
specialties=["documentation", "readme", "api-docs", "tutorials"],
|
|
115
|
+
cost_tier="low",
|
|
116
|
+
max_complexity=3,
|
|
117
|
+
),
|
|
118
|
+
"MAINTAINER": Agent(
|
|
119
|
+
name="MAINTAINER",
|
|
120
|
+
model="sonnet", # Uses Opus for complex tasks
|
|
121
|
+
specialties=["bugfix", "refactor", "tech-debt", "legacy", "maintenance"],
|
|
122
|
+
cost_tier="medium",
|
|
123
|
+
max_complexity=4,
|
|
124
|
+
),
|
|
125
|
+
"REVIEWER": Agent(
|
|
126
|
+
name="REVIEWER",
|
|
127
|
+
model="opus",
|
|
128
|
+
specialties=["review", "quality", "security", "best-practices", "critique"],
|
|
129
|
+
cost_tier="high",
|
|
130
|
+
max_complexity=5,
|
|
131
|
+
),
|
|
132
|
+
"SECURITY": Agent(
|
|
133
|
+
name="SECURITY",
|
|
134
|
+
model="opus",
|
|
135
|
+
specialties=["security", "vulnerabilities", "auth", "encryption", "penetration"],
|
|
136
|
+
cost_tier="high",
|
|
137
|
+
max_complexity=5,
|
|
138
|
+
),
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
# Keyword patterns for task type detection
|
|
143
|
+
TASK_PATTERNS = {
|
|
144
|
+
TaskType.BUGFIX: [
|
|
145
|
+
r"\bbug\b",
|
|
146
|
+
r"\bfix\b",
|
|
147
|
+
r"\berror\b",
|
|
148
|
+
r"\bcrash\b",
|
|
149
|
+
r"\bbroken\b",
|
|
150
|
+
r"\bfailing\b",
|
|
151
|
+
r"\bissue\b",
|
|
152
|
+
r"\bdefect\b",
|
|
153
|
+
r"\bregression\b",
|
|
154
|
+
],
|
|
155
|
+
TaskType.SECURITY: [
|
|
156
|
+
r"\bsecurity\b",
|
|
157
|
+
r"\bvulnerability\b",
|
|
158
|
+
r"\bauth\b",
|
|
159
|
+
r"\bpassword\b",
|
|
160
|
+
r"\btoken\b",
|
|
161
|
+
r"\bencrypt\b",
|
|
162
|
+
r"\bxss\b",
|
|
163
|
+
r"\bsql.?injection\b",
|
|
164
|
+
r"\bcve\b",
|
|
165
|
+
r"\boauth\b",
|
|
166
|
+
r"\bjwt\b",
|
|
167
|
+
r"\bcors\b",
|
|
168
|
+
],
|
|
169
|
+
TaskType.PERFORMANCE: [
|
|
170
|
+
r"\bperformance\b",
|
|
171
|
+
r"\bslow\b",
|
|
172
|
+
r"\boptimize\b",
|
|
173
|
+
r"\blatency\b",
|
|
174
|
+
r"\bcache\b",
|
|
175
|
+
r"\bmemory\b",
|
|
176
|
+
r"\bcpu\b",
|
|
177
|
+
r"\bprofile\b",
|
|
178
|
+
r"\bbottleneck\b",
|
|
179
|
+
],
|
|
180
|
+
TaskType.REFACTOR: [
|
|
181
|
+
r"\brefactor\b",
|
|
182
|
+
r"\brestructure\b",
|
|
183
|
+
r"\bcleanup\b",
|
|
184
|
+
r"\bclean.?up\b",
|
|
185
|
+
r"\breorganize\b",
|
|
186
|
+
r"\bsimplify\b",
|
|
187
|
+
r"\bmodularize\b",
|
|
188
|
+
],
|
|
189
|
+
TaskType.DOCUMENTATION: [
|
|
190
|
+
r"\bdoc\b",
|
|
191
|
+
r"\bdocument\b",
|
|
192
|
+
r"\breadme\b",
|
|
193
|
+
r"\bcomment\b",
|
|
194
|
+
r"\bexplain\b",
|
|
195
|
+
r"\bapi.?doc\b",
|
|
196
|
+
r"\btutorial\b",
|
|
197
|
+
],
|
|
198
|
+
TaskType.TESTING: [
|
|
199
|
+
r"\btest\b",
|
|
200
|
+
r"\bspec\b",
|
|
201
|
+
r"\bcoverage\b",
|
|
202
|
+
r"\bassert\b",
|
|
203
|
+
r"\bmock\b",
|
|
204
|
+
r"\bunit.?test\b",
|
|
205
|
+
r"\bintegration.?test\b",
|
|
206
|
+
],
|
|
207
|
+
TaskType.MIGRATION: [
|
|
208
|
+
r"\bmigrat\b",
|
|
209
|
+
r"\bupgrad\b",
|
|
210
|
+
r"\bconvert\b",
|
|
211
|
+
r"\bport\b",
|
|
212
|
+
r"\bversion\b",
|
|
213
|
+
r"\bdeprecate\b",
|
|
214
|
+
],
|
|
215
|
+
TaskType.TECH_DEBT: [
|
|
216
|
+
r"\btech.?debt\b",
|
|
217
|
+
r"\btodo\b",
|
|
218
|
+
r"\bfixme\b",
|
|
219
|
+
r"\bhack\b",
|
|
220
|
+
r"\bworkaround\b",
|
|
221
|
+
r"\btemporary\b",
|
|
222
|
+
r"\blegacy\b",
|
|
223
|
+
],
|
|
224
|
+
TaskType.ARCHITECTURE: [
|
|
225
|
+
r"\barchitect\b",
|
|
226
|
+
r"\bdesign\b",
|
|
227
|
+
r"\bpattern\b",
|
|
228
|
+
r"\bstructure\b",
|
|
229
|
+
r"\bscalable\b",
|
|
230
|
+
r"\bmicroservice\b",
|
|
231
|
+
r"\bmonolith\b",
|
|
232
|
+
r"\bapi.?design\b",
|
|
233
|
+
],
|
|
234
|
+
TaskType.INVESTIGATION: [
|
|
235
|
+
r"\binvestigate\b",
|
|
236
|
+
r"\bexplore\b",
|
|
237
|
+
r"\banalyze\b",
|
|
238
|
+
r"\bunderstand\b",
|
|
239
|
+
r"\bresearch\b",
|
|
240
|
+
r"\bspike\b",
|
|
241
|
+
r"\bpoc\b",
|
|
242
|
+
],
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
# File extension to specialty mapping
|
|
246
|
+
FILE_SPECIALTIES = {
|
|
247
|
+
# Security-sensitive files
|
|
248
|
+
".pem": ["security"],
|
|
249
|
+
".key": ["security"],
|
|
250
|
+
".env": ["security"],
|
|
251
|
+
# Documentation
|
|
252
|
+
".md": ["documentation"],
|
|
253
|
+
".rst": ["documentation"],
|
|
254
|
+
".txt": ["documentation"],
|
|
255
|
+
# Test files
|
|
256
|
+
"test_": ["testing"],
|
|
257
|
+
"_test.": ["testing"],
|
|
258
|
+
".spec.": ["testing"],
|
|
259
|
+
# Config files - often architectural
|
|
260
|
+
".yaml": ["architecture", "devops"],
|
|
261
|
+
".yml": ["architecture", "devops"],
|
|
262
|
+
".toml": ["architecture"],
|
|
263
|
+
".json": ["configuration"],
|
|
264
|
+
# Database
|
|
265
|
+
".sql": ["database", "migration"],
|
|
266
|
+
"migration": ["migration", "database"],
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
@dataclass
|
|
271
|
+
class TaskAnalysis:
|
|
272
|
+
"""Result of analyzing a task."""
|
|
273
|
+
|
|
274
|
+
task_type: TaskType
|
|
275
|
+
complexity: Complexity
|
|
276
|
+
detected_patterns: list[str]
|
|
277
|
+
file_contexts: list[str]
|
|
278
|
+
confidence: float # 0.0 to 1.0
|
|
279
|
+
|
|
280
|
+
def to_dict(self) -> dict:
|
|
281
|
+
return {
|
|
282
|
+
"task_type": self.task_type.value,
|
|
283
|
+
"complexity": self.complexity.value,
|
|
284
|
+
"detected_patterns": self.detected_patterns,
|
|
285
|
+
"file_contexts": self.file_contexts,
|
|
286
|
+
"confidence": self.confidence,
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
@dataclass
|
|
291
|
+
class RoutingResult:
|
|
292
|
+
"""Result of agent routing."""
|
|
293
|
+
|
|
294
|
+
agents: list[str] # Ordered list of recommended agents
|
|
295
|
+
workflow: str # sequential, parallel, swarm
|
|
296
|
+
task_analysis: TaskAnalysis
|
|
297
|
+
reasoning: str
|
|
298
|
+
alternative_agents: list[str] = field(default_factory=list)
|
|
299
|
+
model_overrides: dict[str, str] = field(default_factory=dict) # agent -> model
|
|
300
|
+
|
|
301
|
+
def to_dict(self) -> dict:
|
|
302
|
+
return {
|
|
303
|
+
"agents": self.agents,
|
|
304
|
+
"workflow": self.workflow,
|
|
305
|
+
"task_analysis": self.task_analysis.to_dict(),
|
|
306
|
+
"reasoning": self.reasoning,
|
|
307
|
+
"alternative_agents": self.alternative_agents,
|
|
308
|
+
"model_overrides": self.model_overrides,
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
class AgentRouter:
|
|
313
|
+
"""Dynamic agent selection based on task analysis."""
|
|
314
|
+
|
|
315
|
+
def __init__(self, project_root: Optional[Path] = None):
|
|
316
|
+
self.project_root = project_root or Path.cwd()
|
|
317
|
+
self.agents = AGENTS.copy()
|
|
318
|
+
|
|
319
|
+
def analyze_task(self, description: str, files: Optional[list[str]] = None) -> TaskAnalysis:
|
|
320
|
+
"""
|
|
321
|
+
Analyze a task to determine type and complexity.
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
description: Natural language description of the task to analyze.
|
|
325
|
+
Keywords in the description are matched against patterns
|
|
326
|
+
to determine task type (bugfix, security, feature, etc.)
|
|
327
|
+
files: Optional list of file paths related to the task. Used to:
|
|
328
|
+
- Detect security-sensitive files (.pem, .key, .env)
|
|
329
|
+
- Identify documentation tasks (.md, .rst files)
|
|
330
|
+
- Detect test-related work (test_ prefixed files)
|
|
331
|
+
- Infer architectural scope from config files
|
|
332
|
+
If None, analysis is based solely on the description.
|
|
333
|
+
|
|
334
|
+
Returns:
|
|
335
|
+
TaskAnalysis object containing detected task type, complexity level,
|
|
336
|
+
matched patterns, file contexts, and confidence score (0.0-1.0).
|
|
337
|
+
"""
|
|
338
|
+
description_lower = description.lower()
|
|
339
|
+
|
|
340
|
+
# Detect task type from patterns
|
|
341
|
+
type_scores: dict[TaskType, int] = {}
|
|
342
|
+
detected_patterns: list[str] = []
|
|
343
|
+
|
|
344
|
+
for task_type, patterns in TASK_PATTERNS.items():
|
|
345
|
+
score = 0
|
|
346
|
+
for pattern in patterns:
|
|
347
|
+
matches = re.findall(pattern, description_lower)
|
|
348
|
+
if matches:
|
|
349
|
+
score += len(matches)
|
|
350
|
+
detected_patterns.extend(matches)
|
|
351
|
+
if score > 0:
|
|
352
|
+
type_scores[task_type] = score
|
|
353
|
+
|
|
354
|
+
# Determine primary task type
|
|
355
|
+
if type_scores:
|
|
356
|
+
task_type = max(type_scores.keys(), key=lambda t: type_scores[t])
|
|
357
|
+
else:
|
|
358
|
+
task_type = TaskType.FEATURE # Default
|
|
359
|
+
|
|
360
|
+
# Analyze file contexts
|
|
361
|
+
file_contexts: list[str] = []
|
|
362
|
+
if files:
|
|
363
|
+
for file_path in files:
|
|
364
|
+
for pattern, specialties in FILE_SPECIALTIES.items():
|
|
365
|
+
if pattern in file_path.lower():
|
|
366
|
+
file_contexts.extend(specialties)
|
|
367
|
+
|
|
368
|
+
# Estimate complexity
|
|
369
|
+
complexity = self._estimate_complexity(description, files)
|
|
370
|
+
|
|
371
|
+
# Calculate confidence
|
|
372
|
+
confidence = min(1.0, 0.3 + (0.1 * len(detected_patterns)) + (0.1 * len(file_contexts)))
|
|
373
|
+
|
|
374
|
+
return TaskAnalysis(
|
|
375
|
+
task_type=task_type,
|
|
376
|
+
complexity=complexity,
|
|
377
|
+
detected_patterns=list(set(detected_patterns)),
|
|
378
|
+
file_contexts=list(set(file_contexts)),
|
|
379
|
+
confidence=confidence,
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
def _estimate_complexity(
|
|
383
|
+
self, description: str, files: Optional[list[str]] = None
|
|
384
|
+
) -> Complexity:
|
|
385
|
+
"""Estimate task complexity."""
|
|
386
|
+
score = 1
|
|
387
|
+
|
|
388
|
+
# Description-based heuristics
|
|
389
|
+
complexity_indicators = {
|
|
390
|
+
"simple": -1,
|
|
391
|
+
"trivial": -1,
|
|
392
|
+
"minor": -1,
|
|
393
|
+
"quick": -1,
|
|
394
|
+
"complex": 2,
|
|
395
|
+
"difficult": 2,
|
|
396
|
+
"critical": 3,
|
|
397
|
+
"major": 2,
|
|
398
|
+
"redesign": 3,
|
|
399
|
+
"rewrite": 3,
|
|
400
|
+
"overhaul": 3,
|
|
401
|
+
"multiple": 1,
|
|
402
|
+
"several": 1,
|
|
403
|
+
"many": 2,
|
|
404
|
+
"across": 1,
|
|
405
|
+
"throughout": 2,
|
|
406
|
+
"system-wide": 3,
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
desc_lower = description.lower()
|
|
410
|
+
for indicator, delta in complexity_indicators.items():
|
|
411
|
+
if indicator in desc_lower:
|
|
412
|
+
score += delta
|
|
413
|
+
|
|
414
|
+
# File count based
|
|
415
|
+
if files:
|
|
416
|
+
file_count = len(files)
|
|
417
|
+
if file_count > 10:
|
|
418
|
+
score += 2
|
|
419
|
+
elif file_count > 5:
|
|
420
|
+
score += 1
|
|
421
|
+
|
|
422
|
+
# Clamp to valid range
|
|
423
|
+
score = max(1, min(5, score))
|
|
424
|
+
|
|
425
|
+
return Complexity(score)
|
|
426
|
+
|
|
427
|
+
def route(
|
|
428
|
+
self,
|
|
429
|
+
description: str,
|
|
430
|
+
files: Optional[list[str]] = None,
|
|
431
|
+
prefer_cost: bool = False,
|
|
432
|
+
force_agents: Optional[list[str]] = None,
|
|
433
|
+
) -> RoutingResult:
|
|
434
|
+
"""Route a task to the appropriate agent(s)."""
|
|
435
|
+
|
|
436
|
+
# Allow manual override
|
|
437
|
+
if force_agents:
|
|
438
|
+
analysis = self.analyze_task(description, files)
|
|
439
|
+
return RoutingResult(
|
|
440
|
+
agents=force_agents,
|
|
441
|
+
workflow="sequential",
|
|
442
|
+
task_analysis=analysis,
|
|
443
|
+
reasoning="Manual agent selection override",
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
# Analyze the task
|
|
447
|
+
analysis = self.analyze_task(description, files)
|
|
448
|
+
|
|
449
|
+
# Select agents based on task type and complexity
|
|
450
|
+
agents, workflow, reasoning = self._select_agents(analysis, prefer_cost)
|
|
451
|
+
|
|
452
|
+
# Determine model overrides for complex tasks
|
|
453
|
+
model_overrides = {}
|
|
454
|
+
if analysis.complexity.value >= 4:
|
|
455
|
+
# Use Opus for complex tasks even for typically Sonnet agents
|
|
456
|
+
for agent in agents:
|
|
457
|
+
if self.agents[agent].cost_tier == "low":
|
|
458
|
+
model_overrides[agent] = "opus"
|
|
459
|
+
|
|
460
|
+
# Find alternative agents
|
|
461
|
+
alternatives = self._find_alternatives(agents, analysis)
|
|
462
|
+
|
|
463
|
+
return RoutingResult(
|
|
464
|
+
agents=agents,
|
|
465
|
+
workflow=workflow,
|
|
466
|
+
task_analysis=analysis,
|
|
467
|
+
reasoning=reasoning,
|
|
468
|
+
alternative_agents=alternatives,
|
|
469
|
+
model_overrides=model_overrides,
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
def _select_agents(
|
|
473
|
+
self, analysis: TaskAnalysis, prefer_cost: bool
|
|
474
|
+
) -> tuple[list[str], str, str]:
|
|
475
|
+
"""Select agents based on task analysis."""
|
|
476
|
+
|
|
477
|
+
task_type = analysis.task_type
|
|
478
|
+
complexity = analysis.complexity
|
|
479
|
+
|
|
480
|
+
# Define routing rules
|
|
481
|
+
routing_rules = {
|
|
482
|
+
TaskType.FEATURE: {
|
|
483
|
+
"simple": (["DEV"], "sequential", "Direct implementation"),
|
|
484
|
+
"complex": (
|
|
485
|
+
["SM", "ARCHITECT", "DEV", "REVIEWER"],
|
|
486
|
+
"sequential",
|
|
487
|
+
"Full pipeline for complex feature",
|
|
488
|
+
),
|
|
489
|
+
},
|
|
490
|
+
TaskType.BUGFIX: {
|
|
491
|
+
"simple": (["MAINTAINER"], "sequential", "Simple bug fix"),
|
|
492
|
+
"complex": (
|
|
493
|
+
["DEV", "REVIEWER"],
|
|
494
|
+
"sequential",
|
|
495
|
+
"Complex bug requires senior review",
|
|
496
|
+
),
|
|
497
|
+
},
|
|
498
|
+
TaskType.SECURITY: {
|
|
499
|
+
"simple": (["SECURITY", "DEV"], "sequential", "Security fix with review"),
|
|
500
|
+
"complex": (
|
|
501
|
+
["SECURITY", "DEV", "REVIEWER"],
|
|
502
|
+
"swarm",
|
|
503
|
+
"Critical security requires multi-agent review",
|
|
504
|
+
),
|
|
505
|
+
},
|
|
506
|
+
TaskType.PERFORMANCE: {
|
|
507
|
+
"simple": (["DEV"], "sequential", "Performance optimization"),
|
|
508
|
+
"complex": (
|
|
509
|
+
["ARCHITECT", "DEV", "REVIEWER"],
|
|
510
|
+
"sequential",
|
|
511
|
+
"Architectural review for performance",
|
|
512
|
+
),
|
|
513
|
+
},
|
|
514
|
+
TaskType.REFACTOR: {
|
|
515
|
+
"simple": (["MAINTAINER"], "sequential", "Simple refactor"),
|
|
516
|
+
"complex": (
|
|
517
|
+
["ARCHITECT", "DEV", "REVIEWER"],
|
|
518
|
+
"sequential",
|
|
519
|
+
"Architectural oversight for major refactor",
|
|
520
|
+
),
|
|
521
|
+
},
|
|
522
|
+
TaskType.DOCUMENTATION: {
|
|
523
|
+
"simple": (["WRITER"], "sequential", "Documentation update"),
|
|
524
|
+
"complex": (["BA", "WRITER"], "sequential", "Requirements review before docs"),
|
|
525
|
+
},
|
|
526
|
+
TaskType.TESTING: {
|
|
527
|
+
"simple": (["DEV"], "sequential", "Test implementation"),
|
|
528
|
+
"complex": (["DEV", "REVIEWER"], "sequential", "Test review for coverage"),
|
|
529
|
+
},
|
|
530
|
+
TaskType.MIGRATION: {
|
|
531
|
+
"simple": (["MAINTAINER"], "sequential", "Simple migration"),
|
|
532
|
+
"complex": (
|
|
533
|
+
["ARCHITECT", "DEV", "REVIEWER"],
|
|
534
|
+
"sequential",
|
|
535
|
+
"Full review for migration",
|
|
536
|
+
),
|
|
537
|
+
},
|
|
538
|
+
TaskType.TECH_DEBT: {
|
|
539
|
+
"simple": (["MAINTAINER"], "sequential", "Tech debt cleanup"),
|
|
540
|
+
"complex": (
|
|
541
|
+
["ARCHITECT", "MAINTAINER", "REVIEWER"],
|
|
542
|
+
"sequential",
|
|
543
|
+
"Architectural review for major cleanup",
|
|
544
|
+
),
|
|
545
|
+
},
|
|
546
|
+
TaskType.ARCHITECTURE: {
|
|
547
|
+
"simple": (["ARCHITECT"], "sequential", "Design decision"),
|
|
548
|
+
"complex": (["ARCHITECT", "DEV", "REVIEWER"], "swarm", "Multi-agent design review"),
|
|
549
|
+
},
|
|
550
|
+
TaskType.INVESTIGATION: {
|
|
551
|
+
"simple": (["SM"], "sequential", "Code exploration"),
|
|
552
|
+
"complex": (
|
|
553
|
+
["SM", "ARCHITECT"],
|
|
554
|
+
"sequential",
|
|
555
|
+
"Deep analysis with architecture context",
|
|
556
|
+
),
|
|
557
|
+
},
|
|
558
|
+
TaskType.QUICK_FIX: {
|
|
559
|
+
"simple": (["MAINTAINER"], "sequential", "Quick targeted fix"),
|
|
560
|
+
"complex": (["MAINTAINER"], "sequential", "Quick fix"),
|
|
561
|
+
},
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
# Determine complexity level
|
|
565
|
+
complexity_level = "simple" if complexity.value <= 2 else "complex"
|
|
566
|
+
|
|
567
|
+
# Get routing
|
|
568
|
+
rule = routing_rules.get(task_type, {}).get(complexity_level)
|
|
569
|
+
if not rule:
|
|
570
|
+
rule = (["DEV"], "sequential", "Default routing")
|
|
571
|
+
|
|
572
|
+
agents, workflow, reasoning = rule
|
|
573
|
+
|
|
574
|
+
# Cost optimization
|
|
575
|
+
if prefer_cost and complexity.value <= 3:
|
|
576
|
+
# Replace Opus agents with Sonnet equivalents where possible
|
|
577
|
+
cost_effective_agents = []
|
|
578
|
+
for agent in agents:
|
|
579
|
+
if agent == "REVIEWER" and complexity.value <= 2:
|
|
580
|
+
cost_effective_agents.append("SM") # SM can do simple reviews
|
|
581
|
+
else:
|
|
582
|
+
cost_effective_agents.append(agent)
|
|
583
|
+
agents = cost_effective_agents
|
|
584
|
+
reasoning += " (cost-optimized)"
|
|
585
|
+
|
|
586
|
+
return agents, workflow, reasoning
|
|
587
|
+
|
|
588
|
+
def _find_alternatives(self, primary: list[str], analysis: TaskAnalysis) -> list[str]:
|
|
589
|
+
"""Find alternative agents that could handle the task."""
|
|
590
|
+
alternatives = []
|
|
591
|
+
|
|
592
|
+
for name, agent in self.agents.items():
|
|
593
|
+
if name in primary:
|
|
594
|
+
continue
|
|
595
|
+
|
|
596
|
+
# Check if agent specialties match any file contexts or detected patterns
|
|
597
|
+
matches = set(agent.specialties) & (
|
|
598
|
+
set(analysis.file_contexts) | {p.lower() for p in analysis.detected_patterns}
|
|
599
|
+
)
|
|
600
|
+
|
|
601
|
+
if matches and agent.max_complexity >= analysis.complexity.value:
|
|
602
|
+
alternatives.append(name)
|
|
603
|
+
|
|
604
|
+
return alternatives[:3] # Top 3 alternatives
|
|
605
|
+
|
|
606
|
+
def get_workflow_for_agents(self, agents: list[str]) -> str:
|
|
607
|
+
"""Determine the best workflow for a set of agents."""
|
|
608
|
+
if len(agents) == 1:
|
|
609
|
+
return "single"
|
|
610
|
+
|
|
611
|
+
# If includes REVIEWER and DEV, could use pair mode
|
|
612
|
+
if "DEV" in agents and "REVIEWER" in agents and len(agents) == 2:
|
|
613
|
+
return "pair"
|
|
614
|
+
|
|
615
|
+
# If includes ARCHITECT, likely needs sequential
|
|
616
|
+
if "ARCHITECT" in agents:
|
|
617
|
+
return "sequential"
|
|
618
|
+
|
|
619
|
+
# If 3+ agents with REVIEWER, consider swarm
|
|
620
|
+
if len(agents) >= 3 and "REVIEWER" in agents:
|
|
621
|
+
return "swarm"
|
|
622
|
+
|
|
623
|
+
return "sequential"
|
|
624
|
+
|
|
625
|
+
def explain_routing(self, result: RoutingResult) -> str:
|
|
626
|
+
"""Generate a human-readable explanation of the routing decision."""
|
|
627
|
+
lines = [
|
|
628
|
+
"🎯 **Task Analysis**",
|
|
629
|
+
f" - Type: {result.task_analysis.task_type.value}",
|
|
630
|
+
f" - Complexity: {result.task_analysis.complexity.name} ({result.task_analysis.complexity.value}/5)",
|
|
631
|
+
f" - Confidence: {result.task_analysis.confidence:.0%}",
|
|
632
|
+
"",
|
|
633
|
+
"🤖 **Recommended Agents**",
|
|
634
|
+
]
|
|
635
|
+
|
|
636
|
+
for i, agent in enumerate(result.agents, 1):
|
|
637
|
+
agent_info = self.agents.get(agent)
|
|
638
|
+
model = result.model_overrides.get(agent, agent_info.model if agent_info else "sonnet")
|
|
639
|
+
lines.append(f" {i}. **{agent}** (model: {model})")
|
|
640
|
+
|
|
641
|
+
lines.extend(
|
|
642
|
+
[
|
|
643
|
+
"",
|
|
644
|
+
f"📋 **Workflow**: {result.workflow}",
|
|
645
|
+
f"💡 **Reasoning**: {result.reasoning}",
|
|
646
|
+
]
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
if result.alternative_agents:
|
|
650
|
+
lines.extend(["", f"🔄 **Alternatives**: {', '.join(result.alternative_agents)}"])
|
|
651
|
+
|
|
652
|
+
if result.task_analysis.detected_patterns:
|
|
653
|
+
lines.extend(
|
|
654
|
+
[
|
|
655
|
+
"",
|
|
656
|
+
f"🔍 **Detected patterns**: {', '.join(result.task_analysis.detected_patterns[:5])}",
|
|
657
|
+
]
|
|
658
|
+
)
|
|
659
|
+
|
|
660
|
+
return "\n".join(lines)
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
# Convenience functions
|
|
664
|
+
def route_task(description: str, files: Optional[list[str]] = None) -> RoutingResult:
|
|
665
|
+
"""Quick routing of a task."""
|
|
666
|
+
router = AgentRouter()
|
|
667
|
+
return router.route(description, files)
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+
def explain_route(description: str, files: Optional[list[str]] = None) -> str:
|
|
671
|
+
"""Get an explanation of how a task would be routed."""
|
|
672
|
+
router = AgentRouter()
|
|
673
|
+
result = router.route(description, files)
|
|
674
|
+
return router.explain_routing(result)
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+
if __name__ == "__main__":
|
|
678
|
+
# Demo usage
|
|
679
|
+
print("=== Agent Router Demo ===\n")
|
|
680
|
+
|
|
681
|
+
router = AgentRouter()
|
|
682
|
+
|
|
683
|
+
test_cases = [
|
|
684
|
+
"Fix login authentication bug in auth.py",
|
|
685
|
+
"Add user profile feature with photo upload",
|
|
686
|
+
"Security vulnerability in password hashing",
|
|
687
|
+
"Refactor payment service for better maintainability",
|
|
688
|
+
"Update README with new API documentation",
|
|
689
|
+
"Performance optimization for database queries",
|
|
690
|
+
"Investigate memory leak in production",
|
|
691
|
+
"Migrate from React 17 to React 18",
|
|
692
|
+
]
|
|
693
|
+
|
|
694
|
+
for task in test_cases:
|
|
695
|
+
print(f'Task: "{task}"')
|
|
696
|
+
result = router.route(task)
|
|
697
|
+
print(router.explain_routing(result))
|
|
698
|
+
print("\n" + "=" * 60 + "\n")
|