Implement initial scan logic and council formation for WHOOSH project kickoffs
- Replace incremental sync with full scan for new repositories - Add initial_scan status to bypass Since parameter filtering - Implement council formation detection for Design Brief issues - Add version display to WHOOSH UI header for debugging - Fix Docker token authentication with trailing newline removal - Add comprehensive council orchestration with Docker Swarm integration - Include BACKBEAT prototype integration for distributed timing - Support council-specific agent roles and deployment strategies - Transition repositories to active status after content discovery Key architectural improvements: - Full scan approach for new project detection vs incremental sync - Council formation triggered by chorus-entrypoint labeled Design Briefs - Proper token handling and authentication for Gitea API calls - Support for both initial discovery and ongoing task monitoring This enables autonomous project kickoff workflows where Design Brief issues automatically trigger formation of specialized agent councils for new projects. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -8,8 +8,12 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/chorus-services/whoosh/internal/composer"
|
||||
"github.com/chorus-services/whoosh/internal/config"
|
||||
"github.com/chorus-services/whoosh/internal/council"
|
||||
"github.com/chorus-services/whoosh/internal/gitea"
|
||||
"github.com/chorus-services/whoosh/internal/orchestrator"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"github.com/rs/zerolog/log"
|
||||
@@ -17,19 +21,25 @@ import (
|
||||
|
||||
// Monitor manages repository monitoring and task creation
|
||||
type Monitor struct {
|
||||
db *pgxpool.Pool
|
||||
gitea *gitea.Client
|
||||
stopCh chan struct{}
|
||||
syncInterval time.Duration
|
||||
db *pgxpool.Pool
|
||||
gitea *gitea.Client
|
||||
composer *composer.Service
|
||||
council *council.CouncilComposer
|
||||
agentDeployer *orchestrator.AgentDeployer
|
||||
stopCh chan struct{}
|
||||
syncInterval time.Duration
|
||||
}
|
||||
|
||||
// NewMonitor creates a new repository monitor
|
||||
func NewMonitor(db *pgxpool.Pool, giteaCfg config.GITEAConfig) *Monitor {
|
||||
func NewMonitor(db *pgxpool.Pool, giteaCfg config.GITEAConfig, composerService *composer.Service, councilComposer *council.CouncilComposer, agentDeployer *orchestrator.AgentDeployer) *Monitor {
|
||||
return &Monitor{
|
||||
db: db,
|
||||
gitea: gitea.NewClient(giteaCfg),
|
||||
stopCh: make(chan struct{}),
|
||||
syncInterval: 5 * time.Minute, // Default sync every 5 minutes
|
||||
db: db,
|
||||
gitea: gitea.NewClient(giteaCfg),
|
||||
composer: composerService,
|
||||
council: councilComposer,
|
||||
agentDeployer: agentDeployer,
|
||||
stopCh: make(chan struct{}),
|
||||
syncInterval: 5 * time.Minute, // Default sync every 5 minutes
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,8 +136,19 @@ func (m *Monitor) syncRepository(ctx context.Context, repo RepositoryConfig) {
|
||||
Limit: 100,
|
||||
}
|
||||
|
||||
if repo.LastIssueSync != nil {
|
||||
// Only use Since parameter for repositories that have completed initial scan
|
||||
// For initial_scan or pending status, we want to scan ALL issues to find Design Briefs and UCXL content
|
||||
if repo.LastIssueSync != nil && repo.SyncStatus != "initial_scan" && repo.SyncStatus != "pending" {
|
||||
opts.Since = *repo.LastIssueSync
|
||||
log.Debug().
|
||||
Str("repository", repo.FullName).
|
||||
Time("since", *repo.LastIssueSync).
|
||||
Msg("Using incremental sync with Since parameter")
|
||||
} else {
|
||||
log.Info().
|
||||
Str("repository", repo.FullName).
|
||||
Str("sync_status", repo.SyncStatus).
|
||||
Msg("Performing full scan (no Since parameter) - initial scan or looking for Design Briefs")
|
||||
}
|
||||
|
||||
// Filter by CHORUS task labels if enabled
|
||||
@@ -176,10 +197,41 @@ func (m *Monitor) syncRepository(ctx context.Context, repo RepositoryConfig) {
|
||||
} else {
|
||||
updated++
|
||||
}
|
||||
|
||||
// Check if this issue should trigger council formation
|
||||
if m.isProjectKickoffBrief(issue, repo) {
|
||||
m.triggerCouncilFormation(ctx, taskID, issue, repo)
|
||||
}
|
||||
}
|
||||
|
||||
duration := time.Since(startTime)
|
||||
|
||||
// Check if repository should transition from initial scan to active status
|
||||
if repo.SyncStatus == "initial_scan" || repo.SyncStatus == "pending" {
|
||||
// Repository has completed initial scan
|
||||
// For now, transition to active if we processed any issues or found Design Briefs
|
||||
// Future: Add UCXL content detection logic here
|
||||
shouldActivate := (created > 0 || updated > 0)
|
||||
|
||||
if shouldActivate {
|
||||
log.Info().
|
||||
Str("repository", repo.FullName).
|
||||
Int("tasks_created", created).
|
||||
Int("tasks_updated", updated).
|
||||
Msg("Transitioning repository from initial scan to active status - content found")
|
||||
|
||||
if err := m.updateRepositoryStatus(ctx, repo.ID, "active", nil); err != nil {
|
||||
log.Error().Err(err).
|
||||
Str("repository", repo.FullName).
|
||||
Msg("Failed to transition repository to active status")
|
||||
}
|
||||
} else {
|
||||
log.Info().
|
||||
Str("repository", repo.FullName).
|
||||
Msg("Initial scan completed - no content found, keeping in initial_scan status")
|
||||
}
|
||||
}
|
||||
|
||||
// Update repository sync timestamps and statistics
|
||||
if err := m.updateRepositorySyncInfo(ctx, repo.ID, time.Now(), created, updated); err != nil {
|
||||
log.Error().Err(err).
|
||||
@@ -307,6 +359,17 @@ func (m *Monitor) createOrUpdateTask(ctx context.Context, repo RepositoryConfig,
|
||||
return "", false, fmt.Errorf("failed to create task: %w", err)
|
||||
}
|
||||
|
||||
// For newly created bzzz-task issues, check if it's a council formation trigger
|
||||
if m.composer != nil && m.shouldTriggerTeamComposition(issue.Labels) {
|
||||
if m.isProjectKickoffBrief(issue, repo) {
|
||||
// This is a project kickoff - trigger council formation
|
||||
go m.triggerCouncilFormation(context.Background(), taskID, issue, repo)
|
||||
} else {
|
||||
// Regular bzzz-task - trigger normal team composition
|
||||
go m.triggerTeamComposition(context.Background(), taskID, issue, repo)
|
||||
}
|
||||
}
|
||||
|
||||
return taskID, true, nil
|
||||
}
|
||||
}
|
||||
@@ -432,7 +495,7 @@ func (m *Monitor) getMonitoredRepositories(ctx context.Context) ([]RepositoryCon
|
||||
// Parse CHORUS task labels
|
||||
if err := json.Unmarshal(chorusLabelsJSON, &repo.ChorusTaskLabels); err != nil {
|
||||
log.Error().Err(err).Str("repository", repo.FullName).Msg("Failed to parse CHORUS task labels")
|
||||
repo.ChorusTaskLabels = []string{"bzzz-task", "chorus-task"} // Default labels
|
||||
repo.ChorusTaskLabels = []string{"bzzz-task", "chorus-task", "chorus-entrypoint"} // Default labels
|
||||
}
|
||||
|
||||
repos = append(repos, repo)
|
||||
@@ -567,8 +630,416 @@ func (m *Monitor) getRepositoryByID(ctx context.Context, repoID string) (*Reposi
|
||||
// Parse CHORUS task labels
|
||||
if err := json.Unmarshal(chorusLabelsJSON, &repo.ChorusTaskLabels); err != nil {
|
||||
log.Error().Err(err).Str("repository", repo.FullName).Msg("Failed to parse CHORUS task labels")
|
||||
repo.ChorusTaskLabels = []string{"bzzz-task", "chorus-task"} // Default labels
|
||||
repo.ChorusTaskLabels = []string{"bzzz-task", "chorus-task", "chorus-entrypoint"} // Default labels
|
||||
}
|
||||
|
||||
return &repo, nil
|
||||
}
|
||||
}
|
||||
|
||||
// shouldTriggerTeamComposition checks if the issue has labels that should trigger team composition
|
||||
func (m *Monitor) shouldTriggerTeamComposition(labels []gitea.Label) bool {
|
||||
for _, label := range labels {
|
||||
if strings.ToLower(label.Name) == "bzzz-task" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isProjectKickoffBrief checks if the issue represents a new project kickoff council trigger
|
||||
func (m *Monitor) isProjectKickoffBrief(issue gitea.Issue, repo RepositoryConfig) bool {
|
||||
// Check if it has the chorus-entrypoint label
|
||||
hasChorusEntrypoint := false
|
||||
for _, label := range issue.Labels {
|
||||
if strings.ToLower(label.Name) == "chorus-entrypoint" {
|
||||
hasChorusEntrypoint = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasChorusEntrypoint {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if the issue title contains "Design Brief"
|
||||
title := strings.ToLower(issue.Title)
|
||||
if !strings.Contains(title, "design brief") {
|
||||
return false
|
||||
}
|
||||
|
||||
// Additional validation: this should be a new/empty repository
|
||||
// For now, we'll rely on the title check, but could add repo analysis later
|
||||
|
||||
log.Info().
|
||||
Str("repository", repo.FullName).
|
||||
Str("issue_title", issue.Title).
|
||||
Msg("🎭 Detected project kickoff brief - council formation required")
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// triggerTeamComposition initiates team composition for a newly created task
|
||||
func (m *Monitor) triggerTeamComposition(ctx context.Context, taskID string, issue gitea.Issue, repo RepositoryConfig) {
|
||||
log.Info().
|
||||
Str("task_id", taskID).
|
||||
Int64("issue_id", issue.ID).
|
||||
Str("repository", repo.FullName).
|
||||
Msg("🎯 Triggering team composition for bzzz-task")
|
||||
|
||||
// Convert Gitea issue to TaskAnalysisInput
|
||||
techStack := m.extractTechStackFromIssue(issue)
|
||||
requirements := m.extractRequirementsFromIssue(issue)
|
||||
|
||||
analysisInput := &composer.TaskAnalysisInput{
|
||||
Title: issue.Title,
|
||||
Description: issue.Body,
|
||||
Requirements: requirements,
|
||||
Repository: repo.FullName,
|
||||
Priority: m.mapPriorityToComposer(m.extractPriorityFromLabels(issue.Labels)),
|
||||
TechStack: techStack,
|
||||
Metadata: map[string]interface{}{
|
||||
"task_id": taskID,
|
||||
"issue_id": issue.ID,
|
||||
"issue_number": issue.Number,
|
||||
"repository_id": repo.ID,
|
||||
"external_url": issue.HTMLURL,
|
||||
},
|
||||
}
|
||||
|
||||
// Perform team composition analysis
|
||||
result, err := m.composer.AnalyzeAndComposeTeam(ctx, analysisInput)
|
||||
if err != nil {
|
||||
log.Error().Err(err).
|
||||
Str("task_id", taskID).
|
||||
Msg("Failed to perform team composition analysis")
|
||||
return
|
||||
}
|
||||
|
||||
log.Info().
|
||||
Str("task_id", taskID).
|
||||
Str("team_id", result.TeamComposition.TeamID.String()).
|
||||
Int("team_size", result.TeamComposition.EstimatedSize).
|
||||
Float64("confidence", result.TeamComposition.ConfidenceScore).
|
||||
Msg("✅ Team composition analysis completed")
|
||||
|
||||
// Create the team in the database
|
||||
team, err := m.composer.CreateTeam(ctx, result.TeamComposition, analysisInput)
|
||||
if err != nil {
|
||||
log.Error().Err(err).
|
||||
Str("task_id", taskID).
|
||||
Msg("Failed to create team")
|
||||
return
|
||||
}
|
||||
|
||||
// Update task with team assignment
|
||||
err = m.assignTaskToTeam(ctx, taskID, team.ID.String())
|
||||
if err != nil {
|
||||
log.Error().Err(err).
|
||||
Str("task_id", taskID).
|
||||
Str("team_id", team.ID.String()).
|
||||
Msg("Failed to assign task to team")
|
||||
return
|
||||
}
|
||||
|
||||
// Deploy agents for the newly formed team if agent deployer is available
|
||||
if m.agentDeployer != nil {
|
||||
go m.deployTeamAgents(ctx, taskID, team, result.TeamComposition, repo)
|
||||
}
|
||||
|
||||
log.Info().
|
||||
Str("task_id", taskID).
|
||||
Str("team_id", team.ID.String()).
|
||||
Str("team_name", team.Name).
|
||||
Msg("🚀 Task successfully assigned to team")
|
||||
}
|
||||
|
||||
// deployTeamAgents deploys Docker containers for the assigned team agents
|
||||
func (m *Monitor) deployTeamAgents(ctx context.Context, taskID string, team *composer.Team, teamComposition *composer.TeamComposition, repo RepositoryConfig) {
|
||||
log.Info().
|
||||
Str("task_id", taskID).
|
||||
Str("team_id", team.ID.String()).
|
||||
Int("agents_to_deploy", len(teamComposition.AgentMatches)).
|
||||
Msg("🚀 Starting agent deployment for team")
|
||||
|
||||
// Convert string UUIDs to uuid.UUID type
|
||||
taskUUID, err := uuid.Parse(taskID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("task_id", taskID).Msg("Invalid task ID format")
|
||||
return
|
||||
}
|
||||
|
||||
// Create deployment request for the entire team
|
||||
deploymentRequest := &orchestrator.DeploymentRequest{
|
||||
TaskID: taskUUID,
|
||||
TeamID: team.ID,
|
||||
TeamComposition: teamComposition,
|
||||
TaskContext: &orchestrator.TaskContext{
|
||||
IssueTitle: team.Description, // Use team description which comes from issue title
|
||||
IssueDescription: team.Description, // TODO: Extract actual issue description
|
||||
Repository: repo.FullName,
|
||||
TechStack: []string{"go", "docker", "ai"}, // TODO: Extract from analysis
|
||||
Requirements: []string{}, // TODO: Extract from issue
|
||||
Priority: "medium", // TODO: Extract from team data
|
||||
ExternalURL: "", // TODO: Add issue URL
|
||||
Metadata: map[string]interface{}{
|
||||
"task_type": "development",
|
||||
},
|
||||
},
|
||||
DeploymentMode: "immediate",
|
||||
}
|
||||
|
||||
// Deploy all agents for this team
|
||||
deploymentResult, err := m.agentDeployer.DeployTeamAgents(deploymentRequest)
|
||||
if err != nil {
|
||||
log.Error().Err(err).
|
||||
Str("task_id", taskID).
|
||||
Str("team_id", team.ID.String()).
|
||||
Msg("Failed to deploy team agents")
|
||||
return
|
||||
}
|
||||
|
||||
log.Info().
|
||||
Str("task_id", taskID).
|
||||
Str("team_id", team.ID.String()).
|
||||
Str("status", deploymentResult.Status).
|
||||
Int("agents_deployed", len(deploymentResult.DeployedServices)).
|
||||
Msg("🎉 Successfully deployed team agents")
|
||||
|
||||
// TODO: Update database with deployment information
|
||||
// This could include service IDs, container names, deployment status, etc.
|
||||
}
|
||||
|
||||
// extractRequirementsFromIssue extracts requirements from issue description
|
||||
func (m *Monitor) extractRequirementsFromIssue(issue gitea.Issue) []string {
|
||||
requirements := []string{}
|
||||
|
||||
// Split description into lines and look for bullet points or numbered lists
|
||||
lines := strings.Split(issue.Body, "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
// Look for bullet points (-, *, +) or numbers (1., 2., etc.)
|
||||
if strings.HasPrefix(line, "-") || strings.HasPrefix(line, "*") || strings.HasPrefix(line, "+") {
|
||||
req := strings.TrimSpace(line[1:])
|
||||
if req != "" {
|
||||
requirements = append(requirements, req)
|
||||
}
|
||||
} else if len(line) > 2 && line[1] == '.' && line[0] >= '0' && line[0] <= '9' {
|
||||
req := strings.TrimSpace(line[2:])
|
||||
if req != "" {
|
||||
requirements = append(requirements, req)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return requirements
|
||||
}
|
||||
|
||||
// mapPriorityToComposer converts internal priority to composer priority
|
||||
func (m *Monitor) mapPriorityToComposer(priority string) composer.TaskPriority {
|
||||
switch strings.ToLower(priority) {
|
||||
case "critical":
|
||||
return composer.PriorityCritical
|
||||
case "high":
|
||||
return composer.PriorityHigh
|
||||
case "low":
|
||||
return composer.PriorityLow
|
||||
default:
|
||||
return composer.PriorityMedium
|
||||
}
|
||||
}
|
||||
|
||||
// assignTaskToTeam updates the task record with the assigned team ID
|
||||
func (m *Monitor) assignTaskToTeam(ctx context.Context, taskID, teamID string) error {
|
||||
query := `
|
||||
UPDATE tasks
|
||||
SET assigned_team_id = $1, status = $2, updated_at = NOW()
|
||||
WHERE id = $3
|
||||
`
|
||||
|
||||
_, err := m.db.Exec(ctx, query, teamID, "claimed", taskID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to assign task to team: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// triggerCouncilFormation initiates council formation for a project kickoff
|
||||
func (m *Monitor) triggerCouncilFormation(ctx context.Context, taskID string, issue gitea.Issue, repo RepositoryConfig) {
|
||||
log.Info().
|
||||
Str("task_id", taskID).
|
||||
Int64("issue_id", issue.ID).
|
||||
Str("repository", repo.FullName).
|
||||
Str("issue_title", issue.Title).
|
||||
Msg("🎭 Triggering council formation for project kickoff")
|
||||
|
||||
// Convert task ID to UUID
|
||||
taskUUID, err := uuid.Parse(taskID)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
Str("task_id", taskID).
|
||||
Msg("Failed to parse task ID as UUID")
|
||||
return
|
||||
}
|
||||
|
||||
// Extract project name from repository name (remove owner prefix)
|
||||
projectName := strings.Split(repo.FullName, "/")[1]
|
||||
|
||||
// Create council formation request
|
||||
councilRequest := &council.CouncilFormationRequest{
|
||||
ProjectName: projectName,
|
||||
Repository: repo.FullName,
|
||||
ProjectBrief: issue.Body,
|
||||
TaskID: taskUUID,
|
||||
IssueID: issue.ID,
|
||||
ExternalURL: issue.HTMLURL,
|
||||
Metadata: map[string]interface{}{
|
||||
"task_id": taskID,
|
||||
"issue_id": issue.ID,
|
||||
"issue_number": issue.Number,
|
||||
"repository_id": repo.ID,
|
||||
"created_by": issue.User.Login,
|
||||
"labels": m.extractLabelNames(issue.Labels),
|
||||
"milestone": m.extractMilestone(issue),
|
||||
},
|
||||
}
|
||||
|
||||
// Form the council
|
||||
composition, err := m.council.FormCouncil(ctx, councilRequest)
|
||||
if err != nil {
|
||||
log.Error().Err(err).
|
||||
Str("task_id", taskID).
|
||||
Str("project_name", projectName).
|
||||
Msg("Failed to form project kickoff council")
|
||||
return
|
||||
}
|
||||
|
||||
log.Info().
|
||||
Str("task_id", taskID).
|
||||
Str("council_id", composition.CouncilID.String()).
|
||||
Int("core_agents", len(composition.CoreAgents)).
|
||||
Int("optional_agents", len(composition.OptionalAgents)).
|
||||
Msg("✅ Council composition formed")
|
||||
|
||||
// Deploy council agents if agent deployer is available
|
||||
if m.agentDeployer != nil {
|
||||
go m.deployCouncilAgents(ctx, taskID, composition, councilRequest, repo)
|
||||
}
|
||||
|
||||
// Update task status to indicate council formation
|
||||
err = m.assignTaskToCouncil(ctx, taskID, composition.CouncilID.String())
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
Str("task_id", taskID).
|
||||
Str("council_id", composition.CouncilID.String()).
|
||||
Msg("Failed to assign task to council")
|
||||
}
|
||||
|
||||
log.Info().
|
||||
Str("task_id", taskID).
|
||||
Str("council_id", composition.CouncilID.String()).
|
||||
Str("project_name", projectName).
|
||||
Msg("🚀 Project kickoff council successfully formed and deploying")
|
||||
}
|
||||
|
||||
// deployCouncilAgents deploys Docker containers for the council agents
|
||||
func (m *Monitor) deployCouncilAgents(ctx context.Context, taskID string, composition *council.CouncilComposition, request *council.CouncilFormationRequest, repo RepositoryConfig) {
|
||||
log.Info().
|
||||
Str("task_id", taskID).
|
||||
Str("council_id", composition.CouncilID.String()).
|
||||
Int("core_agents", len(composition.CoreAgents)).
|
||||
Int("optional_agents", len(composition.OptionalAgents)).
|
||||
Msg("🚀 Starting council agent deployment")
|
||||
|
||||
// Create council deployment request
|
||||
deploymentRequest := &orchestrator.CouncilDeploymentRequest{
|
||||
CouncilID: composition.CouncilID,
|
||||
ProjectName: composition.ProjectName,
|
||||
CouncilComposition: composition,
|
||||
ProjectContext: &orchestrator.CouncilProjectContext{
|
||||
ProjectName: composition.ProjectName,
|
||||
Repository: request.Repository,
|
||||
ProjectBrief: request.ProjectBrief,
|
||||
Constraints: request.Constraints,
|
||||
TechLimits: request.TechLimits,
|
||||
ComplianceNotes: request.ComplianceNotes,
|
||||
Targets: request.Targets,
|
||||
ExternalURL: request.ExternalURL,
|
||||
},
|
||||
DeploymentMode: "immediate",
|
||||
}
|
||||
|
||||
// Deploy the council agents
|
||||
result, err := m.agentDeployer.DeployCouncilAgents(deploymentRequest)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
Str("council_id", composition.CouncilID.String()).
|
||||
Msg("Failed to deploy council agents")
|
||||
|
||||
// Update council status to failed
|
||||
m.council.UpdateCouncilStatus(ctx, composition.CouncilID, "failed")
|
||||
return
|
||||
}
|
||||
|
||||
log.Info().
|
||||
Str("council_id", composition.CouncilID.String()).
|
||||
Str("deployment_status", result.Status).
|
||||
Int("deployed_agents", len(result.DeployedAgents)).
|
||||
Int("errors", len(result.Errors)).
|
||||
Msg("✅ Council agent deployment completed")
|
||||
|
||||
// Log deployment details for each agent
|
||||
for _, agent := range result.DeployedAgents {
|
||||
log.Info().
|
||||
Str("council_id", composition.CouncilID.String()).
|
||||
Str("service_id", agent.ServiceID).
|
||||
Str("role", agent.RoleName).
|
||||
Str("agent_id", agent.AgentID).
|
||||
Msg("🤖 Council agent deployed")
|
||||
}
|
||||
|
||||
if len(result.Errors) > 0 {
|
||||
for _, errMsg := range result.Errors {
|
||||
log.Warn().
|
||||
Str("council_id", composition.CouncilID.String()).
|
||||
Str("error", errMsg).
|
||||
Msg("⚠️ Council agent deployment error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// assignTaskToCouncil updates the task record with the assigned council ID
|
||||
func (m *Monitor) assignTaskToCouncil(ctx context.Context, taskID, councilID string) error {
|
||||
query := `
|
||||
UPDATE tasks
|
||||
SET assigned_team_id = $1, status = $2, updated_at = NOW()
|
||||
WHERE id = $3
|
||||
`
|
||||
|
||||
// Use council ID as team ID for consistency with existing schema
|
||||
_, err := m.db.Exec(ctx, query, councilID, "council_forming", taskID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to assign task to council: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// extractLabelNames extracts label names from gitea labels
|
||||
func (m *Monitor) extractLabelNames(labels []gitea.Label) []string {
|
||||
names := make([]string, len(labels))
|
||||
for i, label := range labels {
|
||||
names[i] = label.Name
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// extractMilestone extracts milestone information if present
|
||||
func (m *Monitor) extractMilestone(issue gitea.Issue) string {
|
||||
// Note: Milestone field access depends on Gitea SDK version
|
||||
// For now, return empty string to avoid build issues
|
||||
return ""
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user