BUG-043
Setting up your dashboard 0 entities found · 4/8 branches scanned
ID:BUG-043Status:fixed

Building plan content slows with the length of git history

Pages that render plan entities read git history during the build, and the cost grows with the repository's history rather than with the content being built. In this repository, two tests that build real plan content take ~17s on a shallow clone (96 commits) and ~170s on the full history (3,473 commits):

Severity:majorMilestone:v0.41.3Source:SPEC-072

Criteria completion

Criteria completion: 4 of 4 (100%) checked; tracking started on Oct 22, no incremental history yet0%25%50%75%100%Oct 22Oct 28

Tracking started Oct 22 — check back for trends.

Where the time goes (measured on the full history)

  • extractBatchHistory (plugins/plan/src/history.ts, called from the plan pipeline's aggregate hook in pipeline.ts): one git log over the plan directory, then one git show <hash>:<file> per commit that touched each plan file. That is 3,669 git show processes for this history, at ~3ms each: ~11s per extraction. .plan-history-cache.json only helps on a warm second run in the same checkout, never in CI or a fresh clone.
  • packages/content/src/timestamps.ts: git log --name-only over the whole history, ~3.8s, plus ~0.4s for the created-date pass.

These account for ~15s per build. The ~170s the tests take means the work runs several times per test, or a path not measured here dominates. That is part of what the fix has to establish.

The pipeline test also reads the real repository's history through cwd: '.', so its runtime depends on the clone it runs in, not on its fixtures.

Steps to Reproduce

  1. In a clone with full history (git fetch --unshallow), run npx vitest run packages/content/test/plan-site-dogfood-real.test.ts plugins/plan/test/pipeline.test.ts.
  2. Run the same in a depth-1 clone.

Expected

Build time tracks the plan content, not the length of the repository's history. Both tests pass on either clone.

Actual

~17s shallow; on full history both time out at 30s and take ~165–176s to finish.

Environment

  • @refrakt-md/plan and @refrakt-md/content at v0.41.1.

Acceptance Criteria

  • The remaining time is accounted for: which calls run, how many times, for each of the two tests
  • History extraction reads file contents without one process per commit and file, for example with a single git log -p or git cat-file --batch, or is bounded or skipped where no page shows history
  • Both tests pass on a full-history clone within their timeouts
  • The pipeline test no longer depends on the history of the clone it runs in

Resolution

Completed: 2026-10-24

Branch: claude/milestone-v0-41-3-1mzttp

What was done

  • Accounted for the time (cold cache, full history, 3,504 commits): both tests ran ~5,220 git processes, 938 git log --follow (~98s) plus 4,276 git show (~30s), once per test. --follow was the unmeasured cost.
  • plugins/plan/src/history.ts: extractBatchHistory now runs one git log --full-history -M --name-status over the plan dir, groups commits per file following renames, and reads all contents with one git cat-file --batch (readFilesAtCommits). Only stale cache entries are read. extractEntityHistory uses the same event builder and one batch read. 115s → 0.6s for this repo.
  • plugins/plan/src/pipeline.ts: runs git from the plan dir, not the process cwd.
  • plugins/plan/test/pipeline.test.ts: points setPlanDir at an empty temp dir outside any repo.
  • packages/content/src/timestamps.ts: both git log passes are limited to the content dir with -- ..
  • Tests: batch matches per-entity output, rename following, cache reuse, outside-a-repo, cat-file parsing (multi-byte, missing).
  • site/content/runes/plan/plan-history.md: updated How it works and Rename tracking.

Notes

  • Measured after the fix on full history: the dogfood test takes ~8s, the pipeline test 11ms, and the whole build runs 7 git processes. The full suite passes on the full-history clone.
  • Output diff against the old code over this repo: 725 files identical. Renamed files now carry the commits from before the rename, and old paths no longer appear as separate keys (they used to double-count in the global feed). The only commits lost are from before a file entered plan/.
  • --full-history is needed because default simplification at the directory level prunes side branches that per-file --follow kept.