Folder structure
decisions you can't redo later
What it is
The folder structure is the architecture from article 13.1 written into the file system. Its main value isn't beauty but that it answers the question "where do I put this new code" without a discussion. Until there is an answer, every new piece lands wherever the cursor happened to be.
For working with an AI the structure has a second, equally important role:
it is what you name in the request. "Add it to
app/services/weather.py" is a request you can get a predictable answer
to. "Add a weather function" is an invitation to invent the structure
again.
What it looks like in code
Two ways of arranging things, and both are right
By kind of code — everything of the same kind together:
app/
├── models/ what is stored
├── services/ what happens
├── routes/ what's visible from outside
└── utils/By feature — everything belonging to one subject together:
app/
├── weather/ the weather model, logic and screen
├── users/ the user model, logic and screens
└── shared/Choosing between them isn't a matter of taste but the answer to "what changes as a whole more often":
| Arranged by kind | Arranged by feature | |
|---|---|---|
| Good when | the project is small, few subjects | many subjects, independent |
| An edit touches | several folders | usually one |
| Easy to see | what's in the project at all | how one subject works |
| Breaks down when | there get to be many subjects | the subjects start to interweave |
For a first project, arranging by kind makes more sense: everyone understands it, AI included, and it involves fewer decisions. Moving to arrangement by feature makes sense once the project has more than five or six independent subjects.
What you shouldn't do is mix the two. A models/ folder next to a
weather/ folder with its own models inside isn't a compromise but two
answers to one question. After that, new code can go in both places.
What is expensive to redo
Not all decisions are equal. These three are worth thinking about on day one:
1. The boundary between the layers. If the logic and the database work are mixed together from the start, separating them later means rewriting nearly everything. Nobody has ever asked to go the other way, from separated to mixed.
2. The root package's name. It goes into every import in the project.
Changing it means changing every file. Not app and not src if the
project might one day become a package: something of your own, short and
clear.
3. Where the data lives. The decision "we'll just put it in JSON for now" lives longer than planned (article 13.3).
The rest — folder names, file order, where the tests are — is redone in half an hour and isn't worth long deliberation.
File size
A guideline that works: one file, one reason to change. If you have to open a file both when the API's response format changes and when a screen's appearance changes, there are two different files inside it.
The numeric guideline is 300–500 lines. Not a rule but a signal: once it reaches a thousand, two files are probably hiding in there.
With an AI that has a practical consequence: a file that fits into a request as a whole is far easier to discuss. An edit in a two-hundred-line file is a precise request and a precise answer. An edit in a thousand-line file is a paraphrase and a hope.
What shouldn't be there
app/
├── utils.py ← 900 lines of everything
├── helpers.py ← the same thing, but different
├── main_v2.py ← the previous version, might come in handy
├── test.py ← not a test, a "let's try it"
└── new/ ← we're rewriting, but let's keep the old one for nowEvery line here is a deferred decision. utils and helpers are "I'll
sort it out later". main_v2 and new/ are "I'll delete it later".
Neither ever happens, but every request to an AI now has to explain which
files are the real ones.
The old doesn't live next to the new: git exists for that (article 5.1).
What to tell the AI
Here is my project's structure:
<folder tree>
I'm adding a feature: <which>.
Say which files the code will go into and which files will
have to change. Name new files by my scheme.
Don't write the code yet.That request is worth making a habit: it takes a minute and removes the commonest cause of mess — code put in the wrong place.
Project structure: <tree>.
Rules: models imports nothing, services imports models,
routes imports services.
Check the file you just wrote against those rules.I have a 900-line file: <contents or description>.
Propose which files to split it into and on what basis.
One phrase per file saying what it's responsible for.
Don't move the code yet.The project has a utils.py with everything in it.
Distribute its contents across the existing folders.
For each function, say where and why.
If something really is shared, leave it, but name it more
precisely.What to look for
The AI proposed a new folder next to an existing one. helpers/ while
services/ is alive, db/ while models/ is alive. A sign that the
structure wasn't named in the request.
Two arrangements mixed. models/ and weather/ in one folder.
utils is growing. Every function put there is a deferred decision.
Files with versions in the name. main_v2, app_new, index_final.
Git exists for that.
Tests mixed in with the code. A separate tests/ folder alongside, not
a test.py in every folder.
One file grows with every request. If the AI adds a fourth unrelated function to the same file, it is time to split.
Cheat sheet
The folder structure answers "where do I put this new code".
By kind or by feature — choose by what changes as a whole more often.
Don't mix the two arrangements: you get two answers to one question.
Expensive to redo: the layer boundary, the root package's name, where the data lives.
One file, one reason to change; 300–500 lines as a signal.
A file that fits into a request is many times easier to discuss.
Write the structure down as text in the repository and paste it into requests.
utils,main_v2,new/are deferred decisions, not folders.