Skip to content

fix: make generated outputs deterministic#12

Merged
thegdsks merged 2 commits intoglincker:masterfrom
aayushprsingh:fix/deterministic-output-writes
Apr 12, 2026
Merged

fix: make generated outputs deterministic#12
thegdsks merged 2 commits intoglincker:masterfrom
aayushprsingh:fix/deterministic-output-writes

Conversation

@aayushprsingh
Copy link
Copy Markdown
Contributor

Summary

  • preserve generated_at when the semantic index content is unchanged
  • stabilize capped ype_defs selection by sorting keys before trimming
  • write JSON, Mermaid, and HTML outputs only when bytes actually changed
  • stabilize Mermaid node/class ordering

Why

Repeated stacklit generate runs on unchanged source were still rewriting generated artifacts. On Windows this increases noisy git churn and makes file-lock issues more likely when outputs are open in another process.

This patch makes repeated generation stable when source content has not changed.

Validation

  • go test ./...
  • repeated go run ./cmd/stacklit generate produced stable stacklit.json
  • repeated go run ./cmd/stacklit generate produced stable DEPENDENCIES.md

Copy link
Copy Markdown
Member

@thegdsks thegdsks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid work — sorting map keys before trimming and the write-only-when-changed pattern are both the right calls. The stable.go helper for preserving generated_at is a nice touch too.

Since this includes the path normalization from #11 as well, I'll merge this one and close #11. Thanks for the contribution!

@thegdsks
Copy link
Copy Markdown
Member

Looks good. Sorting map keys before trimming was needed, and skipping writes on unchanged content is a smart call. The stable.go approach for generated_at is clean too.

Since this covers the path changes from #11 as well, I'll go ahead and merge this one. Appreciate the PR!

Copy link
Copy Markdown
Contributor

Copilot AI left a 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 reduces noisy churn from repeated stacklit generate runs by making generation outputs more deterministic and by skipping writes when the output bytes are unchanged, improving stability especially on Windows.

Changes:

  • Normalize walked file paths to forward slashes for OS-stable ignore matching and generated paths.
  • Stabilize ordering in generated artifacts (Mermaid node/class ordering; deterministic trimming of capped TypeDefs).
  • Avoid rewriting JSON/Mermaid/HTML outputs when the generated bytes match what’s already on disk; preserve generated_at when semantic index content is unchanged.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
internal/walker/walker.go Normalizes relative paths with filepath.ToSlash for cross-OS stability.
internal/walker/walker_test.go Updates expectations to match forward-slash normalized paths.
internal/renderer/stable.go Adds helper to preserve generated_at when the index content is unchanged.
internal/renderer/mermaid.go Makes Mermaid output deterministic and skips writing when unchanged.
internal/renderer/json.go Preserves generated_at when content is unchanged and skips writing when unchanged.
internal/renderer/html.go Skips writing HTML when unchanged.
internal/engine/engine.go Uses config-driven scan ignore list; deterministic TypeDefs trimming.
internal/config/config.go Adds ScanIgnore() to include outputs in ignore patterns.
internal/config/config_test.go Adds coverage ensuring output paths are included (and normalized) in ScanIgnore().
internal/cli/diff.go Uses config-driven scan ignore list to exclude generated outputs from diffs.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +36 to 39
// 2. Walk current source files, excluding Stacklit's own generated outputs.
cfg := config.Load(".")
files, err := walker.Walk(".", cfg.ScanIgnore())
if err != nil {
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The diff command now loads config to compute scan ignore patterns, but it still reads the index from a hard-coded stacklit.json. If a user overrides output.json in .stacklitrc.json, stacklit diff will read the wrong file or fail. Consider using cfg.Output.JSON (joined to the same root as the scan) for the initial read and error message to keep diff consistent with configured outputs.

Copilot uses AI. Check for mistakes.
Comment on lines 17 to +31
func WriteJSON(idx *schema.Index, path string) error {
idx.Schema = schemaURL
idx.GeneratedAt = time.Now().UTC().Format(time.RFC3339)
idx.StacklitVersion = version
preserveGeneratedAtIfUnchanged(idx, path)

data, err := json.MarshalIndent(idx, "", " ")
if err != nil {
return err
}

if existing, err := os.ReadFile(path); err == nil && bytes.Equal(existing, data) {
return nil
}

Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

preserveGeneratedAtIfUnchanged changes WriteJSON semantics by keeping the previous generated_at (and potentially skipping the write entirely). There’s existing test coverage for WriteJSON, but it doesn’t assert this new determinism behavior. Consider adding a test that writes an index twice with unchanged content and verifies (1) generated_at remains the same and (2) the output bytes are unchanged between runs (or that file mtime doesn’t change).

Copilot uses AI. Check for mistakes.
@thegdsks thegdsks merged commit 7eb4382 into glincker:master Apr 12, 2026
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants