I built deariary with Claude Code
The deariary backend’s first commit is dated 16 February 2026. As of this writing it has around 1,441 commits, one human author, and a dependabot. It runs three components (API, admin, worker) out of one NestJS codebase on Cloud Run, with Drizzle ORM against PostgreSQL 15 on Cloud SQL, about 397 TypeScript source files and about 211 test files.
Most of it was written with Claude Code driving. Not “AI-assisted” in the autocomplete sense: an agent reading the repository, making changes across files, running the tests, and opening pull requests.
This is what actually changed, and what did not. No productivity multipliers, because I did not measure any and neither has anyone else who claims one.
The context file is the artifact, not the prompts
The single highest-leverage file in the backend repo is AGENTS.md. It is 158 lines. It says what the components are, what the stack is, which commands to run for lint and unit and integration tests, where migrations live, and the conventions that are not inferable from reading the code.
CLAUDE.md in the same repo is one line long:
@AGENTS.md
That indirection is deliberate. Several tools look for their own filename, and maintaining parallel guides means they drift and then contradict each other. One canonical file, one-line pointers from the rest.
The reason this matters more than prompt technique: nearly every bad output I got traced back to a missing line in that file rather than a badly worded request. The agent adding a migration SQL file without the corresponding entry in drizzle/meta/_journal.json is not a prompting failure. It is a repository whose invariant was never written down. Once it was written down, the class of error stopped.
If I were starting over, I would spend the first day writing that file and none of it on prompt phrasing.
Where the leverage actually was
deariary has twelve data fetchers plus a webhook: GitHub, Google Calendar, Slack, Todoist, Bluesky, Discord, Steam, Trakt, Last.fm, Toggl Track, Swarm, Linear. Each one authenticates differently, paginates differently, and returns a differently shaped mess. Each one has to end up as the same internal structure.
That is the work. Not intellectually hard, genuinely large, and structurally identical every time. Trakt’s fetcher is around 1,200 lines. Toggl’s is around 215. The difference is entirely how much the upstream API makes you do, not how much thought the design took.
This is exactly the shape of work an agent is good at: the twelfth instance of a pattern you have already defined, where the specification is “same as the other eleven but for this API.” I would give it the API docs and an existing fetcher and get a first pass that was structurally right and wrong in the details, which is a much better starting position than an empty file.
Where it was not useful: deciding that the product should generate prose rather than a dashboard. Deciding that a missed day should produce an entry anyway. Deciding not to add a streak counter. Every one of those is a judgment about what the thing is for, and no amount of context in a file produces it.
The honest summary is that the boring sixty percent got much cheaper and the hard forty percent did not move at all.
A design decision that came out of the workflow
Adding an integration used to mean editing a central prompt somewhere, which is where a project like this normally rots: one enormous prompt string accumulating conditionals for twelve services.
Instead each fetcher carries its own instructions to the language model, as data, next to the code that fetches. From the Last.fm fetcher:
readonly llmHint = [
'Music listening history (scrobbles) from Last.fm.',
'Data is grouped by artist > album > tracks. Each track has a playedAt timestamp.',
'`summary.topArtists` is pre-sorted by play count.',
'Weave the listening into the narrative as background texture.',
'Mention at most 2-3 standout artists or tracks. Do NOT list every scrobble.',
'If there are very few scrobbles (< 5), a brief one-line mention is enough.',
].join(' ');
Adding a service means adding a directory. Nothing central changes. The instruction about not listing every scrobble lives with the code that knows what a scrobble is.
I am not claiming an agent invented this. It is a fairly ordinary application of keeping related things together. But it became obvious because of the workflow: when you watch a tool repeatedly need to understand one central prompt file in order to change one integration, the cost of that coupling stops being theoretical.
Review became the bottleneck, immediately
The failure mode of generated code is not that it looks wrong. Code that looks wrong gets fixed in thirty seconds. The failure mode is code that looks completely reasonable, follows the surrounding conventions, passes the tests, and quietly does the wrong thing at a boundary.
I read every diff. That is not a virtue, it is the only thing that makes the rest of it work. Writing stopped being the constraint and reviewing became it, which is a real trade rather than a free win. Some days it is a worse trade, because reviewing someone else’s plausible code is more tiring than writing your own.
Two things that reduced the load:
Tests as the contract. 211 test files against 397 source files, with unit and integration suites separated and integration tests running against a real local Postgres from docker compose. The suite is not there for coverage numbers. It is there so that “the tests pass” carries information. In a workflow where a lot of code arrives faster than you can read it carefully, a suite that only catches typos is actively dangerous, because it produces confidence without evidence.
Small pull requests. A 200-line diff gets read. An 800-line diff gets skimmed, and skimmed review is the same as no review with extra steps.
What I would tell someone starting
Write the context file first, and treat every recurring mistake as a missing line in it rather than a prompting problem.
Point every tool’s config at one canonical guide instead of maintaining several.
Make the test suite mean something before you accelerate. Speed without a real suite just gets you to the bug faster.
Keep the diffs small enough that you actually read them, and accept that reading is now the job.
And do not expect help with the part that matters. The reason deariary generates a paragraph you might actually reread, rather than a dashboard of charts, is a product decision that no agent was going to hand me.
Why this shows up on a diary company’s blog
Because the reason deariary ships thirteen integrations rather than three is that the marginal integration got cheap.
A one-person product with a per-service cost of two weeks supports maybe four services, and an automatic diary with four services is a much worse product. The whole premise depends on connecting to whatever you already happen to use, and that only works if the twelfth one is not a heroic effort.
That is the actual effect, and it is a product effect rather than a personal-velocity one. Not that I write more code per day. That a product shape which was previously not worth attempting became worth attempting.
If you want to see what it produces: deariary reads what your tools logged and writes the day for you, free for one integration at deariary.com.