feat(core): cache, background refresh and stale-result protection #28

Merged
deadstyle merged 7 commits from feature/7-core-cache into develop 2026-10-02 12:35:40 -05:00
Owner

Summary

The core cache and refresh layer for #7. Screens can show what's already loaded at once and refresh in the background. Forge calls are capped at four at a time, and host-wide scans such as ★ Renovate can report "scanned X of Y".

Changes

  • ADR 0007 records the decisions. The core returns plain blocking calls that take a context, and it does not import Bubble Tea. The UI wraps the calls in commands and owns the five-minute timer, r, and dropping stale results.
  • internal/core:
    • Service wraps the session's one forge.
    • Repos, open PRs, issues, releases and runs each have an instant "peek" (the cached value plus when it was fetched) and a fetching call.
    • A failed fetch keeps the old data. Invalidate drops an entry.
    • Runs report unavailable on a forge without Actions.
    • Forge calls are capped by a semaphore of 4, and waiting for a slot honors cancellation.
  • Coverage tracks per-repo scan outcomes as a set: duplicates count once and repos outside the set are ignored, so "scanned X" never exceeds Y. It also tracks failures and completeness.
  • docs/architecture.md "Caching and refresh" now points to ADR 0007.
  • .gitignore: coverage.* is now /coverage.*. The old pattern silently ignored internal/core/coverage.go, which the mutation check caught before this MR was opened.

Test plan

  • Tests cover the cache, peek and fetch, failure retention, invalidation, the concurrency bound, cancellation while queued, and coverage semantics. They were written from the scope before the code existed.
  • Mutation check: 37 mutations. One weak test (the cancel-while-queued check depended on timing) was made deterministic. Two mutations are equivalent and can't change behavior.
  • make check exit 0 after rebasing onto develop.

Rulings

  • The scope came from the ADR without a planner.
  • The core returns blocking calls and doesn't import Bubble Tea.
  • The reviewer's Info finding on coverage counting was promoted to Medium and fixed, because "scanned X of Y" must never exceed Y.
  • The ignore pattern was narrowed to the repo root, and the hidden file got its own fresh review, which passed.

Closes #7

🤖 Generated with Claude Code

## Summary The core cache and refresh layer for #7. Screens can show what's already loaded at once and refresh in the background. Forge calls are capped at four at a time, and host-wide scans such as ★ Renovate can report "scanned X of Y". ## Changes - ADR 0007 records the decisions. The core returns plain blocking calls that take a context, and it does not import Bubble Tea. The UI wraps the calls in commands and owns the five-minute timer, `r`, and dropping stale results. - `internal/core`: - `Service` wraps the session's one forge. - Repos, open PRs, issues, releases and runs each have an instant "peek" (the cached value plus when it was fetched) and a fetching call. - A failed fetch keeps the old data. `Invalidate` drops an entry. - Runs report unavailable on a forge without Actions. - Forge calls are capped by a semaphore of 4, and waiting for a slot honors cancellation. - `Coverage` tracks per-repo scan outcomes as a set: duplicates count once and repos outside the set are ignored, so "scanned X" never exceeds Y. It also tracks failures and completeness. - `docs/architecture.md` "Caching and refresh" now points to ADR 0007. - `.gitignore`: `coverage.*` is now `/coverage.*`. The old pattern silently ignored `internal/core/coverage.go`, which the mutation check caught before this MR was opened. ## Test plan - Tests cover the cache, peek and fetch, failure retention, invalidation, the concurrency bound, cancellation while queued, and coverage semantics. They were written from the scope before the code existed. - Mutation check: 37 mutations. One weak test (the cancel-while-queued check depended on timing) was made deterministic. Two mutations are equivalent and can't change behavior. - `make check` exit 0 after rebasing onto develop. ## Rulings - The scope came from the ADR without a planner. - The core returns blocking calls and doesn't import Bubble Tea. - The reviewer's Info finding on coverage counting was promoted to Medium and fixed, because "scanned X of Y" must never exceed Y. - The ignore pattern was narrowed to the repo root, and the hidden file got its own fresh review, which passed. Closes #7 🤖 Generated with [Claude Code](https://claude.com/claude-code)
#7

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#7

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#7

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
NewCoverage dedupes its input and Done ignores repos outside the set,
so Scanned never exceeds Total and Complete cannot be true while a
listed repo is unscanned.

#7

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#7

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
fix(core): commit coverage.go hidden by .gitignore
All checks were successful
ci / check (pull_request) Successful in 1m50s
351b855828
The coverage.* ignore rule matched internal/core/coverage.go, so the file
was never committed. Narrow the rule to root-level coverage output and add
the file, with set semantics: NewCoverage dedupes and Done ignores repos
outside the expected set.

#7

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
deadstyle merged commit e4b709297c into develop 2026-10-02 12:35:40 -05:00
deadstyle deleted branch feature/7-core-cache 2026-10-02 12:35:40 -05:00
Sign in to join this conversation.
No description provided.