···107107 }
108108109109 seen := make(map[string]struct{})
110110- var modules []string
110110+ modules := make([]string, 0)
111111112112 for _, file := range changedFiles {
113113 // Get the directory of the changed file
+1-2
controller/activities/git_test.go
···11package activities
2233import (
44- "context"
54 "os"
65 "path/filepath"
76 "reflect"
···133132// Helper function to simulate ChangedModules logic without Git
134133func getChangedModulesFromFiles(repoPath string, changedFiles []string) []string {
135134 seen := make(map[string]struct{})
136136- var modules []string
135135+ modules := make([]string, 0) // Initialize as empty slice instead of nil
137136138137 for _, file := range changedFiles {
139138 // Get the directory of the changed file
+70
controller/activities/graph.go
···137137 b.WriteString("}")
138138 return b.String()
139139}
140140+141141+// TopologicalSort returns modules grouped by dependency levels.
142142+// Modules in the same level can be executed in parallel.
143143+// Each level must complete before the next level can start.
144144+// An edge from A to B means A depends on B, so B must run before A.
145145+func (g *Graph) TopologicalSort() [][]string {
146146+ // Build adjacency list and in-degree count
147147+ adjList := make(map[string][]string)
148148+ inDegree := make(map[string]int)
149149+150150+ // Initialize all nodes with in-degree 0
151151+ for _, node := range g.Nodes {
152152+ inDegree[node.Name] = 0
153153+ adjList[node.Name] = []string{}
154154+ }
155155+156156+ // Build the graph and calculate in-degrees
157157+ // Edge from Src to Dest means Src depends on Dest
158158+ // So Dest should run before Src
159159+ for _, edge := range g.Edges {
160160+ adjList[edge.Dest] = append(adjList[edge.Dest], edge.Src)
161161+ inDegree[edge.Src]++
162162+ }
163163+164164+ var levels [][]string
165165+ remaining := make(map[string]bool)
166166+ for _, node := range g.Nodes {
167167+ remaining[node.Name] = true
168168+ }
169169+170170+ // Process nodes level by level
171171+ for len(remaining) > 0 {
172172+ var currentLevel []string
173173+174174+ // Find all nodes with in-degree 0 (no dependencies)
175175+ for nodeName := range remaining {
176176+ if inDegree[nodeName] == 0 {
177177+ currentLevel = append(currentLevel, nodeName)
178178+ }
179179+ }
180180+181181+ // If no nodes found with in-degree 0, there's a cycle
182182+ if len(currentLevel) == 0 {
183183+ // Return remaining nodes as the final level to handle cycles gracefully
184184+ var cycleNodes []string
185185+ for nodeName := range remaining {
186186+ cycleNodes = append(cycleNodes, nodeName)
187187+ }
188188+ if len(cycleNodes) > 0 {
189189+ levels = append(levels, cycleNodes)
190190+ }
191191+ break
192192+ }
193193+194194+ // Add current level
195195+ levels = append(levels, currentLevel)
196196+197197+ // Remove processed nodes and update in-degrees
198198+ for _, nodeName := range currentLevel {
199199+ delete(remaining, nodeName)
200200+ for _, dependent := range adjList[nodeName] {
201201+ if remaining[dependent] {
202202+ inDegree[dependent]--
203203+ }
204204+ }
205205+ }
206206+ }
207207+208208+ return levels
209209+}