syntaxai/tdd.md · commit 043b835

edit content/sama/skill.md via web

Submitted by syntaxai via the tdd.md self-hosted editor.
author
syntaxai <[email protected]>
date
2026-05-22 08:42:55Z
parent
c9e085a
commit
043b835baaa42fc2be8fcbbbd890b96f0a1fef77

1 file changed · +193 −191

modified content/sama/skill.md +193 −191
@@ -1,191 +1,193 @@
1----
2-name: sama-architecture
3-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.
4----
5-
6-# SAMA — Sorted, Architecture, Modeled, Atomic
7-
8-## Overview
9-
10-A four-property file-naming and module-organisation convention so files sort by their dependency layer and one grep proves the structure.
11-
12-**Core principle:** pick the layer first, then the name, then the code.
13-
14-**The Iron Rule:** lower-numbered layers NEVER import from higher-numbered ones.
15-
16-## When to Use
17-
18-**Always, when:**
19-- Adding a new source file
20-- Moving a function between files
21-- Splitting a file that has grown past ~700 lines
22-- Reviewing a diff for layer violations
23-
24-**Exceptions (ask your human partner):**
25-- Generated code
26-- Vendored third-party files
27-
28-Thinking "this one helper doesn't need a prefix"? Stop. That's how the rule erodes.
29-
30-## The Iron Rule
31-
32-```
33-UI SITS AT THE EDGE — FOUNDATION, DATA AND LOGIC LAYERS NEVER DEPEND ON UI
34-```
35-
36-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.
37-
38-Verify with one grep:
39-
40-```bash
41-grep -rE 'from "\./c[5-9]' src/c1*.ts src/c3*.ts
42-```
43-
44-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.
45-
46-## The Four Letters
47-
48-### S — Sorted
49-
50-Alphabetical sort = dependency direction. `ls src/` is the architecture diagram. The file system is the contract.
51-
52-### A — Architecture
53-
54-The prefix number is the layer; the layer is the contract.
55-
56-| prefix | layer | what's allowed | what's not |
57-|---|---|---|---|
58-| `c11_*` | server entry | env, `Bun.serve()`, port wiring | route logic, SQL, HTML |
59-| `c13_*` | database | SQLite queries, schema | HTTP, HTML, route handling |
60-| `c14_*` | secondary I/O | HTTP clients (GitHub, mail, etc.) | SQL, business logic |
61-| `c21_*` | handlers | route handlers, composes lower | direct SQL, raw HTTP, model defs |
62-| `c31_*` | models | types, parsers, pure data helpers | I/O of any kind, side effects |
63-| `c32_*` | logic | pure business logic, deterministic | I/O, randomness, time without injection |
64-| `c51_*` | UI | HTML rendering, page chrome | data fetching, mutations |
65-
66-Numbers are spaced. Future layers land between existing ones without renaming the world.
67-
68-### M — Modeled
69-
70-Tests live next to source. Types and parse-functions live in `c31_*`.
71-
72-```
73-src/c32_session.ts impl
74-src/c32_session.test.ts proof
75-```
76-
77-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.
78-
79-### A — Atomic
80-
81-One responsibility per module. When a layer file passes ~700 lines, split per UI/data domain using the same prefix:
82-
83-```
84-c51_render_layout.ts chrome (renderPage, escape, ...)
85-c51_render_projects.ts /projects body builders
86-c51_render_reports.ts /reports body builders
87-```
88-
89-**No barrel re-exports.** Consumers import directly from the atom.
90-
91-## Picking the Right Layer
92-
93-Decide in this order:
94-
95-1. Does it perform I/O? → `c13` (SQL) or `c14` (HTTP).
96-2. Is it pure types or parsers? → `c31`.
97-3. Is it pure logic deriving one value from others? → `c32`.
98-4. Does it produce HTML? → `c51`.
99-5. Is it a route handler that composes the above? → `c21`.
100-
101-Cannot fit? The file does more than one job. Split it.
102-
103-## Good vs Bad
104-
105-<Good>
106-```ts
107-// src/c21_handlers_projects.ts
108-import { parseProjectConfig } from "./c31_project_config.ts";
109-import { upsertProject } from "./c13_database.ts";
110-
111-const config = parseProjectConfig(await req.json());
112-await upsertProject(viewer.login, config);
113-```
114-Handler composes lower layers. Parser in c31 handles untyped input. SQL stays in c13.
115-</Good>
116-
117-<Bad>
118-```ts
119-// src/c21_handlers_projects.ts
120-const raw = await req.json() as { test_runner: string };
121-const stmt = db.prepare("INSERT INTO projects ...");
122-stmt.run(raw.test_runner);
123-```
124-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.
125-</Bad>
126-
127-## Common Rationalizations
128-
129-| Excuse | Reality |
130-|---|---|
131-| "This helper is generic, no specific layer" | Every helper has callers. The lowest caller's layer is its home. |
132-| "I'll add the prefix later" | You won't. Add it now. |
133-| "A barrel makes imports cleaner" | A barrel hides the dependency direction the grep proves. |
134-| "It's still under 700 lines" | The threshold isn't the only signal. If you'd want a TOC, split. |
135-| "Tests in a parallel tree are fine" | Then deletes orphan their tests. Move them next to source. |
136-| "An `as` cast is faster than a parser" | Until the input lies and you debug for hours. Write the parser. |
137-| "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. |
138-
139-## Red Flags — STOP
140-
141-- A `from "./cXX_..."` import where the source's prefix is HIGHER than the target's prefix.
142-- A `cXX_*.ts` without a sibling `cXX_*.test.ts` and it's not pure data.
143-- A file over 700 lines with multiple unrelated functions.
144-- A `c51_render.ts` or similar that re-exports from per-domain files.
145-- An `as Foo` cast on data from outside the process.
146-- A function defined in one file but logically belonging to another layer.
147-
148-**All of these mean: stop, fix the layout before adding more code.**
149-
150-## Verification Checklist
151-
152-Before merging:
153-
154-- [ ] `grep -rE 'from "\./c[5-9]' src/c1*.ts src/c2*.ts src/c3*.ts` returns empty
155-- [ ] every non-data `cXX_*.ts` has a sibling `cXX_*.test.ts`
156-- [ ] no file in `src/` over 700 lines without a per-domain split rationale
157-- [ ] no barrel re-export files (`cXX_render.ts`, `cXX_handlers.ts`, `index.ts` in src/)
158-- [ ] no `as` cast on data from outside the process
159-
160-## Examples
161-
162-### Adding a new feature
163-
164-A new "/exports" feature that lets a user download their kata results as JSON.
165-
166-1. **Type + parser** for the export shape → `c31_export.ts` (with `c31_export.test.ts` next to it).
167-2. **Logic** that turns runs into the export shape → `c32_export.ts` (pure, no I/O).
168-3. **Handler** at `/exports` → add to `c21_app.ts` (or new `c21_handlers_exports.ts` if it grows).
169-4. **Render** the JSON page → if there's an HTML index of past exports, that goes in `c51_render_exports.ts`.
170-
171-Four files, four layers, every dependency points DOWN. Run the grep. Empty? Done.
172-
173-### Refactoring an oversized file
174-
175-`c51_render_reports.ts` has grown to 850 lines covering `/reports`, `/reports/demo`, and `/reports/live`. Split per sub-domain:
176-
177-```
178-c51_render_reports.ts → kept: shared helpers (sparkline, tile, bars)
179-c51_render_reports_demo.ts ← new: demo-specific body builders
180-c51_render_reports_live.ts ← new: live-specific body builders
181-```
182-
183-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.
184-
185-## Reference
186-
187-- The four disciplines with examples: https://tdd.md/sama
188-- Why SAMA compounds with TDD and token-discipline: https://tdd.md/blog/three-constraints-agentic-coding
189-- The Reddit harness postmortem this skill is a response to: https://tdd.md/blog/claude-code-harness-postmortem
190-- Ten more threads, three patterns, mitigation tables: https://tdd.md/blog/agentic-coding-corpus-three-patterns
191-- Mechanically verify any public repo against these rules: https://tdd.md/sama/verify
1+---
2+name: sama-architecture
3+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.
4+---
5+
6+# SAMA — Sorted, Architecture, Modeled, Atomic
7+
8+## Overview
9+
10+A four-property file-naming and module-organisation convention so files sort by their dependency layer and one grep proves the structure.
11+
12+**Core principle:** pick the layer first, then the name, then the code.
13+
14+**The Iron Rule:** lower-numbered layers NEVER import from higher-numbered ones.
15+
16+## When to Use
17+
18+**Always, when:**
19+- Adding a new source file
20+- Moving a function between files
21+- Splitting a file that has grown past ~700 lines
22+- Reviewing a diff for layer violations
23+
24+**Exceptions (ask your human partner):**
25+- Generated code
26+- Vendored third-party files
27+
28+Thinking "this one helper doesn't need a prefix"? Stop. That's how the rule erodes.
29+
30+## The Iron Rule
31+
32+```
33+UI SITS AT THE EDGE — FOUNDATION, DATA AND LOGIC LAYERS NEVER DEPEND ON UI
34+```
35+
36+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.
37+
38+Verify with one grep:
39+
40+```bash
41+grep -rE 'from "\./c[5-9]' src/c1*.ts src/c3*.ts
42+```
43+
44+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.
45+
46+## The Four Letters
47+
48+### S — Sorted
49+
50+Alphabetical sort = dependency direction. `ls src/` is the architecture diagram. The file system is the contract.
51+
52+### A — Architecture
53+
54+The prefix number is the layer; the layer is the contract.
55+
56+| prefix | layer | what's allowed | what's not |
57+|---|---|---|---|
58+| `c11_*` | server entry | env, `Bun.serve()`, port wiring | route logic, SQL, HTML |
59+| `c13_*` | database | SQLite queries, schema | HTTP, HTML, route handling |
60+| `c14_*` | secondary I/O | HTTP clients (GitHub, mail, etc.) | SQL, business logic |
61+| `c21_*` | handlers | route handlers, composes lower | direct SQL, raw HTTP, model defs |
62+| `c31_*` | models | types, parsers, pure data helpers | I/O of any kind, side effects |
63+| `c32_*` | logic | pure business logic, deterministic | I/O, randomness, time without injection |
64+| `c51_*` | UI | HTML rendering, page chrome | data fetching, mutations |
65+
66+Numbers are spaced. Future layers land between existing ones without renaming the world.
67+
68+### M — Modeled
69+
70+Tests live next to source. Types and parse-functions live in `c31_*`.
71+
72+```
73+src/c32_session.ts impl
74+src/c32_session.test.ts proof
75+```
76+
77+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.
78+
79+### A — Atomic
80+
81+One responsibility per module. When a layer file passes ~700 lines, split per UI/data domain using the same prefix:
82+
83+```
84+c51_render_layout.ts chrome (renderPage, escape, ...)
85+c51_render_projects.ts /projects body builders
86+c51_render_reports.ts /reports body builders
87+```
88+
89+**No barrel re-exports.** Consumers import directly from the atom.
90+
91+## Picking the Right Layer
92+
93+Decide in this order:
94+
95+1. Does it perform I/O? → `c13` (SQL) or `c14` (HTTP).
96+2. Is it pure types or parsers? → `c31`.
97+3. Is it pure logic deriving one value from others? → `c32`.
98+4. Does it produce HTML? → `c51`.
99+5. Is it a route handler that composes the above? → `c21`.
100+
101+Cannot fit? The file does more than one job. Split it.
102+
103+## Good vs Bad
104+
105+<Good>
106+```ts
107+// src/c21_handlers_projects.ts
108+import { parseProjectConfig } from "./c31_project_config.ts";
109+import { upsertProject } from "./c13_database.ts";
110+
111+const config = parseProjectConfig(await req.json());
112+await upsertProject(viewer.login, config);
113+```
114+Handler composes lower layers. Parser in c31 handles untyped input. SQL stays in c13.
115+</Good>
116+
117+<Bad>
118+```ts
119+// src/c21_handlers_projects.ts
120+const raw = await req.json() as { test_runner: string };
121+const stmt = db.prepare("INSERT INTO projects ...");
122+stmt.run(raw.test_runner);
123+```
124+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.
125+</Bad>
126+
127+## Common Rationalizations
128+
129+| Excuse | Reality |
130+|---|---|
131+| "This helper is generic, no specific layer" | Every helper has callers. The lowest caller's layer is its home. |
132+| "I'll add the prefix later" | You won't. Add it now. |
133+| "A barrel makes imports cleaner" | A barrel hides the dependency direction the grep proves. |
134+| "It's still under 700 lines" | The threshold isn't the only signal. If you'd want a TOC, split. |
135+| "Tests in a parallel tree are fine" | Then deletes orphan their tests. Move them next to source. |
136+| "An `as` cast is faster than a parser" | Until the input lies and you debug for hours. Write the parser. |
137+| "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. |
138+
139+## Red Flags — STOP
140+
141+- A `from "./cXX_..."` import where the source's prefix is HIGHER than the target's prefix.
142+- A `cXX_*.ts` without a sibling `cXX_*.test.ts` and it's not pure data.
143+- A file over 700 lines with multiple unrelated functions.
144+- A `c51_render.ts` or similar that re-exports from per-domain files.
145+- An `as Foo` cast on data from outside the process.
146+- A function defined in one file but logically belonging to another layer.
147+
148+**All of these mean: stop, fix the layout before adding more code.**
149+
150+## Verification Checklist
151+
152+Before merging:
153+
154+- [ ] `grep -rE 'from "\./c[5-9]' src/c1*.ts src/c2*.ts src/c3*.ts` returns empty
155+- [ ] every non-data `cXX_*.ts` has a sibling `cXX_*.test.ts`
156+- [ ] no file in `src/` over 700 lines without a per-domain split rationale
157+- [ ] no barrel re-export files (`cXX_render.ts`, `cXX_handlers.ts`, `index.ts` in src/)
158+- [ ] no `as` cast on data from outside the process
159+
160+## Examples
161+
162+### Adding a new feature
163+
164+A new "/exports" feature that lets a user download their kata results as JSON.
165+
166+1. **Type + parser** for the export shape → `c31_export.ts` (with `c31_export.test.ts` next to it).
167+2. **Logic** that turns runs into the export shape → `c32_export.ts` (pure, no I/O).
168+3. **Handler** at `/exports` → add to `c21_app.ts` (or new `c21_handlers_exports.ts` if it grows).
169+4. **Render** the JSON page → if there's an HTML index of past exports, that goes in `c51_render_exports.ts`.
170+
171+Four files, four layers, every dependency points DOWN. Run the grep. Empty? Done.
172+
173+### Refactoring an oversized file
174+
175+`c51_render_reports.ts` has grown to 850 lines covering `/reports`, `/reports/demo`, and `/reports/live`. Split per sub-domain:
176+
177+```
178+c51_render_reports.ts → kept: shared helpers (sparkline, tile, bars)
179+c51_render_reports_demo.ts ← new: demo-specific body builders
180+c51_render_reports_live.ts ← new: live-specific body builders
181+```
182+
183+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.
184+
185+## Reference
186+
187+- The four disciplines with examples: https://tdd.md/sama
188+- Why SAMA compounds with TDD and token-discipline: https://tdd.md/blog/three-constraints-agentic-coding
189+- The Reddit harness postmortem this skill is a response to: https://tdd.md/blog/claude-code-harness-postmortem
190+- Ten more threads, three patterns, mitigation tables: https://tdd.md/blog/agentic-coding-corpus-three-patterns
191+- Mechanically verify any public repo against these rules: https://tdd.md/sama/verify
192+
193+<!-- git-native proof @ 2026-05-22T08:42:54.742Z -->