package routing import ( "encoding/json" "fmt" "io/ioutil" "os" "path/filepath" ) // KnowledgeBase represents the activity knowledge base type KnowledgeBase struct { Version string `json:"version"` Activities []ActivityMetadata `json:"activities"` Metadata KnowledgeBaseMetadata `json:"metadata"` // Index for fast lookups byName map[string]*ActivityMetadata } // KnowledgeBaseMetadata tracks KB metadata type KnowledgeBaseMetadata struct { TotalActivities int `json:"totalActivities"` LastUpdated string `json:"lastUpdated"` Categories map[string]int `json:"categories"` } // LoadKnowledgeBase loads the activity knowledge base from a JSON file func LoadKnowledgeBase(filePath string) (*KnowledgeBase, error) { // Read file data, err := ioutil.ReadFile(filePath) if err != nil { return nil, fmt.Errorf("failed to read knowledge base file: %w", err) } // Parse JSON var kb KnowledgeBase err = json.Unmarshal(data, &kb) if err != nil { return nil, fmt.Errorf("failed to parse knowledge base JSON: %w", err) } // Build index kb.byName = make(map[string]*ActivityMetadata) for i := range kb.Activities { kb.byName[kb.Activities[i].Name] = &kb.Activities[i] } return &kb, nil } // LoadKnowledgeBaseFromDefaultPath loads KB from default location // Looks for activity_knowledge_base.json in same directory as caller func LoadKnowledgeBaseFromDefaultPath() (*KnowledgeBase, error) { // Try to find from package directory execDir, err := os.Executable() if err == nil { // Try in same directory as binary path := filepath.Join(filepath.Dir(execDir), "activity_knowledge_base.json") if _, err := os.Stat(path); err == nil { return LoadKnowledgeBase(path) } } // Try from current working directory if _, err := os.Stat("activity_knowledge_base.json"); err == nil { return LoadKnowledgeBase("activity_knowledge_base.json") } // Try from internal/routing directory relative to cwd if _, err := os.Stat("internal/routing/activity_knowledge_base.json"); err == nil { return LoadKnowledgeBase("internal/routing/activity_knowledge_base.json") } return nil, fmt.Errorf("activity_knowledge_base.json not found in any expected location") } // GetActivity returns metadata for a specific activity func (kb *KnowledgeBase) GetActivity(name string) *ActivityMetadata { return kb.byName[name] } // ListActivities returns all activities func (kb *KnowledgeBase) ListActivities() []ActivityMetadata { return kb.Activities } // ListActivitiesByCategory returns all activities in a category func (kb *KnowledgeBase) ListActivitiesByCategory(category string) []ActivityMetadata { var result []ActivityMetadata for _, activity := range kb.Activities { if activity.Category == category { result = append(result, activity) } } return result } // GetActivityNames returns all activity names func (kb *KnowledgeBase) GetActivityNames() []string { names := make([]string, len(kb.Activities)) for i, activity := range kb.Activities { names[i] = activity.Name } return names } // HasActivity checks if an activity exists func (kb *KnowledgeBase) HasActivity(name string) bool { _, exists := kb.byName[name] return exists } // GetDependencies returns all dependencies for an activity func (kb *KnowledgeBase) GetDependencies(activityName string) []string { activity := kb.GetActivity(activityName) if activity == nil { return []string{} } return activity.Constraints.Dependencies } // GetTimeoutForActivity returns the timeout for an activity func (kb *KnowledgeBase) GetTimeoutForActivity(activityName string) string { activity := kb.GetActivity(activityName) if activity == nil { return "5m" // Default timeout } return activity.Constraints.DefaultTimeout } // GetRetryPolicyForActivity returns retry configuration for an activity func (kb *KnowledgeBase) GetRetryPolicyForActivity(activityName string) *RetryPolicy { activity := kb.GetActivity(activityName) if activity == nil { return &RetryPolicy{ MaxAttempts: 1, BackoffRate: 1.0, InitialInterval: "1s", } } return &RetryPolicy{ MaxAttempts: int32(activity.Constraints.RecommendedRetries), BackoffRate: activity.Constraints.RetryBackoff, InitialInterval: "1s", MaxInterval: "30s", } } // IsFlaky returns whether an activity is marked as flaky func (kb *KnowledgeBase) IsFlaky(activityName string) bool { activity := kb.GetActivity(activityName) if activity == nil { return false } return activity.Constraints.IsFlaky } // GetNotes returns implementation notes for an activity func (kb *KnowledgeBase) GetNotes(activityName string) string { activity := kb.GetActivity(activityName) if activity == nil { return "" } return activity.Constraints.Notes } // Validate checks the knowledge base for consistency func (kb *KnowledgeBase) Validate() error { // Check for circular dependencies visited := make(map[string]bool) for _, activity := range kb.Activities { if err := kb.checkDependencies(activity.Name, visited, []string{}); err != nil { return err } } // Check that all dependencies exist for _, activity := range kb.Activities { for _, dep := range activity.Constraints.Dependencies { if !kb.HasActivity(dep) { return fmt.Errorf("activity %s depends on non-existent activity %s", activity.Name, dep) } } } return nil } // checkDependencies validates activity dependencies for cycles func (kb *KnowledgeBase) checkDependencies(activityName string, visited map[string]bool, path []string) error { // Check for cycles for _, p := range path { if p == activityName { cycleStr := "" found := false for _, n := range path { if found { cycleStr += " -> " + n } if n == activityName { found = true cycleStr += n } } cycleStr += " -> " + activityName return fmt.Errorf("circular dependency detected: %s", cycleStr) } } if visited[activityName] { return nil // Already checked this branch } visited[activityName] = true newPath := append(path, activityName) activity := kb.GetActivity(activityName) if activity == nil { return nil // Non-existent activity will be caught elsewhere } for _, dep := range activity.Constraints.Dependencies { if err := kb.checkDependencies(dep, visited, newPath); err != nil { return err } } return nil } // String returns a human-readable description of the knowledge base func (kb *KnowledgeBase) String() string { return fmt.Sprintf("KnowledgeBase(v%s, %d activities)", kb.Version, kb.Metadata.TotalActivities) } // PrintSummary prints a summary of available activities func (kb *KnowledgeBase) PrintSummary() string { summary := fmt.Sprintf("=== Activity Knowledge Base ===\nVersion: %s\nTotal Activities: %d\n\n", kb.Version, kb.Metadata.TotalActivities) summary += "Activities by Category:\n" for category, count := range kb.Metadata.Categories { summary += fmt.Sprintf(" %s: %d\n", category, count) } summary += "\nActivity Details:\n" for _, activity := range kb.Activities { summary += fmt.Sprintf("\n[%s] %s\n", activity.Name, activity.Description) summary += fmt.Sprintf(" Category: %s\n", activity.Category) summary += fmt.Sprintf(" Timeout: %s\n", activity.Constraints.DefaultTimeout) summary += fmt.Sprintf(" Flaky: %v (Retries: %d)\n", activity.Constraints.IsFlaky, activity.Constraints.RecommendedRetries) if len(activity.Constraints.Dependencies) > 0 { summary += fmt.Sprintf(" Dependencies: %v\n", activity.Constraints.Dependencies) } } return summary }