-
Notifications
You must be signed in to change notification settings - Fork 0
Fix topological sort violating hook dependencies by using Kahn's algorithm with priority tie-breaking #191
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…ng DFS Co-authored-by: twisti-dev <76837088+twisti-dev@users.noreply.github.com>
Co-authored-by: twisti-dev <76837088+twisti-dev@users.noreply.github.com>
|
@copilot There is already a topological sort implemented under dev/slne/surf/surfapi/core/api/algorithms/kahn-topological-sort.kt maybe you can use that |
Co-authored-by: twisti-dev <76837088+twisti-dev@users.noreply.github.com>
Refactored to use Kahn's algorithm with a PriorityQueue for priority-based tie-breaking in commit 0d30fd4. The implementation follows the same pattern as the existing topological sort in |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR refactors the hook topological sort to fix a critical bug where dependencies were violated after sorting. The old DFS implementation correctly computed a topological order but then re-sorted by priority, breaking the dependency constraints.
Changes:
- Replaced DFS-based topological sort with Kahn's algorithm using a priority queue for tie-breaking
- Added cycle detection using graph traversal to identify circular dependencies
- Updated to use fastutil data structures consistent with existing codebase patterns
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| for (depClassName in meta.hookDependencies) { | ||
| if (depClassName in hooksByClassName) { | ||
| graph.computeIfAbsent(depClassName) { mutableListOf() }.add(meta.className) | ||
| } | ||
| } |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The condition if (depClassName in hooksByClassName) checks against all hooks, but should check against validHooks only. If a dependency exists in hooksByClassName but was filtered out of validHooks (due to missing dependencies), it will create a graph node that doesn't correspond to a valid hook. This causes the algorithm to fail because the graph will have nodes that don't represent actual processable hooks. The check should ensure the dependency is in the validHooks set.
| // Trace back through dependencies to find the cycle | ||
| val visited = mutableSetOf<String>() | ||
| val chain = mutableListOf<String>() | ||
| var current = startClassName | ||
| var current = cycleNode | ||
|
|
||
| while (current !in chain) { | ||
| while (current !in visited) { | ||
| visited.add(current) | ||
| chain.add(current) | ||
| val deps = metaByClassName[current]?.hookDependencies ?: break | ||
| current = deps.firstOrNull { it in visiting } ?: break | ||
| // Find a successor that still has incoming edges (part of cycle) | ||
| current = graph[current]?.firstOrNull { incomingEdges[it] ?: 0 > 0 } ?: break | ||
| } |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The cycle detection logic is incorrect. The graph represents edges from dependencies to dependents (A -> B means B depends on A). To trace a cycle, we need to follow backwards through the dependency chain (from dependent to dependency), but this code follows successors (dependents) instead. This means the function won't correctly identify the actual cycle path. The algorithm needs to either build a reverse graph or use a different approach (like DFS) to correctly trace dependencies and find the cycle.
The
topologicalSortfunction computed a valid dependency-respecting order via DFS, but then re-sorted the result by priority, breaking@DependsOnHookconstraints.Changes
kahn-topological-sort.ktin the codebasePriorityQueueinstead ofArrayDequeto naturally handle priority-based ordering when multiple hooks have no dependenciesImplementation
The refactored implementation:
Object2IntMapPriorityQueueordered by hook priority (lower value = higher priority)Example
Before this fix, with hooks:
The topological sort would produce
[A, C]correctly, but the final re-sort would produce[C, A], causing C to run before its dependency A.After this fix, the result is
[A, C]- dependencies always run before dependents, with priority acting as a tie-breaker for independent hooks.Benefits
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.