The Repository Is Not Your Memory

It started with two repositories. Six months later, seventeen microservices were staring back at me. My old habits almost broke both me and our build pipeline. The first few services were a joy. A clean docker-compose.yml here, a tidy Makefile there.

Each one was a small monument to good intentions. Then the auth service needed a breaking change. Suddenly I was spelunking through five different codebases just to trace a single token refresh. My self-hosted GPU cluster on the other side of the house kept humming along fine. My mental model of the codebase did not.

I tried brute force. More documentation, more Notion pages, more “just remember where this lives” notes scribbled in comments. That worked for about three weeks. Then a stale README sent me hunting for an endpoint that had been renamed twice. The real turning point came when a routine dependency bump cascaded into a 45-minute debugging session.

I’d forgotten which repo pinned which version of our shared protobufs. This isn’t a story about elegant architecture from day one. It’s about the messy, pragmatic systems I’ve built since: tagging every repo with semantic versioning rules enforced by CI, generating API docs straight from OpenAPI specs so they can’t drift stale. And scripting dependency updates with Renovate so nothing gets quietly left behind.

We’ll get into the exact Makefile targets that keep 17 directories in sync. And the one bash function that saves me ten minutes every single morning. Here’s what matters: managing many repos is not about being smarter or more disciplined. It’s about making your future self too lazy to make mistakes.

The discipline gap gets bridged the moment your README becomes authoritative. For years, I kept project context in my head: branch conventions, deploy quirks, the one test that always flakes. Then a three-week vacation erased six months of hard-won knowledge. Every time you re-open a repo and re-derive its structure from scratch, you burn roughly 20 minutes of mental RAM on context reconstruction. Multiply that by 17 repos and suddenly half your morning is gone before lunch.

The fix is brutally simple: make each repository self-documenting through a standardized template. The Kubernetes project maintains a CONTRIBUTING.md format so rigid that every new maintainer knows exactly where to look for CI instructions, release cadence, and breaking-change policies. That consistency isn’t bureaucratic theater. My template runs 14 lines long: purpose statement, architecture sketch, setup commands, test suite location, deploy target, known gotchas.

Sixteen of my repos follow it verbatim; the seventeenth deviates because it’s a monorepo with three services crammed into one tree. Even that exception is documented in the first five lines. Concrete beats clever every time. A README saying “see scripts/deploy.sh” prevents ten Slack messages from future-you asking what port staging actually runs. Your memory is terrible at this job. Writing things down once means never re-learning them twice across all seventeen contexts I juggle daily.

The same logic that forces a 14-line template into every repo also demands a standardized structure for the decisions those repos record.

The Wiki Problem Gets Worse With Numbers

Seventeen repos means seventeen wikis. Ten repos, six wikis, zero searchable index. I was opening separate tabs for each project’s GitHub wiki just to trace one architecture decision. which turned out to be buried in a PR comment from March 2026. Context-switching isn’t the click; it’s re-learning each repo’s folder structure and naming quirks. Merriam-Webster defines “manage” as direct handling toward a result. I’d call that scattered approach the opposite.

Developers lose focus every time they switch tasks. The fix is centralizing decisions into one source of truth. I keep a single markdown file per repo inside docs/decisions/ with a standardized ADR (Architecture Decision Record) template. Number, Status, Context, Decision, Consequences.

Kubernetes uses ADRs across their org; the format scales because it forces consistency rather than relying on each maintainer’s whims. A standard template works because it externalizes structure so your brain doesn’t have. You read the file names alphabetically: 0001-use-postgres.md, 0002-graphql-over-rest.md. Each one answers three questions: what we chose, why we chose it, and what we gave up to get there.

The “what we gave up” field matters more than people think. It captures the trade-off conversation that normally evaporates into Slack history or meeting memory. the exact content wikis tend to bury under unrelated release notes and troubleshooting guides. Your tooling should enforce this pattern too. A pre-commit hook in each repo can validate that new decision files match the template before merging; mine fails with an exit code 1 if any required field is blank.

That pushes compliance earlier into workflow rather than trusting developers to remember after fifteen commits of feature work.

The ARCHITECTURE.md Template That Ends Context Switching

That exit code 1 only catches blank fields. It does nothing for the deeper problem: every repo still told a different story about itself. So I standardized the narrative structure itself, not just the validation. The template lives at templates/ARCHITECTURE.md in my meta-repo, and every new project starts as a copy. Seven sections, fixed order: Overview, Directory Map, Data Flow, External Dependencies, Build Pipeline, Deployment Topology, and Decision Log links.

Each section has three subsections. Current State, Known Gaps, and Recent Changes. with character limits that force terse writing. The Overview section is capped at 150 words. That constraint hurts at first; it also compels you to write the thing that matters rather than the thing that sounds full. The Directory Map is a flat list of top-level paths with one-line purposes each. no prose nesting allowed.

What changed is measurable. A repo I hadn’t touched in eight months took me about four minutes to get productive in again; before this standard, that was closer to an hour of reading stale READMEs and piecing together intent from commit messages. Fourteen minutes versus roughly sixty is not subtle. The template also encodes ownership explicitly: each section header carries a maintainer name and last-reviewed date in YAML front matter.

When that date slips past ninety days old, a GitHub Action opens an issue with stale-doc label assigned to that person’s team. Teams occasionally backfill sections with wishful thinking or copy from design docs that predate implementation reality. But even inaccurate entries are useful. they give you a precise starting point for asking the right question instead of wandering through five directories guessing where the truth lives.

My rule of thumb: if reading ARCHITECTURE.md takes longer than ninety seconds per repository in continuous integration work sessions, something. That system drifted off-specification from the actual codebase behavior fast enough to warrant immediate correction rather than deferral until later maintenance windows arrive.

Which never materialize anyway when multiple initiatives compete simultaneously across fourteen active branches weekly while releases ship biweekly without fail on alternating Wednesdays irrespective of individual developer vacation calendars or local holidays observed differently across distributed time zones participating.

Automation Is the Only Honest Teammate

That ninety-second reading rule only works if the docs actually match the code. The moment I stopped trusting myself to keep them in sync manually, everything changed. semantic-release workflows saved my sanity more than any other single change. Every commit message now follows conventional commit conventions. The tool handles version bumps, changelog generation, and GitHub tags without me touching a single file.

No more agonizing over whether a minor bump deserves a patch or major version when half the changes hide in merge commits from three weeks ago.

Renovate is my silent partner for dependency updates. My configuration lives in .GitHub/renovate.json with two critical settings: "prHourlyLimit": 2 and grouped PRs by system. Fifty individual pull requests every Monday morning would drown me otherwise. The math is simple here. Before automation, each repository needed roughly an hour of maintenance per week. dependency checks, version bumps, changelog edits.

Across seventeen repos, that was nearly a full workday devoted entirely to grunt work nobody remembers doing anyway. Now CI handles it all on every push to main. Semantic release reads those conventional commits and decides whether to cut a new tag or skip quietly. Renovate opens its handful of grouped PRs daily. CI run the test suite against each one, and I merge whatever passes without ever opening my editor.

The same automation that handles versioning and dependencies also centralizes the pipelines themselves. A single .GitHub repository holds every reusable workflow via workflow_call — lint, test, build, release — so each of the 17 service repos carries a three-line file pointing at that central definition instead of 200 lines of duplicated YAML drift. When I updated our Node version from 18 to 20 last quarter, the change was one commit in one place.

No spelunking through seventeen identical-but-slightly-different CI files hunting for the one that pinned an ancient runtime.

Renovate’s grouping rules batch patch updates into one PR per repo per week, with patch and minor updates merging automatically after CI passes. Breaking changes get scheduled for Monday at 9 AM via the schedule field, labeled deps:breaking, and stalled until a human types “approved.” Pin major versions explicitly in each repo’s package manifest rather than using wildcards. ��� a floating caret range turns Renovate into an argument generator; pinned majors turn it into a librarian that files things quietly.

Set dependencyDashboard: true to open a single issue per repo listing every pending update and its status.

My weekly maintenance time dropped from that fuzzy multi-hour grind to roughly 90 minutes of review spread across Monday mornings. That’s it. The overhead math shifted dramatically once this settled — and the same dullness that makes automation feel boring is exactly what makes it trustworthy.

Centralized Pipelines: One Repo to Rule Them All

The real open came from a single .GitHub repository. That tiny name hides enormous use. GitHub Actions allows reusable workflows via workflow_call, and I’ve stuffed every shared job there. lint, test, build, release. Each of the 17 service repos now carries a three-line file pointing at that central definition instead of 200 lines of duplicated YAML drift. When I updated our Node version from 18 to 20 last quarter, the change was one commit in one place.

No spelunking through seventeen identical-but-slightly-different CI files hunting for the one that pinned an ancient runtime. Renovate handles dependency updates with surgical precision rather than shotgun blasts. The critical config is grouping. set groupName aggressively so related packages bump together, then schedule PRs for Tuesday mornings only.

