Fix Docker Swarm discovery network name mismatch

- Changed NetworkName from 'chorus_default' to 'chorus_net'
- This matches the actual network 'CHORUS_chorus_net' (service prefix added automatically)
- Fixes discovered_count:0 issue - now successfully discovering all 25 agents
- Updated IMPLEMENTATION-SUMMARY with deployment status

Result: All 25 CHORUS agents now discovered successfully via Docker Swarm API

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude Code
2025-10-10 10:35:25 +11:00
parent 2826b28645
commit 9aeaa433fc
36 changed files with 4721 additions and 2213 deletions

View File

@@ -0,0 +1,112 @@
package composer
import (
"context"
"fmt"
"time"
"github.com/google/uuid"
)
// Enterprise plugin stubs - disable enterprise features but allow core system to function
// EnterprisePlugins manages enterprise plugin integrations (stub)
type EnterprisePlugins struct {
specKitClient *SpecKitClient
config *EnterpriseConfig
}
// EnterpriseConfig holds configuration for enterprise features
type EnterpriseConfig struct {
SpecKitServiceURL string `json:"spec_kit_service_url"`
EnableSpecKit bool `json:"enable_spec_kit"`
DefaultTimeout time.Duration `json:"default_timeout"`
MaxConcurrentCalls int `json:"max_concurrent_calls"`
RetryAttempts int `json:"retry_attempts"`
FallbackToCommunity bool `json:"fallback_to_community"`
}
// SpecKitWorkflowRequest represents a request to execute spec-kit workflow
type SpecKitWorkflowRequest struct {
ProjectName string `json:"project_name"`
Description string `json:"description"`
RepositoryURL string `json:"repository_url,omitempty"`
ChorusMetadata map[string]interface{} `json:"chorus_metadata"`
WorkflowPhases []string `json:"workflow_phases"`
CustomTemplates map[string]string `json:"custom_templates,omitempty"`
}
// SpecKitWorkflowResponse represents the response from spec-kit service
type SpecKitWorkflowResponse struct {
ProjectID string `json:"project_id"`
Status string `json:"status"`
PhasesCompleted []string `json:"phases_completed"`
Artifacts []SpecKitArtifact `json:"artifacts"`
QualityMetrics map[string]float64 `json:"quality_metrics"`
ProcessingTime time.Duration `json:"processing_time"`
Metadata map[string]interface{} `json:"metadata"`
}
// SpecKitArtifact represents an artifact generated by spec-kit
type SpecKitArtifact struct {
Type string `json:"type"`
Phase string `json:"phase"`
Content map[string]interface{} `json:"content"`
FilePath string `json:"file_path"`
Metadata map[string]interface{} `json:"metadata"`
CreatedAt time.Time `json:"created_at"`
Quality float64 `json:"quality"`
}
// EnterpriseFeatures represents what enterprise features are available
type EnterpriseFeatures struct {
SpecKitEnabled bool `json:"spec_kit_enabled"`
CustomTemplates bool `json:"custom_templates"`
AdvancedAnalytics bool `json:"advanced_analytics"`
PrioritySupport bool `json:"priority_support"`
WorkflowQuota int `json:"workflow_quota"`
RemainingWorkflows int `json:"remaining_workflows"`
LicenseTier string `json:"license_tier"`
}
// NewEnterprisePlugins creates a new enterprise plugin manager (stub)
func NewEnterprisePlugins(
specKitClient *SpecKitClient,
config *EnterpriseConfig,
) *EnterprisePlugins {
return &EnterprisePlugins{
specKitClient: specKitClient,
config: config,
}
}
// CheckEnterpriseFeatures returns community features only (stub)
func (ep *EnterprisePlugins) CheckEnterpriseFeatures(
ctx context.Context,
deploymentID uuid.UUID,
projectContext map[string]interface{},
) (*EnterpriseFeatures, error) {
// Return community-only features
return &EnterpriseFeatures{
SpecKitEnabled: false,
CustomTemplates: false,
AdvancedAnalytics: false,
PrioritySupport: false,
WorkflowQuota: 0,
RemainingWorkflows: 0,
LicenseTier: "community",
}, nil
}
// All other enterprise methods return "not available" errors
func (ep *EnterprisePlugins) ExecuteSpecKitWorkflow(ctx context.Context, deploymentID uuid.UUID, request *SpecKitWorkflowRequest) (*SpecKitWorkflowResponse, error) {
return nil, fmt.Errorf("spec-kit workflows require enterprise license - community version active")
}
func (ep *EnterprisePlugins) GetWorkflowTemplate(ctx context.Context, deploymentID uuid.UUID, templateType string) (map[string]interface{}, error) {
return nil, fmt.Errorf("custom templates require enterprise license - community version active")
}
func (ep *EnterprisePlugins) GetEnterpriseAnalytics(ctx context.Context, deploymentID uuid.UUID, timeRange string) (map[string]interface{}, error) {
return nil, fmt.Errorf("advanced analytics require enterprise license - community version active")
}

