Bound each provider's startFetch()/resolveFetch() to an optional
since/until window, wire a SQLite-backed ContributionStore into
ContributionAggregator (fetch only the trailing window past the last
stored date, merge into the store, prune by CONTRIBUTIONS_RETENTION_DAYS),
and add a graph:contributions:refetch command to force a full or ranged
re-fetch in <=365-day chunks. This is the root fix for the full
365-day-refetch timeout that used to hit on every cache miss.
Providers were moved into the App\Service\Provider namespace, but
services.yaml still tagged/configured them under the old App\Service
FQCNs, so the _instanceof conditional never matched and the container
failed to compile.
Declare empty-string defaults for all optional env vars as Symfony
parameters so the container compiles without errors when GITHUB_USER,
GITLAB_TOKEN, GITEA_URL, etc. are not set in the environment.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>