My current setup produces roughly four to six dependency PRs weekly across all repositories combined, each one mergeable after a green CI run without human review unless it touches a major version bump or a lockfile conflict arises unexpectedly. Rebasing operations triggered automatically by branch update mechanisms built into the platform itself.

This saves something more valuable than time: attention. I’ve watched colleagues burn entire afternoons clicking through identical GitHub UI screen updating dependencies repo by repo by repo until their eyes glaze. And they start merging without reading diffs just to escape the tedium of yet another pull request template requiring manual completion against JIRA tickets linked in descriptions referencing sprint boards configured.

Years ago by people who no longer work at whatever company employed them during happier simpler eras before organizational complexity metastasized beyond recognition into sprawling multi-team structures. Where nobody owns anything completely anymore except blame allocation meetings scheduled quarterly at inconvenient times guaranteed to maximize calendar fragmentation across timezone boundaries spanning continents oceans hemispheres simultaneously.

Which is precisely why automation exists in the first place despite philosophical objections raised occasionally by purists claiming manual processes build character like some sort of digital calisthenics routine nobody actually enjoys performing voluntarily. The maintenance math favors centralization overwhelmingly.

Dependency Bots That Don’t Nag

That manual drift pattern was the symptom. The cure is letting robots own the boring parts, but only when you train them to shut up. I run Renovate across all 17 repos with a shared renovate.json checked into a central config repo. Dependabot works fine, but Renovate’s grouping rules let me batch patch updates into one PR per repo per week. The config is deliberately strict. patch and minor updates merge automatically after CI passes.

That takes my daily action count from dozens of triage clicks down to zero on most days. Breaking changes are the exception. Those get scheduled for Monday at 9 AM via Renovate’s schedule field. I wake up to a single digest email instead of three repositories failing simultaneously at 2 PM on a Friday. Here’s the trick that saved me: assign labels like deps:patch and deps:breaking in the config.

Then wire branch protection rules to require different reviewers per label. Automated merges pass with no human touch; breaking changes stall until an actual person types “approved”. That guardrail alone cut my emergency hotfix frequency by more than half over six months. Nothing surprising lands mid-week anymore. One rule keeps the whole thing sane: pin major versions explicitly in each repo’s package manifest rather than using wildcards.

A floating caret range turns Renovate into an argument generator; pinned majors turn it into a librarian that files things quietly. Set dependencyDashboard: true too. It opens a single issue per repo listing every pending update and its status. Anyone auditing any repository can see exactly what got merged and what’s waiting without spelunking through pull request history. The overhead math shifted dramatically once this settled.

My weekly maintenance time dropped from that fuzzy multi-hour grind to roughly 90 minutes of review spread across Monday mornings and occasional mid-week security alerts from Renovate’s vulnerability lookups against GitHub Advisory Database. That’s it for dependency handling. It feels dull precisely because it works.

The 90-Minute Triage Routine

That same dullness extends to everything else. My Monday review now follows a strict queue: Renovate PRs first, then GitHub’s security alerts, then any failing CI across the 17 repos. Each gets a hard time cap. Security patches get fifteen minutes max. Dependency bumps get ten unless tests break. Anything needing deeper thought goes into a “Tuesday problems” list I refuse to look at before 9 AM.

This brutal triage cut my decision fatigue more than any tool ever did. The real shift happened when I stopped treating every repo as sacred. Two of my projects now share a single release pipeline because they deploy together anyway. Another one gets merged into its parent next quarter. It’s maintenance you no longer have to schedule.

What surprised me most was the emotional math. Running fewer moving parts meant fewer 2 AM panic checks and less guilt about unread notifications. My terminal feels lighter somehow, even though the actual workload barely changed. The patterns that stuck are embarrassingly simple: Docker Compose profiles for local dev, Renovate for dependency updates, and a ruthless weekly review that treats every repository like an inbox item rather than a child.

You don’t need seventeen repositories to test these habits either. Start with two or three and watch where your attention actually goes during a typical week. That’s the data that matters. What does your multi-repo setup look like. Let me know in the comments below, or subscribe if you want weekly notes on scaling codebases without burning out.

Seventeen repos no longer feel like a hoard to wrangle; they feel like a pipeline I can actually trace. The insight that stuck is simple: your tooling should absorb the remembering, so your brain can do the reasoning. I still trip over the occasional stale assumption, but the panic is gone.


Keep Reading

Now my morning starts with one bash function and ends without spelunking through five codebases for a token refresh. What’s the first manual check you’re still doing that a script could quietly own. Pick one small, boring task and automate it before Monday. your future self will thank you in 45-minute increments.