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:
Claude Code
2025-09-12 09:49:36 +10:00
parent b5c0deb6bc
commit 56ea52b743
74 changed files with 17778 additions and 236 deletions

View File

@@ -0,0 +1,257 @@
package council
import (
"context"
"encoding/json"
"fmt"
"strings"
"time"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/rs/zerolog/log"
)
// CouncilComposer manages the formation and orchestration of project kickoff councils
type CouncilComposer struct {
db *pgxpool.Pool
ctx context.Context
cancel context.CancelFunc
}
// NewCouncilComposer creates a new council composer service
func NewCouncilComposer(db *pgxpool.Pool) *CouncilComposer {
ctx, cancel := context.WithCancel(context.Background())
return &CouncilComposer{
db: db,
ctx: ctx,
cancel: cancel,
}
}
// Close shuts down the council composer
func (cc *CouncilComposer) Close() error {
cc.cancel()
return nil
}
// FormCouncil creates a council composition for a project kickoff
func (cc *CouncilComposer) FormCouncil(ctx context.Context, request *CouncilFormationRequest) (*CouncilComposition, error) {
startTime := time.Now()
councilID := uuid.New()
log.Info().
Str("council_id", councilID.String()).
Str("project_name", request.ProjectName).
Str("repository", request.Repository).
Msg("🎭 Forming project kickoff council")
// Create core council agents (always required)
coreAgents := make([]CouncilAgent, len(CoreCouncilRoles))
for i, roleName := range CoreCouncilRoles {
agentID := fmt.Sprintf("council-%s-%s", strings.ReplaceAll(request.ProjectName, " ", "-"), roleName)
coreAgents[i] = CouncilAgent{
AgentID: agentID,
RoleName: roleName,
AgentName: cc.formatRoleName(roleName),
Required: true,
Deployed: false,
Status: "pending",
}
}
// Determine optional agents based on project characteristics
optionalAgents := cc.selectOptionalAgents(request)
// Create council composition
composition := &CouncilComposition{
CouncilID: councilID,
ProjectName: request.ProjectName,
CoreAgents: coreAgents,
OptionalAgents: optionalAgents,
CreatedAt: startTime,
Status: "forming",
}
// Store council composition in database
err := cc.storeCouncilComposition(ctx, composition, request)
if err != nil {
return nil, fmt.Errorf("failed to store council composition: %w", err)
}
log.Info().
Str("council_id", councilID.String()).
Int("core_agents", len(coreAgents)).
Int("optional_agents", len(optionalAgents)).
Dur("formation_time", time.Since(startTime)).
Msg("✅ Council composition formed")
return composition, nil
}
// selectOptionalAgents determines which optional council agents should be included
func (cc *CouncilComposer) selectOptionalAgents(request *CouncilFormationRequest) []CouncilAgent {
var selectedAgents []CouncilAgent
// Analyze project brief and characteristics to determine needed optional roles
brief := strings.ToLower(request.ProjectBrief)
// Data/AI projects
if strings.Contains(brief, "ai") || strings.Contains(brief, "machine learning") ||
strings.Contains(brief, "data") || strings.Contains(brief, "analytics") {
selectedAgents = append(selectedAgents, cc.createOptionalAgent("data-ai-architect", request.ProjectName))
}
// Privacy/compliance sensitive projects
if strings.Contains(brief, "privacy") || strings.Contains(brief, "personal data") ||
strings.Contains(brief, "gdpr") || strings.Contains(brief, "compliance") {
selectedAgents = append(selectedAgents, cc.createOptionalAgent("privacy-data-governance-officer", request.ProjectName))
}
// Regulated industries
if strings.Contains(brief, "healthcare") || strings.Contains(brief, "finance") ||
strings.Contains(brief, "banking") || strings.Contains(brief, "regulated") {
selectedAgents = append(selectedAgents, cc.createOptionalAgent("compliance-legal-liaison", request.ProjectName))
}
// Performance-critical systems
if strings.Contains(brief, "performance") || strings.Contains(brief, "high-load") ||
strings.Contains(brief, "scale") || strings.Contains(brief, "benchmark") {
selectedAgents = append(selectedAgents, cc.createOptionalAgent("performance-benchmarking-analyst", request.ProjectName))
}
// User-facing applications
if strings.Contains(brief, "user interface") || strings.Contains(brief, "ui") ||
strings.Contains(brief, "ux") || strings.Contains(brief, "frontend") {
selectedAgents = append(selectedAgents, cc.createOptionalAgent("ui-ux-designer", request.ProjectName))
}
// Mobile applications
if strings.Contains(brief, "mobile") || strings.Contains(brief, "ios") ||
strings.Contains(brief, "android") || strings.Contains(brief, "app store") {
selectedAgents = append(selectedAgents, cc.createOptionalAgent("ios-macos-developer", request.ProjectName))
}
// Games or graphics-intensive applications
if strings.Contains(brief, "game") || strings.Contains(brief, "graphics") ||
strings.Contains(brief, "rendering") || strings.Contains(brief, "3d") {
selectedAgents = append(selectedAgents, cc.createOptionalAgent("engine-programmer", request.ProjectName))
}
// Integration-heavy projects
if strings.Contains(brief, "integration") || strings.Contains(brief, "api") ||
strings.Contains(brief, "microservice") || strings.Contains(brief, "third-party") {
selectedAgents = append(selectedAgents, cc.createOptionalAgent("integration-architect", request.ProjectName))
}
// Cost-sensitive or enterprise projects
if strings.Contains(brief, "budget") || strings.Contains(brief, "cost") ||
strings.Contains(brief, "enterprise") || strings.Contains(brief, "licensing") {
selectedAgents = append(selectedAgents, cc.createOptionalAgent("cost-licensing-steward", request.ProjectName))
}
return selectedAgents
}
// createOptionalAgent creates an optional council agent
func (cc *CouncilComposer) createOptionalAgent(roleName, projectName string) CouncilAgent {
agentID := fmt.Sprintf("council-%s-%s", strings.ReplaceAll(projectName, " ", "-"), roleName)
return CouncilAgent{
AgentID: agentID,
RoleName: roleName,
AgentName: cc.formatRoleName(roleName),
Required: false,
Deployed: false,
Status: "pending",
}
}
// formatRoleName converts role key to human-readable name
func (cc *CouncilComposer) formatRoleName(roleName string) string {
// Convert kebab-case to Title Case
parts := strings.Split(roleName, "-")
for i, part := range parts {
parts[i] = strings.Title(part)
}
return strings.Join(parts, " ")
}
// storeCouncilComposition stores the council composition in the database
func (cc *CouncilComposer) storeCouncilComposition(ctx context.Context, composition *CouncilComposition, request *CouncilFormationRequest) error {
// Store council metadata
councilQuery := `
INSERT INTO councils (id, project_name, repository, project_brief, status, created_at, task_id, issue_id, external_url, metadata)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
`
metadataJSON, _ := json.Marshal(request.Metadata)
_, err := cc.db.Exec(ctx, councilQuery,
composition.CouncilID,
composition.ProjectName,
request.Repository,
request.ProjectBrief,
composition.Status,
composition.CreatedAt,
request.TaskID,
request.IssueID,
request.ExternalURL,
metadataJSON,
)
if err != nil {
return fmt.Errorf("failed to store council metadata: %w", err)
}
// Store council agents
for _, agent := range composition.CoreAgents {
err = cc.storeCouncilAgent(ctx, composition.CouncilID, agent)
if err != nil {
return fmt.Errorf("failed to store core agent %s: %w", agent.AgentID, err)
}
}
for _, agent := range composition.OptionalAgents {
err = cc.storeCouncilAgent(ctx, composition.CouncilID, agent)
if err != nil {
return fmt.Errorf("failed to store optional agent %s: %w", agent.AgentID, err)
}
}
return nil
}
// storeCouncilAgent stores a single council agent in the database
func (cc *CouncilComposer) storeCouncilAgent(ctx context.Context, councilID uuid.UUID, agent CouncilAgent) error {
query := `
INSERT INTO council_agents (council_id, agent_id, role_name, agent_name, required, deployed, status, created_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, NOW())
`
_, err := cc.db.Exec(ctx, query,
councilID,
agent.AgentID,
agent.RoleName,
agent.AgentName,
agent.Required,
agent.Deployed,
agent.Status,
)
return err
}
// GetCouncilComposition retrieves a council composition by ID
func (cc *CouncilComposer) GetCouncilComposition(ctx context.Context, councilID uuid.UUID) (*CouncilComposition, error) {
// Implementation would query the database and reconstruct the composition
// For now, return a simple error
return nil, fmt.Errorf("not implemented yet")
}
// UpdateCouncilStatus updates the status of a council
func (cc *CouncilComposer) UpdateCouncilStatus(ctx context.Context, councilID uuid.UUID, status string) error {
query := `UPDATE councils SET status = $1, updated_at = NOW() WHERE id = $2`
_, err := cc.db.Exec(ctx, query, status, councilID)
return err
}