edit content/sama/skill.md via web
Submitted by syntaxai via the tdd.md self-hosted editor.
1 file changed · +193 −191
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 --> | |