package intelligence import ( "context" "fmt" "io/ioutil" "os" "path/filepath" "regexp" "sort" "strings" "time" "github.com/anthonyrawlins/bzzz/pkg/ucxl" slurpContext "github.com/anthonyrawlins/bzzz/pkg/slurp/context" ) // DefaultDirectoryAnalyzer provides comprehensive directory structure analysis type DefaultDirectoryAnalyzer struct { config *EngineConfig organizationDetector *OrganizationDetector conventionAnalyzer *ConventionAnalyzer relationshipAnalyzer *RelationshipAnalyzer } // OrganizationDetector detects organizational patterns in directory structures type OrganizationDetector struct { commonPatterns map[string]*OrganizationalPattern } // ConventionAnalyzer analyzes naming and organizational conventions type ConventionAnalyzer struct { namingRegexes map[string]*regexp.Regexp standards map[string]*CodingStandard } // RelationshipAnalyzer analyzes relationships between directories and files type RelationshipAnalyzer struct { dependencyDetectors map[string]*DependencyDetector } // DependencyDetector detects dependencies for specific languages/frameworks type DependencyDetector struct { importPatterns []*regexp.Regexp configFiles []string } // CodingStandard represents a coding standard or convention type CodingStandard struct { Name string Rules []*ConventionRule FileTypes []string Description string } // ConventionRule represents a single convention rule type ConventionRule struct { Type string // naming, structure, organization Pattern string Description string Severity string // error, warning, info } // NewDefaultDirectoryAnalyzer creates a new directory analyzer func NewDefaultDirectoryAnalyzer(config *EngineConfig) *DefaultDirectoryAnalyzer { return &DefaultDirectoryAnalyzer{ config: config, organizationDetector: NewOrganizationDetector(), conventionAnalyzer: NewConventionAnalyzer(), relationshipAnalyzer: NewRelationshipAnalyzer(), } } // NewOrganizationDetector creates an organization pattern detector func NewOrganizationDetector() *OrganizationDetector { detector := &OrganizationDetector{ commonPatterns: make(map[string]*OrganizationalPattern), } // Define common organizational patterns patterns := []*OrganizationalPattern{ { Pattern: Pattern{ ID: "mvc", Name: "Model-View-Controller (MVC)", Type: "architectural", Description: "Separates concerns into models, views, and controllers", Confidence: 0.9, Examples: []string{"models/", "views/", "controllers/"}, Benefits: []string{"Clear separation of concerns", "Maintainable code structure"}, }, Structure: "layered", Depth: 2, FanOut: 3, Modularity: 0.8, Scalability: "good", }, { Pattern: Pattern{ ID: "clean_architecture", Name: "Clean Architecture", Type: "architectural", Description: "Dependency inversion with clear boundaries", Confidence: 0.85, Examples: []string{"entities/", "usecases/", "adapters/", "frameworks/"}, Benefits: []string{"Testable", "Independent of frameworks", "Independent of UI"}, }, Structure: "onion", Depth: 3, FanOut: 4, Modularity: 0.95, Scalability: "excellent", }, { Pattern: Pattern{ ID: "domain_driven", Name: "Domain-Driven Design (DDD)", Type: "architectural", Description: "Organized around business domains", Confidence: 0.8, Examples: []string{"domain/", "application/", "infrastructure/"}, Benefits: []string{"Business-focused", "Clear domain boundaries"}, }, Structure: "domain-based", Depth: 3, FanOut: 5, Modularity: 0.9, Scalability: "excellent", }, { Pattern: Pattern{ ID: "feature_based", Name: "Feature-Based Organization", Type: "organizational", Description: "Organized by features rather than technical layers", Confidence: 0.75, Examples: []string{"user-management/", "payment/", "notifications/"}, Benefits: []string{"Feature-focused development", "Team autonomy"}, }, Structure: "feature-vertical", Depth: 2, FanOut: 6, Modularity: 0.85, Scalability: "good", }, { Pattern: Pattern{ ID: "microservices", Name: "Microservices Pattern", Type: "architectural", Description: "Independent services with their own data", Confidence: 0.8, Examples: []string{"services/", "api-gateway/", "shared/"}, Benefits: []string{"Independent deployment", "Technology diversity", "Fault isolation"}, }, Structure: "service-oriented", Depth: 2, FanOut: 8, Modularity: 0.95, Scalability: "excellent", }, } for _, pattern := range patterns { detector.commonPatterns[pattern.ID] = pattern } return detector } // NewConventionAnalyzer creates a convention analyzer func NewConventionAnalyzer() *ConventionAnalyzer { analyzer := &ConventionAnalyzer{ namingRegexes: make(map[string]*regexp.Regexp), standards: make(map[string]*CodingStandard), } // Define naming convention regexes analyzer.namingRegexes["camelCase"] = regexp.MustCompile(`^[a-z][a-zA-Z0-9]*$`) analyzer.namingRegexes["PascalCase"] = regexp.MustCompile(`^[A-Z][a-zA-Z0-9]*$`) analyzer.namingRegexes["snake_case"] = regexp.MustCompile(`^[a-z][a-z0-9_]*$`) analyzer.namingRegexes["kebab-case"] = regexp.MustCompile(`^[a-z][a-z0-9-]*$`) analyzer.namingRegexes["SCREAMING_SNAKE"] = regexp.MustCompile(`^[A-Z][A-Z0-9_]*$`) // Define coding standards goStandard := &CodingStandard{ Name: "Go Standard", FileTypes: []string{".go"}, Description: "Go language conventions", Rules: []*ConventionRule{ {Type: "naming", Pattern: "^[A-Z][a-zA-Z0-9]*$", Description: "Exported functions/types use PascalCase"}, {Type: "naming", Pattern: "^[a-z][a-zA-Z0-9]*$", Description: "Private functions/variables use camelCase"}, {Type: "structure", Pattern: "package main", Description: "Executable packages use 'main'"}, }, } pythonStandard := &CodingStandard{ Name: "PEP 8", FileTypes: []string{".py"}, Description: "Python enhancement proposal 8 style guide", Rules: []*ConventionRule{ {Type: "naming", Pattern: "^[a-z][a-z0-9_]*$", Description: "Functions and variables use snake_case"}, {Type: "naming", Pattern: "^[A-Z][a-zA-Z0-9]*$", Description: "Classes use PascalCase"}, {Type: "naming", Pattern: "^[A-Z][A-Z0-9_]*$", Description: "Constants use SCREAMING_SNAKE_CASE"}, }, } jsStandard := &CodingStandard{ Name: "JavaScript Standard", FileTypes: []string{".js", ".jsx", ".ts", ".tsx"}, Description: "JavaScript/TypeScript conventions", Rules: []*ConventionRule{ {Type: "naming", Pattern: "^[a-z][a-zA-Z0-9]*$", Description: "Variables and functions use camelCase"}, {Type: "naming", Pattern: "^[A-Z][a-zA-Z0-9]*$", Description: "Classes and components use PascalCase"}, {Type: "naming", Pattern: "^[A-Z][A-Z0-9_]*$", Description: "Constants use SCREAMING_SNAKE_CASE"}, }, } analyzer.standards["go"] = goStandard analyzer.standards["python"] = pythonStandard analyzer.standards["javascript"] = jsStandard analyzer.standards["typescript"] = jsStandard return analyzer } // NewRelationshipAnalyzer creates a relationship analyzer func NewRelationshipAnalyzer() *RelationshipAnalyzer { analyzer := &RelationshipAnalyzer{ dependencyDetectors: make(map[string]*DependencyDetector), } // Go dependency detector goDetector := &DependencyDetector{ importPatterns: []*regexp.Regexp{ regexp.MustCompile(`import\s+"([^"]+)"`), regexp.MustCompile(`import\s+\w+\s+"([^"]+)"`), }, configFiles: []string{"go.mod", "go.sum"}, } // Python dependency detector pythonDetector := &DependencyDetector{ importPatterns: []*regexp.Regexp{ regexp.MustCompile(`from\s+([^\s]+)\s+import`), regexp.MustCompile(`import\s+([^\s]+)`), }, configFiles: []string{"requirements.txt", "Pipfile", "pyproject.toml", "setup.py"}, } // JavaScript dependency detector jsDetector := &DependencyDetector{ importPatterns: []*regexp.Regexp{ regexp.MustCompile(`import\s+.*from\s+['"]([^'"]+)['"]`), regexp.MustCompile(`require\s*\(\s*['"]([^'"]+)['"]`), }, configFiles: []string{"package.json", "yarn.lock", "package-lock.json"}, } analyzer.dependencyDetectors["go"] = goDetector analyzer.dependencyDetectors["python"] = pythonDetector analyzer.dependencyDetectors["javascript"] = jsDetector analyzer.dependencyDetectors["typescript"] = jsDetector return analyzer } // AnalyzeStructure analyzes directory organization patterns func (da *DefaultDirectoryAnalyzer) AnalyzeStructure(ctx context.Context, dirPath string) (*DirectoryStructure, error) { structure := &DirectoryStructure{ Path: dirPath, FileTypes: make(map[string]int), Languages: make(map[string]int), Dependencies: []string{}, AnalyzedAt: time.Now(), } // Walk the directory tree err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if info.IsDir() { structure.DirectoryCount++ } else { structure.FileCount++ structure.TotalSize += info.Size() // Track file types ext := strings.ToLower(filepath.Ext(path)) if ext != "" { structure.FileTypes[ext]++ // Map extensions to languages if lang := da.mapExtensionToLanguage(ext); lang != "" { structure.Languages[lang]++ } } } return nil }) if err != nil { return nil, fmt.Errorf("failed to walk directory: %w", err) } // Analyze organization patterns orgInfo, err := da.analyzeOrganization(dirPath) if err != nil { orgInfo = &OrganizationInfo{ Pattern: "unknown", Consistency: 0.5, } } structure.Organization = orgInfo // Analyze conventions convInfo, err := da.analyzeConventions(ctx, dirPath) if err != nil { convInfo = &ConventionInfo{ NamingStyle: "mixed", Consistency: 0.5, } } structure.Conventions = convInfo // Determine purpose and architecture structure.Purpose = da.determinePurpose(structure) structure.Architecture = da.determineArchitecture(structure, orgInfo) return structure, nil } // DetectConventions identifies naming and organizational conventions func (da *DefaultDirectoryAnalyzer) DetectConventions(ctx context.Context, dirPath string) (*ConventionAnalysis, error) { analysis := &ConventionAnalysis{ NamingPatterns: []*NamingPattern{}, OrganizationalPatterns: []*OrganizationalPattern{}, Consistency: 0.0, Violations: []*Violation{}, Recommendations: []*Recommendation{}, AppliedStandards: []string{}, AnalyzedAt: time.Now(), } // Collect all files and directories files, dirs, err := da.collectFilesAndDirs(dirPath) if err != nil { return nil, fmt.Errorf("failed to collect files and directories: %w", err) } // Detect naming patterns namingPatterns := da.detectNamingPatterns(files, dirs) analysis.NamingPatterns = namingPatterns // Detect organizational patterns orgPatterns := da.detectOrganizationalPatterns(ctx, dirPath, dirs) analysis.OrganizationalPatterns = orgPatterns // Calculate consistency analysis.Consistency = da.calculateConventionConsistency(files, dirs, namingPatterns) // Find violations violations := da.findConventionViolations(files, dirs, namingPatterns) analysis.Violations = violations // Generate recommendations recommendations := da.generateConventionRecommendations(analysis) analysis.Recommendations = recommendations return analysis, nil } // IdentifyPurpose determines the primary purpose of a directory func (da *DefaultDirectoryAnalyzer) IdentifyPurpose(ctx context.Context, structure *DirectoryStructure) (string, float64, error) { purpose := "General purpose directory" confidence := 0.5 dirName := strings.ToLower(filepath.Base(structure.Path)) // Common directory purposes purposes := map[string]struct { purpose string confidence float64 }{ "src": {"Source code repository", 0.9}, "source": {"Source code repository", 0.9}, "lib": {"Library code", 0.8}, "libs": {"Library code", 0.8}, "vendor": {"Third-party dependencies", 0.9}, "node_modules": {"Node.js dependencies", 0.95}, "build": {"Build artifacts", 0.9}, "dist": {"Distribution files", 0.9}, "bin": {"Binary executables", 0.9}, "test": {"Test code", 0.9}, "tests": {"Test code", 0.9}, "docs": {"Documentation", 0.9}, "doc": {"Documentation", 0.9}, "config": {"Configuration files", 0.9}, "configs": {"Configuration files", 0.9}, "scripts": {"Utility scripts", 0.8}, "tools": {"Development tools", 0.8}, "assets": {"Static assets", 0.8}, "public": {"Public web assets", 0.8}, "static": {"Static files", 0.8}, "templates": {"Template files", 0.8}, "migrations": {"Database migrations", 0.9}, "models": {"Data models", 0.8}, "views": {"View layer", 0.8}, "controllers": {"Controller layer", 0.8}, "services": {"Service layer", 0.8}, "components": {"Reusable components", 0.8}, "modules": {"Modular components", 0.8}, "packages": {"Package organization", 0.7}, "internal": {"Internal implementation", 0.8}, "cmd": {"Command-line applications", 0.9}, "api": {"API implementation", 0.8}, "pkg": {"Go package directory", 0.8}, } if p, exists := purposes[dirName]; exists { purpose = p.purpose confidence = p.confidence } else { // Analyze content to determine purpose if structure.Languages != nil { totalFiles := 0 for _, count := range structure.Languages { totalFiles += count } if totalFiles > 0 { // Determine purpose based on file types if structure.Languages["javascript"] > totalFiles/2 || structure.Languages["typescript"] > totalFiles/2 { purpose = "Frontend application code" confidence = 0.7 } else if structure.Languages["go"] > totalFiles/2 { purpose = "Go application or service" confidence = 0.7 } else if structure.Languages["python"] > totalFiles/2 { purpose = "Python application or library" confidence = 0.7 } else if structure.FileTypes[".html"] > 0 || structure.FileTypes[".css"] > 0 { purpose = "Web frontend resources" confidence = 0.7 } else if structure.FileTypes[".sql"] > 0 { purpose = "Database schema and queries" confidence = 0.8 } } } } return purpose, confidence, nil } // AnalyzeRelationships analyzes relationships between subdirectories func (da *DefaultDirectoryAnalyzer) AnalyzeRelationships(ctx context.Context, dirPath string) (*RelationshipAnalysis, error) { analysis := &RelationshipAnalysis{ Dependencies: []*DirectoryDependency{}, Relationships: []*DirectoryRelation{}, CouplingMetrics: &CouplingMetrics{}, ModularityScore: 0.0, ArchitecturalStyle: "unknown", AnalyzedAt: time.Now(), } // Find subdirectories subdirs, err := da.findSubdirectories(dirPath) if err != nil { return nil, fmt.Errorf("failed to find subdirectories: %w", err) } // Analyze dependencies between directories dependencies, err := da.analyzeDependencies(ctx, subdirs) if err != nil { return nil, fmt.Errorf("failed to analyze dependencies: %w", err) } analysis.Dependencies = dependencies // Analyze relationships relationships := da.analyzeDirectoryRelationships(subdirs, dependencies) analysis.Relationships = relationships // Calculate coupling metrics couplingMetrics := da.calculateCouplingMetrics(subdirs, dependencies) analysis.CouplingMetrics = couplingMetrics // Calculate modularity score analysis.ModularityScore = da.calculateModularityScore(relationships, couplingMetrics) // Determine architectural style analysis.ArchitecturalStyle = da.determineArchitecturalStyle(subdirs, dependencies) return analysis, nil } // GenerateHierarchy generates context hierarchy for directory tree func (da *DefaultDirectoryAnalyzer) GenerateHierarchy(ctx context.Context, rootPath string, maxDepth int) ([]*slurpContext.ContextNode, error) { nodes := []*slurpContext.ContextNode{} err := da.walkDirectoryHierarchy(rootPath, 0, maxDepth, func(path string, depth int) error { // Analyze this directory structure, err := da.AnalyzeStructure(ctx, path) if err != nil { return err } // Generate UCXL address ucxlAddr, err := da.generateUCXLAddress(path) if err != nil { return fmt.Errorf("failed to generate UCXL address: %w", err) } // Determine purpose purpose, purposeConf, err := da.IdentifyPurpose(ctx, structure) if err != nil { purpose = "Directory" purposeConf = 0.5 } // Generate summary summary := da.generateDirectorySummary(structure) // Generate tags tags := da.generateDirectoryTags(structure, path) // Generate technologies list technologies := da.extractTechnologiesFromStructure(structure) // Create context node contextNode := &slurpContext.ContextNode{ Path: path, UCXLAddress: *ucxlAddr, Summary: summary, Purpose: purpose, Technologies: technologies, Tags: tags, Insights: []string{}, OverridesParent: false, ContextSpecificity: da.calculateDirectorySpecificity(structure), AppliesToChildren: depth < maxDepth-1, GeneratedAt: time.Now(), RAGConfidence: purposeConf, EncryptedFor: []string{"*"}, // Default access AccessLevel: slurpContext.AccessLow, Metadata: make(map[string]interface{}), } // Add structure metadata contextNode.Metadata["structure"] = structure contextNode.Metadata["depth"] = depth nodes = append(nodes, contextNode) return nil }) if err != nil { return nil, fmt.Errorf("failed to walk directory hierarchy: %w", err) } return nodes, nil } // Helper methods func (da *DefaultDirectoryAnalyzer) mapExtensionToLanguage(ext string) string { 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", ".kt": "kotlin", ".swift": "swift", } return langMap[ext] } func (da *DefaultDirectoryAnalyzer) analyzeOrganization(dirPath string) (*OrganizationInfo, error) { // Get immediate subdirectories files, err := ioutil.ReadDir(dirPath) if err != nil { return nil, fmt.Errorf("failed to read directory: %w", err) } subdirs := []string{} for _, file := range files { if file.IsDir() { subdirs = append(subdirs, file.Name()) } } // Detect organizational pattern pattern := da.detectOrganizationalPattern(subdirs) // Calculate metrics fanOut := len(subdirs) consistency := da.calculateOrganizationalConsistency(subdirs) return &OrganizationInfo{ Pattern: pattern, Consistency: consistency, Depth: da.calculateMaxDepth(dirPath), FanOut: fanOut, Modularity: da.calculateModularity(subdirs), Cohesion: 0.7, // Default cohesion score Coupling: 0.3, // Default coupling score Metadata: make(map[string]interface{}), }, nil } func (da *DefaultDirectoryAnalyzer) detectOrganizationalPattern(subdirs []string) string { // Check for common patterns subdirSet := make(map[string]bool) for _, dir := range subdirs { subdirSet[strings.ToLower(dir)] = true } // MVC pattern if subdirSet["models"] && subdirSet["views"] && subdirSet["controllers"] { return "MVC" } // Clean Architecture if subdirSet["entities"] && subdirSet["usecases"] && subdirSet["adapters"] { return "Clean Architecture" } // Domain-Driven Design if subdirSet["domain"] && subdirSet["application"] && subdirSet["infrastructure"] { return "Domain-Driven Design" } // Layered architecture if subdirSet["presentation"] && subdirSet["business"] && subdirSet["data"] { return "Layered Architecture" } // Feature-based if len(subdirs) > 3 && da.allAreDomainLike(subdirs) { return "Feature-Based" } // Package by layer (technical) technicalDirs := []string{"api", "service", "repository", "model", "dto", "util"} technicalCount := 0 for _, tech := range technicalDirs { if subdirSet[tech] { technicalCount++ } } if technicalCount >= 3 { return "Package by Layer" } return "Custom" } func (da *DefaultDirectoryAnalyzer) allAreDomainLike(subdirs []string) bool { // Simple heuristic: if directories don't look like technical layers, // they might be domain/feature based technicalTerms := []string{"api", "service", "repository", "model", "dto", "util", "config", "test", "lib"} for _, subdir := range subdirs { lowerDir := strings.ToLower(subdir) for _, term := range technicalTerms { if strings.Contains(lowerDir, term) { return false } } } return true } func (da *DefaultDirectoryAnalyzer) calculateOrganizationalConsistency(subdirs []string) float64 { if len(subdirs) < 2 { return 1.0 } // Simple consistency check: naming convention consistency camelCaseCount := 0 kebabCaseCount := 0 snakeCaseCount := 0 for _, dir := range subdirs { if da.isCamelCase(dir) { camelCaseCount++ } else if da.isKebabCase(dir) { kebabCaseCount++ } else if da.isSnakeCase(dir) { snakeCaseCount++ } } total := len(subdirs) maxConsistent := camelCaseCount if kebabCaseCount > maxConsistent { maxConsistent = kebabCaseCount } if snakeCaseCount > maxConsistent { maxConsistent = snakeCaseCount } return float64(maxConsistent) / float64(total) } func (da *DefaultDirectoryAnalyzer) isCamelCase(s string) bool { matched, _ := regexp.MatchString(`^[a-z][a-zA-Z0-9]*$`, s) return matched } func (da *DefaultDirectoryAnalyzer) isKebabCase(s string) bool { matched, _ := regexp.MatchString(`^[a-z][a-z0-9-]*$`, s) return matched } func (da *DefaultDirectoryAnalyzer) isSnakeCase(s string) bool { matched, _ := regexp.MatchString(`^[a-z][a-z0-9_]*$`, s) return matched } func (da *DefaultDirectoryAnalyzer) calculateMaxDepth(dirPath string) int { maxDepth := 0 filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error { if err != nil { return nil } if info.IsDir() { relativePath, _ := filepath.Rel(dirPath, path) depth := strings.Count(relativePath, string(os.PathSeparator)) if depth > maxDepth { maxDepth = depth } } return nil }) return maxDepth } func (da *DefaultDirectoryAnalyzer) calculateModularity(subdirs []string) float64 { // Simple modularity heuristic based on directory count and naming if len(subdirs) == 0 { return 0.0 } // More subdirectories with clear separation indicates higher modularity if len(subdirs) > 5 { return 0.8 } else if len(subdirs) > 2 { return 0.6 } else { return 0.4 } } func (da *DefaultDirectoryAnalyzer) analyzeConventions(ctx context.Context, dirPath string) (*ConventionInfo, error) { files, err := ioutil.ReadDir(dirPath) if err != nil { return nil, fmt.Errorf("failed to read directory: %w", err) } fileNames := []string{} dirNames := []string{} for _, file := range files { if file.IsDir() { dirNames = append(dirNames, file.Name()) } else { fileNames = append(fileNames, file.Name()) } } // Detect dominant naming style namingStyle := da.detectDominantNamingStyle(append(fileNames, dirNames...)) // Calculate consistency consistency := da.calculateNamingConsistency(append(fileNames, dirNames...), namingStyle) return &ConventionInfo{ NamingStyle: namingStyle, FileNaming: da.detectFileNamingPattern(fileNames), DirectoryNaming: da.detectDirectoryNamingPattern(dirNames), Consistency: consistency, Violations: []*Violation{}, Standards: []string{}, }, nil } func (da *DefaultDirectoryAnalyzer) detectDominantNamingStyle(names []string) string { styles := map[string]int{ "camelCase": 0, "kebab-case": 0, "snake_case": 0, "PascalCase": 0, } for _, name := range names { if da.isCamelCase(name) { styles["camelCase"]++ } else if da.isKebabCase(name) { styles["kebab-case"]++ } else if da.isSnakeCase(name) { styles["snake_case"]++ } else if da.isPascalCase(name) { styles["PascalCase"]++ } } maxCount := 0 dominantStyle := "mixed" for style, count := range styles { if count > maxCount { maxCount = count dominantStyle = style } } return dominantStyle } func (da *DefaultDirectoryAnalyzer) isPascalCase(s string) bool { matched, _ := regexp.MatchString(`^[A-Z][a-zA-Z0-9]*$`, s) return matched } func (da *DefaultDirectoryAnalyzer) detectFileNamingPattern(fileNames []string) string { // Analyze file naming patterns if len(fileNames) == 0 { return "none" } return da.detectDominantNamingStyle(fileNames) } func (da *DefaultDirectoryAnalyzer) detectDirectoryNamingPattern(dirNames []string) string { if len(dirNames) == 0 { return "none" } return da.detectDominantNamingStyle(dirNames) } func (da *DefaultDirectoryAnalyzer) calculateNamingConsistency(names []string, expectedStyle string) float64 { if len(names) == 0 { return 1.0 } consistentCount := 0 for _, name := range names { if da.matchesNamingStyle(name, expectedStyle) { consistentCount++ } } return float64(consistentCount) / float64(len(names)) } func (da *DefaultDirectoryAnalyzer) matchesNamingStyle(name, style string) bool { switch style { case "camelCase": return da.isCamelCase(name) case "kebab-case": return da.isKebabCase(name) case "snake_case": return da.isSnakeCase(name) case "PascalCase": return da.isPascalCase(name) default: return true // Mixed style always matches } } func (da *DefaultDirectoryAnalyzer) determinePurpose(structure *DirectoryStructure) string { // Determine purpose based on directory structure analysis if structure.Languages["javascript"] > 0 || structure.Languages["typescript"] > 0 { if structure.FileTypes[".html"] > 0 || structure.FileTypes[".css"] > 0 { return "Frontend web application" } else { return "JavaScript/TypeScript application" } } if structure.Languages["go"] > 0 { return "Go application or service" } if structure.Languages["python"] > 0 { return "Python application or library" } if structure.Languages["java"] > 0 { return "Java application" } if structure.FileTypes[".md"] > 0 { return "Documentation repository" } return "General purpose directory" } func (da *DefaultDirectoryAnalyzer) determineArchitecture(structure *DirectoryStructure, orgInfo *OrganizationInfo) string { if orgInfo.Pattern != "Custom" && orgInfo.Pattern != "unknown" { return orgInfo.Pattern } // Infer architecture from structure if structure.Languages["go"] > 0 { return "Go service architecture" } if structure.Languages["javascript"] > 0 || structure.Languages["typescript"] > 0 { if structure.FileTypes[".json"] > 0 { return "Node.js application" } else { return "Frontend application" } } return "Unknown architecture" } // Additional helper methods for comprehensive analysis func (da *DefaultDirectoryAnalyzer) collectFilesAndDirs(rootPath string) ([]string, []string, error) { files := []string{} dirs := []string{} err := filepath.Walk(rootPath, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if info.IsDir() { dirs = append(dirs, path) } else { files = append(files, path) } return nil }) return files, dirs, err } func (da *DefaultDirectoryAnalyzer) detectNamingPatterns(files, dirs []string) []*NamingPattern { patterns := []*NamingPattern{} // Analyze file naming patterns filePattern := da.analyzeNamingPattern(files, "file") if filePattern != nil { patterns = append(patterns, filePattern) } // Analyze directory naming patterns dirPattern := da.analyzeNamingPattern(dirs, "directory") if dirPattern != nil { patterns = append(patterns, dirPattern) } return patterns } func (da *DefaultDirectoryAnalyzer) analyzeNamingPattern(paths []string, scope string) *NamingPattern { if len(paths) == 0 { return nil } // Extract just the names names := make([]string, len(paths)) for i, path := range paths { names[i] = filepath.Base(path) } // Detect the dominant convention convention := da.detectDominantNamingStyle(names) return &NamingPattern{ Pattern: Pattern{ ID: fmt.Sprintf("%s_naming", scope), Name: fmt.Sprintf("%s Naming Convention", strings.Title(scope)), Type: "naming", Description: fmt.Sprintf("Naming convention for %ss", scope), Confidence: da.calculateNamingConsistency(names, convention), Examples: names[:min(5, len(names))], }, Convention: convention, Scope: scope, CaseStyle: convention, } } func (da *DefaultDirectoryAnalyzer) detectOrganizationalPatterns(ctx context.Context, rootPath string, dirs []string) []*OrganizationalPattern { patterns := []*OrganizationalPattern{} // Check against known organizational patterns for _, pattern := range da.organizationDetector.commonPatterns { if da.matchesOrganizationalPattern(dirs, pattern) { patterns = append(patterns, pattern) } } return patterns } func (da *DefaultDirectoryAnalyzer) matchesOrganizationalPattern(dirs []string, pattern *OrganizationalPattern) bool { dirSet := make(map[string]bool) for _, dir := range dirs { dirSet[strings.ToLower(filepath.Base(dir))] = true } matchCount := 0 for _, example := range pattern.Examples { exampleName := strings.TrimSuffix(strings.ToLower(example), "/") if dirSet[exampleName] { matchCount++ } } // Require at least half of the examples to match return matchCount >= len(pattern.Examples)/2 } func (da *DefaultDirectoryAnalyzer) calculateConventionConsistency(files, dirs []string, patterns []*NamingPattern) float64 { if len(patterns) == 0 { return 0.5 } totalConsistency := 0.0 for _, pattern := range patterns { totalConsistency += pattern.Confidence } return totalConsistency / float64(len(patterns)) } func (da *DefaultDirectoryAnalyzer) findConventionViolations(files, dirs []string, patterns []*NamingPattern) []*Violation { violations := []*Violation{} // Check for naming violations for _, pattern := range patterns { if pattern.Scope == "file" { for _, file := range files { name := filepath.Base(file) if !da.matchesNamingStyle(name, pattern.Convention) { violations = append(violations, &Violation{ Type: "naming", Path: file, Expected: pattern.Convention, Actual: da.detectNamingStyle(name), Severity: "warning", Suggestion: fmt.Sprintf("Rename to follow %s convention", pattern.Convention), }) } } } else if pattern.Scope == "directory" { for _, dir := range dirs { name := filepath.Base(dir) if !da.matchesNamingStyle(name, pattern.Convention) { violations = append(violations, &Violation{ Type: "naming", Path: dir, Expected: pattern.Convention, Actual: da.detectNamingStyle(name), Severity: "info", Suggestion: fmt.Sprintf("Rename to follow %s convention", pattern.Convention), }) } } } } return violations } func (da *DefaultDirectoryAnalyzer) detectNamingStyle(name string) string { if da.isCamelCase(name) { return "camelCase" } else if da.isKebabCase(name) { return "kebab-case" } else if da.isSnakeCase(name) { return "snake_case" } else if da.isPascalCase(name) { return "PascalCase" } return "unknown" } func (da *DefaultDirectoryAnalyzer) generateConventionRecommendations(analysis *ConventionAnalysis) []*Recommendation { recommendations := []*Recommendation{} // Recommend consistency improvements if analysis.Consistency < 0.8 { recommendations = append(recommendations, &Recommendation{ Type: "consistency", Title: "Improve naming consistency", Description: "Consider standardizing naming conventions across the project", Priority: 2, Effort: "medium", Impact: "high", Steps: []string{"Choose a consistent naming style", "Rename files/directories", "Update style guide"}, }) } // Recommend architectural improvements if len(analysis.OrganizationalPatterns) == 0 { recommendations = append(recommendations, &Recommendation{ Type: "architecture", Title: "Consider architectural patterns", Description: "Project structure could benefit from established architectural patterns", Priority: 3, Effort: "high", Impact: "high", Steps: []string{"Evaluate current structure", "Choose appropriate pattern", "Refactor gradually"}, }) } return recommendations } // More helper methods for relationship analysis func (da *DefaultDirectoryAnalyzer) findSubdirectories(dirPath string) ([]string, error) { files, err := ioutil.ReadDir(dirPath) if err != nil { return nil, err } subdirs := []string{} for _, file := range files { if file.IsDir() { subdirs = append(subdirs, filepath.Join(dirPath, file.Name())) } } return subdirs, nil } func (da *DefaultDirectoryAnalyzer) analyzeDependencies(ctx context.Context, subdirs []string) ([]*DirectoryDependency, error) { dependencies := []*DirectoryDependency{} for _, dir := range subdirs { deps, err := da.findDirectoryDependencies(ctx, dir, subdirs) if err != nil { continue // Skip directories we can't analyze } dependencies = append(dependencies, deps...) } return dependencies, nil } func (da *DefaultDirectoryAnalyzer) findDirectoryDependencies(ctx context.Context, dir string, allDirs []string) ([]*DirectoryDependency, error) { dependencies := []*DirectoryDependency{} // Walk through files in the directory err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { if err != nil || info.IsDir() { return nil } // Read file content to find imports content, err := ioutil.ReadFile(path) if err != nil { return nil } // Detect language and find imports ext := strings.ToLower(filepath.Ext(path)) language := da.mapExtensionToLanguage(ext) if detector, exists := da.relationshipAnalyzer.dependencyDetectors[language]; exists { imports := da.extractImports(string(content), detector.importPatterns) // Check which imports refer to other directories for _, imp := range imports { for _, otherDir := range allDirs { if otherDir != dir && da.isLocalDependency(imp, dir, otherDir) { dependencies = append(dependencies, &DirectoryDependency{ From: dir, To: otherDir, Type: "import", Strength: 1.0, Reason: fmt.Sprintf("Import: %s", imp), }) } } } } return nil }) return dependencies, err } func (da *DefaultDirectoryAnalyzer) extractImports(content string, patterns []*regexp.Regexp) []string { imports := []string{} for _, pattern := range patterns { matches := pattern.FindAllStringSubmatch(content, -1) for _, match := range matches { if len(match) > 1 { imports = append(imports, match[1]) } } } return imports } func (da *DefaultDirectoryAnalyzer) isLocalDependency(importPath, fromDir, toDir string) bool { // Simple heuristic: check if import path references the target directory fromBase := filepath.Base(fromDir) toBase := filepath.Base(toDir) return strings.Contains(importPath, toBase) || strings.Contains(importPath, "../"+toBase) || strings.Contains(importPath, "./"+toBase) } func (da *DefaultDirectoryAnalyzer) analyzeDirectoryRelationships(subdirs []string, dependencies []*DirectoryDependency) []*DirectoryRelation { relationships := []*DirectoryRelation{} // Create relationship map depMap := make(map[string]map[string]int) for _, dep := range dependencies { if depMap[dep.From] == nil { depMap[dep.From] = make(map[string]int) } depMap[dep.From][dep.To]++ } // Create bidirectional relationships processed := make(map[string]bool) for _, dir1 := range subdirs { for _, dir2 := range subdirs { if dir1 >= dir2 { continue } key := dir1 + ":" + dir2 if processed[key] { continue } processed[key] = true // Check for relationships deps1to2 := depMap[dir1][dir2] deps2to1 := depMap[dir2][dir1] if deps1to2 > 0 || deps2to1 > 0 { relType := "depends" strength := float64(deps1to2 + deps2to1) bidirectional := deps1to2 > 0 && deps2to1 > 0 if bidirectional { relType = "mutual" } relationships = append(relationships, &DirectoryRelation{ Directory1: dir1, Directory2: dir2, Type: relType, Strength: strength, Description: fmt.Sprintf("%d dependencies between directories", deps1to2+deps2to1), Bidirectional: bidirectional, }) } } } return relationships } func (da *DefaultDirectoryAnalyzer) calculateCouplingMetrics(subdirs []string, dependencies []*DirectoryDependency) *CouplingMetrics { if len(subdirs) == 0 { return &CouplingMetrics{} } // Calculate afferent and efferent coupling afferent := make(map[string]int) efferent := make(map[string]int) for _, dep := range dependencies { efferent[dep.From]++ afferent[dep.To]++ } // Calculate averages totalAfferent := 0 totalEfferent := 0 for _, dir := range subdirs { totalAfferent += afferent[dir] totalEfferent += efferent[dir] } avgAfferent := float64(totalAfferent) / float64(len(subdirs)) avgEfferent := float64(totalEfferent) / float64(len(subdirs)) // Calculate instability (efferent / (afferent + efferent)) instability := 0.0 if avgAfferent+avgEfferent > 0 { instability = avgEfferent / (avgAfferent + avgEfferent) } return &CouplingMetrics{ AfferentCoupling: avgAfferent, EfferentCoupling: avgEfferent, Instability: instability, Abstractness: 0.5, // Would need more analysis to determine DistanceFromMain: 0.0, // Would need to calculate distance from main sequence } } func (da *DefaultDirectoryAnalyzer) calculateModularityScore(relationships []*DirectoryRelation, coupling *CouplingMetrics) float64 { // Simple modularity score based on coupling metrics if coupling.Instability < 0.3 { return 0.8 // High modularity } else if coupling.Instability < 0.7 { return 0.6 // Medium modularity } else { return 0.4 // Low modularity } } func (da *DefaultDirectoryAnalyzer) determineArchitecturalStyle(subdirs []string, dependencies []*DirectoryDependency) string { if len(subdirs) == 0 { return "unknown" } // Analyze dependency patterns if len(dependencies) == 0 { return "independent" } // Check for layered architecture (unidirectional dependencies) bidirectionalCount := 0 for _, dep := range dependencies { // Check if there's a reverse dependency for _, otherDep := range dependencies { if dep.From == otherDep.To && dep.To == otherDep.From { bidirectionalCount++ break } } } if float64(bidirectionalCount)/float64(len(dependencies)) < 0.2 { return "layered" } else { return "interconnected" } } // Additional utility methods func (da *DefaultDirectoryAnalyzer) walkDirectoryHierarchy(rootPath string, currentDepth, maxDepth int, fn func(string, int) error) error { if currentDepth > maxDepth { return nil } // Process current directory if err := fn(rootPath, currentDepth); err != nil { return err } // Process subdirectories files, err := ioutil.ReadDir(rootPath) if err != nil { return err } for _, file := range files { if file.IsDir() && !strings.HasPrefix(file.Name(), ".") { subPath := filepath.Join(rootPath, file.Name()) if err := da.walkDirectoryHierarchy(subPath, currentDepth+1, maxDepth, fn); err != nil { return err } } } return nil } func (da *DefaultDirectoryAnalyzer) generateUCXLAddress(path string) (*ucxl.Address, error) { cleanPath := filepath.Clean(path) addr, err := ucxl.ParseAddress(fmt.Sprintf("dir://%s", cleanPath)) if err != nil { return nil, fmt.Errorf("failed to generate UCXL address: %w", err) } return addr, nil } func (da *DefaultDirectoryAnalyzer) generateDirectorySummary(structure *DirectoryStructure) string { summary := fmt.Sprintf("Directory with %d files and %d subdirectories", structure.FileCount, structure.DirectoryCount) // Add language information if len(structure.Languages) > 0 { var langs []string for lang, count := range structure.Languages { langs = append(langs, fmt.Sprintf("%s (%d)", lang, count)) } sort.Strings(langs) summary += fmt.Sprintf(", containing: %s", strings.Join(langs[:min(3, len(langs))], ", ")) } return summary } func (da *DefaultDirectoryAnalyzer) generateDirectoryTags(structure *DirectoryStructure, path string) []string { tags := []string{} // Add directory name as tag dirName := filepath.Base(path) if dirName != "." && dirName != "/" { tags = append(tags, "dir:"+dirName) } // Add language tags for lang := range structure.Languages { tags = append(tags, lang) } // Add size category if structure.FileCount > 100 { tags = append(tags, "large-project") } else if structure.FileCount > 20 { tags = append(tags, "medium-project") } else { tags = append(tags, "small-project") } // Add architecture tags if structure.Architecture != "unknown" && structure.Architecture != "" { tags = append(tags, strings.ToLower(strings.ReplaceAll(structure.Architecture, " ", "-"))) } return tags } func (da *DefaultDirectoryAnalyzer) extractTechnologiesFromStructure(structure *DirectoryStructure) []string { technologies := []string{} // Add languages as technologies for lang := range structure.Languages { technologies = append(technologies, lang) } // Add framework detection based on file types and structure if structure.FileTypes[".json"] > 0 && (structure.Languages["javascript"] > 0 || structure.Languages["typescript"] > 0) { technologies = append(technologies, "Node.js") } if structure.FileTypes[".py"] > 0 && structure.FileTypes[".txt"] > 0 { technologies = append(technologies, "Python") } return technologies } func (da *DefaultDirectoryAnalyzer) calculateDirectorySpecificity(structure *DirectoryStructure) int { specificity := 1 // More specific if it has many files if structure.FileCount > 50 { specificity += 2 } else if structure.FileCount > 10 { specificity += 1 } // More specific if it uses specific technologies if len(structure.Languages) > 2 { specificity += 1 } // More specific if it has clear purpose if structure.Purpose != "General purpose directory" { specificity += 1 } return specificity } func min(a, b int) int { if a < b { return a } return b }