Step One — Set
Standards
The orders the work is done under.
Tiger Style is Qompass AI's standing orders for code. Safety first, everything bounded, nothing hidden — in every language we ship.
These standards come first. They are the terms under which work is judged: how claims are weighed in Analysis, what counts as a result worth recording in Field Notes, and what may stand. When promoted Research lands, it returns here — and these standards are revised.
RESPECT WHERE DUE
This standard descends from TigerStyle, the coding standard written for TigerBeetle — a database that cannot afford to be wrong — and, behind it, NASA JPL's Power of Ten, Gerard J. Holzmann's rules for developing safety-critical code. It is adapted here with respect, and the debt is stated plainly.
What Qompass takes from both: determinism over cleverness — explicit limits, bounded work, deterministic interfaces. A program should verify itself while it runs, and shut down rather than run wrong. And doing it right the first time, because production rarely grants a second draft.
THE ORDERS THEMSELVES
Safety, then performance, then convenience
The priority order, always. When the three compete — and they compete daily — safety wins outright, performance is argued with measurements, and developer experience takes what is left.
Priority order, always: Safety > Performance > Developer Experience.
— the Rust guide, below
Contracts first
Every substantial operation answers, before it is written: accepted and rejected inputs, the trust boundary, the maximum work, memory, output, and time, the owner of every allocation and handle, and what failure and cancellation do. If the contract cannot be stated, the operation is not understood yet — and is not written yet.
Bound everything
Limits are named constants carrying units — output_bytes_max, retry_count, queue_capacity, deadline — and index ranges are validated with checked arithmetic before an end is computed. A bound that lives only in someone's head is not a bound.
No unbounded loops, retries, queues, or growth.
— the Rust guide, below
Errors are data; assertions are law
Invalid input, configuration, or protocol data gets a typed error with bounded context; the caller owns recovery. A corrupt internal invariant gets an assertion — the program verifies itself while it runs and stops rather than run wrong. Nothing unwraps external data, and no required mutation hides inside an assertion.
Paired tools
No guide ships alone. Each names its toolchain — language server, formatter, linter, debugger — so the standard is enforced by machines, not taste. The pairing is in every chapter below: rust-analyzer, clippy, and rustfmt for Rust; zls and zig fmt for Zig; shellcheck and shfmt for Bash.
THE ORDERS, BY LANGUAGE
Seventeen chapters — the operational core of each language's guide, verbatim. Open one and read it here; the full reference behind each chapter is one link further, on its own page.
AsepriteSafety-first Aseprite — headless CLI and Lua pipelines with explicit contracts, version guards, and every flag verified against official docs.
Tiger Style Aseprite
Prerequisite:
tiger-style-lua(sibling skill at../tiger-style-lua/SKILL.md) — apply it first to every Lua script this skill produces or reviews, including Lua embedded in generated Aseprite scripts.
Purpose
Automate Aseprite the way its docs describe it: the CLI for exports and
introspection, Lua scripts for pixel-level work, always in batch mode for
headless pipelines. Safety order, always: Safety > Performance >
Developer Experience. Every flag and API member used here was verified
against the official docs on 2026-09-27; anything the docs do not say is
marked unverified. references/aseprite-cli.md and
references/aseprite-scripting-api.md hold the complete verified tables —
this file is the operational core.
Official sources:
- CLI: https://www.aseprite.org/docs/cli/
- Scripting overview: https://www.aseprite.org/docs/scripting/
- API index: https://www.aseprite.org/api/
- API changes (version guards): https://aseprite.com/api/Changes
- LibreSprite scripting API: https://github.com/libresprite/libresprite/blob/HEAD/SCRIPTING.md
Workflow
- Detect the binary and probe it. Never assume
asepriteis installed, on PATH, or the right version. - Prefer direct CLI flags when they cover the job (
--save-as,--sheet,--tag,--split-layers,--scale); reach for--scriptonly when pixel-level logic is required. - Run everything in batch mode (
-b). Guard every script with the batch contract below. - Write generated Lua to a temp file, invoke with an argv array, enforce a bounded timeout, check the exit code, clean up the temp file.
- Version-guard every API feature younger than the minimum supported
Aseprite with
app.apiVersion. Never use a deprecated alias. - Treat LibreSprite as a different product: consult the divergence table in
references/aseprite-scripting-api.mdbefore running any Aseprite-1.3 script or flag against it.
Operating Rules
Binary detection and probing
- Search, in order:
ASEPRITE_BIN, thenNVIM_ASEPRITE_BIN(diver convention), then executable candidates on PATH. Record which one won and report it. - Probe with an argv array, never a shell string:
{ bin, '--version' }. Bound stdout (a few KB) and time (10 s). - Parse the version from stdout; compare with
Versionsemantics. Require Aseprite ≥ 1.3 for any feature listed as v1.3+ in the references, and LibreSprite ≥ 1.1 only for the LibreSprite-safe subset. - If no binary is found, stop and say so. Do not download Aseprite silently; a download needs explicit approval and a checksum/signature note.
CLI invocation contract
- Always an argv array (one element per argument), never an interpolated shell string. Paths with spaces stay single elements.
- Always
-b(batch) for automation. GUI launches need explicit approval. - Option order matters. Selection/export options (
--tag,--frame-range,--layer,--ignore-layer,--all-layers,--split-layers,--split-tags,--split-slices) apply to the next sprite opened on the command line: place them before the.asepritefile.--script-param name=valuegoes before--script script.lua.--scaleaffects sprites opened before the option. - Bound every run: an explicit timeout (default 120 s for exports; longer only with a named reason) and bounded stdout/stderr capture.
- Exit-code contract:
0means success. Any non-zero exit, or a Lua error printed to stderr, means the run failed — even if some output files exist. Verify expected output files exist after a0exit before reporting success. --sheetoverwrites its output file.--data ""prints sheet JSON to stdout.--save-as frame001.pngwrites a numbered PNG sequence.- Temp-file hygiene: generated scripts go to a temp file with a distinctive
suffix (e.g.
_diver_bgremove.lua), and are removed after the run whether it succeeds or fails. - Do not pass
--version-unverified flags. The full verified flag table is inreferences/aseprite-cli.md; notably--split-gridand--export-tilesetrequire v1.3-beta21+, and fractional--scalefactors are unverified (the docs show integer examples only).
Lua script contract (batch mode)
Every script run via -b --script must satisfy all of these:
- No UI, ever.
app.isUIAvailableisfalsein batch mode.Dialog()returnsnilthere (since v1.2.35). Do not callDialog,app.alert, orapp.tip— report status withprint()to stdout and failures witherror()oros.exit(1)(non-zero exit is the failure signal). - Input through
app.params. CLI--script-param name=valuepairs arrive as strings inapp.params. Validate and coerce every param at the top of the script; fail loudly on missing/invalid values. app.spritecan benil. Check it before touching it. When the CLI opens the input file (-b input.aseprite --script s.lua), that sprite becomes active — but never assume it.- Color mode first. Pixel math with
app.pixelColor.rgbaR/G/B/Aassumes RGBA. Readsprite.colorModeand compare againstColorMode.RGBbefore interpreting pixels; indexed images hold palette indices, not channel bytes. RGB components are not premultiplied by alpha. - Transactions for edits. Wrap document mutations in
app.transaction([label], function() ... end)— it groups undo and rolls back the whole change on error. Since v1.3.16, property changes outside a transaction do not generate undo steps, so edits that must be undoable (or atomic) belong inside one. - No destructive action without confirmation.
sprite:close()does not prompt;saveAs()marks the document saved and retargets it. Scripts that must not disturb the open document usesaveCopyAs(). In interactive contexts, confirm before overwriting user files; in batch pipelines the explicit output path in the invocation is the confirmation — and it must be named, never defaulted to the input path. - Version guards. Use
app.apiVersion(number;nilon the very first scripting builds) for API features, andapp.version(aVersionobject) for app behavior. The per-version feature table is inreferences/aseprite-scripting-api.md. Never use a deprecated alias (app.activeSprite,app.activeLayer,app.activeFrame,app.activeCel,app.activeImage,app.activeTag,app.activeTool,app.activeBrush,putPixel/putImage/putSprite). - Stdlib limits. Aseprite embeds Lua 5.3 libraries;
os.exitandos.tmpnameare unavailable, andos.execute/io.openmay ask the user for permission — avoid them in batch scripts. - Deterministic pixel work. For raw pixels prefer
Image:pixels()iteration and the clone→modify→cel.image = imgpattern (the image setter is the documented single-undoable-action replacement) overapp.useTool, which depends on thin batch-mode editor state.
LibreSprite boundary
LibreSprite 1.1 is not API-compatible with Aseprite 1.3 scripts:
- Different
appsurface:app.activeSprite/app.activeLayerNumber/app.activeImage/app.activeFrameNumber(the names Aseprite deprecated),app.createDialog()instead ofDialog(),app.versionreturns a string, and there is no documentedapp.apiVersion,app.params, orapp.isUIAvailable. - Different
Image: onlygetPixel/putPixel,getImageData/putImageData,getPNGData,clear(color)— nopixels()iterator, nodrawPixel,saveAs,resize,flip, orshrinkBounds. - Different
Sprite:saveAs(fileName, asCopy),commit(),layer(n)(0-indexed from bottom), nosaveCopyAs, no transactions named as such. - No
--script-paramin its scripting docs. CLI batch/script support (-b,--script) is corroborated by third-party source reading, not official LibreSprite docs — verify withlibresprite --helpon the real binary before relying on it.
Write a compat shim (feature-detect, never version-sniff LibreSprite) when
one pipeline must serve both, or keep two scripts. The full divergence
table is in references/aseprite-scripting-api.md.
Diver wiring (games/aseprite)
- Binary lookup:
ASEPRITE_BIN,NVIM_ASEPRITE_BIN, then PATH candidates (autil.find_binary()). - Established export argv shapes:
-b <sprite> --sheet <png> --data <json> --format json-array;--save-asfor PNG sequences, GIF, and palette output;--sheet-type packed|rows|columns|horizontal|vertical. - The AI import pipeline generates a background-keying Lua script run as
{ bin, '-b', input, '--script', script_path }. Known gaps to fix when touching it: the generated script callsapp.alert()on a nil sprite (invalid in batch mode — replace withprint+ non-zero exit) and assumes RGBA pixels without checkingsprite.colorMode. Palette quantization is manual on the Aseprite path (ImageMagick fallback does keying + quantize + point filter, bounded at 64 files / 16 MB / 120 s). - Default config: 128 px frames, background-key options, palette size,
auto-open behavior,
scale_via_cli. Fractional--scaleis unverified — keep integer factors until the docs say otherwise.
Review checklist
- Binary detected and version probed; the winning path is reported.
-
Every CLI flag is in
references/aseprite-cli.md(or verified in the docs for the probed version). - argv arrays everywhere; bounded timeout and output; exit code checked; output files verified after exit 0.
-
Options ordered correctly (selection filters before the input file;
--script-parambefore--script). -
Script has no UI calls in batch mode; input via
app.params, validated; status viaprint, failure via non-zero exit. -
app.spritenil-checked;colorModechecked before pixel math. -
Mutations inside
app.transaction;saveCopyAsused where the open document must be undisturbed. -
app.apiVersionguards on every post-1.3-beta API feature; no deprecated aliases. - LibreSprite divergence consulted if the target might be LibreSprite.
- Temp scripts removed after the run, success or failure.
ReferencesAseprite · Aseprite CLI docs · Aseprite scripting docs
BashSafety-first Bash — strict mode, every expansion quoted, bounds on every loop and retry, and exit statuses checked, never assumed.
Tiger Style Bash
Purpose
Apply the Tiger Style standard to Bash scripts. Priority order, always: Safety > Performance > Developer Experience. The full guide lives in references/TIGER_STYLE_BASH.md; this file is the operational core. When the two disagree, the full guide wins.
Workflow
- Declare the interpreter and version up front:
#!/usr/bin/env bashplus a runtime version guard (BASH_VERSINFO). Never write bashisms in a#!/bin/shscript — pick one and be honest about it. - Open every script with the strict-mode header (see below). Lay out
top-to-bottom: header comment (purpose, usage, exit codes), strict
mode, constants, helpers,
main()with"$@", and themain "$@"invocation guard. - Quote every expansion:
"$var","${arr[@]}","$(cmd)". An unquoted expansion is a bug until proven otherwise. - Bound everything with named
UPPER_SNAKE_CASEconstants: timeouts, retry counts, bytes read, loop iterations. - Review against the checklist below before calling the script done.
Operating Rules
- Strict mode, honestly.
set -euo pipefailis the starting point, not a safety proof:-edoes not fire insideifconditions,&&/||lists, or the last command of a pipeline's context in some versions. Check the exit status of anything that matters explicitly.set -uturns unset variables into errors — initialize or default (${VAR:-default}) deliberately. - Contracts first. Every function answers: permitted inputs, rejected inputs, max work, what state changes, exit codes, failure behavior. A function communicates status via its return code and data via stdout — never mix the two.
- Quoting.
"$var"always."${arr[@]}"for arrays, never$arror${arr[*]}unquoted.read -ralways (raw backslashes).IFS=prefix when reading whole lines. Neverfor x in $(cmd)— usewhile IFS= read -rloops ormapfile. - No
eval. Noeval, nosourceon computed paths, nocurl | bash. If a value must become code, that is a design failure — restructure. Verify checksums/signatures on downloaded artifacts before executing them. - Temp files:
mktemponly, never predictable/tmpnames (symlink attacks). Register cleanup withtrap 'rm -rf "$TMPDIR"' EXITat creation time. One trap per concern; append, don't overwrite. - Globbing: never parse
ls. Setnullglob/failglobdeliberately and document the choice. Quote globs you mean literally. - Errors: a
die()helper (printf ... >&2; exit 1) for fatal errors. Distinguish exit codes: 0 success, 1 general error, 2 usage error. Log to stderr; data to stdout. Never swallow a failure with|| trueunless you can state what you are ignoring and why. - Pipelines:
pipefailso a mid-pipe failure is not masked by the last command's success. Capture pipeline status explicitly when the stages have different failure meanings (PIPESTATUS). - Bound everything.
timeouton network/subprocess calls,head -con unbounded reads,read -ton interactive input,--max-counton searches. No unboundedfind /or recursive globs without depth limits. - Filesystem:
mkdir -pbefore writing; check with-e/-f/-dbefore acting on paths from outside. Use--to end option parsing before user-supplied paths (rm -- "$f"). Neverrm -rfan unvalidated variable — assert it is non-empty and under an expected prefix first. - Environment: treat the environment as untrusted input.
PATHmanipulation must be explicit and logged. Never trustCDPATH,IFS, orLD_PRELOADacross a privilege boundary. - Portability: if the script must run on macOS/BSD too, avoid GNU-only
flags or gate them behind
unamechecks. Document the supported platforms in the header. - Functions:
localfor every function variable. Functions stay near or below 70 lines. One purpose per function; helpers prefixed consistently. - Formatting:
shfmt -i 4 -ci(4-space indent). One formatter, no hand-formatting fights. shellcheck must report zero warnings — suppressions (# shellcheck disable=) require a comment explaining why the warning is wrong, not just noisy. - Testing: bats for behavior tests; test the failure paths, not just
the happy path (half-adversarial). Test with
shellcheckclean as a gate. - Comments explain why, not what. Header carries the contract: purpose, usage, arguments, exit codes, side effects, required tools.
Output Contract
Bash you produce must: open with #!/usr/bin/env bash and the
strict-mode header, quote every expansion, use local in all functions,
create temp files only via mktemp with trap cleanup, never eval or
pipe curl to bash, bound all reads/loops/retries/timeouts with named
constants, separate data (stdout) from diagnostics (stderr), pass
shellcheck with zero warnings and shfmt clean, and pass the review
checklist.
Review Checklist
-
#!/usr/bin/env bash, version guard, strict-mode header present. -
Every expansion quoted;
read -r; nofor x in $(...). -
No
eval, nosourceon computed paths, nocurl | bash. -
Temp files via
mktempwithtrapcleanup registered at creation. -
Exit codes documented and deliberate;
die()for fatal errors; no unexplained|| true. -
pipefailset;PIPESTATUSchecked where stage failures differ. - All reads, loops, retries, timeouts bounded by named constants.
-
rm -rftargets validated non-empty and under expected prefix;--before user paths. -
Functions use
local; ≤ ~70 lines; single purpose. -
shellcheckzero warnings;shfmtclean; bats tests cover failure paths.
ReferencesBash manual · ShellCheck
CSafety-first C — explicit contracts, checked bounds, and disciplined manual ownership, hardened against overflow and use-after-free.
Tiger Style C
Purpose
Apply the Tiger Style standard to C code. Priority order, always: Safety > Performance > Developer Experience. The full guide lives in references/TIGER_STYLE_C.md; this file is the operational core. When the two disagree, the full guide wins.
Workflow
- Declare the contract up front: C standard (C17; gate C23 features on a feature check, not a compiler flag), compiler versions, data model, target arch, libc, and required POSIX/Linux APIs. Isolate platform adapters from portable domain code.
- Lay the module out: small public header for types/contracts, one
implementation file for
staticprivate helpers. Include guards, self-contained headers, explicit standard includes, one clear public API prefix. No surprising mutable global state from headers. - Validate external input with a documented status enum plus output
parameters; assert internal invariants with
assert()/_Static_assert(one invariant per assert). Never validate only insideassert—NDEBUGstrips it, and external checks must survive release builds. - Bound everything with named
*_MAXconstants: loops, retries, queues, sizes, nesting depth, cumulative bytes. Check size multiplication (count <= SIZE_MAX / size) before allocating. - Compile with strict warnings (
-Wall -Wextra -Wpedantic -Wconversion -Wshadow), run under ASan/UBSan, test again with-DNDEBUG, then review against the checklist below.
Operating Rules
- Contracts first. Every function answers: permitted inputs, rejected
inputs, max work, what state changes, who owns each resource, failure
behavior, whether
(NULL, 0)is valid, and whether outputs stay unchanged on failure. - Assertions are for invariants, never for external input.
_Static_assertfor compile-time representation constraints;assert()for runtime invariants. Never acquire resources, validate, or mutate state only inside them. - Errors are status enums, not prose. Prefer leaving outputs unchanged
until success. Capture
errnoright after the failing call, before cleanup can clobber it; distinguish library status from OSerrno. Handle partial reads/writes, EOF, interruption — and never blindly retrycloseafterEINTRon Linux (the descriptor may already be released and reused). - Bound everything. No unexplained
while (1), no unbounded retry, no unbounded growth, no VLAs for external sizes. Retries require an idempotent operation, a shared deadline, and bounded backoff. - Control flow: straightforward branches, bounded loops, exhaustive
handling of accepted states. A
gotocleanup label is acceptable (local labels, monotonic ownership) when it beats duplicated cleanup. No recursion over attacker-controlled depth; no macros that hide control flow or evaluate arguments twice. - Absence discipline: a null pointer, an empty valid span, a missing
optional value, and an invalid argument are four different things. State
the
(NULL, 0)policy per API. Never call a memory function with null unless its contract permits it;memsetis not type-aware zero-initialization. - Names are
snake_casefunctions/fields,UPPER_SNAKE_CASEmacros, units last:input_count,output_capacity_bytes. One public prefix; never use reserved identifiers (leading_+uppercase,__). Prefer explicit structs/enums over opaque typedef ownership tricks. - Formatting: 4-space indent, max 100 columns, one statement per line,
braces for multi-step control flow. Pinned clang-format:
BasedOnStyle: LLVM,IndentWidth: 4,ColumnLimit: 100,UseTab: Never. Never reformat vendored code. - Functions: ordinary functions stay near or below 70 lines. Signatures carry explicit buffers, capacities, statuses, and ownership. No positional flag piles; document aliasing/overlap policy per API.
- Ownership: one owner per mutable buffer, fd, and handle. One cleanup path per acquisition sequence, released in reverse order; record ownership immediately after acquisition. Never return a pointer into automatic storage or retain a borrowed pointer past its owner's release. Freeing one owner pointer does not invalidate aliases — clear the owner and document it. Teardown must document how it detects already-released state.
- Security: fixed format strings for untrusted text; bounds-check every
copy (
strncpyis not a safestrcpy); cast non-EOF bytes tounsigned charfor ctype functions. Treat pointer arithmetic, casts, FFI, format strings, dynamic loading, and shell/process boundaries as review points. Use argv form; neversystem()/popen()for externally influenced commands. Treat sanitizer findings as real defects until disproved. - Determinism: no uninitialized bytes or struct padding to hashes, logs,
or the wire; explicit byte encodings and stable ordering; fix locale and
encoding assumptions for parsing;
rand()is not a security primitive. - Comments explain why and state contracts: pointer validity, size units, overlap, thread safety, error outcomes, output mutation, ownership transfer, and whether an interface is ISO C, POSIX, or Linux-specific.
- Concurrency: documented pthread/C11-atomic protocol;
volatileis not synchronization; data races are UB. Signal handlers must be async-signal-safe; define shutdown, wakeup, cancellation, join, and buffer lifetime before workers start. - Numbers:
size_tfor object sizes, fixed-width types for protocol fields, correct format macros. Signed overflow is UB; unsigned wrap is defined but rarely the right size policy. Decode endianness explicitly — never cast packet bytes to structs. Never serialize raw struct bytes as a portable wire format.
Output Contract
C you produce must: compile cleanly under strict warnings for the
declared standard, document contracts in public headers, return status enums
on expected failures with outputs unchanged, assert its invariants, bound all
loops/retries/sizes, pass clean under ASan/UBSan and with -DNDEBUG,
stay within 100 columns and ~70 lines per function, and pass the review
checklist.
Review Checklist
-
External input validated with status returns in every build; invariants asserted, never validation-inside-
assert. - All loops/retries/sizes/depths bounded by named constants; size arithmetic checked before allocation.
-
Every allocation result checked;
reallocvia a temporary;(NULL, 0)policy stated per API. - Null/empty/missing/invalid distinguished; no null passed to memory functions unless the contract permits.
- Every resource has one owner; one cleanup path covers all failure paths; no pointer into automatic storage escapes.
-
No
system()/popen()or shell interpolation for external input; fixed format strings; argv form used. - No uninitialized bytes or padding reach hashes/logs/wire; encoding and locale assumptions explicit.
-
Integer edges, partial I/O, interrupted syscalls, and allocation failure tested; sanitizers clean; tests pass under
-DNDEBUG. - Functions ≤ ~70 lines, lines ≤ 100 cols, clang-format clean, comments explain why and state contracts.
ReferencesC reference (cppreference) · clangd
C++Safety-first C++ — C++23 with contracts first, std::expected for validation, Rule-of-Zero RAII ownership, and one explicit exception policy.
Tiger Style C++
Purpose
Apply the Tiger Style standard to C++. Priority order, always: Safety > Performance > Developer Experience. The full guide lives in references/TIGER_STYLE_CPP.md; this file is the operational core. When the two disagree, the full guide wins.
Workflow
- Declare the profile up front: language standard (C++23 reference; C++20 needs an explicit expected-result alternative), compiler, standard library, exception policy, and target platform.
- Lay the module out top-to-bottom: purpose comment, narrow public header with domain types and contracts first, private implementation helpers, Rule-of-Zero value types where possible.
- Validate external input with
std::expected<T, E>; assert internal invariants withstatic_assert(compile-time) andassert()(runtime — rememberNDEBUGremoves it). - Bound everything with named
constexprconstants: item counts, byte sizes, nesting depth, retries, queues, captured output. - Review against the checklist below before calling the code done.
Operating Rules
- Contracts first. Every public operation answers: permitted inputs, rejected inputs, max work, what state changes, who owns each resource, failure behavior (unchanged / valid-but-changed / partial — documented).
- Errors: one exception policy plus one typed expected-error policy per
subsystem.
std::expected<T, E>carries routine domain failure — check state before touching value or error. Never let an exception cross a C ABI boundary; translate it there. - Assertions are for programmer errors, never for external input. No
side effects inside assertions. A
std::spanis a borrow, not a bounds check — prove indices before access. - Bound everything. No unexplained
while (true), no unbounded retry, no unbounded growth. Retries require an idempotent op and a shared deadline; name the budgets (item_count_max,input_bytes_max). - Ownership: RAII and the Rule of Zero.
unique_ptrfor exclusive heap ownership;shared_ptronly for genuine shared ownership, cycles broken deliberately. Destructors are nonthrowing. Fallible completion (flush, commit) gets an explicit checked finish before cleanup. - Views borrow, never own.
std::string_view/std::spando not extend lifetime; keep them in the smallest useful scope; never return references to temporaries or locals. Capturingthisin a lambda keeps nothing alive. - Numbers: signed overflow is UB; unsigned wrapping is not a capacity
policy. Check before conversion and arithmetic (
value > total_max - totalbefore+=). No C-style casts, no silent negative-to-size_tconversion. - Absence:
std::optionalfor absence,std::expectedfor failure — never a null pointer or magic integer carrying several meanings. Moved-from objects are valid but unspecified; don't assume "empty". - Names:
snake_casefor functions/variables,PascalCasefor domain types, units last:payload_size_bytes,timeoutasstd::chronodurations. Non,tmp2, reserved identifiers, or ownership-hiding prefixes. - Formatting: pinned clang-format —
BasedOnStyle: LLVM,IndentWidth: 4,ColumnLimit: 100,UseTab: Never. Includes organized, headers self-contained; run semantic rewrites separately from formatting. - Functions: ordinary functions stay near or below 70 lines. Narrow interfaces, explicit result types, domain option structs over boolean flag lists.
- Concurrency: prefer
std::jthreadwith an explicit stop-token protocol — stopping is cooperative, not forced. Data races are UB;volatiledoes not synchronize. Coroutine frames need a clear owner. - Security: treat reinterpretation, raw pointers, C casts, format
strings, and dynamic loading as trust boundaries. Build with warnings
plus ASan/UBSan (
-Wall -Wextra -Wpedantic -fsanitize=address,undefined). - Determinism: never serialize raw object representation (padding, vptrs, endianness); unordered-container iteration must not define canonical output; inject clocks/randomness in tests.
- Comments explain why (lifetimes, why a cast is valid, exception
guarantees, complexity bounds), not what. A
noexceptmust be justified across every called operation.
Output Contract
C++ you produce must: compile cleanly under the declared profile,
carry documented contracts on public operations, assert its invariants,
return std::expected on expected failures, bound all loops/retries/queues,
give every resource one RAII owner with failure-path teardown, stay within
100 columns and ~70 lines per function, and pass the review checklist.
Review Checklist
-
External input validated before use; internal invariants asserted; release (
NDEBUG) behavior tested separately. -
All loops/retries/queues/allocations bounded by named
constexprconstants. -
Ownership explicit on success and every failure path; no
new/delete, no dangling views or captures. -
std::expectedchecked before value/error access; exception guarantees documented; no throw across C ABI. - Arithmetic checked before conversion/overflow; units explicit in names and types.
-
No shell construction from untrusted input; argv form used; no
std::systemon external inputs. - Functions ≤ ~70 lines, lines ≤ 100 cols, clang-format clean, comments explain intent.
ReferencesC++ reference (cppreference) · C++ Core Guidelines
GoSafety-first Go — errors as values, panic reserved for broken contracts, and goroutines, queues, and retries bounded by named limits.
Tiger Style Go
Purpose
Apply the Tiger Style standard to Go code. Priority order, always: Safety > Performance > Developer Experience. The full guide lives in references/TIGER_STYLE_GO.md; this file is the operational core. When the two disagree, the full guide wins.
Workflow
- Record the module's Go/toolchain version, GOOS/GOARCH, and cgo policy;
never assume 64-bit
int, scheduling order, or tested cross-compiles. - Lay the package out top-to-bottom: header/purpose, constants, types,
constructors/validators, operations, tests. Use
cmd/<program>for executables,internal/for implementation boundaries; avoidinit()side effects. - Validate external input with explicit error returns; reserve
panicfor broken programmer contracts and irrecoverable internal state. - Bound everything with named
Maxconstants: input bytes, items, nesting depth, workers, queue entries, retries, output capture. - Review against the checklist below before calling the code done.
Operating Rules
- Contracts first. Every function answers: permitted inputs, rejected inputs, max work, what state changes, who owns each resource, failure behavior. Public operations follow input → validation → bounded computation → validated change → commit → cleanup.
- Errors are values. Return
(value, error)consistently; wrap with%wforerrors.Is/errors.As; never compare message strings. HandleCloseerrors where writes can fail. A typed nil in anerrorinterface is not nil. Neverpanicon malformed external data. - Bound everything. No goroutine per unbounded input item — use bounded
workers with admission. No unbounded retry; retries need idempotence, a
shared deadline, bounded backoff. No unbounded
io.ReadAll. - Concurrency: contexts signal cancellation, they don't kill goroutines — every worker needs a shutdown path and an owner that waits. Sender closes owned channels exactly once. One goroutine owns shared state, or guard it with a mutex/atomic protocol. Define who may mutate a pointer after it crosses a channel.
- Resources:
defercleanup after checking acquisition succeeded; neverdeferin a large loop (per-item function instead).os.Exitskips deferred cleanup — return to the entry point. Never copy a used mutex. - Zero values and absence: document zero-value behavior. Nil slice,
empty slice, nil map, missing key are distinct; use comma-ok lookups.
Use
*boolwhen omitted vsfalsediffer. - Names are
MixedCaps, units last:requestCount,payloadSizeBytes; durations astime.Durationinternally. No underscores in ordinary identifiers; exported names start uppercase; standard initialisms (HTTPClient,userID); short receiver names. - Formatting:
gofmtis authoritative, including its tabs. Never force spaces or a hard 100-column wrap onto gofmt output; keep lines readable through function design. - Functions: ordinary functions stay near or below 70 lines. Pass
context.Contextfirst for cancellable operations; never store request contexts in long-lived structs. Narrowest useful interface at the consumer boundary; options struct for complex construction. - State and mutation: validate, do bounded leaf work, commit at one obvious point; stale async results must not mutate a newer generation. Slices alias their backing arrays — document ownership on send/return. Config is immutable after construction.
- Determinism: sort map keys before canonical output; never rely on
goroutine completion order or
intwidth. - Security: treat
unsafe, cgo, reflection, plugins, and build directives as trust boundaries. Bound HTTP headers/bodies, set timeouts, verify TLS. The race detector is not proof of race-freedom. - Comments explain why, not what. Doc comments on exported symbols stating cancellation, concurrency, ownership, zero-value guarantees.
Output Contract
Go you produce must: build under the recorded toolchain, return
(zero, err) on expected failures with wrapped sentinel errors, verify
internal invariants with explicit checks, bound all input/workers/queues/
retries, pass go vet and go test -race, stay gofmt-clean with
functions near ~70 lines, and pass the review checklist.
Review Checklist
-
External input validated before use; invariants checked,
paniconly for programmer-contract violations. - All bytes/items/workers/queues/retries bounded by named constants.
-
Errors wrapped with
%w; no message-string comparison;Closeerrors handled; no typed-nilerror. - Every goroutine has a shutdown path and a waiting owner; channel ownership and close responsibility explicit.
- Contexts passed as first param, never stored in long-lived structs.
- Zero-value/nil-slice/nil-map/comma-ok semantics deliberate.
- Slice aliasing and ownership documented at boundaries.
- Deterministic output: sorted keys, no scheduling-order reliance.
- Secrets out of errors/logs; privileged ops (unsafe/cgo/exec) narrowly scoped.
- Functions ≤ ~70 lines, gofmt-clean, comments explain intent.
ReferencesGo documentation · Effective Go
KotlinSafety-first Kotlin — contracts that name threading and lifecycle, sealed result types for expected failures, and budgeted resources.
Tiger Style Kotlin
Purpose
Apply the Tiger Style standard to Kotlin code. Priority order, always: Safety > Performance > Developer Experience. The full guide lives in references/TIGER_STYLE_KOTLIN.md; this file is the operational core. When the two disagree, the full guide wins.
Workflow
- Define the target as OS + CPU + runtime/ABI + SDK + dependencies + packaging — never just an architecture name. Check the Kotlin and Compose support tables for the exact release; neither table proves support for every dependency.
- Lay the module out top-to-bottom: purpose, public contract (types, entry points, threading and lifecycle expectations), then private machinery.
- Validate external input before allocating expensive resources or publishing state. Use assertions for internal facts, normal error paths (sealed result types) for expected external failures.
- Encode resource budgets as named configuration with units.
- Review against the checklist below before calling the code done.
Operating Rules
- Contracts first. Each substantial operation identifies its accepted input, maximum work, owner, commit point, failure mode, cancellation behavior, and evidence.
- A type-safe language is not a secure application. Null-safety does not cover authorization, deadlines, queue admission, or secret handling — those remain application responsibilities, reviewed explicitly.
- Bound everything. No unbounded loops, retries, queues, collection
growth, or coroutine fan-out. Name limits with units
(
input_bytes_max,retry_count,queue_capacity,deadline). Reject invalid input before expensive allocation or state publication. - Idioms welcome when their cost and lifetime are clear. Sealed hierarchies, extension functions, collection operations, and coroutines are fine — but do not mechanically translate Rust ownership syntax or Lua naming into Kotlin. Write Kotlin as Kotlin.
- Coroutines need structured concurrency. Every scope has an owner
and a cancellation policy; cancellation is cooperative, prompt, and
cannot publish partial effects. No
GlobalScopefor work the app owns. Document dispatcher choices and what blocks where. - Lifecycle discipline (Android). No work outlives its owner: activities, fragments, and view-models release subscriptions, jobs, and listeners. Configuration changes and process death are expected events with tested behavior, not edge cases.
- Multiplatform honesty. Android, iOS, JVM, and JS are separate
lanes with separate evidence. Kotlin/Wasm and Compose web are beta —
say so when used.
linuxArm64is a Tier 2 target but Linux ARM64 is not a supported host for producing final native binaries: build on a supported host, run on the board. Apple final binaries require macOS. Real-device tests for mobile lanes; simulators alone prove little. - Exceptions live next to the affected code: the rule, reason, affected targets, owner, remaining bound, and test or removal condition. A blanket exception for a whole application is not an engineering argument.
Output Contract
Kotlin you produce must: compile on the pinned toolchain with warnings treated as errors, carry KDoc on public items stating contract and bounds, use sealed result types (not exceptions) for expected failures, assert internal invariants, bound all work/queues/retries/ coroutines, keep lifecycle and cancellation explicit, record the target matrix evidence, and pass the review checklist.
Review Checklist
- External input validated before expensive allocation or state publication.
- Null-safety not mistaken for authorization, deadline, or secret-handling review.
- Work, queues, collection growth, retries, coroutines, and time have explicit budgets.
- Coroutine scopes owned; cancellation cooperative, prompt, and side-effect safe.
- Android lifecycle: no leaked subscriptions, jobs, or listeners; config change and process death tested.
- Multiplatform target recorded as OS+CPU+runtime/ABI+SDK+packaging with evidence; beta lanes labeled.
- Errors preserve failure and cause; assertions enforce established invariants.
- Secrets never in logs, bundles, or checked-in config.
- Dependency and toolchain changes received execution-trust review.
- Performance claims include measurements and their environment.
ReferencesKotlin documentation · Kotlin standard library
LuaSafety-first Lua — the runtime named up front, validation by nil-plus-error returns, no bare pcall, and every loop bounded.
Tiger Style Lua
Purpose
Apply the Tiger Style standard to Lua code. Priority order, always: Safety > Performance > Developer Experience. The full guide lives in references/TIGER_STYLE_LUA.md; this file is the operational core. When the two disagree, the full guide wins.
Workflow
- Determine the runtime (Lua 5.4+, LuaJIT, Neovim, embedded). Put runtime-specific behavior behind a small explicit boundary; never assume one silently.
- Lay the module out top-to-bottom: header/purpose, host API aliases,
local M = {}, constants, LuaCATS types, validation helpers, private leaf helpers, orchestration functions,setup()if needed,return M. - Validate external input with
return nil, "descriptive error"; assert internal invariants withassert()(one invariant per assert). - Bound everything with named
*_MAXconstants: loops, retries, queues, caches, histories, output sizes. - Review against the checklist below before calling the code done.
Operating Rules
- Contracts first. Every function answers: permitted inputs, rejected inputs, max work, what state changes, who owns each resource, failure behavior.
- Assertions are for programmer errors and invariants, never for
external input. Never write bare
pcall(do_work)— capture and propagate the error. - Bound everything. No unexplained
while true, no unbounded retry, no unbounded growth. Retry budgets are named constants (e.g.RETRY_COUNT_MAX = 4). - Control flow: positive conditions, branch-light leaf loops, no
ready and start()statement-shorthand, no recursion over attacker-controlled depth. - Nil/false discipline: never
local enabled = options.enabled or truewhenfalseis valid — check== nilexplicitly. Givenilone documented meaning per context. - Names are
snake_casewith units last:timeout_ms,payload_size_bytes,retry_count,generation_id. Non,sz,tmp2,stuff. - Formatting: 4-space indent, max 100 columns, one statement per line,
trailing commas in multiline tables. stylua config:
column_width = 100,indent_type = "Spaces",indent_width = 4. - Functions: ordinary functions stay near or below 70 lines. Prefer
named option tables over positional booleans:
copy_range({ source = ..., overwrite = true }). - State and mutation: validate input, do pure/bounded leaf work, commit at a single obvious point. Copy caller-owned config if later mutation is possible. Async callbacks carry a generation token and reject stale results.
- Resources: one clear owner per file, socket, timer,
vim.uvhandle, subprocess. Teardown is idempotent and nils the handle. - Security: never interpolate untrusted values into shell strings —
use
vim.system({ "git", "status", "--", user_path })argv form. Treatload,dofile,package.loadlib, FFI as privileged. - Determinism: sort map keys before serializing; never rely on
pairs()order; make locale/timezone assumptions explicit. - Comments explain why, not what. Use LuaCATS (
---@param,---@return,---@class) where it aids review. - Neovim: prefer
vim.api,vim.fs,vim.system,vim.uv; keepsetup()idempotent;require()loads definitions, not global side effects.
Output Contract
Lua you produce must: compile cleanly, carry LuaCATS types on
exported functions, assert its invariants, return (nil, err) on expected
failures, bound all loops/retries/queues, stay within 100 columns and ~70
lines per function, and pass the review checklist.
Review Checklist
- External input validated before privileged use; internal invariants asserted.
- All loops/retries/caches/queues bounded by named constants.
-
nilvsfalsesemantics deliberate; noordefault clobbersfalse. - Index/count/size/unit names explicit and unit-suffixed.
- Every resource has one owner; teardown exists on failure paths.
- No shell string interpolation of untrusted input; argv form used.
- Async callbacks reject stale generations; handles revalidated after delay.
-
Output deterministic where persisted or tested; no
pairs()order reliance. - Functions ≤ ~70 lines, lines ≤ 100 cols, comments explain intent.
ReferencesLua 5.4 reference manual · LuaJIT
MojoSafety-first Mojo — pinned compiler and libraries, never a moving nightly, typed errors, and recorded evidence for every accelerator target.
Tiger Style Mojo
Purpose
Apply the Tiger Style standard to Mojo code. Priority order, always: Safety > Performance > Developer Experience. The full guide lives in references/TIGER_STYLE_MOJO.md; this file is the operational core. When the two disagree, the full guide wins.
Workflow
- Pin a reviewed compiler version and its matching standard library (plus MAX and accelerator libraries, pinned consistently, when used). Keep the package/environment manifest and lockfile in the project. Never use a moving nightly channel as a reproducibility claim.
- Record build/release evidence: compiler version, source and dependency revisions, CPU target, OS, native libraries, optimization and assertion settings, GPU model/driver/device when applicable, Python environment when Python interop is enabled.
- Run
mojo --version,mojo build, andmojo formatfrom the same environment Neovim uses. A global executable and a project executable can be different language releases — resolve that before weakening diagnostics or changing code. - Lay the module out top-to-bottom: purpose, public contract (types, entry points, ownership), then private machinery.
- Validate external input with ordinary control flow returning a typed error; assert facts a correct implementation already established.
- Review against the checklist below before calling the code done.
Operating Rules
- Contracts first. Every substantial operation answers: accepted and rejected inputs, trust boundary, max work/memory/output/time, owner of every buffer/allocation/device/task, failure and cancellation behavior, cleanup obligations. A successful compilation proves none of this; nor does an impressive benchmark.
- Ownership discipline. Mojo's ownership and borrowing are tools for the contract, not decoration. Borrow for inspection; transfer ownership when retaining or handing off is the contract. Name the owner when work escapes a scope (task, callback, device buffer, Python object).
- Bound everything. No unbounded loops, retries, queues, or growth.
Name limits with units (
input_bytes_max,batch_size_max,retry_count,deadline). Long-lived services may run indefinitely, but each batch is bounded and cancellation has a documented effect. - Numerics are a safety property. State precision, rounding, overflow, and NaN/inf policy where they affect correctness. Keep tight numerical kernels and interactive orchestration behind explicit boundaries — they have different cost profiles.
- Python interop is a trust boundary. Declare the Python implementation and package environment, keep the interop surface narrow and typed, and never let a Python import silently authorize device access, package installation, or network use.
- Exceptions need a reason, a local owner, and a check. Describe what assumption would invalidate the exception. No global suppressions that make later violations invisible.
- Toolchain upgrades are reviewed changes. Re-run formatting, compilation, tests, and representative benchmarks; keep compiler updates separate from unrelated system maintenance so failures can be attributed and rolled back. Build and test as an ordinary user; review package channels, installers, AUR build files, and foreign dependencies before executing them. Arch is rolling — never infer vendor support from local installability.
Output Contract
Mojo you produce must: compile on the pinned toolchain with diagnostics at full strength, carry doc comments on public items stating contract and bounds, return typed errors on expected failures, assert internal invariants, bound all work/queues/retries/memory, keep kernels and orchestration behind explicit boundaries, record build evidence, and pass the review checklist.
Review Checklist
- External input validated before allocation, indexing, and mutation.
- Every buffer, device allocation, task, and Python object has one owner and a cleanup path.
- Work, batches, queues, memory, retries, and time have explicit budgets.
- Numeric precision, overflow, and NaN/inf policy stated where they matter.
- Errors preserve failure and cause; assertions enforce established invariants.
- Python interop surface is narrow, typed, and its environment recorded.
- Cancellation cannot publish stale results or duplicate effects silently.
- Compiler/stdlib/MAX/accelerator versions pinned; build evidence recorded.
- No global diagnostic suppressions; exceptions are local, owned, checked.
- Logs, argv, and build environments expose no secrets.
- Performance claims include measurements and their environment.
ReferencesMojo documentation · Mojo manual
NixSafety-first Nix — pure evaluation, explicit inputs, and pinned sources, validated by a pinned toolchain.
Tiger Style Nix
Purpose
Apply the Tiger Style standard to Nix expressions. Priority order, always: Safety > Performance > Developer Experience. The full guide lives in references/TIGER_STYLE_NIX.md; this file is the operational core. When the two disagree, the full guide wins. Rules the Lua template covers but Nix lacks (async callbacks, OS handles) are omitted rather than invented.
Workflow
- Determine the Nix language version, evaluator/CLI version, Nixpkgs
revision, module system, and target system. Declare
nix-commandandflakeswhen a project uses them; never assume them silently. - Lay the module out top-to-bottom: header/purpose, pinned inputs, small
letbindings with explicit names, pure expression logic first, derivations / modules / flake outputs after. Keep reusable pure logic separate from derivations and modules. - Validate external input with
{ ok = false; error = "descriptive"; }; assert internal invariants withassert cond; value(one invariant per assert). Remember an assert only fires when that expression evaluates. - Bound everything with named constants (
itemCountMax,totalMax): list lengths, nesting depth, byte sizes, jobs, attempts. - Review against the checklist below before calling the code done.
- Validate with the pinned toolchain per the guide's recipes:
nixfmt --check,statix check .,deadnix --fail .; for flake projectsnix flake check --no-write-lock-fileonce their declared experimental features are enabled.
Operating Rules
- Contracts first. Every function answers: permitted inputs, rejected inputs, max work, which derivations/builds/activation effects it selects, and failure behavior.
- Assertions are for programmer errors and invariants, never for
expected invalid configuration. For that, return a tagged
{ ok = false; error = ...; }orthrowwith context at the boundary. Force the values needed to establish a contract — shallow success proves nothing. - Bound everything. No unbounded fold, no recursion over attacker-controlled depth. Retry budgets are named constants and require an idempotent operation.
- Control flow: prefer finite transforms over recursive traversal of external data. Guard a length bound before a fold, make per-element work explicit, avoid repeated list concatenation in folds.
- Absence/null/false discipline: absence,
null,false,[], and{}are different states. Neverattrs.value or fallbackwhenfalseornullis valid — checkattrs ? valueexplicitly. Do not confuse module option merging with ordinaryor. - Laziness discipline: producing an attrset is not proof its fields
evaluated. Document laziness/strictness boundaries; force at deliberate
boundaries with
builtins.deepSeqover bounded values only. Preferfoldl'; remember it is strict only to weak head normal form. - Names are
camelCasewith units last:payloadSizeBytes,itemCountMax,totalMax. Use conventional Nixpkgs names where interoperating (pname,version,nativeBuildInputs,meta). No near-duplicate aliases of well-known attributes. - Formatting: 2-space indent, max 100 columns, nixfmt from the pinned dev environment (record its version/interface). Deterministic formatter output wins over manual wrapping; never force Lua's four spaces onto Nix.
- Functions: explicit argument sets with documented required/optional fields; reject unknown arguments for narrow configuration. Ordinary functions stay near or below 70 lines. Keep policy validation separate from derivation building.
- Scope: keep
letscopes small; no broadwithscopes that hide identifier origins.recis a semantic tool, not a default. Pass the package set and system intentionally — no floating<nixpkgs>in reproducible code. - State: expressions do not mutate, but evaluation selects builds and activation effects. Make configuration changes as new values and review the generated plan before applying; avoid recursive attrsets when explicit dependencies suffice.
- Security: never interpolate secrets into strings that become store paths, derivations, generated files, or logs — runtime secrets belong to a separately reviewed mechanism. Treat substituters, signing keys, and fetchers as trust boundaries; never disable the sandbox to make a build pass.
- Shells and filesystem:
buildPhaseis a shell boundary — fixed commands pluslib.escapeShellArg/lib.escapeShellArgs; escape Nix interpolation separately for literal shell${...}. A Nix path value is not a runtime-path string; interpolating a path copies content into the store, so filter sources deliberately. - Determinism: pin source revisions and hashes; commit a reviewed flake lock; use explicit systems. Pure evaluation is not proof of a deterministic build.
- Comments explain why, not what. Document argument types, laziness/strictness boundaries, system assumptions, store exposure, and derivation side effects. Module options need description, example, type, and merge policy.
Output Contract
Nix you produce must: evaluate cleanly under the declared Nix
version, document argument sets on functions, assert its invariants, return
tagged { ok = false; error = ...; } on expected invalid input, bound all
folds/traversals with named constants, stay near ≤70 lines per function and
≤100 columns, format clean with the pinned nixfmt, and pass the review
checklist.
Review Checklist
- Language/evaluator/Nixpkgs/module-system/target explicit; experimental features declared.
- External input validated with tagged results; internal invariants asserted and forced where the contract needs it.
- All folds/traversals bounded by named constants; no unbounded recursion over external data.
-
Absent vs
nullvsfalsevs[]vs{}deliberate; noordefault clobbers a valid value. -
Laziness/strictness boundaries documented;
deepSeqused on bounded values only. -
Names: Nixpkgs conventions where interoperating,
camelCasewith units for locals. - No secrets in store paths/derivations/logs; shell args escaped; sources filtered.
- Dependencies pinned with hashes; flake lock committed; sandbox intact.
- Functions ≤ ~70 lines, lines ≤ 100 cols, nixfmt-clean, comments explain intent.
ReferencesNix manual · Nixpkgs manual · nix.dev
OdinSafety-first Odin — explicit allocators on every procedure, typed error returns, and contracts that state ownership and failure behavior.
Tiger Style Odin
Purpose
Apply the Tiger Style standard to Odin code. Priority order, always: Safety > Performance > Developer Experience. The full guide lives in references/TIGER_STYLE_ODIN.md; this file is the operational core. When the two disagree, the full guide wins.
Workflow
- Pin the exact compiler release and inspect its target list and help output before claiming any deployment target. A CPU architecture is not a deployment target: record OS, ABI, object format, libc, minimum SDK/runtime, native libraries, and packaging separately.
- Lay the package out top-to-bottom: purpose, public contract (types, entry procedures, allocator expectations), then private machinery.
- Make allocators explicit. Every procedure that allocates answers: which allocator, who frees, and what happens on allocator failure.
- Validate external input with ordinary control flow returning a typed error; assert facts a correct implementation already established.
- Review against the checklist below before calling the code done.
Operating Rules
- Contracts first. For every important procedure, a reviewer must be able to identify permitted inputs, maximum work, valid state, resource owner, mutation point, failure behavior, and evidence. Avoid implicit conventions that require reading the whole program to establish one invariant.
- Odin gives control, not proofs. There are no Rust-style compile-time ownership proofs. Pointers, aliases, foreign data, manual lifetime management, and concurrent access require deliberate review. Never call code "memory-safe" merely because it compiles or keeps bounds checks enabled.
- Bound everything. No unbounded loops, retries, queues, or growth.
Name limits with units (
input_bytes_max,retry_count,queue_capacity,deadline). Prefer simple data and shallow control flow; a fixed buffer, explicit loop, or dedicated arena can make an invariant clearer, but is not automatically superior — choose by the workload's limits, failure policy, and resource lifetime. - Allocator discipline. Default to explicit context allocators; never silently switch allocators mid-operation. Temporary arenas have documented lifetimes. Allocation failure is an expected error, not an assertion — handle it on every path that can allocate.
- Concurrency. Document the threading model, shared-state protocol, and lock ordering. Never hold a lock across a blocking call without a stated reason and bound. Own thread/task handles; cancellation follows a documented policy; stale completions cannot publish.
- Foreign data is untrusted. C pointers, loaded files, network buffers, and plugin inputs are validated before use: lengths, tags, versions, and provenance checked with explicit code, not assumed from the producer.
- Deployment honesty. Linux x86-64 is the baseline native lane; every other target needs its own evidence (ARM64 libraries and board tests, MSVC/SDK compatibility for Windows, signing for Apple). Do not infer Android from Linux ARM64 or iOS from macOS ARM64. Prototype and validate the entire pipeline before committing a product to a conditional target. A phone can consume an Odin service through the same versioned API as a desktop client.
- Exceptions record the rule, need, affected targets, owner, compensating check, and removal condition. No blanket "performance" waiver for bounds checks, allocator errors, authentication, or release testing.
Output Contract
Odin you produce must: compile on the pinned compiler with diagnostics at full strength, carry doc comments on public procedures stating contract, allocator expectations, and bounds, return typed errors on expected failures, assert internal invariants, bound all work/queues/retries/memory, make every allocation's owner and failure path explicit, record the deployment target evidence, and pass the review checklist.
Review Checklist
- External input validated before allocation, indexing, and mutation.
- Every allocation names its allocator, owner, free path, and failure behavior.
- Pointers, aliases, lifetimes, and foreign data reviewed as untrusted until proven.
- Work, queues, memory, retries, and time have explicit budgets.
- Errors preserve failure and cause; assertions enforce established invariants.
- Concurrency model, lock ordering, and cancellation policy documented.
- No code called "memory-safe" without the review to back it.
- Deployment target recorded as OS+CPU+ABI+libc+SDK+packaging with evidence.
- Logs, argv, and subprocess environments expose no secrets.
- Toolchain and dependency changes received execution-trust review.
- Performance claims include measurements and their environment.
ReferencesOdin · Odin language overview
PHPSafety-first PHP — strict_types everywhere, domain exceptions for input, identity comparisons on untrusted values, and validation surviving production settings.
Tiger Style PHP
Purpose
Apply the Tiger Style standard to PHP code. Priority order, always: Safety > Performance > Developer Experience. The full guide lives in references/TIGER_STYLE_PHP.md; this file is the operational core. When the two disagree, the full guide wins.
Workflow
- Declare the SAPI and runtime up front (CLI vs FPM, PHP 8.3+ target, opcache/JIT settings). Never assume request isolation holds for a long-running worker — put SAPI-specific behavior behind an explicit boundary. Type declarations are dev checks, not runtime validation of untrusted input.
- Every file opens with
declare(strict_types=1);. Lay code out top-to-bottom: namespace, imports, class/interface docblock, constants, constructor with promoted properties, public methods, private helpers. PSR-4 autoloading via Composer; including a file must not start sessions, open connections, or emit output. - Validate external input by throwing a precise domain exception;
assert()only for programmer invariants — rememberzend.assertions=-1strips assertions in production, so external rejection must survive it. Never use==on untrusted values;===always. - Bound everything with named
UPPER_SNAKE_CASEclass constants: input bytes, item counts, nesting depth, retries, captured output. - Review against the checklist below before calling the code done.
Operating Rules
- Contracts first. Every function answers: permitted inputs, rejected inputs, max work, what state changes, who owns each resource, failure behavior. State whether failure leaves state unchanged or partial.
- Errors: use domain exception classes consistently per boundary;
catch only failures the layer can interpret. Chain with
throw new DomainException(..., previous: $e). Never use@error suppression — a silenced failure is a hidden failure. No authorization or mutation insideassert(). - Strict equality.
==performs type juggling ("0" == false,"1e3" == 1000); it is banned at trust boundaries. Use===/!==.empty()conflatesnull,false,0,"","0",[]— test=== nullexplicitly when absence is the question. - Bound everything. No unbounded
file_get_contentson remote URLs,iterator_to_array, or retry. Retries need an idempotent operation, a shared deadline, bounded backoff, and an injected jitter source. - Superglobals are untrusted input.
$_GET,$_POST,$_COOKIE,$_SERVER,$_FILESare attacker-controlled bytes. Read them once at the boundary through a validated request object; never index them deep in business logic. - Serialization: never
unserialize()untrusted bytes (object injection); prefer JSON with schema validation, or pass['allowed_classes' => false]. Nevereval()external text. - Database: PDO with real prepared statements
(
ATTR_EMULATE_PREPARES => false); never interpolate values into SQL. - Shell: every
proc_open/execcall gets an explicit argv array, cwd, env, deadline, and output bounds; escape withescapeshellarg(). Distinguish exit status, signal, timeout, and launch failure. - Includes: never
include/requirea path built from user input (LFI). Autoload via Composer PSR-4; allowlist any dynamic class resolution. - State: no mutable globals or static caches holding request data in long-running processes (workers, RoadRunner, FrankenPHP); the shared-nothing request model does not hold there. Inject dependencies; construct per request or prove reuse is safe.
- Concurrency: PHP is shared-nothing per request;
Fiber(8.1+) is cooperative, not parallel. Bound fiber counts; never assume ordering across fibers without explicit synchronization. - Names:
camelCasemethods/variables,PascalCaseclasses,UPPER_SNAKE_CASEconstants, units in names ($payloadSizeBytes). Typed properties everywhere;readonlywhere mutation is not part of the contract. Nomixedto silence diagnostics. - Formatting: 4-space indent, 120 columns, one configured formatter — php-cs-fixer with PSR-12 (or Laravel Pint). Stable import ordering, no competing formatters in sequence. Pin formatter and analyzer versions.
- Functions: ordinary methods stay near or below 70 lines. Named arguments for policy flags instead of positional booleans. Separate decoding, validation, computation, and I/O; return a stable shape for similar errors.
- Resources: one clear owner per handle, lock, transaction, process.
try/finallyfor cleanup; register cleanup as acquisition succeeds. Never rely on destructors for timely release. - Determinism: sort canonical keys before serializing; never let
array-order or hash iteration define a wire format. Inject clocks and
randomness;
random_bytes/random_intfor tokens. - Comments explain why, not what. Docblocks carry the public contract: units, accepted types, thrown exceptions, side effects.
Output Contract
PHP you produce must: open with declare(strict_types=1);,
type every property/parameter/return, throw precise domain exceptions on
expected failures, assert only internal invariants (valid with
zend.assertions=-1), use === at trust boundaries, bound all
input/loops/retries/output with named constants, read superglobals once
at the boundary, use real prepared statements, stay within 120 columns
and ~70 lines per method, and pass the review checklist.
Review Checklist
-
declare(strict_types=1);present; all types declared, nomixedescapes. - External input validated by explicit throw; invariants asserted (and valid with assertions disabled).
-
===at every trust boundary; no==, noempty()for absence checks. - All input sizes, loops, retries, output captures bounded by named constants.
-
nullvsfalse/0/""semantics deliberate. - Superglobals read once at the boundary into a validated object.
-
No
unserialize/evalon untrusted data; no user-controlled include paths. - PDO real prepares; shell calls use argv form with timeout and cleanup.
- Every resource has one owner; cleanup runs on failure paths.
- Deterministic output where persisted or tested.
- Methods ≤ ~70 lines, lines ≤ 120 cols, one formatter (php-cs-fixer PSR-12 or Pint).
ReferencesPHP manual · PSR-12 coding style
PythonSafety-first Python — typed signatures, precise domain exceptions, bounded I/O and retries, and validation that survives python -O.
Tiger Style Python
Purpose
Apply the Tiger Style standard to Python code. Priority order, always: Safety > Performance > Developer Experience. The full guide lives in references/TIGER_STYLE_PYTHON.md; this file is the operational core. When the two disagree, the full guide wins.
Workflow
- Declare the interpreter (CPython 3.12+ target) and platform up front. Never assume the GIL, reference counting, or a particular ABI — put runtime-specific behavior (3.14 free-threading, OS adapters) behind an explicit boundary. Type hints are dev checks, not runtime validation.
- Lay the module out top-to-bottom: docstring/purpose, public types and
constants, private validation helpers, leaf logic, orchestration,
explicit
main()withif __name__ == "__main__":. Keep__init__.pylight; imports must not start threads or touch services. - Validate external input by raising a precise domain exception (e.g.
ValidationError); assert only programmer invariants — rememberpython -Ostripsassert, so external rejection must survive it. - Bound everything with named
UPPER_SNAKE_CASEconstants: input bytes, item counts, nesting depth, retries, queues, captured output. - Review against the checklist below before calling the code done.
Operating Rules
- Contracts first. Every function answers: permitted inputs, rejected inputs, max work, what state changes, who owns each resource, failure behavior. State whether failure leaves state unchanged or partial.
- Errors: use domain exception classes (or one explicit result type)
consistently per boundary; catch only failures the layer can interpret.
Add context with
raise DomainError(...) from exc. Neverexcept BaseException: pass— interrupts and cancellation must propagate. No authorization or mutation insideassert. - Bound everything. No unbounded
list(iterator), task creation, or retry. Retries need an idempotent operation, a shared deadline, bounded backoff, and an injected jitter source. - Control flow: early returns for invalid cases, ordinary loops for stateful work. Comprehensions only for simple transforms of already bounded collections; no nested comprehensions with hidden effects.
- Absence discipline:
value is None, nevervalue or defaultwhenFalse/0is valid. Use a sentinel to distinguish omitted from explicitNone; never use mutable default arguments. - Names:
snake_casefunctions/variables,PascalCaseclasses,UPPER_SNAKE_CASEconstants, units in names (payload_size_bytes,deadline_monotonic_s). Leading underscore for nonpublic; never invent dunder methods. - Type hints: annotate public functions; prefer
Protocolover broadobject. Hints describe real values — noAny/castto silence diagnostics, and never an unchecked cast after an annotation. Decide deliberately whetherboolcounts asint(prefer exacttype(x) is intat strict boundaries). - Formatting: 4-space indent, 100 columns, one configured formatter —
Ruff's (
[tool.ruff] target-version = "py312",line-length = 100). Stable import ordering, no competing formatters in sequence. Pin formatter and type-checker versions. - Functions: ordinary functions stay near or below 70 lines. Use keyword-only arguments for policy flags instead of positional booleans. Separate decoding, validation, computation, and I/O; return a stable shape for similar errors.
- Resources: one clear owner per file, lock, transaction, subprocess.
Use context managers (
ExitStack/AsyncExitStackfor variable counts); register cleanup as acquisition succeeds. Never rely on__del__; never return truthy from__exit__by accident. - Security: never
pickle.loadsuntrusted bytes,eval/execexternal text, or import from untrusted paths. Parameterized SQL, argv arrays withshell=False, validated URL schemes. A venv is not a sandbox. - Processes: every
subprocesscall gets explicit argv, cwd, env, deadline, and output bounds; a timeout has a cleanup owner that stops, drains, and reaps. Distinguish exit status, signal, timeout, and launch failure. - Concurrency:
asyncio.TaskGroupfor related tasks; bound admission before creating tasks. Re-check state after everyawait; clean up infinallyand re-raiseCancelledError. Use processes for killable untrusted computation. - Determinism: sort canonical keys before serializing; never let set
iteration order define a wire format. Inject clocks and randomness; use
secretsfor tokens, monotonic clocks for deadlines. - Comments explain why, not what. Docstrings carry the public contract: units, accepted types, raised exceptions, side effects, cancellation behavior.
Output Contract
Python you produce must: run under python and python -O
identically, carry type hints on public functions, raise precise domain
exceptions on expected failures, assert only internal invariants, bound
all input/loops/retries/queues with named constants, close every resource
via a context manager, stay within 100 columns and ~70 lines per
function, and pass the review checklist.
Review Checklist
-
External input validated by explicit raise; invariants asserted (and
valid under
-O). - All input sizes, loops, retries, queues, output captures bounded by named constants.
-
NonevsFalse/0semantics deliberate; no mutable default args. -
Names explicit with units; type hints real, no
Any/castescapes. - Every resource has one owner; cleanup runs on failure paths.
-
No
pickle/evalof untrusted data; subprocess uses argv form with timeout and cleanup owner. -
Async code re-checks state after
await; cancellation propagates. - Output deterministic where persisted or tested; no set-order reliance.
- Functions ≤ ~70 lines, lines ≤ 100 cols, one formatter (Ruff).
ReferencesPython documentation · PEP 8
RustSafety-first Rust — pinned toolchain, typed errors over panics, an owner for every allocation and task, and bounds with units.
Tiger Style Rust
Purpose
Apply the Tiger Style standard to Rust code. Priority order, always: Safety > Performance > Developer Experience. The full guide lives in references/TIGER_STYLE_RUST.md; this file is the operational core. When the two disagree, the full guide wins.
Workflow
- Pin the toolchain in the project's
rust-toolchain.toml(an exact, reviewed nightly date), not only in editor config. Record the active toolchain when terminal, editor, and CI disagree. - Lay the crate out top-to-bottom: crate docs/purpose, public contract
(types, constructors, key functions), then private machinery. Private
first, then
pub(crate), thenpubonly for a real external API. - Validate external input with ordinary control flow returning a typed
error; assert facts a correct implementation already established
(
assert!for invariants,debug_assert!for expensive dev checks). - Bound everything with named constants carrying units: input bytes, iterations, retries, queue capacity, memory, output, deadlines.
- Review against the checklist below before calling the code done.
Operating Rules
- Contracts first. Every substantial operation answers: accepted and rejected inputs, trust boundary, max work/memory/output/time, owner of every allocation/handle/task/subprocess, failure and cancellation behavior, cleanup obligations.
- Errors vs assertions. Invalid input/config/protocol data → typed
error with bounded context. Corrupt internal relationship →
assert!. Neverunwrap()/expect()on external data, I/O, locks, or subprocess results. Never put required mutation inside an assertion. Propagate with?where the caller owns recovery; match explicitly where failure changes cleanup, retry, or state. Neverlet _ =aResultunless the loss is recorded or justified as best-effort. - Bound everything. No unbounded loops, retries, queues, or growth.
Name limits with units (
output_bytes_max,retry_count,queue_capacity,deadline). Validate index ranges with checked arithmetic (checked_sub/checked_add) before computing an end. Use fixed-width integers for wire/disk formats;usizefor native indexing only after checked conversion (TryFrom, never uncheckedason external sizes). - Ownership discipline. Borrow for inspection (
&str,&[T]); take ownership when retaining or transferring is the contract. Treat.clone()as an explicit cost decision — cloning anArcdiffers from cloning a buffer. RAII for ordinary resources; name the owner when work escapes a scope (task, callback,Arc, subprocess). - Unsafe policy. Default to
#![forbid(unsafe_code)]. Where unsafe is genuinely required: isolate the boundary, document every safety obligation locally (allocation, provenance, alignment, init, bounds, aliasing, lifetime, thread access), consider#![deny(unsafe_op_in_unsafe_fn)], and add adversarial tests to the safe wrapper. Never unwind across an FFI boundary. - Concurrency. Bounded queues with a documented full-queue policy
(reject, block-to-deadline, replace-obsolete). Document lock ordering
and the protected invariant; never hold a blocking mutex across
.await. Own task handles; cancellation follows a documented policy. Use generation tokens so stale completions cannot publish. - Control flow: early returns, shallow branches, no recursion over attacker-controlled depth (explicit stack with a depth cap instead). Retries need a retryable error class, bounded attempts, a total deadline, and an idempotency argument.
- Names carry domain meaning and units:
snake_casefor items/locals,UpperCamelCasefor types/traits,SCREAMING_SNAKE_CASEfor constants. Non,sz,tmp2,stuff. Prefer options structs over positional booleans. - Formatting: rustfmt is the mechanical authority — 4-space indent, 100 columns, edition 2024. Ordinary functions stay near or below 70 physical lines; split at meaningful contracts. Explicit imports; no wildcard imports in production modules.
- Types and state: distinct types for distinct concepts (byte offset vs element count, validated vs raw config). Enums for mutually exclusive states. State transitions follow validate → prepare → commit → observe; rejected validation leaves published state unchanged.
- OS boundaries. Paths are requests, not authorization — no string
prefix checks for containment.
std::process::Commandwith separate argv values (no implicit shell); bound and drain stdout/stderr, enforce deadlines, reap children. Atomic writes via temp-file + rename. Never log tokens or credential-bearing URLs; no secrets in argv. - Determinism: sort map keys before serializing; never depend on hash-map iteration order; make float-tolerance decisions explicit with units and a reason.
- Comments explain why, not what. Keep rustdoc on public items focused on contract, bounds, and failure behavior.
- Neovim: use the project's pinned toolchain for rust-analyzer, checks, formatting, and debugging alike. Helpers feeding diagnostics use a versioned schema with bounded records, explicit byte/char and indexing conventions, and buffer identity + input version to reject stale results.
Output Contract
Rust you produce must: compile on the pinned nightly with warnings denied, carry doc comments on public items stating contract and bounds, return typed errors on expected failures, assert internal invariants, bound all work/queues/retries/memory, stay within 100 columns and ~70 lines per function, and pass the review checklist.
Review Checklist
- External input validated before allocation, indexing, and mutation.
- Types distinguish domain concepts; invalid states hard to construct.
- Work, queues, memory, output, retries, and time have explicit budgets.
- Arithmetic and conversions preserve sizes and protocol meanings.
- Errors preserve failure and cause; assertions enforce established invariants.
- Every resource (allocation, handle, task, subprocess) has one owner and a cleanup path.
- Cancellation cannot publish stale results or duplicate effects silently.
- Unsafe/FFI contracts are local, reviewed, and adversarially tested where possible.
- Logs, argv, and subprocess environments expose no secrets.
- Toolchain and dependency changes received execution-trust review.
- Boundary tests and the actual supported target/feature matrix are recorded.
- Performance claims include measurements and their environment.
ReferencesThe Rust Book · Clippy documentation · Rust API Guidelines · TigerStyle
ScalaSafety-first Scala — total functions over ADTs, input parsed into validated domain values, effect discipline, and bounded executors and queues.
Tiger Style Scala
Purpose
Apply the Tiger Style standard to Scala 3 code. Priority order, always: Safety > Performance > Developer Experience. The full guide lives in references/TIGER_STYLE_SCALA.md; this file is the operational core. When the two disagree, the full guide wins.
Workflow
- Declare the platform up front (Scala 3.x, JDK, build tool, effect library). Keep runtime-specific behavior behind a small explicit boundary; never assume one silently. No experimental compiler features without an explicit policy and migration tests.
- Lay the module out top-to-bottom: package, opaque types / enums / ADTs, validation, pure operations, wiring. Group a small domain's types, validation, and pure operations together.
- Parse external input into validated domain values with
Either/Option; assert internal invariants withassert, programmer preconditions withrequire. Never put validation or side effects only inside them — the compiler can elide them. - Bound everything with named constants (
ItemCountMax = 1024): input bytes, collection sizes, nesting depth, queues, executors, retries, captured output. - Review against the checklist below before calling the code done.
Operating Rules
- Contracts first. Every public operation answers: permitted inputs, rejected inputs, max work, what state changes, who owns each resource, failure behavior — and whether failure leaves state unchanged, partially updated, or requiring recovery.
- Errors are values.
Either[DomainError, A]for expected rejection,Option[A]for absence,Tryat boundaries where exception-producing code must become a value. Never.getas the ordinary path. Model errors as enums/ADTs — callers never parse exception text. CatchNonFatalonly when recovery is meaningful; never catch allThrowableand return a success-shaped default. - Bound everything. No unbounded loops, retries, queues, or growth. Retries need an idempotent operation, a shared deadline, bounded backoff, and named constants.
- Control flow: exhaustive matches on enums and sealed hierarchies — a
new case is a review event, and a catch-all may conceal it. Prefer
direct named steps to dense operator chains.
@tailrecwhere tail recursion is required; still bound total work. Collection pipelines must have known input/output budgets. - Option/null discipline:
Optionfor optional domain values, never sentinel strings or null. Convert nullable Java values at one boundary with a deliberate policy (Some(null)andOption(null)differ). Distinguish missing configuration from a presentfalseor zero. - Names are
lowerCamelCasevalues/methods,UpperCamelCasetypes, units last:timeoutMs,payloadSizeBytes,retryCount. Opaque types named by meaning (PayloadBytes). Non,sz,tmp2,stuff, and no symbolic operators whose failure behavior isn't obvious. - Formatting: Scalafmt with an exact pinned version and Scala 3 dialect, 2-space indent, max 100 columns. Never damage formatter output to satisfy a metric.
- Functions: explicit result types on public methods, named parameters for policy, ordinary functions near or below 70 lines. Separate parsing, validation, computation, and effects. Keep givens/contextual parameters local and unsurprising.
- State and mutation: pure transitions separate from effects; immutable
value plus one controlled commit point, or explicitly synchronized
mutable state.
valis a stable binding, not deep immutability. Async refreshes carry generation checks and reject stale results. - Resources and concurrency: one clear owner per resource;
Usingfor synchronousAutoCloseable— never leak a lazy value or runningFuturepast it.Futureis eager andAwaittimeouts don't cancel; executor ownership names who stops admission, cancels work, and awaits termination. One coherent effect model per subsystem. - Security: argv form (
ProcessBuilderwith separate args), never shell-string interpolation. Reflection, macros, Java serialization, and dynamic class loading are privileged. Parameterize SQL; type safety doesn't sanitize embedded languages. - Determinism: sort keys where stable order is required; inject clocks, random sources, and schedulers at test boundaries; collect parallel work by explicit sequence when output is ordered.
- Comments explain why, not what. Scaladoc on public types/methods documents algebraic error cases, resource scope, execution-context ownership, and cancellation semantics.
Output Contract
Scala you produce must: compile cleanly, carry Scaladoc on public
API, assert its invariants, return Either/Option on expected failures,
bound all work with named constants, stay within 100 columns and ~70 lines
per function, and pass the review checklist.
Review Checklist
- External input validated before privileged use; internal invariants asserted.
- All loops/retries/queues/executors bounded by named constants.
-
Optionvsnullvsfalsesemantics deliberate; Java nulls converted at one boundary. -
No
.getonOption/Either/Tryas the ordinary path; errors modeled as ADTs. - Matches on sealed ADTs exhaustive; no concealing catch-all.
- Names carry units last; no cryptic temporaries or opaque operators.
-
Every resource has one owner; teardown exists on failure paths; no lazy leak past
Using. -
Cancellation stops or joins owned work;
Awaittimeout not mistaken for cleanup. - Async refreshes reject stale generations; executor shutdown owned.
- No shell string interpolation of untrusted input; argv form used.
- Output deterministic where persisted or tested; keys sorted, clocks injected.
- Functions ≤ ~70 lines, lines ≤ 100 cols, comments explain intent.
ReferencesScala documentation · Scala 3 Book
TypeScriptSafety-first TypeScript under the strict profile — explicit contracts, strict null handling, and bounded async work.
Tiger Style TypeScript
Purpose
Apply the Tiger Style standard to TypeScript code. Priority order, always: Safety > Performance > Developer Experience. The full guide lives in references/TIGER_STYLE_TYPESCRIPT.md; this file is the operational core. When the two disagree, the full guide wins.
Workflow
- Pin the toolchain: TypeScript version (5.9+), strict profile
(
strict,noUncheckedIndexedAccess,exactOptionalPropertyTypes,noImplicitOverride,noFallthroughCasesInSwitch,noEmitOnError), module format (ESM vs CommonJS), target, runtime (Node vs browser). - Lay the module out top-to-bottom: types/interfaces, constants, runtime validators, pure functions, effectful adapters. Never start timers, sockets, or downloads on import.
- Validate external input at the boundary with a discriminated
Result<T, E>(or the subsystem's declared contract); assert internal invariants with real runtime guards. Types are erased — no type can stand in for validation. - Bound everything with named
*_MAXconstants: loops, retries, queues, caches, promise admission, output sizes. - Review against the checklist below before calling the code done.
Operating Rules
- Contracts first. Every function answers: permitted inputs, rejected inputs, max work, what state changes, who owns each resource, failure behavior. Public functions carry explicit return types.
- Static/runtime gap.
as,satisfies, interfaces, branding, andreadonlyguide trusted code only. A branded type needs a validated constructor;satisfiesdoes not validate JSON. Audit both sides. - Assertions test the condition. User-defined assertion signatures must
actually narrow
unknown. Never use!or casts to bypass external validation;console.assertdoes not terminate execution. - Bound everything. No unbounded
Promise.all, no unbounded retry, no unbounded cache. Retries need idempotency, a shared deadline, bounded backoff, and injected jitter. - Control flow: discriminated unions with exhaustive
switch(anevercheck only counts after the runtime boundary validated the discriminator). No nested ternaries for effectful logic. No recursion over attacker-controlled depth. - Absence discipline:
undefined,null,false,0,""are different. Use??only when null and undefined both mean missing — never||for defaults whenfalse/0is valid. - Numbers:
numberis floating-point. UseNumber.isSafeInteger, reject NaN/infinity and out-of-range values;bigintneeds explicit serialization (JSON can't serialize it). - Names are
camelCasewith units in the name:timeoutMs,payloadSizeBytes,retryCount. Types/classes arePascalCase. NoIprefixes on interfaces, no single-letter generics with domain roles. - Formatting: 2-space indent, max 100 columns, one statement per line. One pinned formatter and lint profile (e.g. prettier + eslint).
- Functions: ordinary functions stay near or below 70 lines. Prefer discriminated option objects over positional booleans.
- State and mutation: compute proposed state, validate, commit at one
obvious point.
constprevents rebinding, not object mutation. Async callbacks carry a generation/request identity and reject stale results. - Resources: one owner per timer, listener, file handle, socket,
subprocess, worker.
try/finallywith explicitdispose/close; clear timers, remove listeners, abort owned I/O.FinalizationRegistryis not cleanup. - Async: pass
AbortSignalwhere supported;Promise.racewith a timeout is not cancellation. Keep a bounded task registry, abort owned work on teardown, await settlement. - Security: never
eval/new Functionon untrusted data; noinnerHTMLfor text. The type checker is not a sandbox. Never interpolate untrusted values into shell strings — usespawn/execFilewith argv andshell: false. - Filesystem: path normalization is not authorization;
startsWith(root)is not containment. Write temp file in destination dir, check errors, then rename; restrictive permissions for sensitive files. - Determinism: explicit serialization order; inject clock/random in tests; async completion order never determines persisted results.
- Comments explain why, not what. TSDoc on public APIs: limits, units,
mutation, errors, cancellation. Explain every deliberate
any,!, or cast at its narrow boundary. - Dependencies: committed lockfile, frozen/immutable CI installs; review install scripts, native addons, and build plugins.
Output Contract
TypeScript you produce must: compile under the strict profile with
tsc --noEmit, carry explicit types on exported functions, assert its
invariants with real runtime guards, return Result<T, E> (or the declared
contract) on expected failures, bound all loops/retries/queues/promise
admission, stay within 100 columns and ~70 lines per function, and pass the
review checklist.
Review Checklist
- External input validated before trusted use; internal invariants guarded.
-
No
as/!/branding used to bypass validation; static/runtime gap audited. - All loops/retries/caches/queues/promise sets bounded by named constants.
-
undefined/null/false/0semantics deliberate; no||default clobbers. -
Numeric domains validated (
isSafeInteger, NaN/infinity rejected); units in names. - Every resource has one owner; teardown exists on all paths incl. cancellation.
- Async work abortable; stale results rejected; timeouts own cleanup.
-
No shell string interpolation; argv form with
shell: false. - Output deterministic where persisted or tested; no enumeration-order reliance.
-
Functions ≤ ~70 lines, lines ≤ 100 cols, comments explain intent and any
any.
ReferencesTypeScript documentation · Biome
WGSLSafety-first WGSL — explicit shader contracts, 16-byte uniform alignment, naga validation, and portability proven on every target backend.
Tiger Style WGSL
Purpose
Apply the Tiger Style standard to WGSL shaders. Priority order, always: Safety > Performance > Developer Experience. The full guide lives in references/TIGER_STYLE_WGSL.md; this file is the operational core. When the two disagree, the full guide wins.
Workflow
- Define the shader's contract: inputs (uniforms, textures, vertex attributes), backends targeted, maximum per-pixel work, fallback on shader-creation failure.
- Write the WGSL with 16-byte uniform alignment (every
vec3pads to 16 bytes; prefervec4). Document bind group layout in a comment. - Validate with naga (strip Bevy
#importlines first — they are not WGSL):grep -v "^#import" shader.wgsl > /tmp/check.wgsl && naga /tmp/check.wgsl - Check wgsl-analyzer diagnostics (ignore
#importline false positives for Bevy shaders; usewgsl_bevyfiletype). - Run the portability test (
tests/shader_render.rs) and capture a screenshot on target hardware. Naga cannot catch visual bugs. - Review against the checklist below before calling the shader done.
Operating Rules
- Contracts first. Every shader answers: accepted/rejected inputs, trust boundary (uniform values from CPU), max ALU/texture work per pixel, owner of every binding slot, failure behavior (fallback material), backend coverage.
- Portability is safety. GLES3 (Android) is the constraint driver.
No
f64, no@computewithout backend testing, no dynamic indexing, no subgroup ops without extensions. The portability test enforces this; do not weaken it without a backend-specific validation plan. - Alignment discipline. Uniform structs must be 16-byte aligned.
Order fields largest-first or add explicit
_padfields. Verify the RustShaderTypestruct matches field-for-field (order and types).structLayoutHints = truein the editor shows offsets visually. - Bind groups are engine contracts. Bevy
Material2d= group 2,UiMaterial= group 1. Document the layout in a shader comment. If the engine changes it, the comment makes the breakage obvious. - Bevy
#importis not WGSL. Strip before naga. Do not "fix" wgsl-analyzer errors on#importlines. Usewgsl_bevyfiletype for Bevy shaders (analyzer stays quiet, naga CLI validates). - Bound everything. Named constants with units (
MAX_PULSE_WIDTH,MIN_SOFTNESS). Guard divisions (max(w, 1e-4)). Loop bounds statically determinable or constant-bounded. - No float equality. Use epsilon comparisons. Do not rely on NaN bit patterns or denormal handling across backends.
- Uniformity. Texture samples need uniform control flow. Prefer
procedural shaders (no textures) for 2D effects. Restructure instead
of suppressing
derivative_uniformityunless safety is proven. - Errors vs degradation. Shader-creation failure → log, fall back to safe material, never black screen silently. Define the fallback before writing the shader.
- Test gates. naga validation + portability test + wgsl-analyzer
- screenshot on hardware. All four before done. Headless tests use
Option<ResMut<Assets<T>>>so missing material plugins don't panic.
- screenshot on hardware. All four before done. Headless tests use
Review Checklist
- naga validates (imports stripped)
- Uniform structs 16-byte aligned (Rust side matches)
- Bind group indices correct for material type
-
No non-portable constructs (
f64, dynamic indexing, etc.) -
requiresdirectives document the feature bar - Fallback behavior defined
- Screenshot on target hardware
-
tests/shader_render.rspasses
ReferencesWGSL specification · Bevy
ZigSafety-first Zig — explicit allocators, documented error unions, comptime proofs, and tests run in Debug and ReleaseSafe alike.
Tiger Style Zig
Purpose
Apply the Tiger Style standard to Zig code. Priority order, always: Safety > Performance > Developer Experience. The full guide lives in references/TIGER_STYLE_ZIG.md; this file is the operational core. When the two disagree, the full guide wins.
Workflow
- Pin the toolchain (Zig 0.16.0, target triple, libc, optimization mode).
Copy signatures from that version's standard library; never transplant
older
std.fsor child-process snippets across releases. - Lay the module out top-to-bottom: imports, domain constants, public types/error sets, validation, private leaf work, public orchestration, tests. Export only the intended API.
- Validate external input with error unions; assert internal invariants with
std.debug.assert(one invariant per assert); prove static properties withcomptimechecks. - Bound everything with named
*_maxconstants: loops, allocations, retries, queues, nesting depth, captured output. - Validate before calling it done:
zig fmt --check,zig testin Debug, thenzig test -O ReleaseSafeandzig test -O ReleaseFastso rejection survives disabled safety checks.
Operating Rules
- Contracts first. Every function answers: permitted inputs, rejected inputs, max work, what state changes, who owns each resource, failure behavior — including whether failure leaves state unchanged or partial.
- Errors are values. Return documented error unions (e.g.
ValidationError!u64);tryto propagate,catchonly for a specific recovery policy. No genericanyerroron public surfaces.deferis unconditional scope cleanup;errdeferis error-path cleanup until ownership transfers — never both freeing the same allocation. unreachableis a correctness claim, never a fallback. Never route malformed input, OOM, timeouts, or user configuration tocatch unreachable; it may become unchecked UB in unsafe builds.- Ownership is explicit.
deferimmediately after acquisition;errdeferwhile constructing an owned result, then transfer ownership on success. Document which allocator must free each returned allocation. Slices borrow, they do not own — never return a stack slice. One owner per mutable object. - Allocators are parameters, not globals. Prefer caller-provided buffers
or fixed capacities when the maximum is known; otherwise accept an
allocator, cap sizes, and test allocation failure. Never mix allocators
across free/resize; use
std.testing.allocatorin tests. An arena is not a memory bound. - Bound everything. No unbounded recursion (explicit stacks/depth budgets), no unbounded retry, no unbounded growth. Retries need idempotency, a shared deadline, and bounded backoff.
- Numbers: widths by domain;
usizefor addressable lengths, never as a wire format.@addWithOverflow/@mulWithOverflowwhen explicit reporting is needed;+%/+|only when that behavior is the specification. Validate narrowing and float-to-int domains; encode endianness explicitly. - Absence vs failure:
?Tis absence,E!Tis failure. Unwrap withif/orelseat boundaries;.?only after an established invariant.undefinedis not a default — every read follows a write. - Control flow: exhaustive
switchover tagged unions for domain states; no broadelsethat hides a new state. Policy decisions outside hot loops. - Names:
TitleCasetypes,camelCasefunctions,snake_casevalues/fields, units last (timeout_ms,payload_size_bytes,item_count_max). Non,sz,tmp2. - Formatting:
zig fmtowns formatting — four-space layout, ~100 columns, ~70-line functions, one statement per line.zig fmt --checkin CI; never manually fight its stable output. - Concurrency: pass the I/O dependency explicitly (0.16's interface-based I/O); bound group size, cancel and await owned work before freeing its data, and treat cancellation as a real error path. A concurrency-safe allocator does not make the objects it allocates thread-safe.
- Security: FFI, pointer casts, int-to-pointer, dynamic libraries, build scripts, and child processes are privileged boundaries. Localize casts with alignment/provenance/length/lifetime justification. Child processes use argv form — never hide a shell in a helper that claims a safe argv list.
- Determinism: sort keys before serializing; never serialize raw struct memory or hash-map iteration order; inject clocks and random sources in tests.
- Comments explain why, not what — especially why an unchecked operation
is valid at that point.
//!for modules,///for public declarations.
Output Contract
Zig you produce must: compile cleanly under the pinned toolchain,
pass zig fmt --check, document allocator ownership and error sets on
exported functions, return error unions on fallible paths, assert invariants,
bound all loops/allocations/retries, stay near 70 lines per function and 100
columns per line, and pass the review checklist.
Review Checklist
-
External input validated via error unions in every build mode; internal
invariants asserted; no
catch unreachableon external failure. - All loops/allocations/retries/queues bounded by named constants.
-
?TvsE!Tsemantics deliberate; no.?without an established invariant; noundefinedreads. -
Every allocation has one owner;
defer/errdeferplaced correctly; the freeing allocator is documented. -
Public error sets are meaningful; no
anyerrorleakage. - Integer widths and overflow policy documented; narrowing validated.
- No hidden global allocator; no mixed allocators; no unjustified FFI casts; child processes use argv form.
- Cancellation handled; no use-after-free of owned work; shared thread state and synchronization explicit.
- Output deterministic where persisted or tested; no hash-map order reliance.
-
Functions ≤ ~70 lines, lines ≤ 100 cols,
zig fmtclean, comments explain why.
ReferencesZig learning resources · Zig language reference · TigerStyle
These orders are standing: they do not expire between releases, and they are not suspended for deadlines. Every project on the solutions page was built under them — that is what the phrase means when it appears in a repository: the work was done under these orders.