This comprehensive refactoring addresses critical architectural issues: IMPORT CYCLE RESOLUTION: • pkg/crypto ↔ pkg/slurp/roles: Created pkg/security/access_levels.go • pkg/ucxl → pkg/dht: Created pkg/storage/interfaces.go • pkg/slurp/leader → pkg/election → pkg/slurp/storage: Moved types to pkg/election/interfaces.go MODULE PATH MIGRATION: • Changed from github.com/anthonyrawlins/bzzz to chorus.services/bzzz • Updated all import statements across 115+ files • Maintains compatibility while removing personal GitHub account dependency TYPE SYSTEM IMPROVEMENTS: • Resolved duplicate type declarations in crypto package • Added missing type definitions (RoleStatus, TimeRestrictions, KeyStatus, KeyRotationResult) • Proper interface segregation to prevent future cycles ARCHITECTURAL BENEFITS: • Build now progresses past structural issues to normal dependency resolution • Cleaner separation of concerns between packages • Eliminates circular dependencies that prevented compilation • Establishes foundation for scalable codebase growth 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1147 lines
33 KiB
Go
1147 lines
33 KiB
Go
package intelligence
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"path/filepath"
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
slurpContext "chorus.services/bzzz/pkg/slurp/context"
|
|
)
|
|
|
|
// DefaultPatternDetector provides comprehensive pattern detection capabilities
|
|
type DefaultPatternDetector struct {
|
|
config *EngineConfig
|
|
codeAnalyzer *CodePatternAnalyzer
|
|
namingAnalyzer *NamingPatternAnalyzer
|
|
orgAnalyzer *OrganizationalPatternAnalyzer
|
|
designAnalyzer *DesignPatternAnalyzer
|
|
}
|
|
|
|
// CodePatternAnalyzer detects code-level patterns and anti-patterns
|
|
type CodePatternAnalyzer struct {
|
|
languagePatterns map[string]*LanguageCodePatterns
|
|
}
|
|
|
|
// LanguageCodePatterns contains patterns specific to a programming language
|
|
type LanguageCodePatterns struct {
|
|
DesignPatterns []*DesignPatternMatcher
|
|
AntiPatterns []*AntiPatternMatcher
|
|
ArchPatterns []*ArchitecturalPatternMatcher
|
|
BestPractices []*BestPracticeMatcher
|
|
}
|
|
|
|
// DesignPatternMatcher detects specific design patterns
|
|
type DesignPatternMatcher struct {
|
|
PatternName string
|
|
Description string
|
|
Signatures []*regexp.Regexp
|
|
StructuralCues []string
|
|
Confidence func(matches int, totalLines int) float64
|
|
}
|
|
|
|
// AntiPatternMatcher detects anti-patterns and code smells
|
|
type AntiPatternMatcher struct {
|
|
PatternName string
|
|
Description string
|
|
Signatures []*regexp.Regexp
|
|
Severity string // critical, high, medium, low
|
|
Recommendation string
|
|
}
|
|
|
|
// ArchitecturalPatternMatcher detects architectural patterns
|
|
type ArchitecturalPatternMatcher struct {
|
|
PatternName string
|
|
Description string
|
|
FilePatterns []*regexp.Regexp
|
|
DirectoryHints []string
|
|
Dependencies []string
|
|
}
|
|
|
|
// BestPracticeMatcher detects adherence to best practices
|
|
type BestPracticeMatcher struct {
|
|
PracticeName string
|
|
Description string
|
|
Indicators []*regexp.Regexp
|
|
Violations []*regexp.Regexp
|
|
Impact string
|
|
}
|
|
|
|
// NamingPatternAnalyzer analyzes naming conventions and patterns
|
|
type NamingPatternAnalyzer struct {
|
|
conventionRules map[string]*NamingConventionRule
|
|
}
|
|
|
|
// NamingConventionRule defines a naming convention rule
|
|
type NamingConventionRule struct {
|
|
Language string
|
|
Scope string // function, variable, class, file, etc.
|
|
Pattern *regexp.Regexp
|
|
Description string
|
|
Examples []string
|
|
Violations []*regexp.Regexp
|
|
}
|
|
|
|
// OrganizationalPatternAnalyzer detects organizational and structural patterns
|
|
type OrganizationalPatternAnalyzer struct {
|
|
structuralPatterns []*StructuralPatternMatcher
|
|
}
|
|
|
|
// StructuralPatternMatcher detects structural organization patterns
|
|
type StructuralPatternMatcher struct {
|
|
PatternName string
|
|
Description string
|
|
DirectoryPatterns []*regexp.Regexp
|
|
FilePatterns []*regexp.Regexp
|
|
RequiredFiles []string
|
|
OptionalFiles []string
|
|
Depth int
|
|
Characteristics []string
|
|
}
|
|
|
|
// DesignPatternAnalyzer detects software design patterns
|
|
type DesignPatternAnalyzer struct {
|
|
patternLibrary map[string]*DesignPatternDefinition
|
|
}
|
|
|
|
// DesignPatternDefinition defines a comprehensive design pattern
|
|
type DesignPatternDefinition struct {
|
|
Name string
|
|
Category string // creational, structural, behavioral
|
|
Intent string
|
|
Applicability []string
|
|
Structure *PatternStructure
|
|
Participants []string
|
|
Collaborations []string
|
|
Consequences []string
|
|
Implementation *PatternImplementation
|
|
}
|
|
|
|
// PatternStructure defines the structural elements of a pattern
|
|
type PatternStructure struct {
|
|
Classes []string
|
|
Interfaces []string
|
|
Relationships []string
|
|
KeyComponents []string
|
|
}
|
|
|
|
// PatternImplementation contains implementation-specific details
|
|
type PatternImplementation struct {
|
|
Languages []string
|
|
CodeSignatures []*regexp.Regexp
|
|
FileStructure []string
|
|
Dependencies []string
|
|
}
|
|
|
|
// NewDefaultPatternDetector creates a comprehensive pattern detector
|
|
func NewDefaultPatternDetector(config *EngineConfig) *DefaultPatternDetector {
|
|
return &DefaultPatternDetector{
|
|
config: config,
|
|
codeAnalyzer: NewCodePatternAnalyzer(),
|
|
namingAnalyzer: NewNamingPatternAnalyzer(),
|
|
orgAnalyzer: NewOrganizationalPatternAnalyzer(),
|
|
designAnalyzer: NewDesignPatternAnalyzer(),
|
|
}
|
|
}
|
|
|
|
// NewCodePatternAnalyzer creates a code pattern analyzer
|
|
func NewCodePatternAnalyzer() *CodePatternAnalyzer {
|
|
analyzer := &CodePatternAnalyzer{
|
|
languagePatterns: make(map[string]*LanguageCodePatterns),
|
|
}
|
|
|
|
// Initialize Go patterns
|
|
goPatterns := &LanguageCodePatterns{
|
|
DesignPatterns: []*DesignPatternMatcher{
|
|
{
|
|
PatternName: "Singleton",
|
|
Description: "Ensures a class has only one instance",
|
|
Signatures: []*regexp.Regexp{
|
|
regexp.MustCompile(`var\s+instance\s+\*\w+`),
|
|
regexp.MustCompile(`sync\.Once`),
|
|
regexp.MustCompile(`func\s+GetInstance\s*\(\s*\)\s*\*\w+`),
|
|
},
|
|
StructuralCues: []string{"sync.Once", "private constructor", "static instance"},
|
|
Confidence: func(matches, totalLines int) float64 {
|
|
return float64(matches) / 3.0
|
|
},
|
|
},
|
|
{
|
|
PatternName: "Factory",
|
|
Description: "Creates objects without specifying exact class",
|
|
Signatures: []*regexp.Regexp{
|
|
regexp.MustCompile(`func\s+New\w+\s*\(`),
|
|
regexp.MustCompile(`func\s+Create\w+\s*\(`),
|
|
regexp.MustCompile(`func\s+Make\w+\s*\(`),
|
|
},
|
|
StructuralCues: []string{"factory method", "object creation"},
|
|
},
|
|
{
|
|
PatternName: "Builder",
|
|
Description: "Constructs complex objects step by step",
|
|
Signatures: []*regexp.Regexp{
|
|
regexp.MustCompile(`func\s+\(\w+\s+\*\w+\)\s+With\w+\(`),
|
|
regexp.MustCompile(`func\s+\(\w+\s+\*\w+\)\s+Set\w+\(`),
|
|
regexp.MustCompile(`func\s+\(\w+\s+\*\w+\)\s+Build\s*\(\s*\)`),
|
|
},
|
|
StructuralCues: []string{"fluent interface", "method chaining", "build method"},
|
|
},
|
|
{
|
|
PatternName: "Observer",
|
|
Description: "Notifies multiple objects about state changes",
|
|
Signatures: []*regexp.Regexp{
|
|
regexp.MustCompile(`type\s+\w*Observer\w*\s+interface`),
|
|
regexp.MustCompile(`func\s+\w*Subscribe\w*\s*\(`),
|
|
regexp.MustCompile(`func\s+\w*Notify\w*\s*\(`),
|
|
},
|
|
StructuralCues: []string{"observer interface", "subscription", "notification"},
|
|
},
|
|
},
|
|
AntiPatterns: []*AntiPatternMatcher{
|
|
{
|
|
PatternName: "God Object",
|
|
Description: "Class that does too much",
|
|
Signatures: []*regexp.Regexp{regexp.MustCompile(`func\s+\(\w+\s+\*\w+\)\s+\w+`)},
|
|
Severity: "high",
|
|
Recommendation: "Split responsibilities into smaller, focused types",
|
|
},
|
|
{
|
|
PatternName: "Magic Numbers",
|
|
Description: "Unexplained numeric literals",
|
|
Signatures: []*regexp.Regexp{regexp.MustCompile(`\b\d{2,}\b`)},
|
|
Severity: "medium",
|
|
Recommendation: "Replace with named constants",
|
|
},
|
|
},
|
|
ArchPatterns: []*ArchitecturalPatternMatcher{
|
|
{
|
|
PatternName: "Repository Pattern",
|
|
Description: "Encapsulates data access logic",
|
|
FilePatterns: []*regexp.Regexp{regexp.MustCompile(`.*repository.*\.go$`)},
|
|
DirectoryHints: []string{"repository", "repo", "storage"},
|
|
Dependencies: []string{"database", "storage"},
|
|
},
|
|
},
|
|
BestPractices: []*BestPracticeMatcher{
|
|
{
|
|
PracticeName: "Error Handling",
|
|
Description: "Proper error handling patterns",
|
|
Indicators: []*regexp.Regexp{
|
|
regexp.MustCompile(`if\s+err\s*!=\s*nil`),
|
|
regexp.MustCompile(`return.*,\s*err`),
|
|
},
|
|
Violations: []*regexp.Regexp{
|
|
regexp.MustCompile(`_\s*,\s*_\s*:=`),
|
|
},
|
|
Impact: "high",
|
|
},
|
|
},
|
|
}
|
|
|
|
// Initialize JavaScript/TypeScript patterns
|
|
jsPatterns := &LanguageCodePatterns{
|
|
DesignPatterns: []*DesignPatternMatcher{
|
|
{
|
|
PatternName: "Module Pattern",
|
|
Description: "Encapsulates functionality in modules",
|
|
Signatures: []*regexp.Regexp{
|
|
regexp.MustCompile(`export\s+default`),
|
|
regexp.MustCompile(`module\.exports\s*=`),
|
|
regexp.MustCompile(`export\s+\{.*\}`),
|
|
},
|
|
},
|
|
{
|
|
PatternName: "Singleton",
|
|
Description: "Single instance pattern in JavaScript",
|
|
Signatures: []*regexp.Regexp{
|
|
regexp.MustCompile(`class\s+\w+\s*\{[\s\S]*static\s+instance`),
|
|
regexp.MustCompile(`getInstance\s*\(\s*\)`),
|
|
},
|
|
},
|
|
{
|
|
PatternName: "Observer",
|
|
Description: "Event-driven programming pattern",
|
|
Signatures: []*regexp.Regexp{
|
|
regexp.MustCompile(`addEventListener\s*\(`),
|
|
regexp.MustCompile(`on\s*\(`),
|
|
regexp.MustCompile(`subscribe\s*\(`),
|
|
},
|
|
},
|
|
},
|
|
AntiPatterns: []*AntiPatternMatcher{
|
|
{
|
|
PatternName: "Callback Hell",
|
|
Description: "Deeply nested callbacks",
|
|
Signatures: []*regexp.Regexp{regexp.MustCompile(`function\s*\([^)]*\)\s*\{[\s\S]*function\s*\([^)]*\)\s*\{[\s\S]*function`)},
|
|
Severity: "high",
|
|
Recommendation: "Use Promises or async/await",
|
|
},
|
|
},
|
|
}
|
|
|
|
// Initialize Python patterns
|
|
pythonPatterns := &LanguageCodePatterns{
|
|
DesignPatterns: []*DesignPatternMatcher{
|
|
{
|
|
PatternName: "Decorator Pattern",
|
|
Description: "Adds behavior to objects dynamically",
|
|
Signatures: []*regexp.Regexp{
|
|
regexp.MustCompile(`@\w+`),
|
|
regexp.MustCompile(`def\s+\w+\s*\([^)]*\)\s*->\s*callable`),
|
|
},
|
|
},
|
|
{
|
|
PatternName: "Context Manager",
|
|
Description: "Resource management pattern",
|
|
Signatures: []*regexp.Regexp{
|
|
regexp.MustCompile(`def\s+__enter__\s*\(`),
|
|
regexp.MustCompile(`def\s+__exit__\s*\(`),
|
|
regexp.MustCompile(`with\s+\w+`),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
analyzer.languagePatterns["go"] = goPatterns
|
|
analyzer.languagePatterns["javascript"] = jsPatterns
|
|
analyzer.languagePatterns["typescript"] = jsPatterns
|
|
analyzer.languagePatterns["python"] = pythonPatterns
|
|
|
|
return analyzer
|
|
}
|
|
|
|
// NewNamingPatternAnalyzer creates a naming pattern analyzer
|
|
func NewNamingPatternAnalyzer() *NamingPatternAnalyzer {
|
|
analyzer := &NamingPatternAnalyzer{
|
|
conventionRules: make(map[string]*NamingConventionRule),
|
|
}
|
|
|
|
// Go naming conventions
|
|
goRules := []*NamingConventionRule{
|
|
{
|
|
Language: "go",
|
|
Scope: "function",
|
|
Pattern: regexp.MustCompile(`^[A-Z][a-zA-Z0-9]*$`),
|
|
Description: "Exported functions use PascalCase",
|
|
Examples: []string{"GetUser", "ProcessData"},
|
|
},
|
|
{
|
|
Language: "go",
|
|
Scope: "variable",
|
|
Pattern: regexp.MustCompile(`^[a-z][a-zA-Z0-9]*$`),
|
|
Description: "Variables use camelCase",
|
|
Examples: []string{"userName", "totalCount"},
|
|
},
|
|
{
|
|
Language: "go",
|
|
Scope: "constant",
|
|
Pattern: regexp.MustCompile(`^[A-Z][A-Z0-9_]*$`),
|
|
Description: "Constants use SCREAMING_SNAKE_CASE",
|
|
Examples: []string{"MAX_SIZE", "DEFAULT_TIMEOUT"},
|
|
},
|
|
}
|
|
|
|
// JavaScript/TypeScript naming conventions
|
|
jsRules := []*NamingConventionRule{
|
|
{
|
|
Language: "javascript",
|
|
Scope: "function",
|
|
Pattern: regexp.MustCompile(`^[a-z][a-zA-Z0-9]*$`),
|
|
Description: "Functions use camelCase",
|
|
Examples: []string{"getUserData", "processResults"},
|
|
},
|
|
{
|
|
Language: "javascript",
|
|
Scope: "class",
|
|
Pattern: regexp.MustCompile(`^[A-Z][a-zA-Z0-9]*$`),
|
|
Description: "Classes use PascalCase",
|
|
Examples: []string{"UserManager", "DataProcessor"},
|
|
},
|
|
}
|
|
|
|
// Python naming conventions
|
|
pythonRules := []*NamingConventionRule{
|
|
{
|
|
Language: "python",
|
|
Scope: "function",
|
|
Pattern: regexp.MustCompile(`^[a-z][a-z0-9_]*$`),
|
|
Description: "Functions use snake_case",
|
|
Examples: []string{"get_user_data", "process_results"},
|
|
},
|
|
{
|
|
Language: "python",
|
|
Scope: "class",
|
|
Pattern: regexp.MustCompile(`^[A-Z][a-zA-Z0-9]*$`),
|
|
Description: "Classes use PascalCase",
|
|
Examples: []string{"UserManager", "DataProcessor"},
|
|
},
|
|
}
|
|
|
|
// Register all rules
|
|
for _, rule := range append(append(goRules, jsRules...), pythonRules...) {
|
|
key := fmt.Sprintf("%s_%s", rule.Language, rule.Scope)
|
|
analyzer.conventionRules[key] = rule
|
|
}
|
|
|
|
return analyzer
|
|
}
|
|
|
|
// NewOrganizationalPatternAnalyzer creates an organizational pattern analyzer
|
|
func NewOrganizationalPatternAnalyzer() *OrganizationalPatternAnalyzer {
|
|
analyzer := &OrganizationalPatternAnalyzer{
|
|
structuralPatterns: []*StructuralPatternMatcher{},
|
|
}
|
|
|
|
// Define common structural patterns
|
|
patterns := []*StructuralPatternMatcher{
|
|
{
|
|
PatternName: "Hexagonal Architecture",
|
|
Description: "Ports and adapters architecture",
|
|
DirectoryPatterns: []*regexp.Regexp{
|
|
regexp.MustCompile(`.*/(domain|core)/.*`),
|
|
regexp.MustCompile(`.*/adapters?/.*`),
|
|
regexp.MustCompile(`.*/ports?/.*`),
|
|
},
|
|
RequiredFiles: []string{"domain", "adapters"},
|
|
Characteristics: []string{"dependency_inversion", "testable", "framework_independent"},
|
|
},
|
|
{
|
|
PatternName: "Clean Architecture",
|
|
Description: "Uncle Bob's clean architecture",
|
|
DirectoryPatterns: []*regexp.Regexp{
|
|
regexp.MustCompile(`.*/entities/.*`),
|
|
regexp.MustCompile(`.*/usecases/.*`),
|
|
regexp.MustCompile(`.*/adapters/.*`),
|
|
regexp.MustCompile(`.*/frameworks?/.*`),
|
|
},
|
|
RequiredFiles: []string{"entities", "usecases"},
|
|
Characteristics: []string{"dependency_rule", "testable", "ui_independent"},
|
|
},
|
|
{
|
|
PatternName: "Microservices",
|
|
Description: "Service-oriented architecture",
|
|
DirectoryPatterns: []*regexp.Regexp{
|
|
regexp.MustCompile(`.*/services?/.*`),
|
|
regexp.MustCompile(`.*/api-gateway/.*`),
|
|
},
|
|
RequiredFiles: []string{"services"},
|
|
Characteristics: []string{"distributed", "autonomous", "scalable"},
|
|
},
|
|
{
|
|
PatternName: "Monorepo",
|
|
Description: "Multiple projects in single repository",
|
|
DirectoryPatterns: []*regexp.Regexp{
|
|
regexp.MustCompile(`.*/packages?/.*`),
|
|
regexp.MustCompile(`.*/apps?/.*`),
|
|
regexp.MustCompile(`.*/libs?/.*`),
|
|
},
|
|
RequiredFiles: []string{"packages", "apps"},
|
|
Characteristics: []string{"shared_dependencies", "atomic_commits", "unified_tooling"},
|
|
},
|
|
}
|
|
|
|
analyzer.structuralPatterns = patterns
|
|
return analyzer
|
|
}
|
|
|
|
// NewDesignPatternAnalyzer creates a design pattern analyzer
|
|
func NewDesignPatternAnalyzer() *DesignPatternAnalyzer {
|
|
analyzer := &DesignPatternAnalyzer{
|
|
patternLibrary: make(map[string]*DesignPatternDefinition),
|
|
}
|
|
|
|
// Define comprehensive design patterns
|
|
patterns := []*DesignPatternDefinition{
|
|
{
|
|
Name: "Singleton",
|
|
Category: "creational",
|
|
Intent: "Ensure a class has only one instance and provide global point of access",
|
|
Applicability: []string{
|
|
"exactly one instance needed",
|
|
"instance must be accessible from well-known access point",
|
|
"sole instance should be extensible by subclassing",
|
|
},
|
|
Structure: &PatternStructure{
|
|
Classes: []string{"Singleton"},
|
|
Interfaces: []string{},
|
|
Relationships: []string{"self-reference"},
|
|
KeyComponents: []string{"private constructor", "static instance", "getInstance method"},
|
|
},
|
|
Implementation: &PatternImplementation{
|
|
Languages: []string{"go", "java", "javascript", "python"},
|
|
CodeSignatures: []*regexp.Regexp{
|
|
regexp.MustCompile(`getInstance|GetInstance`),
|
|
regexp.MustCompile(`static.*instance|var.*instance`),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "Factory Method",
|
|
Category: "creational",
|
|
Intent: "Create objects without specifying their concrete classes",
|
|
Structure: &PatternStructure{
|
|
Classes: []string{"Creator", "ConcreteCreator", "Product", "ConcreteProduct"},
|
|
Interfaces: []string{"Product"},
|
|
Relationships: []string{"creator uses product"},
|
|
KeyComponents: []string{"factory method", "product hierarchy"},
|
|
},
|
|
Implementation: &PatternImplementation{
|
|
Languages: []string{"go", "java", "javascript", "python"},
|
|
CodeSignatures: []*regexp.Regexp{
|
|
regexp.MustCompile(`New\w+|Create\w+|Make\w+`),
|
|
regexp.MustCompile(`factory|Factory`),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "Observer",
|
|
Category: "behavioral",
|
|
Intent: "Define a one-to-many dependency between objects",
|
|
Structure: &PatternStructure{
|
|
Classes: []string{"Subject", "Observer", "ConcreteSubject", "ConcreteObserver"},
|
|
Interfaces: []string{"Observer", "Subject"},
|
|
Relationships: []string{"subject notifies observers"},
|
|
KeyComponents: []string{"subscribe", "unsubscribe", "notify"},
|
|
},
|
|
Implementation: &PatternImplementation{
|
|
Languages: []string{"go", "java", "javascript", "python"},
|
|
CodeSignatures: []*regexp.Regexp{
|
|
regexp.MustCompile(`Subscribe|Unsubscribe|Notify`),
|
|
regexp.MustCompile(`Observer|Subject`),
|
|
regexp.MustCompile(`addEventListener|on\(`),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, pattern := range patterns {
|
|
analyzer.patternLibrary[pattern.Name] = pattern
|
|
}
|
|
|
|
return analyzer
|
|
}
|
|
|
|
// DetectCodePatterns identifies code patterns and architectural styles
|
|
func (pd *DefaultPatternDetector) DetectCodePatterns(ctx context.Context, filePath string, content []byte) ([]*CodePattern, error) {
|
|
patterns := []*CodePattern{}
|
|
|
|
// Detect language
|
|
language := pd.detectLanguageFromPath(filePath)
|
|
if language == "" {
|
|
return patterns, nil
|
|
}
|
|
|
|
// Get language-specific patterns
|
|
langPatterns, exists := pd.codeAnalyzer.languagePatterns[language]
|
|
if !exists {
|
|
return patterns, nil
|
|
}
|
|
|
|
contentStr := string(content)
|
|
|
|
// Detect design patterns
|
|
for _, designPattern := range langPatterns.DesignPatterns {
|
|
if pattern := pd.analyzeDesignPattern(contentStr, designPattern, language); pattern != nil {
|
|
patterns = append(patterns, pattern)
|
|
}
|
|
}
|
|
|
|
// Detect architectural patterns
|
|
for _, archPattern := range langPatterns.ArchPatterns {
|
|
if pattern := pd.analyzeArchitecturalPattern(filePath, contentStr, archPattern, language); pattern != nil {
|
|
patterns = append(patterns, pattern)
|
|
}
|
|
}
|
|
|
|
// Detect anti-patterns
|
|
for _, antiPattern := range langPatterns.AntiPatterns {
|
|
if pattern := pd.analyzeAntiPattern(contentStr, antiPattern, language); pattern != nil {
|
|
patterns = append(patterns, pattern)
|
|
}
|
|
}
|
|
|
|
return patterns, nil
|
|
}
|
|
|
|
// DetectNamingPatterns identifies naming conventions and patterns
|
|
func (pd *DefaultPatternDetector) DetectNamingPatterns(ctx context.Context, contexts []*slurpContext.ContextNode) ([]*NamingPattern, error) {
|
|
patterns := []*NamingPattern{}
|
|
|
|
// Group contexts by language
|
|
langGroups := make(map[string][]*slurpContext.ContextNode)
|
|
for _, context := range contexts {
|
|
if analysis, ok := context.Metadata["analysis"].(*FileAnalysis); ok {
|
|
lang := analysis.Language
|
|
if lang != "" {
|
|
langGroups[lang] = append(langGroups[lang], context)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Analyze naming patterns for each language
|
|
for language, langContexts := range langGroups {
|
|
langPatterns := pd.analyzeLanguageNamingPatterns(language, langContexts)
|
|
patterns = append(patterns, langPatterns...)
|
|
}
|
|
|
|
return patterns, nil
|
|
}
|
|
|
|
// DetectOrganizationalPatterns identifies organizational patterns
|
|
func (pd *DefaultPatternDetector) DetectOrganizationalPatterns(ctx context.Context, rootPath string) ([]*OrganizationalPattern, error) {
|
|
patterns := []*OrganizationalPattern{}
|
|
|
|
for _, matcher := range pd.orgAnalyzer.structuralPatterns {
|
|
if pattern := pd.analyzeStructuralPattern(rootPath, matcher); pattern != nil {
|
|
patterns = append(patterns, pattern)
|
|
}
|
|
}
|
|
|
|
return patterns, nil
|
|
}
|
|
|
|
// MatchPatterns matches context against known patterns
|
|
func (pd *DefaultPatternDetector) MatchPatterns(ctx context.Context, node *slurpContext.ContextNode, patterns []*Pattern) ([]*PatternMatch, error) {
|
|
matches := []*PatternMatch{}
|
|
|
|
for _, pattern := range patterns {
|
|
if match := pd.calculatePatternMatch(node, pattern); match != nil {
|
|
matches = append(matches, match)
|
|
}
|
|
}
|
|
|
|
// Sort by match score
|
|
sort.Slice(matches, func(i, j int) bool {
|
|
return matches[i].MatchScore > matches[j].MatchScore
|
|
})
|
|
|
|
return matches, nil
|
|
}
|
|
|
|
// LearnPatterns learns new patterns from context examples
|
|
func (pd *DefaultPatternDetector) LearnPatterns(ctx context.Context, examples []*slurpContext.ContextNode) ([]*Pattern, error) {
|
|
patterns := []*Pattern{}
|
|
|
|
// Group examples by similarity
|
|
groups := pd.groupSimilarContexts(examples)
|
|
|
|
// Extract patterns from each group
|
|
for groupID, group := range groups {
|
|
if len(group) >= 2 { // Need at least 2 examples to form a pattern
|
|
pattern := pd.extractPatternFromGroup(groupID, group)
|
|
if pattern != nil {
|
|
patterns = append(patterns, pattern)
|
|
}
|
|
}
|
|
}
|
|
|
|
return patterns, nil
|
|
}
|
|
|
|
// Helper methods
|
|
|
|
func (pd *DefaultPatternDetector) detectLanguageFromPath(filePath string) string {
|
|
ext := strings.ToLower(filepath.Ext(filePath))
|
|
langMap := map[string]string{
|
|
".go": "go",
|
|
".py": "python",
|
|
".js": "javascript",
|
|
".jsx": "javascript",
|
|
".ts": "typescript",
|
|
".tsx": "typescript",
|
|
".java": "java",
|
|
".c": "c",
|
|
".cpp": "cpp",
|
|
".cs": "csharp",
|
|
".php": "php",
|
|
".rb": "ruby",
|
|
".rs": "rust",
|
|
}
|
|
return langMap[ext]
|
|
}
|
|
|
|
func (pd *DefaultPatternDetector) analyzeDesignPattern(content string, matcher *DesignPatternMatcher, language string) *CodePattern {
|
|
matches := 0
|
|
matchedSignatures := []string{}
|
|
|
|
for _, signature := range matcher.Signatures {
|
|
if signature.MatchString(content) {
|
|
matches++
|
|
matchedSignatures = append(matchedSignatures, signature.String())
|
|
}
|
|
}
|
|
|
|
if matches == 0 {
|
|
return nil
|
|
}
|
|
|
|
confidence := 0.0
|
|
if matcher.Confidence != nil {
|
|
lines := strings.Count(content, "\n") + 1
|
|
confidence = matcher.Confidence(matches, lines)
|
|
} else {
|
|
confidence = float64(matches) / float64(len(matcher.Signatures))
|
|
}
|
|
|
|
if confidence < 0.3 {
|
|
return nil
|
|
}
|
|
|
|
return &CodePattern{
|
|
Pattern: Pattern{
|
|
ID: fmt.Sprintf("%s_%s", language, strings.ToLower(matcher.PatternName)),
|
|
Name: matcher.PatternName,
|
|
Type: "design_pattern",
|
|
Description: matcher.Description,
|
|
Confidence: confidence,
|
|
Examples: matchedSignatures,
|
|
DetectedAt: time.Now(),
|
|
},
|
|
Language: language,
|
|
Complexity: pd.calculatePatternComplexity(matcher.PatternName),
|
|
Usage: &UsagePattern{
|
|
Frequency: pd.determinePatternFrequency(matches),
|
|
Context: matcher.StructuralCues,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (pd *DefaultPatternDetector) analyzeArchitecturalPattern(filePath, content string, matcher *ArchitecturalPatternMatcher, language string) *CodePattern {
|
|
// Check file path patterns
|
|
pathMatches := false
|
|
for _, pattern := range matcher.FilePatterns {
|
|
if pattern.MatchString(filePath) {
|
|
pathMatches = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !pathMatches {
|
|
return nil
|
|
}
|
|
|
|
// Check for dependencies if specified
|
|
hasRequiredDeps := len(matcher.Dependencies) == 0
|
|
if len(matcher.Dependencies) > 0 {
|
|
for _, dep := range matcher.Dependencies {
|
|
if strings.Contains(strings.ToLower(content), strings.ToLower(dep)) {
|
|
hasRequiredDeps = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if !hasRequiredDeps {
|
|
return nil
|
|
}
|
|
|
|
return &CodePattern{
|
|
Pattern: Pattern{
|
|
ID: fmt.Sprintf("%s_%s_arch", language, strings.ToLower(matcher.PatternName)),
|
|
Name: matcher.PatternName,
|
|
Type: "architectural_pattern",
|
|
Description: matcher.Description,
|
|
Confidence: 0.8,
|
|
Examples: []string{filepath.Base(filePath)},
|
|
DetectedAt: time.Now(),
|
|
},
|
|
Language: language,
|
|
Complexity: 0.7,
|
|
Usage: &UsagePattern{
|
|
Frequency: "common",
|
|
Context: matcher.DirectoryHints,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (pd *DefaultPatternDetector) analyzeAntiPattern(content string, matcher *AntiPatternMatcher, language string) *CodePattern {
|
|
matches := 0
|
|
for _, signature := range matcher.Signatures {
|
|
matches += len(signature.FindAllString(content, -1))
|
|
}
|
|
|
|
if matches == 0 {
|
|
return nil
|
|
}
|
|
|
|
severity := 0.5
|
|
switch matcher.Severity {
|
|
case "critical":
|
|
severity = 1.0
|
|
case "high":
|
|
severity = 0.8
|
|
case "medium":
|
|
severity = 0.6
|
|
case "low":
|
|
severity = 0.4
|
|
}
|
|
|
|
return &CodePattern{
|
|
Pattern: Pattern{
|
|
ID: fmt.Sprintf("%s_%s_anti", language, strings.ToLower(matcher.PatternName)),
|
|
Name: matcher.PatternName,
|
|
Type: "anti_pattern",
|
|
Description: matcher.Description,
|
|
Confidence: severity,
|
|
Frequency: matches,
|
|
Drawbacks: []string{matcher.Recommendation},
|
|
DetectedAt: time.Now(),
|
|
},
|
|
Language: language,
|
|
Complexity: severity,
|
|
}
|
|
}
|
|
|
|
func (pd *DefaultPatternDetector) analyzeLanguageNamingPatterns(language string, contexts []*slurpContext.ContextNode) []*NamingPattern {
|
|
patterns := []*NamingPattern{}
|
|
|
|
// Collect all identifiers from contexts
|
|
identifiers := pd.collectIdentifiers(contexts)
|
|
|
|
// Analyze patterns for different scopes
|
|
scopes := []string{"function", "variable", "class", "file"}
|
|
for _, scope := range scopes {
|
|
if pattern := pd.analyzeNamingPatternForScope(language, scope, identifiers[scope]); pattern != nil {
|
|
patterns = append(patterns, pattern)
|
|
}
|
|
}
|
|
|
|
return patterns
|
|
}
|
|
|
|
func (pd *DefaultPatternDetector) collectIdentifiers(contexts []*slurpContext.ContextNode) map[string][]string {
|
|
identifiers := make(map[string][]string)
|
|
|
|
for _, context := range contexts {
|
|
if analysis, ok := context.Metadata["analysis"].(*FileAnalysis); ok {
|
|
identifiers["function"] = append(identifiers["function"], analysis.Functions...)
|
|
identifiers["variable"] = append(identifiers["variable"], analysis.Variables...)
|
|
identifiers["class"] = append(identifiers["class"], analysis.Classes...)
|
|
identifiers["file"] = append(identifiers["file"], filepath.Base(context.Path))
|
|
}
|
|
}
|
|
|
|
return identifiers
|
|
}
|
|
|
|
func (pd *DefaultPatternDetector) analyzeNamingPatternForScope(language, scope string, identifiers []string) *NamingPattern {
|
|
if len(identifiers) < 2 {
|
|
return nil
|
|
}
|
|
|
|
// Detect dominant convention
|
|
conventions := map[string]int{
|
|
"camelCase": 0,
|
|
"PascalCase": 0,
|
|
"snake_case": 0,
|
|
"kebab-case": 0,
|
|
}
|
|
|
|
for _, identifier := range identifiers {
|
|
if matched, _ := regexp.MatchString(`^[a-z][a-zA-Z0-9]*$`, identifier); matched {
|
|
conventions["camelCase"]++
|
|
} else if matched, _ := regexp.MatchString(`^[A-Z][a-zA-Z0-9]*$`, identifier); matched {
|
|
conventions["PascalCase"]++
|
|
} else if matched, _ := regexp.MatchString(`^[a-z][a-z0-9_]*$`, identifier); matched {
|
|
conventions["snake_case"]++
|
|
} else if matched, _ := regexp.MatchString(`^[a-z][a-z0-9-]*$`, identifier); matched {
|
|
conventions["kebab-case"]++
|
|
}
|
|
}
|
|
|
|
// Find dominant convention
|
|
maxCount := 0
|
|
dominantConvention := "mixed"
|
|
for convention, count := range conventions {
|
|
if count > maxCount {
|
|
maxCount = count
|
|
dominantConvention = convention
|
|
}
|
|
}
|
|
|
|
confidence := float64(maxCount) / float64(len(identifiers))
|
|
if confidence < 0.5 {
|
|
return nil
|
|
}
|
|
|
|
return &NamingPattern{
|
|
Pattern: Pattern{
|
|
ID: fmt.Sprintf("%s_%s_naming", language, scope),
|
|
Name: fmt.Sprintf("%s %s Naming", strings.Title(language), strings.Title(scope)),
|
|
Type: "naming_convention",
|
|
Description: fmt.Sprintf("Naming convention for %s %ss", language, scope),
|
|
Confidence: confidence,
|
|
Examples: identifiers[:min(5, len(identifiers))],
|
|
DetectedAt: time.Now(),
|
|
},
|
|
Convention: dominantConvention,
|
|
Scope: scope,
|
|
CaseStyle: dominantConvention,
|
|
}
|
|
}
|
|
|
|
func (pd *DefaultPatternDetector) analyzeStructuralPattern(rootPath string, matcher *StructuralPatternMatcher) *OrganizationalPattern {
|
|
// Check if pattern directory structure exists
|
|
matchCount := 0
|
|
totalRequired := len(matcher.RequiredFiles)
|
|
|
|
for _, required := range matcher.RequiredFiles {
|
|
checkPath := filepath.Join(rootPath, required)
|
|
if pd.pathExists(checkPath) {
|
|
matchCount++
|
|
}
|
|
}
|
|
|
|
if matchCount < totalRequired {
|
|
return nil
|
|
}
|
|
|
|
confidence := float64(matchCount) / float64(totalRequired)
|
|
|
|
return &OrganizationalPattern{
|
|
Pattern: Pattern{
|
|
ID: strings.ToLower(strings.ReplaceAll(matcher.PatternName, " ", "_")),
|
|
Name: matcher.PatternName,
|
|
Type: "organizational",
|
|
Description: matcher.Description,
|
|
Confidence: confidence,
|
|
Examples: matcher.RequiredFiles,
|
|
Benefits: matcher.Characteristics,
|
|
DetectedAt: time.Now(),
|
|
},
|
|
Structure: "hierarchical",
|
|
Depth: matcher.Depth,
|
|
FanOut: len(matcher.RequiredFiles),
|
|
Modularity: confidence,
|
|
Scalability: pd.assessScalability(matcher.Characteristics),
|
|
}
|
|
}
|
|
|
|
func (pd *DefaultPatternDetector) calculatePatternMatch(node *slurpContext.ContextNode, pattern *Pattern) *PatternMatch {
|
|
score := 0.0
|
|
matchedFields := []string{}
|
|
|
|
// Check summary match
|
|
if pd.textContainsKeywords(node.Summary, pattern.Examples) {
|
|
score += 0.3
|
|
matchedFields = append(matchedFields, "summary")
|
|
}
|
|
|
|
// Check purpose match
|
|
if pd.textContainsKeywords(node.Purpose, pattern.Examples) {
|
|
score += 0.3
|
|
matchedFields = append(matchedFields, "purpose")
|
|
}
|
|
|
|
// Check technology match
|
|
for _, tech := range node.Technologies {
|
|
if pd.containsIgnoreCase(pattern.Examples, tech) {
|
|
score += 0.2
|
|
matchedFields = append(matchedFields, "technologies")
|
|
break
|
|
}
|
|
}
|
|
|
|
// Check tag match
|
|
for _, tag := range node.Tags {
|
|
if pd.containsIgnoreCase(pattern.Examples, tag) {
|
|
score += 0.2
|
|
matchedFields = append(matchedFields, "tags")
|
|
break
|
|
}
|
|
}
|
|
|
|
if score < 0.3 {
|
|
return nil
|
|
}
|
|
|
|
return &PatternMatch{
|
|
PatternID: pattern.ID,
|
|
MatchScore: score,
|
|
Confidence: pattern.Confidence * score,
|
|
MatchedFields: matchedFields,
|
|
Explanation: fmt.Sprintf("Pattern %s matched with score %.2f", pattern.Name, score),
|
|
Suggestions: pd.generatePatternSuggestions(pattern),
|
|
}
|
|
}
|
|
|
|
func (pd *DefaultPatternDetector) groupSimilarContexts(contexts []*slurpContext.ContextNode) map[string][]*slurpContext.ContextNode {
|
|
groups := make(map[string][]*slurpContext.ContextNode)
|
|
|
|
for _, context := range contexts {
|
|
// Simple grouping by primary technology
|
|
groupKey := "unknown"
|
|
if len(context.Technologies) > 0 {
|
|
groupKey = context.Technologies[0]
|
|
}
|
|
|
|
groups[groupKey] = append(groups[groupKey], context)
|
|
}
|
|
|
|
return groups
|
|
}
|
|
|
|
func (pd *DefaultPatternDetector) extractPatternFromGroup(groupID string, group []*slurpContext.ContextNode) *Pattern {
|
|
// Find common characteristics
|
|
commonTechs := pd.findCommonTechnologies(group)
|
|
commonTags := pd.findCommonTags(group)
|
|
|
|
if len(commonTechs) == 0 && len(commonTags) == 0 {
|
|
return nil
|
|
}
|
|
|
|
return &Pattern{
|
|
ID: fmt.Sprintf("learned_%s_%d", groupID, time.Now().Unix()),
|
|
Name: fmt.Sprintf("Learned %s Pattern", strings.Title(groupID)),
|
|
Type: "learned",
|
|
Description: fmt.Sprintf("Pattern extracted from %d similar contexts", len(group)),
|
|
Confidence: pd.calculateLearningConfidence(group),
|
|
Examples: append(commonTechs, commonTags...),
|
|
DetectedAt: time.Now(),
|
|
}
|
|
}
|
|
|
|
// Additional helper methods
|
|
|
|
func (pd *DefaultPatternDetector) calculatePatternComplexity(patternName string) float64 {
|
|
complexityMap := map[string]float64{
|
|
"Singleton": 0.3,
|
|
"Factory": 0.5,
|
|
"Builder": 0.7,
|
|
"Observer": 0.6,
|
|
"Strategy": 0.5,
|
|
"Command": 0.6,
|
|
"Decorator": 0.8,
|
|
"Composite": 0.9,
|
|
"Abstract Factory": 0.9,
|
|
"Prototype": 0.4,
|
|
}
|
|
|
|
if complexity, exists := complexityMap[patternName]; exists {
|
|
return complexity
|
|
}
|
|
return 0.5 // Default complexity
|
|
}
|
|
|
|
func (pd *DefaultPatternDetector) determinePatternFrequency(matches int) string {
|
|
if matches > 5 {
|
|
return "frequent"
|
|
} else if matches > 2 {
|
|
return "common"
|
|
} else {
|
|
return "rare"
|
|
}
|
|
}
|
|
|
|
func (pd *DefaultPatternDetector) pathExists(path string) bool {
|
|
_, err := filepath.Abs(path)
|
|
return err == nil
|
|
}
|
|
|
|
func (pd *DefaultPatternDetector) assessScalability(characteristics []string) string {
|
|
for _, char := range characteristics {
|
|
if strings.Contains(char, "scalable") {
|
|
return "excellent"
|
|
}
|
|
}
|
|
return "good"
|
|
}
|
|
|
|
func (pd *DefaultPatternDetector) textContainsKeywords(text string, keywords []string) bool {
|
|
lowerText := strings.ToLower(text)
|
|
for _, keyword := range keywords {
|
|
if strings.Contains(lowerText, strings.ToLower(keyword)) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (pd *DefaultPatternDetector) containsIgnoreCase(slice []string, item string) bool {
|
|
lowerItem := strings.ToLower(item)
|
|
for _, s := range slice {
|
|
if strings.ToLower(s) == lowerItem {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (pd *DefaultPatternDetector) generatePatternSuggestions(pattern *Pattern) []string {
|
|
suggestions := []string{}
|
|
|
|
switch pattern.Type {
|
|
case "design_pattern":
|
|
suggestions = append(suggestions, "Consider documenting the pattern usage")
|
|
suggestions = append(suggestions, "Ensure pattern implementation follows best practices")
|
|
case "anti_pattern":
|
|
suggestions = append(suggestions, "Refactor to eliminate anti-pattern")
|
|
suggestions = append(suggestions, "Consider alternative design approaches")
|
|
case "architectural_pattern":
|
|
suggestions = append(suggestions, "Document architectural decisions")
|
|
suggestions = append(suggestions, "Ensure pattern consistency across project")
|
|
}
|
|
|
|
return suggestions
|
|
}
|
|
|
|
func (pd *DefaultPatternDetector) findCommonTechnologies(contexts []*slurpContext.ContextNode) []string {
|
|
techCount := make(map[string]int)
|
|
|
|
for _, context := range contexts {
|
|
for _, tech := range context.Technologies {
|
|
techCount[tech]++
|
|
}
|
|
}
|
|
|
|
common := []string{}
|
|
threshold := len(contexts) / 2 // At least half should have the technology
|
|
for tech, count := range techCount {
|
|
if count >= threshold {
|
|
common = append(common, tech)
|
|
}
|
|
}
|
|
|
|
return common
|
|
}
|
|
|
|
func (pd *DefaultPatternDetector) findCommonTags(contexts []*slurpContext.ContextNode) []string {
|
|
tagCount := make(map[string]int)
|
|
|
|
for _, context := range contexts {
|
|
for _, tag := range context.Tags {
|
|
tagCount[tag]++
|
|
}
|
|
}
|
|
|
|
common := []string{}
|
|
threshold := len(contexts) / 2
|
|
for tag, count := range tagCount {
|
|
if count >= threshold {
|
|
common = append(common, tag)
|
|
}
|
|
}
|
|
|
|
return common
|
|
}
|
|
|
|
func (pd *DefaultPatternDetector) calculateLearningConfidence(group []*slurpContext.ContextNode) float64 {
|
|
// Simple confidence based on group size and consistency
|
|
baseConfidence := 0.5
|
|
groupBonus := float64(len(group)) * 0.1
|
|
if groupBonus > 0.3 {
|
|
groupBonus = 0.3
|
|
}
|
|
|
|
return baseConfidence + groupBonus
|
|
}
|
|
|
|
func min(a, b int) int {
|
|
if a < b {
|
|
return a
|
|
}
|
|
return b
|
|
} |