View File

@@ -0,0 +1,615 @@
package composer
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
"github.com/google/uuid"
"github.com/rs/zerolog/log"
)
// SpecKitClient handles communication with the spec-kit service
type SpecKitClient struct {
baseURL string
httpClient *http.Client
config *SpecKitClientConfig
}
// SpecKitClientConfig contains configuration for the spec-kit client
type SpecKitClientConfig struct {
ServiceURL string `json:"service_url"`
Timeout time.Duration `json:"timeout"`
MaxRetries int `json:"max_retries"`
RetryDelay time.Duration `json:"retry_delay"`
EnableCircuitBreaker bool `json:"enable_circuit_breaker"`
UserAgent string `json:"user_agent"`
}
// ProjectInitializeRequest for creating new spec-kit projects
type ProjectInitializeRequest struct {
ProjectName string `json:"project_name"`
Description string `json:"description"`
RepositoryURL string `json:"repository_url,omitempty"`
ChorusMetadata map[string]interface{} `json:"chorus_metadata"`
}
// ProjectInitializeResponse from spec-kit service initialization
type ProjectInitializeResponse struct {
ProjectID string `json:"project_id"`
BranchName string `json:"branch_name"`
SpecFilePath string `json:"spec_file_path"`
FeatureNumber string `json:"feature_number"`
Status string `json:"status"`
}
// ConstitutionRequest for executing constitution phase
type ConstitutionRequest struct {
PrinciplesDescription string `json:"principles_description"`
OrganizationContext map[string]interface{} `json:"organization_context"`
}
// ConstitutionResponse from constitution phase execution
type ConstitutionResponse struct {
Constitution ConstitutionData `json:"constitution"`
FilePath string `json:"file_path"`
Status string `json:"status"`
}
// ConstitutionData contains the structured constitution information
type ConstitutionData struct {
Principles []Principle `json:"principles"`
Governance string `json:"governance"`
Version string `json:"version"`
RatifiedDate string `json:"ratified_date"`
}
// Principle represents a single principle in the constitution
type Principle struct {
Name string `json:"name"`
Description string `json:"description"`
}
// SpecificationRequest for executing specification phase
type SpecificationRequest struct {
FeatureDescription string `json:"feature_description"`
AcceptanceCriteria []string `json:"acceptance_criteria"`
}
// SpecificationResponse from specification phase execution
type SpecificationResponse struct {
Specification SpecificationData `json:"specification"`
FilePath string `json:"file_path"`
CompletenessScore float64 `json:"completeness_score"`
ClarificationsNeeded []string `json:"clarifications_needed"`
Status string `json:"status"`
}
// SpecificationData contains structured specification information
type SpecificationData struct {
FeatureName string `json:"feature_name"`
UserScenarios []UserScenario `json:"user_scenarios"`
FunctionalRequirements []Requirement `json:"functional_requirements"`
Entities []Entity `json:"entities"`
}
// UserScenario represents a user story or scenario
type UserScenario struct {
PrimaryStory string `json:"primary_story"`
AcceptanceScenarios []string `json:"acceptance_scenarios"`
}
// Requirement represents a functional requirement
type Requirement struct {
ID string `json:"id"`
Requirement string `json:"requirement"`
}
// Entity represents a key business entity
type Entity struct {
Name string `json:"name"`
Description string `json:"description"`
}
// PlanningRequest for executing planning phase
type PlanningRequest struct {
TechStack map[string]interface{} `json:"tech_stack"`
ArchitecturePreferences map[string]interface{} `json:"architecture_preferences"`
}
// PlanningResponse from planning phase execution
type PlanningResponse struct {
Plan PlanData `json:"plan"`
FilePath string `json:"file_path"`
Status string `json:"status"`
}
// PlanData contains structured planning information
type PlanData struct {
TechStack map[string]interface{} `json:"tech_stack"`
Architecture map[string]interface{} `json:"architecture"`
Implementation map[string]interface{} `json:"implementation"`
TestingStrategy map[string]interface{} `json:"testing_strategy"`
}
// TasksResponse from tasks phase execution
type TasksResponse struct {
Tasks TasksData `json:"tasks"`
FilePath string `json:"file_path"`
Status string `json:"status"`
}
// TasksData contains structured task information
type TasksData struct {
SetupTasks []Task `json:"setup_tasks"`
CoreTasks []Task `json:"core_tasks"`
IntegrationTasks []Task `json:"integration_tasks"`
PolishTasks []Task `json:"polish_tasks"`
}
// Task represents a single implementation task
type Task struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Dependencies []string `json:"dependencies"`
Parallel bool `json:"parallel"`
EstimatedHours int `json:"estimated_hours"`
}
// ProjectStatusResponse contains current project status
type ProjectStatusResponse struct {
ProjectID string `json:"project_id"`
CurrentPhase string `json:"current_phase"`
PhasesCompleted []string `json:"phases_completed"`
OverallProgress float64 `json:"overall_progress"`
Artifacts []ArtifactInfo `json:"artifacts"`
QualityMetrics map[string]float64 `json:"quality_metrics"`
}
// ArtifactInfo contains information about generated artifacts
type ArtifactInfo struct {
Type string `json:"type"`
Path string `json:"path"`
LastModified time.Time `json:"last_modified"`
}
// NewSpecKitClient creates a new spec-kit service client
func NewSpecKitClient(config *SpecKitClientConfig) *SpecKitClient {
if config == nil {
config = &SpecKitClientConfig{
Timeout: 30 * time.Second,
MaxRetries: 3,
RetryDelay: 1 * time.Second,
UserAgent: "WHOOSH-SpecKit-Client/1.0",
}
}
return &SpecKitClient{
baseURL: config.ServiceURL,
httpClient: &http.Client{
Timeout: config.Timeout,
},
config: config,
}
}
// InitializeProject creates a new spec-kit project
func (c *SpecKitClient) InitializeProject(
ctx context.Context,
req *ProjectInitializeRequest,
) (*ProjectInitializeResponse, error) {
log.Info().
Str("project_name", req.ProjectName).
Str("council_id", fmt.Sprintf("%v", req.ChorusMetadata["council_id"])).
Msg("Initializing spec-kit project")
var response ProjectInitializeResponse
err := c.makeRequest(ctx, "POST", "/v1/projects/initialize", req, &response)
if err != nil {
return nil, fmt.Errorf("failed to initialize project: %w", err)
}
log.Info().
Str("project_id", response.ProjectID).
Str("branch_name", response.BranchName).
Str("status", response.Status).
Msg("Spec-kit project initialized successfully")
return &response, nil
}
// ExecuteConstitution runs the constitution phase
func (c *SpecKitClient) ExecuteConstitution(
ctx context.Context,
projectID string,
req *ConstitutionRequest,
) (*ConstitutionResponse, error) {
log.Info().
Str("project_id", projectID).
Msg("Executing constitution phase")
var response ConstitutionResponse
url := fmt.Sprintf("/v1/projects/%s/constitution", projectID)
err := c.makeRequest(ctx, "POST", url, req, &response)
if err != nil {
return nil, fmt.Errorf("failed to execute constitution phase: %w", err)
}
log.Info().
Str("project_id", projectID).
Int("principles_count", len(response.Constitution.Principles)).
Str("status", response.Status).
Msg("Constitution phase completed")
return &response, nil
}
// ExecuteSpecification runs the specification phase
func (c *SpecKitClient) ExecuteSpecification(
ctx context.Context,
projectID string,
req *SpecificationRequest,
) (*SpecificationResponse, error) {
log.Info().
Str("project_id", projectID).
Msg("Executing specification phase")
var response SpecificationResponse
url := fmt.Sprintf("/v1/projects/%s/specify", projectID)
err := c.makeRequest(ctx, "POST", url, req, &response)
if err != nil {
return nil, fmt.Errorf("failed to execute specification phase: %w", err)
}
log.Info().
Str("project_id", projectID).
Str("feature_name", response.Specification.FeatureName).
Float64("completeness_score", response.CompletenessScore).
Int("clarifications_needed", len(response.ClarificationsNeeded)).
Str("status", response.Status).
Msg("Specification phase completed")
return &response, nil
}
// ExecutePlanning runs the planning phase
func (c *SpecKitClient) ExecutePlanning(
ctx context.Context,
projectID string,
req *PlanningRequest,
) (*PlanningResponse, error) {
log.Info().
Str("project_id", projectID).
Msg("Executing planning phase")
var response PlanningResponse
url := fmt.Sprintf("/v1/projects/%s/plan", projectID)
err := c.makeRequest(ctx, "POST", url, req, &response)
if err != nil {
return nil, fmt.Errorf("failed to execute planning phase: %w", err)
}
log.Info().
Str("project_id", projectID).
Str("status", response.Status).
Msg("Planning phase completed")
return &response, nil
}
// ExecuteTasks runs the tasks phase
func (c *SpecKitClient) ExecuteTasks(
ctx context.Context,
projectID string,
) (*TasksResponse, error) {
log.Info().
Str("project_id", projectID).
Msg("Executing tasks phase")
var response TasksResponse
url := fmt.Sprintf("/v1/projects/%s/tasks", projectID)
err := c.makeRequest(ctx, "POST", url, nil, &response)
if err != nil {
return nil, fmt.Errorf("failed to execute tasks phase: %w", err)
}
totalTasks := len(response.Tasks.SetupTasks) +
len(response.Tasks.CoreTasks) +
len(response.Tasks.IntegrationTasks) +
len(response.Tasks.PolishTasks)
log.Info().
Str("project_id", projectID).
Int("total_tasks", totalTasks).
Str("status", response.Status).
Msg("Tasks phase completed")
return &response, nil
}
// GetProjectStatus retrieves current project status
func (c *SpecKitClient) GetProjectStatus(
ctx context.Context,
projectID string,
) (*ProjectStatusResponse, error) {
log.Debug().
Str("project_id", projectID).
Msg("Retrieving project status")
var response ProjectStatusResponse
url := fmt.Sprintf("/v1/projects/%s/status", projectID)
err := c.makeRequest(ctx, "GET", url, nil, &response)
if err != nil {
return nil, fmt.Errorf("failed to get project status: %w", err)
}
return &response, nil
}
// ExecuteWorkflow executes a complete spec-kit workflow
func (c *SpecKitClient) ExecuteWorkflow(
ctx context.Context,
req *SpecKitWorkflowRequest,
) (*SpecKitWorkflowResponse, error) {
startTime := time.Now()
log.Info().
Str("project_name", req.ProjectName).
Strs("phases", req.WorkflowPhases).
Msg("Starting complete spec-kit workflow execution")
// Step 1: Initialize project
initReq := &ProjectInitializeRequest{
ProjectName: req.ProjectName,
Description: req.Description,
RepositoryURL: req.RepositoryURL,
ChorusMetadata: req.ChorusMetadata,
}
initResp, err := c.InitializeProject(ctx, initReq)
if err != nil {
return nil, fmt.Errorf("workflow initialization failed: %w", err)
}
projectID := initResp.ProjectID
var artifacts []SpecKitArtifact
phasesCompleted := []string{}
// Execute each requested phase
for _, phase := range req.WorkflowPhases {
switch phase {
case "constitution":
constReq := &ConstitutionRequest{
PrinciplesDescription: "Create project principles focused on quality, testing, and performance",
OrganizationContext: req.ChorusMetadata,
}
constResp, err := c.ExecuteConstitution(ctx, projectID, constReq)
if err != nil {
log.Error().Err(err).Str("phase", phase).Msg("Phase execution failed")
continue
}
artifact := SpecKitArtifact{
Type: "constitution",
Phase: phase,
Content: map[string]interface{}{"constitution": constResp.Constitution},
FilePath: constResp.FilePath,
CreatedAt: time.Now(),
Quality: 0.95, // High quality for structured constitution
}
artifacts = append(artifacts, artifact)
phasesCompleted = append(phasesCompleted, phase)
case "specify":
specReq := &SpecificationRequest{
FeatureDescription: req.Description,
AcceptanceCriteria: []string{}, // Could be extracted from description
}
specResp, err := c.ExecuteSpecification(ctx, projectID, specReq)
if err != nil {
log.Error().Err(err).Str("phase", phase).Msg("Phase execution failed")
continue
}
artifact := SpecKitArtifact{
Type: "specification",
Phase: phase,
Content: map[string]interface{}{"specification": specResp.Specification},
FilePath: specResp.FilePath,
CreatedAt: time.Now(),
Quality: specResp.CompletenessScore,
}
artifacts = append(artifacts, artifact)
phasesCompleted = append(phasesCompleted, phase)
case "plan":
planReq := &PlanningRequest{
TechStack: map[string]interface{}{
"backend": "Go with chi framework",
"frontend": "React with TypeScript",
"database": "PostgreSQL",
},
ArchitecturePreferences: map[string]interface{}{
"pattern": "microservices",
"api_style": "REST",
"testing": "TDD",
},
}
planResp, err := c.ExecutePlanning(ctx, projectID, planReq)
if err != nil {
log.Error().Err(err).Str("phase", phase).Msg("Phase execution failed")
continue
}
artifact := SpecKitArtifact{
Type: "plan",
Phase: phase,
Content: map[string]interface{}{"plan": planResp.Plan},
FilePath: planResp.FilePath,
CreatedAt: time.Now(),
Quality: 0.90, // High quality for structured plan
}
artifacts = append(artifacts, artifact)
phasesCompleted = append(phasesCompleted, phase)
case "tasks":
tasksResp, err := c.ExecuteTasks(ctx, projectID)
if err != nil {
log.Error().Err(err).Str("phase", phase).Msg("Phase execution failed")
continue
}
artifact := SpecKitArtifact{
Type: "tasks",
Phase: phase,
Content: map[string]interface{}{"tasks": tasksResp.Tasks},
FilePath: tasksResp.FilePath,
CreatedAt: time.Now(),
Quality: 0.88, // Good quality for actionable tasks
}
artifacts = append(artifacts, artifact)
phasesCompleted = append(phasesCompleted, phase)
}
}
// Calculate quality metrics
qualityMetrics := c.calculateQualityMetrics(artifacts)
response := &SpecKitWorkflowResponse{
ProjectID: projectID,
Status: "completed",
PhasesCompleted: phasesCompleted,
Artifacts: artifacts,
QualityMetrics: qualityMetrics,
ProcessingTime: time.Since(startTime),
Metadata: req.ChorusMetadata,
}
log.Info().
Str("project_id", projectID).
Int("phases_completed", len(phasesCompleted)).
Int("artifacts_generated", len(artifacts)).
Int64("total_time_ms", response.ProcessingTime.Milliseconds()).
Msg("Complete spec-kit workflow execution finished")
return response, nil
}
// GetTemplate retrieves workflow templates
func (c *SpecKitClient) GetTemplate(ctx context.Context, templateType string) (map[string]interface{}, error) {
var template map[string]interface{}
url := fmt.Sprintf("/v1/templates/%s", templateType)
err := c.makeRequest(ctx, "GET", url, nil, &template)
if err != nil {
return nil, fmt.Errorf("failed to get template: %w", err)
}
return template, nil
}
// GetAnalytics retrieves analytics data
func (c *SpecKitClient) GetAnalytics(
ctx context.Context,
deploymentID uuid.UUID,
timeRange string,
) (map[string]interface{}, error) {
var analytics map[string]interface{}
url := fmt.Sprintf("/v1/analytics?deployment_id=%s&time_range=%s", deploymentID.String(), timeRange)
err := c.makeRequest(ctx, "GET", url, nil, &analytics)
if err != nil {
return nil, fmt.Errorf("failed to get analytics: %w", err)
}
return analytics, nil
}
// makeRequest handles HTTP requests with retries and error handling
func (c *SpecKitClient) makeRequest(
ctx context.Context,
method, endpoint string,
requestBody interface{},
responseBody interface{},
) error {
url := c.baseURL + endpoint
var bodyReader io.Reader
if requestBody != nil {
jsonBody, err := json.Marshal(requestBody)
if err != nil {
return fmt.Errorf("failed to marshal request body: %w", err)
}
bodyReader = bytes.NewBuffer(jsonBody)
}
var lastErr error
for attempt := 0; attempt <= c.config.MaxRetries; attempt++ {
if attempt > 0 {
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(c.config.RetryDelay * time.Duration(attempt)):
}
}
req, err := http.NewRequestWithContext(ctx, method, url, bodyReader)
if err != nil {
lastErr = fmt.Errorf("failed to create request: %w", err)
continue
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", c.config.UserAgent)
resp, err := c.httpClient.Do(req)
if err != nil {
lastErr = fmt.Errorf("request failed: %w", err)
continue
}
defer resp.Body.Close()
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
if responseBody != nil {
if err := json.NewDecoder(resp.Body).Decode(responseBody); err != nil {
return fmt.Errorf("failed to decode response: %w", err)
}
}
return nil
}
// Read error response
errorBody, _ := io.ReadAll(resp.Body)
lastErr = fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(errorBody))
// Don't retry on client errors (4xx)
if resp.StatusCode >= 400 && resp.StatusCode < 500 {
break
}
}
return fmt.Errorf("request failed after %d attempts: %w", c.config.MaxRetries+1, lastErr)
}
// calculateQualityMetrics computes overall quality metrics from artifacts
func (c *SpecKitClient) calculateQualityMetrics(artifacts []SpecKitArtifact) map[string]float64 {
metrics := map[string]float64{}
if len(artifacts) == 0 {
return metrics
}
var totalQuality float64
for _, artifact := range artifacts {
totalQuality += artifact.Quality
metrics[artifact.Type+"_quality"] = artifact.Quality
}
metrics["overall_quality"] = totalQuality / float64(len(artifacts))
metrics["artifact_count"] = float64(len(artifacts))
metrics["completeness"] = float64(len(artifacts)) / 5.0 // 5 total possible phases
return metrics
}