diff --git a/internal/cli/diff.go b/internal/cli/diff.go index 3866096..b5c781a 100644 --- a/internal/cli/diff.go +++ b/internal/cli/diff.go @@ -5,6 +5,7 @@ import ( "fmt" "os" + "github.com/glincker/stacklit/internal/config" "github.com/glincker/stacklit/internal/git" "github.com/glincker/stacklit/internal/schema" "github.com/glincker/stacklit/internal/walker" @@ -32,8 +33,9 @@ func newDiffCmd() *cobra.Command { return fmt.Errorf("stacklit.json has no merkle_hash; run 'stacklit generate' to rebuild") } - // 2. Walk current source files - files, err := walker.Walk(".", nil) + // 2. Walk current source files, excluding Stacklit's own generated outputs. + cfg := config.Load(".") + files, err := walker.Walk(".", cfg.ScanIgnore()) if err != nil { return fmt.Errorf("failed to walk source files: %w", err) } diff --git a/internal/config/config.go b/internal/config/config.go index e32b340..3237864 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -72,3 +72,16 @@ func Load(root string) *Config { return cfg } + +// ScanIgnore returns ignore patterns plus Stacklit output files so generated artifacts +// never feed back into the next scan. +func (c *Config) ScanIgnore() []string { + ignore := append([]string{}, c.Ignore...) + for _, out := range []string{c.Output.JSON, c.Output.Mermaid, c.Output.HTML} { + if out == "" { + continue + } + ignore = append(ignore, filepath.ToSlash(out)) + } + return ignore +} diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 84ee17d..d242615 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -54,3 +54,22 @@ func TestLoadMalformed(t *testing.T) { t.Errorf("expected default max_depth=4 after malformed file, got %d", cfg.MaxDepth) } } + +func TestScanIgnoreIncludesOutputs(t *testing.T) { + cfg := DefaultConfig() + cfg.Ignore = []string{"custom/"} + cfg.Output.JSON = "out\\stacklit.json" + cfg.Output.Mermaid = "docs\\DEPENDENCIES.md" + cfg.Output.HTML = "stacklit.html" + + got := cfg.ScanIgnore() + want := []string{"custom/", "out/stacklit.json", "docs/DEPENDENCIES.md", "stacklit.html"} + if len(got) != len(want) { + t.Fatalf("expected %d ignore patterns, got %d: %v", len(want), len(got), got) + } + for i := range want { + if got[i] != want[i] { + t.Fatalf("expected ignore[%d]=%q, got %q (all=%v)", i, want[i], got[i], got) + } + } +} diff --git a/internal/engine/engine.go b/internal/engine/engine.go index a6ec071..6cfee88 100644 --- a/internal/engine/engine.go +++ b/internal/engine/engine.go @@ -166,7 +166,7 @@ func Run(opts Options) (*Result, error) { } // 3. Walk the filesystem, honouring extra ignore patterns from config. - files, err := walker.Walk(root, cfg.Ignore) + files, err := walker.Walk(root, cfg.ScanIgnore()) if err != nil { return nil, fmt.Errorf("walking %s: %w", root, err) } diff --git a/internal/walker/walker.go b/internal/walker/walker.go index ef1e193..229266c 100644 --- a/internal/walker/walker.go +++ b/internal/walker/walker.go @@ -99,6 +99,9 @@ func Walk(root string, extraIgnore []string) ([]string, error) { return relErr } + // Normalize path separators so generated output and ignore matching stay stable across OSes. + rel = filepath.ToSlash(rel) + // Skip the root itself. if rel == "." { return nil diff --git a/internal/walker/walker_test.go b/internal/walker/walker_test.go index 17d062f..35115ca 100644 --- a/internal/walker/walker_test.go +++ b/internal/walker/walker_test.go @@ -35,8 +35,8 @@ func TestWalkSourceFiles(t *testing.T) { // Must include Go source files. wantIncluded := []string{ "main.go", - filepath.Join("internal", "handler.go"), - filepath.Join("internal", "handler_test.go"), + filepath.ToSlash(filepath.Join("internal", "handler.go")), + filepath.ToSlash(filepath.Join("internal", "handler_test.go")), } for _, want := range wantIncluded { if !fileSet[want] { @@ -46,8 +46,8 @@ func TestWalkSourceFiles(t *testing.T) { // Must exclude gitignored directories. wantExcluded := []string{ - filepath.Join("vendor", "lib.go"), - filepath.Join("node_modules", "pkg.js"), + filepath.ToSlash(filepath.Join("vendor", "lib.go")), + filepath.ToSlash(filepath.Join("node_modules", "pkg.js")), } for _, exclude := range wantExcluded { if fileSet[exclude] { @@ -124,7 +124,7 @@ func TestWalkAlwaysIgnoresDirs(t *testing.T) { } for _, d := range ignoredDirs { - p := filepath.Join(d, "file.go") + p := filepath.ToSlash(filepath.Join(d, "file.go")) if fileSet[p] { t.Errorf("expected %q inside always-ignore dir to be excluded", p) }