Skip to main content
Back to Blog

How a Programmer Writes a Book

12 min read
By Lauri Mukkala
How a Programmer Writes a Book

What if writing a novel felt a little more like shipping code, and a little less like drowning in your own sentences?

A quick glossary, so nobody has to pretend they already know:

  • Git — software that keeps every version of a file you have ever saved and lets you walk back to any of them. The folder it watches is called a repository, or repo for short.
  • Markdown — plain text with a handful of symbols for formatting. Two asterisks around a word make it bold, and that is more or less the whole idea.
  • Claude — the AI assistant I talk to while I work. It can read the files in the project and answer questions about them, and in my setup it is not allowed to write prose.
  • Linter — a small program that reads your text hunting for rule violations and lists what it finds. Spellcheck's stricter cousin.
  • Regular expression — a search pattern that matches a shape rather than an exact word. "Any of these three verbs followed by any of those three adverbs" is a shape.
  • Build — the step that turns the source file into the finished formats, so PDF, EPUB and the rest.
  • Commit — one saved snapshot in git, with a note attached saying what changed and why.

I'm a software developer by day and a fantasy writer by night, and for an embarrassingly long time I kept those two halves of my brain in separate rooms. Code lived in git with proper commit messages and a CI pipeline. Prose lived in a document whose filename I'd rather not type out in public. Lately I've been writing a cozy fantasy novel in Finnish, Vaeltaen, missä olenkin (Wandering, Wherever I Am), and somewhere around the third chapter I finally stopped pretending the two halves are unrelated. So I put the manuscript in git, wrote a style linter for it, and gave Claude a constitution plus a handful of skills with very clearly defined jobs.

Then I made myself one rule that everything else hangs on: I write the book, the machine checks it. That's not a moral position I want to lecture anyone about, it's just self-interest. If AI writes the novel for you, there's nothing fun left in writing. The joy is in choosing the next sentence, arguing with a character who refuses to behave, and finding the quiet ending that actually lands. Tools can help you see your own text again after you've gone blind to it, and that's genuinely valuable. They should not replace the part that makes you want to open the file in the first place.

The Rule: Write Yourself, Edit With Help

I use Claude for plenty of things around the book. Brainstorming when a scene refuses to start, editorial passes when I can't tell whether a chapter drags, and the occasional "did I already give the miller's daughter a name?" question that would otherwise cost me twenty minutes of scrolling. All of that is useful and none of it worries me. What I refuse to do is hand over a chapter outline and ask for finished prose I can paste into the manuscript, because at that point I'm not writing a book anymore, I'm reviewing one.

The voice rules for the book live in the project as written documentation rather than in my head, which sounds bureaucratic until you've tried to hold a consistent tone across seven chapters written over eight months. They cover the vocabulary I'm reaching for, the dry understatement that carries the humour, the word count band a chapter ought to land in, and what my protagonist Theron actually knows at this point in the story, which is always less than I know. Writing all of that down was useful to me long before any assistant ever read it. When I do ask for help, the request follows the same shape every time: a report first, then one small fix that I approve before it touches the file.

The reason for all this is a problem every writer knows, and it isn't writer's block. Blindness is the real enemy. After you've read the same scene fifteen times you stop seeing what's on the page and start seeing what you meant to put there. Your brain autocorrects the clumsy sentence, skims past the paragraph that drags, and cheerfully waves through the same verbal tic for the ninth time. A linter never gets bored of your habits, and an editorial skill that isn't allowed to rewrite your voice in its own style turns out to be far more valuable than a clever paragraph generator.

The Manuscript as Source of Truth

Programmers already know this pattern in their bones. One source of truth, and everything else is a build artifact you can throw away and regenerate. It maps onto a manuscript almost too neatly.

My book lives as a single markdown file, and that file is the book. PDF, DOCX and EPUB get generated when I ask for them, and I never open the binaries to fix a typo, because a fix that only exists in the PDF is a fix that will vanish the next time I build. If something needs changing, it changes in the .md and I rebuild. A simplified version of the project looks like this:

wandering-wherever-i-am/
├── CLAUDE.md                 # rules for any AI helper
├── plan.md                   # plot ledger (promises & debts)
├── world_bible.md            # characters & places as written
├── manuscript.md             # THE book
├── build.sh                  # → pdf / docx / epub
└── .claude/skills/
    ├── editorial-review/     # editorial report first
    ├── style-check/          # style lint + spellcheck
    └── publish/              # publish / build

The two ledger files are the part I'd recommend to anyone, coder or not. plan.md and world_bible.md are not wish lists or outlines of what I hope to write, they describe only what is already on the page. When a chapter lands, the ledger updates in the same sitting. Open threads get written down as debts I owe the reader, which means a promise made in chapter two doesn't get quietly undone in chapter five just because I forgot I made it. Bookkeeping is unglamorous, but it's a lot cheaper than discovering a contradiction after the book is finished.

