# Coding Best Practices — C The standard code in this repo is held to. PRs are reviewed against it, and review comments cite it by rule number (`blocker: rule 3, this file is 1,400 lines`). Rule numbers are frozen. New rules append; they never renumber. The PR process itself — size, evidence, comment conventions — is in [`../CONTRIBUTING.md`](../CONTRIBUTING.md). --- ## Rules **1. Descriptive names.** Every name says what it is at a glance — files, functions, types, variables. The test: would you know what it is from the tree alone, without opening it? `fc_tensor_matmul()`, not `lookup()`. Abbreviate only what is universal (`id`, `url`, `http`, `kv`, `ctx`). **2. Small functions — ~100 lines of body.** Doc comments don't count. Past ~100, look for the extraction point. One blank line between logical chunks, never two. **3. Small files — under 1,000 lines of code.** Comments and headers don't count. Prefer more files with fewer functions. Every function in a file must belong there. **4. Library-first.** Write every function as if it will be extracted into a standalone library — self-contained, minimal dependencies, no hard-coded project values in reusable logic. Headers define the contract, sources hold the implementation. Extraction should mean moving files, not rewriting them. **5. Folder nesting is navigation.** Group by domain, not by file type. Each directory has one clear purpose. A directory holding one file is usually wrong. **6. Error transparency.** Never swallow an error. Return a code or log it — never both nothing. No empty error paths. Callers get a typed error, not a guess. **7. Delete dead code.** Don't comment it out, don't leave unused functions or includes. Git has it if you need it back. **8. Prove a test can fail.** A test you have never seen fail is not evidence. Break the code it covers, watch it go red, report the failure count. > **Apple's `make` is 3.81 (2006)** and compares mtimes at one-second granularity. A > source edited in the same second as its object is reported `up to date` and is **not > rebuilt** — so the obvious mutation loop can be running the previous binary and > reporting a vacuous gate that isn't. When proving a test red, `rm` the specific `.o` > **and** the linked binary first. Switching build configs needs `rm -rf build` entirely. --- ## File headers Every source file opens with the SPDX pair, then the header block: ```c /* SPDX-License-Identifier: AGPL-3.0-or-later */ /* Copyright (C) 2026 David (Corey) Herrera */ /** * @file attention.c * @brief Multi-head attention, KV cache, and RoPE. * * @status None * @issues None * @todo None */ ``` `@status` / `@issues` / `@todo` are always present, `None` when empty — the empty slot is the reminder that the slot exists. When you work in a file, clear resolved entries and reset to `None`. **Required on every new file.** Existing coverage is partial and is being backfilled; until that lands, CI enforces the rule on added files only. **Exempt:** test files, generated sources, and third-party-derived files (which keep their upstream attribution — a Foundry block may go below it, never in place of it). --- ## Documentation **Public functions are documented in the header**, which is the API contract: ```c /** * Compute scaled dot-product attention into a caller-owned output tensor. * * @param out Output tensor, shape [seq, dim] * @param q, k, v Query, key, value tensors * @param mask Causal mask, or NULL for full attention * @return 0 on success, negative error code on failure * * Thread-safe: yes (internal locking) * Reentrant: no * Signal-safe: no */ ``` The three safety annotations are always present. `TBD` is an acceptable answer; omitting them is not. Internal helpers need docs only when the name and signature don't carry it. **Inline comments explain why, not what.** The code already says what it does. ```c /* K80 has no __ldg for 8-byte types — split the load or it serialises */ ``` --- ## Layout `include/` and `src/` mirror each other. `snake_case` filenames that name their contents (`tensor_matmul.c`, not `tm.c`). One header + one source per logical unit. Tests mirror the source tree. Every public symbol carries its module prefix — `fc_`, `tm_` (tensor), `ft_` (tokenizer, ternarize). Anything not declared in a header is `static`. No exceptions. --- ## C conventions **Formatting.** 4 spaces, no tabs. 100 columns. `char *ptr` — the star binds to the variable. Opening brace on the **same line**, for functions and control flow alike. ```c int fc_tensor_add(Tensor *out, const Tensor *a, const Tensor *b) { if (!out || !a || !b) return -FC_EINVAL; for (size_t i = 0; i < a->count; i++) { if (!shapes_match(a, b)) return -FC_ESHAPE; } return 0; } ``` Omit braces on single-statement bodies — unless any branch of an `if`/`else` chain needs them, in which case every branch gets them. **Includes**, four groups separated by blank lines: this module's own header first (it catches missing dependencies in that header at compile time), then standard library, then system/platform, then project headers. **Errors.** Functions return `int` (0 success, negative error) or a pointer (`NULL` on failure). Use goto-cleanup for anything acquiring more than one resource — one exit path, deterministic teardown. Error codes are a prefixed enum with a string conversion function. **Memory.** `sizeof(*ptr)`, never `sizeof(Type)`. `calloc` or explicit zeroing. Document ownership in the function doc — who allocates, who frees. Every `_open`/`_create`/`_new` has a matching `_close`/`_destroy`/`_free`. `free(p); p = NULL;`. Arena-allocate groups sharing a lifetime. Never return a stack address. **const.** Every pointer parameter is `const` unless the function must modify what it points at. **Strings and buffers.** `sprintf`, `strcpy`, `strcat`, `gets`, and unbounded `scanf("%s")` are banned — use `snprintf`, `strlcpy`, `strlcat`, `fgets`, width-limited `scanf`. Always pass buffer sizes explicitly; check the `snprintf` return for truncation. **Initialize everything.** `int rc = 0;`, `char *buf = NULL;`, `struct foo f = {0};`. Declare close to first use, except where goto-cleanup requires it earlier. **Opaque types** at public API boundaries — the struct definition stays in the `.c`. Callers go through the API, never into fields. **Header guards** use the project prefix (`FOUNDRY_ATTENTION_H`), with the guard name repeated in the `#endif` comment, and `extern "C"` in every public header. **Asserts** document invariants that must never be false if the code is correct. Don't assert things that legitimately fail — I/O, allocation, user input — those get error codes. **Compiler.** The standard, modelled on SQLite and comparable C projects: ``` -std=c11 -Wall -Wextra -Werror -pedantic ``` plus `-Wshadow`, `-Wstrict-prototypes`, `-Wformat=2`, `-Wnull-dereference`, `-Wconversion`, `-fstack-protector-strong`. Zero warnings, always. The build does not yet meet this — it is `-Wall -Wextra -std=c11` today. The standard above is the target the code is held to, not a description of the current Makefile. --- ## Logging | Level | Use | |---|---| | `ERROR` | operation failed, cannot continue | | `WARN` | unexpected but recoverable | | `INFO` | major state transitions, operation boundaries | | `DEBUG` | decision points — which branch, and why | | `TRACE` | entry/exit, raw values, wire data | `DEBUG` and `TRACE` compile out in release builds. Log at boundaries and decisions, never inside hot loops at `INFO` or above. Never log secrets, raw binary, or the success of trivial operations. One line per message, greppable, with timestamp, level, and source location. The test: if you would add a `printf` to debug it, a `log_debug` should already be there.