How it works
The Count was built to replace a Renovate setup that took 6.7 minutes to get through twelve repositories. Most of that time went on work that did not need doing: one repository at a time, caches reset between them, full release histories fetched page by page, every changelog parsed again on every run.
He does the same job differently. One process takes the whole batch.
A run, start to finish
- Repositories come from the command line, or from everything the token can reach. Each one needs
.github/patchula.pklorpatchula.pkl; the rest are skipped. - Files are read through the GitHub API at the default branch's commit. No clone. If neither the commit nor the resolved config changed since the last run, the previous extraction is reused.
- Every dependency's source is queried once per batch, however many repositories share it. Responses are stored with their ETag or Last-Modified validators, so a warm lookup is mostly
304 Not Modified. Release and tag histories are read newest first and stop at the first release already known. - Rules pick the target for each dependency: the newest release allowed by its version policy, release-age wait, schedule window and PR limits. Updates that share a group, or a lockfile, land on one branch.
- For each branch the Count edits the exact bytes of each declaration, runs the lockfile tool in a scratch tree when one is needed, and creates a single commit through the API, which GitHub signs. Atomic ref leases guard the base and the branch, and branches holding anyone else's commits are left alone.
- A receipt records what was written. On the next run a matching receipt skips verification, tooling and PR reconciliation. An unchanged repository costs one GraphQL inventory query on the write side.
The result is a versioned JSON report for whatever called the Count. Logs are for people and are never meant to be parsed.
Principles
- Batch-native: repositories run concurrently (eight by default) behind one GitHub rate-limit governor.
- Incremental: nothing paginates a full history after the first fetch.
- Conditional everywhere: every GET sends a validator when it has one, and GitHub does not count 304 responses against the primary rate limit.
- No clone unless a lockfile tool needs a working tree. Then a persistent partial clone (
blob:none) supplies only the files that tool reads. - Fail loudly: unknown config fields, unknown managers and unsupported lockfile formats are errors, never silent skips. A file that does not parse at all is skipped with a warning, and warnings stop branch cleanup so nothing is closed by mistake.
- Clean-room: Renovate is only ever run as a black box for comparison.
Two ways to run
patchula run and patchula lookup process one batch with a token you supply and exit. patchula --server is a long-running GitHub App server: it verifies webhooks, keeps a deduplicated queue, sweeps on a cron schedule and keeps every cache warm between batches. Both use the same engine, so a batch behaves the same however it started.
--local <dir> reads one repository from a directory on disk instead of GitHub, with the same config discovery and file selection. Lookups still reach the registries, and nothing is written, so it works with lookup and run --dry-run. The Count's own CI uses it to look up a corpus of about 2,600 real dependencies on every pull request.
