🎭 CHORUS now contains full BZZZ functionality adapted for containers Core systems ported: - P2P networking (libp2p with DHT and PubSub) - Task coordination (COOEE protocol) - HMMM collaborative reasoning - SHHH encryption and security - SLURP admin election system - UCXL content addressing - UCXI server integration - Hypercore logging system - Health monitoring and graceful shutdown - License validation with KACHING Container adaptations: - Environment variable configuration (no YAML files) - Container-optimized logging to stdout/stderr - Auto-generated agent IDs for container deployments - Docker-first architecture All proven BZZZ P2P protocols, AI integration, and collaboration features are now available in containerized form. Next: Build and test container deployment. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1037 lines
29 KiB
Go
1037 lines
29 KiB
Go
package intelligence
|
|
|
|
import (
|
|
"crypto/md5"
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
slurpContext "chorus.services/bzzz/pkg/slurp/context"
|
|
)
|
|
|
|
// Utility functions and helper types for the intelligence engine
|
|
|
|
// ContentAnalysisUtils provides utilities for content analysis
|
|
type ContentAnalysisUtils struct{}
|
|
|
|
// NewContentAnalysisUtils creates new content analysis utilities
|
|
func NewContentAnalysisUtils() *ContentAnalysisUtils {
|
|
return &ContentAnalysisUtils{}
|
|
}
|
|
|
|
// ExtractIdentifiers extracts identifiers from code content
|
|
func (cau *ContentAnalysisUtils) ExtractIdentifiers(content, language string) (functions, classes, variables []string) {
|
|
switch strings.ToLower(language) {
|
|
case "go":
|
|
return cau.extractGoIdentifiers(content)
|
|
case "javascript", "typescript":
|
|
return cau.extractJSIdentifiers(content)
|
|
case "python":
|
|
return cau.extractPythonIdentifiers(content)
|
|
case "java":
|
|
return cau.extractJavaIdentifiers(content)
|
|
case "rust":
|
|
return cau.extractRustIdentifiers(content)
|
|
default:
|
|
return cau.extractGenericIdentifiers(content)
|
|
}
|
|
}
|
|
|
|
func (cau *ContentAnalysisUtils) extractGoIdentifiers(content string) (functions, classes, variables []string) {
|
|
// Go function pattern: func FunctionName
|
|
funcPattern := regexp.MustCompile(`func\s+(\w+)\s*\(`)
|
|
funcMatches := funcPattern.FindAllStringSubmatch(content, -1)
|
|
for _, match := range funcMatches {
|
|
if len(match) > 1 {
|
|
functions = append(functions, match[1])
|
|
}
|
|
}
|
|
|
|
// Go type/struct pattern: type TypeName struct
|
|
typePattern := regexp.MustCompile(`type\s+(\w+)\s+struct`)
|
|
typeMatches := typePattern.FindAllStringSubmatch(content, -1)
|
|
for _, match := range typeMatches {
|
|
if len(match) > 1 {
|
|
classes = append(classes, match[1])
|
|
}
|
|
}
|
|
|
|
// Go variable pattern: var varName or varName :=
|
|
varPattern := regexp.MustCompile(`(?:var\s+(\w+)|(\w+)\s*:=)`)
|
|
varMatches := varPattern.FindAllStringSubmatch(content, -1)
|
|
for _, match := range varMatches {
|
|
if len(match) > 1 && match[1] != "" {
|
|
variables = append(variables, match[1])
|
|
} else if len(match) > 2 && match[2] != "" {
|
|
variables = append(variables, match[2])
|
|
}
|
|
}
|
|
|
|
return removeDuplicates(functions), removeDuplicates(classes), removeDuplicates(variables)
|
|
}
|
|
|
|
func (cau *ContentAnalysisUtils) extractJSIdentifiers(content string) (functions, classes, variables []string) {
|
|
// JavaScript function patterns
|
|
funcPatterns := []*regexp.Regexp{
|
|
regexp.MustCompile(`function\s+(\w+)\s*\(`),
|
|
regexp.MustCompile(`(\w+)\s*:\s*function\s*\(`),
|
|
regexp.MustCompile(`const\s+(\w+)\s*=\s*\(`),
|
|
regexp.MustCompile(`(?:let|var)\s+(\w+)\s*=\s*\(`),
|
|
}
|
|
|
|
for _, pattern := range funcPatterns {
|
|
matches := pattern.FindAllStringSubmatch(content, -1)
|
|
for _, match := range matches {
|
|
if len(match) > 1 {
|
|
functions = append(functions, match[1])
|
|
}
|
|
}
|
|
}
|
|
|
|
// JavaScript class pattern
|
|
classPattern := regexp.MustCompile(`class\s+(\w+)`)
|
|
classMatches := classPattern.FindAllStringSubmatch(content, -1)
|
|
for _, match := range classMatches {
|
|
if len(match) > 1 {
|
|
classes = append(classes, match[1])
|
|
}
|
|
}
|
|
|
|
// JavaScript variable patterns
|
|
varPatterns := []*regexp.Regexp{
|
|
regexp.MustCompile(`(?:const|let|var)\s+(\w+)`),
|
|
}
|
|
|
|
for _, pattern := range varPatterns {
|
|
matches := pattern.FindAllStringSubmatch(content, -1)
|
|
for _, match := range matches {
|
|
if len(match) > 1 {
|
|
variables = append(variables, match[1])
|
|
}
|
|
}
|
|
}
|
|
|
|
return removeDuplicates(functions), removeDuplicates(classes), removeDuplicates(variables)
|
|
}
|
|
|
|
func (cau *ContentAnalysisUtils) extractPythonIdentifiers(content string) (functions, classes, variables []string) {
|
|
// Python function pattern
|
|
funcPattern := regexp.MustCompile(`def\s+(\w+)\s*\(`)
|
|
funcMatches := funcPattern.FindAllStringSubmatch(content, -1)
|
|
for _, match := range funcMatches {
|
|
if len(match) > 1 {
|
|
functions = append(functions, match[1])
|
|
}
|
|
}
|
|
|
|
// Python class pattern
|
|
classPattern := regexp.MustCompile(`class\s+(\w+)`)
|
|
classMatches := classPattern.FindAllStringSubmatch(content, -1)
|
|
for _, match := range classMatches {
|
|
if len(match) > 1 {
|
|
classes = append(classes, match[1])
|
|
}
|
|
}
|
|
|
|
// Python variable pattern (simple assignment)
|
|
varPattern := regexp.MustCompile(`^(\w+)\s*=`)
|
|
lines := strings.Split(content, "\n")
|
|
for _, line := range lines {
|
|
line = strings.TrimSpace(line)
|
|
if matches := varPattern.FindStringSubmatch(line); matches != nil && len(matches) > 1 {
|
|
variables = append(variables, matches[1])
|
|
}
|
|
}
|
|
|
|
return removeDuplicates(functions), removeDuplicates(classes), removeDuplicates(variables)
|
|
}
|
|
|
|
func (cau *ContentAnalysisUtils) extractJavaIdentifiers(content string) (functions, classes, variables []string) {
|
|
// Java method pattern
|
|
methodPattern := regexp.MustCompile(`(?:public|private|protected)?\s*(?:static)?\s*\w+\s+(\w+)\s*\(`)
|
|
methodMatches := methodPattern.FindAllStringSubmatch(content, -1)
|
|
for _, match := range methodMatches {
|
|
if len(match) > 1 {
|
|
functions = append(functions, match[1])
|
|
}
|
|
}
|
|
|
|
// Java class pattern
|
|
classPattern := regexp.MustCompile(`(?:public|private)?\s*class\s+(\w+)`)
|
|
classMatches := classPattern.FindAllStringSubmatch(content, -1)
|
|
for _, match := range classMatches {
|
|
if len(match) > 1 {
|
|
classes = append(classes, match[1])
|
|
}
|
|
}
|
|
|
|
// Java field/variable pattern
|
|
varPattern := regexp.MustCompile(`(?:private|public|protected)?\s*\w+\s+(\w+)\s*[=;]`)
|
|
varMatches := varPattern.FindAllStringSubmatch(content, -1)
|
|
for _, match := range varMatches {
|
|
if len(match) > 1 {
|
|
variables = append(variables, match[1])
|
|
}
|
|
}
|
|
|
|
return removeDuplicates(functions), removeDuplicates(classes), removeDuplicates(variables)
|
|
}
|
|
|
|
func (cau *ContentAnalysisUtils) extractRustIdentifiers(content string) (functions, classes, variables []string) {
|
|
// Rust function pattern
|
|
funcPattern := regexp.MustCompile(`fn\s+(\w+)\s*\(`)
|
|
funcMatches := funcPattern.FindAllStringSubmatch(content, -1)
|
|
for _, match := range funcMatches {
|
|
if len(match) > 1 {
|
|
functions = append(functions, match[1])
|
|
}
|
|
}
|
|
|
|
// Rust struct pattern
|
|
structPattern := regexp.MustCompile(`struct\s+(\w+)`)
|
|
structMatches := structPattern.FindAllStringSubmatch(content, -1)
|
|
for _, match := range structMatches {
|
|
if len(match) > 1 {
|
|
classes = append(classes, match[1])
|
|
}
|
|
}
|
|
|
|
// Rust variable pattern
|
|
varPattern := regexp.MustCompile(`let\s+(?:mut\s+)?(\w+)`)
|
|
varMatches := varPattern.FindAllStringSubmatch(content, -1)
|
|
for _, match := range varMatches {
|
|
if len(match) > 1 {
|
|
variables = append(variables, match[1])
|
|
}
|
|
}
|
|
|
|
return removeDuplicates(functions), removeDuplicates(classes), removeDuplicates(variables)
|
|
}
|
|
|
|
func (cau *ContentAnalysisUtils) extractGenericIdentifiers(content string) (functions, classes, variables []string) {
|
|
// Generic patterns for unknown languages
|
|
words := regexp.MustCompile(`\b[a-zA-Z_]\w*\b`).FindAllString(content, -1)
|
|
return removeDuplicates(words), []string{}, []string{}
|
|
}
|
|
|
|
// CalculateComplexity calculates code complexity based on various metrics
|
|
func (cau *ContentAnalysisUtils) CalculateComplexity(content, language string) float64 {
|
|
complexity := 0.0
|
|
|
|
// Lines of code (basic metric)
|
|
lines := strings.Split(content, "\n")
|
|
nonEmptyLines := 0
|
|
for _, line := range lines {
|
|
if strings.TrimSpace(line) != "" && !strings.HasPrefix(strings.TrimSpace(line), "//") {
|
|
nonEmptyLines++
|
|
}
|
|
}
|
|
|
|
// Base complexity from lines of code
|
|
complexity += float64(nonEmptyLines) * 0.1
|
|
|
|
// Control flow complexity (if, for, while, switch, etc.)
|
|
controlFlowPatterns := []*regexp.Regexp{
|
|
regexp.MustCompile(`\b(?:if|for|while|switch|case)\b`),
|
|
regexp.MustCompile(`\b(?:try|catch|finally)\b`),
|
|
regexp.MustCompile(`\?\s*.*\s*:`), // ternary operator
|
|
}
|
|
|
|
for _, pattern := range controlFlowPatterns {
|
|
matches := pattern.FindAllString(content, -1)
|
|
complexity += float64(len(matches)) * 0.5
|
|
}
|
|
|
|
// Function complexity
|
|
functions, _, _ := cau.ExtractIdentifiers(content, language)
|
|
complexity += float64(len(functions)) * 0.3
|
|
|
|
// Nesting level (simple approximation)
|
|
maxNesting := 0
|
|
currentNesting := 0
|
|
for _, line := range lines {
|
|
trimmed := strings.TrimSpace(line)
|
|
openBraces := strings.Count(trimmed, "{")
|
|
closeBraces := strings.Count(trimmed, "}")
|
|
currentNesting += openBraces - closeBraces
|
|
if currentNesting > maxNesting {
|
|
maxNesting = currentNesting
|
|
}
|
|
}
|
|
complexity += float64(maxNesting) * 0.2
|
|
|
|
// Normalize to 0-10 scale
|
|
return math.Min(10.0, complexity/10.0)
|
|
}
|
|
|
|
// DetectTechnologies detects technologies used in the content
|
|
func (cau *ContentAnalysisUtils) DetectTechnologies(content, filename string) []string {
|
|
technologies := []string{}
|
|
lowerContent := strings.ToLower(content)
|
|
ext := strings.ToLower(filepath.Ext(filename))
|
|
|
|
// Language detection
|
|
languageMap := map[string][]string{
|
|
".go": {"go", "golang"},
|
|
".py": {"python"},
|
|
".js": {"javascript", "node.js"},
|
|
".jsx": {"javascript", "react", "jsx"},
|
|
".ts": {"typescript"},
|
|
".tsx": {"typescript", "react", "jsx"},
|
|
".java": {"java"},
|
|
".kt": {"kotlin"},
|
|
".rs": {"rust"},
|
|
".cpp": {"c++"},
|
|
".c": {"c"},
|
|
".cs": {"c#", ".net"},
|
|
".php": {"php"},
|
|
".rb": {"ruby"},
|
|
".swift": {"swift"},
|
|
".scala": {"scala"},
|
|
".clj": {"clojure"},
|
|
".hs": {"haskell"},
|
|
".ml": {"ocaml"},
|
|
}
|
|
|
|
if langs, exists := languageMap[ext]; exists {
|
|
technologies = append(technologies, langs...)
|
|
}
|
|
|
|
// Framework and library detection
|
|
frameworkPatterns := map[string][]string{
|
|
"react": {"import.*react", "from [\"']react[\"']", "<.*/>", "jsx"},
|
|
"vue": {"import.*vue", "from [\"']vue[\"']", "<template>", "vue"},
|
|
"angular": {"import.*@angular", "from [\"']@angular", "ngmodule", "component"},
|
|
"express": {"import.*express", "require.*express", "app.get", "app.post"},
|
|
"django": {"from django", "import django", "django.db", "models.model"},
|
|
"flask": {"from flask", "import flask", "@app.route", "flask.request"},
|
|
"spring": {"@springboot", "@controller", "@service", "@repository"},
|
|
"hibernate": {"@entity", "@table", "@column", "hibernate"},
|
|
"jquery": {"$\\(", "jquery"},
|
|
"bootstrap": {"bootstrap", "btn-", "col-", "row"},
|
|
"docker": {"dockerfile", "docker-compose", "from.*:", "run.*"},
|
|
"kubernetes": {"apiversion:", "kind:", "metadata:", "spec:"},
|
|
"terraform": {"\\.tf$", "resource \"", "provider \"", "terraform"},
|
|
"ansible": {"\\.yml$", "hosts:", "tasks:", "playbook"},
|
|
"jenkins": {"jenkinsfile", "pipeline", "stage", "steps"},
|
|
"git": {"\\.git", "git add", "git commit", "git push"},
|
|
"mysql": {"mysql", "select.*from", "insert into", "create table"},
|
|
"postgresql": {"postgresql", "postgres", "psql"},
|
|
"mongodb": {"mongodb", "mongo", "find\\(", "insert\\("},
|
|
"redis": {"redis", "set.*", "get.*", "rpush"},
|
|
"elasticsearch": {"elasticsearch", "elastic", "query.*", "search.*"},
|
|
"graphql": {"graphql", "query.*{", "mutation.*{", "subscription.*{"},
|
|
"grpc": {"grpc", "proto", "service.*rpc", "\\.proto$"},
|
|
"websocket": {"websocket", "ws://", "wss://", "socket.io"},
|
|
"jwt": {"jwt", "jsonwebtoken", "bearer.*token"},
|
|
"oauth": {"oauth", "oauth2", "client_id", "client_secret"},
|
|
"ssl": {"ssl", "tls", "https", "certificate"},
|
|
"encryption": {"encrypt", "decrypt", "bcrypt", "sha256"},
|
|
}
|
|
|
|
for tech, patterns := range frameworkPatterns {
|
|
for _, pattern := range patterns {
|
|
if matched, _ := regexp.MatchString(pattern, lowerContent); matched {
|
|
technologies = append(technologies, tech)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
return removeDuplicates(technologies)
|
|
}
|
|
|
|
// ScoreUtils provides utilities for scoring and metrics
|
|
type ScoreUtils struct{}
|
|
|
|
// NewScoreUtils creates new score utilities
|
|
func NewScoreUtils() *ScoreUtils {
|
|
return &ScoreUtils{}
|
|
}
|
|
|
|
// NormalizeScore normalizes a score to a 0-1 range
|
|
func (su *ScoreUtils) NormalizeScore(score, min, max float64) float64 {
|
|
if max == min {
|
|
return 0.5 // Default middle value when range is zero
|
|
}
|
|
return math.Max(0, math.Min(1, (score-min)/(max-min)))
|
|
}
|
|
|
|
// CalculateWeightedScore calculates weighted score from multiple scores
|
|
func (su *ScoreUtils) CalculateWeightedScore(scores map[string]float64, weights map[string]float64) float64 {
|
|
totalWeight := 0.0
|
|
weightedSum := 0.0
|
|
|
|
for dimension, score := range scores {
|
|
weight := weights[dimension]
|
|
if weight == 0 {
|
|
weight = 1.0 // Default weight
|
|
}
|
|
weightedSum += score * weight
|
|
totalWeight += weight
|
|
}
|
|
|
|
if totalWeight == 0 {
|
|
return 0.0
|
|
}
|
|
|
|
return weightedSum / totalWeight
|
|
}
|
|
|
|
// CalculatePercentile calculates percentile from a slice of values
|
|
func (su *ScoreUtils) CalculatePercentile(values []float64, percentile int) float64 {
|
|
if len(values) == 0 {
|
|
return 0.0
|
|
}
|
|
|
|
sorted := make([]float64, len(values))
|
|
copy(sorted, values)
|
|
sort.Float64s(sorted)
|
|
|
|
if percentile <= 0 {
|
|
return sorted[0]
|
|
}
|
|
if percentile >= 100 {
|
|
return sorted[len(sorted)-1]
|
|
}
|
|
|
|
index := float64(percentile) / 100.0 * float64(len(sorted)-1)
|
|
lower := int(math.Floor(index))
|
|
upper := int(math.Ceil(index))
|
|
|
|
if lower == upper {
|
|
return sorted[lower]
|
|
}
|
|
|
|
// Linear interpolation
|
|
lowerValue := sorted[lower]
|
|
upperValue := sorted[upper]
|
|
weight := index - float64(lower)
|
|
|
|
return lowerValue + weight*(upperValue-lowerValue)
|
|
}
|
|
|
|
// CalculateStandardDeviation calculates standard deviation
|
|
func (su *ScoreUtils) CalculateStandardDeviation(values []float64) float64 {
|
|
if len(values) <= 1 {
|
|
return 0.0
|
|
}
|
|
|
|
// Calculate mean
|
|
sum := 0.0
|
|
for _, value := range values {
|
|
sum += value
|
|
}
|
|
mean := sum / float64(len(values))
|
|
|
|
// Calculate variance
|
|
variance := 0.0
|
|
for _, value := range values {
|
|
diff := value - mean
|
|
variance += diff * diff
|
|
}
|
|
variance /= float64(len(values) - 1)
|
|
|
|
return math.Sqrt(variance)
|
|
}
|
|
|
|
// FileUtils provides file system utilities
|
|
type FileUtils struct{}
|
|
|
|
// NewFileUtils creates new file utilities
|
|
func NewFileUtils() *FileUtils {
|
|
return &FileUtils{}
|
|
}
|
|
|
|
// GetFileSize returns file size in bytes
|
|
func (fu *FileUtils) GetFileSize(filePath string) (int64, error) {
|
|
info, err := os.Stat(filePath)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return info.Size(), nil
|
|
}
|
|
|
|
// GetFileModTime returns file modification time
|
|
func (fu *FileUtils) GetFileModTime(filePath string) (time.Time, error) {
|
|
info, err := os.Stat(filePath)
|
|
if err != nil {
|
|
return time.Time{}, err
|
|
}
|
|
return info.ModTime(), nil
|
|
}
|
|
|
|
// IsTextFile determines if a file is a text file
|
|
func (fu *FileUtils) IsTextFile(filePath string) bool {
|
|
ext := strings.ToLower(filepath.Ext(filePath))
|
|
textExtensions := map[string]bool{
|
|
".txt": true, ".md": true, ".go": true, ".py": true, ".js": true,
|
|
".jsx": true, ".ts": true, ".tsx": true, ".java": true, ".cpp": true,
|
|
".c": true, ".cs": true, ".php": true, ".rb": true, ".rs": true,
|
|
".swift": true, ".kt": true, ".scala": true, ".clj": true, ".hs": true,
|
|
".ml": true, ".json": true, ".xml": true, ".yaml": true, ".yml": true,
|
|
".toml": true, ".ini": true, ".cfg": true, ".conf": true, ".html": true,
|
|
".css": true, ".scss": true, ".sass": true, ".less": true, ".sql": true,
|
|
".sh": true, ".bat": true, ".ps1": true, ".dockerfile": true,
|
|
}
|
|
return textExtensions[ext]
|
|
}
|
|
|
|
// WalkDirectory recursively walks a directory
|
|
func (fu *FileUtils) WalkDirectory(rootPath string, handler func(path string, info os.FileInfo) error) error {
|
|
return filepath.Walk(rootPath, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return handler(path, info)
|
|
})
|
|
}
|
|
|
|
// StringUtils provides string manipulation utilities
|
|
type StringUtils struct{}
|
|
|
|
// NewStringUtils creates new string utilities
|
|
func NewStringUtils() *StringUtils {
|
|
return &StringUtils{}
|
|
}
|
|
|
|
// Similarity calculates string similarity using Jaccard index
|
|
func (su *StringUtils) Similarity(s1, s2 string) float64 {
|
|
if s1 == s2 {
|
|
return 1.0
|
|
}
|
|
|
|
words1 := strings.Fields(strings.ToLower(s1))
|
|
words2 := strings.Fields(strings.ToLower(s2))
|
|
|
|
if len(words1) == 0 && len(words2) == 0 {
|
|
return 1.0
|
|
}
|
|
|
|
if len(words1) == 0 || len(words2) == 0 {
|
|
return 0.0
|
|
}
|
|
|
|
set1 := make(map[string]bool)
|
|
set2 := make(map[string]bool)
|
|
|
|
for _, word := range words1 {
|
|
set1[word] = true
|
|
}
|
|
for _, word := range words2 {
|
|
set2[word] = true
|
|
}
|
|
|
|
intersection := 0
|
|
for word := range set1 {
|
|
if set2[word] {
|
|
intersection++
|
|
}
|
|
}
|
|
|
|
union := len(set1) + len(set2) - intersection
|
|
|
|
if union == 0 {
|
|
return 1.0
|
|
}
|
|
|
|
return float64(intersection) / float64(union)
|
|
}
|
|
|
|
// ExtractKeywords extracts keywords from text
|
|
func (su *StringUtils) ExtractKeywords(text string, minLength int) []string {
|
|
// Common stop words
|
|
stopWords := map[string]bool{
|
|
"a": true, "an": true, "and": true, "are": true, "as": true, "at": true,
|
|
"be": true, "by": true, "for": true, "from": true, "has": true, "he": true,
|
|
"in": true, "is": true, "it": true, "its": true, "of": true, "on": true,
|
|
"that": true, "the": true, "to": true, "was": true, "will": true, "with": true,
|
|
"this": true, "these": true, "they": true, "them": true, "their": true,
|
|
"have": true, "had": true, "been": true, "were": true, "said": true,
|
|
"what": true, "when": true, "where": true, "who": true, "which": true, "why": true,
|
|
"how": true, "all": true, "any": true, "both": true, "each": true, "few": true,
|
|
"more": true, "most": true, "other": true, "some": true, "such": true,
|
|
"no": true, "nor": true, "not": true, "only": true, "own": true, "same": true,
|
|
"so": true, "than": true, "too": true, "very": true, "can": true, "could": true,
|
|
"should": true, "would": true, "use": true, "used": true, "using": true,
|
|
}
|
|
|
|
// Extract words
|
|
wordRegex := regexp.MustCompile(`\b[a-zA-Z]+\b`)
|
|
words := wordRegex.FindAllString(strings.ToLower(text), -1)
|
|
|
|
keywords := []string{}
|
|
wordFreq := make(map[string]int)
|
|
|
|
for _, word := range words {
|
|
if len(word) >= minLength && !stopWords[word] {
|
|
wordFreq[word]++
|
|
}
|
|
}
|
|
|
|
// Sort by frequency and return top keywords
|
|
type wordCount struct {
|
|
word string
|
|
count int
|
|
}
|
|
|
|
var sortedWords []wordCount
|
|
for word, count := range wordFreq {
|
|
sortedWords = append(sortedWords, wordCount{word, count})
|
|
}
|
|
|
|
sort.Slice(sortedWords, func(i, j int) bool {
|
|
return sortedWords[i].count > sortedWords[j].count
|
|
})
|
|
|
|
maxKeywords := 20
|
|
for i, wc := range sortedWords {
|
|
if i >= maxKeywords {
|
|
break
|
|
}
|
|
keywords = append(keywords, wc.word)
|
|
}
|
|
|
|
return keywords
|
|
}
|
|
|
|
// TruncateText truncates text to specified length with ellipsis
|
|
func (su *StringUtils) TruncateText(text string, maxLength int) string {
|
|
if len(text) <= maxLength {
|
|
return text
|
|
}
|
|
if maxLength <= 3 {
|
|
return text[:maxLength]
|
|
}
|
|
return text[:maxLength-3] + "..."
|
|
}
|
|
|
|
// HashUtils provides hashing utilities
|
|
type HashUtils struct{}
|
|
|
|
// NewHashUtils creates new hash utilities
|
|
func NewHashUtils() *HashUtils {
|
|
return &HashUtils{}
|
|
}
|
|
|
|
// MD5Hash calculates MD5 hash of string
|
|
func (hu *HashUtils) MD5Hash(text string) string {
|
|
hasher := md5.New()
|
|
hasher.Write([]byte(text))
|
|
return hex.EncodeToString(hasher.Sum(nil))
|
|
}
|
|
|
|
// GenerateID generates a random ID
|
|
func (hu *HashUtils) GenerateID(prefix string) string {
|
|
bytes := make([]byte, 8)
|
|
rand.Read(bytes)
|
|
return fmt.Sprintf("%s_%x_%d", prefix, bytes, time.Now().Unix())
|
|
}
|
|
|
|
// TimeUtils provides time-related utilities
|
|
type TimeUtils struct{}
|
|
|
|
// NewTimeUtils creates new time utilities
|
|
func NewTimeUtils() *TimeUtils {
|
|
return &TimeUtils{}
|
|
}
|
|
|
|
// FormatDuration formats duration in human-readable format
|
|
func (tu *TimeUtils) FormatDuration(duration time.Duration) string {
|
|
if duration < time.Microsecond {
|
|
return fmt.Sprintf("%dns", duration.Nanoseconds())
|
|
}
|
|
if duration < time.Millisecond {
|
|
return fmt.Sprintf("%.1fμs", float64(duration.Nanoseconds())/1000.0)
|
|
}
|
|
if duration < time.Second {
|
|
return fmt.Sprintf("%.1fms", float64(duration.Nanoseconds())/1000000.0)
|
|
}
|
|
if duration < time.Minute {
|
|
return fmt.Sprintf("%.1fs", duration.Seconds())
|
|
}
|
|
if duration < time.Hour {
|
|
return fmt.Sprintf("%.1fm", duration.Minutes())
|
|
}
|
|
return fmt.Sprintf("%.1fh", duration.Hours())
|
|
}
|
|
|
|
// IsWithinTimeRange checks if time is within range
|
|
func (tu *TimeUtils) IsWithinTimeRange(t, start, end time.Time) bool {
|
|
return (t.Equal(start) || t.After(start)) && (t.Equal(end) || t.Before(end))
|
|
}
|
|
|
|
// JSONUtils provides JSON utilities
|
|
type JSONUtils struct{}
|
|
|
|
// NewJSONUtils creates new JSON utilities
|
|
func NewJSONUtils() *JSONUtils {
|
|
return &JSONUtils{}
|
|
}
|
|
|
|
// PrettyPrint formats JSON with indentation
|
|
func (ju *JSONUtils) PrettyPrint(data interface{}) (string, error) {
|
|
bytes, err := json.MarshalIndent(data, "", " ")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(bytes), nil
|
|
}
|
|
|
|
// DeepCopy creates a deep copy of a JSON-serializable object
|
|
func (ju *JSONUtils) DeepCopy(src, dst interface{}) error {
|
|
bytes, err := json.Marshal(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return json.Unmarshal(bytes, dst)
|
|
}
|
|
|
|
// ValidationUtils provides validation utilities
|
|
type ValidationUtils struct{}
|
|
|
|
// NewValidationUtils creates new validation utilities
|
|
func NewValidationUtils() *ValidationUtils {
|
|
return &ValidationUtils{}
|
|
}
|
|
|
|
// IsValidEmail validates email address
|
|
func (vu *ValidationUtils) IsValidEmail(email string) bool {
|
|
emailRegex := regexp.MustCompile(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`)
|
|
return emailRegex.MatchString(email)
|
|
}
|
|
|
|
// IsValidURL validates URL
|
|
func (vu *ValidationUtils) IsValidURL(url string) bool {
|
|
urlRegex := regexp.MustCompile(`^https?://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,}(/.*)?$`)
|
|
return urlRegex.MatchString(url)
|
|
}
|
|
|
|
// IsValidPath validates file path
|
|
func (vu *ValidationUtils) IsValidPath(path string) bool {
|
|
if path == "" {
|
|
return false
|
|
}
|
|
// Check for invalid characters
|
|
invalidChars := []string{"<", ">", ":", "\"", "|", "?", "*"}
|
|
for _, char := range invalidChars {
|
|
if strings.Contains(path, char) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Context utilities
|
|
|
|
// CloneContextNode creates a deep copy of a context node
|
|
func CloneContextNode(node *slurpContext.ContextNode) *slurpContext.ContextNode {
|
|
if node == nil {
|
|
return nil
|
|
}
|
|
|
|
clone := &slurpContext.ContextNode{
|
|
Path: node.Path,
|
|
Summary: node.Summary,
|
|
Purpose: node.Purpose,
|
|
Technologies: make([]string, len(node.Technologies)),
|
|
Tags: make([]string, len(node.Tags)),
|
|
Insights: make([]string, len(node.Insights)),
|
|
CreatedAt: node.CreatedAt,
|
|
UpdatedAt: node.UpdatedAt,
|
|
ContextSpecificity: node.ContextSpecificity,
|
|
RAGConfidence: node.RAGConfidence,
|
|
ProcessedForRole: node.ProcessedForRole,
|
|
}
|
|
|
|
copy(clone.Technologies, node.Technologies)
|
|
copy(clone.Tags, node.Tags)
|
|
copy(clone.Insights, node.Insights)
|
|
|
|
if node.RoleSpecificInsights != nil {
|
|
clone.RoleSpecificInsights = make([]*RoleSpecificInsight, len(node.RoleSpecificInsights))
|
|
copy(clone.RoleSpecificInsights, node.RoleSpecificInsights)
|
|
}
|
|
|
|
if node.Metadata != nil {
|
|
clone.Metadata = make(map[string]interface{})
|
|
for k, v := range node.Metadata {
|
|
clone.Metadata[k] = v
|
|
}
|
|
}
|
|
|
|
return clone
|
|
}
|
|
|
|
// MergeContextNodes merges multiple context nodes into one
|
|
func MergeContextNodes(nodes ...*slurpContext.ContextNode) *slurpContext.ContextNode {
|
|
if len(nodes) == 0 {
|
|
return nil
|
|
}
|
|
if len(nodes) == 1 {
|
|
return CloneContextNode(nodes[0])
|
|
}
|
|
|
|
merged := CloneContextNode(nodes[0])
|
|
|
|
for i := 1; i < len(nodes); i++ {
|
|
node := nodes[i]
|
|
if node == nil {
|
|
continue
|
|
}
|
|
|
|
// Merge technologies
|
|
merged.Technologies = mergeStringSlices(merged.Technologies, node.Technologies)
|
|
|
|
// Merge tags
|
|
merged.Tags = mergeStringSlices(merged.Tags, node.Tags)
|
|
|
|
// Merge insights
|
|
merged.Insights = mergeStringSlices(merged.Insights, node.Insights)
|
|
|
|
// Use most recent timestamps
|
|
if node.CreatedAt.Before(merged.CreatedAt) {
|
|
merged.CreatedAt = node.CreatedAt
|
|
}
|
|
if node.UpdatedAt.After(merged.UpdatedAt) {
|
|
merged.UpdatedAt = node.UpdatedAt
|
|
}
|
|
|
|
// Average context specificity
|
|
merged.ContextSpecificity = (merged.ContextSpecificity + node.ContextSpecificity) / 2
|
|
|
|
// Average RAG confidence
|
|
merged.RAGConfidence = (merged.RAGConfidence + node.RAGConfidence) / 2
|
|
|
|
// Merge metadata
|
|
if node.Metadata != nil {
|
|
if merged.Metadata == nil {
|
|
merged.Metadata = make(map[string]interface{})
|
|
}
|
|
for k, v := range node.Metadata {
|
|
merged.Metadata[k] = v
|
|
}
|
|
}
|
|
}
|
|
|
|
return merged
|
|
}
|
|
|
|
// Helper functions
|
|
|
|
func removeDuplicates(slice []string) []string {
|
|
seen := make(map[string]bool)
|
|
result := []string{}
|
|
for _, item := range slice {
|
|
if !seen[item] {
|
|
seen[item] = true
|
|
result = append(result, item)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func mergeStringSlices(slice1, slice2 []string) []string {
|
|
merged := make([]string, len(slice1))
|
|
copy(merged, slice1)
|
|
|
|
for _, item := range slice2 {
|
|
found := false
|
|
for _, existing := range merged {
|
|
if existing == item {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
merged = append(merged, item)
|
|
}
|
|
}
|
|
|
|
return merged
|
|
}
|
|
|
|
// MathUtils provides mathematical utilities
|
|
type MathUtils struct{}
|
|
|
|
// NewMathUtils creates new math utilities
|
|
func NewMathUtils() *MathUtils {
|
|
return &MathUtils{}
|
|
}
|
|
|
|
// Clamp clamps value between min and max
|
|
func (mu *MathUtils) Clamp(value, min, max float64) float64 {
|
|
return math.Max(min, math.Min(max, value))
|
|
}
|
|
|
|
// Lerp performs linear interpolation between a and b
|
|
func (mu *MathUtils) Lerp(a, b, t float64) float64 {
|
|
return a + t*(b-a)
|
|
}
|
|
|
|
// RoundToDecimalPlaces rounds to specified decimal places
|
|
func (mu *MathUtils) RoundToDecimalPlaces(value float64, places int) float64 {
|
|
multiplier := math.Pow(10, float64(places))
|
|
return math.Round(value*multiplier) / multiplier
|
|
}
|
|
|
|
// SafeDivide performs division with zero-check
|
|
func (mu *MathUtils) SafeDivide(numerator, denominator float64) float64 {
|
|
if math.Abs(denominator) < 1e-10 {
|
|
return 0.0
|
|
}
|
|
return numerator / denominator
|
|
}
|
|
|
|
// InRange checks if value is in range (inclusive)
|
|
func (mu *MathUtils) InRange(value, min, max float64) bool {
|
|
return value >= min && value <= max
|
|
}
|
|
|
|
// ParseUtils provides parsing utilities
|
|
type ParseUtils struct{}
|
|
|
|
// NewParseUtils creates new parse utilities
|
|
func NewParseUtils() *ParseUtils {
|
|
return &ParseUtils{}
|
|
}
|
|
|
|
// ParseFloat safely parses float with default
|
|
func (pu *ParseUtils) ParseFloat(s string, defaultValue float64) float64 {
|
|
if value, err := strconv.ParseFloat(s, 64); err == nil {
|
|
return value
|
|
}
|
|
return defaultValue
|
|
}
|
|
|
|
// ParseInt safely parses int with default
|
|
func (pu *ParseUtils) ParseInt(s string, defaultValue int) int {
|
|
if value, err := strconv.Atoi(s); err == nil {
|
|
return value
|
|
}
|
|
return defaultValue
|
|
}
|
|
|
|
// ParseBool safely parses bool with default
|
|
func (pu *ParseUtils) ParseBool(s string, defaultValue bool) bool {
|
|
if value, err := strconv.ParseBool(s); err == nil {
|
|
return value
|
|
}
|
|
return defaultValue
|
|
}
|
|
|
|
// ParseDuration safely parses duration with default
|
|
func (pu *ParseUtils) ParseDuration(s string, defaultValue time.Duration) time.Duration {
|
|
if value, err := time.ParseDuration(s); err == nil {
|
|
return value
|
|
}
|
|
return defaultValue
|
|
}
|
|
|
|
// ParseTime safely parses time with default
|
|
func (pu *ParseUtils) ParseTime(s, layout string, defaultValue time.Time) time.Time {
|
|
if value, err := time.Parse(layout, s); err == nil {
|
|
return value
|
|
}
|
|
return defaultValue
|
|
}
|
|
|
|
// ConversionUtils provides type conversion utilities
|
|
type ConversionUtils struct{}
|
|
|
|
// NewConversionUtils creates new conversion utilities
|
|
func NewConversionUtils() *ConversionUtils {
|
|
return &ConversionUtils{}
|
|
}
|
|
|
|
// ToString converts various types to string
|
|
func (cu *ConversionUtils) ToString(value interface{}) string {
|
|
switch v := value.(type) {
|
|
case string:
|
|
return v
|
|
case int, int8, int16, int32, int64:
|
|
return fmt.Sprintf("%d", v)
|
|
case uint, uint8, uint16, uint32, uint64:
|
|
return fmt.Sprintf("%d", v)
|
|
case float32, float64:
|
|
return fmt.Sprintf("%g", v)
|
|
case bool:
|
|
return fmt.Sprintf("%t", v)
|
|
case time.Time:
|
|
return v.Format(time.RFC3339)
|
|
case time.Duration:
|
|
return v.String()
|
|
default:
|
|
return fmt.Sprintf("%v", v)
|
|
}
|
|
}
|
|
|
|
// ToStringSlice converts interface{} to []string
|
|
func (cu *ConversionUtils) ToStringSlice(value interface{}) []string {
|
|
switch v := value.(type) {
|
|
case []string:
|
|
return v
|
|
case []interface{}:
|
|
result := make([]string, len(v))
|
|
for i, item := range v {
|
|
result[i] = cu.ToString(item)
|
|
}
|
|
return result
|
|
case string:
|
|
return []string{v}
|
|
default:
|
|
return []string{cu.ToString(v)}
|
|
}
|
|
}
|
|
|
|
// ByteUtils provides byte manipulation utilities
|
|
type ByteUtils struct{}
|
|
|
|
// NewByteUtils creates new byte utilities
|
|
func NewByteUtils() *ByteUtils {
|
|
return &ByteUtils{}
|
|
}
|
|
|
|
// FormatBytes formats bytes in human-readable format
|
|
func (bu *ByteUtils) FormatBytes(bytes int64) string {
|
|
const unit = 1024
|
|
if bytes < unit {
|
|
return fmt.Sprintf("%d B", bytes)
|
|
}
|
|
div, exp := int64(unit), 0
|
|
for n := bytes / unit; n >= unit; n /= unit {
|
|
div *= unit
|
|
exp++
|
|
}
|
|
return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
|
|
}
|
|
|
|
// ReadFileWithLimit reads file with size limit
|
|
func (bu *ByteUtils) ReadFileWithLimit(filename string, maxSize int64) ([]byte, error) {
|
|
file, err := os.Open(filename)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer file.Close()
|
|
|
|
stat, err := file.Stat()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if stat.Size() > maxSize {
|
|
return nil, fmt.Errorf("file too large: %d bytes (limit: %d)", stat.Size(), maxSize)
|
|
}
|
|
|
|
return io.ReadAll(file)
|
|
} |