WIP: Save agent roles integration work before CHORUS rebrand
- Agent roles and coordination features - Chat API integration testing - New configuration and workspace management 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
233
repository/factory.go
Normal file
233
repository/factory.go
Normal file
@@ -0,0 +1,233 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/anthonyrawlins/bzzz/gitea"
|
||||
"github.com/anthonyrawlins/bzzz/github"
|
||||
)
|
||||
|
||||
// DefaultProviderFactory implements ProviderFactory
|
||||
type DefaultProviderFactory struct{}
|
||||
|
||||
// CreateProvider creates a task provider based on configuration
|
||||
func (f *DefaultProviderFactory) CreateProvider(ctx context.Context, config *Config) (TaskProvider, error) {
|
||||
switch strings.ToLower(config.Provider) {
|
||||
case "gitea":
|
||||
return f.createGiteaProvider(ctx, config)
|
||||
case "github":
|
||||
return f.createGitHubProvider(ctx, config)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported provider: %s", config.Provider)
|
||||
}
|
||||
}
|
||||
|
||||
// SupportedProviders returns list of supported providers
|
||||
func (f *DefaultProviderFactory) SupportedProviders() []string {
|
||||
return []string{"gitea", "github"}
|
||||
}
|
||||
|
||||
// createGiteaProvider creates a Gitea task provider
|
||||
func (f *DefaultProviderFactory) createGiteaProvider(ctx context.Context, config *Config) (TaskProvider, error) {
|
||||
giteaConfig := &gitea.Config{
|
||||
BaseURL: config.BaseURL,
|
||||
AccessToken: config.AccessToken,
|
||||
Owner: config.Owner,
|
||||
Repository: config.Repository,
|
||||
TaskLabel: config.TaskLabel,
|
||||
InProgressLabel: config.InProgressLabel,
|
||||
CompletedLabel: config.CompletedLabel,
|
||||
Assignee: config.Assignee,
|
||||
BaseBranch: config.BaseBranch,
|
||||
BranchPrefix: config.BranchPrefix,
|
||||
}
|
||||
|
||||
client, err := gitea.NewClient(ctx, giteaConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create Gitea client: %w", err)
|
||||
}
|
||||
|
||||
return &GiteaProvider{
|
||||
client: client,
|
||||
config: config,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// createGitHubProvider creates a GitHub task provider
|
||||
func (f *DefaultProviderFactory) createGitHubProvider(ctx context.Context, config *Config) (TaskProvider, error) {
|
||||
githubConfig := &github.Config{
|
||||
AccessToken: config.AccessToken,
|
||||
Owner: config.Owner,
|
||||
Repository: config.Repository,
|
||||
TaskLabel: config.TaskLabel,
|
||||
InProgressLabel: config.InProgressLabel,
|
||||
CompletedLabel: config.CompletedLabel,
|
||||
Assignee: config.Assignee,
|
||||
BaseBranch: config.BaseBranch,
|
||||
BranchPrefix: config.BranchPrefix,
|
||||
}
|
||||
|
||||
client, err := github.NewClient(ctx, githubConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create GitHub client: %w", err)
|
||||
}
|
||||
|
||||
return &GitHubProvider{
|
||||
client: client,
|
||||
config: config,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GiteaProvider adapts Gitea client to TaskProvider interface
|
||||
type GiteaProvider struct {
|
||||
client *gitea.Client
|
||||
config *Config
|
||||
}
|
||||
|
||||
// ListAvailableTasks implements TaskProvider interface
|
||||
func (p *GiteaProvider) ListAvailableTasks() ([]*Task, error) {
|
||||
giteaTasks, err := p.client.ListAvailableTasks()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tasks := make([]*Task, len(giteaTasks))
|
||||
for i, gTask := range giteaTasks {
|
||||
tasks[i] = p.convertGiteaTask(gTask)
|
||||
}
|
||||
|
||||
return tasks, nil
|
||||
}
|
||||
|
||||
// ClaimTask implements TaskProvider interface
|
||||
func (p *GiteaProvider) ClaimTask(issueNumber int64, agentID string) (*Task, error) {
|
||||
gTask, err := p.client.ClaimTask(issueNumber, agentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p.convertGiteaTask(gTask), nil
|
||||
}
|
||||
|
||||
// CompleteTask implements TaskProvider interface
|
||||
func (p *GiteaProvider) CompleteTask(issueNumber int64, agentID string, results map[string]interface{}) error {
|
||||
return p.client.CompleteTask(issueNumber, agentID, results)
|
||||
}
|
||||
|
||||
// GetTask implements TaskProvider interface
|
||||
func (p *GiteaProvider) GetTask(issueNumber int64) (*Task, error) {
|
||||
// This would need to be implemented in the Gitea client
|
||||
return nil, fmt.Errorf("GetTask not yet implemented for Gitea provider")
|
||||
}
|
||||
|
||||
// GetConfig implements TaskProvider interface
|
||||
func (p *GiteaProvider) GetConfig() *Config {
|
||||
return p.config
|
||||
}
|
||||
|
||||
// convertGiteaTask converts a Gitea task to the unified Task format
|
||||
func (p *GiteaProvider) convertGiteaTask(gTask *gitea.Task) *Task {
|
||||
labels := make([]string, len(gTask.Labels))
|
||||
for i, label := range gTask.Labels {
|
||||
labels[i] = label.Name
|
||||
}
|
||||
|
||||
assignee := ""
|
||||
if gTask.Assignee != nil {
|
||||
assignee = gTask.Assignee.Login
|
||||
}
|
||||
|
||||
return &Task{
|
||||
ID: gTask.ID,
|
||||
Number: gTask.Number,
|
||||
Title: gTask.Title,
|
||||
Description: gTask.Description,
|
||||
State: gTask.State,
|
||||
Provider: "gitea",
|
||||
Repository: fmt.Sprintf("%s/%s", p.config.Owner, p.config.Repository),
|
||||
CreatedAt: gTask.CreatedAt,
|
||||
UpdatedAt: gTask.UpdatedAt,
|
||||
Assignee: assignee,
|
||||
Labels: labels,
|
||||
TaskType: gTask.TaskType,
|
||||
Priority: gTask.Priority,
|
||||
Requirements: gTask.Requirements,
|
||||
Deliverables: gTask.Deliverables,
|
||||
Context: gTask.Context,
|
||||
RequiredRole: gTask.RequiredRole,
|
||||
RequiredExpertise: gTask.RequiredExpertise,
|
||||
}
|
||||
}
|
||||
|
||||
// GitHubProvider adapts GitHub client to TaskProvider interface
|
||||
type GitHubProvider struct {
|
||||
client *github.Client
|
||||
config *Config
|
||||
}
|
||||
|
||||
// ListAvailableTasks implements TaskProvider interface
|
||||
func (p *GitHubProvider) ListAvailableTasks() ([]*Task, error) {
|
||||
githubTasks, err := p.client.ListAvailableTasks()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tasks := make([]*Task, len(githubTasks))
|
||||
for i, gTask := range githubTasks {
|
||||
tasks[i] = p.convertGitHubTask(gTask)
|
||||
}
|
||||
|
||||
return tasks, nil
|
||||
}
|
||||
|
||||
// ClaimTask implements TaskProvider interface
|
||||
func (p *GitHubProvider) ClaimTask(issueNumber int64, agentID string) (*Task, error) {
|
||||
gTask, err := p.client.ClaimTask(int(issueNumber), agentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p.convertGitHubTask(gTask), nil
|
||||
}
|
||||
|
||||
// CompleteTask implements TaskProvider interface
|
||||
func (p *GitHubProvider) CompleteTask(issueNumber int64, agentID string, results map[string]interface{}) error {
|
||||
return p.client.CompleteTask(int(issueNumber), agentID, results)
|
||||
}
|
||||
|
||||
// GetTask implements TaskProvider interface
|
||||
func (p *GitHubProvider) GetTask(issueNumber int64) (*Task, error) {
|
||||
// This would need to be implemented in the GitHub client
|
||||
return nil, fmt.Errorf("GetTask not yet implemented for GitHub provider")
|
||||
}
|
||||
|
||||
// GetConfig implements TaskProvider interface
|
||||
func (p *GitHubProvider) GetConfig() *Config {
|
||||
return p.config
|
||||
}
|
||||
|
||||
// convertGitHubTask converts a GitHub task to the unified Task format
|
||||
func (p *GitHubProvider) convertGitHubTask(gTask *github.Task) *Task {
|
||||
return &Task{
|
||||
ID: gTask.ID,
|
||||
Number: int64(gTask.Number),
|
||||
Title: gTask.Title,
|
||||
Description: gTask.Description,
|
||||
State: gTask.State,
|
||||
Provider: "github",
|
||||
Repository: fmt.Sprintf("%s/%s", p.config.Owner, p.config.Repository),
|
||||
CreatedAt: gTask.CreatedAt,
|
||||
UpdatedAt: gTask.UpdatedAt,
|
||||
Assignee: gTask.Assignee,
|
||||
Labels: gTask.Labels,
|
||||
TaskType: gTask.TaskType,
|
||||
Priority: gTask.Priority,
|
||||
Requirements: gTask.Requirements,
|
||||
Deliverables: gTask.Deliverables,
|
||||
Context: gTask.Context,
|
||||
RequiredRole: "", // Would need to be parsed from GitHub task
|
||||
RequiredExpertise: []string{}, // Would need to be parsed from GitHub task
|
||||
}
|
||||
}
|
||||
164
repository/interface.go
Normal file
164
repository/interface.go
Normal file
@@ -0,0 +1,164 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TaskProvider defines the interface for task management systems (GitHub, Gitea, etc.)
|
||||
type TaskProvider interface {
|
||||
// Task lifecycle management
|
||||
ListAvailableTasks() ([]*Task, error)
|
||||
ClaimTask(issueNumber int64, agentID string) (*Task, error)
|
||||
CompleteTask(issueNumber int64, agentID string, results map[string]interface{}) error
|
||||
|
||||
// Task metadata
|
||||
GetTask(issueNumber int64) (*Task, error)
|
||||
|
||||
// Configuration
|
||||
GetConfig() *Config
|
||||
}
|
||||
|
||||
// Task represents a unified task interface across different providers
|
||||
type Task struct {
|
||||
// Basic task information
|
||||
ID int64 `json:"id"`
|
||||
Number int64 `json:"number"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
State string `json:"state"` // open, closed
|
||||
Provider string `json:"provider"` // github, gitea
|
||||
Repository string `json:"repository"` // owner/repo
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
// Assignment information
|
||||
Assignee string `json:"assignee,omitempty"`
|
||||
Labels []string `json:"labels"`
|
||||
|
||||
// Bzzz-specific metadata
|
||||
TaskType string `json:"task_type"`
|
||||
Priority int `json:"priority"`
|
||||
Requirements []string `json:"requirements"`
|
||||
Deliverables []string `json:"deliverables"`
|
||||
Context map[string]interface{} `json:"context"`
|
||||
RequiredRole string `json:"required_role"`
|
||||
RequiredExpertise []string `json:"required_expertise"`
|
||||
|
||||
// Role-based assignment hints
|
||||
PreferredAgents []string `json:"preferred_agents,omitempty"`
|
||||
ExcludedAgents []string `json:"excluded_agents,omitempty"`
|
||||
CollaborationMode string `json:"collaboration_mode,omitempty"` // solo, pair, team
|
||||
}
|
||||
|
||||
// Config represents unified configuration for task providers
|
||||
type Config struct {
|
||||
// Provider type
|
||||
Provider string `json:"provider"` // "github" or "gitea"
|
||||
|
||||
// Connection details
|
||||
BaseURL string `json:"base_url"`
|
||||
AccessToken string `json:"access_token"`
|
||||
Owner string `json:"owner"`
|
||||
Repository string `json:"repository"`
|
||||
|
||||
// Task management settings
|
||||
TaskLabel string `json:"task_label"`
|
||||
InProgressLabel string `json:"in_progress_label"`
|
||||
CompletedLabel string `json:"completed_label"`
|
||||
Assignee string `json:"assignee"`
|
||||
|
||||
// Branch management
|
||||
BaseBranch string `json:"base_branch"`
|
||||
BranchPrefix string `json:"branch_prefix"`
|
||||
}
|
||||
|
||||
// TaskFilter represents criteria for filtering tasks
|
||||
type TaskFilter struct {
|
||||
// Basic filters
|
||||
State string `json:"state,omitempty"` // open, closed, all
|
||||
Labels []string `json:"labels,omitempty"` // Must have these labels
|
||||
Assignee string `json:"assignee,omitempty"` // Assigned to specific user
|
||||
|
||||
// Role-based filters
|
||||
RequiredRole string `json:"required_role,omitempty"`
|
||||
RequiredExpertise []string `json:"required_expertise,omitempty"`
|
||||
|
||||
// Priority filters
|
||||
MinPriority int `json:"min_priority,omitempty"`
|
||||
MaxPriority int `json:"max_priority,omitempty"`
|
||||
|
||||
// Task type filters
|
||||
TaskTypes []string `json:"task_types,omitempty"`
|
||||
|
||||
// Pagination
|
||||
Page int `json:"page,omitempty"`
|
||||
PerPage int `json:"per_page,omitempty"`
|
||||
}
|
||||
|
||||
// AssignmentRequest represents a task assignment request
|
||||
type AssignmentRequest struct {
|
||||
TaskNumber int64 `json:"task_number"`
|
||||
AgentID string `json:"agent_id"`
|
||||
AgentRole string `json:"agent_role,omitempty"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
}
|
||||
|
||||
// CompletionResult represents task completion information
|
||||
type CompletionResult struct {
|
||||
TaskNumber int64 `json:"task_number"`
|
||||
AgentID string `json:"agent_id"`
|
||||
Status string `json:"status"` // success, failed, partial
|
||||
Results map[string]interface{} `json:"results"`
|
||||
ErrorMessage string `json:"error_message,omitempty"`
|
||||
PullRequestURL string `json:"pull_request_url,omitempty"`
|
||||
BranchName string `json:"branch_name,omitempty"`
|
||||
CommitSHA string `json:"commit_sha,omitempty"`
|
||||
}
|
||||
|
||||
// ProviderFactory creates task providers based on configuration
|
||||
type ProviderFactory interface {
|
||||
CreateProvider(ctx context.Context, config *Config) (TaskProvider, error)
|
||||
SupportedProviders() []string
|
||||
}
|
||||
|
||||
// TaskMatcher defines interface for role-based task assignment
|
||||
type TaskMatcher interface {
|
||||
// Match tasks to agent roles and expertise
|
||||
MatchTasksToRole(tasks []*Task, role string, expertise []string) ([]*Task, error)
|
||||
|
||||
// Score task suitability for an agent
|
||||
ScoreTaskForAgent(task *Task, agentRole string, expertise []string) float64
|
||||
|
||||
// Get recommended agents for a task
|
||||
GetRecommendedAgents(task *Task, availableAgents []AgentInfo) ([]AgentInfo, error)
|
||||
}
|
||||
|
||||
// AgentInfo represents information about an available agent
|
||||
type AgentInfo struct {
|
||||
ID string `json:"id"`
|
||||
Role string `json:"role"`
|
||||
Expertise []string `json:"expertise"`
|
||||
CurrentTasks int `json:"current_tasks"`
|
||||
MaxTasks int `json:"max_tasks"`
|
||||
Status string `json:"status"`
|
||||
LastSeen time.Time `json:"last_seen"`
|
||||
Performance float64 `json:"performance"` // Performance score 0-1
|
||||
Availability float64 `json:"availability"` // Availability score 0-1
|
||||
}
|
||||
|
||||
// TaskEvent represents events that can trigger actions
|
||||
type TaskEvent struct {
|
||||
Type string `json:"type"` // created, updated, assigned, completed, commented
|
||||
TaskNumber int64 `json:"task_number"`
|
||||
Repository string `json:"repository"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Actor string `json:"actor"` // User who triggered the event
|
||||
Data map[string]interface{} `json:"data"`
|
||||
}
|
||||
|
||||
// WebhookHandler defines interface for handling repository webhooks
|
||||
type WebhookHandler interface {
|
||||
HandleWebhook(payload []byte, eventType string) (*TaskEvent, error)
|
||||
SupportedEvents() []string
|
||||
}
|
||||
334
repository/matcher.go
Normal file
334
repository/matcher.go
Normal file
@@ -0,0 +1,334 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DefaultTaskMatcher implements TaskMatcher interface
|
||||
type DefaultTaskMatcher struct{}
|
||||
|
||||
// MatchTasksToRole filters tasks suitable for a specific role and expertise
|
||||
func (m *DefaultTaskMatcher) MatchTasksToRole(tasks []*Task, role string, expertise []string) ([]*Task, error) {
|
||||
var matchedTasks []*Task
|
||||
|
||||
for _, task := range tasks {
|
||||
score := m.ScoreTaskForAgent(task, role, expertise)
|
||||
if score > 0.3 { // Minimum threshold for task suitability
|
||||
matchedTasks = append(matchedTasks, task)
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by score (highest first)
|
||||
sort.Slice(matchedTasks, func(i, j int) bool {
|
||||
scoreI := m.ScoreTaskForAgent(matchedTasks[i], role, expertise)
|
||||
scoreJ := m.ScoreTaskForAgent(matchedTasks[j], role, expertise)
|
||||
return scoreI > scoreJ
|
||||
})
|
||||
|
||||
return matchedTasks, nil
|
||||
}
|
||||
|
||||
// ScoreTaskForAgent calculates how suitable a task is for an agent based on role and expertise
|
||||
func (m *DefaultTaskMatcher) ScoreTaskForAgent(task *Task, agentRole string, agentExpertise []string) float64 {
|
||||
score := 0.0
|
||||
|
||||
// Base score for role matching
|
||||
if task.RequiredRole != "" {
|
||||
if task.RequiredRole == agentRole {
|
||||
score += 0.5 // Perfect role match
|
||||
} else if m.isCompatibleRole(task.RequiredRole, agentRole) {
|
||||
score += 0.3 // Compatible role
|
||||
}
|
||||
} else {
|
||||
// No specific role required, default bonus for general roles
|
||||
if m.isGeneralRole(agentRole) {
|
||||
score += 0.2
|
||||
}
|
||||
}
|
||||
|
||||
// Expertise matching
|
||||
expertiseScore := m.calculateExpertiseScore(task.RequiredExpertise, agentExpertise)
|
||||
score += expertiseScore * 0.4
|
||||
|
||||
// Priority bonus (higher priority tasks get small bonus)
|
||||
priorityBonus := float64(task.Priority) / 10.0 * 0.1
|
||||
score += priorityBonus
|
||||
|
||||
// Task type bonuses based on agent role
|
||||
taskTypeScore := m.calculateTaskTypeScore(task, agentRole)
|
||||
score += taskTypeScore * 0.1
|
||||
|
||||
// Label-based scoring
|
||||
labelScore := m.calculateLabelScore(task.Labels, agentRole, agentExpertise)
|
||||
score += labelScore * 0.1
|
||||
|
||||
// Ensure score is between 0 and 1
|
||||
if score > 1.0 {
|
||||
score = 1.0
|
||||
}
|
||||
if score < 0.0 {
|
||||
score = 0.0
|
||||
}
|
||||
|
||||
return score
|
||||
}
|
||||
|
||||
// GetRecommendedAgents returns agents best suited for a task
|
||||
func (m *DefaultTaskMatcher) GetRecommendedAgents(task *Task, availableAgents []AgentInfo) ([]AgentInfo, error) {
|
||||
type agentScore struct {
|
||||
agent AgentInfo
|
||||
score float64
|
||||
}
|
||||
|
||||
var scoredAgents []agentScore
|
||||
|
||||
for _, agent := range availableAgents {
|
||||
// Skip agents that are offline or at capacity
|
||||
if agent.Status != "online" && agent.Status != "ready" {
|
||||
continue
|
||||
}
|
||||
if agent.CurrentTasks >= agent.MaxTasks {
|
||||
continue
|
||||
}
|
||||
|
||||
// Calculate base task suitability score
|
||||
taskScore := m.ScoreTaskForAgent(task, agent.Role, agent.Expertise)
|
||||
|
||||
// Apply agent-specific modifiers
|
||||
finalScore := taskScore
|
||||
|
||||
// Performance modifier (0.5 to 1.5 multiplier)
|
||||
performanceMod := 0.5 + agent.Performance
|
||||
finalScore *= performanceMod
|
||||
|
||||
// Availability modifier
|
||||
availabilityMod := agent.Availability
|
||||
finalScore *= availabilityMod
|
||||
|
||||
// Workload penalty (agents with fewer current tasks get slight bonus)
|
||||
workloadRatio := float64(agent.CurrentTasks) / float64(agent.MaxTasks)
|
||||
workloadBonus := (1.0 - workloadRatio) * 0.1
|
||||
finalScore += workloadBonus
|
||||
|
||||
// Recent activity bonus (agents seen recently get small bonus)
|
||||
timeSinceLastSeen := time.Since(agent.LastSeen)
|
||||
if timeSinceLastSeen < 5*time.Minute {
|
||||
finalScore += 0.05
|
||||
}
|
||||
|
||||
if finalScore > 0.1 { // Minimum threshold
|
||||
scoredAgents = append(scoredAgents, agentScore{agent: agent, score: finalScore})
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by score (highest first)
|
||||
sort.Slice(scoredAgents, func(i, j int) bool {
|
||||
return scoredAgents[i].score > scoredAgents[j].score
|
||||
})
|
||||
|
||||
// Return top agents (up to 5)
|
||||
maxAgents := 5
|
||||
if len(scoredAgents) < maxAgents {
|
||||
maxAgents = len(scoredAgents)
|
||||
}
|
||||
|
||||
result := make([]AgentInfo, maxAgents)
|
||||
for i := 0; i < maxAgents; i++ {
|
||||
result[i] = scoredAgents[i].agent
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// calculateExpertiseScore computes overlap between required and agent expertise
|
||||
func (m *DefaultTaskMatcher) calculateExpertiseScore(requiredExpertise, agentExpertise []string) float64 {
|
||||
if len(requiredExpertise) == 0 {
|
||||
return 0.5 // No specific expertise required
|
||||
}
|
||||
|
||||
matches := 0
|
||||
for _, required := range requiredExpertise {
|
||||
for _, agent := range agentExpertise {
|
||||
if strings.EqualFold(required, agent) || m.isRelatedExpertise(required, agent) {
|
||||
matches++
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Score based on percentage of required expertise covered
|
||||
score := float64(matches) / float64(len(requiredExpertise))
|
||||
|
||||
// Bonus for having additional relevant expertise
|
||||
if len(agentExpertise) > len(requiredExpertise) {
|
||||
bonus := 0.1 * float64(len(agentExpertise)-len(requiredExpertise)) / float64(len(agentExpertise))
|
||||
score += bonus
|
||||
}
|
||||
|
||||
return score
|
||||
}
|
||||
|
||||
// calculateTaskTypeScore gives bonuses based on task type and agent role compatibility
|
||||
func (m *DefaultTaskMatcher) calculateTaskTypeScore(task *Task, agentRole string) float64 {
|
||||
taskType := strings.ToLower(task.TaskType)
|
||||
role := strings.ToLower(agentRole)
|
||||
|
||||
switch taskType {
|
||||
case "bug_fix", "bug":
|
||||
if strings.Contains(role, "qa") || strings.Contains(role, "test") {
|
||||
return 0.8
|
||||
}
|
||||
if strings.Contains(role, "backend") || strings.Contains(role, "full_stack") {
|
||||
return 0.6
|
||||
}
|
||||
case "feature", "enhancement":
|
||||
if strings.Contains(role, "developer") || strings.Contains(role, "engineer") {
|
||||
return 0.7
|
||||
}
|
||||
case "documentation", "docs":
|
||||
if strings.Contains(role, "writer") || strings.Contains(role, "documentation") {
|
||||
return 0.9
|
||||
}
|
||||
case "security":
|
||||
if strings.Contains(role, "security") {
|
||||
return 0.9
|
||||
}
|
||||
case "design":
|
||||
if strings.Contains(role, "designer") || strings.Contains(role, "ux") {
|
||||
return 0.9
|
||||
}
|
||||
case "infrastructure", "devops":
|
||||
if strings.Contains(role, "devops") || strings.Contains(role, "systems") {
|
||||
return 0.9
|
||||
}
|
||||
}
|
||||
|
||||
return 0.0
|
||||
}
|
||||
|
||||
// calculateLabelScore analyzes task labels for additional role matching
|
||||
func (m *DefaultTaskMatcher) calculateLabelScore(labels []string, agentRole string, agentExpertise []string) float64 {
|
||||
score := 0.0
|
||||
role := strings.ToLower(agentRole)
|
||||
|
||||
for _, label := range labels {
|
||||
label = strings.ToLower(label)
|
||||
|
||||
// Direct role matches
|
||||
if strings.Contains(role, label) || strings.Contains(label, role) {
|
||||
score += 0.3
|
||||
}
|
||||
|
||||
// Expertise matches
|
||||
for _, expertise := range agentExpertise {
|
||||
if strings.Contains(strings.ToLower(expertise), label) || strings.Contains(label, strings.ToLower(expertise)) {
|
||||
score += 0.2
|
||||
}
|
||||
}
|
||||
|
||||
// Specific label bonuses
|
||||
switch label {
|
||||
case "frontend":
|
||||
if strings.Contains(role, "frontend") || strings.Contains(role, "ui") {
|
||||
score += 0.4
|
||||
}
|
||||
case "backend":
|
||||
if strings.Contains(role, "backend") || strings.Contains(role, "api") {
|
||||
score += 0.4
|
||||
}
|
||||
case "urgent", "critical":
|
||||
score += 0.1 // Small bonus for urgent tasks
|
||||
case "good first issue", "beginner":
|
||||
if strings.Contains(role, "junior") {
|
||||
score += 0.3
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return score
|
||||
}
|
||||
|
||||
// isCompatibleRole checks if two roles are compatible
|
||||
func (m *DefaultTaskMatcher) isCompatibleRole(requiredRole, agentRole string) bool {
|
||||
compatibilityMap := map[string][]string{
|
||||
"frontend_developer": {"full_stack_engineer", "ui_ux_designer"},
|
||||
"backend_developer": {"full_stack_engineer", "database_engineer"},
|
||||
"full_stack_engineer": {"frontend_developer", "backend_developer"},
|
||||
"qa_engineer": {"full_stack_engineer", "backend_developer"},
|
||||
"devops_engineer": {"systems_engineer", "backend_developer"},
|
||||
"ui_ux_designer": {"frontend_developer", "lead_designer"},
|
||||
"security_expert": {"backend_developer", "senior_software_architect"},
|
||||
"technical_writer": {"full_stack_engineer"},
|
||||
"database_engineer": {"backend_developer", "full_stack_engineer"},
|
||||
"senior_software_architect": {"full_stack_engineer", "backend_developer", "frontend_developer"},
|
||||
}
|
||||
|
||||
compatibleRoles, exists := compatibilityMap[requiredRole]
|
||||
if !exists {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, compatible := range compatibleRoles {
|
||||
if compatible == agentRole {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// isGeneralRole checks if a role is considered general-purpose
|
||||
func (m *DefaultTaskMatcher) isGeneralRole(role string) bool {
|
||||
generalRoles := []string{
|
||||
"full_stack_engineer",
|
||||
"senior_software_architect",
|
||||
"general_developer",
|
||||
}
|
||||
|
||||
for _, general := range generalRoles {
|
||||
if general == role {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// isRelatedExpertise checks if two expertise areas are related
|
||||
func (m *DefaultTaskMatcher) isRelatedExpertise(required, agent string) bool {
|
||||
relatedMap := map[string][]string{
|
||||
"frontend": {"javascript", "typescript", "react", "vue", "angular", "css", "html"},
|
||||
"backend": {"api_development", "server_frameworks", "databases", "microservices"},
|
||||
"database": {"sql", "nosql", "data_modeling", "query_optimization"},
|
||||
"security": {"cybersecurity", "owasp", "vulnerability_analysis", "penetration_testing"},
|
||||
"devops": {"deployment", "infrastructure", "docker", "kubernetes", "cicd"},
|
||||
"testing": {"qa_methodologies", "test_automation", "debugging"},
|
||||
"design": {"ui_ux", "user_experience", "prototyping", "design_systems"},
|
||||
"documentation": {"technical_writing", "api_documentation"},
|
||||
}
|
||||
|
||||
required = strings.ToLower(required)
|
||||
agent = strings.ToLower(agent)
|
||||
|
||||
// Check direct related expertise
|
||||
if related, exists := relatedMap[required]; exists {
|
||||
for _, rel := range related {
|
||||
if strings.Contains(agent, rel) || strings.Contains(rel, agent) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check reverse mapping
|
||||
if related, exists := relatedMap[agent]; exists {
|
||||
for _, rel := range related {
|
||||
if strings.Contains(required, rel) || strings.Contains(rel, required) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user