Desk with markdown manuscript, CLAUDE.md notes, git, and a stack of books

CLAUDE.md as a Constitution

Every coding agent works better with a project file that spells out what it must not do, and writing turns out to be exactly the same. My CLAUDE.md is short on inspiration and long on boundaries, which is entirely deliberate. What follows is a made-up version rather than my actual file, but the shape of it is honest:

# CLAUDE.md - house rules for this manuscript

You are an editor and a critic. You are not a writer.

## You may

- Read a chapter and report what actually happens in it, in plot terms.
- Name the weakest passages, worst first, and say in one sentence why.
- Check every scene against plan.md and world_bible.md, and flag what
  contradicts them. A contradiction is a finding, not something you fix.
- Correct mechanical errors: typos, spelling, punctuation, wrong name forms.
- Ask questions. A good question is worth more to me than a good suggestion.

## You may never

- Write new prose. Not a scene, not a paragraph, not a replacement sentence.
  Quote the line that is broken and describe what is wrong with it. I write
  the fix.
- Rewrite accepted prose in your own voice.
- Invent plot. Everything you raise must already exist in the manuscript
  or in the plan.
- Implement a structural change. Propose it, then stop and wait.
- Run the publish script. That one is mine.

## How to report

1. What happens in the chapter.
2. Findings, worst first, each with a line reference.
3. For each finding, what I could do about it. Options, not prose.

Stop at step three. Do not continue into the fix.

The rule about the publish script sounds petty until you know that my build copies files to Dropbox, and an eager assistant regenerating everything in the middle of an edit is not helping anyone. The load-bearing rule is the first one in the second list, though, and it's the reason the whole file exists. Without an explicit boundary there, a helpful model will keep improving your sentences until they sound like the model rather than like you, and it will do it one entirely reasonable-looking suggestion at a time. Polished and wrong is worse than rough and yours, and by the time you notice, the voice has drifted so gradually that you can't point at the sentence where it happened.

Skills as Roles, Not Magic Buttons

I don't have a "write my novel" button, and I wouldn't press it if I did. What I have instead is a small cast of specialists, each with one job and a clear boundary around it.

/editorial-review behaves like a professional editor rather than a cheerleader. It reads a chapter, summarizes what actually happens in it, ranks the problems with line references, and proposes a fix plan with before-and-after samples. The report comes first and edits only happen after I say yes, which keeps me in the chair where the decisions get made.

/style-check runs the mechanical style checker together with a spellcheck. Clear errors get fixed on the spot, warnings get a question first, and there's one rule I'm oddly fond of: if the lint fires on a chapter I've already accepted as the voice standard, we change the rule, not the text. The linter serves the book, not the other way around.

/publish is just a release pipeline wearing a literary hat. Preflight lint and spellcheck, then build, then verify that the output files genuinely updated instead of silently failing and leaving yesterday's PDF in place.

Splitting the work into roles is the whole trick, honestly. When you ask a single chat to "make this better" it will dutifully rewrite everything, because that's what the request sounds like. When you ask it to "find the three worst POV leaks and propose minimal fixes," you get something you can actually use on a Tuesday evening with forty minutes to spare.

Lint: Catching "asked quietly" the Fourteenth Time

Time for a confession that will sound painfully familiar if you write a lot of dialogue. I overuse quiet speech tags. Asked quietly. Said softly. Answered in a whisper. Used once or twice it's texture, but by the fourteenth time every character in the book is whispering like they're trapped in a library that never closes. The worst part is that I never catch a single one of them while I'm writing, because in the moment every one of those tags feels like precisely the right call.

The trouble is that my eyes skip straight over it, every single time. A regular expression does not, and the whole thing is small enough to read in one go:

import re
from collections import Counter, namedtuple
from pathlib import Path

# The tic I never notice until the 14th time.
QUIETLY = re.compile(r"\b(asked|said|answered)\s+(quietly|softly|in a whisper)\b")

Hit = namedtuple("Hit", "chapter line excerpt")


def scan(manuscript):
    hits, chapter = [], 0
    for number, line in enumerate(manuscript.read_text().splitlines(), start=1):
        if line.startswith("## "):          # chapter headings look like "## 3 - Name"
            chapter += 1
        for match in QUIETLY.finditer(line):
            hits.append(Hit(chapter, number, match.group(0)))
    return hits


hits = scan(Path("manuscript.md"))
seen = Counter()

for hit in hits:
    seen[hit.excerpt] += 1
    tally = f"({seen[hit.excerpt]}th time)" if seen[hit.excerpt] > 3 else ""
    print(f"chapter {hit.chapter}, line {hit.line:>4}: ERROR  {hit.excerpt:<22}{tally}")

