Skip to content

perf: cross-run cache for tag→commits walk under .ferrflow/cache/ #508

@BryanFRD

Description

@BryanFRD

Observation

On a busy monorepo, the dominant cost of `ferrflow check` is the per-package commit walk from last-tag to HEAD. On every CI run for the same SHA, this walk re-emits the same result. Today there's no caching across runs — we pay the walk every time, even when the input (HEAD sha + tag refs) hasn't changed since the previous `ferrflow` invocation.

For a 200-pkg monorepo with 10k commits, the walk is the largest chunk of the ~800ms current scan time.

Proposal

Persist a small JSON cache under `.git/ferrflow-cache/` (or `$XDG_CACHE_HOME/ferrflow/`) keyed on:

  • HEAD sha
  • tag refs sha hash (fnv of `refs/tags/* → oid` map)
  • config file sha (.ferrflow content hash)

Layout:
```
.git/ferrflow-cache/
<head_sha>-<tags_hash>-<config_hash>.json # 4 KB typically
```

Cache content: per-package `{ commits: [...], last_tag: "...", bump_type: ... }`.

On `check` / `status` / `version` / `tag`:

  1. Compute the cache key
  2. If file exists and < 5 min old → load + return
  3. Otherwise compute + write back

`release` skips the cache (full recompute — too risky to trust stale data when actually mutating).

Numbers (rough estimate)

  • Cache miss path: same as today (~800ms on mono-large)
  • Cache hit path: ~15ms (JSON parse + key check)
  • CI runs typically loop on identical SHAs during a PR (lint → build → test → check) → 3-4 `ferrflow check` invocations on the same head. Saves ~2-3s per PR run.

Edge cases

  • Disabled if `.git` is read-only (CI sometimes mounts it ro after checkout)
  • Invalidate on `.ferrflow` mtime change as well (config edits)
  • LRU prune over 50 entries / 7 days old
  • Add `ferrflow cache clear` subcommand for support

Test plan

  • Cache miss writes file; cache hit returns identical result to fresh compute
  • Tampering with the cache file → fall back to recompute (warn, don't crash)
  • Read-only `.git` doesn't error, just no-ops the cache write

Related #471 (perf followups), #504 (in-run caching). This is the across-run analog.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementImprovement to existing feature

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions