diff --git a/.gitignore b/.gitignore
index 1394fe931..9fe9c7bfb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,5 @@ fastfetch.kdev4
*.user.*
*.swp
*.log
+/*.zh.md
+/.*-ai/
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 000000000..0da08c892
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,724 @@
+# Contributing to fastfetch
+
+
+
+ Note
+
+
+ This document is generated by AI, and mainly for AI use. It is not a substitute for human review, and may contain errors or omissions. Please verify any information before relying on it.
+
+
+Thanks for your interest in fastfetch. This document covers building, architecture, how to add a module or a logo, code style, commit conventions, and the pull request workflow.
+
+fastfetch is a system information tool written in C23, supporting Linux, macOS, Windows, the BSDs, Solaris, Haiku and Android. The project is borderline obsessive about **startup time** and **optional dependencies** — many design decisions only make sense under that constraint, and this document keeps coming back to it.
+
+---
+
+## Table of contents
+
+- [Contributor quick reference](#contributor-quick-reference)
+- [Getting started](#getting-started)
+- [Code map](#code-map)
+- [Core architecture: modules vs. detection](#core-architecture-modules-vs-detection)
+- [Module registration](#module-registration)
+- [Walkthrough: adding a module](#walkthrough-adding-a-module)
+- [Walkthrough: adding a logo](#walkthrough-adding-a-logo)
+- [Platform conventions](#platform-conventions)
+- [Code style](#code-style)
+- [Commit messages](#commit-messages)
+- [Changelog](#changelog)
+- [Tests](#tests)
+- [Pull requests](#pull-requests)
+- [Gotchas](#gotchas)
+
+---
+
+## Contributor quick reference
+
+| Task | Start here | Verify with |
+|---|---|---|
+| Build and run fastfetch | `run.sh`, `CMakeLists.txt` | `./run.sh` |
+| Add a module | `src/modules//`, `src/detection//`, `src/modules/modules.c` | `cmake -B build && cmake --build build -j` |
+| Add a platform implementation | `src/detection//_.c` | Build on the target platform or CI |
+| Add an ASCII logo | `src/logo/ascii//.txt`, matching `.inc` | `./build/fastfetch --logo ` |
+| Change formatting or JSON output | `src/modules//.c` | `./build/fastfetch -s --format json` |
+| Change shared formatting or containers | `src/common/format.h`, `src/common/color.h`, `src/common/FFstrbuf.h` | Build and run the matching test |
+| Run the test suite | `tests/`, `build/` | `cd build && ctest --output-on-failure` |
+
+For a normal code change, the shortest useful loop is: configure, build the `fastfetch` target, run the affected module, then run the tests. If you add a directory or a generated input, re-run `cmake -B build` so CMake refreshes its file globs.
+
+---
+
+## Getting started
+
+### Building
+
+The quickest way is `run.sh` in the repository root — it creates `build/`, configures, compiles and runs the binary:
+
+```sh
+./run.sh # build and run
+./run.sh --format json # arguments are forwarded to fastfetch
+```
+
+Manual build:
+
+```sh
+cmake -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo
+cmake --build build --target fastfetch -j$(nproc)
+./build/fastfetch
+```
+
+The default build type is `RelWithDebInfo`. LTO is enabled whenever `ENABLE_LTO=ON` **and** `CMAKE_BUILD_TYPE != Debug` — so the default already has it, and only `Debug` builds lack it. This matters because LTO here is not merely an optimization: it is what strips the code of disabled modules. See [Gotchas](#gotchas).
+
+### Common build options
+
+| Option | Default | Notes |
+|---|---|---|
+| `CMAKE_BUILD_TYPE` | `RelWithDebInfo` | LTO is on for anything but `Debug` |
+| `BUILD_TESTS` | `OFF` | Builds the unit tests in `tests/` |
+| `BUILD_FLASHFETCH` | `ON` | Also builds the stripped-down `flashfetch` |
+| `BINARY_LINK_TYPE` | `dlopen` | `dlopen` / `dynamic` / `static` |
+| `ENABLE_ASAN` | `OFF` | Address Sanitizer |
+| `ENABLE_LTO` | `ON` | Link-time optimization |
+| `MODULE_DISABLE_` | `OFF` | Disable a module, e.g. `MODULE_DISABLE_GPU=ON` |
+| `SET_TWEAK` | `ON` | Appends a tweak to the dev version; turned off for releases |
+
+`BINARY_LINK_TYPE=dlopen` (the default) is a deliberate design choice: optional dependencies (Vulkan, Wayland, DBus, ImageMagick, chafa, …) are loaded at runtime, so a missing library never prevents startup. When adding a third-party dependency, use the dlopen helpers in `common/library.h` — do not link it directly.
+
+For a minimal-dependency build, see `.github/workflows/build-no-features-test.yml`:
+
+```sh
+cmake -DBUILD_TESTS=On -DENABLE_VULKAN=OFF -DENABLE_WAYLAND=OFF -DENABLE_X11=OFF \
+ -DENABLE_DBUS=OFF -DENABLE_ZLIB=OFF ... .
+```
+
+### Dependencies
+
+Required: CMake ≥ 3.21 and a C23 compiler (GCC, Clang or MSVC). Everything else is optional.
+
+Optional dependencies are auto-detected: libpci, libdrm, vulkan, wayland, xcb, xrandr, dbus, sqlite3, rpm, imagemagick{6,7}, chafa, zlib, egl, glx, opencl, freetype, pulse, ddcutil, elf, libzfs, and more.
+
+---
+
+## Code map
+
+```
+src/
+├── fastfetch.c entry point: CLI parsing, module dispatch, main loop (36 KB)
+├── flashfetch.c entry point for the stripped-down build
+├── options/ global (non-module) config: general / display / logo
+├── modules/ module layer — module formatting and output
+├── detection/ detection layer — platform-specific data retrieval
+├── common/ infrastructure: FFstrbuf, FFlist, formatting, printing, networking
+├── logo/ 530 ASCII logos plus the image logo backends
+└── 3rdparty/ yyjson, widecharwidth, display-library
+```
+
+Flow of control:
+
+```
+fastfetch.c → options/ (global configuration)
+ → modules/ (76 modules, formatting and output)
+ → detection/ (one platform implementation each, raw data)
+ → common/ (infrastructure)
+```
+
+The number of module directories and detection directories does not match, and that is expected. The following relationships are structural rather than a fixed inventory:
+
+- **13 modules have no detection directory** — they are either pure layout (`break`, `separator`, `colors`, `title`, `logo`) or reuse someone else's detection result (`display`, `monitor`, `kernel`, `shell`, `terminal`, `player`, `custom`, `datetime`).
+- **4 detection directories serve modules with different names** — `displayserver` (→ `display`, `monitor`), `gtk_qt` (→ `theme`, `icons`, `font`, `cursor`), `terminalshell` (→ `terminal`, `shell`) and `libc`.
+
+So do not assume `modules//` always has a matching `detection//`.
+
+### Layer boundaries
+
+| Layer | Owns | Must not |
+|---|---|---|
+| `options/` | **Global** config (colors, logo, threading, timeouts) | hold per-module options |
+| `modules/` | formatting, printing, JSON serialization | read `/proc`, call Win32 APIs, parse sysfs |
+| `detection/` | cross-platform data retrieval, clean result structs | print anything, read display config |
+| `common/` | general-purpose utilities, no business logic | depend on a specific module |
+
+The test is simple: **no file under `detection/` should contain `printf` or read `instance.config`.** Symmetrically, no file under `modules/` should contain `#ifdef __linux__`.
+
+---
+
+## Core architecture: modules vs. detection
+
+This is the single most important convention in the codebase. Every feature is a fixed set of files — using CPU as the example:
+
+```
+modules/cpu/option.h module option struct
+modules/cpu/cpu.c formatting and output
+detection/cpu/cpu.h platform-independent interface
+detection/cpu/cpu_linux.c ┐
+detection/cpu/cpu_apple.c │
+detection/cpu/cpu_windows.c ├─ platform implementations, one linked per build
+detection/cpu/cpu_bsd.c │
+detection/cpu/cpu_nosupport.c ┘
+```
+
+The interface is deliberately minimal — usually just two things:
+
+```c
+typedef struct FFCPUResult { ... } FFCPUResult; // result struct
+const char* ffDetectCPU(const FFCPUOptions* options, FFCPUResult* cpu);
+```
+
+The `const char*` return value is an **error string**; `nullptr` means success. This pattern is used throughout `detection/` — please keep it consistent.
+
+Helper functions are only exposed when they are genuinely shared between platform implementations (as in `cpu.h`: `ffCPUAppleCodeToName`, `ffCPUDetectByCpuid`).
+
+**The payoff:** adding a platform never touches `modules/`; fixing output formatting never touches `detection/`.
+
+---
+
+## Module registration
+
+### The descriptor
+
+Every module exports one `FFModuleBaseInfo` (defined in `common/option.h:46`) — essentially a hand-written vtable:
+
+```c
+typedef struct FFModuleBaseInfo {
+ const char* name; // lookup key, e.g. "cpu"
+ const char* description; // help text
+ FFModuleDisplayName displayName; // module name in 20 languages
+
+ void (*initOptions)(void* options);
+ void (*destroyOptions)(void* options);
+ void (*parseJsonObject)(void* options, struct yyjson_val* module);
+ bool (*printModule)(void* options);
+ bool (*generateJsonResult)(void* options, yyjson_mut_doc*, yyjson_mut_val*);
+ void (*generateJsonConfig)(void* options, yyjson_mut_doc*, yyjson_mut_val*);
+
+ FFModuleFormatArgList formatArgs; // format placeholder table
+ const uint8_t defaultOrder; // sort weight
+} FFModuleBaseInfo;
+```
+
+The source comment openly admits this is UB — `void*` is not compatible with `FF*Options*`. It is a pragmatic compromise to get polymorphism in C. Don't try to "fix" it.
+
+### The registry: a first-letter hash bucket
+
+`src/modules/modules.c` defines 26 `static FFModuleBaseInfo*` arrays (`A[]` through `Z[]`), each terminated by `nullptr`, collected into `ffModuleInfos[26]`:
+
+```c
+FFModuleBaseInfo** modules = ffModuleInfos[toupper(name[0]) - 'A']; // O(1) bucket lookup
+for (; *modules; ++modules) { // linear scan in the bucket
+ if (ffStrEqualsIgnCase(name, (*modules)->name)) { ... }
+}
+```
+
+The registry is small enough that a subtraction plus a few string comparisons is preferable to a general-purpose hash table. When adding a module, place its descriptor in the bucket matching the first letter of `.name`; keep the existing `nullptr` terminator at the end.
+
+### The calling convention: zero heap allocation
+
+Every dispatch site (`jsonconfig.c:98`, `commandoption.c:189`) follows the same pattern:
+
+```c
+alignas(uint64_t) uint8_t optionBuf[FF_OPTION_MAX_SIZE]; // 256 bytes, on the stack
+baseInfo->initOptions(optionBuf);
+baseInfo->parseJsonObject(optionBuf, jsonVal); // JSONC path only
+baseInfo->printModule(optionBuf); // or generateJsonResult
+baseInfo->destroyOptions(optionBuf);
+```
+
+`FF_OPTION_MAX_SIZE = 1 << 8` (256 bytes). Every module's `option.h` must end with:
+
+```c
+static_assert(sizeof(FFCPUOptions) <= FF_OPTION_MAX_SIZE, "FFCPUOptions size exceeds maximum allowed size");
+```
+
+**Meaning: no module option struct may exceed 256 bytes.** Hard constraint, enforced at compile time.
+
+The three dispatch entry points:
+
+| Entry point | Location | Used for |
+|---|---|---|
+| `parseModuleJsonObject` | `common/impl/jsonconfig.c:90` | the `modules[]` array in a JSONC config |
+| `parseStructureCommand` | `common/impl/commandoption.c:181` | the colon-separated structure string |
+| `ffParseModuleOptions` | `common/impl/commandoption.c:13` | **deprecated** — see [Gotchas](#4-cli-module-options-are-removed) |
+
+### Build-time module discovery
+
+`CMakeLists.txt:131` globs `src/modules/*/` and generates a `MODULE_DISABLE_` option per directory:
+
+```cmake
+file(GLOB FF_MODULE_DIRS RELATIVE "..." ".../src/modules/*/")
+foreach(FF_MODULE_DIR ${FF_MODULE_DIRS})
+ option(MODULE_DISABLE_${FF_MODULE_UPPER} "Disable module ${FF_MODULE_DIR}" OFF)
+endforeach()
+```
+
+**Adding a module requires no CMake change.** The same trick generates the package manager switches (`:146` regex-scans `src/modules/packages/option.h` for `FF_PACKAGES_FLAG_*_BIT`).
+
+---
+
+## Walkthrough: adding a module
+
+Adding a `Foo` module, step by step.
+
+### 1. Define the option struct
+
+`src/modules/foo/option.h`:
+
+```c
+#pragma once
+
+#include "common/option.h"
+
+typedef struct FFFooOptions {
+ FFModuleArgs moduleArgs; // must be the first field
+ bool showBar;
+} FFFooOptions;
+
+static_assert(sizeof(FFFooOptions) <= FF_OPTION_MAX_SIZE, "FFFooOptions size exceeds maximum allowed size");
+```
+
+`FFModuleArgs` must come first. It provides `key`, `format`, `outputColor`, `keyColor`, `keyIcon` and `keyWidth`; because it is the first field, `ffJsonConfigParseModuleArgs()` can handle these generically and you get them **for free — no parsing code to write**.
+
+Note these fields can only be set through the JSON config; CLI module options have been removed (see [Gotchas](#4-cli-module-options-are-removed)).
+
+### 2. Implement detection
+
+`src/detection/foo/foo.h`:
+
+```c
+#pragma once
+#include "fastfetch.h"
+
+typedef struct FFFooResult {
+ FFstrbuf name;
+ uint32_t count;
+} FFFooResult;
+
+const char* ffDetectFoo(const FFFooOptions* options, FFFooResult* result);
+```
+
+`src/detection/foo/foo_linux.c`:
+
+```c
+#include "foo.h"
+
+const char* ffDetectFoo(const FFFooOptions* options, FFFooResult* result) {
+ // read /sys, /proc, sysfs, dbus, ...
+ return nullptr; // success; return an error string on failure
+}
+```
+
+**Write one file per target platform, and a `foo_nosupport.c` fallback for the rest.** There are already 45 `*_nosupport.c` files doing exactly this.
+
+### 3. Implement the module
+
+`src/modules/foo/foo.c` — implement the six functions, then define the descriptor:
+
+```c
+FFModuleBaseInfo ffFooModuleInfo = {
+ .name = "Foo",
+ .description = "Print foo information",
+ .displayName = {
+ .en = "Foo", .ar = "Foo", .cs = "Foo", .de = "Foo", .es = "Foo",
+ .fr = "Foo", .gl = "Foo", .he = "Foo", .id = "Foo", .it = "Foo",
+ .ja = "Foo", .ko = "Foo", .pl = "Foo", .pt = "Foo", .ru = "Foo",
+ .tr = "Foo", .uk = "Foo", .vi = "Foo", .zh_CN = "Foo", .zh_TW = "Foo",
+ },
+ .initOptions = (void*) ffInitFooOptions,
+ .destroyOptions = (void*) ffDestroyFooOptions,
+ .parseJsonObject = (void*) ffParseFooJsonObject,
+ .printModule = (void*) ffPrintFoo,
+ .generateJsonResult = (void*) ffGenerateFooJsonResult,
+ .generateJsonConfig = (void*) ffGenerateFooJsonConfig,
+ .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {
+ { "Name", "name" },
+ { "Count", "count" },
+ })),
+ .defaultOrder = 73,
+};
+```
+
+Points to note:
+
+- **`displayName`** is a literal block of all 20 language fields — there is no shortcut macro, so copy the layout from a neighboring module: **`en`, `ar`, `cs`, `de`, `es`, `fr`, `gl`, `he`, `id`, `it`, `ja`, `ko`, `pl`, `pt`, `ru`, `tr`, `uk`, `vi`, `zh_CN`, `zh_TW`**.
+ **Fill in all 20.** The struct is read by byte offset (see [Gotchas](#6-localization-uses-byte-offsets-not-enums)) and there is **no fallback** — a missing field means the key prints empty in that language.
+- **`formatArgs`** must be exhaustive. It drives the placeholder list printed by `fastfetch -h foo-format`; **anything you omit is invisible and unusable to the user.**
+- The `moduleFormat` section in `doc/json_schema.json` is generated by `fastfetch -h format-json`. Its metadata comes from `FFModuleBaseInfo::formatArgs`. To keep the schema and module descriptors consistent, do not edit the `moduleFormat` section in `doc/json_schema.json` by hand; update the module metadata and regenerate it instead.
+- **`defaultOrder`**: use the current maximum plus 1. Search the existing descriptors for `.defaultOrder =` before choosing a value; do not copy a hard-coded value from this document. Leaving it out, or setting it to `0`, makes the module **disappear** from the interactive `--gen-config` picker. Only `logo`, `command` and `custom` do this deliberately, because they need user arguments or are invoked directly by the display layer.
+
+### 4. Register it
+
+- Add `#include "modules/foo/foo.h"` to `src/modules/modules.h`
+- Insert into the `F[]` array in `src/modules/modules.c`:
+
+```c
+#if !FF_MODULE_DISABLE_FOO
+ &ffFooModuleInfo,
+#endif
+```
+
+`FF_MODULE_DISABLE_FOO` is generated by CMake — you do not define it yourself.
+
+### 5. Modules that need warm-up
+
+If your module needs a sampling interval (CPU usage) or a network round-trip (public IP, weather), also implement `ffPrepareFoo()` and register it in the switch inside `ffPrepareCommandOption()` in `common/impl/commandoption.c`, under the right first-letter `case`. Six modules do this today: CPUUsage, DiskIO, NetIO, PublicIP, Top and Weather.
+
+### 6. Verify
+
+```sh
+cmake -B build && cmake --build build -j
+./build/fastfetch -s foo --format json # JSON output
+./build/fastfetch -h foo-format # list formatArgs placeholders
+./build/fastfetch --gen-config # confirm it appears in the picker
+```
+
+Note the `-format` suffix on the help flag: `fastfetch -h foo` does not work, only `fastfetch -h foo-format`.
+
+---
+
+## Walkthrough: adding a logo
+
+A new logo **must** have a corresponding "Logo Request" issue, linked from the PR with `Closes #1234`. Logo PRs without a linked issue are not accepted.
+
+### 1. Drop in the ASCII file
+
+```
+src/logo/ascii//.txt
+```
+
+For example `src/logo/ascii/o/omarchy.txt`. Directories are already split by first letter (`a/` … `z/`, plus `_/`).
+
+The file is plain ASCII art with `$1` … `$9` as color placeholders:
+
+```
+ $3 ,-^-___
+$3 /\\\///
+$2refined.$1 /\\\\//
+```
+
+- `$1` … `$9` map to palette slots 1–9; at most 9 (`FASTFETCH_LOGO_MAX_COLORS = 9`)
+- `$$` is a literal `$`
+- Characters without a placeholder inherit the current color. Of the 530 existing logos, 239 use no placeholders at all (monochrome) and 291 do — **new logos should use placeholders**; monochrome is a legacy style
+- Tabs are expanded to 4 spaces
+- Colors can be overridden with `--logo-color-1` … `--logo-color-9`
+
+### 2. Register it in the `.inc`
+
+CMake turns each `.txt` into a `FASTFETCH_DATATEXT_LOGO_` macro (`CMakeLists.txt:418`), but **the registry itself is maintained by hand**. Edit `src/logo/ascii/.inc`:
+
+```c
+#ifdef FASTFETCH_DATATEXT_LOGO_OMARCHY
+// Omarchy
+{
+ .names = { "omarchy", "Omarchy" },
+ .lines = FASTFETCH_DATATEXT_LOGO_OMARCHY,
+ .colors = {
+ FF_COLOR_FG_BLUE,
+ FF_COLOR_FG_WHITE,
+ FF_COLOR_FG_CYAN,
+ },
+},
+#endif
+```
+
+- `names` is matched case-insensitively. Do not add names that differ only by case, because they can never be distinguished. Every name is also shown by `--list-logos`, so use user-friendly spelling such as an initial capital where appropriate.
+- For a new logo, use exactly one name unless there is a specific compatibility reason to add more. That name should first be the `ID` from `/etc/os-release`. If the ID cannot distinguish this logo from another logo for the same OS, use the `NAME` value instead. `logoGetBuiltinDetected` in `src/logo/logo.c` documents the detection order: `ID`, then `NAME`, then tokens from `ID_LIKE`, then the platform name fallback. Manual `logo-source` selection uses the name directly.
+- `colors` is positional — entry 0 is `$1`, entry 1 is `$2`, and so on
+- `colors[0]` also becomes the title color and `colors[1]` the key color, unless the user overrides them
+
+If the same OS has multiple logo variants, mark the variant explicitly with `.type`:
+
+```c
+.type = FF_LOGO_LINE_TYPE_SMALL_BIT,
+```
+
+Use `FF_LOGO_LINE_TYPE_ALTER_BIT` for an alternate logo, `FF_LOGO_LINE_TYPE_SMALL_BIT` for a small logo, or combine the flags when both apply. This is also a lookup optimization: a logo marked `FF_LOGO_LINE_TYPE_SMALL_BIT` is considered only for `type = small`, while a logo marked `FF_LOGO_LINE_TYPE_ALTER_BIT` is never selected by automatic detection. Alternate logos are available only when explicitly requested through `--logo `.
+
+### 3. Verify
+
+```sh
+./build/fastfetch --logo omarchy
+./build/fastfetch --list-logos | grep -i omarchy
+```
+
+---
+
+## Platform conventions
+
+The `detection/` layer picks its implementation by **filename suffix**; the choice is made at build time from `CMAKE_SYSTEM_NAME`:
+
+| Suffix | Platform |
+|---|---|
+| `_linux.c` | Linux |
+| `_apple.c` / `_apple.m` | macOS / iOS (`.m` for Objective-C) |
+| `_windows.c` / `_windows.cpp` | Windows (`.cpp` for WinRT / WMI) |
+| `_bsd.c` | FreeBSD |
+| `_nbsd.c` | NetBSD |
+| `_obsd.c` | OpenBSD |
+| `_sunos.c` | Solaris / illumos |
+| `_haiku.c` / `.cpp` | Haiku |
+| `_android.c` | Android (Termux) |
+| `_gnu.c` | GNU/Hurd |
+| `_nosupport.c` | Empty fallback |
+
+Current distribution of platform files in `detection/`:
+
+| Suffix | Files | Suffix | Files |
+|---|---|---|---|
+| `_linux` | 58 | `_obsd` | 16 |
+| `_windows` | 47 | `_haiku` | 13 |
+| `_apple` | 46 | `_android` | 12 |
+| `_nosupport` | 45 | `_gnu` | 3 |
+| `_bsd` | 29 | | |
+| `_sunos` | 19 | | |
+| `_nbsd` | 17 | | |
+
+(These are repository inventory figures, counting `.c`, `.m`, `.cpp`, and `.h`; they may change as platforms and modules are added.)
+
+**Don't pile `#ifdef __linux__` into one file.** When adding platform support, copy the closest existing implementation and change the suffix.
+
+`common/` uses the same idea: `common/impl/` contains `io_unix.c` / `io_windows.c`, `netif_linux.c` / `netif_apple.c` / `netif_bsd.c` and so on, with `common/apple/`, `common/windows/` and `common/haiku/` holding platform-specific helpers.
+
+When several platform suffixes could apply, use the most specific implementation supported by the build system (for example, `_nbsd.c` instead of the generic `_bsd.c` on NetBSD). Keep the generic file as the fallback for platforms that share its conventions.
+
+---
+
+## Code style
+
+### Formatting
+
+The project uses clang-format with `BasedOnStyle: LLVM` plus these key overrides:
+
+```yaml
+IndentWidth: 4
+UseTab: Never
+ColumnLimit: 0 # never wrap
+InsertBraces: true # braces even on single-statement ifs
+PointerAlignment: Left # char* p, not char *p
+SortIncludes: Never # include order is managed by hand
+AlignAfterOpenBracket: DontAlign
+BinPackParameters: false # all params on one line, or one per line
+```
+
+Before committing:
+
+```sh
+clang-format -i src/modules/foo/*.c src/modules/foo/*.h
+```
+
+`src/3rdparty/**`, `build/**` and `src/logo/builtin.c` are listed in `.clang-format-ignore` — **do not reformat them.**
+
+`.editorconfig` adds LF line endings, 4-space indent, a final newline, and trailing whitespace trimmed (except in Markdown).
+
+### Naming
+
+| Kind | Convention | Example |
+|---|---|---|
+| Functions | `ff` + PascalCase | `ffDetectCPU`, `ffPrintCPU` |
+| Types | `FF` + PascalCase | `FFCPUResult`, `FFModuleBaseInfo` |
+| Variables / fields | camelCase | `coresPhysical`, `keyWidth` |
+| Macros | SCREAMING_SNAKE_CASE | `FF_OPTION_MAX_SIZE` |
+| Module options | `FFOptions` | `FFCPUOptions` |
+| Detection results | `FFResult` | `FFCPUResult` |
+
+### Spelling
+
+CI runs codespell (`.codespellrc`). Known false positives live in `ignore-words-list` (`iterm`, `compiletime`, and various non-English distro words). Add new words there rather than changing the code.
+
+### Compiler warnings
+
+The build enables `-Wall -Wextra -Wconversion` plus several `-Werror`s:
+
+```
+-Werror=uninitialized -Werror=return-type -Werror=vla
+-Werror=incompatible-pointer-types -Werror=implicit-function-declaration -Werror=int-conversion
+```
+
+**`-Wconversion` is strict**: every implicit narrowing conversion needs an explicit cast.
+
+---
+
+## Commit messages
+
+Format:
+
+```
+[ (Platform)]: