From 01a71ac33698704c4a2e603ff19b66d0eb23f5e0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Feb 2026 16:29:34 +0000 Subject: [PATCH 1/3] Initial plan From c11486a183c9a18027711eac9adde04a99d84bcf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Feb 2026 16:34:12 +0000 Subject: [PATCH 2/3] Implement stable topological sort for imports and add comprehensive tests Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/parser/import_processor.go | 10 +- pkg/parser/import_topological_test.go | 167 ++++++++++++++++++++++++++ 2 files changed, 176 insertions(+), 1 deletion(-) diff --git a/pkg/parser/import_processor.go b/pkg/parser/import_processor.go index cc16c650de..2f71caea7b 100644 --- a/pkg/parser/import_processor.go +++ b/pkg/parser/import_processor.go @@ -902,7 +902,15 @@ func topologicalSortImports(imports []string, baseDir string, cache *ImportCache importLog.Printf("Processing import %s (in-degree was 0)", current) // For each import that depends on the current import, reduce its in-degree - for imp, deps := range dependencies { + // Iterate over dependencies in sorted order for stable results + sortedImports := make([]string, 0, len(dependencies)) + for imp := range dependencies { + sortedImports = append(sortedImports, imp) + } + sort.Strings(sortedImports) + + for _, imp := range sortedImports { + deps := dependencies[imp] for _, dep := range deps { if dep == current && allImportsSet[imp] { inDegree[imp]-- diff --git a/pkg/parser/import_topological_test.go b/pkg/parser/import_topological_test.go index ff10690a80..c38c0b9fb2 100644 --- a/pkg/parser/import_topological_test.go +++ b/pkg/parser/import_topological_test.go @@ -128,6 +128,173 @@ tools: // Key constraints: f before c, c and d before a, e before b expectedOrder: []string{"d.md", "e.md", "b.md", "f.md", "c.md", "a.md"}, }, + { + name: "wide tree with many independent branches", + files: map[string]string{ + "a.md": `--- +imports: + - d.md +tools: + tool-a: {} +---`, + "b.md": `--- +imports: + - e.md +tools: + tool-b: {} +---`, + "c.md": `--- +imports: + - f.md +tools: + tool-c: {} +---`, + "d.md": `--- +tools: + tool-d: {} +---`, + "e.md": `--- +tools: + tool-e: {} +---`, + "f.md": `--- +tools: + tool-f: {} +---`, + }, + mainImports: []string{"a.md", "b.md", "c.md"}, + // Each dependency is processed as soon as it's ready: + // d (root) -> a (d's dependent), e (root) -> b (e's dependent), f (root) -> c (f's dependent) + // Alphabetical order within same level + expectedOrder: []string{"d.md", "a.md", "e.md", "b.md", "f.md", "c.md"}, + }, + { + name: "reverse alphabetical with dependencies", + files: map[string]string{ + "z-parent.md": `--- +imports: + - a-child.md +tools: + tool-z: {} +---`, + "y-parent.md": `--- +imports: + - b-child.md +tools: + tool-y: {} +---`, + "a-child.md": `--- +tools: + tool-a: {} +---`, + "b-child.md": `--- +tools: + tool-b: {} +---`, + }, + mainImports: []string{"z-parent.md", "y-parent.md"}, + // Children come first in alphabetical order (a, b), + // then parents in alphabetical order (y, z) + expectedOrder: []string{"a-child.md", "b-child.md", "y-parent.md", "z-parent.md"}, + }, + { + name: "multi-level dependency chain", + files: map[string]string{ + "level-0.md": `--- +imports: + - level-1.md +tools: + tool-0: {} +---`, + "level-1.md": `--- +imports: + - level-2.md +tools: + tool-1: {} +---`, + "level-2.md": `--- +imports: + - level-3.md +tools: + tool-2: {} +---`, + "level-3.md": `--- +imports: + - level-4.md +tools: + tool-3: {} +---`, + "level-4.md": `--- +tools: + tool-4: {} +---`, + }, + mainImports: []string{"level-0.md"}, + expectedOrder: []string{"level-4.md", "level-3.md", "level-2.md", "level-1.md", "level-0.md"}, + }, + { + name: "parallel branches with shared dependency", + files: map[string]string{ + "branch-a-top.md": `--- +imports: + - branch-a-mid.md +tools: + tool-a-top: {} +---`, + "branch-a-mid.md": `--- +imports: + - shared-base.md +tools: + tool-a-mid: {} +---`, + "branch-b-top.md": `--- +imports: + - branch-b-mid.md +tools: + tool-b-top: {} +---`, + "branch-b-mid.md": `--- +imports: + - shared-base.md +tools: + tool-b-mid: {} +---`, + "shared-base.md": `--- +tools: + tool-shared: {} +---`, + }, + mainImports: []string{"branch-a-top.md", "branch-b-top.md"}, + // shared-base (root) -> branch-a-mid (becomes ready) -> branch-a-top (becomes ready) + // -> branch-b-mid (becomes ready) -> branch-b-top (becomes ready) + // Alphabetical order when multiple items are at the same level + expectedOrder: []string{"shared-base.md", "branch-a-mid.md", "branch-a-top.md", "branch-b-mid.md", "branch-b-top.md"}, + }, + { + name: "mixed naming with special characters", + files: map[string]string{ + "01-first.md": `--- +imports: + - 99-last.md +tools: + tool-first: {} +---`, + "02-second.md": `--- +imports: + - 99-last.md +tools: + tool-second: {} +---`, + "99-last.md": `--- +tools: + tool-last: {} +---`, + }, + mainImports: []string{"01-first.md", "02-second.md"}, + // 99-last is shared dependency, comes first + // Then dependents in alphabetical order + expectedOrder: []string{"99-last.md", "01-first.md", "02-second.md"}, + }, } for _, tt := range tests { From ba27ddd9b920cee2df159b6b662fad83556b6256 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Feb 2026 16:40:28 +0000 Subject: [PATCH 3/3] Fix remaining map iteration in in-degree calculation for complete stability Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/parser/import_processor.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pkg/parser/import_processor.go b/pkg/parser/import_processor.go index 2f71caea7b..ac4f1c74e2 100644 --- a/pkg/parser/import_processor.go +++ b/pkg/parser/import_processor.go @@ -868,7 +868,15 @@ func topologicalSortImports(imports []string, baseDir string, cache *ImportCache } // Count dependencies: how many imports does each file depend on (within our import set) - for imp, deps := range dependencies { + // Iterate over imports in sorted order for stable results + sortedImportsForDegree := make([]string, 0, len(dependencies)) + for imp := range dependencies { + sortedImportsForDegree = append(sortedImportsForDegree, imp) + } + sort.Strings(sortedImportsForDegree) + + for _, imp := range sortedImportsForDegree { + deps := dependencies[imp] for _, dep := range deps { // Only count dependencies that are in our import set if allImportsSet[dep] {