I'm Building a Software Factory That Turns My Issues Into Merged Code · Fatih KoçI call it the factory. I write a plan into a GitHub issue and label it ready. A run picks it up, cuts a worktree, writes the code, runs the tests and opens a pull request. Some of those it merges without me now, under conditions I’ll come to.<br>A software factory for exactly one person, which is either a good name or a slightly grand one for something that runs on my laptop. It’s private, it isn’t a product and there’s nothing to sign up for. It moves my side project time from writing the code to planning it, and the parts that broke turned out to be more interesting than the parts that worked.<br>Why I Stopped Writing My Own Side Projects
Link to heading<br>Side projects don’t die of bad ideas. They die of maintenance.<br>Here’s what changed for me. On a normal weeknight I have about half an hour. That’s enough to think clearly about one problem and write down the right fix. It’s nowhere near enough to implement it, run the tests, fix what broke and open a pull request. So the planning always got done and the building never did.<br>That asymmetry is the entire reason the factory exists. My scarce resource was never ideas and it was never judgment, it was implementation time. Deciding what to build and what it must not break is engineering. Turning that decision into working code is labor, and I’ve done it long enough to know I add little by doing it personally. So I kept the first half and gave away the second.<br>What I tried first, and why it broke
Link to heading<br>I didn’t start by building any of this. The first version was a task queue plus a nightly autodev routine in the cloud, and pointed at one project it worked fine. It took a goal off the list, wrote the code and opened a pull request while I slept.<br>It came apart when I pointed it at a second project. The queue lived in one place and the repositories in another, so keeping the two in step became a job of its own. A goal would be marked done while the repo said otherwise, and every extra project multiplied the ways that could happen. I spent more time reconciling the queue than the routine had saved me.<br>That failure is the reason for almost every design decision below. If a queue can disagree with the project it describes, sooner or later it will. The only fix that holds is to not keep a second copy at all.<br>What One Run of the AI Coding Agent Actually Does
Link to heading<br>So the queue is GitHub Issues, and nothing else. No database, no board, no separate task file. The state of a task is the label sitting on it, in the same place the code lives, and every view is derived from that. There’s only one copy to read, so nothing can disagree with anything.<br>An issue joins the queue when I put the ready label on it. That label is the whole interface, and it doesn’t mean I want the thing. It means the body is a plan I agree with. Nothing turns an issue into a plan on its own. Some arrive that way, written clearly enough when I filed them that there’s nothing left to decide. The rest I talk through with Claude in the chat UI until the decisions are made and written into the body, and the label goes on after that, never before. Everything downstream reads that body as the spec, so a vague issue produces vague code, and that’s on me.<br>The one page I look at is the floor, a column per label and nothing of its own. It renders GitHub and the run folders on disk, so it can’t disagree with them. Most days it looks like this.
From there a run does the whole job and exits. It cuts a git worktree fresh from origin/main, so my own checkout is never touched and a stale clone can’t build on old code. It copies the issue body to disk as the spec, because no model should write what a human already wrote. It plans against the repo’s own rules, with a verdict on each, before any code exists. Then it writes the code and runs the gate: the test command, then every line of the issue’s definition of done, verbatim. It ends with a draft pull request saying Closes #180 and a record of turns, wall clock and cost, which is what the ledger reads later.
Green ships. Red also opens a draft pull request, marked needs-review and carrying the failure, because either way a person is needed and hiding it helps nobody. A run that ships deletes its worktree. A run that fails keeps it, because a failed run is evidence.<br>Each stage can name its own model and how hard it thinks, and I used that to split them. Reading a codebase and arguing about an approach looked like cheaper work than writing code that keeps a suite green, so the reading stages ran somewhere cheaper.<br>I had it backwards. Planning is where a bad decision gets expensive: the plan is what implementing is graded against, so a flaw there survives every gate downstream, while a weak line of code fails the suite and gets caught. The split runs the other way now....