---
name: sama-architecture
description: Use when creating, moving, or refactoring any source file in a SAMA codebase. Encodes the four-layer-prefix convention (Sorted, Architecture, Modeled, Atomic) with one mechanical verification grep.
---
# SAMA — Sorted, Architecture, Modeled, Atomic
## Overview
A four-property file-naming and module-organisation convention so files sort by their dependency layer and one grep proves the structure.
**Core principle:** pick the layer first, then the name, then the code.
**The Iron Rule:** lower-numbered layers NEVER import from higher-numbered ones.
## When to Use
**Always, when:**
- Adding a new source file
- Moving a function between files
- Splitting a file that has grown past ~700 lines
- Reviewing a diff for layer violations
**Exceptions (ask your human partner):**
- Generated code
- Vendored third-party files
Thinking "this one helper doesn't need a prefix"? Stop. That's how the rule erodes.
## The Iron Rule
```
UI SITS AT THE EDGE — FOUNDATION, DATA AND LOGIC LAYERS NEVER DEPEND ON UI
```
Foundation/data/logic (`c1*`, `c3*`) must never import UI (`c5*+`). Handlers (`c21`) are the orchestration layer and may compose UI; UI itself may read models for the data it renders.
Verify with one grep:
```bash
grep -rE 'from "\./c[5-9]' src/c1*.ts src/c3*.ts
```
Empty output = rule holds. Any output = a UI dependency has leaked into foundation/data/logic. Move the function or rename the file. Do not "fix" the violation by deleting the import without understanding what broke.
## The Four Letters
### S — Sorted
Alphabetical sort = dependency direction. `ls src/` is the architecture diagram. The file system is the contract.
### A — Architecture
The prefix number is the layer; the layer is the contract.
| prefix | layer | what's allowed | what's not |
|---|---|---|---|
| `c11_*` | server entry | env, `Bun.serve()`, port wiring | route logic, SQL, HTML |
| `c13_*` | database | SQLite queries, schema | HTTP, HTML, route handling |
| `c14_*` | secondary I/O | HTTP clients (GitHub, mail, etc.) | SQL, business logic |
| `c21_*` | handlers | route handlers, composes lower | direct SQL, raw HTTP, model defs |
| `c31_*` | models | types, parsers, pure data helpers | I/O of any kind, side effects |
| `c32_*` | logic | pure business logic, deterministic | I/O, randomness, time without injection |
| `c51_*` | UI | HTML rendering, page chrome | data fetching, mutations |
Numbers are spaced. Future layers land between existing ones without renaming the world.
### M — Modeled
Tests live next to source. Types and parse-functions live in `c31_*`.
```
src/c32_session.ts impl
src/c32_session.test.ts proof
```
External input (JSON, env, request body) goes through a parser in `c31_*` before any logic touches it. Typed values flow downstream from there. **No `as` casts at I/O boundaries** — a cast is a lie the type system promised not to question.
### A — Atomic
One responsibility per module. When a layer file passes ~700 lines, split per UI/data domain using the same prefix:
```
c51_render_layout.ts chrome (renderPage, escape, ...)
c51_render_projects.ts /projects body builders
c51_render_reports.ts /reports body builders
```
**No barrel re-exports.** Consumers import directly from the atom.
## Picking the Right Layer
Decide in this order:
1. Does it perform I/O? → `c13` (SQL) or `c14` (HTTP).
2. Is it pure types or parsers? → `c31`.
3. Is it pure logic deriving one value from others? → `c32`.
4. Does it produce HTML? → `c51`.
5. Is it a route handler that composes the above? → `c21`.
Cannot fit? The file does more than one job. Split it.
## Good vs Bad
```ts
// src/c21_handlers_projects.ts
import { parseProjectConfig } from "./c31_project_config.ts";
import { upsertProject } from "./c13_database.ts";
const config = parseProjectConfig(await req.json());
await upsertProject(viewer.login, config);
```
Handler composes lower layers. Parser in c31 handles untyped input. SQL stays in c13.
```ts
// src/c21_handlers_projects.ts
const raw = await req.json() as { test_runner: string };
const stmt = db.prepare("INSERT INTO projects ...");
stmt.run(raw.test_runner);
```
Handler does its own SQL (belongs in c13). `as` cast at the boundary (belongs as a parser in c31). Two layer violations in three lines.
## Common Rationalizations
| Excuse | Reality |
|---|---|
| "This helper is generic, no specific layer" | Every helper has callers. The lowest caller's layer is its home. |
| "I'll add the prefix later" | You won't. Add it now. |
| "A barrel makes imports cleaner" | A barrel hides the dependency direction the grep proves. |
| "It's still under 700 lines" | The threshold isn't the only signal. If you'd want a TOC, split. |
| "Tests in a parallel tree are fine" | Then deletes orphan their tests. Move them next to source. |
| "An `as` cast is faster than a parser" | Until the input lies and you debug for hours. Write the parser. |
| "I'll re-export through an index for ergonomics" | The five extra characters in the import path are not a real cost. The barrel is. |
## Red Flags — STOP
- A `from "./cXX_..."` import where the source's prefix is HIGHER than the target's prefix.
- A `cXX_*.ts` without a sibling `cXX_*.test.ts` and it's not pure data.
- A file over 700 lines with multiple unrelated functions.
- A `c51_render.ts` or similar that re-exports from per-domain files.
- An `as Foo` cast on data from outside the process.
- A function defined in one file but logically belonging to another layer.
**All of these mean: stop, fix the layout before adding more code.**
## Verification Checklist
Before merging:
- [ ] `grep -rE 'from "\./c[5-9]' src/c1*.ts src/c2*.ts src/c3*.ts` returns empty
- [ ] every non-data `cXX_*.ts` has a sibling `cXX_*.test.ts`
- [ ] no file in `src/` over 700 lines without a per-domain split rationale
- [ ] no barrel re-export files (`cXX_render.ts`, `cXX_handlers.ts`, `index.ts` in src/)
- [ ] no `as` cast on data from outside the process
## Examples
### Adding a new feature
A new "/exports" feature that lets a user download their kata results as JSON.
1. **Type + parser** for the export shape → `c31_export.ts` (with `c31_export.test.ts` next to it).
2. **Logic** that turns runs into the export shape → `c32_export.ts` (pure, no I/O).
3. **Handler** at `/exports` → add to `c21_app.ts` (or new `c21_handlers_exports.ts` if it grows).
4. **Render** the JSON page → if there's an HTML index of past exports, that goes in `c51_render_exports.ts`.
Four files, four layers, every dependency points DOWN. Run the grep. Empty? Done.
### Refactoring an oversized file
`c51_render_reports.ts` has grown to 850 lines covering `/reports`, `/reports/demo`, and `/reports/live`. Split per sub-domain:
```
c51_render_reports.ts → kept: shared helpers (sparkline, tile, bars)
c51_render_reports_demo.ts ← new: demo-specific body builders
c51_render_reports_live.ts ← new: live-specific body builders
```
Or, if the live and demo body builders are mostly the same shape parameterised by a context object: factor the *context* into `c31_reports_context.ts` and keep one `c51_render_reports.ts`. Both are valid. The wrong answer is a barrel.
## Reference
- The four disciplines with examples: https://tdd.md/sama
- Why SAMA compounds with TDD and token-discipline: https://tdd.md/blog/2026-05/three-constraints-agentic-coding
- The Reddit harness postmortem this skill is a response to: https://tdd.md/blog/2026-05/claude-code-harness-postmortem
- Ten more threads, three patterns, mitigation tables: https://tdd.md/blog/2026-05/agentic-coding-corpus-three-patterns
- Mechanically verify any public repo against these rules: https://tdd.md/sama/verify