|
| 1 | +--- |
| 2 | +status: accepted |
| 3 | +date: 2026-06-01 |
| 4 | +deciders: Filip Podstavec |
| 5 | +consulted: Claude Opus 4.8 (brainstorming session) |
| 6 | +informed: stack users |
| 7 | +--- |
| 8 | + |
| 9 | +# 0010. Naming convention: detect-and-conform, not a prescribed house style |
| 10 | + |
| 11 | +## Context and Problem Statement |
| 12 | + |
| 13 | +The stack documents how code should be *shaped* (`Write less, fit in`) but |
| 14 | +says almost nothing about how identifiers should be *named*, beyond a single |
| 15 | +clause — "Naming … should look like the rest of the module." Naming is one of |
| 16 | +the highest-signal factors in whether the *next* agent can safely modify a |
| 17 | +codebase, and AI agents have a specific, repeatable failure mode here: |
| 18 | + |
| 19 | +1. **Casing drift.** A model carries a strong language-default prior |
| 20 | + (Python → `snake_case`, JS → `camelCase`). Dropped into a client repo that |
| 21 | + deviates from that default, it silently imposes its own style, so new code |
| 22 | + reads as "the AI-written part" and the repo's casing fragments. |
| 23 | +2. **Wrong granularity.** Generated names land at the wrong altitude — either |
| 24 | + too vague (`get()`, `data()`, `handle()`) or implementation-leaking and |
| 25 | + verbose (`getting_data_from_mobile()`). Both make the call site harder to |
| 26 | + read than a name pitched at the function's actual intent. |
| 27 | + |
| 28 | +The open question is **what kind** of naming guidance the stack should carry: |
| 29 | +its own opinionated rules, or a discipline for conforming to whatever the |
| 30 | +target repo already does. |
| 31 | + |
| 32 | +## Decision Drivers |
| 33 | + |
| 34 | +- The stack's north star is **"fit in"** — new code should be |
| 35 | + indistinguishable from the surrounding module. A house style that overrides |
| 36 | + the repo's existing convention directly contradicts that. |
| 37 | +- Client repos are heterogeneous: the same agent works in `camelCase`, |
| 38 | + `snake_case`, and `kebab-case` repos in the same week. A single prescribed |
| 39 | + style would be wrong in most of them. |
| 40 | +- Even within one repo, casing legitimately differs **by kind** (types |
| 41 | + `PascalCase`, functions `camelCase`/`snake_case`, constants `UPPER_SNAKE`), |
| 42 | + so a flat global rule cannot be right. |
| 43 | +- Must stay lean per [ADR 0009](0009-agents-md-lean-budget-and-size-tiers.md): |
| 44 | + inline only the always-true principle; push depth to `docs/`. |
| 45 | + |
| 46 | +## Considered Options |
| 47 | + |
| 48 | +1. **Prescribed house style.** The stack ships a naming-rules table every repo |
| 49 | + must follow. Rejected: directly violates "fit in", and is wrong in any repo |
| 50 | + whose existing convention differs — which is most of them. |
| 51 | +2. **Stay implicit.** Rely on the existing "Naming should look like the rest of |
| 52 | + the module" clause. Rejected: it states the goal but not the *discipline* |
| 53 | + (detect first, don't impose your default) and ignores the granularity axis |
| 54 | + entirely, so both failure modes above survive. |
| 55 | +3. **Detect-and-conform discipline + a universal quality principle. Selected.** |
| 56 | + Casing/separator style is treated as repo-specific — the agent must *detect* |
| 57 | + the existing convention (per kind, per local module) and conform, never |
| 58 | + impose its language default. Granularity/clarity is treated as a universal |
| 59 | + principle — name to the function's intent, neither vague nor rambling. |
| 60 | + |
| 61 | +## Decision Outcome |
| 62 | + |
| 63 | +**Chosen: Option 3.** Two layers, with deliberately different epistemics: |
| 64 | + |
| 65 | +- **Casing / separator → descriptive.** There is no stack-wide "correct" |
| 66 | + style; the correct style is whatever the surrounding code already uses. The |
| 67 | + agent detects it (scan sibling files / nearby identifiers; in a mixed repo, |
| 68 | + match the *local* module over the global majority) and conforms per kind. |
| 69 | +- **Granularity / clarity → universal.** Independent of the repo: a name states |
| 70 | + intent at the right altitude — neither `get()` nor |
| 71 | + `getting_data_from_mobile()`. This is the one prescriptive bit, and it is |
| 72 | + about *quality*, not *style*. |
| 73 | + |
| 74 | +Anchored as a tight `Name to fit in` bullet inline in the `Write less, fit in` |
| 75 | +section of both the root `AGENTS.md` and the shipped |
| 76 | +`templates/AGENTS.md.example`, with the detection mechanics and worked examples |
| 77 | +in [`docs/conventions.md`](../conventions.md) per the ADR 0009 lean budget. |
| 78 | + |
| 79 | +### Consequences |
| 80 | + |
| 81 | +**Positive:** |
| 82 | +- Closes both AI naming failure modes (casing drift, wrong granularity) with a |
| 83 | + rule that reinforces "fit in" instead of fighting it. |
| 84 | +- Correct across heterogeneous client repos by construction — there is no |
| 85 | + global style to be wrong about. |
| 86 | + |
| 87 | +**Negative / costs:** |
| 88 | +- "Detect the convention" is softer than a lookup table; in a genuinely |
| 89 | + inconsistent repo the agent must exercise judgment about which local example |
| 90 | + to follow. Accepted — that judgment is the point, and a wrong-but-uniform |
| 91 | + house style would be worse. |
| 92 | +- One more always-on bullet in `AGENTS.md` (~0.3 KiB), kept within the 8 KiB |
| 93 | + budget by housing the depth in `docs/conventions.md`. |
| 94 | + |
| 95 | +## References |
| 96 | + |
| 97 | +- [ADR 0009](0009-agents-md-lean-budget-and-size-tiers.md) — the lean budget |
| 98 | + that dictates inline-principle / docs-depth placement. |
| 99 | +- [ADR 0002](0002-agents-md-canonical-claude-md-import.md) — why AGENTS.md is |
| 100 | + the canonical surface the principle lands in. |
| 101 | +- `AGENTS.md` ("Write less, fit in") and `docs/conventions.md` ("Naming") — the |
| 102 | + convention as carried in this repo. |
| 103 | +- `templates/AGENTS.md.example` — the convention as shipped to client repos. |
0 commit comments