print(f"{len(hits)} errors. Fix these yourself. The linter does not write dialogue for you.")

Here's an invented manuscript fragment of the kind that trips it:

"Are you coming?" he asked quietly.
"I don't know," Mara answered softly.

And here's the sort of thing the linter tells me, also invented but not far from reality:

chapter 3, line  412: ERROR  asked quietly         (14th time)
chapter 5, line   88: ERROR  said softly
chapter 7, line  201: ERROR  answered in a whisper
3 errors. Fix these yourself. The linter does not write dialogue for you.

That last line is the entire philosophy in one sentence. The linter flags the habit and then stops, because I still rewrite the line myself. Sometimes the quiet tag stays exactly where it is, because the scene genuinely needs someone to lower their voice. Sometimes I strip it down to a plain said and let the dialogue carry the volume on its own. The machine has no idea which of those is right, and it isn't pretending to.

Terminal showing style lint hits for repeated quiet dialogue tags

The same linter also catches structures that sound clever and land soft, my favourite offender being the explanatory generalization simile. Something like "he said it the way a name is said when…" looks wonderfully literary on the first read and like pure padding on the fifth, and I reach for it every single time I'm tired. ESLint catches a missing semicolon, and my style lint catches a sentence shape I can't stop writing after ten at night.

There's a longer list in the same spirit: no curly quotes in the body text, no metric system in a low-fantasy village where distances are measured in steps rather than kilometres, chapter word counts inside a sensible band, and numbers written as words in narration. None of that makes a book good, and I want to be clear about that. All of it keeps the book consistent while I'm busy trying to make it good, which is a different and much more achievable job.

Git: History Instead of final_final_v3.docx

Git is not glamorous for fiction writers, and nobody has ever been charmed at a party by a well-structured commit history. It is, however, quietly excellent at exactly the things that make writing stressful. I can delete a subplot I no longer believe in without pretending the old version never existed, because it's still there if I change my mind at two in the morning. I can see the week a chapter suddenly ballooned by three thousand words. I can recover the paragraph I cut too aggressively last night, without the ritual of keeping seventeen backup files. Compare that to manuscript_final_FINAL_v3_really.docx sitting on a desktop next to manuscript_final_FINAL_v3_really_copy.docx, and tell me which system you trust.

A fictionalized log from a normal week of work looks a lot like a week at the day job:

$ git log --oneline -5
c8e1a2b Chapter 4: remove repeated 'asked quietly'
9b3d440 Note in plan.md: the dry well still needs an ending
a1f0c33 Chapter 3: tighten POV, stop explaining Mara

The habit is the same one I've built at work over the years, which is small commits that say why rather than just what. It changes how the cutting feels, too. When I finally ripped out a long stretch of future plot I'd been protecting for months, git turned the deletion into a decision I could reverse rather than a disaster I had to live with.

What This Stack Does Not Do

I should be honest about the limits, because a post like this can easily read as an advertisement for a workflow that solves everything. It doesn't invent the next scene for me in any way that feels like mine, and it doesn't decide whether a quiet ending has been earned by the two hundred pages in front of it. It certainly doesn't make writing effortless. If anything, it makes the boring failures louder and more obvious, which is the whole point, because that's what frees up my attention for the interesting ones.

The fun part is still the sentence. All these tools exist so that I don't spend my good hours hunting for the fourteenth asked quietly, or lying awake wondering whether I promised the reader something in chapter two that I never got around to paying back. That's clerical work, and clerical work is exactly what computers are for.

If you're a programmer who writes, you already have half of this muscle built and you're probably not using it on your fiction. Treat the manuscript like a repo, write a constitution for any AI that touches it, lint the habits you genuinely cannot see, and keep the drafting for yourself. If you're a writer who doesn't code, you don't need my scripts or my file structure at all. You just need the principle underneath them, which is automation for blindness, human hands for voice.

I'm still writing this book the same way anyone writes a book, which is one chapter at a time and slower than I'd like. The stack doesn't change that. It just means that when I open the file tomorrow morning, I'll be a little less blind than I was yesterday, and after four years on my previous novel I've learned exactly how much that's worth.


Do you use any "programmer habits" in your creative work, or deliberately refuse them? I'd love to hear. Find me on Instagram @stories.by.lauri!

Writing & journaling tools I've built

If you're working on your own stories or want a gentler way to keep track of your days, these might help:

Wryterio

A focused writing app for plotting, drafting, and shaping your stories without distractions.

Learn more at wryterio.com

Journario

A cozy digital journal and task manager for reflecting, planning, and noticing the small wins.

Visit journario.com