Zig
Zen of Zig
- Communicate intent precisely.
- Edge cases matter.
- Favor reading code over writing code.
- Only one obvious way to do things.
- Runtime crashes are better than bugs.
- Compile errors are better than runtime crashes.
- Incremental improvements.
- Avoid local maximums.
- Reduce the amount one must remember.
- Focus on code rather than style.
- Resource allocation may fail; resource deallocation must succeed.
- Memory is a resource.
- Together we serve the users.
Operating contract
- Start from the Zen of Zig above. If a recommendation conflicts with it, revisit the recommendation before proceeding.
- Assume Zig 0.15.2 unless the user explicitly requests another version.
- Prefer witness-driven APIs: if a fact matters to safety, zero-copy legality, FFI soundness, or fast-path legality, represent it in a type or constructor, not a loose bool or comment.
- Treat hazard class -> proof obligation mapping as required.
- Prefer minimal incisions with explicit proof signals.
- If the repo already exposes
zig build lint, treat it as a hard gate. If lint is missing, prefer bootstrapping when safe; otherwise state LINT_UNAVAILABLE and compensate with stronger correctness evidence.
- Keep fast paths benchmarked, but keep safety checks on during correctness validation.
- Treat benchmarking and profiling as separate lanes: benchmarks prove the delta, profilers explain where the time or bytes went.
Baseline requirements
- Confirm toolchain version first:
zig version
- If the version is not
0.15.2, stop and state the mismatch.
- Treat
build.zig.zon as the dependency source of truth when packages are involved.
- Before editing, identify which hazard classes apply.
- If the request is performance-focused, run in two lanes:
- Correctness lane (
Debug or ReleaseSafe).
- Performance lane (
ReleaseFast) only after correctness passes.
- Choose the profiling instrument by symptom before changing code:
zprof for allocator or leak questions, system or telemetry profilers for CPU or lock contention, and benchmark lanes for regression proof.
- If a benchmark spans multiple abstraction layers and the aggregate result regresses, add decomposition lanes before changing code so you can separate substrate cost, wrapper cost, and full-path cost on the same workload.
Hazard classes and required proof
parser/decoder/zero-copy: unit tests, differential fuzz, validated witness, and backing-storage proof.
allocator-using: std.testing.checkAllAllocationFailures; if a specific schedule fails, pin a fail-nth regression seed too.
ffi/abi: boundary contract table, wrapper tests, link proof, and centralized raw pointer conversions.
optimizer-sensitive: scalar reference path plus differential checks across Debug, ReleaseSafe, and ReleaseFast.
concurrency/shared-state: deterministic schedule seed or replay harness, documented memory orders, and a sequential spec or witness model when lock-free claims are involved.
dependency/provenance: visible URL/hash plus origin, release, and signer or attestation notes when the pin is security-sensitive or long-lived.
- If multiple hazard classes apply, satisfy all relevant proof lanes.
Lint bootstrap (recommended when missing)
- Add
zlinter for Zig 0.15.x:
zig fetch --save git+https://github.com/kurtwagner/zlinter#0.15.x
- Add a
lint step in build.zig (all built-in rules baseline):
const zlinter = @import("zlinter");
// ...
const lint_cmd = b.step("lint", "Lint source code.");
lint_cmd.dependOn(step: {
var builder = zlinter.builder(b, .{});
inline for (@typeInfo(zlinter.BuiltinLintRule).@"enum".fields) |f| {
builder.addRule(.{ .builtin = @enumFromInt(f.value) }, .{});
}
break :step builder.build();
});
Core workflow
- State the contract: domain, invariants, error model, ownership model, authority model, and complexity target.
- Identify hazard classes and required proof artifacts before touching code.
- Confirm Zig version and lint availability.
- If lint exists, run
zig build lint -- --fix, review the diff, then re-run zig build lint -- --max-warnings 0.
- Build or derive a reference path, schema, or scalar fallback before touching optimized or unsafe code.
- Add witness types and boundary tests first.
- Run the required proof lanes for the active hazard classes.
- Optimize in order: algorithm -> data layout -> zero-copy/batching -> vectorization -> threading -> micro-tuning.
- Re-run lint, correctness, and perf gates after each optimization step.
- Report proof with exact commands and outcomes.
Lint gate
- Every Zig implementation turn should include lint evidence when the repo exposes
zig build lint.
- Default lint flow:
zig build lint -- --fix
zig build lint -- --max-warnings 0
- If
--fix changes code, review the diff before continuing.
- If lint is unavailable, say
LINT_UNAVAILABLE and do not pretend the gate passed.
Correctness gate (required)
- Every Zig change needs at least one correctness signal.
- For parsing, allocation, arithmetic, zero-copy, or safety-sensitive code:
std.testing.fuzz is required.
std.testing.checkAllAllocationFailures is required for allocator-using functions.
- FFI, bit-level, SIMD, and lock-free or atomic code need boundary-specific or differential tests in addition to ordinary unit tests.
- Prefer differential fuzzing (optimized path vs reference path).
- Re-run optimizer-sensitive logic under
Debug, ReleaseSafe, and ReleaseFast.
Standard correctness commands
# Project build/test
zig build
zig build test
# Single-file test
zig test src/main.zig
# Integrated fuzz path (requires a test step in build.zig)
zig build test --fuzz
Allocation-failure pattern
const std = @import("std");
fn parseWithAlloc(alloc: std.mem.Allocator, input: []const u8) !void {
_ = alloc;
_ = input;
}
test "allocation failure coverage" {
try std.testing.checkAllAllocationFailures(
std.testing.allocator,
parseWithAlloc,
.{"seed"},
);
}
- When an allocation failure only reproduces at a specific index, pin that fail index as a deterministic regression seed.
- Reference:
codex/skills/zig/references/fail_nth_alloc.zig
Boundary witnesses and zero-copy
- Separate
scan -> validate -> project.
- Validation should return a small witness type such as
ValidatedFrame, BorrowedView, NonEmptySlice, or AlignedBytes.
- Do not expose typed views, zero-copy projections, or
@ptrCast-based decoding until validation succeeds.
- Make borrowed vs owned states explicit in API types; use distinct types or
union(enum) rather than comments.
- Do not return slices backed by temporary or stack storage.
- Fail fast on over-capacity streaming tokens instead of truncating.
- Prefer one constructor per invariant cluster so fast paths can assume validated state.
- Reference:
codex/skills/zig/references/boundary_witness.zig
Schema-derived parse/format flows
- For wire or storage formats, prefer one comptime schema or policy surface that emits parser, formatter, docs, round-trip tests, and fuzz seeds from the same description.
- Centralize reflection in one classifier/helper layer instead of scattering raw
@typeInfo switches across serializers, deserializers, and adapters.
- Guard
@hasDecl and @hasField behind type-shape checks before duck-typing or custom-hook detection; @hasDecl is not valid on every Zig type.
- Let explicit custom hooks opt out of auto-derivation first, then fall back to a small
Kind or policy dispatcher.
- Put rename, flatten, skip, tag, default, and
with policies in a per-type pub const options surface and query it at comptime.
- Precompute wire names and naming-convention transforms at comptime so field matching and emission do not redo string work per value.
- Initialize flattened or defaulted sub-objects in dedicated helpers before parsing so the runtime path only fills observed fields and validates missing ones.
Build and project commands
# Initialize project
zig init
zig init --minimal
# Format
zig fmt src/main.zig
# Build and run
zig build
zig build run
zig build lint -- --fix
zig build lint -- --max-warnings 0
# Release-oriented build
zig build -Doptimize=ReleaseFast
# Cross-compile examples
zig build -Dtarget=x86_64-linux
zig build -Dtarget=aarch64-macos
# Cleanup
rm -rf zig-out zig-cache
Package management and dependency model
- State the package story explicitly when dependency workflow or "registry" questions come up.
- Zig has a built-in package manager, but no official central registry like
crates.io or npm.
- Treat the ecosystem as decentralized: dependencies are declared in
build.zig.zon, fetched by URL, and pinned by content hash.
- Prefer direct source archives or VCS-backed release archives over unofficial package indexes unless the user explicitly asks for third-party registries.
- When adding a dependency, update
build.zig.zon, review the saved hash, and keep the provenance URL visible in the diff.
- Treat the hash as integrity, not complete provenance. For security-sensitive or long-lived pins, also record origin repo, release tag or commit, fetch date, and signer or attestation notes in sidecar docs or commit notes.
Standard dependency commands
# Add a dependency and save it into build.zig.zon
zig fetch --save <url>
# Fetch dependencies declared by the build
zig build
# Format package metadata edits too
zig fmt build.zig
Registry-answer template
- Say "Zig has a package manager, but not an official central package registry."
- Point to
build.zig.zon plus zig fetch --save as the normal dependency path.
- Distinguish official decentralized workflow from community-maintained indexes.
- If provenance matters, add that Zig's content hash answers integrity, while signer and attestation evidence answer publisher trust and freshness.
Comptime and invariants
- Prefer compile-time invariant checks for shape, ABI, required methods, and specialization bounds.
- Use
@compileError to make illegal states unrepresentable at build time.
- Promote stable runtime facts to witness types or enums once discovered; do not keep rediscovering them with ad hoc checks.
- Keep generated specialization knobs small and measurable.
Template
const std = @import("std");
fn assertHasRead(comptime T: type) void {
if (!std.meta.hasMethod(T, "read")) {
@compileError(@typeName(T) ++ " must implement read()");
}
}
Serde-style derive patterns
- Keep runtime loops limited to I/O and data movement.
- Prefer schema-derived policy dispatch over ad hoc reflection spread across format-specific code.
- Use the same per-type options surface for formatting, parsing, round-trip tests, and fuzz seed generation.
Safe decl template
const std = @import("std");
fn hasDeclSafe(comptime T: type, comptime name: []const u8) bool {
return switch (@typeInfo(T)) {
.@"struct", .@"union", .@"enum", .@"opaque" => @hasDecl(T, name),
else => false,
};
}
fn classify(comptime T: type) enum { auto, custom } {
if (hasDeclSafe(T, "serialize") or hasDeclSafe(T, "deserialize")) {
return .custom;
}
return .auto;
}
FFI and C interop boundary
- Split raw
extern fn declarations from safe wrappers.
- Maintain a boundary contract table for each nontrivial symbol: nullability, length source, mutability, ownership in/out, lifetime, thread-safety, error mapping, and linkage.
- Centralize
@ptrCast, sentinel handling, null handling, and errno or result translation in one wrapper layer.
- Mirror C boundary assumptions in Zig wrapper types, asserts, and witness constructors.
- Test the link step (
linkLibC, linkSystemLibrary) and the unhappy paths, not just happy-path calls.
- Do not let borrowed C pointers escape past the documented lifetime or thread domain.
- When a C dependency changes, compare signatures and ABI expectations; if the code is security-sensitive and tooling is available, prefer an
abidiff-style check before trusting the upgrade.
- Reference:
codex/skills/zig/references/ffi_contract_template.md
Concurrency and weak-memory lane
- Prefer ownership transfer, sharding, or message passing before lock-free shared-state designs.
- For shared mutable state, write down the invariant, owner transitions, and memory order for each atomic before editing code.
- Use seeded schedule fuzzing, deterministic yield injection, or replayable interleavings; check failing seeds into
testdata/.
- For lock-free claims, add a sequential spec and, when feasible, a litmus-style or witness model in a sidecar test or notes file.
- Stress tests alone are not proof of concurrent correctness.
- Re-run concurrent code under multiple optimize modes and on Linux or CI when platform behavior differs.
Profiling stack
Use this section when the request is about speed, latency, throughput, memory growth, leaks, or hotspot analysis.
- Start by naming the symptom precisely:
- allocation churn, live-bytes growth, leaks, or unexplained allocator pressure -> use
zprof
- unknown CPU hotspot, cache issue, branchy slow path, or lock contention -> use a system sampler first and optionally telemetry tooling if the repo already supports it
- broad regression proof or before/after validation -> use the repo benchmark or perf harness first
- Do not flatten all profiling into one tool.
zprof is the default allocator profiler, not a substitute for CPU sampling.
- If the repo already ships benchmark or perf steps, run those before adding new instrumentation so you preserve the existing proof surface.
zprof default lane
Deep-research default: prefer zprof first for allocator-focused profiling. As of 2026-04-04, upstream v3.0.1 is current, build.zig.zon declares minimum_zig_version = "0.15.1", and the library wraps any Zig allocator while exposing allocated, alloc_count, free_count, live_peak, live_bytes, plus hasLeaks() and reset().
Installation path to encourage by default:
# Verify if a newer upstream tag exists when freshness matters.
gh api repos/ANDRVV/zprof/releases/latest --jq '{tag_name,published_at,html_url}'
# Current researched example
zig fetch --save https://github.com/ANDRVV/zprof/archive/v3.0.1.zip
build.zig wiring:
const zprof_dep = b.dependency("zprof", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("zprof", zprof_dep.module("zprof"));
Minimal usage pattern:
const std = @import("std");
const Zprof = @import("zprof").Zprof;
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
var prof: Zprof(.{}) = .init(gpa.allocator(), undefined);
const allocator = prof.allocator();
const buf = try allocator.alloc(u8, 4096);
defer allocator.free(buf);
std.debug.print(
"allocs={d} frees={d} live={d} peak={d}\n",
.{
prof.profiler.alloc_count.get(),
prof.profiler.free_count.get(),
prof.profiler.live_bytes.get(),
prof.profiler.live_peak.get(),
},
);
}
zprof operating rules:
- Wrap the allocator boundary closest to the suspected waste so the counters stay local and explanatory.
- Keep workload, dataset, optimize mode, and child allocator identical across baseline and variant runs.
- Use
prof.profiler.reset() between phases if one process captures multiple steps.
- Enable
.thread_safe = true only when multiple threads truly share the wrapped allocator; otherwise prefer one profiler per thread or per subsystem to keep attribution sharp.
- Disable unneeded counters when chasing a narrow question so overhead stays low and the signal is easier to read.
- Pair
zprof with ordinary benchmarks. Allocator counters can explain why a variant regressed, but they do not prove the user-visible delta by themselves.
- Pair
zprof with correctness gates. A lower allocation count is not a win if it changes semantics or hides a leak elsewhere.
CPU and telemetry lane
- If
zprof counters stay flat while wall time regresses, switch to a CPU sampler instead of guessing.
- On Linux, prefer
perf record and perf report for sampled call stacks on optimized binaries.
- On macOS, prefer Instruments Time Profiler for sampled CPU hotspots.
- For long-running concurrent or frame-oriented systems where time-series causality matters, consider Tracy if the repo already supports it or the user explicitly wants instrumentation. Tracy is strong for CPU, locks, context switches, and telemetry; it is not the first move for a small allocator-only question.
- Do not add heavy profiler integrations before a smaller benchmark or allocator-profile pass rules them in.
Performance lane and $lift handoff
Use this lane when the request is about speed, latency, throughput, memory, or profiling.
- If you can run a workload: produce baseline + after numbers and bottleneck evidence.
- If you cannot run a workload: mark output
UNMEASURED and provide exact commands.
- Keep correctness gates before and after each performance change.
- Pick the profiling stack deliberately: benchmark for proof,
zprof for allocator questions, CPU samplers for hot-path localization, and telemetry tools only when the workload shape justifies them.
- Use statistical discipline for small deltas: fixed dataset, warmups, repeated fresh-process runs, and sample counts. If the delta is small enough to be noisy, report an effect size or interval, not just one median.
- Prefer causal or progress-point instrumentation when the workload is concurrent or pipeline-shaped and flat hotspots are misleading.
- Optimize in order: algorithm -> data layout -> zero-copy/batching -> vectorization -> threading -> micro-tuning.
- Keep scalar fallbacks and differential tests after vectorization or threading changes.
- For large hot Linux binaries, consider a post-link layout or PGO/BOLT-style lane before invasive source rewrites.
Minimal measured loop
# 1) correctness first
zig build test
# 2) safe native build for reference behavior
zig build -Doptimize=ReleaseSafe -Dtarget=native -Dcpu=native
# 3) fast native build for perf work
zig build -Doptimize=ReleaseFast -Dtarget=native -Dcpu=native
# 4) if the repo exposes a benchmark step, prefer it
zig build bench -- --samples 10 --warmup 3
# 5) allocator-focused lane when bytes or leaks are the question
gh api repos/ANDRVV/zprof/releases/latest --jq '{tag_name,published_at,html_url}'
When the request is a broader perf pass with explicit reporting format, apply $lift conventions (contract -> baseline -> bottleneck -> experiments -> result -> regression guard).
Benchmark decomposition rule
Use this when a Zig perf regression crosses abstraction layers and one aggregate benchmark is not enough to localize the cost.
- Keep the same dataset, warmup, sample count, checksum, allocator, and optimize mode across all lanes.
- Add 2-4 lanes inside the same harness to isolate:
- substrate only
- wrapper or shell only
- full path
- Name the lanes concretely (for example
raw_reset_only, effect_passthrough, full_raw, full_effect) and keep their output adjacent so the subtraction is obvious.
- Optimize from the decomposed result, not from the aggregate delta alone.
- Once the bottleneck is understood, either keep the extra lanes as regression guards or gate them behind an explicit benchmark mode; do not leave noisy one-off instrumentation in the default path without a reason.
- When a pipeline exposes throughput or latency progress points, keep them stable across baseline and variant runs.
SIMD and threading policy
- Use SIMD or threading only when profiling shows a CPU-bound hot path.
- Keep scalar fallback paths and deterministic behavior.
- Avoid hidden allocations or synchronization inside hot loops.
- Re-run fuzz, differential tests, and any schedule-sensitive tests after vectorization or parallelization changes.
macOS fuzz caveat
zig build test --fuzz may fail on macOS (InvalidElfMagic) in Zig 0.15.2.
If this occurs:
- State the local limitation explicitly.
- Keep
std.testing.fuzz targets in-tree.
- Run fuzz on Linux/CI or an external harness.
- Add deterministic regression seeds under
testdata/fuzz/.
Trigger-audit workflow ($seq backed)
Use this to measure whether Zig intent is being routed to $zig.
# Run audit on full history
uv run python codex/skills/zig/scripts/zig_trigger_audit.py --root ~/.codex/sessions
# Time-windowed audit
uv run python codex/skills/zig/scripts/zig_trigger_audit.py \
--root ~/.codex/sessions \
--since 2026-02-01T00:00:00Z \
--strict-implicit \
--format json \
--output /tmp/zig-audit.json
# Regression tests for the audit script
uv run python codex/skills/zig/scripts/test_zig_trigger_audit.py
Notes:
- The audit uses literal
contains matching to avoid regex parser limitations in seq for dotted literals (for example .zig, build.zig, std.simd).
- Keep strict-implicit mode enabled when evaluating precision-sensitive routing changes.
- Lint and FFI cues (
zig build lint, zlinter, extern fn, linkSystemLibrary, linkLibC) are tracked as Zig intent.
Monthly drift scorecard
Generate a compact scorecard that combines:
$zig SKILL size.
- Trigger-audit counts/rates.
- Routing-gap invoked-rate metrics for safe Zig cues.
- Recommendation hints when drift is detected.
uv run python codex/skills/zig/scripts/zig_ops_scorecard.py \
--root ~/.codex/sessions \
--since 2026-02-01T00:00:00Z \
--format text
# Regression tests for the scorecard script
uv run python codex/skills/zig/scripts/test_zig_ops_scorecard.py
Notes:
- The scorecard should tolerate local
seq routing-gap builds that do not yet support --since.
skills-zig evidence lane
When validating guidance against current Zig production patterns, inspect:
- Source/build/release repo:
/Users/tk/workspace/tk/skills-zig
- Formula propagation repo:
/Users/tk/workspace/tk/homebrew-tap
Recommended checks:
git -C /Users/tk/workspace/tk/skills-zig log --oneline --max-count=30
rg -n "std.testing.fuzz|checkAllAllocationFailures|FailingAllocator" /Users/tk/workspace/tk/skills-zig/apps -g"*.zig"
rg -n "extern fn|linkSystemLibrary|linkLibC|sqlite3_" /Users/tk/workspace/tk/skills-zig/apps /Users/tk/workspace/tk/skills-zig/build.zig -g"*.zig"
rg -n "std.atomic|compareExchange|fetchAdd|Thread|spawn" /Users/tk/workspace/tk/skills-zig/apps -g"*.zig"
rg -n "std.simd|@Vector|std.Thread.Pool" /Users/tk/workspace/tk/skills-zig/apps -g"*.zig"
rg -n "perf_hub|CountingAllocator|warmup|samples" /Users/tk/workspace/tk/skills-zig/apps /Users/tk/workspace/tk/skills-zig/tools -g"*.zig"
rg -n "@typeInfo|@hasDecl|@hasField|@Type|comptime" /Users/tk/workspace/tk/skills-zig/apps -g"*.zig"
Use these results to keep $zig guidance aligned with what is true in active Zig repos.
Pitfalls
- Claiming performance wins without measured baseline/after evidence.
- Treating allocator counters as a substitute for CPU hotspot data, or CPU samples as a substitute for allocation evidence.
- Treating a validated fact as a comment instead of a witness type.
- Exposing zero-copy or FFI views before validation and lifetime checks.
- Running micro-optimizations before removing algorithmic or data-movement waste.
- Skipping allocation-failure coverage in allocator-heavy code.
- Claiming lock-free or concurrent correctness from stress tests alone.
- Skipping
zig build lint when the repo exposes it, or pretending lint passed when unavailable.
- Running
zig build lint -- --fix on a dirty tree without reviewing the resulting diff.
- Scattering
@typeInfo and trait-probe logic across format-specific code instead of centralizing the classifier and policy layer first.
- Treating borrowed memory as owned (or vice versa).
- Returning stack-backed slices.
- Treating URL+hash as complete supply-chain provenance.
- Assuming regex-like query patterns are portable across all tooling without validation.
References
- Boundary witness template:
codex/skills/zig/references/boundary_witness.zig
- Deterministic fail-nth allocation pattern:
codex/skills/zig/references/fail_nth_alloc.zig
- FFI contract template:
codex/skills/zig/references/ffi_contract_template.md
- Profiling playbook:
codex/skills/zig/references/profiling_playbook.md
- Differential fuzz template:
codex/skills/zig/references/fuzz_differential.zig
- Type-shape dispatcher example:
codex/skills/zig/references/type_switch.zig
@Type partial-builder example: codex/skills/zig/references/partial_type.zig
- Derive-walk policy pipeline:
codex/skills/zig/references/derive_walk_policy.zig
Converted and distributed by TomeVault — claim your Tome and manage your conversions.
1---2name: tkersey-dotfiles-zig3description: Zig4---56# Zig78## Zen of Zig9- Communicate intent precisely.10- Edge cases matter.11- Favor reading code over writing code.12- Only one obvious way to do things.13- Runtime crashes are better than bugs.14- Compile errors are better than runtime crashes.15- Incremental improvements.16- Avoid local maximums.17- Reduce the amount one must remember.18- Focus on code rather than style.19- Resource allocation may fail; resource deallocation must succeed.20- Memory is a resource.21- Together we serve the users.2223## Operating contract24- Start from the Zen of Zig above. If a recommendation conflicts with it, revisit the recommendation before proceeding.25- Assume Zig 0.15.2 unless the user explicitly requests another version.26- Prefer witness-driven APIs: if a fact matters to safety, zero-copy legality, FFI soundness, or fast-path legality, represent it in a type or constructor, not a loose bool or comment.27- Treat hazard class -> proof obligation mapping as required.28- Prefer minimal incisions with explicit proof signals.29- If the repo already exposes `zig build lint`, treat it as a hard gate. If lint is missing, prefer bootstrapping when safe; otherwise state `LINT_UNAVAILABLE` and compensate with stronger correctness evidence.30- Keep fast paths benchmarked, but keep safety checks on during correctness validation.31- Treat benchmarking and profiling as separate lanes: benchmarks prove the delta, profilers explain where the time or bytes went.3233## Baseline requirements34- Confirm toolchain version first:3536```bash37zig version38```3940- If the version is not `0.15.2`, stop and state the mismatch.41- Treat `build.zig.zon` as the dependency source of truth when packages are involved.42- Before editing, identify which hazard classes apply.43- If the request is performance-focused, run in two lanes:44 - Correctness lane (`Debug` or `ReleaseSafe`).45 - Performance lane (`ReleaseFast`) only after correctness passes.46 - Choose the profiling instrument by symptom before changing code: `zprof` for allocator or leak questions, system or telemetry profilers for CPU or lock contention, and benchmark lanes for regression proof.47 - If a benchmark spans multiple abstraction layers and the aggregate result regresses, add decomposition lanes before changing code so you can separate substrate cost, wrapper cost, and full-path cost on the same workload.4849## Hazard classes and required proof50- `parser/decoder/zero-copy`: unit tests, differential fuzz, validated witness, and backing-storage proof.51- `allocator-using`: `std.testing.checkAllAllocationFailures`; if a specific schedule fails, pin a fail-nth regression seed too.52- `ffi/abi`: boundary contract table, wrapper tests, link proof, and centralized raw pointer conversions.53- `optimizer-sensitive`: scalar reference path plus differential checks across `Debug`, `ReleaseSafe`, and `ReleaseFast`.54- `concurrency/shared-state`: deterministic schedule seed or replay harness, documented memory orders, and a sequential spec or witness model when lock-free claims are involved.55- `dependency/provenance`: visible URL/hash plus origin, release, and signer or attestation notes when the pin is security-sensitive or long-lived.56- If multiple hazard classes apply, satisfy all relevant proof lanes.5758### Lint bootstrap (recommended when missing)591. Add `zlinter` for Zig 0.15.x:6061```bash62zig fetch --save git+https://github.com/kurtwagner/zlinter#0.15.x63```64652. Add a `lint` step in `build.zig` (all built-in rules baseline):6667```zig68const zlinter = @import("zlinter");69// ...70const lint_cmd = b.step("lint", "Lint source code.");71lint_cmd.dependOn(step: {72 var builder = zlinter.builder(b, .{});73 inline for (@typeInfo(zlinter.BuiltinLintRule).@"enum".fields) |f| {74 builder.addRule(.{ .builtin = @enumFromInt(f.value) }, .{});75 }76 break :step builder.build();77});78```7980## Core workflow811. State the contract: domain, invariants, error model, ownership model, authority model, and complexity target.822. Identify hazard classes and required proof artifacts before touching code.833. Confirm Zig version and lint availability.844. If lint exists, run `zig build lint -- --fix`, review the diff, then re-run `zig build lint -- --max-warnings 0`.855. Build or derive a reference path, schema, or scalar fallback before touching optimized or unsafe code.866. Add witness types and boundary tests first.877. Run the required proof lanes for the active hazard classes.888. Optimize in order: algorithm -> data layout -> zero-copy/batching -> vectorization -> threading -> micro-tuning.899. Re-run lint, correctness, and perf gates after each optimization step.9010. Report proof with exact commands and outcomes.9192## Lint gate93- Every Zig implementation turn should include lint evidence when the repo exposes `zig build lint`.94- Default lint flow:95 - `zig build lint -- --fix`96 - `zig build lint -- --max-warnings 0`97- If `--fix` changes code, review the diff before continuing.98- If lint is unavailable, say `LINT_UNAVAILABLE` and do not pretend the gate passed.99100## Correctness gate (required)101- Every Zig change needs at least one correctness signal.102- For parsing, allocation, arithmetic, zero-copy, or safety-sensitive code:103 - `std.testing.fuzz` is required.104 - `std.testing.checkAllAllocationFailures` is required for allocator-using functions.105- FFI, bit-level, SIMD, and lock-free or atomic code need boundary-specific or differential tests in addition to ordinary unit tests.106- Prefer differential fuzzing (optimized path vs reference path).107- Re-run optimizer-sensitive logic under `Debug`, `ReleaseSafe`, and `ReleaseFast`.108109### Standard correctness commands110```bash111# Project build/test112zig build113zig build test114115# Single-file test116zig test src/main.zig117118# Integrated fuzz path (requires a test step in build.zig)119zig build test --fuzz120```121122### Allocation-failure pattern123```zig124const std = @import("std");125126fn parseWithAlloc(alloc: std.mem.Allocator, input: []const u8) !void {127 _ = alloc;128 _ = input;129}130131test "allocation failure coverage" {132 try std.testing.checkAllAllocationFailures(133 std.testing.allocator,134 parseWithAlloc,135 .{"seed"},136 );137}138```139140- When an allocation failure only reproduces at a specific index, pin that fail index as a deterministic regression seed.141- Reference: `codex/skills/zig/references/fail_nth_alloc.zig`142143## Boundary witnesses and zero-copy144- Separate `scan -> validate -> project`.145- Validation should return a small witness type such as `ValidatedFrame`, `BorrowedView`, `NonEmptySlice`, or `AlignedBytes`.146- Do not expose typed views, zero-copy projections, or `@ptrCast`-based decoding until validation succeeds.147- Make borrowed vs owned states explicit in API types; use distinct types or `union(enum)` rather than comments.148- Do not return slices backed by temporary or stack storage.149- Fail fast on over-capacity streaming tokens instead of truncating.150- Prefer one constructor per invariant cluster so fast paths can assume validated state.151- Reference: `codex/skills/zig/references/boundary_witness.zig`152153## Schema-derived parse/format flows154- For wire or storage formats, prefer one comptime schema or policy surface that emits parser, formatter, docs, round-trip tests, and fuzz seeds from the same description.155- Centralize reflection in one classifier/helper layer instead of scattering raw `@typeInfo` switches across serializers, deserializers, and adapters.156- Guard `@hasDecl` and `@hasField` behind type-shape checks before duck-typing or custom-hook detection; `@hasDecl` is not valid on every Zig type.157- Let explicit custom hooks opt out of auto-derivation first, then fall back to a small `Kind` or policy dispatcher.158- Put rename, flatten, skip, tag, default, and `with` policies in a per-type `pub const` options surface and query it at comptime.159- Precompute wire names and naming-convention transforms at comptime so field matching and emission do not redo string work per value.160- Initialize flattened or defaulted sub-objects in dedicated helpers before parsing so the runtime path only fills observed fields and validates missing ones.161162## Build and project commands163```bash164# Initialize project165zig init166zig init --minimal167168# Format169zig fmt src/main.zig170171# Build and run172zig build173zig build run174zig build lint -- --fix175zig build lint -- --max-warnings 0176177# Release-oriented build178zig build -Doptimize=ReleaseFast179180# Cross-compile examples181zig build -Dtarget=x86_64-linux182zig build -Dtarget=aarch64-macos183184# Cleanup185rm -rf zig-out zig-cache186```187188## Package management and dependency model189- State the package story explicitly when dependency workflow or "registry" questions come up.190- Zig has a built-in package manager, but no official central registry like `crates.io` or `npm`.191- Treat the ecosystem as decentralized: dependencies are declared in `build.zig.zon`, fetched by URL, and pinned by content hash.192- Prefer direct source archives or VCS-backed release archives over unofficial package indexes unless the user explicitly asks for third-party registries.193- When adding a dependency, update `build.zig.zon`, review the saved hash, and keep the provenance URL visible in the diff.194- Treat the hash as integrity, not complete provenance. For security-sensitive or long-lived pins, also record origin repo, release tag or commit, fetch date, and signer or attestation notes in sidecar docs or commit notes.195196### Standard dependency commands197```bash198# Add a dependency and save it into build.zig.zon199zig fetch --save <url>200201# Fetch dependencies declared by the build202zig build203204# Format package metadata edits too205zig fmt build.zig206```207208### Registry-answer template209- Say "Zig has a package manager, but not an official central package registry."210- Point to `build.zig.zon` plus `zig fetch --save` as the normal dependency path.211- Distinguish official decentralized workflow from community-maintained indexes.212- If provenance matters, add that Zig's content hash answers integrity, while signer and attestation evidence answer publisher trust and freshness.213214## Comptime and invariants215- Prefer compile-time invariant checks for shape, ABI, required methods, and specialization bounds.216- Use `@compileError` to make illegal states unrepresentable at build time.217- Promote stable runtime facts to witness types or enums once discovered; do not keep rediscovering them with ad hoc checks.218- Keep generated specialization knobs small and measurable.219220### Template221```zig222const std = @import("std");223224fn assertHasRead(comptime T: type) void {225 if (!std.meta.hasMethod(T, "read")) {226 @compileError(@typeName(T) ++ " must implement read()");227 }228}229```230231## Serde-style derive patterns232- Keep runtime loops limited to I/O and data movement.233- Prefer schema-derived policy dispatch over ad hoc reflection spread across format-specific code.234- Use the same per-type options surface for formatting, parsing, round-trip tests, and fuzz seed generation.235236### Safe decl template237```zig238const std = @import("std");239240fn hasDeclSafe(comptime T: type, comptime name: []const u8) bool {241 return switch (@typeInfo(T)) {242 .@"struct", .@"union", .@"enum", .@"opaque" => @hasDecl(T, name),243 else => false,244 };245}246247fn classify(comptime T: type) enum { auto, custom } {248 if (hasDeclSafe(T, "serialize") or hasDeclSafe(T, "deserialize")) {249 return .custom;250 }251 return .auto;252}253```254255## FFI and C interop boundary256- Split raw `extern fn` declarations from safe wrappers.257- Maintain a boundary contract table for each nontrivial symbol: nullability, length source, mutability, ownership in/out, lifetime, thread-safety, error mapping, and linkage.258- Centralize `@ptrCast`, sentinel handling, null handling, and errno or result translation in one wrapper layer.259- Mirror C boundary assumptions in Zig wrapper types, asserts, and witness constructors.260- Test the link step (`linkLibC`, `linkSystemLibrary`) and the unhappy paths, not just happy-path calls.261- Do not let borrowed C pointers escape past the documented lifetime or thread domain.262- When a C dependency changes, compare signatures and ABI expectations; if the code is security-sensitive and tooling is available, prefer an `abidiff`-style check before trusting the upgrade.263- Reference: `codex/skills/zig/references/ffi_contract_template.md`264265## Concurrency and weak-memory lane266- Prefer ownership transfer, sharding, or message passing before lock-free shared-state designs.267- For shared mutable state, write down the invariant, owner transitions, and memory order for each atomic before editing code.268- Use seeded schedule fuzzing, deterministic yield injection, or replayable interleavings; check failing seeds into `testdata/`.269- For lock-free claims, add a sequential spec and, when feasible, a litmus-style or witness model in a sidecar test or notes file.270- Stress tests alone are not proof of concurrent correctness.271- Re-run concurrent code under multiple optimize modes and on Linux or CI when platform behavior differs.272273## Profiling stack274Use this section when the request is about speed, latency, throughput, memory growth, leaks, or hotspot analysis.275276- Start by naming the symptom precisely:277 - allocation churn, live-bytes growth, leaks, or unexplained allocator pressure -> use `zprof`278 - unknown CPU hotspot, cache issue, branchy slow path, or lock contention -> use a system sampler first and optionally telemetry tooling if the repo already supports it279 - broad regression proof or before/after validation -> use the repo benchmark or perf harness first280- Do not flatten all profiling into one tool. `zprof` is the default allocator profiler, not a substitute for CPU sampling.281- If the repo already ships benchmark or perf steps, run those before adding new instrumentation so you preserve the existing proof surface.282283### `zprof` default lane284Deep-research default: prefer [`zprof`](https://github.com/ANDRVV/zprof) first for allocator-focused profiling. As of 2026-04-04, upstream `v3.0.1` is current, `build.zig.zon` declares `minimum_zig_version = "0.15.1"`, and the library wraps any Zig allocator while exposing `allocated`, `alloc_count`, `free_count`, `live_peak`, `live_bytes`, plus `hasLeaks()` and `reset()`.285286Installation path to encourage by default:287288```bash289# Verify if a newer upstream tag exists when freshness matters.290gh api repos/ANDRVV/zprof/releases/latest --jq '{tag_name,published_at,html_url}'291292# Current researched example293zig fetch --save https://github.com/ANDRVV/zprof/archive/v3.0.1.zip294```295296`build.zig` wiring:297298```zig299const zprof_dep = b.dependency("zprof", .{300 .target = target,301 .optimize = optimize,302});303exe.root_module.addImport("zprof", zprof_dep.module("zprof"));304```305306Minimal usage pattern:307308```zig309const std = @import("std");310const Zprof = @import("zprof").Zprof;311312pub fn main() !void {313 var gpa = std.heap.GeneralPurposeAllocator(.{}){};314 defer _ = gpa.deinit();315316 var prof: Zprof(.{}) = .init(gpa.allocator(), undefined);317 const allocator = prof.allocator();318319 const buf = try allocator.alloc(u8, 4096);320 defer allocator.free(buf);321322 std.debug.print(323 "allocs={d} frees={d} live={d} peak={d}\n",324 .{325 prof.profiler.alloc_count.get(),326 prof.profiler.free_count.get(),327 prof.profiler.live_bytes.get(),328 prof.profiler.live_peak.get(),329 },330 );331}332```333334`zprof` operating rules:335- Wrap the allocator boundary closest to the suspected waste so the counters stay local and explanatory.336- Keep workload, dataset, optimize mode, and child allocator identical across baseline and variant runs.337- Use `prof.profiler.reset()` between phases if one process captures multiple steps.338- Enable `.thread_safe = true` only when multiple threads truly share the wrapped allocator; otherwise prefer one profiler per thread or per subsystem to keep attribution sharp.339- Disable unneeded counters when chasing a narrow question so overhead stays low and the signal is easier to read.340- Pair `zprof` with ordinary benchmarks. Allocator counters can explain why a variant regressed, but they do not prove the user-visible delta by themselves.341- Pair `zprof` with correctness gates. A lower allocation count is not a win if it changes semantics or hides a leak elsewhere.342343### CPU and telemetry lane344- If `zprof` counters stay flat while wall time regresses, switch to a CPU sampler instead of guessing.345- On Linux, prefer `perf record` and `perf report` for sampled call stacks on optimized binaries.346- On macOS, prefer Instruments Time Profiler for sampled CPU hotspots.347- For long-running concurrent or frame-oriented systems where time-series causality matters, consider Tracy if the repo already supports it or the user explicitly wants instrumentation. Tracy is strong for CPU, locks, context switches, and telemetry; it is not the first move for a small allocator-only question.348- Do not add heavy profiler integrations before a smaller benchmark or allocator-profile pass rules them in.349350## Performance lane and `$lift` handoff351Use this lane when the request is about speed, latency, throughput, memory, or profiling.352353- If you can run a workload: produce baseline + after numbers and bottleneck evidence.354- If you cannot run a workload: mark output `UNMEASURED` and provide exact commands.355- Keep correctness gates before and after each performance change.356- Pick the profiling stack deliberately: benchmark for proof, `zprof` for allocator questions, CPU samplers for hot-path localization, and telemetry tools only when the workload shape justifies them.357- Use statistical discipline for small deltas: fixed dataset, warmups, repeated fresh-process runs, and sample counts. If the delta is small enough to be noisy, report an effect size or interval, not just one median.358- Prefer causal or progress-point instrumentation when the workload is concurrent or pipeline-shaped and flat hotspots are misleading.359- Optimize in order: algorithm -> data layout -> zero-copy/batching -> vectorization -> threading -> micro-tuning.360- Keep scalar fallbacks and differential tests after vectorization or threading changes.361- For large hot Linux binaries, consider a post-link layout or PGO/BOLT-style lane before invasive source rewrites.362363### Minimal measured loop364```bash365# 1) correctness first366zig build test367368# 2) safe native build for reference behavior369zig build -Doptimize=ReleaseSafe -Dtarget=native -Dcpu=native370371# 3) fast native build for perf work372zig build -Doptimize=ReleaseFast -Dtarget=native -Dcpu=native373374# 4) if the repo exposes a benchmark step, prefer it375zig build bench -- --samples 10 --warmup 3376377# 5) allocator-focused lane when bytes or leaks are the question378gh api repos/ANDRVV/zprof/releases/latest --jq '{tag_name,published_at,html_url}'379```380381When the request is a broader perf pass with explicit reporting format, apply `$lift` conventions (contract -> baseline -> bottleneck -> experiments -> result -> regression guard).382383### Benchmark decomposition rule384Use this when a Zig perf regression crosses abstraction layers and one aggregate benchmark is not enough to localize the cost.385386- Keep the same dataset, warmup, sample count, checksum, allocator, and optimize mode across all lanes.387- Add 2-4 lanes inside the same harness to isolate:388 - substrate only389 - wrapper or shell only390 - full path391- Name the lanes concretely (for example `raw_reset_only`, `effect_passthrough`, `full_raw`, `full_effect`) and keep their output adjacent so the subtraction is obvious.392- Optimize from the decomposed result, not from the aggregate delta alone.393- Once the bottleneck is understood, either keep the extra lanes as regression guards or gate them behind an explicit benchmark mode; do not leave noisy one-off instrumentation in the default path without a reason.394- When a pipeline exposes throughput or latency progress points, keep them stable across baseline and variant runs.395396## SIMD and threading policy397- Use SIMD or threading only when profiling shows a CPU-bound hot path.398- Keep scalar fallback paths and deterministic behavior.399- Avoid hidden allocations or synchronization inside hot loops.400- Re-run fuzz, differential tests, and any schedule-sensitive tests after vectorization or parallelization changes.401402## macOS fuzz caveat403`zig build test --fuzz` may fail on macOS (`InvalidElfMagic`) in Zig 0.15.2.404405If this occurs:406- State the local limitation explicitly.407- Keep `std.testing.fuzz` targets in-tree.408- Run fuzz on Linux/CI or an external harness.409- Add deterministic regression seeds under `testdata/fuzz/`.410411## Trigger-audit workflow (`$seq` backed)412Use this to measure whether Zig intent is being routed to `$zig`.413414```bash415# Run audit on full history416uv run python codex/skills/zig/scripts/zig_trigger_audit.py --root ~/.codex/sessions417418# Time-windowed audit419uv run python codex/skills/zig/scripts/zig_trigger_audit.py \420 --root ~/.codex/sessions \421 --since 2026-02-01T00:00:00Z \422 --strict-implicit \423 --format json \424 --output /tmp/zig-audit.json425426# Regression tests for the audit script427uv run python codex/skills/zig/scripts/test_zig_trigger_audit.py428```429430Notes:431- The audit uses literal `contains` matching to avoid regex parser limitations in `seq` for dotted literals (for example `.zig`, `build.zig`, `std.simd`).432- Keep strict-implicit mode enabled when evaluating precision-sensitive routing changes.433- Lint and FFI cues (`zig build lint`, `zlinter`, `extern fn`, `linkSystemLibrary`, `linkLibC`) are tracked as Zig intent.434435## Monthly drift scorecard436Generate a compact scorecard that combines:437- `$zig` SKILL size.438- Trigger-audit counts/rates.439- Routing-gap invoked-rate metrics for safe Zig cues.440- Recommendation hints when drift is detected.441442```bash443uv run python codex/skills/zig/scripts/zig_ops_scorecard.py \444 --root ~/.codex/sessions \445 --since 2026-02-01T00:00:00Z \446 --format text447448# Regression tests for the scorecard script449uv run python codex/skills/zig/scripts/test_zig_ops_scorecard.py450```451452Notes:453- The scorecard should tolerate local `seq routing-gap` builds that do not yet support `--since`.454455## `skills-zig` evidence lane456When validating guidance against current Zig production patterns, inspect:457- Source/build/release repo: `/Users/tk/workspace/tk/skills-zig`458- Formula propagation repo: `/Users/tk/workspace/tk/homebrew-tap`459460Recommended checks:461```bash462git -C /Users/tk/workspace/tk/skills-zig log --oneline --max-count=30463rg -n "std.testing.fuzz|checkAllAllocationFailures|FailingAllocator" /Users/tk/workspace/tk/skills-zig/apps -g"*.zig"464rg -n "extern fn|linkSystemLibrary|linkLibC|sqlite3_" /Users/tk/workspace/tk/skills-zig/apps /Users/tk/workspace/tk/skills-zig/build.zig -g"*.zig"465rg -n "std.atomic|compareExchange|fetchAdd|Thread|spawn" /Users/tk/workspace/tk/skills-zig/apps -g"*.zig"466rg -n "std.simd|@Vector|std.Thread.Pool" /Users/tk/workspace/tk/skills-zig/apps -g"*.zig"467rg -n "perf_hub|CountingAllocator|warmup|samples" /Users/tk/workspace/tk/skills-zig/apps /Users/tk/workspace/tk/skills-zig/tools -g"*.zig"468rg -n "@typeInfo|@hasDecl|@hasField|@Type|comptime" /Users/tk/workspace/tk/skills-zig/apps -g"*.zig"469```470471Use these results to keep `$zig` guidance aligned with what is true in active Zig repos.472473## Pitfalls474- Claiming performance wins without measured baseline/after evidence.475- Treating allocator counters as a substitute for CPU hotspot data, or CPU samples as a substitute for allocation evidence.476- Treating a validated fact as a comment instead of a witness type.477- Exposing zero-copy or FFI views before validation and lifetime checks.478- Running micro-optimizations before removing algorithmic or data-movement waste.479- Skipping allocation-failure coverage in allocator-heavy code.480- Claiming lock-free or concurrent correctness from stress tests alone.481- Skipping `zig build lint` when the repo exposes it, or pretending lint passed when unavailable.482- Running `zig build lint -- --fix` on a dirty tree without reviewing the resulting diff.483- Scattering `@typeInfo` and trait-probe logic across format-specific code instead of centralizing the classifier and policy layer first.484- Treating borrowed memory as owned (or vice versa).485- Returning stack-backed slices.486- Treating URL+hash as complete supply-chain provenance.487- Assuming regex-like query patterns are portable across all tooling without validation.488489## References490- Boundary witness template: `codex/skills/zig/references/boundary_witness.zig`491- Deterministic fail-nth allocation pattern: `codex/skills/zig/references/fail_nth_alloc.zig`492- FFI contract template: `codex/skills/zig/references/ffi_contract_template.md`493- Profiling playbook: `codex/skills/zig/references/profiling_playbook.md`494- Differential fuzz template: `codex/skills/zig/references/fuzz_differential.zig`495- Type-shape dispatcher example: `codex/skills/zig/references/type_switch.zig`496- `@Type` partial-builder example: `codex/skills/zig/references/partial_type.zig`497- Derive-walk policy pipeline: `codex/skills/zig/references/derive_walk_policy.zig`498499---500> Converted and distributed by [TomeVault](https://tomevault.io/claim/tkersey) — claim your Tome and manage your conversions.501<!-- tomevault:4.0:skill_md:2026-04-11 -->