diff --git a/CHANGELOG.md b/CHANGELOG.md index 323cf3b7a..9e71da6f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- **A new directory of files now counts as the files it holds, not as one.** The + changed-file count came from `git status --porcelain`, which reports an + untracked directory as a single `?? pkg/` entry — so an agent writing 25 files + into fresh packages showed 4 changed files in the footer and on the session + card, while the Review tab listed all of them. The count now asks git for + every untracked file; the line totals were already right. - **A model newer than your lich no longer reads its context window as 200k.** Opus 5 sessions showed five times their real usage in the footer, because the window came from a table of the 1M models and anything missing from it fell diff --git a/internal/project/project.go b/internal/project/project.go index c0915ca62..469cfa793 100644 --- a/internal/project/project.go +++ b/internal/project/project.go @@ -169,7 +169,10 @@ type DiffStats struct { // yields the zero value, matching Branch's contract. func (s *Service) Diff(path string) DiffStats { var stats DiffStats - if out, err := command("git", "-C", path, "status", "--porcelain").Output(); err == nil { + // --untracked-files=all, because the default collapses a new directory into + // one "?? dir/" line: an agent writing 25 files into fresh packages would + // count as one changed file. + if out, err := command("git", "-C", path, "status", "--porcelain", "--untracked-files=all").Output(); err == nil { stats.Files = countLines(out) } // A repository without commits has no HEAD; diff against git's empty tree, diff --git a/internal/project/project_test.go b/internal/project/project_test.go index 8fb35c35b..518ad8559 100644 --- a/internal/project/project_test.go +++ b/internal/project/project_test.go @@ -170,6 +170,21 @@ func TestDiff(t *testing.T) { t.Errorf("Diff(untracked binary).Added = %d, want 5", got.Added) } + // Untracked files inside a new directory count one by one: git's default + // porcelain collapses them into a single "?? pkg/" entry, which would report + // a whole new package as one changed file. + if err := os.MkdirAll(filepath.Join(repo, "pkg", "deep"), 0o755); err != nil { + t.Fatal(err) + } + for _, name := range []string{"one.txt", "two.txt"} { + if err := os.WriteFile(filepath.Join(repo, "pkg", "deep", name), []byte("l\n"), 0o644); err != nil { + t.Fatal(err) + } + } + if got := svc.Diff(repo); got.Files != 5 || got.Added != 7 { + t.Errorf("Diff(new directory) = %+v, want {Files:5 Added:7 Deleted:1}", got) + } + if got := svc.Diff(t.TempDir()); got != (DiffStats{}) { t.Errorf("Diff(non-repo) = %+v, want zero", got) }