feat: cached CDLI reads (30s LRU + singleflight)

Closes #13 (closed).

Added src/util/cache.ts: a shared LRU (max 500, 30s TTL) plus an in-flight map that collapses identical concurrent requests (singleflight). Wired into cdliFetch, cdliFetchWithHeaders, and cdliFetchText, keyed by URL and namespaced per helper (json:/headers:/text:).

Only successful results are cached; failures are shared among concurrent waiters but never persisted, so a transient error retries on the next call. Callers don't mutate fetched objects, so a shared cached reference is safe.

Verified: lint + build clean; same artifact fetched twice -> 960ms then 0ms (cache hit); singleflight collapses concurrent calls to one fetch; errors not cached.

Merge request reports

Loading