The 31 dependencies we argued about
Every dependency is a future security advisory, a future breaking change and a future licence question. Here is the test we apply, the four we removed last year, and the one we kept despite failing our own rules.
24 June 2026
Quire has 31 direct runtime dependencies. That number has been between 28 and 44 for two years, and every movement in it was an argument. This post is the argument, written down, so we stop having it from scratch every time.
The framing that helped most: a dependency is not free code, it is a permanent obligation. You inherit its security advisories, its release cadence, its maintainer's attention span, its transitive tree, and its licence. The question is never "does this library work". It is "are we willing to be responsible for this for the next five years".
The test
Five questions, in order. A no at any point does not end it, but it has to be answered out loud in the pull request.
- Is this the standard library's job? A surprising share of proposed dependencies are a nicer interface over something already present. Nicer is not a sufficient reason.
- How much of it would we use? Pulling a framework for one function is the classic bad trade. If we use under 10% of the surface, we usually vendor the part we need, with attribution and the licence header intact.
- What is the transitive tree? One direct dependency with forty transitives is forty obligations. We count them, and the number goes in the pull request.
- What happens if it is abandoned? Could we fork and maintain it with the people we have? For a 500-line library, yes. For a numerical runtime, no, which is not disqualifying, but it does change what we are agreeing to.
- Does it phone home? Any network access at import or at runtime that is not the explicit purpose of the library is disqualifying, with no discussion. This has removed more candidates than every other rule combined.
Adding a dependency is a decision one person can make in a minute and everybody maintains for years. That asymmetry is the whole reason for the checklist.
The four we removed
| Removed | Why | Replaced with | Net lines |
|---|---|---|---|
| A date parsing library | Used for two formats out of hundreds it supports; 4 transitives | Explicit parsers per locale, which we needed anyway for version dating | +180 |
| An HTTP client | Ingest has no network access at all. It was there for one optional connector | Standard library, in the connector only | -40 |
| A retry and backoff helper | Nine lines of logic, 1,100 lines of library, one CVE in the transitive tree | Nine lines | -1,100 |
| A progress bar | Terminal handling conflicted with our own output; carried a colour library | Twelve lines writing to stderr | -2 transitives |
The date one is the interesting case, because we added code to remove a dependency and we would do it again. Explicit per-locale parsers are more code and they fail loudly on formats we have not handled, where the library silently guessed. And a silent guess about a date is exactly the class of bug that ends up in an answer nobody can trace.
The one that fails our rules and stays
The OCR engine. It is enormous, its transitive tree is not something we could maintain, and if it were abandoned tomorrow we would have a serious problem. It stays because 11% of user documents are scans and the alternative is telling those users the tool does not work on their corpus.
What we did instead of pretending the risk away: it is optional, the build works without it, the interface it sits behind is small enough to swap, and there is a second supported engine so that neither one is load-bearing alone. That is not risk elimination. It is risk with a stated plan, and a stated plan is the most that is available here.
Things we do not count as savings
Two arguments that sound good and are not:
- "It is only one small dependency." Small libraries have the worst abandonment rate and the highest chance of a single maintainer. Size is a weak predictor of obligation.
- "We can always remove it later." Removal costs proportional to how long it has been in and how deep its types leaked. In practice later means never, and the argument is really "let me not have this conversation now".
What this buys
A full install is 31 direct and 58 transitive packages, and it audits clean at the time of writing. Median time from an advisory in a dependency to a released patch here is under two days, only possible because the tree is small enough for one person to hold in their head on a Saturday.
None of this is virtue. It is the cheapest way we found to keep a small maintainer group from drowning in other people's release notes.
The dependency policy is in CONTRIBUTING.md and the checklist is a pull request template. If you propose an addition, filling it in honestly is most of the review.
Local-first isn't a feature, it's a default
What it costs to keep offline as the configuration everything is tested against.
The Quire Note
One email a month, engineering-first.